When starting an image-based phenotyping project it is important to consider what the end goals of the project are. The goals of the project will determine the the camera type, imaging layout, and will help to guide downstream analysis. For example, if the goal of the project is to quantify the growth rates of a population of Arabidopsis plants, you may want to take timelapse images of whole flats of plants with an RGB (VIS) camera.
To run a VIS workflow over a single VIS image there are two required inputs:
Image processing will work with adjustments if images are well lit and free of background that is similar in color to plant material.
2. Output directory: If debug mode is set to 'print' output images from each step are produced.
# Import Libraries from plantcv import plantcv as pcv import matplotlib
class options: def __init__(self): self.image = "./img/original_image.jpg" self.debug = "plot" self.writeimg= False self.result = "vis_tutorial_results.json" self.outdir = "." # Store the output to the current directory # Get options args = options() # Set debug to the global parameter pcv.params.debug = args.debug
# Read image # Inputs: # filename - Image file to be read in # mode - How to read in the image; either 'native' (default), 'rgb', 'gray', or 'csv' img, path, filename = pcv.readimage(filename=args.image)
# 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')
# Take a binary threshold to separate plant from background. # Threshold can be on either light or dark objects in the image. # Inputs: # gray_img - Grayscale image data # threshold- Threshold value (between 0-255) # max_value - Value to apply above threshold (255 = white) # object_type - 'light' (default) or 'dark'. If the object is lighter than # the background then standard threshold is done. If the object # is darker than the background then inverse thresholding is done. s_thresh = pcv.threshold.binary(gray_img=s, threshold=85, max_value=255, object_type='light')
# 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)
# 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)
Using multiple colorspace channels can lead to better descrimination between plant and the background in an image.
# 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') # Threshold the blue channel image 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)
# 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')
Now we'll focus on capturing the plant in the masked image. We will use masked green-magenta and blue-yellow channels. Then two channels are thresholded to caputre different portions of the plant, and thre three images are joined together. Small objected are filled, and the resulting binary image is used to mask the masked image previously obtained.
# 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)