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
def _pseudocolored_image(histogram, bins, img, mask, background, channel, filename, analysis_images): """Pseudocolor image. Inputs: histogram = a normalized histogram of color values from one color channel bins = number of color bins the channel is divided into img = input image mask = binary mask image background = what background image?: channel image (img) or white channel = color channel name filename = input image filename analysis_images = list of analysis image filenames Returns: analysis_images = list of analysis image filenames :param histogram: list :param bins: int :param img: numpy array :param mask: numpy array :param background: str :param channel: str :param filename: str :param analysis_images: list :return analysis_images: list """ mask_inv = cv2.bitwise_not(mask) cplant = cv2.applyColorMap(histogram, colormap=2) cplant1 = cv2.bitwise_and(cplant, cplant, mask=mask) output_imgs = { "pseudo_on_img": { "background": "img", "img": None }, "pseudo_on_white": { "background": "white", "img": None } } if background == 'img' or background == 'both': # mask the background and color the plant with color scheme 'jet' img_gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) img_back = cv2.bitwise_and(img_gray, img_gray, mask=mask_inv) img_back3 = np.dstack((img_back, img_back, img_back)) output_imgs["pseudo_on_img"]["img"] = cv2.add(cplant1, img_back3) if background == 'white' or background == 'both': # Get the image size if np.shape(img)[2] == 3: ix, iy, iz = np.shape(img) else: ix, iy = np.shape(img) size = ix, iy back = np.zeros(size, dtype=np.uint8) w_back = back + 255 w_back3 = np.dstack((w_back, w_back, w_back)) img_back3 = cv2.bitwise_and(w_back3, w_back3, mask=mask_inv) output_imgs["pseudo_on_white"]["img"] = cv2.add(cplant1, img_back3) if filename: for key in output_imgs: if output_imgs[key]["img"] is not None: fig_name_pseudo = str(filename[0:-4]) + '_' + str(channel) + '_pseudo_on_' + \ output_imgs[key]["background"] + '.jpg' path = os.path.dirname(filename) print_image(output_imgs[key]["img"], fig_name_pseudo) analysis_images.append(['IMAGE', 'pseudo', fig_name_pseudo]) else: path = "." if params.debug is not None: if params.debug == 'print': for key in output_imgs: if output_imgs[key]["img"] is not None: print_image( output_imgs[key]["img"], os.path.join( params.debug_outdir, str(params.device) + "_" + output_imgs[key]["background"] + '_pseudocolor.jpg')) fig_name = 'VIS_pseudocolor_colorbar_' + str( channel) + '_channel.svg' if not os.path.isfile(os.path.join(params.debug_outdir, fig_name)): plot_colorbar(path, fig_name, bins) elif params.debug == 'plot': for key in output_imgs: if output_imgs[key]["img"] is not None: plot_image(output_imgs[key]["img"]) return analysis_images
def fluor_fvfm(fdark, fmin, fmax, mask, device, filename, bins=1000, debug=None): """Analyze PSII camera images. Inputs: fdark = 16-bit grayscale fdark image fmin = 16-bit grayscale fmin image fmax = 16-bit grayscale fmax image mask = mask of plant (binary,single channel) device = counter for debug filename = name of file bins = number of bins from 0 to 65,536 (default is 1000) debug = None, print, or plot. Print = save to file, Plot = print to screen. Returns: device = device number hist_header = fvfm data table headers hist_data = fvfm data table values :param fdark: numpy array :param fmin: numpy array :param fmax: numpy array :param mask: numpy array :param device: int :param filename: str :param bins: int :param debug: str :return device: int :return hist_header: list :return hist_data: list """ # Auto-increment the device counter device += 1 # Check that fdark, fmin, and fmax are grayscale (single channel) if not all(len(np.shape(i)) == 2 for i in [fdark, fmin, fmax]): fatal_error("The fdark, fmin, and fmax images must be grayscale images.") # Check that fdark, fmin, and fmax are 16-bit images if not all(i.dtype == "uint16" for i in [fdark, fmin, fmax]): fatal_error("The fdark, fmin, and fmax images must be 16-bit images.") # QC Fdark Image fdark_mask = cv2.bitwise_and(fdark, fdark, mask=mask) if np.amax(fdark_mask) > 2000: qc_fdark = False else: qc_fdark = True # Mask Fmin and Fmax Image fmin_mask = cv2.bitwise_and(fmin, fmin, mask=mask) fmax_mask = cv2.bitwise_and(fmax, fmax, mask=mask) # Calculate Fvariable, where Fv = Fmax - Fmin (masked) fv = np.subtract(fmax_mask, fmin_mask) # When Fmin is greater than Fmax, a negative value is returned. # Because the data type is unsigned integers, negative values roll over, resulting in nonsensical values # Wherever Fmin is greater than Fmax, set Fv to zero fv[np.where(fmax_mask < fmin_mask)] = 0 # Calculate Fv/Fm (Fvariable / Fmax) where Fmax is greater than zero # By definition above, wherever Fmax is zero, Fvariable will also be zero # To calculate the divisions properly we need to change from unit16 to float64 data types fvfm = fv.astype(np.float64) fmax_flt = fmax_mask.astype(np.float64) fvfm[np.where(fmax_mask > 0)] /= fmax_flt[np.where(fmax_mask > 0)] # Calculate the median Fv/Fm value for non-zero pixels fvfm_median = np.median(fvfm[np.where(fvfm > 0)]) # Calculate the histogram of Fv/Fm non-zero values fvfm_hist, fvfm_bins = np.histogram(fvfm[np.where(fvfm > 0)], bins, range=(0, 1)) # fvfm_bins is a bins + 1 length list of bin endpoints, so we need to calculate bin midpoints so that # the we have a one-to-one list of x (FvFm) and y (frequency) values. # To do this we add half the bin width to each lower bin edge x-value midpoints = fvfm_bins[:-1] + 0.5 * np.diff(fvfm_bins) # Calculate which non-zero bin has the maximum Fv/Fm value max_bin = midpoints[np.argmax(fvfm_hist)] # Store Fluorescence Histogram Data hist_header = ( 'HEADER_HISTOGRAM', 'bin-number', 'fvfm_bins', 'fvfm_hist', 'fvfm_hist_peak', 'fvfm_median', 'fdark_passed_qc' ) hist_data = ( 'FLU_DATA', bins, np.around(midpoints, decimals=len(str(bins))).tolist(), fvfm_hist.tolist(), float(max_bin), float(np.around(fvfm_median, decimals=4)), qc_fdark ) if filename: import matplotlib matplotlib.use('Agg', warn=False) from matplotlib import pyplot as plt from matplotlib import cm as cm # Print F-variable image print_image(fv, (str(filename[0:-4]) + '_fv_img.png')) print('\t'.join(map(str, ('IMAGE', 'fv', str(filename[0:-4]) + '_fv_img.png')))) # Create Histogram Plot, if you change the bin number you might need to change binx so that it prints # an appropriate number of labels binx = int(bins / 50) plt.plot(midpoints, fvfm_hist, color='green', label='Fv/Fm') plt.xticks(list(midpoints[0::binx]), rotation='vertical', size='xx-small') plt.legend() ax = plt.subplot(111) ax.set_ylabel('Plant Pixels') ax.text(0.05, 0.95, ('Peak Bin Value: ' + str(max_bin)), transform=ax.transAxes, verticalalignment='top') plt.grid() plt.title('Fv/Fm of ' + str(filename[0:-4])) fig_name = (str(filename[0:-4]) + '_fvfm_hist.svg') plt.savefig(fig_name) plt.clf() print('\t'.join(map(str, ('IMAGE', 'hist', fig_name)))) # Pseudocolored Fv/Fm image fvfm_8bit = fvfm * 255 fvfm_8bit = fvfm_8bit.astype(np.uint8) plt.imshow(fvfm_8bit, vmin=0, vmax=1, cmap=cm.jet_r) plt.subplot(111) mask_inv = cv2.bitwise_not(mask) background = np.dstack((mask, mask, mask, mask_inv)) my_cmap = plt.get_cmap('binary_r') plt.imshow(background, cmap=my_cmap) plt.axis('off') fig_name = (str(filename[0:-4]) + '_pseudo_fvfm.png') plt.savefig(fig_name, dpi=600, bbox_inches='tight') plt.clf() print('\t'.join(map(str, ('IMAGE', 'pseudo', fig_name)))) path = os.path.dirname(filename) fig_name = 'FvFm_pseudocolor_colorbar.svg' if not os.path.isfile(path + '/' + fig_name): plot_colorbar(path, fig_name, 2) if debug == 'print': print_image(fmin_mask, (str(device) + '_fmin_mask.png')) print_image(fmax_mask, (str(device) + '_fmax_mask.png')) print_image(fv, (str(device) + '_fv_convert.png')) elif debug == 'plot': plot_image(fmin_mask, cmap='gray') plot_image(fmax_mask, cmap='gray') plot_image(fv, cmap='gray') return device, hist_header, hist_data