Esempio n. 1
0
def segment_tangent_angle(segmented_img, objects, size):
    """ Find 'tangent' angles in degrees of skeleton segments. Use `size` pixels on either end of
        each segment to find a linear regression line, and calculate angle between the two lines
        drawn per segment.

        Inputs:
        segmented_img  = Segmented image to plot slope lines and intersection angles on
        objects   = List of contours
        size      = Size of ends used to calculate "tangent" lines

        Returns:
        labeled_img    = Segmented debugging image with angles labeled

        :param segmented_img: numpy.ndarray
        :param objects: list
        :param size: int
        :return labeled_img: numpy.ndarray
        """
    # Store debug
    debug = params.debug
    params.debug = None

    labeled_img = segmented_img.copy()
    intersection_angles = []
    label_coord_x = []
    label_coord_y = []

    rand_color = color_palette(len(objects))

    for i, cnt in enumerate(objects):
        find_tangents = np.zeros(segmented_img.shape[:2], np.uint8)
        cv2.drawContours(find_tangents, objects, i, 255, 1, lineType=8)
        cv2.drawContours(labeled_img, objects, i, rand_color[i], params.line_thickness, lineType=8)
        pruned_segment = _iterative_prune(find_tangents, size)
        segment_ends = find_tangents - pruned_segment
        segment_end_obj, segment_end_hierarchy = find_objects(segment_ends, segment_ends)
        slopes = []
        for j, obj in enumerate(segment_end_obj):
            # Find bounds for regression lines to get drawn
            rect = cv2.minAreaRect(cnt)
            pts = cv2.boxPoints(rect)
            df = pd.DataFrame(pts, columns=('x', 'y'))
            x_max = int(df['x'].max())
            x_min = int(df['x'].min())

            # Find line fit to each segment
            [vx, vy, x, y] = cv2.fitLine(obj, cv2.DIST_L2, 0, 0.01, 0.01)
            slope = -vy / vx
            left_list = int(((x - x_min) * slope) + y)
            right_list = int(((x - x_max) * slope) + y)
            slopes.append(slope)

            if slope > 1000000 or slope < -1000000:
                print("Slope of contour with ID#", i, "is", slope, "and cannot be plotted.")
            else:
                # Draw slope lines
                cv2.line(labeled_img, (x_max - 1, right_list), (x_min, left_list), rand_color[i], 1)

        if len(slopes) < 2:
            # If size*2>len(obj) then pruning will remove the segment completely, and
            # makes segment_end_objs contain just one contour.
            print("Size too large, contour with ID#", i, "got pruned away completely.")
            intersection_angles.append("NA")
        else:
            # Calculate intersection angles
            slope1 = slopes[0][0]
            slope2 = slopes[1][0]
            intersection_angle = _slope_to_intesect_angle(slope1, slope2)
            intersection_angles.append(intersection_angle)

        # Store coordinates for labels
        label_coord_x.append(objects[i][0][0][0])
        label_coord_y.append(objects[i][0][0][1])

    segment_ids = []
    for i, cnt in enumerate(objects):
        # Label slope lines
        w = label_coord_x[i]
        h = label_coord_y[i]
        if type(intersection_angles[i]) is str:
            text = "{}".format(intersection_angles[i])
        else:
            text = "{:.2f}".format(intersection_angles[i])
        cv2.putText(img=labeled_img, text=text, org=(w, h), fontFace=cv2.FONT_HERSHEY_SIMPLEX,
                    fontScale=params.text_size, color=(150, 150, 150), thickness=params.text_thickness)
        segment_label = "ID" + str(i)
        segment_ids.append(i)

    outputs.add_observation(variable='segment_tangent_angle', trait='segment tangent angle',
                            method='plantcv.plantcv.morphology.segment_tangent_angle', scale='degrees', datatype=list,
                            value=intersection_angles, label=segment_ids)

    # Reset debug mode
    params.debug = debug
    # Auto-increment device
    params.device += 1

    if params.debug == 'print':
        print_image(labeled_img, os.path.join(params.debug_outdir, str(params.device) + '_segment_tangent_angles.png'))
    elif params.debug == 'plot':
        plot_image(labeled_img)

    return labeled_img
def segment_insertion_angle(skel_img, segmented_img, leaf_objects, stem_objects, size):
    """ Find leaf insertion angles in degrees of skeleton segments. Fit a linear regression line to the stem.
        Use `size` pixels on  the portion of leaf next to the stem find a linear regression line,
        and calculate angle between the two lines per leaf object.

        Inputs:
        skel_img         = Skeletonized image
        segmented_img    = Segmented image to plot slope lines and intersection angles on
        leaf_objects     = List of leaf segments
        stem_objects     = List of stem segments
        size             = Size of inner leaf used to calculate slope lines

        Returns:
        labeled_img      = Debugging image with angles labeled

        :param skel_img: numpy.ndarray
        :param segmented_img: numpy.ndarray
        :param leaf_objects: list
        :param stem_objects: list
        :param size: int
        :return labeled_img: numpy.ndarray
        """

    # Store debug
    debug = params.debug
    params.debug = None

    rows,cols = segmented_img.shape[:2]
    labeled_img = segmented_img.copy()
    segment_slopes = []
    insertion_segments = []
    insertion_hierarchies = []
    intersection_angles = []
    label_coord_x = []
    label_coord_y = []
    valid_segment = []

    # Create a list of tip tuples to use for sorting
    tips = find_tips(skel_img)
    tips = dilate(tips, 3, 1)
    tip_objects, tip_hierarchies = find_objects(tips, tips)
    tip_tuples = []
    for i, cnt in enumerate(tip_objects):
        tip_tuples.append((cnt[0][0][0], cnt[0][0][1]))

    rand_color = color_palette(len(leaf_objects))

    for i, cnt in enumerate(leaf_objects):
        # Draw leaf objects
        find_segment_tangents = np.zeros(segmented_img.shape[:2], np.uint8)
        cv2.drawContours(find_segment_tangents, leaf_objects, i, 255, 1, lineType=8)

        # Prune back ends of leaves
        pruned_segment = _iterative_prune(find_segment_tangents, size)

        # Segment ends are the portions pruned off
        segment_ends = find_segment_tangents - pruned_segment
        segment_end_obj, segment_end_hierarchy = find_objects(segment_ends, segment_ends)
        is_insertion_segment = []

        if not len(segment_end_obj) == 2:
            print("Size too large, contour with ID#", i, "got pruned away completely.")
        else:
            # The contour can have insertion angle calculated
            valid_segment.append(cnt)

            # Determine if a segment is leaf end or leaf insertion segment
            for j, obj in enumerate(segment_end_obj):

                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 np.sum(overlap_img) == 0:
                    insertion_segments.append(segment_end_obj[j])
                    insertion_hierarchies.append(segment_end_hierarchy[0][j])

            # Store coordinates for labels
            label_coord_x.append(leaf_objects[i][0][0][0])
            label_coord_y.append(leaf_objects[i][0][0][1])

    rand_color = color_palette(len(valid_segment))

    for i, cnt in enumerate(valid_segment):
        cv2.drawContours(labeled_img, valid_segment, i, rand_color[i], params.line_thickness, lineType=8)

    # Plot stem segments
    stem_img = np.zeros(segmented_img.shape[:2], np.uint8)
    cv2.drawContours(stem_img, stem_objects, -1, 255, 2, lineType=8)
    branch_pts = find_branch_pts(skel_img)
    stem_img = stem_img + branch_pts
    stem_img = closing(stem_img)
    combined_stem, combined_stem_hier = find_objects(stem_img, stem_img)

    # Make sure stem objects are a single contour
    loop_count=0
    while len(combined_stem) > 1 and loop_count<50:
        loop_count += 1
        stem_img = dilate(stem_img, 2, 1)
        stem_img = closing(stem_img)
        combined_stem, combined_stem_hier = find_objects(stem_img, stem_img)

    if loop_count == 50:
        fatal_error('Unable to combine stem objects.')

    # Find slope of the stem
    [vx, vy, x, y] = cv2.fitLine(combined_stem[0], cv2.DIST_L2, 0, 0.01, 0.01)
    stem_slope = -vy / vx
    stem_slope = stem_slope[0]
    lefty = int((-x * vy / vx) + y)
    righty = int(((cols - x) * vy / vx) + y)
    cv2.line(labeled_img, (cols - 1, righty), (0, lefty), (150, 150, 150), 3)

    rand_color = color_palette(len(insertion_segments))

    for t, segment in enumerate(insertion_segments):
        # Find line fit to each segment
        [vx, vy, x, y] = cv2.fitLine(segment, cv2.DIST_L2, 0, 0.01, 0.01)
        slope = -vy / vx
        left_list = int((-x * vy / vx) + y)
        right_list = int(((cols - x) * vy / vx) + y)
        segment_slopes.append(slope[0])

        # Draw slope lines if possible
        if slope > 1000000 or slope < -1000000:
            print("Slope of contour with ID#", t, "is", slope, "and cannot be plotted.")
        else:
            cv2.line(labeled_img, (cols - 1, right_list), (0, left_list), rand_color[t], 1)

        # Store intersection angles between insertion segment and stem line
        intersection_angle = _slope_to_intesect_angle(slope[0], stem_slope)
        # Function measures clockwise but we want the acute angle between stem and leaf insertion
        if intersection_angle > 90:
            intersection_angle = 180 - intersection_angle
        intersection_angles.append(intersection_angle)

    segment_ids = []

    for i, cnt in enumerate(insertion_segments):
        # Label slope lines
        w = label_coord_x[i]
        h = label_coord_y[i]
        text = "{:.2f}".format(intersection_angles[i])
        cv2.putText(img=labeled_img, text=text, org=(w, h), fontFace=cv2.FONT_HERSHEY_SIMPLEX,
                    fontScale=params.text_size, color=(150, 150, 150), thickness=params.text_thickness)
        segment_label = "ID" + str(i)
        segment_ids.append(i)

    outputs.add_observation(variable='segment_insertion_angle', trait='segment insertion angle',
                            method='plantcv.plantcv.morphology.segment_insertion_angle', scale='degrees', datatype=list,
                            value=intersection_angles, label=segment_ids)

    # Reset debug mode
    params.debug = debug
    # Auto-increment device
    params.device += 1

    if params.debug == 'print':
        print_image(labeled_img,
                    os.path.join(params.debug_outdir, str(params.device) + '_segment_insertion_angles.png'))
    elif params.debug == 'plot':
        plot_image(labeled_img)

    return labeled_img
Esempio n. 3
0
def prune(skel_img, size=0, mask=None):
    """
    The pruning algorithm proposed by https://github.com/karnoldbio
    Segments a skeleton into discrete pieces, prunes off all segments less than or
    equal to user specified size. Returns the remaining objects as a list and the
    pruned skeleton.

    Inputs:
    skel_img    = Skeletonized image
    size        = Size to get pruned off each branch
    mask        = (Optional) binary mask for debugging. If provided, debug image will be overlaid on the mask.

    Returns:
    pruned_img      = Pruned image
    segmented_img   = Segmented debugging image
    segment_objects = List of contours

    :param skel_img: numpy.ndarray
    :param size: int
    :param mask: numpy.ndarray
    :return pruned_img: numpy.ndarray
    :return segmented_img: numpy.ndarray
    :return segment_objects: list

    """
    # Store debug
    debug = params.debug
    params.debug = None

    pruned_img = skel_img.copy()

    # Check to see if the skeleton has multiple objects
    skel_objects, _ = find_objects(skel_img, skel_img)

    _, objects = segment_skeleton(skel_img)
    kept_segments = []
    removed_segments = []

    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)

        # Keep segments longer than specified size
        for i in range(0, len(secondary_objects)):
            if len(secondary_objects[i]) > size:
                kept_segments.append(secondary_objects[i])
            else:
                removed_segments.append(secondary_objects[i])

        # Draw the contours that got removed
        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)

    # Reset debug mode
    params.debug = debug

    # Make debugging image
    if mask is None:
        pruned_plot = np.zeros(skel_img.shape[:2], np.uint8)
    else:
        pruned_plot = mask.copy()
    pruned_plot = cv2.cvtColor(pruned_plot, cv2.COLOR_GRAY2RGB)
    pruned_obj, pruned_hierarchy = find_objects(pruned_img, pruned_img)
    cv2.drawContours(pruned_plot,
                     removed_segments,
                     -1, (0, 0, 255),
                     params.line_thickness,
                     lineType=8)
    cv2.drawContours(pruned_plot,
                     pruned_obj,
                     -1, (150, 150, 150),
                     params.line_thickness,
                     lineType=8)

    # Auto-increment device
    params.device += 1

    if params.debug == 'print':
        print_image(
            pruned_img,
            os.path.join(params.debug_outdir,
                         str(params.device) + '_pruned.png'))
        print_image(
            pruned_plot,
            os.path.join(params.debug_outdir,
                         str(params.device) + '_pruned_debug.png'))

    elif params.debug == 'plot':
        plot_image(pruned_img, cmap='gray')
        plot_image(pruned_plot)

    # Segment the pruned skeleton
    segmented_img, segment_objects = segment_skeleton(pruned_img, mask)

    return pruned_img, segmented_img, segment_objects