danforthcenter / plantcv

Plant phenotyping with image analysis
Mozilla Public License 2.0
667 stars 265 forks source link

`pcv.roi.filter` plotting bugfix within `roi_type = "largest"` functionality #1520

Closed HaleySchuhl closed 6 months ago

HaleySchuhl commented 6 months ago

Describe your changes Previously we were manually checking for hierarchy of contours and then we would draw the parent contour white, and carve out the child contour shape with black by plotting the first level hierarchy. This means that child of children contours are lost, plus we noticed a bug where the shape of the child contour is slightly enlarged by re-drawing it this way, regardless of settings to cv2.drawContours it still seemed like there was a line thickness that would change the result of cv2.countNonZero(filtered_mask) and we don't want to change the shape/size of masks by redrawing them. Instead, now we use the maxDepth=2 setting within drawing, provide the function all filtered contours, and just provide the contourIdx of the contour we ID as being the largest after ROI filtering. Removes the logic which had been replicating opencv contour and hierarchy datatype syntax.

Also edit to the unit test which is what brought my attention to this bugfix. Now assert that the pixel count of the largest shape is the same pre & post filtering, and add a secondary small object to get filtered out.

Type of update Is this a:

Associated issues

For the reviewer See this page for instructions on how to review the pull request.

Test on simulated data to see the difference before & after

from plantcv.plantcv import Objects
import numpy as np
import cv2
from plantcv import plantcv as pcv

"""Test for PlantCV."""
# Create test data
img = np.zeros((100, 100), dtype=np.uint8)
mask = np.zeros(np.shape(img)[:2], dtype=np.uint8)
cnt = [np.array([[[25, 25]], [[25, 49]], [[49, 49]], [[49, 25]]], dtype=np.int32),
       np.array([[[34, 35]], [[35, 34]], [[39, 34]], [[40, 35]], [[40, 39]], [[39, 40]], [[35, 40]], [[34, 39]]],
                dtype=np.int32)]
cnt_str = np.array([[[-1, -1,  1, -1], [-1, -1, -1,  0]]], dtype=np.int32)
cv2.drawContours(mask, cnt, -1, (255), -1, lineType=8, hierarchy=cnt_str)
pcv.plot_image(mask)

# Add small square that should get filtered out
mask = cv2.rectangle(mask, (5,5), (7,7), 255, -1)
pcv.plot_image(mask)
print(cv2.countNonZero(mask))

area_total = cv2.countNonZero(mask)

pcv.params.debug = "plot"
roi_r = pcv.roi.rectangle(mask, x=0, y=0, h=99, w=99)
roi = [np.array([[[0, 0]], [[0, 99]], [[99, 99]], [[99, 0]]], dtype=np.int32)]
roi_str = np.array([[[-1, -1, -1, -1]]], dtype=np.int32)
roi_Obj = Objects(contours=[roi], hierarchy=[roi_str])
filtered_mask = pcv.roi.filter(mask=mask, roi=roi_Obj, roi_type="largest")
print(cv2.countNonZero(filtered_mask))
deepsource-io[bot] commented 6 months ago

Here's the code health analysis summary for commits 8e34132..559d57c. View details on DeepSource ↗.

Analysis Summary

AnalyzerStatusSummaryLink
DeepSource Python LogoPython✅ SuccessView Check ↗
DeepSource Test coverage LogoTest coverage✅ SuccessView Check ↗

Code Coverage Report

MetricAggregatePython
Branch Coverage100%100%
Composite Coverage99.7%99.7%
Line Coverage99.7%99.7%
New Branch Coverage100%100%
New Composite Coverage100%100%
New Line Coverage100%, ✅ Above Threshold100%, ✅ Above Threshold

💡 If you’re a repository administrator, you can configure the quality gates from the settings.
nfahlgren commented 6 months ago

Changed merged in previous PR