From 9dbe3f07cbf9a3d211bad1f7fc48fb7fab4cefcc Mon Sep 17 00:00:00 2001 From: HaleySchuhl Date: Thu, 22 Aug 2019 15:54:45 -0500 Subject: [PATCH 1/3] misc function improvements - Make it optional for the first segment to be classified as stem regardless in the segment_sort function - Update method for sorting through segments in the segment_insertion_angle function, makes it quicker and more robust to measuring every leaf. - Update plotting method for segment_id since the previous method can give a weird artifact when plotting segments that have been combined. - Update pruning function - Removing old warnings that aren't really relevant anymore - --- docs/segment_sort.md | 7 +++--- .../plantcv/morphology/_iterative_prune.py | 2 -- plantcv/plantcv/morphology/prune.py | 7 +++--- plantcv/plantcv/morphology/segment_id.py | 2 +- .../morphology/segment_insertion_angle.py | 23 ++++--------------- plantcv/plantcv/morphology/segment_sort.py | 9 ++++---- 6 files changed, 17 insertions(+), 33 deletions(-) diff --git a/docs/segment_sort.md b/docs/segment_sort.md index f81fb6224..a0557b897 100644 --- a/docs/segment_sort.md +++ b/docs/segment_sort.md @@ -2,7 +2,7 @@ Sort segments from a skeletonized image into two categories: leaf objects and other objects. -**plantcv.morphology.segment_sort**(*skel_img, objects, mask=None*) +**plantcv.morphology.segment_sort**(*skel_img, objects, mask=None, first_stem=True*) **returns** Secondary objects, primary objects @@ -10,6 +10,7 @@ Sort segments from a skeletonized image into two categories: leaf objects and ot - skel_img - Skeleton image (output from [plantcv.morphology.skeletonize](skeletonize.md)) - objects - Segment objects (output from [plantcv.morphology.prune](prune.md), or [plantcv.morphology.segment_skeleton](segment_skeleton.md)) - mask - Binary mask for debugging. If provided, debug image will be overlaid on the mask. + - first_stem - When True, the first segment (the bottom segment) gets classified as stem. If False, then the algorithm classification is applied to each segment. - **Context:** - Sorts skeleton segments into two categories: primary and secondary segments. Segments get classified as primary if both end points of the segment coincide with branch points. Segments get classified as secondary if at least one of their @@ -34,12 +35,10 @@ pcv.params.debug = "print" pcv.params.line_thickness = 3 leaf_obj, other_obj = pcv.morphology.segment_sort(skel_img=skeleton, - objects=obj, - hierarchies=hier) + objects=obj) leaf_obj, other_obj = pcv.morphology.segment_sort(skel_img=skeleton, objects=obj, - hierarchies=hier, mask=plant_mask) ``` diff --git a/plantcv/plantcv/morphology/_iterative_prune.py b/plantcv/plantcv/morphology/_iterative_prune.py index 87083ee12..432f1bb0d 100644 --- a/plantcv/plantcv/morphology/_iterative_prune.py +++ b/plantcv/plantcv/morphology/_iterative_prune.py @@ -26,8 +26,6 @@ def _iterative_prune(skel_img, size): # Check to see if the skeleton has multiple objects objects, _ = find_objects(pruned_img, pruned_img) - if not len(objects) == 1: - print("Warning: Multiple objects detected! Pruning will further separate the difference pieces.") # Iteratively remove endpoints (tips) from a skeleton for i in range(0, size): diff --git a/plantcv/plantcv/morphology/prune.py b/plantcv/plantcv/morphology/prune.py index 24fdc7035..2ac3b4f00 100644 --- a/plantcv/plantcv/morphology/prune.py +++ b/plantcv/plantcv/morphology/prune.py @@ -8,9 +8,9 @@ from plantcv.plantcv import print_image from plantcv.plantcv import find_objects from plantcv.plantcv import image_subtract -from plantcv.plantcv.morphology import find_tips from plantcv.plantcv.morphology import segment_sort from plantcv.plantcv.morphology import segment_skeleton +from plantcv.plantcv.morphology import _iterative_prune def prune(skel_img, size=0, mask=None): @@ -46,8 +46,6 @@ def prune(skel_img, size=0, mask=None): # Check to see if the skeleton has multiple objects skel_objects, _ = find_objects(skel_img, skel_img) - if not len(skel_objects) == 1: - print("Warning: Multiple objects detected! Pruning will further separate the difference pieces.") _, objects = segment_skeleton(skel_img) kept_segments = [] @@ -56,7 +54,6 @@ def prune(skel_img, size=0, mask=None): if size>0: # If size>0 then check for segments that are smaller than size pixels long - # Sort through segments since we don't want to remove primary segments secondary_objects, primary_objects = segment_sort(skel_img, objects) @@ -71,8 +68,10 @@ def prune(skel_img, size=0, mask=None): removed_barbs = np.zeros(skel_img.shape[:2], np.uint8) cv2.drawContours(removed_barbs, removed_segments, -1, 255, 1, lineType=8) + # Subtract all short segments from the skeleton image pruned_img = image_subtract(pruned_img, removed_barbs) + pruned_img = _iterative_prune(pruned_img, 1) # Make debugging image if mask is None: diff --git a/plantcv/plantcv/morphology/segment_id.py b/plantcv/plantcv/morphology/segment_id.py index 005d97c64..d8d065c63 100644 --- a/plantcv/plantcv/morphology/segment_id.py +++ b/plantcv/plantcv/morphology/segment_id.py @@ -41,7 +41,7 @@ def segment_id(skel_img, objects, mask=None): # Plot all segment contours for i, cnt in enumerate(objects): - cv2.drawContours(segmented_img, objects, i, rand_color[i], params.line_thickness, lineType=8) + cv2.drawContours(segmented_img, cnt, -1, rand_color[i], params.line_thickness, lineType=8) # Store coordinates for labels label_coord_x.append(objects[i][0][0][0]) label_coord_y.append(objects[i][0][0][1]) diff --git a/plantcv/plantcv/morphology/segment_insertion_angle.py b/plantcv/plantcv/morphology/segment_insertion_angle.py index 8c12e2dd2..e0870fb2e 100644 --- a/plantcv/plantcv/morphology/segment_insertion_angle.py +++ b/plantcv/plantcv/morphology/segment_insertion_angle.py @@ -8,6 +8,7 @@ from plantcv.plantcv import closing from plantcv.plantcv import outputs from plantcv.plantcv import plot_image +from plantcv.plantcv import logical_and from plantcv.plantcv import fatal_error from plantcv.plantcv import print_image from plantcv.plantcv import find_objects @@ -86,26 +87,12 @@ def segment_insertion_angle(skel_img, segmented_img, leaf_objects, stem_objects, # Determine if a segment is leaf end or leaf insertion segment for j, obj in enumerate(segment_end_obj): - cnt_as_tuples = [] - num_pixels = len(obj) - count = 0 - - # Turn each contour into a list of tuples (can't search for list of coords, so reformat) - while num_pixels > count: - x_coord = obj[count][0][0] - y_coord = obj[count][0][1] - cnt_as_tuples.append((x_coord, y_coord)) - count += 1 - - for tip_tups in tip_tuples: - # If a tip is inside the list of contour tuples then it is a leaf end segment - if tip_tups in cnt_as_tuples: - is_insertion_segment.append(False) - else: - is_insertion_segment.append(True) + segment_plot = np.zeros(segmented_img.shape[:2], np.uint8) + cv2.drawContours(segment_plot, obj, -1, 255, 1, lineType=8) + overlap_img = logical_and(segment_plot, tips) # If none of the tips are within a segment_end then it's an insertion segment - if all(is_insertion_segment): + if np.sum(overlap_img) == 0: insertion_segments.append(segment_end_obj[j]) insertion_hierarchies.append(segment_end_hierarchy[0][j]) diff --git a/plantcv/plantcv/morphology/segment_sort.py b/plantcv/plantcv/morphology/segment_sort.py index 06c82a877..845d6e86d 100644 --- a/plantcv/plantcv/morphology/segment_sort.py +++ b/plantcv/plantcv/morphology/segment_sort.py @@ -10,13 +10,14 @@ from plantcv.plantcv.morphology import find_tips -def segment_sort(skel_img, objects, mask=None): +def segment_sort(skel_img, objects, mask=None, first_stem=True): """ Calculate segment curvature as defined by the ratio between geodesic and euclidean distance Inputs: skel_img = Skeletonized image objects = List of contours mask = (Optional) binary mask for debugging. If provided, debug image will be overlaid on the mask. + first_stem = (Optional) if True, then the first (bottom) segment always gets classified as stem Returns: labeled_img = Segmented debugging image with lengths labeled @@ -25,8 +26,8 @@ def segment_sort(skel_img, objects, mask=None): :param skel_img: numpy.ndarray :param objects: list - :param labeled_img: numpy.ndarray :param mask: numpy.ndarray + :param first_stem: bool :return secondary_objects: list :return other_objects: list """ @@ -52,7 +53,7 @@ def segment_sort(skel_img, objects, mask=None): overlap_img = logical_and(segment_plot, tips_img) # The first contour is the base, and while it contains a tip, it isn't a leaf - if i == 0: + if i == 0 and first_stem: primary_objects.append(cnt) # Sort segments @@ -81,4 +82,4 @@ def segment_sort(skel_img, objects, mask=None): elif params.debug == 'plot': plot_image(labeled_img) - return secondary_objects, primary_objects \ No newline at end of file + return secondary_objects, primary_objects From e6daae052f0bb6a8fe0b7f926356fd6ebdda7256 Mon Sep 17 00:00:00 2001 From: HaleySchuhl Date: Thu, 22 Aug 2019 15:57:08 -0500 Subject: [PATCH 2/3] update test --- tests/tests.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/tests/tests.py b/tests/tests.py index 6128e144e..ad0d4a47e 100755 --- a/tests/tests.py +++ b/tests/tests.py @@ -3419,7 +3419,16 @@ def test_plantcv_morphology_segment_insertion_angle(): pcv.params.debug = "print" insert_angles = pcv.morphology.segment_insertion_angle(pruned, segmented_img, leaf_obj, stem_obj, 10) pcv.print_results(os.path.join(cache_dir, "results.txt")) - assert len(pcv.outputs.observations['segment_insertion_angle']['value']) == 14 + assert pcv.outputs.observations['segment_insertion_angle']['value'] == [24.97999120101794, 50.75442037373474, + 56.45078448114704, 64.19513117863062, + 45.146799092975584, 57.80220388909291, + 66.1559145648012, 77.57112958360631, + 39.245580536881675, 84.24558178912076, + 84.24558178912076, 50.75442037373474, + 26.337516798081822, 58.46112771993523, + 39.245580536881675, 28.645972294617223, + 35.371548466069214, 20.64797104069403, + 62.89851538735208] pcv.outputs.clear() From 77dc0b3b7dcd7c03a9c23c0f2c95d8f7f7ae26c6 Mon Sep 17 00:00:00 2001 From: HaleySchuhl Date: Thu, 22 Aug 2019 16:11:42 -0500 Subject: [PATCH 3/3] update init file --- plantcv/plantcv/morphology/__init__.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/plantcv/plantcv/morphology/__init__.py b/plantcv/plantcv/morphology/__init__.py index 553c04e6e..618b8e819 100644 --- a/plantcv/plantcv/morphology/__init__.py +++ b/plantcv/plantcv/morphology/__init__.py @@ -1,12 +1,12 @@ from plantcv.plantcv.morphology.find_branch_pts import find_branch_pts -from plantcv.plantcv.morphology.segment_skeleton import segment_skeleton from plantcv.plantcv.morphology.find_tips import find_tips +from plantcv.plantcv.morphology._iterative_prune import _iterative_prune +from plantcv.plantcv.morphology.segment_skeleton import segment_skeleton from plantcv.plantcv.morphology.segment_sort import segment_sort from plantcv.plantcv.morphology.prune import prune from plantcv.plantcv.morphology.skeletonize import skeletonize from plantcv.plantcv.morphology.check_cycles import check_cycles from plantcv.plantcv.morphology.segment_skeleton import segment_skeleton -from plantcv.plantcv.morphology._iterative_prune import _iterative_prune from plantcv.plantcv.morphology.segment_angle import segment_angle from plantcv.plantcv.morphology.segment_path_length import segment_path_length from plantcv.plantcv.morphology.segment_euclidean_length import segment_euclidean_length @@ -18,4 +18,4 @@ __all__ = ["find_branch_pts", "find_tips", "prune", "skeletonize", "check_cycles", "segment_skeleton", "segment_angle", "segment_path_length", "segment_euclidean_length", "segment_curvature", "segment_sort", "segment_id", - "segment_tangent_angle", "segment_insertion_angle", "segment_combine", "_iterative_prune"] + "segment_tangent_angle", "segment_insertion_angle", "segment_combine", "_iterative_prune"] \ No newline at end of file