Beispiel #1
0
def vismask(img):

    a_img = pcv.rgb2gray_lab(img, channel='a')
    thresh_a = pcv.threshold.binary(a_img, 124, 255, 'dark')
    b_img = pcv.rgb2gray_lab(img, channel='b')
    thresh_b = pcv.threshold.binary(b_img, 127, 255, 'light')

    mask = pcv.logical_and(thresh_a, thresh_b)
    mask = pcv.fill(mask, 800)
    final_mask = pcv.dilate(mask, 2, 1)

    return final_mask
Beispiel #2
0
b = pcv.rgb2gray_lab(rgb_img=img1, channel='b')
b_thresh = pcv.threshold.binary(gray_img=b,
                                threshold=135,
                                max_value=255,
                                object_type='light')

#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)
Beispiel #3
0
def segment_sort(skel_img, objects, mask=None, first_stem=True):
    """ Calculate segment curvature as defined by the ratio between geodesic and euclidean distance

        Inputs:
        skel_img          = Skeletonized image
        objects           = List of contours
        mask              = (Optional) binary mask for debugging. If provided, debug image will be overlaid on the mask.
        first_stem        = (Optional) if True, then the first (bottom) segment always gets classified as stem

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

        :param skel_img: numpy.ndarray
        :param objects: list
        :param mask: numpy.ndarray
        :param first_stem: bool
        :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_img = find_tips(skel_img)
    tips_img = dilate(tips_img, 3, 1)

    # Loop through segment contours
    for i, cnt in enumerate(objects):
        segment_plot = np.zeros(skel_img.shape[:2], np.uint8)
        cv2.drawContours(segment_plot, objects, i, 255, 1, lineType=8)
        is_leaf = False
        overlap_img = logical_and(segment_plot, tips_img)

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

        # Sort segments
        else:

            if np.sum(overlap_img) > 0:
                secondary_objects.append(cnt)
                is_leaf = True
            else:
                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
  sftp = client.open_sftp()
  with sftp.open('/plantdb/ftp-2019/{dbname}/{path}'.format(dbname=dbname,path=path)) as f:

    file_size = f.stat().st_size
    f.prefetch(file_size)
    img = cv2.imdecode(np.frombuffer(f.read(file_size), np.uint8), 1)



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

  # Threshold the saturation image removing highs and lows and join
  s_thresh_1 = pcv.threshold.binary(gray_img=s, threshold=10, max_value=255, object_type='light')
  s_thresh_2 = pcv.threshold.binary(gray_img=s, threshold=245, max_value=255, object_type='dark')
  s_thresh = pcv.logical_and(bin_img1=s_thresh_1, bin_img2=s_thresh_2)

  # Median Blur
  s_mblur = 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
  b_cnt = pcv.threshold.binary(gray_img=b, threshold=128, max_value=255, object_type='light')

  # Fill small objects
  b_fill = pcv.fill(b_cnt, 10)

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
def main():
    # create options object for argument parsing
    args = options()
    # set device
    device = 0
    # set debug
    pcv.params.debug = args.debug

    outfile = False
    if args.writeimg:
        outfile = os.path.join(args.outdir, os.path.basename(args.image)[:-4])

    # read in image
    img, path, filename = pcv.readimage(filename=args.image, debug=args.debug)

    # read in a background image for each zoom level
    config_file = open(args.bkg, 'r')
    config = json.load(config_file)
    config_file.close()
    if "z1500" in args.image:
        bkg_image = config["z1500"]
    elif "z2500" in args.image:
        bkg_image = config["z2500"]
    else:
        pcv.fatal_error("Image {0} has an unsupported zoom level.".format(args.image))

    bkg, bkg_path, bkg_filename = pcv.readimage(filename=bkg_image, debug=args.debug)

    # Detect edges in the background image
    device, bkg_sat = pcv.rgb2gray_hsv(img=bkg, channel="s", device=device, debug=args.debug)
    device += 1
    bkg_edges = feature.canny(bkg_sat)
    if args.debug == "print":
        pcv.print_image(img=bkg_edges, filename=str(device) + '_background_edges.png')
    elif args.debug == "plot":
        pcv.plot_image(img=bkg_edges, cmap="gray")

    # Close background edge contours
    bkg_edges_closed = ndi.binary_closing(bkg_edges)
    device += 1
    if args.debug == "print":
        pcv.print_image(img=bkg_edges_closed, filename=str(device) + '_closed_background_edges.png')
    elif args.debug == "plot":
        pcv.plot_image(img=bkg_edges_closed, cmap="gray")

    # Fill in closed contours in background
    bkg_fill_contours = ndi.binary_fill_holes(bkg_edges_closed)
    device += 1
    if args.debug == "print":
        pcv.print_image(img=bkg_fill_contours, filename=str(device) + '_filled_background_edges.png')
    elif args.debug == "plot":
        pcv.plot_image(img=bkg_fill_contours, cmap="gray")

    # Naive Bayes image classification/segmentation
    device, mask = pcv.naive_bayes_classifier(img=img, pdf_file=args.pdf, device=device, debug=args.debug)

    # Do a light cleaning of the plant mask to remove small objects
    cleaned = morphology.remove_small_objects(mask["plant"].astype(bool), 2)
    device += 1
    if args.debug == "print":
        pcv.print_image(img=cleaned, filename=str(device) + '_cleaned_mask.png')
    elif args.debug == "plot":
        pcv.plot_image(img=cleaned, cmap="gray")

    # Convert the input image to a saturation channel grayscale image
    device, sat = pcv.rgb2gray_hsv(img=img, channel="s", device=device, debug=args.debug)

    # Detect edges in the saturation image
    edges = feature.canny(sat)
    device += 1
    if args.debug == "print":
        pcv.print_image(img=edges, filename=str(device) + '_plant_edges.png')
    elif args.debug == "plot":
        pcv.plot_image(img=edges, cmap="gray")

    # Combine pixels that are in both foreground edges and the filled background edges
    device, combined_bkg = pcv.logical_and(img1=edges.astype(np.uint8) * 255,
                                           img2=bkg_fill_contours.astype(np.uint8) * 255, device=device,
                                           debug=args.debug)

    # Remove background pixels from the foreground edges
    device += 1
    filtered = np.copy(edges)
    filtered[np.where(combined_bkg == 255)] = False
    if args.debug == "print":
        pcv.print_image(img=filtered, filename=str(device) + '_filtered_edges.png')
    elif args.debug == "plot":
        pcv.plot_image(img=filtered, cmap="gray")

    # Combine the cleaned naive Bayes mask and the filtered foreground edges
    device += 1
    combined = cleaned + filtered
    if args.debug == "print":
        pcv.print_image(img=combined, filename=str(device) + '_combined_foreground.png')
    elif args.debug == "plot":
        pcv.plot_image(img=combined, cmap="gray")

    # Close off broken edges and other incomplete contours
    device += 1
    closed_features = ndi.binary_closing(combined, structure=np.ones((3, 3)))
    if args.debug == "print":
        pcv.print_image(img=closed_features, filename=str(device) + '_closed_features.png')
    elif args.debug == "plot":
        pcv.plot_image(img=closed_features, cmap="gray")

    # Fill in holes in contours
    # device += 1
    # fill_contours = ndi.binary_fill_holes(closed_features)
    # if args.debug == "print":
    #     pcv.print_image(img=fill_contours, filename=str(device) + '_filled_contours.png')
    # elif args.debug == "plot":
    #     pcv.plot_image(img=fill_contours, cmap="gray")

    # Use median blur to break horizontal and vertical thin edges (pot edges)
    device += 1
    blurred_img = ndi.median_filter(closed_features.astype(np.uint8) * 255, (3, 1))
    blurred_img = ndi.median_filter(blurred_img, (1, 3))
    # Remove small objects left behind by blurring
    cleaned2 = morphology.remove_small_objects(blurred_img.astype(bool), 200)
    if args.debug == "print":
        pcv.print_image(img=cleaned2, filename=str(device) + '_cleaned_by_median_blur.png')
    elif args.debug == "plot":
        pcv.plot_image(img=cleaned2, cmap="gray")

    # Define region of interest based on camera zoom level for masking the naive Bayes classified image
    # if "z1500" in args.image:
    #     h = 1000
    # elif "z2500" in args.image:
    #     h = 1050
    # else:
    #     pcv.fatal_error("Image {0} has an unsupported zoom level.".format(args.image))
    # roi, roi_hierarchy = pcv.roi.rectangle(x=300, y=150, w=1850, h=h, img=img)

    # Mask the classified image to remove noisy areas prior to finding contours
    # side_mask = np.zeros(np.shape(img)[:2], dtype=np.uint8)
    # cv2.drawContours(side_mask, roi, -1, (255), -1)
    # device, masked_img = pcv.apply_mask(img=cv2.merge([mask["plant"], mask["plant"], mask["plant"]]), mask=side_mask,
    #                                     mask_color="black", device=device, debug=args.debug)
    # Convert the masked image back to grayscale
    # masked_img = masked_img[:, :, 0]
    # Close off contours at the base of the plant
    # if "z1500" in args.image:
    #     pt1 = (1100, 1118)
    #     pt2 = (1340, 1120)
    # elif "z2500" in args.image:
    #     pt1 = (1020, 1162)
    #     pt2 = (1390, 1166)
    # else:
    #     pcv.fatal_error("Image {0} has an unsupported zoom level.".format(args.image))
    # masked_img = cv2.rectangle(np.copy(masked_img), pt1, pt2, (255), -1)
    # closed_mask = ndi.binary_closing(masked_img.astype(bool), iterations=3)

    # Find objects in the masked naive Bayes mask
    # device, objects, obj_hierarchy = pcv.find_objects(img=img, mask=np.copy(masked_img), device=device,
    #                                                   debug=args.debug)
    # objects, obj_hierarchy = cv2.findContours(np.copy(closed_mask.astype(np.uint8) * 255), cv2.RETR_CCOMP,
    #                                           cv2.CHAIN_APPROX_NONE)[-2:]

    # Clean up the combined plant edges/mask image by removing filled in gaps/holes
    # device += 1
    # cleaned3 = np.copy(cleaned2)
    # cleaned3 = cleaned3.astype(np.uint8) * 255
    # # Loop over the contours from the naive Bayes mask
    # for c, contour in enumerate(objects):
    #     # Calculate the area of each contour
    #     # area = cv2.contourArea(contour)
    #     # If the contour is a hole (i.e. it has no children and it has a parent)
    #     # And it is not a small hole in a leaf that was not classified
    #     if obj_hierarchy[0][c][2] == -1 and obj_hierarchy[0][c][3] > -1:
    #         # Then fill in the contour (hole) black on the cleaned mask
    #         cv2.drawContours(cleaned3, objects, c, (0), -1, hierarchy=obj_hierarchy)
    # if args.debug == "print":
    #     pcv.print_image(img=cleaned3, filename=str(device) + '_gaps_removed.png')
    # elif args.debug == "plot":
    #     pcv.plot_image(img=cleaned3, cmap="gray")

    # Find contours using the cleaned mask
    device, contours, contour_hierarchy = pcv.find_objects(img=img, mask=np.copy(cleaned2.astype(np.uint8)),
                                                           device=device, debug=args.debug)

    # Define region of interest based on camera zoom level for contour filtering
    if "z1500" in args.image:
        h = 940
    elif "z2500" in args.image:
        h = 980
    else:
        pcv.fatal_error("Image {0} has an unsupported zoom level.".format(args.image))
    roi, roi_hierarchy = pcv.roi.rectangle(x=300, y=150, w=1850, h=h, img=img)

    # Filter contours in the region of interest
    device, roi_objects, hierarchy, kept_mask, obj_area = pcv.roi_objects(img=img, roi_type='partial', roi_contour=roi,
                                                                          roi_hierarchy=roi_hierarchy,
                                                                          object_contour=contours,
                                                                          obj_hierarchy=contour_hierarchy,
                                                                          device=device, debug=args.debug)

    # Analyze only images with plants present
    if len(roi_objects) > 0:
        # Object combine kept objects
        device, plant_contour, plant_mask = pcv.object_composition(img=img, contours=roi_objects, hierarchy=hierarchy,
                                                                   device=device, debug=args.debug)

        if args.writeimg:
            # Save the plant mask if requested
            pcv.print_image(img=plant_mask, filename=outfile + "_mask.png")

        # Find shape properties, output shape image
        device, shape_header, shape_data, shape_img = pcv.analyze_object(img=img, imgname=args.image, obj=plant_contour,
                                                                         mask=plant_mask, device=device,
                                                                         debug=args.debug,
                                                                         filename=outfile)
        # Set the boundary line based on the camera zoom level
        if "z1500" in args.image:
            line_position = 930
        elif "z2500" in args.image:
            line_position = 885
        else:
            pcv.fatal_error("Image {0} has an unsupported zoom level.".format(args.image))

        # Shape properties relative to user boundary line
        device, boundary_header, boundary_data, boundary_img = pcv.analyze_bound_horizontal(img=img, obj=plant_contour,
                                                                                            mask=plant_mask,
                                                                                            line_position=line_position,
                                                                                            device=device,
                                                                                            debug=args.debug,
                                                                                            filename=outfile)

        # Determine color properties: Histograms, Color Slices and Pseudocolored Images,
        # output color analyzed images
        device, color_header, color_data, color_img = pcv.analyze_color(img=img, imgname=args.image, mask=plant_mask,
                                                                        bins=256,
                                                                        device=device, debug=args.debug,
                                                                        hist_plot_type=None,
                                                                        pseudo_channel="v", pseudo_bkg="img",
                                                                        resolution=300,
                                                                        filename=outfile)

        # Output shape and color data
        result = open(args.result, "a")
        result.write('\t'.join(map(str, shape_header)) + "\n")
        result.write('\t'.join(map(str, shape_data)) + "\n")
        for row in shape_img:
            result.write('\t'.join(map(str, row)) + "\n")
        result.write('\t'.join(map(str, color_header)) + "\n")
        result.write('\t'.join(map(str, color_data)) + "\n")
        result.write('\t'.join(map(str, boundary_header)) + "\n")
        result.write('\t'.join(map(str, boundary_data)) + "\n")
        result.write('\t'.join(map(str, boundary_img)) + "\n")
        for row in color_img:
            result.write('\t'.join(map(str, row)) + "\n")
        result.close()
Beispiel #7
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")
Beispiel #8
0
def roi_objects(img, roi_contour, roi_hierarchy, object_contour, obj_hierarchy, roi_type="partial"):
    """Find objects partially inside a region of interest or cut objects to the ROI.

    Inputs:
    img            = RGB or grayscale image data for plotting
    roi_contour    = contour of roi, output from "View and Adjust ROI" function
    roi_hierarchy  = contour of roi, output from "View and Adjust ROI" function
    object_contour = contours of objects, output from "find_objects" function
    obj_hierarchy  = hierarchy of objects, output from "find_objects" function
    roi_type       = 'cutto', 'partial' (for partially inside, default), or 'largest' (keep only the largest contour)

    Returns:
    kept_cnt       = kept contours
    hierarchy      = contour hierarchy list
    mask           = mask image
    obj_area       = total object pixel area

    :param img: numpy.ndarray
    :param roi_type: str
    :param roi_contour: list
    :param roi_hierarchy: numpy.ndarray
    :param object_contour: list
    :param obj_hierarchy: numpy.ndarray
    :return kept_cnt: list
    :return hierarchy: numpy.ndarray
    :return mask: numpy.ndarray
    :return obj_area: int
    """
    # Store debug
    debug = params.debug
    params.debug = None

    params.device += 1
    # Create an empty grayscale (black) image the same dimensions as the input image
    mask = np.zeros(np.shape(img)[:2], dtype=np.uint8)
    cv2.drawContours(mask, object_contour, -1, (255), -1, lineType=8, hierarchy=obj_hierarchy)

    # Create a mask of the filled in ROI
    roi_mask = np.zeros(np.shape(img)[:2], dtype=np.uint8)
    roi_points = np.vstack(roi_contour[0])
    cv2.fillPoly(roi_mask, [roi_points], (255))

    # Make a copy of the input image for plotting
    ori_img = np.copy(img)
    # If the reference image is grayscale convert it to color
    if len(np.shape(ori_img)) == 2:
        ori_img = cv2.cvtColor(ori_img, cv2.COLOR_GRAY2BGR)

    # Allows user to find all objects that are completely inside or overlapping with ROI
    if roi_type.upper() == 'PARTIAL' or roi_type.upper() == 'LARGEST':
        # Filter contours outside of the region of interest
        for c, cnt in enumerate(object_contour):
            filtering_mask = np.zeros(np.shape(img)[:2], dtype=np.uint8)
            cv2.fillPoly(filtering_mask, [np.vstack(object_contour[c])], (255))
            overlap_img = logical_and(filtering_mask, roi_mask)
            # Delete contours that do not overlap at all with the ROI
            if np.sum(overlap_img) == 0:
                cv2.drawContours(mask, object_contour, c, (0), -1, lineType=8, hierarchy=obj_hierarchy)

        # Find the kept contours and area
        kept_cnt, kept_hierarchy = cv2.findContours(np.copy(mask), cv2.RETR_TREE, cv2.CHAIN_APPROX_NONE)[-2:]
        obj_area = cv2.countNonZero(mask)

        # Find the largest contour if roi_type is set to 'largest'
        if roi_type.upper() == 'LARGEST':
            # Print warning statement about this feature
            print("Warning: roi_type='largest' will only return the largest contour and its immediate children. Other "
                  "subcontours will be dropped.")
            # Find the index of the largest contour in the list of contours
            largest_area = 0
            index = 0
            for c, cnt in enumerate(kept_cnt):
                area = len(cnt)
                if area > largest_area:
                    largest_area = area
                    index = c

            # Store the largest contour as a list
            largest_cnt = [kept_cnt[index]]

            # Store the hierarchy of the largest contour into a list
            largest_hierarchy = [kept_hierarchy[0][index]]

            # Iterate through contours to find children of the largest contour
            for i, khi in enumerate(kept_hierarchy[0]):
                if khi[3] == index:  # is the parent equal to the largest contour?
                    largest_hierarchy.append(khi)
                    largest_cnt.append(kept_cnt[i])

            # Make the kept hierarchies into an array so that cv2 can use it
            largest_hierarchy = np.array([largest_hierarchy])

            # Overwrite mask so it only has the largest contour
            mask = np.zeros(np.shape(img)[:2], dtype=np.uint8)
            for i, cnt in enumerate(largest_cnt):
                # print(cnt)
                if i == 0:
                    color = (255)
                else:
                    color = (0)
                    # print(i)
                cv2.drawContours(mask, largest_cnt, i, color, -1, lineType=8, hierarchy=largest_hierarchy, maxLevel=0)

            # Refind contours and hierarchy from new mask so they are easier to work with downstream
            kept_cnt, kept_hierarchy = cv2.findContours(np.copy(mask), cv2.RETR_TREE, cv2.CHAIN_APPROX_NONE)[-2:]

            # Compute object area
            obj_area = cv2.countNonZero(mask)

        cv2.drawContours(ori_img, kept_cnt, -1, (0, 255, 0), -1, lineType=8, hierarchy=kept_hierarchy)
        cv2.drawContours(ori_img, roi_contour, -1, (255, 0, 0), params.line_thickness, lineType=8,
                         hierarchy=roi_hierarchy)
    # Allows user to cut objects to the ROI (all objects completely outside ROI will not be kept)
    elif roi_type.upper() == 'CUTTO':
        background1 = np.zeros(np.shape(img)[:2], dtype=np.uint8)
        background2 = np.zeros(np.shape(img)[:2], dtype=np.uint8)
        cv2.drawContours(background1, object_contour, -1, (255), -1, lineType=8, hierarchy=obj_hierarchy)
        roi_points = np.vstack(roi_contour[0])
        cv2.fillPoly(background2, [roi_points], (255))
        mask = cv2.multiply(background1, background2)
        obj_area = cv2.countNonZero(mask)
        kept_cnt, kept_hierarchy = cv2.findContours(np.copy(mask), cv2.RETR_TREE, cv2.CHAIN_APPROX_NONE)[-2:]
        cv2.drawContours(ori_img, kept_cnt, -1, (0, 255, 0), -1, lineType=8, hierarchy=kept_hierarchy)
        cv2.drawContours(ori_img, roi_contour, -1, (255, 0, 0), params.line_thickness, lineType=8,
                         hierarchy=roi_hierarchy)
    else:
        # Reset debug mode
        params.debug = debug
        fatal_error('ROI Type ' + str(roi_type) + ' is not "cutto", "largest", or "partial"!')

    # Reset debug mode
    params.debug = debug
    if params.debug == 'print':
        print_image(ori_img, os.path.join(params.debug_outdir, str(params.device) + '_obj_on_img.png'))
        print_image(mask, os.path.join(params.debug_outdir, str(params.device) + '_roi_mask.png'))
    elif params.debug == 'plot':
        plot_image(ori_img)
        plot_image(mask, cmap='gray')

    return kept_cnt, kept_hierarchy, mask, obj_area
    def get_coordinates(self):
        if self.debug:
            pcv.params.debug = 'print'  # set debug mode
            pcv.params.debug_outdir = './images/DracaenaVision/'  # set output directory

        blue_threshold = pcv.threshold.binary(gray_img=self.blue,
                                              threshold=50,
                                              max_value=255,
                                              object_type='dark')
        pcv.apply_mask(self.colour_image,
                       mask=blue_threshold,
                       mask_color='white')

        # Calculate moments of binary image
        moments = cv.moments(blue_threshold)

        # Calculate x,y coordinate of center
        self.centre_x = int(moments["m10"] / moments["m00"])
        self.centre_y = int(moments["m01"] / moments["m00"])

        # Put text and highlight the center
        cv.circle(self.colour_image, (self.centre_x, self.centre_y), 5,
                  (255, 255, 255), -1)
        cv.putText(self.colour_image, "Centre",
                   (self.centre_x - 25, self.centre_y - 25),
                   cv.FONT_HERSHEY_SIMPLEX, 0.5, (255, 255, 255), 2)

        red_threshold = pcv.threshold.binary(gray_img=self.red,
                                             threshold=70,
                                             max_value=255,
                                             object_type='light')

        # Erode/Dilate the red threshold to remove noise
        red_threshold = pcv.erode(red_threshold, ksize=5, i=1)
        red_threshold = pcv.dilate(red_threshold, ksize=5, i=2)

        # Create mask of area that is of interest
        mask_pts = np.array(
            [[576, 415], [800, 420], [1105, 630], [720, 685], [285, 590]],
            np.int32).reshape((-1, 1, 2))
        area_of_interest = np.zeros(self.colour_image.shape[:2], np.uint8)
        cv.fillPoly(area_of_interest, [mask_pts], (255, 255, 255))

        red_threshold_in_area_of_interest = pcv.logical_and(
            area_of_interest, red_threshold)
        pcv.apply_mask(img=self.colour_image,
                       mask=red_threshold_in_area_of_interest,
                       mask_color='black')

        params = cv.SimpleBlobDetector_Params()
        # Unused filters
        params.filterByCircularity = False
        params.filterByConvexity = False
        params.filterByInertia = False
        # Area filter
        params.filterByArea = True
        params.maxArea = 50000
        params.minArea = 100
        # Colour filter
        params.filterByColor = True
        params.blobColor = 255
        # Misc options
        params.minDistBetweenBlobs = 100

        blob_detector = cv.SimpleBlobDetector_create(params)

        keypoints = blob_detector.detect(red_threshold, mask=area_of_interest)
        keypoints.sort(reverse=True, key=lambda kp: kp.size)

        now = datetime.now().strftime('%d-%m %H %M %S')

        im_with_keypoints = cv.drawKeypoints(
            self.colour_image, keypoints, np.array([]), (0, 0, 255),
            cv.DRAW_MATCHES_FLAGS_DRAW_RICH_KEYPOINTS)

        self.detection_image = im_with_keypoints.copy()
        if self.full_screen:
            self.detection_image = cv.resize(self.detection_image,
                                             (1920, 1080))
        cv.imshow(self.window_name, self.detection_image)
        cv.waitKey(1)

        cv.polylines(im_with_keypoints, [mask_pts],
                     True, (0, 255, 0),
                     thickness=3)
        cv.imwrite('./images/DracaenaVision/{} detections.png'.format(now),
                   im_with_keypoints)

        coordinates = []

        for keypoint in keypoints:
            x = keypoint.pt[0]
            y = keypoint.pt[1]

            # For each X and Y determine depth (z-coordinate)
            depth = -1
            step_size_to_centre_x = (self.centre_x - x) / 100
            step_size_to_centre_y = (self.centre_y - y) / 100

            for i in range(21):
                delta_x = step_size_to_centre_x * i
                delta_y = step_size_to_centre_y * i
                new_x = round(x + delta_x)
                new_y = round(y + delta_y)
                depth = self.depth_array[new_y, new_x]
                if depth < 0.55:
                    print('Depth found with delta ({},{})'.format(
                        delta_x, delta_y))
                    print('Depth of: {} at {} X, {} Y'.format(
                        depth, new_x, new_y))
                    break

            z = depth if depth < 0.55 or depth <= 0 else 0.55

            # Determine the angle at which the tool should be held towards the plant
            angle = 180 - math.degrees(
                math.atan2(y - self.centre_y, x - self.centre_x))

            coordinate = [x, y, z, angle]
            coordinates.append(coordinate)

        return coordinates
Beispiel #10
0
def main():
    # Get options
    args = options()

    # Set variables
    pcv.params.debug = args.debug  # Replace the hard-coded debug with the debug flag
    pcv.params.debug_outdir = args.outdir  # set output directory

    ### Main pipeline ###

    # Read image (readimage mode defaults to native but if image is RGBA then specify mode='rgb')
    img, path, filename = pcv.readimage(args.image, mode='rgb')

    # Read reference image for colour correction (currently unused)
    #ref_img, ref_path, ref_filename = pcv.readimage(
    #    "/home/leonard/Dropbox/2020-01_LAC_phenotyping/images/top/renamed/20200128_2.jpg",
    #    mode="rgb")

    # Find colour cards
    #df, start, space = pcv.transform.find_color_card(rgb_img=ref_img)
    #ref_mask = pcv.transform.create_color_card_mask(rgb_img=ref_img, radius=10, start_coord=start, spacing=space, ncols=4, nrows=6)

    df, start, space = pcv.transform.find_color_card(rgb_img=img)
    img_mask = pcv.transform.create_color_card_mask(rgb_img=img,
                                                    radius=10,
                                                    start_coord=start,
                                                    spacing=space,
                                                    ncols=4,
                                                    nrows=6)

    output_directory = "."

    # Correct colour (currently unused)
    #target_matrix, source_matrix, transformation_matrix, corrected_img = pcv.transform.correct_color(ref_img, ref_mask, img, img_mask, output_directory)

    # Check that the colour correction worked (source~target should be strictly linear)
    #pcv.transform.quick_color_check(source_matrix = source_matrix, target_matrix = target_matrix, num_chips = 24)

    # Write the spacing of the colour card to file as size marker
    with open(os.path.join(path, 'output/size_marker_trays.csv'), 'a') as f:
        writer = csv.writer(f)
        writer.writerow([filename, space[0]])

    ### Crop tray ###

    # 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
    card_crop_img = cv2.rectangle(img, start_point, end_point, colour,
                                  thickness)

    # Convert RGB to HSV and extract the value channel
    v = pcv.rgb2gray_hsv(card_crop_img, "v")

    # Threshold the value image
    v_thresh = pcv.threshold.binary(
        v, 100, 255, "light"
    )  # start threshold at 150 with bright corner-markers, 100 without

    # Fill out bright imperfections (siliques and other dirt on the background)
    v_thresh = pcv.fill(
        v_thresh, 100)  # fill at 500 with bright corner-markers, 100 without

    # Create bounding rectangle around the tray
    x, y, w, h = cv2.boundingRect(v_thresh)

    # Crop image to tray
    #crop_img = card_crop_img[y:y+h, x:x+int(w - (w * 0.03))] # crop extra 3% from right because of tray labels
    crop_img = card_crop_img[y:y + h, x:x + w]  # crop symmetrically

    # Save cropped image for quality control
    pcv.print_image(crop_img,
                    filename=path + "/output/" + "cropped" + filename + ".png")

    ### Threshold plants ###

    # Threshold the green-magenta, blue, and hue channels
    a_thresh, _ = pcv.threshold.custom_range(img=crop_img,
                                             lower_thresh=[0, 0, 0],
                                             upper_thresh=[255, 108, 255],
                                             channel='LAB')
    b_thresh, _ = pcv.threshold.custom_range(img=crop_img,
                                             lower_thresh=[0, 0, 135],
                                             upper_thresh=[255, 255, 255],
                                             channel='LAB')
    h_thresh, _ = pcv.threshold.custom_range(img=crop_img,
                                             lower_thresh=[35, 0, 0],
                                             upper_thresh=[70, 255, 255],
                                             channel='HSV')

    # Join the thresholds (AND)
    ab = pcv.logical_and(b_thresh, a_thresh)
    abh = pcv.logical_and(ab, h_thresh)

    # Fill small objects depending on expected plant size based on DPG (make sure to take the correct file suffix jpg/JPG/jpeg...)
    match = re.search("(\d+).(\d)\.jpg$", filename)

    if int(match.group(1)) < 10:
        abh_clean = pcv.fill(abh, 50)
        print("50")
    elif int(match.group(1)) < 15:
        abh_clean = pcv.fill(abh, 200)
        print("200")
    else:
        abh_clean = pcv.fill(abh, 500)
        print("500")

    # Dilate to close broken borders
    abh_dilated = pcv.dilate(abh_clean, 3, 1)

    # Close holes
    # abh_fill = pcv.fill_holes(abh_dilated) # silly -- removed
    abh_fill = abh_dilated

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

    # Save masked image for quality control
    pcv.print_image(masked,
                    filename=path + "/output/" + "masked" + filename + ".png")

    ### Filter and group contours ###

    # Identify objects
    id_objects, obj_hierarchy = pcv.find_objects(crop_img, abh_fill)

    # Create bounding box with margins to avoid border artifacts
    roi_y = 0 + crop_img.shape[0] * 0.05
    roi_x = 0 + crop_img.shape[0] * 0.05
    roi_h = crop_img.shape[0] - (crop_img.shape[0] * 0.1)
    roi_w = crop_img.shape[1] - (crop_img.shape[0] * 0.1)
    roi_contour, roi_hierarchy = pcv.roi.rectangle(crop_img, roi_y, roi_x,
                                                   roi_h, roi_w)

    # Keep all objects in the bounding box
    roi_objects, roi_obj_hierarchy, kept_mask, obj_area = pcv.roi_objects(
        img=crop_img,
        roi_type='partial',
        roi_contour=roi_contour,
        roi_hierarchy=roi_hierarchy,
        object_contour=id_objects,
        obj_hierarchy=obj_hierarchy)

    # Cluster the objects by plant
    clusters, contours, hierarchies = pcv.cluster_contours(
        crop_img, roi_objects, roi_obj_hierarchy, 3, 5)

    # Split image into single plants
    out = args.outdir
    #output_path, imgs, masks = pcv.cluster_contour_splitimg(crop_img,
    #                                                        clusters,
    #                                                        contours,
    #                                                        hierarchies,
    #                                                        out,
    #                                                        file = filename)

    ### Analysis ###

    # Approximate the position of the top left plant as grid start
    coord_y = int(
        round(((crop_img.shape[0] / 3) * 0.5) + (crop_img.shape[0] * 0.025)))
    coord_x = int(
        round(((crop_img.shape[1] / 5) * 0.5) + (crop_img.shape[1] * 0.025)))

    # Set the ROI spacing relative to image dimensions
    spc_y = int((round(crop_img.shape[0] - (crop_img.shape[0] * 0.05)) / 3))
    spc_x = int((round(crop_img.shape[1] - (crop_img.shape[1] * 0.05)) / 5))

    # Set the ROI radius relative to image width
    if int(match.group(1)) < 16:
        r = int(round(crop_img.shape[1] / 12.5))
    else:
        r = int(round(crop_img.shape[1] / 20))

    # Make a grid of ROIs at the expected positions of plants
    # This allows for gaps due to dead/not germinated plants, without messing up the plant numbering
    imgs, masks = pcv.roi.multi(img=crop_img,
                                nrows=3,
                                ncols=5,
                                coord=(coord_x, coord_y),
                                radius=r,
                                spacing=(spc_x, spc_y))

    # Loop through the ROIs in the grid
    for i in range(0, len(imgs)):
        # Find objects within the ROI
        filtered_contours, filtered_hierarchy, filtered_mask, filtered_area = pcv.roi_objects(
            img=crop_img,
            roi_type="partial",
            roi_contour=imgs[i],
            roi_hierarchy=masks[i],
            object_contour=id_objects,
            obj_hierarchy=obj_hierarchy)
        # Continue only if not empty
        if len(filtered_contours) > 0:
            # Combine objects within each ROI
            plant_contour, plant_mask = pcv.object_composition(
                img=crop_img,
                contours=filtered_contours,
                hierarchy=filtered_hierarchy)

            # Analyse the shape of each plant
            analysis_images = pcv.analyze_object(img=crop_img,
                                                 obj=plant_contour,
                                                 mask=plant_mask)

            pcv.print_image(analysis_images,
                            filename=path + "/output/" + filename + "_" +
                            str(i) + "_analysed.png")

            # Determine color properties
            color_images = pcv.analyze_color(crop_img, plant_mask, "hsv")

            # Watershed plant area to count leaves (computationally intensive, use when needed)
            #watershed_images = pcv.watershed_segmentation(crop_img, plant_mask, 15)

            # Print out a .json file with the analysis data for the plant
            pcv.outputs.save_results(filename=path + "/" + filename + "_" +
                                     str(i) + '.json')

            # Clear the measurements stored globally into the Ouptuts class
            pcv.outputs.clear()
Beispiel #11
0
def main():
    # Get options
    args = options()

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

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

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

    # Threshold the saturation image removing highs and lows and join
    s_thresh_1 = pcv.threshold.binary(gray_img=s,
                                      threshold=10,
                                      max_value=255,
                                      object_type='light')
    s_thresh_2 = pcv.threshold.binary(gray_img=s,
                                      threshold=245,
                                      max_value=255,
                                      object_type='dark')
    s_thresh = pcv.logical_and(bin_img1=s_thresh_1, bin_img2=s_thresh_2)

    # Median Blur
    s_mblur = 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
    b_cnt = pcv.threshold.binary(gray_img=b,
                                 threshold=128,
                                 max_value=255,
                                 object_type='light')

    # Fill small objects
    b_fill = pcv.fill(b_cnt, 10)

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

    # Apply Mask (for VIS images, mask_color=white)
    masked = pcv.apply_mask(rgb_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
    maskeda_thresh = pcv.threshold.binary(gray_img=masked_a,
                                          threshold=127,
                                          max_value=255,
                                          object_type='dark')
    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)
    ab = pcv.logical_or(bin_img1=maskeda_thresh, bin_img2=maskedb_thresh)

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

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

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

    # Define ROI
    w = 1600
    h = 1200
    pot = 230  #340
    roi1, roi_hierarchy = pcv.roi.rectangle(img=masked2,
                                            x=(2472 - w) / 2,
                                            y=(3296 - h - pot),
                                            h=h,
                                            w=w)

    # 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)

    # Find shape properties, output shape image (optional)
    shape_imgs = pcv.analyze_object(img=img, obj=obj, mask=mask)

    if args.writeimg == True:
        pcv.print_image(img=shape_imgs,
                        filename="{}_shape.png".format(args.result[:-5]))
        pcv.print_image(img=masked2,
                        filename="{}_obj_on_img.png".format(args.result[:-5]))

    # Shape properties relative to user boundary line (optional)
    #boundary_img1 = pcv.analyze_bound_horizontal(img=img, obj=obj, mask=mask, line_position=1680)

    # Determine color properties: Histograms, Color Slices, output color analyzed histogram (optional)
    color_histogram = pcv.analyze_color(rgb_img=img,
                                        mask=kept_mask,
                                        hist_plot_type='all')

    # Pseudocolor the grayscale image
    pseudocolored_img = pcv.visualize.pseudocolor(gray_img=s,
                                                  mask=kept_mask,
                                                  cmap='jet')

    # Write shape and color data to results file
    pcv.print_results(filename=args.result)
def main():
    # Get options
    args = options()

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

    # Read metadata
    with open(args.metadata, 'r', encoding='utf-8') as f:
        md = json.load(f)

    camera_label = md['camera_label']

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

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

    # Threshold the saturation image removing highs and lows and join
    s_thresh_1 = pcv.threshold.binary(gray_img=s,
                                      threshold=10,
                                      max_value=255,
                                      object_type='light')
    s_thresh_2 = pcv.threshold.binary(gray_img=s,
                                      threshold=245,
                                      max_value=255,
                                      object_type='dark')
    s_thresh = pcv.logical_and(bin_img1=s_thresh_1, bin_img2=s_thresh_2)

    # Median Blur
    s_mblur = 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
    b_cnt = pcv.threshold.binary(gray_img=b,
                                 threshold=128,
                                 max_value=255,
                                 object_type='light')

    # Fill small objects
    b_fill = pcv.fill(b_cnt, 10)

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

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

    # Convert RGB to LAB and extract the Green-Magenta and Blue-Yellow channels
    # Threshold the green-magenta and blue images

    masked_a = pcv.rgb2gray_lab(rgb_img=masked, channel='a')
    maskeda_thresh = pcv.threshold.binary(gray_img=masked_a,
                                          threshold=127,
                                          max_value=255,
                                          object_type='dark')

    # Convert RGB to LAB and extract the Green-Magenta and Blue-Yellow channels
    # Threshold the green-magenta and blue images
    masked_b = pcv.rgb2gray_lab(rgb_img=masked, channel='b')
    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)
    ab = pcv.logical_or(bin_img1=maskeda_thresh, bin_img2=maskedb_thresh)

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

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

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

    # Define ROI

    W = 2472
    H = 3296

    if "far" in camera_label:
        # SIDE FAR
        w = 1600
        h = 1200
        pot = 230  #340
        roi1, roi_hierarchy = pcv.roi.rectangle(img=masked2,
                                                x=(W - w) / 2,
                                                y=(H - h - pot),
                                                h=h,
                                                w=w)
    elif "lower" in camera_label:
        # SIDE LOWER
        w = 800
        h = 2400
        pot = 340
        roi1, roi_hierarchy = pcv.roi.rectangle(img=masked2,
                                                x=1000 - w / 2,
                                                y=(H - h - pot),
                                                h=h,
                                                w=w)
    elif "upper" in camera_label:
        # SIDE UPPER
        w = 600
        h = 800
        pot = 550
        roi1, roi_hierarchy = pcv.roi.rectangle(img=masked2,
                                                x=1400 - w / 2,
                                                y=(H - h - pot),
                                                h=h,
                                                w=w)
    elif "top" in camera_label:
        # TOP
        w = 450
        h = 450
        roi1, roi_hierarchy = pcv.roi.rectangle(img=masked2,
                                                x=(H - h) / 2,
                                                y=(W - w) / 2,
                                                h=h,
                                                w=w)

    # 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)

    #TODO: Update for plantCV metadata import
    for key in md.keys():
        if str(md[key]).isdigit():
            pcv.outputs.add_observation(variable=key,
                                        trait=key,
                                        method='',
                                        scale='',
                                        datatype=int,
                                        value=md[key],
                                        label='')
        else:
            pcv.outputs.add_observation(variable=key,
                                        trait=key,
                                        method='',
                                        scale='',
                                        datatype=str,
                                        value=md[key],
                                        label='')

    if obj is not None:

        # Find shape properties, output shape image (optional)
        shape_imgs = pcv.analyze_object(img=img, obj=obj, mask=mask)

        # Shape properties relative to user boundary line (optional)
        #boundary_img1 = pcv.analyze_bound_horizontal(img=img, obj=obj, mask=mask, line_position=1680)

        # Determine color properties: Histograms, Color Slices, output color analyzed histogram (optional)
        color_histogram = pcv.analyze_color(rgb_img=img,
                                            mask=kept_mask,
                                            hist_plot_type='all')

        # Pseudocolor the grayscale image
        #pseudocolored_img = pcv.visualize.pseudocolor(gray_img=s, mask=kept_mask, cmap='jet')

        #print(pcv.outputs.images)
        if args.writeimg == True:
            for idx, item in enumerate(pcv.outputs.images[0]):
                pcv.print_image(item,
                                "{}_{}.png".format(args.result[:-5], idx))

    # Write shape and color data to results file
    pcv.print_results(filename=args.result)
def test(true_positive_file, test_parameters):
    saturation_lower_tresh = test_parameters[0]
    saturation_higher_tresh = test_parameters[1]
    hue_lower_tresh = test_parameters[2]
    hue_higher_tresh = test_parameters[3]
    value_lower_tresh = test_parameters[4]
    value_higher_tresh = test_parameters[5]
    l_lower_thresh = test_parameters[6]
    a_lower_thresh_1 = test_parameters[7]
    a_lower_thresh_2 = test_parameters[8]
    b_lower_thresh_1 = test_parameters[9]
    b_higher_thresh_1 = test_parameters[10]
    b_lower_thresh_2 = test_parameters[11]
    b_higher_thresh_2 = test_parameters[12]
    HSV_blur_k = test_parameters[13]
    LAB_blur_k = test_parameters[14]
    b_fill_k = test_parameters[15]
    LAB_fill_k = test_parameters[16]

    class args:
        #image = "C:\\Users\\RensD\\OneDrive\\studie\\Master\\The_big_project\\top_perspective\\0214_2018-03-07 08.55 - 26_cam9.png"
        image = true_positive_file
        outdir = "C:\\Users\\RensD\\OneDrive\\studie\\Master\\The_big_project\\top_perspective\\output"
        debug = debug_setting
        result = "results.txt"

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

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

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

    #______________________________________________________________#### BEGIN HSV COLORSPACE WORKFLOW ###
    # Convert RGB to HSV and extract the saturation channel
    # Threshold the saturation

    s = pcv.rgb2gray_hsv(rgb_img=img, channel='s')
    s_thresh, maskeds_image = pcv.threshold.custom_range(
        rgb_img=s,
        lower_thresh=[saturation_lower_tresh],
        upper_thresh=[saturation_higher_tresh],
        channel='gray')
    # Threshold the hue
    h = pcv.rgb2gray_hsv(rgb_img=img, channel='h')
    h_thresh, maskedh_image = pcv.threshold.custom_range(
        rgb_img=h,
        lower_thresh=[hue_lower_tresh],
        upper_thresh=[hue_higher_tresh],
        channel='gray')
    v = pcv.rgb2gray_hsv(rgb_img=img, channel='v')
    v_thresh, maskedv_image = pcv.threshold.custom_range(
        rgb_img=v,
        lower_thresh=[value_lower_tresh],
        upper_thresh=[value_higher_tresh],
        channel='gray')
    # Join saturation, Hue and Value
    sh = pcv.logical_and(bin_img1=s_thresh, bin_img2=h_thresh)
    hsv = pcv.logical_and(bin_img1=sh, bin_img2=v_thresh)
    # Median Blur
    s_mblur = pcv.median_blur(gray_img=hsv, ksize=HSV_blur_k)
    #s_cnt = pcv.median_blur(gray_img=s_thresh, ksize=5)
    #______________________________________________________________#### END HSV COLORSPACE WORKFLOW ###

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

    # Threshold the blue image
    b_thresh = pcv.threshold.binary(gray_img=b,
                                    threshold=b_lower_thresh_1,
                                    max_value=b_higher_thresh_1,
                                    object_type='light')
    b_cnt = pcv.threshold.binary(gray_img=b,
                                 threshold=b_lower_thresh_1,
                                 max_value=b_higher_thresh_1,
                                 object_type='light')
    # Fill small objects
    b_cnt = pcv.fill(
        b_thresh, b_fill_k
    )  # If the fill step fails because of small objects try a smaller fill, else abort.

    # Join the thresholded saturation and blue-yellow images
    bs = pcv.logical_and(bin_img1=s_mblur, bin_img2=b_cnt)  # CHANGER OR TO AND

    # Apply Mask (for VIS images, mask_color=white)
    masked = pcv.apply_mask(rgb_img=img, mask=bs, mask_color='white')
    #Now the background is filtered away. Next step is to capture the plant.

    # Convert RGB to LAB and extract the Green-Magenta and Blue-Yellow channels
    masked_l = pcv.rgb2gray_lab(rgb_img=masked, channel='l')
    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
    maskedl_thresh, maskedl_image = pcv.threshold.custom_range(
        rgb_img=masked_l,
        lower_thresh=[120],
        upper_thresh=[247],
        channel='gray')
    maskeda_thresh, maskeda_image = pcv.threshold.custom_range(
        rgb_img=masked_a, lower_thresh=[0], upper_thresh=[114], channel='gray')
    maskedb_thresh, maskedb_image = pcv.threshold.custom_range(
        rgb_img=masked_b,
        lower_thresh=[130],
        upper_thresh=[240],
        channel='gray')

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

    # Fill small objects
    ab_fill = pcv.median_blur(gray_img=ab, ksize=LAB_blur_k)
    ab_fill = pcv.fill(bin_img=ab_fill, size=LAB_fill_k)
    # Apply mask (for VIS images, mask_color=white)
    masked2 = pcv.apply_mask(rgb_img=masked, mask=ab_fill, mask_color='white')

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

    # Define ROI
    roi1, roi_hierarchy = pcv.roi.rectangle(img=masked2,
                                            x=0,
                                            y=0,
                                            h=960,
                                            w=1280)

    # 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)

    if use_mask == True:
        return (mask)
    else:
        masked2 = pcv.apply_mask(rgb_img=masked, mask=mask, mask_color='white')
        return (masked2)
Beispiel #14
0
for i,g in enumerate(grps):
    print(i,g)

    # read fluor data
    imgfns = get_filenames(basepath,g)
    Fo, Fm, FsLss, FmpLss = get_fluor(imgfns, os.path.join(basepath,g))

    # make mask
    # filters.try_all_threshold(Fm_gray)
    ty = filters.threshold_otsu(Fm)
    print(ty)
    mask = pcv.threshold.binary(Fm, ty, 255, 'light')
    mask = pcv.fill(mask, 100)
    _,rect,_,_ = pcv.rectangle_mask(mask,(300,180),(750,600),'white')
    mask = pcv.logical_and(mask,rect)

    # compute apply mask and compute paramters
    out_flt = np.zeros_like(Fm, dtype='float')
    
    # fv/fm
    Fv = np.subtract(Fm,Fo, out=out_flt.copy(), where=mask>0)
    FvFm = np.divide(Fv,Fm, out=out_flt.copy(), where=np.logical_and(mask>0, Fo>0))
    fvfm_fig = pcv.visualize.pseudocolor(FvFm,
                            mask=mask,
                            cmap='viridis',
                            max_value=1)
    outfn = g+'_fvfm.png'
    fvfm_fig.set_size_inches(6, 6, forward=False)
    fvfm_fig.savefig(os.path.join(outdir,outfn),
                    bbox_inches='tight',