def plant_cv(img): counter = 0 debug = None counter, s = pcv.rgb2gray_hsv(img, 's', counter, debug) counter, s_thresh = pcv.binary_threshold(s, 145, 255, 'light', counter, debug) counter, s_mblur = pcv.median_blur(s_thresh, 5, counter, debug) # Convert RGB to LAB and extract the Blue channel counter, b = pcv.rgb2gray_lab(img, 'b', counter, debug) # Threshold the blue image counter, b_thresh = pcv.binary_threshold(b, 145, 255, 'light', counter, debug) counter, b_cnt = pcv.binary_threshold(b, 145, 255, 'light', counter, debug) # Join the thresholded saturation and blue-yellow images counter, bs = pcv.logical_or(s_mblur, b_cnt, counter, debug) counter, masked = pcv.apply_mask(img, bs, 'white', counter, debug) #---------------------------------------- # Convert RGB to LAB and extract the Green-Magenta and Blue-Yellow channels counter, masked_a = pcv.rgb2gray_lab(masked, 'a', counter, debug) counter, masked_b = pcv.rgb2gray_lab(masked, 'b', counter, debug) # Threshold the green-magenta and blue images counter, maskeda_thresh = pcv.binary_threshold(masked_a, 115, 255, 'dark', counter, debug) counter, maskeda_thresh1 = pcv.binary_threshold(masked_a, 135, 255, 'light', counter, debug) counter, maskedb_thresh = pcv.binary_threshold(masked_b, 128, 255, 'light', counter, debug) # Join the thresholded saturation and blue-yellow images (OR) counter, ab1 = pcv.logical_or(maskeda_thresh, maskedb_thresh, counter, debug) counter, ab = pcv.logical_or(maskeda_thresh1, ab1, counter, debug) counter, ab_cnt = pcv.logical_or(maskeda_thresh1, ab1, counter, debug) # Fill small objects counter, ab_fill = pcv.fill(ab, ab_cnt, 200, counter, debug) # Apply mask (for vis images, mask_color=white) counter, masked2 = pcv.apply_mask(masked, ab_fill, 'white', counter, debug) zeros = np.zeros(masked2.shape[:2], dtype="uint8") merged = cv2.merge([zeros, ab_fill, zeros]) return merged, masked2
def watershed_segmentation(rgb_img, mask, distance=10): """Uses the watershed algorithm to detect boundary of objects. Needs a marker file which specifies area which is object (white), background (grey), unknown area (black). Inputs: rgb_img = image to perform watershed on needs to be 3D (i.e. np.shape = x,y,z not np.shape = x,y) mask = binary image, single channel, object in white and background black distance = min_distance of local maximum Returns: analysis_images = list of output images :param rgb_img: numpy.ndarray :param mask: numpy.ndarray :param distance: int :return analysis_images: list """ params.device += 1 # # Will be depricating opencv version 2 # if cv2.__version__[0] == '2': # dist_transform = cv2.distanceTransform(mask, cv2.cv.CV_DIST_L2, maskSize=0) # else: dist_transform = cv2.distanceTransformWithLabels(mask, cv2.DIST_L2, maskSize=0)[0] localMax = peak_local_max(dist_transform, indices=False, min_distance=distance, labels=mask) markers = ndi.label(localMax, structure=np.ones((3, 3)))[0] dist_transform1 = -dist_transform labels = watershed(dist_transform1, markers, mask=mask) img1 = np.copy(rgb_img) for x in np.unique(labels): rand_color = color_palette(len(np.unique(labels))) img1[labels == x] = rand_color[x] img2 = apply_mask(img1, mask, 'black') joined = np.concatenate((img2, rgb_img), axis=1) estimated_object_count = len(np.unique(markers)) - 1 analysis_image = [] analysis_image.append(joined) if params.debug == 'print': print_image(dist_transform, os.path.join(params.debug_outdir, str(params.device) + '_watershed_dist_img.png')) print_image(joined, os.path.join(params.debug_outdir, str(params.device) + '_watershed_img.png')) elif params.debug == 'plot': plot_image(dist_transform, cmap='gray') plot_image(joined) outputs.add_observation(variable='estimated_object_count', trait='estimated object count', method='plantcv.plantcv.watershed', scale='none', datatype=int, value=estimated_object_count, label='none') # Store images outputs.images.append(analysis_image) return analysis_image
def watershed_segmentation(rgb_img, mask, distance=10): """Uses the watershed algorithm to detect boundary of objects. Needs a marker file which specifies area which is object (white), background (grey), unknown area (black). Inputs: rgb_img = image to perform watershed on needs to be 3D (i.e. np.shape = x,y,z not np.shape = x,y) mask = binary image, single channel, object in white and background black distance = min_distance of local maximum Returns: analysis_images = list of output images :param rgb_img: numpy.ndarray :param mask: numpy.ndarray :param distance: int :return analysis_images: list """ params.device += 1 # Store debug mode debug = params.debug params.debug = None dist_transform = cv2.distanceTransformWithLabels(mask, cv2.DIST_L2, maskSize=0)[0] localMax = peak_local_max(dist_transform, indices=False, min_distance=distance, labels=mask) markers = ndi.label(localMax, structure=np.ones((3, 3)))[0] dist_transform1 = -dist_transform labels = watershed(dist_transform1, markers, mask=mask) img1 = np.copy(rgb_img) for x in np.unique(labels): rand_color = color_palette(len(np.unique(labels))) img1[labels == x] = rand_color[x] img2 = apply_mask(img1, mask, 'black') joined = np.concatenate((img2, rgb_img), axis=1) estimated_object_count = len(np.unique(markers)) - 1 # Reset debug mode params.debug = debug if params.debug == 'print': print_image(dist_transform, os.path.join(params.debug_outdir, str(params.device) + '_watershed_dist_img.png')) print_image(joined, os.path.join(params.debug_outdir, str(params.device) + '_watershed_img.png')) elif params.debug == 'plot': plot_image(dist_transform, cmap='gray') plot_image(joined) outputs.add_observation(variable='estimated_object_count', trait='estimated object count', method='plantcv.plantcv.watershed', scale='none', datatype=int, value=estimated_object_count, label='none') # Store images outputs.images.append([dist_transform, joined]) return joined
def _max(img, hmax, mask, x, y, h, w, type): imgcp = np.copy(img) cv2.rectangle(mask, (x, y), (x + w, y + h), (255, 255, 255), -1) mask_binary = mask[:, :, 0] retval, mask_binary = cv2.threshold(mask_binary, 254, 255, cv2.THRESH_BINARY) masked = apply_mask(imgcp, mask_binary, 'black') max1 = np.amax(masked) alpha = hmax / float(max1) corrected = np.asarray(np.where(img <= max1, np.multiply(alpha, img), hmax), type) return corrected
def _max(img, hmax, mask, x, y, h, w, type): imgcp = np.copy(img) cv2.rectangle(mask, (x, y), (x + w, y + h), (255, 255, 255), -1) mask_binary = mask[:, :, 0] retval, mask_binary = cv2.threshold(mask_binary, 254, 255, cv2.THRESH_BINARY) masked = apply_mask(imgcp, mask_binary, 'black') max1 = np.amax(masked) alpha = hmax / float(max1) corrected = np.asarray(np.where(img <= max1, np.multiply(alpha, img), hmax), type) return corrected
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 watershed_segmentation(rgb_img, mask, distance=10): """Uses the watershed algorithm to detect boundary of objects. Needs a marker file which specifies area which is object (white), background (grey), unknown area (black). Inputs: rgb_img = image to perform watershed on needs to be 3D (i.e. np.shape = x,y,z not np.shape = x,y) mask = binary image, single channel, object in white and background black distance = min_distance of local maximum Returns: watershed_header = shape data table headers watershed_data = shape data table values analysis_images = list of output images :param rgb_img: numpy.ndarray :param mask: numpy.ndarray :param distance: int :return watershed_header: list :return watershed_data: list :return analysis_images: list """ params.device += 1 # # Will be depricating opencv version 2 # if cv2.__version__[0] == '2': # dist_transform = cv2.distanceTransform(mask, cv2.cv.CV_DIST_L2, maskSize=0) # else: dist_transform = cv2.distanceTransformWithLabels(mask, cv2.DIST_L2, maskSize=0)[0] localMax = peak_local_max(dist_transform, indices=False, min_distance=distance, labels=mask) markers = ndi.label(localMax, structure=np.ones((3, 3)))[0] dist_transform1 = -dist_transform labels = watershed(dist_transform1, markers, mask=mask) img1 = np.copy(rgb_img) for x in np.unique(labels): rand_color = color_palette(len(np.unique(labels))) img1[labels == x] = rand_color[x] img2 = apply_mask(img1, mask, 'black') joined = np.concatenate((img2, rgb_img), axis=1) estimated_object_count = len(np.unique(markers)) - 1 analysis_image = [] analysis_image.append(joined) watershed_header = ( 'HEADER_WATERSHED', 'estimated_object_count' ) watershed_data = ( 'WATERSHED_DATA', estimated_object_count ) if params.debug == 'print': print_image(dist_transform, os.path.join(params.debug_outdir, str(params.device) + '_watershed_dist_img.png')) print_image(joined, os.path.join(params.debug_outdir, str(params.device) + '_watershed_img.png')) elif params.debug == 'plot': plot_image(dist_transform, cmap='gray') plot_image(joined) # Store into global measurements if not 'watershed' in outputs.measurements: outputs.measurements['watershed'] = {} outputs.measurements['watershed']['estimated_object_count'] = estimated_object_count # Store images outputs.images.append(analysis_image) return watershed_header, watershed_data, analysis_image
def analyze_nir_intensity(gray_img, mask, bins, histplot=False, filename=False): """This function calculates the intensity of each pixel associated with the plant and writes the values out to a file. It can also print out a histogram plot of pixel intensity and a pseudocolor image of the plant. Inputs: gray_img = 8- or 16-bit grayscale image data mask = Binary mask made from selected contours bins = number of classes to divide spectrum into histplot = if True plots histogram of intensity values filename = False or image name. If defined print image Returns: hist_header = NIR histogram data table headers hist_data = NIR histogram data table values analysis_img = output image :param gray_img: numpy array :param mask: numpy array :param bins: int :param histplot: bool :param filename: str :return hist_header: list :return hist_data: list :return analysis_img: str """ params.device += 1 # apply plant shaped mask to image mask1 = binary_threshold(mask, 0, 255, 'light') mask1 = (mask1 / 255) masked = np.multiply(gray_img, mask1) # calculate histogram if gray_img.dtype == 'uint16': maxval = 65536 else: maxval = 256 # Make a pseudo-RGB image rgbimg = cv2.cvtColor(gray_img, cv2.COLOR_GRAY2BGR) hist_nir, hist_bins = np.histogram(masked, bins, (1, maxval), False, None, None) hist_bins1 = hist_bins[:-1] hist_bins2 = [l for l in hist_bins1] hist_nir1 = [l for l in hist_nir] # make hist percentage for plotting pixels = cv2.countNonZero(mask1) hist_percent = (hist_nir / float(pixels)) * 100 # report histogram data hist_header = ['HEADER_HISTOGRAM', 'bin-number', 'bin-values', 'nir'] hist_data = ['HISTOGRAM_DATA', bins, hist_bins2, hist_nir1] analysis_img = [] # make mask to select the background mask_inv = cv2.bitwise_not(mask) img_back = cv2.bitwise_and(rgbimg, rgbimg, mask=mask_inv) img_back1 = cv2.applyColorMap(img_back, colormap=1) # mask the background and color the plant with color scheme 'jet' cplant = cv2.applyColorMap(rgbimg, colormap=2) masked1 = apply_mask(cplant, mask, 'black') cplant_back = cv2.add(masked1, img_back1) if filename: path = os.path.dirname(filename) fig_name = 'NIR_pseudocolor_colorbar.svg' if not os.path.isfile(path + '/' + fig_name): plot_colorbar(path, fig_name, bins) fig_name_pseudo = (os.path.splitext(filename)[0] + '_nir_pseudo_col.jpg') print_image(cplant_back, fig_name_pseudo) analysis_img.append(['IMAGE', 'pseudo', fig_name_pseudo]) if params.debug is not None: if params.debug == "print": print_image( masked1, os.path.join(params.debug_outdir, str(params.device) + "_nir_pseudo_plant.jpg")) print_image( cplant_back, os.path.join(params.debug_outdir, str(params.device) + "_nir_pseudo_plant_back.jpg")) if params.debug == "plot": plot_image(masked1) plot_image(cplant_back) if histplot is True: import matplotlib matplotlib.use('Agg', warn=False) from matplotlib import pyplot as plt # plot hist percent plt.plot(hist_percent, color='green', label='Signal Intensity') plt.xlim([0, (bins - 1)]) plt.xlabel(('Grayscale pixel intensity (0-' + str(bins) + ")")) plt.ylabel('Proportion of pixels (%)') if filename: fig_name_hist = (os.path.splitext(filename)[0] + '_nir_hist.svg') plt.savefig(fig_name_hist) analysis_img.append(['IMAGE', 'hist', fig_name_hist]) if params.debug == "print": plt.savefig( os.path.join(params.debug_outdir, str(params.device) + "_nir_histogram.png")) if params.debug == "plot": plt.figure() plt.clf() return hist_header, hist_data, analysis_img
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) # In[113]:
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()
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 iterate_rois(img, c, h, rc, rh, args, masked=True, gi=False, shape=False, hist=True, hue=False): """Analyze each ROI separately and store results Parameters ---------- img : ndarray rgb image c : list object contours h : list object countour hierarchy rc : list roi contours rh : list roi contour hierarchy threshold_mask : ndarray binary image (mask) from threshold steps args : dict commandline arguments and metadata from running the workflow masked : boolean whether to print masked rgb images for each roi gi : boolean whether to print greenness index false color shape : boolean whether to print object shapes on an image hist : boolean whether to print color histogram hue : boolean whether to save hsv color info and print the hue false color image Returns ------- binary image of plant mask that includes both threshold and roi filter steps : ndarray """ final_mask = np.zeros(shape=np.shape(img)[0:2], dtype='uint8') # Compute greenness if gi: img_gi = cppc.compute.greenness_index(img=img, mask=final_mask+1) if hue: img_h = pcv.rgb2gray_hsv(img, 'h') for i, rc_i in enumerate(rc): rh_i = rh[i] # Add ROI number to output. Before roi_objects so result has NA if no object. pcv.outputs.add_observation( sample='default', variable='roi', trait='roi', method='roi', scale='int', datatype=int, value=i, label='#') roi_obj, hierarchy_obj, submask, obj_area = pcv.roi_objects( img, roi_contour=rc_i, roi_hierarchy=rh_i, object_contour=c, obj_hierarchy=h, roi_type='partial') if obj_area == 0: print('\t!!! No object found in ROI', str(i)) pcv.outputs.add_observation( sample='default', variable='plantarea', trait='plant area in sq mm', method='observations.area*pixelresolution^2', scale=cppc.pixelresolution, datatype="<class 'float'>", value=0, label='sq mm') else: # Combine multiple objects # ple plant objects within an roi together plant_object, plant_mask = pcv.object_composition( img=img, contours=roi_obj, hierarchy=hierarchy_obj) final_mask = pcv.image_add(final_mask, plant_mask) if gi: # Save greenness for individual ROI grnindex = cppc.utils.mean(img_gi, plant_mask) grnindexstd = cppc.utils.std(img_gi, plant_mask) pcv.outputs.add_observation( sample='default', variable='greenness_index', trait='mean normalized greenness index', method='g/sum(b+g+r)', scale='[0,1]', datatype="<class 'float'>", value=float(grnindex), label='/1') pcv.outputs.add_observation( sample='default', variable='greenness_index_std', trait='std normalized greenness index', method='g/sum(b+g+r)', scale='[0,1]', datatype="<class 'float'>", value=float(grnindexstd), label='/1') # Analyze all colors if hist: colorhist = pcv.analyze_color(img, plant_mask, 'all') elif hue: _ = pcv.analyze_color(img, plant_mask, 'hsv') # Analyze the shape of the current plant (always do this even if shape is False so you can get plant_area) img_shape = pcv.analyze_object(img, plant_object, plant_mask) plant_area = pcv.outputs.observations['default']['area']['value'] * cppc.pixelresolution**2 pcv.outputs.add_observation( sample='default', variable='plantarea', trait='plant area in sq mm', method='observations.area*pixelresolution^2', scale=cppc.pixelresolution, datatype="<class 'float'>", value=plant_area, label='sq mm') # end if-else # At this point we have observations for one plant # We can write these out to a unique results file write_output(args, i) if args.writeimg and obj_area != 0: if shape: imgdir = os.path.join(args.outdir, 'shape_images', args.plantbarcode) os.makedirs(imgdir, exist_ok=True) pcv.print_image(img_shape, os.path.join(imgdir, args.imagename + '-roi' + str(i) + '-shape.png')) if hist: imgdir = os.path.join(args.outdir, 'colorhist_images', args.plantbarcode) os.makedirs(imgdir, exist_ok=True) pcv.print_image(colorhist, os.path.join(imgdir, args.imagename + '-roi' + str(i) + '-colorhist.png')) if masked: # save masked rgb image for entire tray but only 1 plant imgdir = os.path.join(args.outdir, 'maskedrgb_images') os.makedirs(imgdir, exist_ok=True) img_masked = pcv.apply_mask(img, plant_mask, 'black') pcv.print_image( img_masked, os.path.join(imgdir, args.imagename + '-roi' + str(i) + '-masked.png')) if hue: # save hue false color image for entire tray but only 1 plant imgdir = os.path.join(args.outdir, 'hue_images') os.makedirs(imgdir, exist_ok=True) fig_hue = pcv.visualize.pseudocolor(img_h*2, obj=None, mask=plant_mask, cmap=cppc.viz.get_cmap('hue'), axes=False, min_value=0, max_value=179, background='black', obj_padding=0) fig_hue = cppc.viz.add_scalebar(fig_hue, pixelresolution=cppc.pixelresolution, barwidth=10, barlabel='1 cm', barlocation='lower left') fig_hue.set_size_inches(6, 6, forward=False) fig_hue.savefig(os.path.join(imgdir, args.imagename + '-roi' + str(i) + '-hue.png'), bbox_inches='tight', dpi=300) fig_hue.clf() if gi: # save grnness image of entire tray but only 1 plant imgdir = os.path.join(args.outdir, 'grnindex_images') os.makedirs(imgdir, exist_ok=True) fig_gi = pcv.visualize.pseudocolor(img_gi, obj=None, mask=plant_mask, cmap='viridis', axes=False, min_value=0.3, max_value=0.6, background='black', obj_padding=0) fig_gi = cppc.viz.add_scalebar( fig_gi, pixelresolution=cppc.pixelresolution, barwidth=10, barlabel='1 cm', barlocation='lower left') fig_gi.set_size_inches(6, 6, forward=False) fig_gi.savefig(os.path.join( imgdir, args.imagename + '-roi' + str(i) + '-greenness.png'), bbox_inches='tight', dpi=300) fig_gi.clf() # end roi loop return final_mask
# Inputs: # bin_img1 - Binary image data to be compared to bin_img2 # bin_img2 - Binary image data to be compared to bin_img1 bs = pcv.logical_or(bin_img1=s_mblur, bin_img2=b_thresh) pcv.print_image(img=bs, filename="upload/output_imgs/BSature_img.jpg") # In[10]: # Appy Mask (for VIS images, mask_color='white') # Inputs: # rgb_img - RGB image data # mask - Binary mask image data # mask_color - 'white' or 'black' rgb_img = img masked = pcv.apply_mask(rgb_img, mask=bs, mask_color='white') pcv.print_image(img=masked, filename="upload/output_imgs/VIS_img.jpg") # In[11]: # 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') pcv.print_image(img=masked_b, filename="upload/output_imgs/GrayScale_img.jpg") pcv.print_image(img=masked_a, filename="upload/output_imgs/Green-Magenta.jpg") # In[12]: maskeda_thresh = pcv.threshold.binary(gray_img=masked_a, threshold=115,
def generateMask(input, output, maskType=MASK_TYPES['BW']): pcv.params.debug = True #set debug mode # pcv.params.debug_outdir="./output.txt" #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', 'envi', or 'csv' img, path, filename = pcv.readimage(filename=input, mode='rgb') s = pcv.rgb2gray_hsv(rgb_img=img, channel='s') # Threshold the saturation image s_thresh = pcv.threshold.binary(gray_img=s, threshold=85, 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 b_thresh = pcv.threshold.binary(gray_img=b, threshold=160, max_value=255, object_type='light') b_cnt = pcv.threshold.binary(gray_img=b, threshold=160, max_value=255, object_type='light') # Fill small objects # b_fill = pcv.fill(b_thresh, 10) # 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 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) if maskType == MASK_TYPES['BW']: pcv.print_image(ab, filename=output) return (True, None) # 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='black') if maskType == MASK_TYPES['COLORED']: pcv.print_image(masked2, filename=output) return (True, None) return (False, 'Unknown mask type.') """
def main(): # Get options args = options() debug = args.debug # Read image img, path, filename = pcv.readimage(args.image) # Pipeline step device = 0 device, img1 = pcv.white_balance(device, img, debug, roi=(1000, 1000, 500, 500)) device, a = pcv.rgb2gray_lab(img1, 'a', device, debug) device, img_binary = pcv.binary_threshold(a, 116, 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(img1, fill_image, device, debug) device, roi, roi_hierarchy = pcv.define_roi(img1, 'rectangle', device, None, 'default', debug, True, 1800, 1600, -1500, -500) device, roi_objects, roi_obj_hierarchy, kept_mask, obj_area = pcv.roi_objects(img1, 'partial', roi, roi_hierarchy, id_objects, obj_hierarchy, device, debug) outfile = os.path.join(args.outdir, filename) device, color_header, color_data, color_img = pcv.analyze_color(img1, img1, kept_mask, 256, device, debug, None, 'v', 'img', 300, outfile) device, masked = pcv.apply_mask(img1, kept_mask, 'white', device, debug) device, dilated = pcv.dilate(kept_mask, 10, 2, device, debug) device, plant_objects, plant_hierarchy = pcv.find_objects(img1, dilated, device, debug) img_copy = np.copy(img1) color = [(255, 0, 255), (0, 255, 0), (66, 134, 244), (255, 255, 0)] for i in range(0, len(plant_objects)): if len(plant_objects[i]) < 100: pass else: background = np.zeros((np.shape(img1)), np.uint8) cv2.drawContours(background, plant_objects, i, (255, 255, 255), -1, lineType=8, hierarchy=plant_hierarchy) device, grayimg = pcv.rgb2gray(background, device, debug) device, masked1 = pcv.apply_mask(masked, grayimg, 'white', device, debug) device, a1 = pcv.rgb2gray_lab(masked1, 'a', device, debug) device, img_binary1 = pcv.binary_threshold(a1, 116, 255, 'dark', device, debug) device, single_object, single_hierarchy = pcv.find_objects(masked1, img_binary1, device, debug) device, obj, mask = pcv.object_composition(img1, single_object, single_hierarchy, device, debug) device, shape_header, shape_data, shape_img = pcv.analyze_object(img, "img", obj, mask, device, debug) cv2.drawContours(img_copy, plant_objects, i, color[i], -1, lineType=8, hierarchy=plant_hierarchy) plantsize = "Plant matching this color is " + str(shape_data[1]) + " pixels large" cv2.putText(img_copy, plantsize, (500, (i + 1) * 300), cv2.FONT_HERSHEY_SIMPLEX, 5, color[i], 10) pcv.print_image(img_copy, os.path.join(args.outdir, "arabidopsis-out_shapes.jpg"))
def main(): # 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') ### SELECTING THE PLANT ### Attempt 5 combineren # Parameters hue_lower_tresh = 22 # 24 hue_higher_tresh = 50 # 50 saturation_lower_tresh = 138 # 140 saturation_higher_tresh = 230 # 230 value_lower_tresh = 120 # 125 value_higher_tresh = 255 # 255 # RGB color space green_lower_tresh = 105 # 110 green_higher_tresh = 255 # 255 red_lower_tresh = 22 # 24 red_higher_thresh = 98 # 98 blue_lower_tresh = 85 # 85 blue_higher_tresh = 253 # 255 # CIELAB color space #lab_blue_lower_tresh = 0 # Blue yellow channel #lab_blue_higher_tresh = 255 s = pcv.rgb2gray_hsv(rgb_img=img, channel='h') mask, masked_image = pcv.threshold.custom_range( rgb_img=s, lower_thresh=[hue_lower_tresh], upper_thresh=[hue_higher_tresh], channel='gray') masked = pcv.apply_mask(rgb_img=img, mask=mask, mask_color='white') # Filtered on Hue s = pcv.rgb2gray_hsv(rgb_img=masked, channel='s') mask, masked_image = pcv.threshold.custom_range( rgb_img=s, lower_thresh=[saturation_lower_tresh], upper_thresh=[saturation_higher_tresh], channel='gray') masked = pcv.apply_mask(rgb_img=masked, mask=mask, mask_color='white') #filtered on saturation s = pcv.rgb2gray_hsv(rgb_img=masked, channel='v') mask, masked_image = pcv.threshold.custom_range( rgb_img=s, lower_thresh=[value_lower_tresh], upper_thresh=[value_higher_tresh], channel='gray') masked = pcv.apply_mask(rgb_img=masked, mask=mask, mask_color='white') #filtered on value mask, masked = pcv.threshold.custom_range( rgb_img=masked, lower_thresh=[0, green_lower_tresh, 0], upper_thresh=[255, green_higher_tresh, 255], channel='RGB') masked = pcv.apply_mask(rgb_img=masked, mask=mask, mask_color='white') #filtered on green mask, masked = pcv.threshold.custom_range( rgb_img=masked, lower_thresh=[red_lower_tresh, 0, 0], upper_thresh=[red_higher_thresh, 255, 255], channel='RGB') masked = pcv.apply_mask(rgb_img=masked, mask=mask, mask_color='white') #filtered on red mask_old, masked_old = pcv.threshold.custom_range( rgb_img=masked, lower_thresh=[0, 0, blue_lower_tresh], upper_thresh=[255, 255, blue_higher_tresh], channel='RGB') masked = pcv.apply_mask(rgb_img=masked_old, mask=mask_old, mask_color='white') #filtered on blue #b = pcv.rgb2gray_lab(rgb_img = masked, channel = 'b') # Converting toe CIElab blue_yellow image #b_thresh =pcv.threshold.binary(gray_img = b, threshold=lab_blue_lower_tresh, max_value = lab_blue_higher_tresh) ###_____________________________________ Now to identify objects 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=125, # original 115 max_value=255, object_type='dark') maskeda_thresh1 = pcv.threshold.binary( gray_img=masked_a, threshold=140, # original 135 max_value=255, object_type='light') maskedb_thresh = pcv.threshold.binary(gray_img=masked_b, threshold=128, max_value=255, object_type='light') 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 # Inputs: # bin_img - Binary image data # size - Minimum object area size in pixels (must be an integer), and smaller objects will be filled ab = pcv.median_blur(gray_img=ab, ksize=3) ab_fill = pcv.fill(bin_img=ab, size=1000) #print("filled") # Apply mask (for VIS images, mask_color=white) masked2 = pcv.apply_mask(rgb_img=masked, mask=ab_fill, mask_color='white') # ID the objects id_objects, obj_hierarchy = pcv.find_objects(masked2, ab_fill) # Let's just take the largest roi1, roi_hierarchy = pcv.roi.rectangle(img=masked2, x=0, y=0, h=960, w=1280) # Currently hardcoded # Decide which objects to keep # 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 pcv.find_objects function # obj_hierarchy = hierarchy of objects, output from pcv.find_objects function # roi_type = 'partial' (default, for partially inside), 'cutto', or # 'largest' (keep only largest contour) 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='partial') # Object combine kept objects # Inputs: # img - RGB or grayscale image data for plotting # contours - Contour list # hierarchy - Contour hierarchy array obj, mask = pcv.object_composition(img=img, contours=roi_objects, hierarchy=hierarchy3) #print("final plant") new_im = Image.fromarray(masked2) new_im.save("output//" + args.filename + "last_masked.png") ##################_________________ Analysis outfile = args.outdir + "/" + filename # Here come all the analyse functions. # pcv.acute_vertex(img, obj, 30, 15, 100) color_img = pcv.analyze_color(rgb_img=img, mask=kept_mask, hist_plot_type=None) #new_im = Image.fromarray(color_img) #new_im.save(args.filename + "color_img.png") # Find shape properties, output shape image (optional) # Inputs: # img - RGB or grayscale image data # obj- Single or grouped contour object # mask - Binary image mask to use as mask for moments analysis shape_img = pcv.analyze_object(img=img, obj=obj, mask=mask) new_im = Image.fromarray(shape_img) new_im.save("output//" + args.filename + "shape_img.png") # Shape properties relative to user boundary line (optional) # Inputs: # img - RGB or grayscale image data # obj - Single or grouped contour object # mask - Binary mask of selected contours # line_position - Position of boundary line (a value of 0 would draw a line # through the bottom of the image) boundary_img1 = pcv.analyze_bound_horizontal(img=img, obj=obj, mask=mask, line_position=1680) new_im = Image.fromarray(boundary_img1) new_im.save("output//" + args.filename + "boundary_img.png") # Determine color properties: Histograms, Color Slices, output color analyzed histogram (optional) # Inputs: # rgb_img - RGB image data # mask - Binary mask of selected contours # hist_plot_type - None (default), 'all', 'rgb', 'lab', or 'hsv' # This is the data to be printed to the SVG histogram file color_histogram = pcv.analyze_color(rgb_img=img, mask=kept_mask, hist_plot_type='all') #new_im = Image.fromarray(color_histogram) #new_im.save(args.filename + "color_histogram_img.png") # Pseudocolor the grayscale image # Inputs: # gray_img - Grayscale image data # obj - Single or grouped contour object (optional), if provided the pseudocolored image gets # cropped down to the region of interest. # mask - Binary mask (optional) # background - Background color/type. Options are "image" (gray_img, default), "white", or "black". A mask # must be supplied. # cmap - Colormap # min_value - Minimum value for range of interest # max_value - Maximum value for range of interest # dpi - Dots per inch for image if printed out (optional, if dpi=None then the default is set to 100 dpi). # axes - If False then the title, x-axis, and y-axis won't be displayed (default axes=True). # colorbar - If False then the colorbar won't be displayed (default colorbar=True) pseudocolored_img = pcv.visualize.pseudocolor(gray_img=s, mask=kept_mask, cmap='jet') #new_im = Image.fromarray(pseudocolored_img) #new_im.save(args.filename + "pseudocolored.png") # Write shape and color data to results file pcv.print_results(filename=args.result)
def main_side(): # Setting "args" # 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' filename = args.image img = cv2.imread(args.image, flags=0) #img = pcv.invert(img) path, img_name = os.path.split(args.image) img_bkgrd = cv2.imread("background.png", flags=0) #print(img) #print(img_bkgrd) bkg_sub_img = pcv.image_subtract(img_bkgrd, img) bkg_sub_thres_img, masked_img = pcv.threshold.custom_range( rgb_img=bkg_sub_img, lower_thresh=[50], upper_thresh=[255], channel='gray') # Laplace filtering (identify edges based on 2nd derivative) # Inputs: # gray_img - Grayscale image data # ksize - Aperture size used to calculate the second derivative filter, # specifies the size of the kernel (must be an odd integer) # scale - Scaling factor applied (multiplied) to computed Laplacian values # (scale = 1 is unscaled) lp_img = pcv.laplace_filter(gray_img=img, ksize=1, scale=1) # Plot histogram of grayscale values pcv.visualize.histogram(gray_img=lp_img) # Lapacian image sharpening, this step will enhance the darkness of the edges detected lp_shrp_img = pcv.image_subtract(gray_img1=img, gray_img2=lp_img) # Plot histogram of grayscale values, this helps to determine thresholding value pcv.visualize.histogram(gray_img=lp_shrp_img) # Sobel filtering # 1st derivative sobel filtering along horizontal axis, kernel = 1) # Inputs: # gray_img - Grayscale image data # dx - Derivative of x to analyze # dy - Derivative of y to analyze # ksize - Aperture size used to calculate 2nd derivative, specifies the size of the kernel and must be an odd integer # NOTE: Aperture size must be greater than the largest derivative (ksize > dx & ksize > dy) sbx_img = pcv.sobel_filter(gray_img=img, dx=1, dy=0, ksize=1) # 1st derivative sobel filtering along vertical axis, kernel = 1) sby_img = pcv.sobel_filter(gray_img=img, dx=0, dy=1, ksize=1) # Combine the effects of both x and y filters through matrix addition # This will capture edges identified within each plane and emphasize edges found in both images # Inputs: # gray_img1 - Grayscale image data to be added to gray_img2 # gray_img2 - Grayscale image data to be added to gray_img1 sb_img = pcv.image_add(gray_img1=sbx_img, gray_img2=sby_img) # Use a lowpass (blurring) filter to smooth sobel image # Inputs: # gray_img - Grayscale image data # ksize - Kernel size (integer or tuple), (ksize, ksize) box if integer input, # (n, m) box if tuple input mblur_img = pcv.median_blur(gray_img=sb_img, ksize=1) # Inputs: # gray_img - Grayscale image data mblur_invert_img = pcv.invert(gray_img=mblur_img) # combine the smoothed sobel image with the laplacian sharpened image # combines the best features of both methods as described in "Digital Image Processing" by Gonzalez and Woods pg. 169 edge_shrp_img = pcv.image_add(gray_img1=mblur_invert_img, gray_img2=lp_shrp_img) # Perform thresholding to generate a binary image tr_es_img = pcv.threshold.binary(gray_img=edge_shrp_img, threshold=145, max_value=255, object_type='dark') # Do erosion with a 3x3 kernel (ksize=3) # Inputs: # gray_img - Grayscale (usually binary) image data # ksize - The size used to build a ksize x ksize # matrix using np.ones. Must be greater than 1 to have an effect # i - An integer for the number of iterations e1_img = pcv.erode(gray_img=tr_es_img, ksize=3, i=1) # Bring the two object identification approaches together. # Using a logical OR combine object identified by background subtraction and the object identified by derivative filter. # Inputs: # bin_img1 - Binary image data to be compared in bin_img2 # bin_img2 - Binary image data to be compared in bin_img1 comb_img = pcv.logical_or(bin_img1=e1_img, bin_img2=bkg_sub_thres_img) # Get masked image, Essentially identify pixels corresponding to plant and keep those. # Inputs: # rgb_img - RGB image data # mask - Binary mask image data # mask_color - 'black' or 'white' masked_erd = pcv.apply_mask(rgb_img=img, mask=comb_img, mask_color='black') # Need to remove the edges of the image, we did that by generating a set of rectangles to mask the edges # img is (1280 X 960) # mask for the bottom of the image # Inputs: # img - RGB or grayscale image data # p1 - Point at the top left corner of the rectangle (tuple) # p2 - Point at the bottom right corner of the rectangle (tuple) # color 'black' (default), 'gray', or 'white' # masked1, box1_img, rect_contour1, hierarchy1 = pcv.rectangle_mask(img=img, p1=(500, 875), p2=(720, 960)) # mask the edges masked2, box2_img, rect_contour2, hierarchy2 = pcv.rectangle_mask(img=img, p1=(1, 1), p2=(1279, 959)) bx12_img = pcv.logical_or(bin_img1=box1_img, bin_img2=box2_img) inv_bx1234_img = bx12_img # we dont invert inv_bx1234_img = bx12_img #inv_bx1234_img = pcv.invert(gray_img=bx12_img) edge_masked_img = pcv.apply_mask(rgb_img=masked_erd, mask=inv_bx1234_img, mask_color='black') #print("here we create a mask") mask, masked = pcv.threshold.custom_range(rgb_img=edge_masked_img, lower_thresh=[25], upper_thresh=[175], channel='gray') masked = pcv.apply_mask(rgb_img=masked, mask=mask, mask_color='white') #print("end") # 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=edge_masked_img, mask=mask) # Define ROI # 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=edge_masked_img, x=100, y=100, h=800, w=1000) # Decide which objects to keep # 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 pcv.find_objects function # obj_hierarchy = hierarchy of objects, output from pcv.find_objects function # roi_type = 'partial' (default, for partially inside), 'cutto', or # 'largest' (keep only largest contour) with HiddenPrints(): roi_objects, hierarchy5, kept_mask, obj_area = pcv.roi_objects( img=edge_masked_img, roi_contour=roi1, roi_hierarchy=roi_hierarchy, object_contour=id_objects, obj_hierarchy=obj_hierarchy, roi_type='largest') rgb_img = cv2.cvtColor(img, cv2.COLOR_GRAY2RGB) # Inputs: # img - RGB or grayscale image data for plotting # contours - Contour list # hierarchy - Contour hierarchy array o, m = pcv.object_composition(img=rgb_img, contours=roi_objects, hierarchy=hierarchy5) ### Analysis ### outfile = False if args.writeimg == True: outfile = args.outdir + "/" + filename # Perform signal analysis # Inputs: # img - RGB or grayscale image data # obj- Single or grouped contour object # mask - Binary image mask to use as mask for moments analysis shape_img = pcv.analyze_object(img=img, obj=o, mask=m) new_im = Image.fromarray(shape_img) new_im.save("output//" + args.filename + "shape_img_side.png") # Inputs: # gray_img - 8 or 16-bit grayscale image data # mask - Binary mask made from selected contours # bins - Number of classes to divide the spectrum into # histplot - If True, plots the histogram of intensity values nir_hist = pcv.analyze_nir_intensity(gray_img=img, mask=kept_mask, bins=256, histplot=True) # Pseudocolor the grayscale image to a colormap # Inputs: # gray_img - Grayscale image data # obj - Single or grouped contour object (optional), if provided the pseudocolored image gets cropped down to the region of interest. # mask - Binary mask (optional) # background - Background color/type. Options are "image" (gray_img), "white", or "black". A mask must be supplied. # cmap - Colormap # min_value - Minimum value for range of interest # max_value - Maximum value for range of interest # dpi - Dots per inch for image if printed out (optional, if dpi=None then the default is set to 100 dpi). # axes - If False then the title, x-axis, and y-axis won't be displayed (default axes=True). # colorbar - If False then the colorbar won't be displayed (default colorbar=True) pseudocolored_img = pcv.visualize.pseudocolor(gray_img=img, mask=kept_mask, cmap='viridis') # Perform shape analysis # Inputs: # img - RGB or grayscale image data # obj- Single or grouped contour object # mask - Binary image mask to use as mask for moments analysis shape_imgs = pcv.analyze_object(img=rgb_img, obj=o, mask=m) # Write shape and nir 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 image img, path, filename = pcv.readimage(filename=args.image) # 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=85, 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 b_thresh = pcv.threshold.binary(gray_img=b, threshold=160, max_value=255, object_type='light') b_cnt = pcv.threshold.binary(gray_img=b, threshold=160, max_value=255, object_type='light') # Fill small objects # b_fill = pcv.fill(b_thresh, 10) # 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') cv2.imwrite("masked.jpeg", masked) # 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=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') cv2.imwrite("masked2.jpeg", masked2)
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")
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))
object_type='light') # In[18]: # Optional step in vis workflow that I tried. Fills small objects, not very useful here. b_fill = pcv.fill(b_thresh, 10) # In[22]: # Joining the s_mblur image with the b_cnt image. bs = pcv.logical_or(bin_img1=s_mblur, bin_img2=b_cnt) # In[24]: # The image above is now used as a "mask" over the original image to wipe the background. masked = pcv.apply_mask(img=img, mask=bs, mask_color='white') # In[25]: # Extracting the Green-Magenta channel. masked_a = pcv.rgb2gray_lab(rgb_img=masked, channel='a') # In[26]: # Extracting the Blue-Yellow channel. masked_b = pcv.rgb2gray_lab(rgb_img=masked, channel='b') # In[27]: # Thresholding the Green-Magenta channel using "dark". maskeda_thresh = pcv.threshold.binary(gray_img=masked_a,
# Collect the cropped image cropped_img = image[int(roi[1]):int(roi[1] + roi[3]), int(roi[0]):int(roi[0] + roi[2])] # Convert RGB to HSV and extract saturation channel # The HSV value can be changed to be h, s, or v depending on the colour of the flower saturation_img = pcv.rgb2gray_hsv(cropped_img, 'h') # Threshold the saturation img # Depending on the output of the saturation image, the value can be light or dark # Light or dark is what defines what part of the image its to be removed saturation_thresh = pcv.threshold.binary(saturation_img, 85, 255, 'light') # Apply median blur saturation_mblur = pcv.median_blur(saturation_thresh, 5) # Convert RGB to LAB and extract blue channel # Like the HSV function this can be l, a, or b depending on the colour of the flower blue_channel_img = pcv.rgb2gray_lab(cropped_img, 'l') blue_channel_cnt = pcv.threshold.binary(blue_channel_img, 160, 255, 'light') # Join thresholded saturated and blue channel imgs blue_saturated = pcv.logical_or(saturation_mblur, blue_channel_cnt) # Apply black colour mask masked = pcv.apply_mask(cropped_img, blue_saturated, 'black') # Save image cv2.imwrite('tmp/' + image, masked)
def plantCVProcess(img, x, y, w, h): # 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=85, 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 b_thresh = pcv.threshold.binary(gray_img=b, threshold=160, max_value=255, object_type='light') b_cnt = pcv.threshold.binary(gray_img=b, threshold=160, max_value=255, object_type='light') # Fill small objects # b_fill = pcv.fill(b_thresh, 10) # 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 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 roi1, roi_hierarchy = pcv.roi.rectangle(img=masked2, x=x, y=y, 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) ############### Analysis ################ # 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=mask, hist_plot_type='all') # Pseudocolor the grayscale image pseudocolored_img = pcv.visualize.pseudocolor(gray_img=s, mask=mask, cmap='jet') return print_results()
def root(): uploaded_file = st.file_uploader("Choose an image...", type="jpg") if uploaded_file is not None: inp = Image.open(uploaded_file) inp.save('input.jpg') img, path, filename = pcv.readimage(filename='input.jpg') image = Image.open('input.jpg') st.image(image, caption='Original Image',use_column_width=True) # Convert RGB to HSV and extract the saturation channel # Inputs: # rgb_image - RGB image data # channel - Split by 'h' (hue), 's' (saturation), or 'v' (value) channel s = pcv.rgb2gray_hsv(rgb_img=img, channel='s') pcv.print_image(s, "plant/rgbtohsv.png") image = Image.open('plant/rgbtohsv.png') st.image(image, caption='RGB to HSV', use_column_width=True) s_thresh = pcv.threshold.binary(gray_img=s, threshold=85, max_value=255, object_type='light') pcv.print_image(s_thresh, "plant/binary_threshold.png") image = Image.open('plant/binary_threshold.png') st.image(image, caption='Binary Threshold',use_column_width=True) # Median Blur to clean noise # Inputs: # gray_img - Grayscale image data # ksize - Kernel size (integer or tuple), (ksize, ksize) box if integer input, # (n, m) box if tuple input s_mblur = pcv.median_blur(gray_img=s_thresh, ksize=5) pcv.print_image(s_mblur, "plant/Median_blur.png") image = Image.open('plant/Median_blur.png') st.image(image, caption='Median Blur',use_column_width=True) # An alternative to using median_blur is gaussian_blur, which applies # a gaussian blur filter to the image. Depending on the image, one # technique may be more effective than others. # Inputs: # img - RGB or grayscale image data # ksize - Tuple of kernel size # sigma_x - Standard deviation in X direction; if 0 (default), # calculated from kernel size # sigma_y - Standard deviation in Y direction; if sigmaY is # None (default), sigmaY is taken to equal sigmaX gaussian_img = pcv.gaussian_blur(img=s_thresh, ksize=(5, 5), sigma_x=0, sigma_y=None) # Convert RGB to LAB and extract the blue channel ('b') # Input: # rgb_img - RGB image data # channel- Split by 'l' (lightness), 'a' (green-magenta), or 'b' (blue-yellow) channel b = pcv.rgb2gray_lab(rgb_img=img, channel='b') b_thresh = pcv.threshold.binary(gray_img=b, threshold=160, max_value=255, object_type='light') # Join the threshold saturation and blue-yellow images with a logical or operation # Inputs: # bin_img1 - Binary image data to be compared to bin_img2 # bin_img2 - Binary image data to be compared to bin_img1 bs = pcv.logical_or(bin_img1=s_mblur, bin_img2=b_thresh) pcv.print_image(bs, "plant/threshold comparison.png") image = Image.open('plant/threshold comparison.png') st.image(image, caption='Threshold Comparision',use_column_width=True) # Appy Mask (for VIS images, mask_color='white') # Inputs: # img - RGB or grayscale image data # mask - Binary mask image data # mask_color - 'white' or 'black' masked = pcv.apply_mask(img=img, mask=bs, mask_color='white') pcv.print_image(masked, "plant/Apply_mask.png") image = Image.open('plant/Apply_mask.png') st.image(image, caption='Applied Mask',use_column_width=True) # 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=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') pcv.print_image( maskeda_thresh, "plant/maskeda_thresh.png") pcv.print_image(maskeda_thresh1, "plant/maskeda_thresh1.png") pcv.print_image(maskedb_thresh, "plant/maskedb_thresh1.png") image = Image.open('plant/maskeda_thresh.png') st.image(image, caption='Threshold green-magneta and blue image',use_column_width=True) # 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) # Opening filters out bright noise from an image. # Inputs: # gray_img - Grayscale or binary image data # kernel - Optional neighborhood, expressed as an array of 1's and 0's. If None (default), # uses cross-shaped structuring element. opened_ab = pcv.opening(gray_img=ab) # Depending on the situation it might be useful to use the # exclusive or (pcv.logical_xor) function. # Inputs: # bin_img1 - Binary image data to be compared to bin_img2 # bin_img2 - Binary image data to be compared to bin_img1 xor_img = pcv.logical_xor(bin_img1=maskeda_thresh, bin_img2=maskedb_thresh) # Fill small objects (reduce image noise) # Inputs: # bin_img - Binary image data # size - Minimum object area size in pixels (must be an integer), and smaller objects will be filled ab_fill = pcv.fill(bin_img=ab, size=200) # Closing filters out dark noise from an image. # Inputs: # gray_img - Grayscale or binary image data # kernel - Optional neighborhood, expressed as an array of 1's and 0's. If None (default), # uses cross-shaped structuring element. closed_ab = pcv.closing(gray_img=ab_fill) # Apply mask (for VIS images, mask_color=white) masked2 = pcv.apply_mask(img=masked, mask=ab_fill, mask_color='white') # 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) # Define the region of interest (ROI) # 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=masked2, x=50, y=50, h=100, w=100) # Decide which objects to keep # 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 pcv.find_objects function # obj_hierarchy = hierarchy of objects, output from pcv.find_objects function # roi_type = 'partial' (default, for partially inside the ROI), 'cutto', or # 'largest' (keep only largest contour) 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 # Inputs: # img - RGB or grayscale image data for plotting # contours - Contour list # hierarchy - Contour hierarchy array obj, mask = pcv.object_composition(img=img, contours=roi_objects, hierarchy=hierarchy3) ############### Analysis ################ # Find shape properties, data gets stored to an Outputs class automatically # Inputs: # img - RGB or grayscale image data # obj- Single or grouped contour object # mask - Binary image mask to use as mask for moments analysis analysis_image = pcv.analyze_object(img=img, obj=obj, mask=mask) pcv.print_image(analysis_image, "plant/analysis_image.png") image = Image.open('plant/analysis_image.png') st.image(image, caption='Analysis_image',use_column_width=True) # Shape properties relative to user boundary line (optional) # Inputs: # img - RGB or grayscale image data # obj - Single or grouped contour object # mask - Binary mask of selected contours # line_position - Position of boundary line (a value of 0 would draw a line # through the bottom of the image) boundary_image2 = pcv.analyze_bound_horizontal(img=img, obj=obj, mask=mask, line_position=370) pcv.print_image(boundary_image2, "plant/boundary_image2.png") image = Image.open('plant/boundary_image2.png') st.image(image, caption='Boundary Image',use_column_width=True) # Determine color properties: Histograms, Color Slices and Pseudocolored Images, output color analyzed images (optional) # Inputs: # rgb_img - RGB image data # mask - Binary mask of selected contours # hist_plot_type - None (default), 'all', 'rgb', 'lab', or 'hsv' # This is the data to be printed to the SVG histogram file color_histogram = pcv.analyze_color(rgb_img=img, mask=kept_mask, hist_plot_type='all') # Print the histogram out to save it pcv.print_image(img=color_histogram, filename="plant/vis_tutorial_color_hist.jpg") image = Image.open('plant/vis_tutorial_color_hist.jpg') st.image(image, caption='Color Histogram',use_column_width=True) # Divide plant object into twenty equidistant bins and assign pseudolandmark points based upon their # actual (not scaled) position. Once this data is scaled this approach may provide some information # regarding shape independent of size. # Inputs: # img - RGB or grayscale image data # obj - Single or grouped contour object # mask - Binary mask of selected contours 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) # The print_results function will take the measurements stored when running any (or all) of these functions, format, # and print an output text file for data analysis. The Outputs class stores data whenever any of the following functions # are ran: analyze_bound_horizontal, analyze_bound_vertical, analyze_color, analyze_nir_intensity, analyze_object, # fluor_fvfm, report_size_marker_area, watershed. If no functions have been run, it will print an empty text file pcv.print_results(filename='vis_tutorial_results.txt')
def cluster_contour_splitimg(device, img, grouped_contour_indexes, contours, outdir=None, file=None, filenames=None, debug=None): """ 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. Inputs: device = Counter for image processing steps 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 outdir = out directory for output images file = the name of the input image to use as a plantcv 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) debug = print debugging images Returns: device = pipeline step counter output_path = array of paths to output images :param device: int :param img: ndarray :param grouped_contour_indexes: list :param contours: list :param outdir: str :param file: str :param filenames: str :param debug: str :return device: int :return output_path: str """ # get names to split also to check the target number of objects i = datetime.now() timenow = i.strftime('%m-%d-%Y_%H:%M:%S') if file == None: filebase = timenow else: filebase = file[:-4] if filenames == None: l = len(grouped_contour_indexes) namelist = [] for x in range(0, l): namelist.append(x) else: with open(filenames, 'r') as n: namelist = n.read().splitlines() n.close() # make sure the number of objects matches the namelist, and if not, remove the smallest grouped countor # removing contours is not ideal but the lists don't match there is a warning to check output if len(namelist) == len(grouped_contour_indexes): corrected_contour_indexes = grouped_contour_indexes elif len(namelist) < len(grouped_contour_indexes): print("Warning number of names is less than number of grouped contours, attempting to fix, to double check " "output") diff = len(grouped_contour_indexes) - len(namelist) size = [] for i, x in enumerate(grouped_contour_indexes): totallen = [] for a in x: g = i la = len(contours[a]) totallen.append(la) sumlen = np.sum(totallen) size.append((sumlen, g, i)) dtype = [('len', int), ('group', list), ('index', int)] lencontour = np.array(size, dtype=dtype) lencontour = np.sort(lencontour, order='len') rm_contour = lencontour[diff:] rm_contour = np.sort(rm_contour, order='group') corrected_contour_indexes = [] for x in rm_contour: index = x[2] corrected_contour_indexes.append(grouped_contour_indexes[index]) elif len(namelist) > len(grouped_contour_indexes): print("Warning number of names is more than number of grouped contours, double check output") diff = len(namelist) - len(grouped_contour_indexes) namelist = namelist[0:-diff] corrected_contour_indexes = grouped_contour_indexes # create filenames group_names = [] for i, x in enumerate(namelist): plantname = str(filebase) + '_' + str(x) + '_p' + str(i) + '.jpg' group_names.append(plantname) # split image output_path = [] for y, x in enumerate(corrected_contour_indexes): if outdir != None: savename = os.path.join(str(outdir), group_names[y]) else: savename = os.path.join(".", group_names[y]) iy, ix, iz = np.shape(img) mask = np.zeros((iy, ix, 3), dtype=np.uint8) masked_img = np.copy(img) for a in x: cv2.drawContours(mask, contours, a, (255, 255, 255), -1, lineType=8) mask_binary = mask[:, :, 0] if np.sum(mask_binary) == 0: pass else: retval, mask_binary = cv2.threshold(mask_binary, 254, 255, cv2.THRESH_BINARY) device, masked1 = apply_mask(masked_img, mask_binary, 'white', device, debug) if outdir != None: print_image(masked1, savename) output_path.append(savename) if debug == 'print': print_image(masked1, (str(device) + '_clusters.png')) elif debug == 'plot': if len(np.shape(masked1)) == 3: plot_image(masked1) else: plot_image(masked1, cmap='gray') plot_image(masked1) return device, output_path
def main(): # Get options args = options() debug = args.debug # Read image img, path, filename = pcv.readimage(args.image) # Pipeline step device = 0 device, img1 = pcv.white_balance(device, img, debug, (100, 100, 1000, 1000)) img = img1 seedmask, path1, filename1 = pcv.readimage(args.mask) device, seedmask = pcv.rgb2gray(seedmask, device, debug) device, inverted = pcv.invert(seedmask, device, debug) device, masked_img = pcv.apply_mask(img, inverted, 'white', device, debug) device, img_gray_sat = pcv.rgb2gray_hsv(masked_img, 's', device, debug) device, img_binary = pcv.binary_threshold(img_gray_sat, 50, 255, 'light', device, debug) img_binary1 = np.copy(img_binary) device, fill_image = pcv.fill(img_binary1, img_binary, 300, device, debug) device, seed_objects, seed_hierarchy = pcv.find_objects( img, fill_image, device, debug) device, roi1, roi_hierarchy1 = 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', roi1, roi_hierarchy1, seed_objects, seed_hierarchy, device, debug) img_copy = np.copy(img) for i in range(0, len(roi_objects)): rand_color = color_palette(1) cv2.drawContours(img_copy, roi_objects, i, rand_color[0], -1, lineType=8, hierarchy=roi_obj_hierarchy) pcv.print_image( img_copy, os.path.join(args.outdir, filename[:-4]) + "-seed-confetti.jpg") shape_header = [] # Store the table header table = [] # Store the PlantCV measurements for each seed in a table for i in range(0, len(roi_objects)): if roi_obj_hierarchy[0][i][ 3] == -1: # Only continue if the object is an outermost contour # Object combine kept objects # Inputs: # contours = object list # device = device number. Used to count steps in the pipeline # debug = None, print, or plot. Print = save to file, Plot = print to screen. device, obj, mask = pcv.object_composition( img, [roi_objects[i]], np.array([[roi_obj_hierarchy[0][i]]]), device, None) if obj is not None: # Measure the area and other shape properties of each seed # Inputs: # img = image object (most likely the original), color(RGB) # imgname = name of image # obj = single or grouped contour object # device = device number. Used to count steps in the pipeline # debug = None, print, or plot. Print = save to file, Plot = print to screen. # filename = False or image name. If defined print image device, shape_header, shape_data, shape_img = pcv.analyze_object( img, "img", obj, mask, device, None) if shape_data is not None: table.append(shape_data[1]) data_array = np.array(table) maxval = np.argmax(data_array) maxseed = np.copy(img) cv2.drawContours(maxseed, roi_objects, maxval, (0, 255, 0), 50) imgtext = "This image has " + str(len(data_array)) + " seeds" sizeseed = "The largest seed is in green and is " + str( data_array[maxval]) + " pixels" cv2.putText(maxseed, imgtext, (500, 500), cv2.FONT_HERSHEY_SIMPLEX, 5, (0, 255, 0), 10) cv2.putText(maxseed, sizeseed, (500, 1000), cv2.FONT_HERSHEY_SIMPLEX, 5, (255, 0, 255), 10) pcv.print_image(maxseed, os.path.join(args.outdir, filename[:-4]) + "-maxseed.jpg")
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 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
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
def cluster_contour_splitimg(rgb_img, grouped_contour_indexes, contours, hierarchy, outdir=None, file=None, filenames=None): """ 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. Inputs: rgb_img = RGB image data grouped_contour_indexes = output of cluster_contours, indexes of clusters of contours contours = contours to cluster, output of cluster_contours hierarchy = hierarchy of contours, output of find_objects outdir = out directory for output images file = the name of the input image to use as a plantcv 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) Returns: output_path = array of paths to output images :param rgb_img: numpy.ndarray :param grouped_contour_indexes: list :param contours: list :param hierarchy: numpy.ndarray :param outdir: str :param file: str :param filenames: str :return output_path: str """ params.device += 1 sys.stderr.write( 'This function has been updated to include object hierarchy so object holes can be included\n') # get names to split also to check the target number of objects i = datetime.now() timenow = i.strftime('%m-%d-%Y_%H:%M:%S') if file is None: filebase = timenow else: filebase = os.path.splitext(file)[0] if filenames is None: l = len(grouped_contour_indexes) namelist = [] for x in range(0, l): namelist.append(x) else: with open(filenames, 'r') as n: namelist = n.read().splitlines() n.close() # make sure the number of objects matches the namelist, and if not, remove the smallest grouped countor # removing contours is not ideal but the lists don't match there is a warning to check output if len(namelist) == len(grouped_contour_indexes): corrected_contour_indexes = grouped_contour_indexes elif len(namelist) < len(grouped_contour_indexes): print("Warning number of names is less than number of grouped contours, attempting to fix, to double check " "output") diff = len(grouped_contour_indexes) - len(namelist) size = [] for i, x in enumerate(grouped_contour_indexes): totallen = [] for a in x: g = i la = len(contours[a]) totallen.append(la) sumlen = np.sum(totallen) size.append((sumlen, g, i)) dtype = [('len', int), ('group', list), ('index', int)] lencontour = np.array(size, dtype=dtype) lencontour = np.sort(lencontour, order='len') rm_contour = lencontour[diff:] rm_contour = np.sort(rm_contour, order='group') corrected_contour_indexes = [] for x in rm_contour: index = x[2] corrected_contour_indexes.append(grouped_contour_indexes[index]) elif len(namelist) > len(grouped_contour_indexes): print("Warning number of names is more than number of grouped contours, double check output") diff = len(namelist) - len(grouped_contour_indexes) namelist = namelist[0:-diff] corrected_contour_indexes = grouped_contour_indexes # create filenames group_names = [] group_names1 = [] for i, x in enumerate(namelist): plantname = str(filebase) + '_' + str(x) + '_p' + str(i) + '.jpg' maskname = str(filebase) + '_' + str(x) + '_p' + str(i) + '_mask.jpg' group_names.append(plantname) group_names1.append(maskname) # split image output_path = [] output_imgs = [] output_masks = [] for y, x in enumerate(corrected_contour_indexes): if outdir is not None: savename = os.path.join(str(outdir), group_names[y]) savename1 = os.path.join(str(outdir), group_names1[y]) else: savename = os.path.join(".", group_names[y]) savename1 = os.path.join(".", group_names1[y]) iy, ix, iz = np.shape(rgb_img) mask = np.zeros((iy, ix, 3), dtype=np.uint8) masked_img = np.copy(rgb_img) for a in x: if hierarchy[0][a][3] > -1: cv2.drawContours(mask, contours, a, (0, 0, 0), -1, lineType=8, hierarchy=hierarchy) else: cv2.drawContours(mask, contours, a, (255, 255, 255), -1, lineType=8, hierarchy=hierarchy) mask_binary = mask[:, :, 0] if np.sum(mask_binary) == 0: pass else: retval, mask_binary = cv2.threshold(mask_binary, 254, 255, cv2.THRESH_BINARY) masked1 = apply_mask(masked_img, mask_binary, 'white') output_imgs.append(masked1) output_masks.append(mask_binary) if outdir is not None: print_image(masked1, savename) print_image(mask_binary, savename1) output_path.append(savename) if params.debug == 'print': print_image(masked1, os.path.join(params.debug_outdir, str(params.device) + '_clusters.png')) print_image(mask_binary, os.path.join(params.debug_outdir, str(params.device) + '_clusters_mask.png')) elif params.debug == 'plot': plot_image(masked1) plot_image(mask_binary, cmap='gray') return output_path, output_imgs, output_masks
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")
def cluster_contour_splitimg(rgb_img, grouped_contour_indexes, contours, hierarchy, outdir=None, file=None, filenames=None): """ 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. Inputs: rgb_img = RGB image data grouped_contour_indexes = output of cluster_contours, indexes of clusters of contours contours = contours to cluster, output of cluster_contours hierarchy = hierarchy of contours, output of find_objects outdir = out directory for output images file = the name of the input image to use as a plantcv 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) Returns: output_path = array of paths to output images :param rgb_img: numpy.ndarray :param grouped_contour_indexes: list :param contours: list :param hierarchy: numpy.ndarray :param outdir: str :param file: str :param filenames: str :return output_path: str """ params.device += 1 sys.stderr.write( 'This function has been updated to include object hierarchy so object holes can be included\n' ) # get names to split also to check the target number of objects i = datetime.now() timenow = i.strftime('%m-%d-%Y_%H:%M:%S') if file is None: filebase = timenow else: filebase = os.path.splitext(file)[0] if filenames is None: l = len(grouped_contour_indexes) namelist = [] for x in range(0, l): namelist.append(x) else: with open(filenames, 'r') as n: namelist = n.read().splitlines() n.close() # make sure the number of objects matches the namelist, and if not, remove the smallest grouped countor # removing contours is not ideal but the lists don't match there is a warning to check output if len(namelist) == len(grouped_contour_indexes): corrected_contour_indexes = grouped_contour_indexes elif len(namelist) < len(grouped_contour_indexes): print( "Warning number of names is less than number of grouped contours, attempting to fix, to double check " "output") diff = len(grouped_contour_indexes) - len(namelist) size = [] for i, x in enumerate(grouped_contour_indexes): totallen = [] for a in x: g = i la = len(contours[a]) totallen.append(la) sumlen = np.sum(totallen) size.append((sumlen, g, i)) dtype = [('len', int), ('group', list), ('index', int)] lencontour = np.array(size, dtype=dtype) lencontour = np.sort(lencontour, order='len') rm_contour = lencontour[diff:] rm_contour = np.sort(rm_contour, order='group') corrected_contour_indexes = [] for x in rm_contour: index = x[2] corrected_contour_indexes.append(grouped_contour_indexes[index]) elif len(namelist) > len(grouped_contour_indexes): print( "Warning number of names is more than number of grouped contours, double check output" ) diff = len(namelist) - len(grouped_contour_indexes) namelist = namelist[0:-diff] corrected_contour_indexes = grouped_contour_indexes # create filenames group_names = [] group_names1 = [] for i, x in enumerate(namelist): plantname = str(filebase) + '_' + str(x) + '_p' + str(i) + '.jpg' maskname = str(filebase) + '_' + str(x) + '_p' + str(i) + '_mask.jpg' group_names.append(plantname) group_names1.append(maskname) # split image output_path = [] output_imgs = [] output_masks = [] for y, x in enumerate(corrected_contour_indexes): if outdir is not None: savename = os.path.join(str(outdir), group_names[y]) savename1 = os.path.join(str(outdir), group_names1[y]) else: savename = os.path.join(".", group_names[y]) savename1 = os.path.join(".", group_names1[y]) iy, ix, iz = np.shape(rgb_img) mask = np.zeros((iy, ix, 3), dtype=np.uint8) masked_img = np.copy(rgb_img) for a in x: if hierarchy[0][a][3] > -1: cv2.drawContours(mask, contours, a, (0, 0, 0), -1, lineType=8, hierarchy=hierarchy) else: cv2.drawContours(mask, contours, a, (255, 255, 255), -1, lineType=8, hierarchy=hierarchy) mask_binary = mask[:, :, 0] if np.sum(mask_binary) == 0: pass else: retval, mask_binary = cv2.threshold(mask_binary, 254, 255, cv2.THRESH_BINARY) masked1 = apply_mask(masked_img, mask_binary, 'white') output_imgs.append(masked1) output_masks.append(mask_binary) if outdir is not None: print_image(masked1, savename) print_image(mask_binary, savename1) output_path.append(savename) if params.debug == 'print': print_image( masked1, os.path.join(params.debug_outdir, str(params.device) + '_clusters.png')) print_image( mask_binary, os.path.join(params.debug_outdir, str(params.device) + '_clusters_mask.png')) elif params.debug == 'plot': plot_image(masked1) plot_image(mask_binary, cmap='gray') return output_path, output_imgs, output_masks
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)
def test(true_positive_file, test_parameters): hue_lower_tresh = test_parameters[0] hue_higher_tresh = test_parameters[1] saturation_lower_tresh = test_parameters[2] saturation_higher_tresh = test_parameters[3] value_lower_tresh = test_parameters[4] value_higher_tresh = test_parameters[5] green_lower_tresh = test_parameters[6] green_higher_tresh = test_parameters[7] red_lower_tresh = test_parameters[8] red_higher_thresh = test_parameters[9] blue_lower_tresh = test_parameters[10] blue_higher_tresh = test_parameters[11] blur_k = test_parameters[12] fill_k = test_parameters[13] 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 # 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='h') mask, masked_image = pcv.threshold.custom_range(rgb_img=s, lower_thresh=[hue_lower_tresh], upper_thresh=[hue_higher_tresh], channel='gray') masked = pcv.apply_mask(rgb_img=img, mask = mask, mask_color = 'white') #print("filtered on hue") s = pcv.rgb2gray_hsv(rgb_img=masked, channel='s') mask, masked_image = pcv.threshold.custom_range(rgb_img=s, lower_thresh=[saturation_lower_tresh], upper_thresh=[saturation_higher_tresh], channel='gray') masked = pcv.apply_mask(rgb_img=masked, mask = mask, mask_color = 'white') #print("filtered on saturation") s = pcv.rgb2gray_hsv(rgb_img=masked, channel='v') mask, masked_image = pcv.threshold.custom_range(rgb_img=s, lower_thresh=[value_lower_tresh], upper_thresh=[value_higher_tresh], channel='gray') masked = pcv.apply_mask(rgb_img=masked, mask = mask, mask_color = 'white') #print("filtered on value") mask, masked = pcv.threshold.custom_range(rgb_img=masked, lower_thresh=[0,green_lower_tresh,0], upper_thresh=[255,green_higher_tresh,255], channel='RGB') masked = pcv.apply_mask(rgb_img=masked, mask = mask, mask_color = 'white') #print("filtered on green") mask, masked = pcv.threshold.custom_range(rgb_img=masked, lower_thresh=[red_lower_tresh,0,0], upper_thresh=[red_higher_thresh,255,255], channel='RGB') masked = pcv.apply_mask(rgb_img=masked, mask = mask, mask_color = 'white') #print("filtered on red") mask_old, masked_old = pcv.threshold.custom_range(rgb_img=masked, lower_thresh=[0,0,blue_lower_tresh], upper_thresh=[255,255,blue_higher_tresh], channel='RGB') masked = pcv.apply_mask(rgb_img=masked_old, mask = mask_old, mask_color = 'white') #print("filtered on blue") ###____________________________________ Blur to minimize try: s_mblur = pcv.median_blur(gray_img = masked_old, ksize = blur_k) s = pcv.rgb2gray_hsv(rgb_img=s_mblur, channel='v') mask, masked_image = pcv.threshold.custom_range(rgb_img=s, lower_thresh=[0], upper_thresh=[254], channel='gray') except: print("failed blur step") try: mask = pcv.fill(mask, fill_k) except: pass masked = pcv.apply_mask(rgb_img=masked, mask = mask, mask_color = 'white') ###_____________________________________ Now to identify objects 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=135, max_value=255, object_type='light') maskedb_thresh = pcv.threshold.binary(gray_img=masked_b, threshold=128, max_value=255, object_type='light') 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 # Inputs: # bin_img - Binary image data # size - Minimum object area size in pixels (must be an integer), and smaller objects will be filled ab_fill = pcv.fill(bin_img=ab, size=200) #print("filled") # Apply mask (for VIS images, mask_color=white) masked2 = pcv.apply_mask(rgb_img=masked, mask=ab_fill, mask_color='white') id_objects, obj_hierarchy = pcv.find_objects(masked, ab_fill) # Let's just take the largest 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) if use_mask == True: return(mask) else: masked2 = pcv.apply_mask(rgb_img=masked, mask=mask, mask_color='white') return(masked2)