Пример #1
0
def prune(skel_img, size):
    """
    The pruning algorithm was inspired by Jean-Patrick Pommier: https://gist.github.com/jeanpat/5712699
    Iteratively remove endpoints (tips) from a skeletonized image. "Prunes" barbs off a skeleton.

    Inputs:
    skel_img    = Skeletonized image
    size        = Size to get pruned off each branch

    Returns:
    pruned_img  = Pruned image

    :param skel_img: numpy.ndarray
    :param size: int
    :return pruned_img: numpy.ndarray

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

    pruned_img = skel_img.copy()

    # 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):
        endpoints = find_tips(pruned_img)
        pruned_img = image_subtract(pruned_img, endpoints)

    # Make debugging image
    pruned_plot = np.zeros(skel_img.shape[:2], np.uint8)
    pruned_plot = cv2.cvtColor(pruned_plot, cv2.COLOR_GRAY2RGB)
    skel_obj, skel_hierarchy = find_objects(skel_img, skel_img)
    pruned_obj, pruned_hierarchy = find_objects(pruned_img, pruned_img)
    cv2.drawContours(pruned_plot, skel_obj, -1, (0, 0, 255), params.line_thickness,
                     lineType=8, hierarchy=skel_hierarchy)
    cv2.drawContours(pruned_plot, pruned_obj, -1, (255, 255, 255), params.line_thickness,
                     lineType=8, hierarchy=pruned_hierarchy)

    # Reset debug mode
    params.debug = debug

    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)

    return pruned_img
def _iterative_prune(skel_img, size):
    """
    The pruning algorithm was inspired by Jean-Patrick Pommier: https://gist.github.com/jeanpat/5712699
    Iteratively remove endpoints (tips) from a skeletonized image. "Prunes" barbs off a skeleton.
    Inputs:
    skel_img    = Skeletonized image
    size        = Size to get pruned off each branch
    Returns:
    pruned_img  = Pruned image
    :param skel_img: numpy.ndarray
    :param size: int
    :return pruned_img: numpy.ndarray
    """
    pruned_img = skel_img.copy()
    # Store debug
    debug = params.debug
    params.debug = None

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

    # Iteratively remove endpoints (tips) from a skeleton
    for i in range(0, size):
        endpoints = find_tips(pruned_img)
        pruned_img = image_subtract(pruned_img, endpoints)

    # Make debugging image
    pruned_plot = np.zeros(skel_img.shape[:2], np.uint8)
    pruned_plot = cv2.cvtColor(pruned_plot, cv2.COLOR_GRAY2RGB)
    skel_obj, skel_hierarchy = find_objects(skel_img, skel_img)
    pruned_obj, pruned_hierarchy = find_objects(pruned_img, pruned_img)

    # Reset debug mode
    params.debug = debug

    cv2.drawContours(pruned_plot,
                     skel_obj,
                     -1, (0, 0, 255),
                     params.line_thickness,
                     lineType=8,
                     hierarchy=skel_hierarchy)
    cv2.drawContours(pruned_plot,
                     pruned_obj,
                     -1, (255, 255, 255),
                     params.line_thickness,
                     lineType=8,
                     hierarchy=pruned_hierarchy)

    return pruned_img
 def process_pot(self, pot_image):
     device = 0
     # debug=None
     updated_pot_image = self.threshold_green(pot_image)
     # plt.imshow(updated_pot_image)
     # plt.show()
     device, a = pcv.rgb2gray_lab(updated_pot_image, 'a', device)
     device, img_binary = pcv.binary_threshold(a, 127, 255, 'dark', device,
                                               None)
     # plt.imshow(img_binary)
     # plt.show()
     mask = np.copy(img_binary)
     device, fill_image = pcv.fill(img_binary, mask, 50, device)
     device, dilated = pcv.dilate(fill_image, 1, 1, device)
     device, id_objects, obj_hierarchy = pcv.find_objects(
         updated_pot_image, updated_pot_image, device)
     device, roi1, roi_hierarchy = pcv.define_roi(updated_pot_image,
                                                  'rectangle', device, None,
                                                  'default', debug, False)
     device, roi_objects, hierarchy3, kept_mask, obj_area = pcv.roi_objects(
         updated_pot_image, 'partial', roi1, roi_hierarchy, id_objects,
         obj_hierarchy, device, debug)
     device, obj, mask = pcv.object_composition(updated_pot_image,
                                                roi_objects, hierarchy3,
                                                device, debug)
     device, shape_header, shape_data, shape_img = pcv.analyze_object(
         updated_pot_image, "Example1", obj, mask, device, debug, False)
     print(shape_data[1])
def main(path, imagename):
    args = {'names': 'names.txt', 'outdir': './output-images'}
    #Read image
    img1, path, filename = pcv.readimage(path + imagename, "native")
    #pcv.params.debug=args['debug']
    #img1 = pcv.white_balance(img,roi=(400,800,200,200))
    #img1 = cv2.resize(img1,(4000,2000))
    shift1 = pcv.shift_img(img1, 10, 'top')
    img1 = shift1
    a = pcv.rgb2gray_lab(img1, 'a')
    img_binary = pcv.threshold.binary(a, 120, 255, 'dark')
    fill_image = pcv.fill(img_binary, 10)
    dilated = pcv.dilate(fill_image, 1, 1)
    id_objects, obj_hierarchy = pcv.find_objects(img1, dilated)
    roi_contour, roi_hierarchy = pcv.roi.rectangle(4000, 2000, -2000, -4000,
                                                   img1)
    #print(roi_contour)
    roi_objects, roi_obj_hierarchy, kept_mask, obj_area = pcv.roi_objects(
        img1, 'partial', roi_contour, roi_hierarchy, id_objects, obj_hierarchy)
    clusters_i, contours, hierarchies = pcv.cluster_contours(
        img1, roi_objects, roi_obj_hierarchy, 1, 4)
    '''
	pcv.params.debug = "print"'''
    out = args['outdir']
    names = args['names']
    output_path = pcv.cluster_contour_splitimg(img1,
                                               clusters_i,
                                               contours,
                                               hierarchies,
                                               out,
                                               file=filename,
                                               filenames=names)
Пример #5
0
def get_plant_object(image, mask):
    '''
    Use the mask information to filter out the plant object
    '''

    id_objects, obj_heirachy = pcv.find_objects(image, mask)

    return id_objects, obj_heirachy
Пример #6
0
def segment_skeleton(skel_img, mask=None):
    """ Segment a skeleton image into pieces

        Inputs:
        skel_img      = Skeletonized image
        mask          = (Optional) binary mask for debugging. If provided, debug image will be overlaid on the mask.

        Returns:
        segmented_img = Segmented debugging image
        objects       = list of contours
        hierarchy     = contour hierarchy list

        :param skel_img: numpy.ndarray
        :param mask: numpy.ndarray
        :return segmented_img: numpy.ndarray
        :return segment_objects: list
        "return segment_hierarchies: numpy.ndarray
        """

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

    # Find branch points
    bp = find_branch_pts(skel_img)
    bp = dilate(bp, 3, 1)

    # Subtract from the skeleton so that leaves are no longer connected
    segments = image_subtract(skel_img, bp)

    # Gather contours of leaves
    segment_objects, _ = find_objects(segments, segments)

    # Color each segment a different color
    rand_color = color_palette(len(segment_objects))

    if mask is None:
        segmented_img = skel_img.copy()
    else:
        segmented_img = mask.copy()

    segmented_img = cv2.cvtColor(segmented_img, cv2.COLOR_GRAY2RGB)
    for i, cnt in enumerate(segment_objects):
        cv2.drawContours(segmented_img, segment_objects, i, rand_color[i], params.line_thickness, lineType=8)

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

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

    return segmented_img, segment_objects
Пример #7
0
def main():
    # Get options
    args = options()

    debug = args.debug

    # Read image
    img, path, filename = pcv.readimage(args.image)

    # Pipeline step
    device = 0

    device, corrected_img = pcv.white_balance(device, img, debug,
                                              (500, 1000, 500, 500))
    img = corrected_img

    device, img_gray_sat = pcv.rgb2gray_lab(img, 'a', device, debug)

    device, img_binary = pcv.binary_threshold(img_gray_sat, 120, 255, 'dark',
                                              device, debug)

    mask = np.copy(img_binary)
    device, fill_image = pcv.fill(img_binary, mask, 300, device, debug)

    device, id_objects, obj_hierarchy = pcv.find_objects(
        img, fill_image, device, debug)

    device, roi, roi_hierarchy = pcv.define_roi(img, 'rectangle', device, None,
                                                'default', debug, True, 1800,
                                                1600, -1500, -500)

    device, roi_objects, roi_obj_hierarchy, kept_mask, obj_area = pcv.roi_objects(
        img, 'partial', roi, roi_hierarchy, id_objects, obj_hierarchy, device,
        debug)

    device, obj, mask = pcv.object_composition(img, roi_objects,
                                               roi_obj_hierarchy, device,
                                               debug)

    outfile = os.path.join(args.outdir, filename)

    device, color_header, color_data, color_img = pcv.analyze_color(
        img, img, mask, 256, device, debug, None, 'v', 'img', 300, outfile)

    device, shape_header, shape_data, shape_img = pcv.analyze_object(
        img, "img", obj, mask, device, debug, outfile)

    shapepath = outfile[:-4] + '_shapes.jpg'
    shapepic = cv2.imread(shapepath)
    plantsize = "The plant is " + str(np.sum(mask)) + " pixels large"
    cv2.putText(shapepic, plantsize, (500, 500), cv2.FONT_HERSHEY_SIMPLEX, 5,
                (0, 255, 0), 10)
    pcv.print_image(shapepic, outfile[:-4] + '-out_shapes.jpg')
Пример #8
0
def main():
    #Menangkap gambar IP Cam dengan opencv
    ##Ngosek, koding e wis ono tapi carane ngonek ning gcloud rung reti wkwk

    #Mengambil gambar yang sudah didapatkan dari opencv untuk diproses di plantcv
    path = 'Image test\capture (1).jpg'
    gmbTumbuhanRaw, path, filename = pcv.readimage(path, mode='native')

    #benarkan gambar yang miring
    koreksiRot = pcv.rotate(gmbTumbuhanRaw, 2, True)
    gmbKoreksi = koreksiRot
    pcv.print_image(gmbKoreksi, 'Image test\Hasil\gambar_koreksi.jpg')

    #Mengatur white balance dari gambar
    #usahakan gambar rata (tanpa bayangan dari manapun!)!
    #GANTI nilai dari region of intrest (roi) berdasarkan ukuran gambar!!
    koreksiWhiteBal = pcv.white_balance(gmbTumbuhanRaw,
                                        roi=(2, 100, 1104, 1200))
    pcv.print_image(koreksiWhiteBal, 'Image test\Hasil\koreksi_white_bal.jpg')

    #mengubah kontras gambar agar berbeda dengan warna background
    #tips: latar jangan sama hijaunya
    kontrasBG = pcv.rgb2gray_lab(koreksiWhiteBal, channel='a')
    pcv.print_image(kontrasBG, 'Image test\Hasil\koreksi_kontras.jpg')

    #binary threshol gambar
    #sesuaikan thresholdnya
    binthres = pcv.threshold.binary(gray_img=kontrasBG,
                                    threshold=115,
                                    max_value=255,
                                    object_type='dark')

    #hilangkan noise dengan fill noise
    resiksitik = pcv.fill(binthres, size=10)
    pcv.print_image(resiksitik, 'Image test\Hasil\\noiseFill.jpg')

    #haluskan dengan dilate
    dilasi = pcv.dilate(resiksitik, ksize=12, i=1)

    #ambil objek dan set besar roi
    id_objek, hirarki_objek = pcv.find_objects(gmbTumbuhanRaw, mask=dilasi)
    roi_contour, roi_hierarchy = pcv.roi.rectangle(img=gmbKoreksi,
                                                   x=20,
                                                   y=96,
                                                   h=1100,
                                                   w=680)

    #keluarkan gambar (untuk debug aja sih)
    roicontour = cv2.drawContours(gmbKoreksi, roi_contour, -1, (0, 0, 255), 3)
    pcv.print_image(roicontour, 'Image test\Hasil\\roicontour.jpg')
    """
def read_true_positive(true_positive_file):
    class args:
        #image = "C:\\Users\\RensD\\OneDrive\\studie\\Master\\The_big_project\\top_perspective\\0214_2018-03-07 08.55 - 26_true_positive.png"
        image = true_positive_file
        outdir = "C:\\Users\\RensD\\OneDrive\\studie\\Master\\The_big_project\\top_perspective\\output"
        debug = "None"
        result = "results.txt"

    # Get options
    pcv.params.debug = args.debug  #set debug mode
    pcv.params.debug_outdir = args.outdir  #set output directory

    # Read image (readimage mode defaults to native but if image is RGBA then specify mode='rgb')
    # Inputs:
    #   filename - Image file to be read in
    #   mode - Return mode of image; either 'native' (default), 'rgb', 'gray', or 'csv'
    img, path, filename = pcv.readimage(filename=args.image, mode='rgb')

    s = pcv.rgb2gray_hsv(rgb_img=img, channel='s')
    mask, masked_image = pcv.threshold.custom_range(rgb_img=s,
                                                    lower_thresh=[10],
                                                    upper_thresh=[255],
                                                    channel='gray')
    masked = pcv.apply_mask(rgb_img=img, mask=mask, mask_color='white')
    #new_im = Image.fromarray(mask)
    #name = "positive_test.png"
    #Recognizing objects
    id_objects, obj_hierarchy = pcv.find_objects(masked, mask)
    roi1, roi_hierarchy = pcv.roi.rectangle(img=masked,
                                            x=0,
                                            y=0,
                                            h=960,
                                            w=1280)  # Currently hardcoded
    with HiddenPrints():
        roi_objects, hierarchy3, kept_mask, obj_area = pcv.roi_objects(
            img=img,
            roi_contour=roi1,
            roi_hierarchy=roi_hierarchy,
            object_contour=id_objects,
            obj_hierarchy=obj_hierarchy,
            roi_type=roi_type)

    obj, mask = pcv.object_composition(img=img,
                                       contours=roi_objects,
                                       hierarchy=hierarchy3)
    #new_im.save(name)
    return (mask)
    def read_dot(self, imageread):

        img1 = imageread
        device, img1gray = pcv.rgb2gray(img1, 0)
        img1hsv = cv2.cvtColor(img1, cv2.COLOR_BGR2HSV)
        dev, img_binary = pcv.binary_threshold(img1gray, 1, 255, 'light', 0)
        device, id_objects, obj_hierarchy = pcv.find_objects(
            img1, img_binary, 0)

        device, roi, roi_hierarchy = pcv.define_roi(img1,
                                                    shape="rectangle",
                                                    device=device,
                                                    roi_input="default",
                                                    adjust=False,
                                                    x_adj=600,
                                                    y_adj=600,
                                                    w_adj=1200,
                                                    h_adj=1200)
        device, roi_objects, roi_obj_hierarchy, kept_mask, obj_area = pcv.roi_objects(
            img1, "partial", roi, roi_hierarchy, id_objects, obj_hierarchy,
            device)

        device, clusters_i, contours, obj_hierarchy = pcv.cluster_contours(
            device=device,
            img=img1,
            roi_objects=roi_objects,
            roi_obj_hierarchy=roi_obj_hierarchy,
            nrow=1,
            ncol=int(3))
        dotQ = list()
        obj_hierarchy = obj_hierarchy[0]
        for clusters1 in clusters_i:
            for contourtocheck in clusters1:
                if not obj_hierarchy[contourtocheck][2] == -1:
                    if not cv2.contourArea(
                            contours[obj_hierarchy[contourtocheck][2]]) >= 20:
                        obj_hierarchy[contourtocheck][2] = -1
        counter = 0
        for foundcontour in clusters_i:
            hierarchycontours = [obj_hierarchy[j][2] for j in foundcontour]
            if not all([bool(j == -1) for j in hierarchycontours]):
                dotQ.append(True)
            else:
                dotQ.append(False)
        return dotQ
Пример #11
0
def find_branch_pts(skel_img, mask=None):
    """
    The branching algorithm was inspired by Jean-Patrick Pommier: https://gist.github.com/jeanpat/5712699
    Inputs:
    skel_img    = Skeletonized image
    mask        = (Optional) binary mask for debugging. If provided, debug image will be overlaid on the mask.

    Returns:
    branch_pts_img = Image with just branch points, rest 0

    :param skel_img: numpy.ndarray
    :return branch_pts_img: numpy.ndarray
    """

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

    # In a kernel: 1 values line up with 255s, -1s line up with 0s, and 0s correspond to dont care
    # T like branch points
    t1 = np.array([[-1,  1, -1],
                   [ 1,  1,  1],
                   [-1, -1, -1]])
    t2 = np.array([[ 1, -1,  1],
                   [-1,  1, -1],
                   [ 1, -1, -1]])
    t3 = np.rot90(t1)
    t4 = np.rot90(t2)
    t5 = np.rot90(t3)
    t6 = np.rot90(t4)
    t7 = np.rot90(t5)
    t8 = np.rot90(t6)

    # Y like branch points
    y1 = np.array([[ 1, -1,  1],
                   [ 0,  1,  0],
                   [ 0,  1,  0]])
    y2 = np.array([[-1,  1, -1],
                   [ 1,  1,  0],
                   [-1,  0,  1]])
    y3 = np.rot90(y1)
    y4 = np.rot90(y2)
    y5 = np.rot90(y3)
    y6 = np.rot90(y4)
    y7 = np.rot90(y5)
    y8 = np.rot90(y6)
    kernels = [t1, t2, t3, t4, t5, t6, t7, t8, y1, y2, y3, y4, y5, y6, y7, y8]

    branch_pts_img = np.zeros(skel_img.shape[:2], dtype=int)

    # Store branch points
    for kernel in kernels:
        branch_pts_img = np.logical_or(cv2.morphologyEx(skel_img, op=cv2.MORPH_HITMISS, kernel=kernel,
                                                        borderType=cv2.BORDER_CONSTANT, borderValue=0), branch_pts_img)

    # Switch type to uint8 rather than bool
    branch_pts_img = branch_pts_img.astype(np.uint8) * 255

    # Make debugging image
    if mask is None:
        dilated_skel = dilate(skel_img, params.line_thickness, 1)
        branch_plot = cv2.cvtColor(dilated_skel, cv2.COLOR_GRAY2RGB)
    else:
        # Make debugging image on mask
        mask_copy = mask.copy()
        branch_plot = cv2.cvtColor(mask_copy, cv2.COLOR_GRAY2RGB)
        skel_obj, skel_hier = find_objects(skel_img, skel_img)
        cv2.drawContours(branch_plot, skel_obj, -1, (150, 150, 150), params.line_thickness, lineType=8,
                         hierarchy=skel_hier)

    branch_objects, _ = find_objects(branch_pts_img, branch_pts_img)
    for i in branch_objects:
        x, y = i.ravel()[:2]
        cv2.circle(branch_plot, (x, y), params.line_thickness, (255, 0, 255), -1)

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

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

    return branch_pts_img
Пример #12
0
#Setting threshold continued
b_cnt = pcv.threshold.binary(gray_img=b,
                             threshold=135,
                             max_value=255,
                             object_type='light')

# In[112]:

#Join the blue and yellow binary images
bs = pcv.logical_and(bin_img1=s_mblur, bin_img2=b_cnt)

masked = pcv.apply_mask(img=img1, mask=bs, mask_color='white')

#identify objects
obj2 = id_objects, obj_hierarchy = pcv.find_objects(img=masked, mask=bs)

#Define Range of Intrest
# Inputs:
#   img - RGB or grayscale image to plot the ROI on
#   x - The x-coordinate of the upper left corner of the rectangle
#   y - The y-coordinate of the upper left corner of the rectangle
#   h - The height of the rectangle
#   w - The width of the rectangle
roi1, roi_hierarchy = pcv.roi.rectangle(img=img1, x=75, y=60, h=20, w=20)

# In[113]:

# Decide which objects to keep

# Inputs:
Пример #13
0
def main():
    # Create input arguments object
    args = options()

    # Set debug mode
    pcv.params.debug = args.debug

    # Open a single image
    img, imgpath, imgname = pcv.readimage(filename=args.image)

    # Visualize colorspaces
    all_cs = pcv.visualize.colorspaces(rgb_img=img)

    # Extract the Blue-Yellow ("b") channel from the LAB colorspace
    gray_img = pcv.rgb2gray_lab(rgb_img=img, channel="b")

    # Plot a histogram of pixel values for the Blue-Yellow ("b") channel.
    hist_plot = pcv.visualize.histogram(gray_img=gray_img)

    # Apply a binary threshold to the Blue-Yellow ("b") grayscale image.
    thresh_img = pcv.threshold.binary(gray_img=gray_img,
                                      threshold=140,
                                      max_value=255,
                                      object_type="light")

    # Apply a dilation with a 5x5 kernel and 3 iterations
    dil_img = pcv.dilate(gray_img=thresh_img, ksize=5, i=3)

    # Fill in small holes in the leaves
    closed_img = pcv.fill_holes(bin_img=dil_img)

    # Erode the plant pixels using a 5x5 kernel and 3 iterations
    er_img = pcv.erode(gray_img=closed_img, ksize=5, i=3)

    # Apply a Gaussian blur with a 5 x 5 kernel.
    blur_img = pcv.gaussian_blur(img=er_img, ksize=(5, 5))

    # Set pixel values less than 255 to 0
    blur_img[np.where(blur_img < 255)] = 0

    # Fill/remove objects less than 300 pixels in area
    cleaned = pcv.fill(bin_img=blur_img, size=300)

    # Create a circular ROI
    roi, roi_str = pcv.roi.circle(img=img, x=1725, y=1155, r=400)

    # Identify objects in the binary image
    cnts, cnts_str = pcv.find_objects(img=img, mask=cleaned)

    # Filter objects by region of interest
    plant_cnt, plant_str, plant_mask, plant_area = pcv.roi_objects(
        img=img,
        roi_contour=roi,
        roi_hierarchy=roi_str,
        object_contour=cnts,
        obj_hierarchy=cnts_str)

    # Combine objects into one
    plant, mask = pcv.object_composition(img=img,
                                         contours=plant_cnt,
                                         hierarchy=plant_str)

    # Measure size and shape properties
    shape_img = pcv.analyze_object(img=img, obj=plant, mask=mask)
    if args.writeimg:
        pcv.print_image(img=shape_img,
                        filename=os.path.join(args.outdir,
                                              "shapes_" + imgname))

    # Analyze color properties
    color_img = pcv.analyze_color(rgb_img=img, mask=mask, hist_plot_type="hsv")
    if args.writeimg:
        pcv.print_image(img=color_img,
                        filename=os.path.join(args.outdir,
                                              "histogram_" + imgname))

    # Save the measurements to a file
    pcv.print_results(filename=args.result)
Пример #14
0
def segment_euclidean_length(segmented_img, objects, label="default"):
    """ Use segmented skeleton image to gather euclidean length measurements per segment

        Inputs:
        segmented_img = Segmented image to plot lengths on
        objects       = List of contours
        label         = optional label parameter, modifies the variable name of observations recorded

        Returns:
        labeled_img      = Segmented debugging image with lengths labeled

        :param segmented_img: numpy.ndarray
        :param objects: list
        :param label: str
        :return labeled_img: numpy.ndarray

        """
    x_list = []
    y_list = []
    segment_lengths = []
    # Create a color scale, use a previously stored scale if available
    rand_color = color_palette(num=len(objects), saved=True)

    labeled_img = segmented_img.copy()
    # Store debug
    debug = params.debug
    params.debug = None

    for i, cnt in enumerate(objects):
        # Store coordinates for labels
        x_list.append(objects[i][0][0][0])
        y_list.append(objects[i][0][0][1])

        # Draw segments one by one to group segment tips together
        finding_tips_img = np.zeros(segmented_img.shape[:2], np.uint8)
        cv2.drawContours(finding_tips_img,
                         objects,
                         i, (255, 255, 255),
                         1,
                         lineType=8)
        segment_tips = find_tips(finding_tips_img)
        tip_objects, tip_hierarchies = find_objects(segment_tips, segment_tips)
        points = []
        if not len(tip_objects) == 2:
            fatal_error("Too many tips found per segment, try pruning again")
        for t in tip_objects:
            # Gather pairs of coordinates
            x, y = t.ravel()
            coord = (x, y)
            points.append(coord)

        # Draw euclidean distance lines
        cv2.line(labeled_img, points[0], points[1], rand_color[i], 1)

        # Calculate euclidean distance between tips of each contour
        segment_lengths.append(float(euclidean(points[0], points[1])))

    segment_ids = []
    # Reset debug mode
    params.debug = debug

    # Put labels of length
    for c, value in enumerate(segment_lengths):
        text = "{:.2f}".format(value)
        w = x_list[c]
        h = y_list[c]
        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(c)
        segment_ids.append(c)

    outputs.add_observation(
        sample=label,
        variable='segment_eu_length',
        trait='segment euclidean length',
        method='plantcv.plantcv.morphology.segment_euclidean_length',
        scale='pixels',
        datatype=list,
        value=segment_lengths,
        label=segment_ids)

    # Auto-increment device
    params.device += 1

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

    return labeled_img
Пример #15
0
def segment_curvature(segmented_img, objects, label="default"):
    """ Calculate segment curvature as defined by the ratio between geodesic and euclidean distance.
        Measurement of two-dimensional tortuosity.

        Inputs:
        segmented_img     = Segmented image to plot lengths on
        objects           = List of contours
        label          = optional label parameter, modifies the variable name of observations recorded

        Returns:
        labeled_img        = Segmented debugging image with curvature labeled


        :param segmented_img: numpy.ndarray
        :param objects: list
        :param label: str
        :return labeled_img: numpy.ndarray

        """

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

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

    _ = segment_euclidean_length(segmented_img, objects, label="backend")
    _ = segment_path_length(segmented_img, objects, label="backend")
    eu_lengths = outputs.observations['backend']['segment_eu_length']['value']
    path_lengths = outputs.observations['backend']['segment_path_length'][
        'value']
    curvature_measure = [
        float(x / y) for x, y in zip(path_lengths, eu_lengths)
    ]
    # Create a color scale, use a previously stored scale if available
    rand_color = color_palette(num=len(objects), saved=True)

    for i, cnt in enumerate(objects):
        # Store coordinates for labels
        label_coord_x.append(objects[i][0][0][0])
        label_coord_y.append(objects[i][0][0][1])

        # Draw segments one by one to group segment tips together
        finding_tips_img = np.zeros(segmented_img.shape[:2], np.uint8)
        cv2.drawContours(finding_tips_img,
                         objects,
                         i, (255, 255, 255),
                         1,
                         lineType=8)
        segment_tips = find_tips(finding_tips_img)
        tip_objects, tip_hierarchies = find_objects(segment_tips, segment_tips)
        points = []

        for t in tip_objects:
            # Gather pairs of coordinates
            x, y = t.ravel()
            coord = (x, y)
            points.append(coord)

        # Draw euclidean distance lines
        cv2.line(labeled_img, points[0], points[1], rand_color[i], 1)

    segment_ids = []
    # Reset debug mode
    params.debug = debug

    for i, cnt in enumerate(objects):
        # Calculate geodesic distance
        text = "{:.3f}".format(curvature_measure[i])
        w = label_coord_x[i]
        h = label_coord_y[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(
        sample=label,
        variable='segment_curvature',
        trait='segment curvature',
        method='plantcv.plantcv.morphology.segment_curvature',
        scale='none',
        datatype=list,
        value=curvature_measure,
        label=segment_ids)

    # Auto-increment device
    params.device += 1

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

    return labeled_img
Пример #16
0
    green = np.zeros_like(img, np.uint8)
    green[imask] = img[imask]

    ## save
    cv2.imwrite("upload/output_imgs/Crop.png", green)
    i = i + 1
filename = pcv.readimage(filename="upload/output_imgs/Crop.png")

# In[20]:

# Identify objects

# Inputs:
#   img - RGB or grayscale image data for plotting
#   mask - Binary mask used for detecting contours
id_objects, obj_hierarchy = pcv.find_objects(img=masked2, mask=ab_fill)

# In[21]:

# Define the region of interest (ROI)

# Inputs:
#   img - RGB or grayscale image to plot the ROI on
#   x - The x-coordiate of the upper left corner of the rectangle
#   y - The y-coordinate of the upper left corner of the rectangle
#   h - The height of the rectangle
#   w - The width of the rectangle
roi1, roi_hierarchy = pcv.roi.rectangle(img=masked2,
                                        x=200,
                                        y=300,
                                        h=200,
def segment_euclidean_length(segmented_img, objects):
    """ Use segmented skeleton image to gather euclidean length measurements per segment

        Inputs:
        segmented_img = Segmented image to plot lengths on
        objects       = List of contours

        Returns:
        labeled_img      = Segmented debugging image with lengths labeled

        :param segmented_img: numpy.ndarray
        :param objects: list
        :return labeled_img: numpy.ndarray

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

    x_list = []
    y_list = []
    segment_lengths = []
    rand_color = color_palette(len(objects))


    labeled_img = segmented_img.copy()

    for i, cnt in enumerate(objects):
        # Store coordinates for labels
        x_list.append(objects[i][0][0][0])
        y_list.append(objects[i][0][0][1])

        # Draw segments one by one to group segment tips together
        finding_tips_img = np.zeros(segmented_img.shape[:2], np.uint8)
        cv2.drawContours(finding_tips_img, objects, i, (255, 255, 255), 1, lineType=8)
        segment_tips = find_tips(finding_tips_img)
        tip_objects, tip_hierarchies = find_objects(segment_tips, segment_tips)
        points = []
        if not len(tip_objects) == 2:
            fatal_error("Too many tips found per segment, try pruning again")
        for t in tip_objects:
            # Gather pairs of coordinates
            x, y = t.ravel()
            coord = (x, y)
            points.append(coord)

        # Draw euclidean distance lines
        cv2.line(labeled_img, points[0], points[1], rand_color[i], 1)

        # Calculate euclidean distance between tips of each contour
        segment_lengths.append(euclidean(points[0], points[1]))

    segment_ids = []
    # Put labels of length
    for c, value in enumerate(segment_lengths):
        text = "{:.2f}".format(value)
        w = x_list[c]
        h = y_list[c]
        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(c)
        segment_ids.append(c)

    outputs.add_observation(variable='segment_eu_length', trait='segment euclidean length',
                            method='plantcv.plantcv.morphology.segment_euclidean_length', scale='pixels', datatype=list,
                            value=segment_lengths, 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_eu_lengths.png'))
    elif params.debug == 'plot':
        plot_image(labeled_img)

    return labeled_img
Пример #18
0
def segment_sort(skel_img, objects, mask=None):
    """ 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.

        Returns:
        labeled_img       = Segmented debugging image with lengths labeled
        secondary_objects = List of secondary segments (leaf)
        primary_objects   = List of primary objects (stem)

        :param skel_img: numpy.ndarray
        :param objects: list
        :param labeled_img: numpy.ndarray
        :param mask: numpy.ndarray
        :return secondary_objects: list
        :return other_objects: list
        """
    # Store debug
    debug = params.debug
    params.debug = None

    secondary_objects = []
    primary_objects = []

    if mask is None:
        labeled_img = np.zeros(skel_img.shape[:2], np.uint8)
    else:
        labeled_img = mask.copy()

    tips = find_tips(skel_img)
    tip_objects, tip_hierarchies = find_objects(tips, tips)


    # Create a list of tip tuples
    tip_tuples = []
    for i, cnt in enumerate(tip_objects):
        tip_tuples.append((cnt[0][0][0], cnt[0][0][1]))

    # Loop through segment contours
    for i, cnt in enumerate(objects):
        is_leaf = False
        cnt_as_tuples = []
        num_pixels = len(cnt)
        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 = cnt[count][0][0]
            y_coord = cnt[count][0][1]
            cnt_as_tuples.append((x_coord, y_coord))
            count += 1

        # The first contour is the base, and while it contains a tip, it isn't a leaf
        if i == 0:
            primary_objects.append(cnt)

        # Sort segments
        else:
            for tip_tups in tip_tuples:
                # If a tip is inside the list of contour tuples then it is a leaf segment
                if tip_tups in cnt_as_tuples:
                    secondary_objects.append(cnt)
                    is_leaf = True

            # If none of the tip tuples are inside the contour, then it isn't a leaf segment
            if is_leaf == False:
                primary_objects.append(cnt)

    # Plot segments where green segments are leaf objects and fuschia are other objects
    labeled_img = cv2.cvtColor(labeled_img, cv2.COLOR_GRAY2RGB)
    for i, cnt in enumerate(primary_objects):
        cv2.drawContours(labeled_img, primary_objects, i, (255, 0, 255), params.line_thickness, lineType=8)
    for i, cnt in enumerate(secondary_objects):
        cv2.drawContours(labeled_img, secondary_objects, i, (0, 255, 0), params.line_thickness, lineType=8)

    # 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) + '_sorted_segments.png'))
    elif params.debug == 'plot':
        plot_image(labeled_img)

    return secondary_objects, primary_objects
Пример #19
0
def image_avg(fundf):
    # dn't understand why import suddently needs to be inside function
    # import cv2 as cv2
    # import numpy as np
    # import pandas as pd
    # import os
    # from matplotlib import pyplot as plt
    # from skimage import filters
    # from skimage import morphology
    # from skimage import segmentation

    # Predefine some variables
    global c, h, roi_c, roi_h, ilegend, mask_Fm, fn_Fm

    # Get the filename for minimum and maximum fluoresence
    fn_min = fundf.query('frame == "Fo" or frame == "Fp"').filename.values[0]
    fn_max = fundf.query('frame == "Fm" or frame == "Fmp"').filename.values[0]

    # Get the parameter name that links these 2 frames
    param_name = fundf['parameter'].iloc[0]

    # Create a new output filename that combines existing filename with parameter
    outfn = os.path.splitext(os.path.basename(fn_max))[0]
    outfn_split = outfn.split('-')
    # outfn_split[2] = datetime.strptime(fundf.jobdate.values[0],'%Y-%m-%d').strftime('%Y%m%d')
    outfn_split[2] = fundf.jobdate.dt.strftime('%Y%m%d').values[0]
    basefn = "-".join(outfn_split[0:-1])
    outfn_split[-1] = param_name
    outfn = "-".join(outfn_split)
    print(outfn)

    # Make some directories based on sample id to keep output organized
    plantbarcode = outfn_split[0]
    fmaxdir = os.path.join(fluordir, plantbarcode)
    os.makedirs(fmaxdir, exist_ok=True)

    # If debug mode is 'print', create a specific debug dir for each pim file
    if pcv.params.debug == 'print':
        debug_outdir = os.path.join(debugdir, outfn)
        os.makedirs(debug_outdir, exist_ok=True)
        pcv.params.debug_outdir = debug_outdir

    # read images and create mask from max fluorescence
    # read image as is. only gray values in PSII images
    imgmin, _, _ = pcv.readimage(fn_min)
    img, _, _ = pcv.readimage(fn_max)
    fdark = np.zeros_like(img)
    out_flt = fdark.astype('float32')  # <- needs to be float32 for imwrite

    if param_name == 'FvFm':
        # save max fluorescence filename
        fn_Fm = fn_max

        # create mask
        # #create black mask over lower half of image to threshold upper plant only
        # img_half, _, _, _ = pcv.rectangle_mask(img, p1=(0,321), p2=(480,640))
        # # mask1 = pcv.threshold.otsu(img_half,255)
        # algaethresh = filters.threshold_otsu(image=img_half)
        # mask0 = pcv.threshold.binary(img_half, algaethresh, 255, 'light')

        # # create black mask over upper half of image to threshold lower plant only
        # img_half, _, _, _ = pcv.rectangle_mask(img, p1=(0, 0), p2=(480, 319), color='black')
        # # mask0 = pcv.threshold.otsu(img_half,255)
        # algaethresh = filters.threshold_otsu(image=img_half)
        # mask1 = pcv.threshold.binary(img_half, algaethresh, 255, 'light')

        # mask = pcv.logical_xor(mask0, mask1)
        # # mask = pcv.dilate(mask, 2, 1)
        # mask = pcv.fill(mask, 350)
        # mask = pcv.erode(mask, 2, 2)

        # mask = pcv.erode(mask, 2, 1)
        # mask = pcv.fill(mask, 100)

        # otsuT = filters.threshold_otsu(img)
        # # sigma=(k-1)/6. This is because the length for 99 percentile of gaussian pdf is 6sigma.
        # k = int(2 * np.ceil(3 * otsuT) + 1)
        # gb = pcv.gaussian_blur(img, ksize = (k,k), sigma_x = otsuT)
        # mask = img >= gb + 10
        # pcv.plot_image(mask)

        # local_otsu = filters.rank.otsu(img, pcv.get_kernel((9,9), 'rectangle'))#morphology.disk(2))
        # thresh_image = img >= local_otsu

        #_------>
        elevation_map = filters.sobel(img)
        # pcv.plot_image(elevation_map)
        thresh = filters.threshold_otsu(image=img)
        # thresh = 50

        markers = np.zeros_like(img, dtype='uint8')
        markers[img > thresh + 8] = 2
        markers[img <= thresh + 8] = 1
        # pcv.plot_image(markers,cmap=plt.cm.nipy_spectral)

        mask = segmentation.watershed(elevation_map, markers)
        mask = mask.astype(np.uint8)
        # pcv.plot_image(mask)

        mask[mask == 1] = 0
        mask[mask == 2] = 1
        # pcv.plot_image(mask, cmap=plt.cm.nipy_spectral)

        # mask = pcv.erode(mask, 2, 1)
        mask = pcv.fill(mask, 100)
        # pcv.plot_image(mask, cmap=plt.cm.nipy_spectral)
        # <-----------
        roi_c, roi_h = pcv.roi.multi(img,
                                     coord=(250, 200),
                                     radius=70,
                                     spacing=(0, 220),
                                     ncols=1,
                                     nrows=2)

        if len(np.unique(mask)) == 1:
            c = []
            YII = mask
            NPQ = mask
            newmask = mask
        else:
            # find objects and setup roi
            c, h = pcv.find_objects(img, mask)

            # setup individual roi plant masks
            newmask = np.zeros_like(mask)

            # compute fv/fm and save to file
            YII, hist_fvfm = pcv.photosynthesis.analyze_fvfm(fdark=fdark,
                                                             fmin=imgmin,
                                                             fmax=img,
                                                             mask=mask,
                                                             bins=128)
            # YII = np.divide(Fv,
            #                 img,
            #                 out=out_flt.copy(),
            #                 where=np.logical_and(mask > 0, img > 0))

            # NPQ is 0
            NPQ = np.zeros_like(YII)

        # cv2.imwrite(os.path.join(fmaxdir, outfn + '-fvfm.tif'), YII)
        # print Fm - will need this later
        # cv2.imwrite(os.path.join(fmaxdir, outfn + '-fmax.tif'), img)
        # NPQ will always be an array of 0s

    else:  # compute YII and NPQ if parameter is other than FvFm
        newmask = mask_Fm
        # use cv2 to read image becase pcv.readimage will save as input_image.png overwriting img
        # newmask = cv2.imread(os.path.join(maskdir, basefn + '-FvFm-mask.png'),-1)
        if len(np.unique(newmask)) == 1:
            YII = np.zeros_like(newmask)
            NPQ = np.zeros_like(newmask)

        else:
            # compute YII
            YII, hist_yii = pcv.photosynthesis.analyze_fvfm(fdark,
                                                            fmin=imgmin,
                                                            fmax=img,
                                                            mask=newmask,
                                                            bins=128)
            # make sure to initialize with out=. using where= provides random values at False pixels. you will get a strange result. newmask comes from Fm instead of Fm' so they can be different
            #newmask<0, img>0 = FALSE: not part of plant but fluorescence detected.
            #newmask>0, img<=0 = FALSE: part of plant in Fm but no fluorescence detected <- this is likely the culprit because pcv.apply_mask doesn't always solve issue.
            # YII = np.divide(Fvp,
            #                 img,
            #                 out=out_flt.copy(),
            #                 where=np.logical_and(newmask > 0, img > 0))

            # compute NPQ
            # Fm = cv2.imread(os.path.join(fmaxdir, basefn + '-FvFm-fmax.tif'), -1)
            Fm = cv2.imread(fn_Fm, -1)
            NPQ = np.divide(Fm,
                            img,
                            out=out_flt.copy(),
                            where=np.logical_and(newmask > 0, img > 0))
            NPQ = np.subtract(NPQ,
                              1,
                              out=out_flt.copy(),
                              where=np.logical_and(NPQ >= 1, newmask > 0))

        # cv2.imwrite(os.path.join(fmaxdir, outfn + '-yii.tif'), YII)
        # cv2.imwrite(os.path.join(fmaxdir, outfn + '-npq.tif'), NPQ)

    # end if-else Fv/Fm

    # Make as many copies of incoming dataframe as there are ROIs so all results can be saved
    outdf = fundf.copy()
    for i in range(0, len(roi_c) - 1):
        outdf = outdf.append(fundf)
    outdf.frameid = outdf.frameid.astype('uint8')

    # Initialize lists to store variables for each ROI and iterate through each plant
    frame_avg = []
    yii_avg = []
    yii_std = []
    npq_avg = []
    npq_std = []
    plantarea = []
    ithroi = []
    inbounds = []
    if len(c) == 0:

        for i, rc in enumerate(roi_c):
            # each variable needs to be stored 2 x #roi
            frame_avg.append(np.nan)
            frame_avg.append(np.nan)
            yii_avg.append(np.nan)
            yii_avg.append(np.nan)
            yii_std.append(np.nan)
            yii_std.append(np.nan)
            npq_avg.append(np.nan)
            npq_avg.append(np.nan)
            npq_std.append(np.nan)
            npq_std.append(np.nan)
            inbounds.append(False)
            inbounds.append(False)
            plantarea.append(0)
            plantarea.append(0)
            # Store iteration Number even if there are no objects in image
            ithroi.append(int(i))
            ithroi.append(int(i))  # append twice so each image has a value.

    else:
        i = 1
        rc = roi_c[i]
        for i, rc in enumerate(roi_c):
            # Store iteration Number
            ithroi.append(int(i))
            ithroi.append(int(i))  # append twice so each image has a value.
            # extract ith hierarchy
            rh = roi_h[i]

            # Filter objects based on being in the defined ROI
            roi_obj, hierarchy_obj, submask, obj_area = pcv.roi_objects(
                img,
                roi_contour=rc,
                roi_hierarchy=rh,
                object_contour=c,
                obj_hierarchy=h,
                roi_type='partial')

            if obj_area == 0:
                print('!!! No plant detected in ROI ', str(i))

                frame_avg.append(np.nan)
                frame_avg.append(np.nan)
                yii_avg.append(np.nan)
                yii_avg.append(np.nan)
                yii_std.append(np.nan)
                yii_std.append(np.nan)
                npq_avg.append(np.nan)
                npq_avg.append(np.nan)
                npq_std.append(np.nan)
                npq_std.append(np.nan)
                inbounds.append(False)
                inbounds.append(False)
                plantarea.append(0)
                plantarea.append(0)

            else:

                # Combine multiple plant objects within an roi together
                plant_contour, plant_mask = pcv.object_composition(
                    img=img, contours=roi_obj, hierarchy=hierarchy_obj)

                #combine plant masks after roi filter
                if param_name == 'FvFm':
                    newmask = pcv.image_add(newmask, plant_mask)

                # Calc mean and std dev of fluoresence, YII, and NPQ and save to list
                frame_avg.append(cppc.utils.mean(imgmin, plant_mask))
                frame_avg.append(cppc.utils.mean(img, plant_mask))
                # need double because there are two images per loop
                yii_avg.append(cppc.utils.mean(YII, plant_mask))
                yii_avg.append(cppc.utils.mean(YII, plant_mask))
                yii_std.append(cppc.utils.std(YII, plant_mask))
                yii_std.append(cppc.utils.std(YII, plant_mask))
                npq_avg.append(cppc.utils.mean(NPQ, plant_mask))
                npq_avg.append(cppc.utils.mean(NPQ, plant_mask))
                npq_std.append(cppc.utils.std(NPQ, plant_mask))
                npq_std.append(cppc.utils.std(NPQ, plant_mask))
                plantarea.append(obj_area * cppc.pixelresolution**2)
                plantarea.append(obj_area * cppc.pixelresolution**2)

                # Check if plant is compeltely within the frame of the image
                inbounds.append(pcv.within_frame(plant_mask))
                inbounds.append(pcv.within_frame(plant_mask))

                # Output a pseudocolor of NPQ and YII for each induction period for each image
                imgdir = os.path.join(outdir, 'pseudocolor_images')
                outfn_roi = outfn + '-roi' + str(i)
                os.makedirs(imgdir, exist_ok=True)
                npq_img = pcv.visualize.pseudocolor(NPQ,
                                                    obj=None,
                                                    mask=plant_mask,
                                                    cmap='inferno',
                                                    axes=False,
                                                    min_value=0,
                                                    max_value=2.5,
                                                    background='black',
                                                    obj_padding=0)
                npq_img = cppc.viz.add_scalebar(
                    npq_img,
                    pixelresolution=cppc.pixelresolution,
                    barwidth=10,
                    barlabel='1 cm',
                    barlocation='lower left')
                # If you change the output size and resolution you will need to adjust the timelapse video script
                npq_img.set_size_inches(6, 6, forward=False)
                npq_img.savefig(
                    os.path.join(imgdir, outfn_roi + '-NPQ.png'),
                    bbox_inches='tight',
                    dpi=100)  #100 is default for matplotlib/plantcv
                if ilegend == 1:  #only need to print legend once
                    npq_img.savefig(os.path.join(imgdir, 'npq_legend.pdf'),
                                    bbox_inches='tight')
                npq_img.clf()

                yii_img = pcv.visualize.pseudocolor(
                    YII,
                    obj=None,
                    mask=plant_mask,
                    cmap='gist_rainbow',  #custom_colormaps.get_cmap(
                    # 'imagingwin')#
                    axes=False,
                    min_value=0,
                    max_value=1,
                    background='black',
                    obj_padding=0)
                yii_img = cppc.viz.add_scalebar(
                    yii_img,
                    pixelresolution=cppc.pixelresolution,
                    barwidth=10,
                    barlabel='1 cm',
                    barlocation='lower left')
                yii_img.set_size_inches(6, 6, forward=False)
                yii_img.savefig(os.path.join(imgdir, outfn_roi + '-YII.png'),
                                bbox_inches='tight',
                                dpi=100)
                if ilegend == 1:  #print legend once and increment ilegend  to stop in future iterations
                    yii_img.savefig(os.path.join(imgdir, 'yii_legend.pdf'),
                                    bbox_inches='tight')
                    ilegend = ilegend + 1
                yii_img.clf()

            # end try-except-else

        # end roi loop

    # end if there are objects from roi filter

    # save mask of all plants to file after roi filter
    if param_name == 'FvFm':
        mask_Fm = newmask.copy()
        # pcv.print_image(newmask, os.path.join(maskdir, outfn + '-mask.png'))

    # check YII values for uniqueness between all ROI. nonunique ROI suggests the plants grew into each other and can no longer be reliably separated in image processing.
    # a single value isn't always robust. I think because there are small independent objects that fall in one roi but not the other that change the object within the roi slightly.
    # also note, I originally designed this for trays of 2 pots. It will not detect if e.g. 2 out of 9 plants grow into each other
    rounded_avg = [round(n, 3) for n in yii_avg]
    rounded_std = [round(n, 3) for n in yii_std]
    if len(roi_c) > 1:
        isunique = not (rounded_avg.count(rounded_avg[0]) == len(yii_avg)
                        and rounded_std.count(rounded_std[0]) == len(yii_std))
    else:
        isunique = True

    # save all values to outgoing dataframe
    outdf['roi'] = ithroi
    outdf['frame_avg'] = frame_avg
    outdf['yii_avg'] = yii_avg
    outdf['npq_avg'] = npq_avg
    outdf['yii_std'] = yii_std
    outdf['npq_std'] = npq_std
    outdf['obj_in_frame'] = inbounds
    outdf['unique_roi'] = isunique

    return (outdf)
# In[32]:

# Filling any small objects even though there were none visible in the last image.
ab_fill = pcv.fill(bin_img=ab, size=200)

# In[34]:

# Appying the new mask for a wiped background.
masked2 = pcv.apply_mask(img=masked, mask=ab_fill, mask_color='white')

# In[35]:

# Object is masked and filled. If wispy leaves were pictured, the spaces between the
# leaves would've been filled as well. That does not apply here.
id_objects, obj_hierarchy = pcv.find_objects(masked2, ab_fill)

# In[46]:

# Selecting a region of interest (roi). Defining (x,y) sets the location for
# the TOP LEFT corner of the rectangle.
roi1, roi_hierarchy = pcv.roi.rectangle(img=masked2, x=110, y=40, h=200, w=190)

# In[47]:

# This function is useful for separating plant from backgroud if there are many spaces between leaves.
roi_objects, hierarchy3, kept_mask, obj_area = pcv.roi_objects(
    img=img,
    roi_contour=roi1,
    roi_hierarchy=roi_hierarchy,
    object_contour=id_objects,
Пример #21
0
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
Пример #22
0
def segment_euclidean_length(segmented_img, objects, hierarchies):
    """ Use segmented skeleton image to gather euclidean length measurements per segment

        Inputs:
        segmented_img = Segmented image to plot lengths on
        objects       = List of contours
        hierarchy     = Contour hierarchy NumPy array

        Returns:
        eu_length_header = Segment euclidean length data header
        eu_length_data   = Segment euclidean length data values
        labeled_img      = Segmented debugging image with lengths labeled

        :param segmented_img: numpy.ndarray
        :param objects: list
        :param hierarchy: numpy.ndarray
        :return labeled_img: numpy.ndarray
        :return eu_length_header: list
        :return eu_length_data: list

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

    x_list = []
    y_list = []
    segment_lengths = []
    rand_color = color_palette(len(objects))


    labeled_img = segmented_img.copy()

    for i, cnt in enumerate(objects):
        # Store coordinates for labels
        x_list.append(objects[i][0][0][0])
        y_list.append(objects[i][0][0][1])

        # Draw segments one by one to group segment tips together
        finding_tips_img = np.zeros(segmented_img.shape[:2], np.uint8)
        cv2.drawContours(finding_tips_img, objects, i, (255, 255, 255), 1, lineType=8,
                         hierarchy=hierarchies)
        segment_tips = find_tips(finding_tips_img)
        tip_objects, tip_hierarchies = find_objects(segment_tips, segment_tips)
        points = []
        if not len(tip_objects) == 2:
            fatal_error("Too many tips found per segment, try pruning again")
        for t in tip_objects:
            # Gather pairs of coordinates
            x, y = t.ravel()
            coord = (x, y)
            points.append(coord)

        # Draw euclidean distance lines
        cv2.line(labeled_img, points[0], points[1], rand_color[i], 1)

        # Calculate euclidean distance between tips of each contour
        segment_lengths.append(euclidean(points[0], points[1]))

    eu_length_header = ['HEADER_EU_LENGTH']
    eu_length_data = ['EU_LENGTH_DATA']
    # Put labels of length
    for c, value in enumerate(segment_lengths):
        text = "{:.2f}".format(value)
        w = x_list[c]
        h = y_list[c]
        cv2.putText(img=labeled_img, text=text, org=(w, h), fontFace=cv2.FONT_HERSHEY_SIMPLEX, fontScale=.4,
                    color=(150, 150, 150), thickness=1)
        segment_label = "ID" + str(c)
        eu_length_header.append(segment_label)
        eu_length_data.append(segment_lengths[c])

    if 'morphology_data' not in outputs.measurements:
        outputs.measurements['morphology_data'] = {}
    outputs.measurements['morphology_data']['segment_eu_lengths'] = segment_lengths

    # 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_eu_lengths.png'))
    elif params.debug == 'plot':
        plot_image(labeled_img)

    return eu_length_header, eu_length_data, labeled_img
Пример #23
0
def check_cycles(skel_img):
    """ Check for cycles in a skeleton image
    Inputs:
    skel_img     = Skeletonized image

    Returns:
    cycle_img    = Image with cycles identified

    :param skel_img: numpy.ndarray
    :return cycle_img: numpy.ndarray
    """

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

    # Create the mask needed for cv2.floodFill, must be larger than the image
    h, w = skel_img.shape[:2]
    mask = np.zeros((h + 2, w + 2), np.uint8)

    # Copy the skeleton since cv2.floodFill will draw on it
    skel_copy = skel_img.copy()
    cv2.floodFill(skel_copy, mask=mask, seedPoint=(0, 0), newVal=255)

    # Invert so the holes are white and background black
    just_cycles = cv2.bitwise_not(skel_copy)

    # Erode slightly so that cv2.findContours doesn't think diagonal pixels are separate contours
    just_cycles = erode(just_cycles, 2, 1)

    # Use pcv.find_objects to turn plots of holes into countable contours
    cycle_objects, cycle_hierarchies = find_objects(just_cycles, just_cycles)

    # Count the number of holes
    num_cycles = len(cycle_objects)

    # Make debugging image
    cycle_img = skel_img.copy()
    cycle_img = dilate(cycle_img, params.line_thickness, 1)
    cycle_img = cv2.cvtColor(cycle_img, cv2.COLOR_GRAY2RGB)
    if num_cycles > 0:
        rand_color = color_palette(num_cycles)
        for i, cnt in enumerate(cycle_objects):
            cv2.drawContours(cycle_img,
                             cycle_objects,
                             i,
                             rand_color[i],
                             params.line_thickness,
                             lineType=8,
                             hierarchy=cycle_hierarchies)

    # Store Cycle Data
    outputs.add_observation(variable='num_cycles',
                            trait='number of cycles',
                            method='plantcv.plantcv.morphology.check_cycles',
                            scale='none',
                            datatype=int,
                            value=num_cycles,
                            label='none')

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

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

    return cycle_img
Пример #24
0
def main():
    # Get options
    args = options()

    # Read image
    img, path, filename = pcv.readimage(args.image)

    pcv.params.debug=args.debug #set debug mode

    # STEP 1: Check if this is a night image, for some of these dataset's images were captured
    # at night, even if nothing is visible. To make sure that images are not taken at
    # night we check that the image isn't mostly dark (0=black, 255=white).
    # if it is a night image it throws a fatal error and stops the workflow.

    if np.average(img) < 50:
        pcv.fatal_error("Night Image")
    else:
        pass

    # STEP 2: Normalize the white color so you can later
    # compare color between images.
    # Inputs:
    #   img = image object, RGB colorspace
    #   roi = region for white reference, if none uses the whole image,
    #         otherwise (x position, y position, box width, box height)

    # white balance image based on white toughspot

    #img1 = pcv.white_balance(img=img,roi=(400,800,200,200))
    img1 = pcv.white_balance(img=img, mode='hist', roi=None)

    # STEP 3: Rotate the image
    # Inputs:
    #   img = image object, RGB color space
    #   rotation_deg = Rotation angle in degrees, can be negative, positive values 
    #                  will move counter-clockwise 
    #   crop = If True then image will be cropped to original image dimensions, if False
    #          the image size will be adjusted to accommodate new image dimensions 


    rotate_img = pcv.rotate(img=img1,rotation_deg=-1, crop=False)

    # STEP 4: Shift image. This step is important for clustering later on.
    # For this image it also allows you to push the green raspberry pi camera
    # out of the image. This step might not be necessary for all images.
    # The resulting image is the same size as the original.
    # Inputs:
    #   img    = image object
    #   number = integer, number of pixels to move image
    #   side   = direction to move from "top", "bottom", "right","left"

    shift1 = pcv.shift_img(img=img1, number=300, side='top')
    img1 = shift1

    # STEP 5: Convert image from RGB colorspace to LAB colorspace
    # Keep only the green-magenta channel (grayscale)
    # Inputs:
    #    img     = image object, RGB colorspace
    #    channel = color subchannel ('l' = lightness, 'a' = green-magenta , 'b' = blue-yellow)

    #a = pcv.rgb2gray_lab(img=img1, channel='a')
    a = pcv.rgb2gray_lab(rgb_img=img1, channel='a')

    # STEP 6: Set a binary threshold on the saturation channel image
    # Inputs:
    #    img         = img object, grayscale
    #    threshold   = threshold value (0-255)
    #    max_value   = value to apply above threshold (usually 255 = white)
    #    object_type = light or dark
    #       - If object is light then standard thresholding is done
    #       - If object is dark then inverse thresholding is done

    img_binary = pcv.threshold.binary(gray_img=a, threshold=120, max_value=255, object_type='dark')
    #img_binary = pcv.threshold.binary(gray_img=a, threshold=120, max_value=255, object_type'dark')
    #                                                   ^
    #                                                   |
    #                                     adjust this value

    # STEP 7: Fill in small objects (speckles)
    # Inputs:
    #    bin_img  = image object, binary. img will be returned after filling
    #    size = minimum object area size in pixels (integer)

    fill_image = pcv.fill(bin_img=img_binary, size=10)
    #                                          ^
    #                                          |
    #                           adjust this value

    # STEP 8: Dilate so that you don't lose leaves (just in case)
    # Inputs:
    #    img    = input image
    #    ksize  = kernel size
    #    i      = iterations, i.e. number of consecutive filtering passes

    #dilated = pcv.dilate(img=fill_image, ksize=1, i=1)
    dilated = pcv.dilate(gray_img=fill_image, ksize=2, i=1)

    # STEP 9: Find objects (contours: black-white boundaries)
    # Inputs:
    #    img  = image that the objects will be overlayed
    #    mask = what is used for object detection

    id_objects, obj_hierarchy = pcv.find_objects(img=img1, mask=dilated)
    #id_objects, obj_hierarchy = pcv.find_objects(gray_img, mask)

    # STEP 10: Define region of interest (ROI)
    # Inputs:
    #    img       = img to overlay roi
    #    x_adj     = adjust center along x axis
    #    y_adj     = adjust center along y axis
    #    h_adj     = adjust height
    #    w_adj     = adjust width
    # roi_contour, roi_hierarchy = pcv.roi.rectangle(img1, 10, 500, -10, -100)
    #                                                      ^                ^
    #                                                      |________________|
    #                                            adjust these four values

    roi_contour, roi_hierarchy = pcv.roi.rectangle(img=img1, x=200, y=190, h=2000, w=3000)

    # STEP 11: Keep objects that overlap with the ROI
    # Inputs:
    #    img            = img to display kept objects
    #    roi_contour    = contour of roi, output from any ROI function
    #    roi_hierarchy  = contour of roi, output from any ROI function
    #    object_contour = contours of objects, output from "Identifying Objects" function
    #    obj_hierarchy  = hierarchy of objects, output from "Identifying Objects" function
    #    roi_type       = 'partial' (default, for partially inside), 'cutto', or 'largest' (keep only largest contour)

    roi_objects, roi_obj_hierarchy, kept_mask, obj_area = pcv.roi_objects(img=img1, roi_contour=roi_contour, 
                                                                          roi_hierarchy=roi_hierarchy,
                                                                          object_contour=id_objects,
                                                                          obj_hierarchy=obj_hierarchy, 
                                                                          roi_type='partial')

    # STEP 12: This function take a image with multiple contours and
    # clusters them based on user input of rows and columns

    # Inputs:
    #    img               = An RGB image
    #    roi_objects       = object contours in an image that are needed to be clustered.
    #    roi_obj_hierarchy = object hierarchy
    #    nrow              = number of rows to cluster (this should be the approximate  number of desired rows in the entire image even if there isn't a literal row of plants)
    #    ncol              = number of columns to cluster (this should be the approximate number of desired columns in the entire image even if there isn't a literal row of plants)
    #    show_grid         = if True then a grid gets displayed in debug mode (default show_grid=False)

    clusters_i, contours, hierarchies = pcv.cluster_contours(img=img1, roi_objects=roi_objects, 
                                                             roi_obj_hierarchy=roi_obj_hierarchy, 
                                                             nrow=2, ncol=3)

    # STEP 13: This function takes clustered contours and splits them into multiple images,
    # also does a check to make sure that the number of inputted filenames matches the number
    # of clustered contours. If no filenames are given then the objects are just numbered
    # Inputs:
    #    img                     = ideally a masked RGB image.
    #    grouped_contour_indexes = output of cluster_contours, indexes of clusters of contours
    #    contours                = contours to cluster, output of cluster_contours
    #    hierarchy               = object hierarchy
    #    outdir                  = directory for output images
    #    file                    = the name of the input image to use as a base name , output of filename from read_image function
    #    filenames               = input txt file with list of filenames in order from top to bottom left to right (likely list of genotypes)

    # Set global debug behavior to None (default), "print" (to file), or "plot" (Jupyter Notebooks or X11)
    pcv.params.debug = "print"

    out = args.outdir
    names = args.names

    output_path, imgs, masks = pcv.cluster_contour_splitimg(rgb_img=img1, grouped_contour_indexes=clusters_i, 
                                                            contours=contours, hierarchy=hierarchies, 
                                                            outdir=out, file=filename, filenames=names)
def report_size_marker_area(img, roi_contour, roi_hierarchy, marker='define', objcolor='dark', thresh_channel=None,
                            thresh=None):
    """Detects a size marker in a specified region and reports its size and eccentricity

    Inputs:
    img             = An RGB or grayscale image to plot the marker object on
    roi_contour     = A region of interest contour (e.g. output from pcv.roi.rectangle or other methods)
    roi_hierarchy   = A region of interest contour hierarchy (e.g. output from pcv.roi.rectangle or other methods)
    marker          = 'define' or 'detect'. If define it means you set an area, if detect it means you want to
                      detect within an area
    objcolor        = Object color is 'dark' or 'light' (is the marker darker or lighter than the background)
    thresh_channel  = 'h', 's', or 'v' for hue, saturation or value
    thresh          = Binary threshold value (integer)

    Returns:
    analysis_images = List of output images

    :param img: numpy.ndarray
    :param roi_contour: list
    :param roi_hierarchy: numpy.ndarray
    :param marker: str
    :param objcolor: str
    :param thresh_channel: str
    :param thresh: int
    :return: analysis_images: list
    """

    params.device += 1
    # Make a copy of the reference image
    ref_img = np.copy(img)
    # If the reference image is grayscale convert it to color
    if len(np.shape(ref_img)) == 2:
        ref_img = cv2.cvtColor(ref_img, cv2.COLOR_GRAY2BGR)

    # Marker components
    # If the marker type is "defined" then the marker_mask and marker_contours are equal to the input ROI
    # Initialize a binary image
    roi_mask = np.zeros(np.shape(img)[:2], dtype=np.uint8)
    # Draw the filled ROI on the mask
    cv2.drawContours(roi_mask, roi_contour, -1, (255), -1)
    marker_mask = []
    marker_contour = []

    # If the marker type is "detect" then we will use the ROI to isolate marker contours from the input image
    if marker.upper() == 'DETECT':
        # We need to convert the input image into an one of the HSV channels and then threshold it
        if thresh_channel is not None and thresh is not None:
            # Mask the input image
            masked = apply_mask(rgb_img=ref_img, mask=roi_mask, mask_color="black")
            # Convert the masked image to hue, saturation, or value
            marker_hsv = rgb2gray_hsv(rgb_img=masked, channel=thresh_channel)
            # Threshold the HSV image
            marker_bin = binary_threshold(gray_img=marker_hsv, threshold=thresh, max_value=255, object_type=objcolor)
            # Identify contours in the masked image
            contours, hierarchy = find_objects(img=ref_img, mask=marker_bin)
            # Filter marker contours using the input ROI
            kept_contours, kept_hierarchy, kept_mask, obj_area = roi_objects(img=ref_img, object_contour=contours,
                                                                             obj_hierarchy=hierarchy,
                                                                             roi_contour=roi_contour,
                                                                             roi_hierarchy=roi_hierarchy,
                                                                             roi_type="partial")
            # If there are more than one contour detected, combine them into one
            # These become the marker contour and mask
            marker_contour, marker_mask = object_composition(img=ref_img, contours=kept_contours,
                                                             hierarchy=kept_hierarchy)
        else:
            fatal_error('thresh_channel and thresh must be defined in detect mode')
    elif marker.upper() == "DEFINE":
        # Identify contours in the masked image
        contours, hierarchy = find_objects(img=ref_img, mask=roi_mask)
        # If there are more than one contour detected, combine them into one
        # These become the marker contour and mask
        marker_contour, marker_mask = object_composition(img=ref_img, contours=contours, hierarchy=hierarchy)
    else:
        fatal_error("marker must be either 'define' or 'detect' but {0} was provided.".format(marker))

    # Calculate the moments of the defined marker region
    m = cv2.moments(marker_mask, binaryImage=True)
    # Calculate the marker area
    marker_area = m['m00']

    # Fit a bounding ellipse to the marker
    center, axes, angle = cv2.fitEllipse(marker_contour)
    major_axis = np.argmax(axes)
    minor_axis = 1 - major_axis
    major_axis_length = axes[major_axis]
    minor_axis_length = axes[minor_axis]
    # Calculate the bounding ellipse eccentricity
    eccentricity = np.sqrt(1 - (axes[minor_axis] / axes[major_axis]) ** 2)

    # Make a list to store output images
    analysis_image = []
    cv2.drawContours(ref_img, marker_contour, -1, (255, 0, 0), 5)
    # out_file = os.path.splitext(filename)[0] + '_sizemarker.jpg'
    # print_image(ref_img, out_file)
    analysis_image.append(ref_img)
    if params.debug is 'print':
        print_image(ref_img, os.path.join(params.debug_outdir, str(params.device) + '_marker_shape.png'))
    elif params.debug is 'plot':
        plot_image(ref_img)

    outputs.add_observation(variable='marker_area', trait='marker area',
                            method='plantcv.plantcv.report_size_marker_area', scale='pixels', datatype=int,
                            value=marker_area, label='pixels')
    outputs.add_observation(variable='marker_ellipse_major_axis', trait='marker ellipse major axis length',
                            method='plantcv.plantcv.report_size_marker_area', scale='pixels', datatype=int,
                            value=major_axis_length, label='pixels')
    outputs.add_observation(variable='marker_ellipse_minor_axis', trait='marker ellipse minor axis length',
                            method='plantcv.plantcv.report_size_marker_area', scale='pixels', datatype=int,
                            value=minor_axis_length, label='pixels')
    outputs.add_observation(variable='marker_ellipse_eccentricity', trait='marker ellipse eccentricity',
                            method='plantcv.plantcv.report_size_marker_area', scale='none', datatype=float,
                            value=eccentricity, label='none')

    # Store images
    outputs.images.append(analysis_image)

    return analysis_image
def main():

    # Set variables
    args = options()
    pcv.params.debug = args.debug

    # Read and rotate image
    img, path, filename = pcv.readimage(filename=args.image)
    img = pcv.rotate(img, -90, False)

    # Create mask from LAB b channel
    l = pcv.rgb2gray_lab(rgb_img=img, channel='b')
    l_thresh = pcv.threshold.binary(gray_img=l,
                                    threshold=115,
                                    max_value=255,
                                    object_type='dark')
    l_mblur = pcv.median_blur(gray_img=l_thresh, ksize=5)

    # Apply mask to image
    masked = pcv.apply_mask(img=img, mask=l_mblur, mask_color='white')
    ab_fill = pcv.fill(bin_img=l_mblur, size=50)

    # Extract plant object from image
    id_objects, obj_hierarchy = pcv.find_objects(img=img, mask=ab_fill)
    roi1, roi_hierarchy = pcv.roi.rectangle(img=masked,
                                            x=150,
                                            y=270,
                                            h=100,
                                            w=100)
    roi_objects, hierarchy3, kept_mask, obj_area = pcv.roi_objects(
        img=img,
        roi_contour=roi1,
        roi_hierarchy=roi_hierarchy,
        object_contour=id_objects,
        obj_hierarchy=obj_hierarchy,
        roi_type='partial')
    obj, mask = pcv.object_composition(img=img,
                                       contours=roi_objects,
                                       hierarchy=hierarchy3)

    ############### Analysis ################

    # Analyze shape properties
    analysis_image = pcv.analyze_object(img=img, obj=obj, mask=mask)
    boundary_image2 = pcv.analyze_bound_horizontal(img=img,
                                                   obj=obj,
                                                   mask=mask,
                                                   line_position=370)

    # Analyze colour properties
    color_histogram = pcv.analyze_color(rgb_img=img,
                                        mask=kept_mask,
                                        hist_plot_type='all')

    # Analyze shape independent of size
    top_x, bottom_x, center_v_x = pcv.x_axis_pseudolandmarks(img=img,
                                                             obj=obj,
                                                             mask=mask)
    top_y, bottom_y, center_v_y = pcv.y_axis_pseudolandmarks(img=img,
                                                             obj=obj,
                                                             mask=mask)

    # Print results
    pcv.print_results(filename='{}'.format(args.result))
    pcv.print_image(img=color_histogram,
                    filename='{}_color_hist.jpg'.format(args.outdir))
    pcv.print_image(img=kept_mask, filename='{}_mask.jpg'.format(args.outdir))
Пример #27
0
def segment_curvature(segmented_img, objects):
    """ Calculate segment curvature as defined by the ratio between geodesic and euclidean distance.
        Measurement of two-dimensional tortuosity.

        Inputs:
        segmented_img     = Segmented image to plot lengths on
        objects           = List of contours

        Returns:
        labeled_img        = Segmented debugging image with curvature labeled


        :param segmented_img: numpy.ndarray
        :param objects: list
        :return labeled_img: numpy.ndarray

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

    label_coord_x = []
    label_coord_y = []

    _ = segment_euclidean_length(segmented_img, objects)
    labeled_img = segment_path_length(segmented_img, objects)
    eu_lengths = outputs.observations['segment_eu_length']['value']
    path_lengths = outputs.observations['segment_path_length']['value']
    curvature_measure = [x/y for x, y in zip(path_lengths, eu_lengths)]
    rand_color = color_palette(len(objects))

    for i, cnt in enumerate(objects):
        # Store coordinates for labels
        label_coord_x.append(objects[i][0][0][0])
        label_coord_y.append(objects[i][0][0][1])

        # Draw segments one by one to group segment tips together
        finding_tips_img = np.zeros(segmented_img.shape[:2], np.uint8)
        cv2.drawContours(finding_tips_img, objects, i, (255, 255, 255), 1, lineType=8)
        segment_tips = find_tips(finding_tips_img)
        tip_objects, tip_hierarchies = find_objects(segment_tips, segment_tips)
        points = []

        for t in tip_objects:
            # Gather pairs of coordinates
            x, y = t.ravel()
            coord = (x, y)
            points.append(coord)

        # Draw euclidean distance lines
        cv2.line(labeled_img, points[0], points[1], rand_color[i], 1)

    segment_ids = []
    for i, cnt in enumerate(objects):
        # Calculate geodesic distance
        text = "{:.3f}".format(curvature_measure[i])
        w = label_coord_x[i]
        h = label_coord_y[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_curvature', trait='segment curvature',
                            method='plantcv.plantcv.morphology.segment_curvature', scale='none', datatype=list,
                            value=curvature_measure, 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_curvature.png'))
    elif params.debug == 'plot':
        plot_image(labeled_img)

    return labeled_img
Пример #28
0
def segment_skeleton(skel_img, mask=None):
    """ Segment a skeleton image into pieces

        Inputs:
        skel_img         = Skeletonized image
        mask             = (Optional) binary mask for debugging. If provided, debug image will be overlaid on the mask.

        Returns:
        segmented_img       = Segmented debugging image
        segment_objects     = list of contours

        :param skel_img: numpy.ndarray
        :param mask: numpy.ndarray
        :return segmented_img: numpy.ndarray
        :return segment_objects: list
        """

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

    # Find branch points
    bp = find_branch_pts(skel_img)
    bp = dilate(bp, 3, 1)

    # Subtract from the skeleton so that leaves are no longer connected
    segments = image_subtract(skel_img, bp)

    # Gather contours of leaves
    segment_objects, _ = find_objects(segments, segments)

    # Reset debug mode
    params.debug = debug

    # Color each segment a different color, do not used a previously saved color scale
    rand_color = color_palette(num=len(segment_objects), saved=False)

    if mask is None:
        segmented_img = skel_img.copy()
    else:
        segmented_img = mask.copy()

    segmented_img = cv2.cvtColor(segmented_img, cv2.COLOR_GRAY2RGB)
    for i, cnt in enumerate(segment_objects):
        cv2.drawContours(segmented_img,
                         segment_objects,
                         i,
                         rand_color[i],
                         params.line_thickness,
                         lineType=8)

    # Auto-increment device
    params.device += 1

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

    return segmented_img, segment_objects
Пример #29
0
def segment_sort(skel_img, objects, hierarchies, mask=None):
    """ Calculate segment curvature as defined by the ratio between geodesic and euclidean distance

        Inputs:
        skel_img          = Skeletonized image
        objects           = List of contours
        hierarchy         = Contour hierarchy NumPy array
        mask              = (Optional) binary mask for debugging. If provided, debug image will be overlaid on the mask.

        Returns:
        labeled_img       = Segmented debugging image with lengths labeled
        leaf_objects      = List of leaf segments
        leaf_hierarchies  = Contour hierarchy NumPy array
        other_objects     = List of other objects (stem)
        other_hierarchies = Contour hierarchy NumPy array

        :param skel_img: numpy.ndarray
        :param objects: list
        :param hierarchy: numpy.ndarray
        :param labeled_img: numpy.ndarray
        :param mask: numpy.ndarray
        :return leaf_objects: list
        :return leaf_hierarchies: numpy.ndarray
        :return other_objects: list
        :return other_hierarchies: numpy.ndarray
        """
    # Store debug
    debug = params.debug
    params.debug = None

    leaf_objects = []
    leaf_hierarchies = []
    other_objects = []
    other_hierarchies = []

    if mask is None:
        labeled_img = np.zeros(skel_img.shape[:2], np.uint8)
    else:
        labeled_img = mask.copy()

    tips = find_tips(skel_img)
    tip_objects, tip_hierarchies = find_objects(tips, tips)


    # Create a list of tip tuples
    tip_tuples = []
    for i, cnt in enumerate(tip_objects):
        tip_tuples.append((cnt[0][0][0], cnt[0][0][1]))

    # Loop through segment contours
    for i, cnt in enumerate(objects):
        is_leaf = False
        cnt_as_tuples = []
        num_pixels = len(cnt)
        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 = cnt[count][0][0]
            y_coord = cnt[count][0][1]
            cnt_as_tuples.append((x_coord, y_coord))
            count += 1

        # The first contour is the base, and while it contains a tip, it isn't a leaf
        if i == 0:
            other_objects.append(cnt)
            other_hierarchies.append(hierarchies[0][i])

        # Sort segments
        else:
            for tip_tups in tip_tuples:
                # If a tip is inside the list of contour tuples then it is a leaf segment
                if tip_tups in cnt_as_tuples:
                    leaf_objects.append(cnt)
                    leaf_hierarchies.append(hierarchies[0][i])
                    is_leaf = True

        # If none of the tip tuples are inside the contour, then it isn't a leaf segment
        if is_leaf == False:
            other_objects.append(cnt)
            other_hierarchies.append(hierarchies[0][i])

    # Format list of hierarchies so that cv2 can use them
    leaf_hierarchies = np.array([leaf_hierarchies])
    other_hierarchies = np.array([other_hierarchies])

    # Plot segments where green segments are leaf objects and fuschia are other objects
    labeled_img = cv2.cvtColor(labeled_img, cv2.COLOR_GRAY2RGB)
    for i, cnt in enumerate(other_objects):
        cv2.drawContours(labeled_img, other_objects, i, (255, 0, 255), params.line_thickness,
                         lineType=8, hierarchy=other_hierarchies)
    for i, cnt in enumerate(leaf_objects):
        cv2.drawContours(labeled_img, leaf_objects, i, (0, 255, 0), params.line_thickness,
                         lineType=8, hierarchy=leaf_hierarchies)

    # 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) + '_sorted_segments.png'))
    elif params.debug == 'plot':
        plot_image(labeled_img)

    return leaf_objects, leaf_hierarchies, other_objects, other_hierarchies
Пример #30
0
def report_size_marker_area(img,
                            roi_contour,
                            roi_hierarchy,
                            marker='define',
                            objcolor='dark',
                            thresh_channel=None,
                            thresh=None):
    """Detects a size marker in a specified region and reports its size and eccentricity

    Inputs:
    img             = An RGB or grayscale image to plot the marker object on
    roi_contour     = A region of interest contour (e.g. output from pcv.roi.rectangle or other methods)
    roi_hierarchy   = A region of interest contour hierarchy (e.g. output from pcv.roi.rectangle or other methods)
    marker          = 'define' or 'detect'. If define it means you set an area, if detect it means you want to
                      detect within an area
    objcolor        = Object color is 'dark' or 'light' (is the marker darker or lighter than the background)
    thresh_channel  = 'h', 's', or 'v' for hue, saturation or value
    thresh          = Binary threshold value (integer)

    Returns:
    analysis_images = List of output images

    :param img: numpy.ndarray
    :param roi_contour: list
    :param roi_hierarchy: numpy.ndarray
    :param marker: str
    :param objcolor: str
    :param thresh_channel: str
    :param thresh: int
    :return: analysis_images: list
    """
    # Store debug
    debug = params.debug
    params.debug = None

    params.device += 1
    # Make a copy of the reference image
    ref_img = np.copy(img)
    # If the reference image is grayscale convert it to color
    if len(np.shape(ref_img)) == 2:
        ref_img = cv2.cvtColor(ref_img, cv2.COLOR_GRAY2BGR)

    # Marker components
    # If the marker type is "defined" then the marker_mask and marker_contours are equal to the input ROI
    # Initialize a binary image
    roi_mask = np.zeros(np.shape(img)[:2], dtype=np.uint8)
    # Draw the filled ROI on the mask
    cv2.drawContours(roi_mask, roi_contour, -1, (255), -1)
    marker_mask = []
    marker_contour = []

    # If the marker type is "detect" then we will use the ROI to isolate marker contours from the input image
    if marker.upper() == 'DETECT':
        # We need to convert the input image into an one of the HSV channels and then threshold it
        if thresh_channel is not None and thresh is not None:
            # Mask the input image
            masked = apply_mask(rgb_img=ref_img,
                                mask=roi_mask,
                                mask_color="black")
            # Convert the masked image to hue, saturation, or value
            marker_hsv = rgb2gray_hsv(rgb_img=masked, channel=thresh_channel)
            # Threshold the HSV image
            marker_bin = binary_threshold(gray_img=marker_hsv,
                                          threshold=thresh,
                                          max_value=255,
                                          object_type=objcolor)
            # Identify contours in the masked image
            contours, hierarchy = find_objects(img=ref_img, mask=marker_bin)
            # Filter marker contours using the input ROI
            kept_contours, kept_hierarchy, kept_mask, obj_area = roi_objects(
                img=ref_img,
                object_contour=contours,
                obj_hierarchy=hierarchy,
                roi_contour=roi_contour,
                roi_hierarchy=roi_hierarchy,
                roi_type="partial")
            # If there are more than one contour detected, combine them into one
            # These become the marker contour and mask
            marker_contour, marker_mask = object_composition(
                img=ref_img, contours=kept_contours, hierarchy=kept_hierarchy)
        else:
            fatal_error(
                'thresh_channel and thresh must be defined in detect mode')
    elif marker.upper() == "DEFINE":
        # Identify contours in the masked image
        contours, hierarchy = find_objects(img=ref_img, mask=roi_mask)
        # If there are more than one contour detected, combine them into one
        # These become the marker contour and mask
        marker_contour, marker_mask = object_composition(img=ref_img,
                                                         contours=contours,
                                                         hierarchy=hierarchy)
    else:
        fatal_error(
            "marker must be either 'define' or 'detect' but {0} was provided.".
            format(marker))

    # Calculate the moments of the defined marker region
    m = cv2.moments(marker_mask, binaryImage=True)
    # Calculate the marker area
    marker_area = m['m00']

    # Fit a bounding ellipse to the marker
    center, axes, angle = cv2.fitEllipse(marker_contour)
    major_axis = np.argmax(axes)
    minor_axis = 1 - major_axis
    major_axis_length = axes[major_axis]
    minor_axis_length = axes[minor_axis]
    # Calculate the bounding ellipse eccentricity
    eccentricity = np.sqrt(1 - (axes[minor_axis] / axes[major_axis])**2)

    cv2.drawContours(ref_img, marker_contour, -1, (255, 0, 0), 5)
    analysis_image = ref_img

    # Reset debug mode
    params.debug = debug

    if params.debug is 'print':
        print_image(
            ref_img,
            os.path.join(params.debug_outdir,
                         str(params.device) + '_marker_shape.png'))
    elif params.debug is 'plot':
        plot_image(ref_img)

    outputs.add_observation(variable='marker_area',
                            trait='marker area',
                            method='plantcv.plantcv.report_size_marker_area',
                            scale='pixels',
                            datatype=int,
                            value=marker_area,
                            label='pixels')
    outputs.add_observation(variable='marker_ellipse_major_axis',
                            trait='marker ellipse major axis length',
                            method='plantcv.plantcv.report_size_marker_area',
                            scale='pixels',
                            datatype=int,
                            value=major_axis_length,
                            label='pixels')
    outputs.add_observation(variable='marker_ellipse_minor_axis',
                            trait='marker ellipse minor axis length',
                            method='plantcv.plantcv.report_size_marker_area',
                            scale='pixels',
                            datatype=int,
                            value=minor_axis_length,
                            label='pixels')
    outputs.add_observation(variable='marker_ellipse_eccentricity',
                            trait='marker ellipse eccentricity',
                            method='plantcv.plantcv.report_size_marker_area',
                            scale='none',
                            datatype=float,
                            value=eccentricity,
                            label='none')

    # Store images
    outputs.images.append(analysis_image)

    return analysis_image
Пример #31
0
def main():
    
    # Get options
    args = options()
    
    # Set variables
    pcv.params.debug = args.debug        # Replace the hard-coded debug with the debug flag
    img_file = args.image     # Replace the hard-coded input image with image flag

    ############### Image read-in ################

    # Read target image
    img, path, filename = pcv.readimage(filename = img_file, mode = "rgb")
    
    ############### Find scale and crop ################
    
    # find colour card in the image to be analysed
    df, start, space = pcv.transform.find_color_card(rgb_img = img)
    if int(start[0]) < 2000:
            img = imutils.rotate_bound(img, -90)
            rotated = 1
            df, start, space = pcv.transform.find_color_card(rgb_img = img)
    else: rotated = 0
    #if img.shape[0] > 6000:
    #    rotated = 1
    #else: rotated = 0
    img_mask = pcv.transform.create_color_card_mask(rgb_img = img, radius = 10, start_coord = start, spacing = space, ncols = 4, nrows = 6)
    
    # write the spacing of the colour card to file as size marker   
    with open(r'size_marker.csv', 'a') as f:
        writer = csv.writer(f)
        writer.writerow([filename, space[0]])

    # define a bounding rectangle around the colour card
    x_cc,y_cc,w_cc,h_cc = cv2.boundingRect(img_mask)
    x_cc = int(round(x_cc - 0.3 * w_cc))
    y_cc = int(round(y_cc - 0.3 * h_cc))
    h_cc = int(round(h_cc * 1.6))
    w_cc = int(round(w_cc * 1.6))

    # crop out colour card
    start_point = (x_cc, y_cc)
    end_point = (x_cc+w_cc, y_cc+h_cc)
    colour = (0, 0, 0)
    thickness = -1
    crop_img = cv2.rectangle(img, start_point, end_point, colour, thickness)
    
    ############### Fine segmentation ################
    
    # Threshold A and B channels of the LAB colourspace and the Hue channel of the HSV colourspace
    l_thresh, _ = pcv.threshold.custom_range(img=crop_img, lower_thresh=[70,0,0], upper_thresh=[255,255,255], channel='LAB')
    a_thresh, _ = pcv.threshold.custom_range(img=crop_img, lower_thresh=[0,0,0], upper_thresh=[255,145,255], channel='LAB')
    b_thresh, _ = pcv.threshold.custom_range(img=crop_img, lower_thresh=[0,0,123], upper_thresh=[255,255,255], channel='LAB')
    h_thresh_low, _ = pcv.threshold.custom_range(img=crop_img, lower_thresh=[0,0,0], upper_thresh=[130,255,255], channel='HSV')
    h_thresh_high, _ = pcv.threshold.custom_range(img=crop_img, lower_thresh=[150,0,0], upper_thresh=[255,255,255], channel='HSV')
    h_thresh = pcv.logical_or(h_thresh_low, h_thresh_high)

    # Join the thresholded images to keep only consensus pixels
    ab = pcv.logical_and(b_thresh, a_thresh)
    lab = pcv.logical_and(l_thresh, ab)
    labh = pcv.logical_and(lab, h_thresh)

    # Fill small objects
    labh_clean = pcv.fill(labh, 200)

    # Dilate to close broken borders
    #labh_dilated = pcv.dilate(labh_clean, 4, 1)
    labh_dilated = labh_clean

    # Apply mask (for VIS images, mask_color=white)
    masked = pcv.apply_mask(crop_img, labh_dilated, "white")

    # Identify objects
    contours, hierarchy = pcv.find_objects(crop_img, labh_dilated)

    # Define ROI

    if rotated == 1:
        roi_height = 3000
        roi_lwr_bound = y_cc + (h_cc * 0.5) - roi_height
        roi_contour, roi_hierarchy= pcv.roi.rectangle(x=1000, y=roi_lwr_bound, h=roi_height, w=2000, img=crop_img)
    else:
        roi_height = 1500
        roi_lwr_bound = y_cc + (h_cc * 0.5) - roi_height
        roi_contour, roi_hierarchy= pcv.roi.rectangle(x=2000, y=roi_lwr_bound, h=roi_height, w=2000, img=crop_img)

    # Decide which objects to keep
    filtered_contours, filtered_hierarchy, mask, area = pcv.roi_objects(img = crop_img,
                                                                roi_type = 'partial',
                                                                roi_contour = roi_contour,
                                                                roi_hierarchy = roi_hierarchy,
                                                                object_contour = contours,
                                                                obj_hierarchy = hierarchy)
    # Combine kept objects
    obj, mask = pcv.object_composition(crop_img, filtered_contours, filtered_hierarchy)

    ############### Analysis ################

    outfile=False
    if args.writeimg==True:
        outfile_black=args.outdir+"/"+filename+"_black"
        outfile_white=args.outdir+"/"+filename+"_white"
        outfile_analysed=args.outdir+"/"+filename+"_analysed"

    # analyse shape
    shape_img = pcv.analyze_object(crop_img, obj, mask)
    pcv.print_image(shape_img, outfile_analysed)

    # analyse colour
    colour_img = pcv.analyze_color(crop_img, mask, 'hsv')

    # keep the segmented plant for visualisation
    picture_mask = pcv.apply_mask(crop_img, mask, "black")
    pcv.print_image(picture_mask, outfile_black)
    
    picture_mask = pcv.apply_mask(crop_img, mask, "white")
    pcv.print_image(picture_mask, outfile_white)

    # print out results
    pcv.outputs.save_results(filename=args.result, outformat="json")
Пример #32
0
def main():
    # Get options
    args = options()

    # Set variables
    device = 0
    pcv.params.debug = args.debug
    img_file = args.image

    # Read image
    img, path, filename = pcv.readimage(filename=img_file, mode='rgb')

    # Process saturation channel from HSV colour space
    s = pcv.rgb2gray_hsv(rgb_img=img, channel='s')
    lp_s = pcv.laplace_filter(s, 1, 1)
    shrp_s = pcv.image_subtract(s, lp_s)
    s_eq = pcv.hist_equalization(shrp_s)
    s_thresh = pcv.threshold.binary(gray_img=s_eq,
                                    threshold=215,
                                    max_value=255,
                                    object_type='light')
    s_mblur = pcv.median_blur(gray_img=s_thresh, ksize=5)

    # Process green-magenta channel from LAB colour space
    b = pcv.rgb2gray_lab(rgb_img=img, channel='a')
    b_lp = pcv.laplace_filter(b, 1, 1)
    b_shrp = pcv.image_subtract(b, b_lp)
    b_thresh = pcv.threshold.otsu(b_shrp, 255, object_type='dark')

    # Create and apply mask
    bs = pcv.logical_or(bin_img1=s_mblur, bin_img2=b_thresh)
    filled = pcv.fill_holes(bs)
    masked = pcv.apply_mask(img=img, mask=filled, mask_color='white')

    # Extract colour channels from masked image
    masked_a = pcv.rgb2gray_lab(rgb_img=masked, channel='a')
    masked_b = pcv.rgb2gray_lab(rgb_img=masked, channel='b')

    # Threshold the green-magenta and blue images
    maskeda_thresh = pcv.threshold.binary(gray_img=masked_a,
                                          threshold=115,
                                          max_value=255,
                                          object_type='dark')
    maskeda_thresh1 = pcv.threshold.binary(gray_img=masked_a,
                                           threshold=140,
                                           max_value=255,
                                           object_type='light')
    maskedb_thresh = pcv.threshold.binary(gray_img=masked_b,
                                          threshold=128,
                                          max_value=255,
                                          object_type='light')

    # Join the thresholded saturation and blue-yellow images (OR)
    ab1 = pcv.logical_or(bin_img1=maskeda_thresh, bin_img2=maskedb_thresh)
    ab = pcv.logical_or(bin_img1=maskeda_thresh1, bin_img2=ab1)

    # Produce and apply a mask
    opened_ab = pcv.opening(gray_img=ab)
    ab_fill = pcv.fill(bin_img=ab, size=200)
    closed_ab = pcv.closing(gray_img=ab_fill)
    masked2 = pcv.apply_mask(img=masked, mask=bs, mask_color='white')

    # Identify objects
    id_objects, obj_hierarchy = pcv.find_objects(img=masked2, mask=ab_fill)

    # Define region of interest (ROI)
    roi1, roi_hierarchy = pcv.roi.rectangle(img=masked2,
                                            x=250,
                                            y=100,
                                            h=200,
                                            w=200)

    # Decide what objects to keep
    roi_objects, hierarchy3, kept_mask, obj_area = pcv.roi_objects(
        img=img,
        roi_contour=roi1,
        roi_hierarchy=roi_hierarchy,
        object_contour=id_objects,
        obj_hierarchy=obj_hierarchy,
        roi_type='partial')

    # Object combine kept objects
    obj, mask = pcv.object_composition(img=img,
                                       contours=roi_objects,
                                       hierarchy=hierarchy3)

    ############### Analysis ################

    outfile = False
    if args.writeimg == True:
        outfile = args.outdir + "/" + filename

    # Analyze the plant
    analysis_image = pcv.analyze_object(img=img, obj=obj, mask=mask)
    color_histogram = pcv.analyze_color(rgb_img=img,
                                        mask=kept_mask,
                                        hist_plot_type='all')
    top_x, bottom_x, center_v_x = pcv.x_axis_pseudolandmarks(img=img,
                                                             obj=obj,
                                                             mask=mask)
    top_y, bottom_y, center_v_y = pcv.y_axis_pseudolandmarks(img=img,
                                                             obj=obj,
                                                             mask=mask)

    # Print results of the analysis
    pcv.print_results(filename=args.result)
    pcv.output_mask(img,
                    kept_mask,
                    filename,
                    outdir=args.outdir,
                    mask_only=True)
def main():
    # Initialize options
    args = options()
    # Set PlantCV debug mode to input debug method
    pcv.params.debug = args.debug

    # Use PlantCV to read in the input image. The function outputs an image as a NumPy array, the path to the file,
    # and the image filename
    img, path, filename = pcv.readimage(filename=args.image)

    # ## Segmentation

    # ### Saturation channel
    # Convert the RGB image to HSV colorspace and extract the saturation channel
    s = pcv.rgb2gray_hsv(rgb_img=img, channel='s')

    # Use a binary threshold to set an inflection value where all pixels in the grayscale saturation image below the
    # threshold get set to zero (pure black) and all pixels at or above the threshold get set to 255 (pure white)
    s_thresh = pcv.threshold.binary(gray_img=s, threshold=80, max_value=255, object_type='light')

    # ### Blue-yellow channel
    # Convert the RGB image to LAB colorspace and extract the blue-yellow channel
    b = pcv.rgb2gray_lab(rgb_img=img, channel='b')

    # Use a binary threshold to set an inflection value where all pixels in the grayscale blue-yellow image below the
    # threshold get set to zero (pure black) and all pixels at or above the threshold get set to 255 (pure white)
    b_thresh = pcv.threshold.binary(gray_img=b, threshold=134, max_value=255, object_type='light')

    # ### Green-magenta channel
    # Convert the RGB image to LAB colorspace and extract the green-magenta channel
    a = pcv.rgb2gray_lab(rgb_img=img, channel='a')

    # In the green-magenta image the plant pixels are darker than the background. Setting object_type="dark" will
    # invert the image first and then use a binary threshold to set an inflection value where all pixels in the
    # grayscale green-magenta image below the threshold get set to zero (pure black) and all pixels at or above the
    # threshold get set to 255 (pure white)
    a_thresh = pcv.threshold.binary(gray_img=a, threshold=122, max_value=255, object_type='dark')

    # Combine the binary images for the saturation and blue-yellow channels. The "or" operator returns a binary image
    # that is white when a pixel was white in either or both input images
    bs = pcv.logical_or(bin_img1=s_thresh, bin_img2=b_thresh)

    # Combine the binary images for the combined saturation and blue-yellow channels and the green-magenta channel.
    # The "or" operator returns a binary image that is white when a pixel was white in either or both input images
    bsa = pcv.logical_or(bin_img1=bs, bin_img2=a_thresh)

    # The combined binary image labels plant pixels well but the background still has pixels labeled as foreground.
    # Small white noise (salt) in the background can be removed by filtering white objects in the image by size and
    # setting a size threshold where smaller objects can be removed
    bsa_fill1 = pcv.fill(bin_img=bsa, size=15)  # Fill small noise

    # Before more stringent size filtering is done we want to connect plant parts that may still be disconnected from
    # the main plant. Use a dilation to expand the boundary of white regions. Ksize is the size of a box scanned
    # across the image and i is the number of times a scan is done
    bsa_fill2 = pcv.dilate(gray_img=bsa_fill1, ksize=3, i=3)

    # Remove small objects by size again but use a higher threshold
    bsa_fill3 = pcv.fill(bin_img=bsa_fill2, size=250)

    # Use the binary image to identify objects or connected components.
    id_objects, obj_hierarchy = pcv.find_objects(img=img, mask=bsa_fill3)

    # Because the background still contains pixels labeled as foreground, the object list contains background.
    # Because these images were collected in an automated system the plant is always centered in the image at the
    # same position each time. Define a region of interest (ROI) to set the area where we expect to find plant
    # pixels. PlantCV can make simple ROI shapes like rectangles, circles, etc. but here we use a custom ROI to fit a
    # polygon around the plant area
    roi_custom, roi_hier_custom = pcv.roi.custom(img=img, vertices=[[1085, 1560], [1395, 1560], [1395, 1685],
                                                                    [1890, 1744], [1890, 25], [600, 25], [615, 1744],
                                                                    [1085, 1685]])

    # Use the ROI to filter out objects found outside the ROI. When `roi_type = "cutto"` objects outside the ROI are
    # cropped out. The default `roi_type` is "partial" which allows objects to overlap the ROI and be retained
    roi_objects, hierarchy, kept_mask, obj_area = pcv.roi_objects(img=img, roi_contour=roi_custom,
                                                                  roi_hierarchy=roi_hier_custom,
                                                                  object_contour=id_objects,
                                                                  obj_hierarchy=obj_hierarchy, roi_type='cutto')

    # Filter remaining objects by size again to remove any remaining background objects
    filled_mask1 = pcv.fill(bin_img=kept_mask, size=350)

    # Use a closing operation to first dilate (expand) and then erode (shrink) the plant to fill in any additional
    # gaps in leaves or stems
    filled_mask2 = pcv.closing(gray_img=filled_mask1)

    # Remove holes or dark spot noise (pepper) in the plant binary image
    filled_mask3 = pcv.fill_holes(filled_mask2)

    # With the clean binary image identify the contour of the plant
    id_objects, obj_hierarchy = pcv.find_objects(img=img, mask=filled_mask3)

    # Because a plant or object of interest may be composed of multiple contours, it is required to combine all
    # remaining contours into a single contour before measurements can be done
    obj, mask = pcv.object_composition(img=img, contours=id_objects, hierarchy=obj_hierarchy)

    # ## Measurements PlantCV has several built-in measurement or analysis methods. Here, basic measurements of size
    # and shape are done. Additional typical modules would include plant height (`pcv.analyze_bound_horizontal`) and
    # color (`pcv.analyze_color`)
    shape_img = pcv.analyze_object(img=img, obj=obj, mask=mask)

    # Save the shape image if requested
    if args.writeimg:
        outfile = os.path.join(args.outdir, filename[:-4] + "_shapes.png")
        pcv.print_image(img=shape_img, filename=outfile)

    # ## Morphology workflow

    # Update a few PlantCV parameters for plotting purposes
    pcv.params.text_size = 1.5
    pcv.params.text_thickness = 5
    pcv.params.line_thickness = 15

    # Convert the plant mask into a "skeletonized" image where each path along the stem and leaves are a single pixel
    # wide
    skel = pcv.morphology.skeletonize(mask=mask)

    # Sometimes wide parts of leaves or stems are skeletonized in the direction perpendicular to the main path. These
    # "barbs" or "spurs" can be removed by pruning the skeleton to remove small paths. Pruning will also separate the
    # individual path segments (leaves and stem parts)
    pruned, segmented_img, segment_objects = pcv.morphology.prune(skel_img=skel, size=30, mask=mask)
    pruned, segmented_img, segment_objects = pcv.morphology.prune(skel_img=pruned, size=3, mask=mask)

    # Leaf and stem segments above are separated but only into individual paths. We can sort the segments into stem
    # and leaf paths by identifying primary segments (stems; those that end in a branch point) and secondary segments
    # (leaves; those that begin at a branch point and end at a tip point)
    leaf_objects, other_objects = pcv.morphology.segment_sort(skel_img=pruned, objects=segment_objects, mask=mask)

    # Label the segment unique IDs
    segmented_img, labeled_id_img = pcv.morphology.segment_id(skel_img=pruned, objects=leaf_objects, mask=mask)

    # Measure leaf insertion angles. Measures the angle between a line fit through the stem paths and a line fit
    # through the first `size` points of each leaf path
    labeled_angle_img = pcv.morphology.segment_insertion_angle(skel_img=pruned, segmented_img=segmented_img,
                                                               leaf_objects=leaf_objects, stem_objects=other_objects,
                                                               size=22)

    # Save leaf angle image if requested
    if args.writeimg:
        outfile = os.path.join(args.outdir, filename[:-4] + "_leaf_insertion_angles.png")
        pcv.print_image(img=labeled_angle_img, filename=outfile)

    # ## Other potential morphological measurements There are many other functions that extract data from within the
    # morphology sub-package of PlantCV. For our purposes, we are most interested in the relative angle between each
    # leaf and the stem which we measure with `plantcv.morphology.segment_insertion_angle`. However, the following
    # cells show some of the other traits that we are able to measure from images that can be succesfully sorted into
    # primary and secondary segments.

    # Segment the plant binary mask using the leaf and stem segments. Allows for the measurement of individual leaf
    # areas
    # filled_img = pcv.morphology.fill_segments(mask=mask, objects=leaf_objects)

    # Measure the path length of each leaf (geodesic distance)
    # labeled_img2 = pcv.morphology.segment_path_length(segmented_img=segmented_img, objects=leaf_objects)

    # Measure the straight-line, branch point to tip distance (Euclidean) for each leaf
    # labeled_img3 = pcv.morphology.segment_euclidean_length(segmented_img=segmented_img, objects=leaf_objects)

    # Measure the curvature of each leaf (Values closer to 1 indicate that a segment is a straight line while larger
    # values indicate the segment has more curvature)
    # labeled_img4 = pcv.morphology.segment_curvature(segmented_img=segmented_img, objects=leaf_objects)

    # Measure absolute leaf angles (angle of linear regression line fit to each leaf object) Note: negative values
    # signify leaves to the left of the stem, positive values signify leaves to the right of the stem
    # labeled_img5 = pcv.morphology.segment_angle(segmented_img=segmented_img, objects=leaf_objects)

    # Measure leaf curvature in degrees
    # labeled_img6 = pcv.morphology.segment_tangent_angle(segmented_img=segmented_img, objects=leaf_objects, size=35)

    # Measure stem characteristics like stem angle and length
    # stem_img = pcv.morphology.analyze_stem(rgb_img=img, stem_objects=other_objects)

    # Remove unneeded observations (hack)
    _ = pcv.outputs.observations.pop("tips")
    _ = pcv.outputs.observations.pop("branch_pts")
    angles = pcv.outputs.observations["segment_insertion_angle"]["value"]
    remove_indices = []
    for i, value in enumerate(angles):
        if value == "NA":
            remove_indices.append(i)
    remove_indices.sort(reverse=True)
    for i in remove_indices:
        _ = pcv.outputs.observations["segment_insertion_angle"]["value"].pop(i)

    # ## Save the results out to file for downsteam analysis
    pcv.print_results(filename=args.result)
Пример #34
0
def segmentation(imgW, imgNIR, shape):
    # VIS example from PlantCV with few modifications

    # Higher value = more strict selection
    s_threshold = 165
    b_threshold = 200

    # Read image
    img = imread(imgW)
    #img = cvtColor(img, COLOR_BGR2RGB)
    imgNIR = imread(imgNIR)
    #imgNIR = cvtColor(imgNIR, COLOR_BGR2RGB)
    #img, path, img_filename = pcv.readimage(filename=imgW, mode="native")
    #imgNIR, pathNIR, imgNIR_filename = pcv.readimage(filename=imgNIR, mode="native")

    # Convert RGB to HSV and extract the saturation channel
    s = pcv.rgb2gray_hsv(rgb_img=img, channel='s')

    # Threshold the saturation image
    s_thresh = pcv.threshold.binary(gray_img=s, threshold=s_threshold, max_value=255, object_type='light')

    # Median Blur
    s_mblur = pcv.median_blur(gray_img=s_thresh, ksize=5)
    s_cnt = pcv.median_blur(gray_img=s_thresh, ksize=5)

    # Convert RGB to LAB and extract the Blue channel
    b = pcv.rgb2gray_lab(rgb_img=img, channel='b')

    # Threshold the blue image ORIGINAL 160
    b_thresh = pcv.threshold.binary(gray_img=b, threshold=b_threshold, max_value=255, object_type='light')
    b_cnt = pcv.threshold.binary(gray_img=b, threshold=b_threshold, max_value=255, object_type='light')

    # Join the thresholded saturation and blue-yellow images
    bs = pcv.logical_or(bin_img1=s_mblur, bin_img2=b_cnt)

    # Apply Mask (for VIS images, mask_color=white)
    masked = pcv.apply_mask(img=img, mask=bs, mask_color='white')

    # Convert RGB to LAB and extract the Green-Magenta and Blue-Yellow channels
    masked_a = pcv.rgb2gray_lab(rgb_img=masked, channel='a')
    masked_b = pcv.rgb2gray_lab(rgb_img=masked, channel='b')

    # Threshold the green-magenta and blue images
    # 115
    # 135
    # 128
    maskeda_thresh = pcv.threshold.binary(gray_img=masked_a, threshold=115, max_value=255, object_type='dark')
    maskeda_thresh1 = pcv.threshold.binary(gray_img=masked_a, threshold=135, max_value=255, object_type='light')
    maskedb_thresh = pcv.threshold.binary(gray_img=masked_b, threshold=128, max_value=255, object_type='light')

    # Join the thresholded saturation and blue-yellow images (OR)
    ab1 = pcv.logical_or(bin_img1=maskeda_thresh, bin_img2=maskedb_thresh)
    ab = pcv.logical_or(bin_img1=maskeda_thresh1, bin_img2=ab1)

    # Fill small objects
    ab_fill = pcv.fill(bin_img=ab, size=200)

    # Apply mask (for VIS images, mask_color=white)
    masked2 = pcv.apply_mask(img=masked, mask=ab_fill, mask_color='white')

    # Identify objects
    id_objects, obj_hierarchy = pcv.find_objects(img=masked2, mask=ab_fill)

    # Define ROI
    height = shape[0]
    width = shape[1]
    roi1, roi_hierarchy= pcv.roi.rectangle(img=masked2, x=0, y=0, h=height, w=width)

    # Decide which objects to keep
    roi_objects, hierarchy3, kept_mask, obj_area = pcv.roi_objects(img=img, roi_contour=roi1, 
                                                               roi_hierarchy=roi_hierarchy, 
                                                               object_contour=id_objects, 
                                                               obj_hierarchy=obj_hierarchy,
                                                               roi_type='partial')

    # Object combine kept objects
    obj, mask = pcv.object_composition(img=img, contours=roi_objects, hierarchy=hierarchy3)
    
    # Filling holes in the mask, works great for alive plants, not so good for dead plants
    filled_mask = pcv.fill_holes(mask)

    final = pcv.apply_mask(img=imgNIR, mask=mask, mask_color='white')
    pcv.print_image(final, "./segment/segment-temp.png")
Пример #35
0
def find_branch_pts(skel_img, mask=None, label="default"):
    """
    The branching algorithm was inspired by Jean-Patrick Pommier: https://gist.github.com/jeanpat/5712699
    Inputs:
    skel_img    = Skeletonized image
    mask        = (Optional) binary mask for debugging. If provided, debug image will be overlaid on the mask.
    label        = optional label parameter, modifies the variable name of observations recorded

    Returns:
    branch_pts_img = Image with just branch points, rest 0

    :param skel_img: numpy.ndarray
    :param mask: np.ndarray
    :param label: str
    :return branch_pts_img: numpy.ndarray
    """

    # In a kernel: 1 values line up with 255s, -1s line up with 0s, and 0s correspond to don't care
    # T like branch points
    t1 = np.array([[-1, 1, -1], [1, 1, 1], [-1, -1, -1]])
    t2 = np.array([[1, -1, 1], [-1, 1, -1], [1, -1, -1]])
    t3 = np.rot90(t1)
    t4 = np.rot90(t2)
    t5 = np.rot90(t3)
    t6 = np.rot90(t4)
    t7 = np.rot90(t5)
    t8 = np.rot90(t6)

    # Y like branch points
    y1 = np.array([[1, -1, 1], [0, 1, 0], [0, 1, 0]])
    y2 = np.array([[-1, 1, -1], [1, 1, 0], [-1, 0, 1]])
    y3 = np.rot90(y1)
    y4 = np.rot90(y2)
    y5 = np.rot90(y3)
    y6 = np.rot90(y4)
    y7 = np.rot90(y5)
    y8 = np.rot90(y6)
    kernels = [t1, t2, t3, t4, t5, t6, t7, t8, y1, y2, y3, y4, y5, y6, y7, y8]

    branch_pts_img = np.zeros(skel_img.shape[:2], dtype=int)

    # Store branch points
    for kernel in kernels:
        branch_pts_img = np.logical_or(
            cv2.morphologyEx(skel_img,
                             op=cv2.MORPH_HITMISS,
                             kernel=kernel,
                             borderType=cv2.BORDER_CONSTANT,
                             borderValue=0), branch_pts_img)

    # Switch type to uint8 rather than bool
    branch_pts_img = branch_pts_img.astype(np.uint8) * 255

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

    # Make debugging image
    if mask is None:
        dilated_skel = dilate(skel_img, params.line_thickness, 1)
        branch_plot = cv2.cvtColor(dilated_skel, cv2.COLOR_GRAY2RGB)
    else:
        # Make debugging image on mask
        mask_copy = mask.copy()
        branch_plot = cv2.cvtColor(mask_copy, cv2.COLOR_GRAY2RGB)
        skel_obj, skel_hier = find_objects(skel_img, skel_img)
        cv2.drawContours(branch_plot,
                         skel_obj,
                         -1, (150, 150, 150),
                         params.line_thickness,
                         lineType=8,
                         hierarchy=skel_hier)

    branch_objects, _ = find_objects(branch_pts_img, branch_pts_img)

    # Initialize list of tip data points
    branch_list = []
    branch_labels = []
    for i, branch in enumerate(branch_objects):
        x, y = branch.ravel()[:2]
        coord = (int(x), int(y))
        branch_list.append(coord)
        branch_labels.append(i)
        cv2.circle(branch_plot, (x, y), params.line_thickness, (255, 0, 255),
                   -1)

    outputs.add_observation(
        sample=label,
        variable='branch_pts',
        trait='list of branch-point coordinates identified from a skeleton',
        method='plantcv.plantcv.morphology.find_branch_pts',
        scale='pixels',
        datatype=list,
        value=branch_list,
        label=branch_labels)

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

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

    return branch_pts_img
Пример #36
0
def main():
    args = options()

    os.chdir(args.outdir)

    # Read RGB image
    img, path, filename = pcv.readimage(args.image, mode="native")

    # Get metadata from file name
    geno_name = filename.split("}{")
    geno_name = geno_name[5]
    geno_name = geno_name.split("_")
    geno_name = geno_name[1]

    day = filename.split("}{")
    day = day[7]
    day = day.split("_")
    day = day[1]
    day = day.split("}")
    day = day[0]

    plot = filename.split("}{")
    plot = plot[0]
    plot = plot.split("_")
    plot = plot[1]

    exp_name = filename.split("}{")
    exp_name = exp_name[1]
    exp_name = exp_name.split("_")
    exp_name = exp_name[1]

    treat_name = filename.split("}{")
    treat_name = treat_name[6]

    # Create masks using Naive Bayes Classifier and PDFs file
    masks = pcv.naive_bayes_classifier(img, args.pdfs)

    # The following code will identify the racks in the image, find the top edge, and choose a line along the edge to pick a y coordinate to trim any soil/pot pixels identified as plant material.

    # Convert RGB to HSV and extract the Value channel
    v = pcv.rgb2gray_hsv(img, 'v')

    # Threshold the Value image
    v_thresh = pcv.threshold.binary(v, 98, 255, 'light')

    # Dilate mask to fill holes
    dilate_racks = pcv.dilate(v_thresh, 2, 1)

    # Fill in small objects
    mask = np.copy(dilate_racks)
    fill_racks = pcv.fill(mask, 100000)

    #edge detection
    edges = cv2.Canny(fill_racks, 60, 180)

    #write all the straight lines from edge detection
    lines = cv2.HoughLinesP(edges,
                            rho=1,
                            theta=1 * np.pi / 180,
                            threshold=150,
                            minLineLength=50,
                            maxLineGap=15)
    N = lines.shape[0]
    for i in range(N):
        x1 = lines[i][0][0]
        y1 = lines[i][0][1]
        x2 = lines[i][0][2]
        y2 = lines[i][0][3]
        cv2.line(img, (x1, y1), (x2, y2), (255, 0, 0), 2)

    # keep only horizontal lines
    N = lines.shape[0]
    tokeep = []
    for i in range(N):
        want = (abs(lines[i][0][1] - lines[i][0][3])) <= 10
        tokeep.append(want)

    lines = lines[tokeep]

    # keep only lines in lower half of image
    N = lines.shape[0]
    tokeep = []
    for i in range(N):
        want = 3100 > lines[i][0][1] > 2300
        tokeep.append(want)

    lines = lines[tokeep]

    # assign lines to positions around plants
    N = lines.shape[0]
    tokeep = []
    left = []
    mid = []
    right = []

    for i in range(N):
        leftones = lines[i][0][2] <= 2000
        left.append(leftones)

        midones = 3000 > lines[i][0][2] > 2000
        mid.append(midones)

        rightones = lines[i][0][0] >= 3300
        right.append(rightones)

    right = lines[right]
    left = lines[left]
    mid = lines[mid]

    # choose y values for right left mid adding some pixels to go about the pot (subtract because of orientation of axis)
    y_left = left[0][0][3] - 50
    y_mid = mid[0][0][3] - 50
    y_right = right[0][0][3] - 50

    # reload original image to write new lines on
    img, path, filename = pcv.readimage(args.image)

    # write horizontal lines on image
    cv2.line(img, (left[0][0][0], left[0][0][1]),
             (left[0][0][2], left[0][0][3]), (255, 255, 51), 2)
    cv2.line(img, (mid[0][0][0], mid[0][0][1]), (mid[0][0][2], mid[0][0][3]),
             (255, 255, 51), 2)
    cv2.line(img, (right[0][0][0], right[0][0][1]),
             (right[0][0][2], right[0][0][3]), (255, 255, 51), 2)

    # Add masks together
    added = masks["healthy"] + masks["necrosis"] + masks["stem"]

    # Dilate mask to fill holes
    dilate_img = pcv.dilate(added, 2, 1)

    # Fill in small objects
    mask = np.copy(dilate_img)
    fill_img = pcv.fill(mask, 400)

    ret, inverted = cv2.threshold(fill_img, 75, 255, cv2.THRESH_BINARY_INV)

    # Dilate mask to fill holes of plant
    dilate_inv = pcv.dilate(inverted, 2, 1)

    # Fill in small objects of plant
    mask2 = np.copy(dilate_inv)
    fill_plant = pcv.fill(mask2, 20)

    inverted_img = pcv.invert(fill_plant)

    # Identify objects
    id_objects, obj_hierarchy = pcv.find_objects(img, inverted_img)

    # Define ROIs
    roi_left, roi_hierarchy_left = pcv.roi.rectangle(280, 1280, 1275, 1200,
                                                     img)
    roi_mid, roi_hierarchy_mid = pcv.roi.rectangle(1900, 1280, 1275, 1200, img)
    roi_right, roi_hierarchy_right = pcv.roi.rectangle(3600, 1280, 1275, 1200,
                                                       img)

    # Decide which objects to keep
    roi_objects_left, roi_obj_hierarchy_left, kept_mask_left, obj_area_left = pcv.roi_objects(
        img, 'partial', roi_left, roi_hierarchy_left, id_objects,
        obj_hierarchy)
    roi_objects_mid, roi_obj_hierarchy_mid, kept_mask_mid, obj_area_mid = pcv.roi_objects(
        img, 'partial', roi_mid, roi_hierarchy_mid, id_objects, obj_hierarchy)
    roi_objects_right, roi_obj_hierarchy_right, kept_mask_right, obj_area_right = pcv.roi_objects(
        img, 'partial', roi_right, roi_hierarchy_right, id_objects,
        obj_hierarchy)

    # Combine objects
    obj_r, mask_r = pcv.object_composition(img, roi_objects_right,
                                           roi_obj_hierarchy_right)
    obj_m, mask_m = pcv.object_composition(img, roi_objects_mid,
                                           roi_obj_hierarchy_mid)
    obj_l, mask_l = pcv.object_composition(img, roi_objects_left,
                                           roi_obj_hierarchy_left)

    def analyze_bound_horizontal2(img,
                                  obj,
                                  mask,
                                  line_position,
                                  filename=False):

        ori_img = np.copy(img)

        # Draw line horizontal line through bottom of image, that is adjusted to user input height
        if len(np.shape(ori_img)) == 3:
            iy, ix, iz = np.shape(ori_img)
        else:
            iy, ix = np.shape(ori_img)
        size = (iy, ix)
        size1 = (iy, ix, 3)
        background = np.zeros(size, dtype=np.uint8)
        wback = np.zeros(size1, dtype=np.uint8)
        x_coor = int(ix)
        y_coor = int(iy) - int(line_position)
        rec_corner = int(iy - 2)
        rec_point1 = (1, rec_corner)
        rec_point2 = (x_coor - 2, y_coor - 2)
        cv2.rectangle(background, rec_point1, rec_point2, (255), 1)
        below_contour, below_hierarchy = cv2.findContours(
            background, cv2.RETR_TREE, cv2.CHAIN_APPROX_NONE)[-2:]

        below = []
        above = []
        mask_nonzerox, mask_nonzeroy = np.nonzero(mask)
        obj_points = np.vstack((mask_nonzeroy, mask_nonzerox))
        obj_points1 = np.transpose(obj_points)

        for i, c in enumerate(obj_points1):
            xy = tuple(c)
            pptest = cv2.pointPolygonTest(below_contour[0],
                                          xy,
                                          measureDist=False)
            if pptest == 1:
                below.append(xy)
                cv2.circle(ori_img, xy, 1, (0, 0, 255))
                cv2.circle(wback, xy, 1, (0, 0, 0))
            else:
                above.append(xy)
                cv2.circle(ori_img, xy, 1, (0, 255, 0))
                cv2.circle(wback, xy, 1, (255, 255, 255))

        return wback

    ori_img = np.copy(img)

    # Draw line horizontal line through bottom of image, that is adjusted to user input height
    if len(np.shape(ori_img)) == 3:
        iy, ix, iz = np.shape(ori_img)
    else:
        iy, ix = np.shape(ori_img)

    if obj_r is not None:
        wback_r = analyze_bound_horizontal2(img, obj_r, mask_r, iy - y_right)
    if obj_m is not None:
        wback_m = analyze_bound_horizontal2(img, obj_m, mask_m, iy - y_mid)
    if obj_l is not None:
        wback_l = analyze_bound_horizontal2(img, obj_l, mask_l, iy - y_left)

    threshold_light = pcv.threshold.binary(img, 1, 1, 'dark')

    if obj_r is not None:
        fgmask_r = pcv.background_subtraction(wback_r, threshold_light)
    if obj_m is not None:
        fgmask_m = pcv.background_subtraction(wback_m, threshold_light)
    if obj_l is not None:
        fgmask_l = pcv.background_subtraction(wback_l, threshold_light)

    if obj_l is not None:
        id_objects_left, obj_hierarchy_left = pcv.find_objects(img, fgmask_l)
    if obj_m is not None:
        id_objects_mid, obj_hierarchy_mid = pcv.find_objects(img, fgmask_m)
    if obj_r is not None:
        id_objects_right, obj_hierarchy_right = pcv.find_objects(img, fgmask_r)

    # Combine objects
    if obj_r is not None:
        obj_r2, mask_r2 = pcv.object_composition(img, id_objects_right,
                                                 obj_hierarchy_right)
    if obj_m is not None:
        obj_m2, mask_m2 = pcv.object_composition(img, id_objects_mid,
                                                 obj_hierarchy_mid)
    if obj_l is not None:
        obj_l2, mask_l2 = pcv.object_composition(img, id_objects_left,
                                                 obj_hierarchy_left)

    # Shape measurements
    if obj_l is not None:
        shape_header_left, shape_data_left, shape_img_left = pcv.analyze_object(
            img, obj_l2, fgmask_l,
            geno_name + '_' + plot + '_' + 'A' + '_' + day + '_' + 'shape.jpg')

    if obj_r is not None:
        shape_header_right, shape_data_right, shape_img_right = pcv.analyze_object(
            img, obj_r2, fgmask_r,
            geno_name + '_' + plot + '_' + 'C' + '_' + day + '_' + 'shape.jpg')

    if obj_m is not None:
        shape_header_mid, shape_data_mid, shape_img_mid = pcv.analyze_object(
            img, obj_m2, fgmask_m,
            geno_name + '_' + plot + '_' + 'B' + '_' + day + '_' + 'shape.jpg')

    # Color data
    if obj_r is not None:
        color_header_right, color_data_right, norm_slice_right = pcv.analyze_color(
            img, fgmask_r, 256, None, 'v', 'img',
            geno_name + '_' + plot + '_' + 'C' + '_' + day + '_' + 'color.jpg')

    if obj_m is not None:
        color_header_mid, color_data_mid, norm_slice_mid = pcv.analyze_color(
            img, fgmask_m, 256, None, 'v', 'img',
            geno_name + '_' + plot + '_' + 'B' + '_' + day + '_' + 'color.jpg')

    if obj_l is not None:
        color_header_left, color_data_left, norm_slice_left = pcv.analyze_color(
            img, fgmask_l, 256, None, 'v', 'img',
            geno_name + '_' + plot + '_' + 'A' + '_' + day + '_' + 'color.jpg')

    new_header = [
        'experiment', 'day', 'genotype', 'treatment', 'plot', 'plant',
        'percent.necrosis', 'area', 'hull-area', 'solidity', 'perimeter',
        'width', 'height', 'longest_axis', 'center-of-mass-x',
        'center-of-mass-y', 'hull_vertices', 'in_bounds', 'ellipse_center_x',
        'ellipse_center_y', 'ellipse_major_axis', 'ellipse_minor_axis',
        'ellipse_angle', 'ellipse_eccentricity', 'bin-number', 'bin-values',
        'blue', 'green', 'red', 'lightness', 'green-magenta', 'blue-yellow',
        'hue', 'saturation', 'value'
    ]
    table = []
    table.append(new_header)

    added2 = masks["healthy"] + masks["stem"]

    # Object combine kept objects
    if obj_l is not None:
        masked_image_healthy_left = pcv.apply_mask(added2, fgmask_l, 'black')
        masked_image_necrosis_left = pcv.apply_mask(masks["necrosis"],
                                                    fgmask_l, 'black')
        added_obj_left = masked_image_healthy_left + masked_image_necrosis_left

        sample = "A"

        # Calculations
        necrosis_left = np.sum(masked_image_necrosis_left)
        necrosis_percent_left = float(necrosis_left) / np.sum(added_obj_left)

        table.append([
            exp_name, day, geno_name, treat_name, plot, sample,
            round(necrosis_percent_left, 5), shape_data_left[1],
            shape_data_left[2], shape_data_left[3], shape_data_left[4],
            shape_data_left[5], shape_data_left[6], shape_data_left[7],
            shape_data_left[8], shape_data_left[9], shape_data_left[10],
            shape_data_left[11], shape_data_left[12], shape_data_left[13],
            shape_data_left[14], shape_data_left[15], shape_data_left[16],
            shape_data_left[17], '"{}"'.format(color_data_left[1]),
            '"{}"'.format(color_data_left[2]), '"{}"'.format(
                color_data_left[3]), '"{}"'.format(color_data_left[4]),
            '"{}"'.format(color_data_left[5]), '"{}"'.format(
                color_data_left[6]), '"{}"'.format(color_data_left[7]),
            '"{}"'.format(color_data_left[8]), '"{}"'.format(
                color_data_left[9]), '"{}"'.format(color_data_left[10]),
            '"{}"'.format(color_data_left[11])
        ])

    # Object combine kept objects
    if obj_m is not None:
        masked_image_healthy_mid = pcv.apply_mask(added2, fgmask_m, 'black')
        masked_image_necrosis_mid = pcv.apply_mask(masks["necrosis"], fgmask_m,
                                                   'black')
        added_obj_mid = masked_image_healthy_mid + masked_image_necrosis_mid

        sample = "B"

        # Calculations
        necrosis_mid = np.sum(masked_image_necrosis_mid)
        necrosis_percent_mid = float(necrosis_mid) / np.sum(added_obj_mid)

        table.append([
            exp_name, day, geno_name, treat_name, plot, sample,
            round(necrosis_percent_mid,
                  5), shape_data_mid[1], shape_data_mid[2], shape_data_mid[3],
            shape_data_mid[4], shape_data_mid[5], shape_data_mid[6],
            shape_data_mid[7], shape_data_mid[8], shape_data_mid[9],
            shape_data_mid[10], shape_data_mid[11], shape_data_mid[12],
            shape_data_mid[13], shape_data_mid[14], shape_data_mid[15],
            shape_data_mid[16], shape_data_mid[17],
            '"{}"'.format(color_data_mid[1]), '"{}"'.format(color_data_mid[2]),
            '"{}"'.format(color_data_mid[3]), '"{}"'.format(color_data_mid[4]),
            '"{}"'.format(color_data_mid[5]), '"{}"'.format(color_data_mid[6]),
            '"{}"'.format(color_data_mid[7]), '"{}"'.format(color_data_mid[8]),
            '"{}"'.format(color_data_mid[9]), '"{}"'.format(
                color_data_mid[10]), '"{}"'.format(color_data_mid[11])
        ])

    # Object combine kept objects
    if obj_r is not None:
        masked_image_healthy_right = pcv.apply_mask(added2, fgmask_r, 'black')
        masked_image_necrosis_right = pcv.apply_mask(masks["necrosis"],
                                                     fgmask_r, 'black')
        added_obj_right = masked_image_healthy_right + masked_image_necrosis_right

        sample = "C"

        # Calculations
        necrosis_right = np.sum(masked_image_necrosis_right)
        necrosis_percent_right = float(necrosis_right) / np.sum(
            added_obj_right)

        table.append([
            exp_name, day, geno_name, treat_name, plot, sample,
            round(necrosis_percent_right, 5), shape_data_right[1],
            shape_data_right[2], shape_data_right[3], shape_data_right[4],
            shape_data_right[5], shape_data_right[6], shape_data_right[7],
            shape_data_right[8], shape_data_right[9], shape_data_right[10],
            shape_data_right[11], shape_data_right[12], shape_data_right[13],
            shape_data_right[14], shape_data_right[15], shape_data_right[16],
            shape_data_right[17], '"{}"'.format(color_data_right[1]),
            '"{}"'.format(color_data_right[2]), '"{}"'.format(
                color_data_right[3]), '"{}"'.format(color_data_right[4]),
            '"{}"'.format(color_data_right[5]), '"{}"'.format(
                color_data_right[6]), '"{}"'.format(color_data_right[7]),
            '"{}"'.format(color_data_right[8]), '"{}"'.format(
                color_data_right[9]), '"{}"'.format(color_data_right[10]),
            '"{}"'.format(color_data_right[11])
        ])

    if obj_l is not None:
        merged2 = cv2.merge([
            masked_image_healthy_left,
            np.zeros(np.shape(masks["healthy"]), dtype=np.uint8),
            masked_image_necrosis_left
        ])  #blue, green, red
        pcv.print_image(
            merged2, geno_name + '_' + plot + '_' + 'A' + '_' + day + '_' +
            'merged.jpg')
    if obj_m is not None:
        merged3 = cv2.merge([
            masked_image_healthy_mid,
            np.zeros(np.shape(masks["healthy"]), dtype=np.uint8),
            masked_image_necrosis_mid
        ])  #blue, green, red
        pcv.print_image(
            merged3, geno_name + '_' + plot + '_' + 'B' + '_' + day + '_' +
            'merged.jpg')
    if obj_r is not None:
        merged4 = cv2.merge([
            masked_image_healthy_right,
            np.zeros(np.shape(masks["healthy"]), dtype=np.uint8),
            masked_image_necrosis_right
        ])  #blue, green, red
        pcv.print_image(
            merged4, geno_name + '_' + plot + '_' + 'C' + '_' + day + '_' +
            'merged.jpg')

    # Save area results to file (individual csv files for one image...)
    file_name = filename.split("}{")
    file_name = file_name[0] + "}{" + file_name[5] + "}{" + file_name[7]

    outfile = str(file_name[:-4]) + 'csv'
    with open(outfile, 'w') as f:
        for row in table:
            f.write(','.join(map(str, row)) + '\n')

    print(filename)
Пример #37
0
def check_cycles(skel_img):
    """ Check for cycles in a skeleton image
    Inputs:
    skel_img     = Skeletonized image

    Returns:
    cycle_img    = Image with cycles identified

    :param skel_img: numpy.ndarray
    :return cycle_img: numpy.ndarray
    """

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

    # Create the mask needed for cv2.floodFill, must be larger than the image
    h, w = skel_img.shape[:2]
    mask = np.zeros((h + 2, w + 2), np.uint8)

    # Copy the skeleton since cv2.floodFill will draw on it
    skel_copy = skel_img.copy()
    cv2.floodFill(skel_copy, mask=mask, seedPoint=(0, 0), newVal=255)

    # Invert so the holes are white and background black
    just_cycles = cv2.bitwise_not(skel_copy)

    # Erode slightly so that cv2.findContours doesn't think diagonal pixels are separate contours
    just_cycles = erode(just_cycles, 2, 1)

    # Use pcv.find_objects to turn plots of holes into countable contours
    cycle_objects, cycle_hierarchies = find_objects(just_cycles, just_cycles)

    # Count the number of holes
    num_cycles = len(cycle_objects)

    # Make debugging image
    cycle_img = skel_img.copy()
    cycle_img = dilate(cycle_img, params.line_thickness, 1)
    cycle_img = cv2.cvtColor(cycle_img, cv2.COLOR_GRAY2RGB)
    rand_color = color_palette(num_cycles)
    for i, cnt in enumerate(cycle_objects):
        cv2.drawContours(cycle_img, cycle_objects, i, rand_color[i], params.line_thickness, lineType=8,
                         hierarchy=cycle_hierarchies)

    # Store Cycle Data
    outputs.add_observation(variable='num_cycles', trait='number of cycles',
                            method='plantcv.plantcv.morphology.check_cycles', scale='none', datatype=int,
                            value=num_cycles, label='none')

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

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

    return cycle_img