def calibrate(raw_data, white_reference, dark_reference): """This function allows you calibrate raw hyperspectral image data with white and dark reference data. Inputs: raw_data = Raw image 'Spectral_data' class instance white_reference = White reference 'Spectral_data' class instance dark_reference = Dark reference 'Spectral_data' class instance Returns: calibrated = Calibrated hyperspectral image :param raw_data: __main__.Spectral_data :param white_reference: __main__.Spectral_data :param dark_reference: __main__.Spectral_data :return calibrated: __main__.Spectral_data """ # Auto-increment device params.device += 1 # Collect the number of wavelengths present num_bands = len(white_reference.wavelength_dict) den = white_reference.array_data - dark_reference.array_data # Calibrate using reflectance = (raw data - dark reference) / (white reference - dark reference) output_num = [] for i in range(0, raw_data.lines): ans = raw_data.array_data[i,].astype(np.float16) - dark_reference.array_data output_num.append(ans) num = np.stack(output_num, axis=2) output_calibrated = [] for i in range(0, raw_data.lines): ans1 = raw_data.array_data[i,] / den output_calibrated.append(ans1) # Reshape into hyperspectral datacube scalibrated = np.stack(output_calibrated, axis=2) calibrated_array = np.transpose(scalibrated[0], (1, 0, 2)) # Make a new class instance with the calibrated hyperspectral image calibrated = Spectral_data(array_data=calibrated_array, max_wavelength=raw_data.max_wavelength, min_wavelength=raw_data.min_wavelength, d_type=raw_data.d_type, wavelength_dict=raw_data.wavelength_dict, samples=raw_data.samples, lines=raw_data.lines, interleave=raw_data.interleave, wavelength_units=raw_data.wavelength_units, array_type=raw_data.array_type, pseudo_rgb=None, filename=None, default_bands=raw_data.default_bands) # Make pseudo-rgb image for the calibrated image pseudo_rgb = _make_pseudo_rgb(spectral_array=calibrated) calibrated.pseudo_rgb = pseudo_rgb if params.debug == "plot": # Gamma correct pseudo_rgb image plot_image(pseudo_rgb) elif params.debug == "print": print_image(pseudo_rgb, os.path.join(params.debug_outdir, str(params.device) + "_calibrated_rgb.png")) return calibrated
def egi(rgb_img): """Excess Green Index. r = R / (R + G + B) g = G / (R + G + B) b = B / (R + G + B) EGI = 2g - r - b The theoretical range for EGI is (-1, 2). Inputs: rgb_img = Color image (np.array) Returns: index_array = Index data as a Spectral_data instance :param rgb_img: np.array :return index_array: np.array """ # Split the RGB image into component channels blue, green, red = cv2.split(rgb_img) # Calculate float32 sum of all channels total = red.astype(np.float32) + green.astype(np.float32) + blue.astype(np.float32) # Calculate normalized channels r = red.astype(np.float32) / total g = green.astype(np.float32) / total b = blue.astype(np.float32) / total index_array_raw = (2 * g) - r - b hsi = Spectral_data(array_data=None, max_wavelength=0, min_wavelength=0, max_value=255, min_value=0, d_type=np.uint8, wavelength_dict={}, samples=None, lines=None, interleave=None, wavelength_units=None, array_type=None, pseudo_rgb=None, filename=None, default_bands=None) return _package_index(hsi=hsi, raw_index=index_array_raw, method="EGI")
def extract_wavelength(spectral_data, wavelength): """Find index of a target wavelength band in a hyperspectral data instance. Inputs: spectral_array = Hyperspectral data instance wavelength = Target wavelength value Returns: index_array = Data instance of request wavelength band :param spectral_array: __main__.Spectral_data :param target: float :return index_array: __main__.Spectral_data """ # Make a list of all keys which are the wavelengths all_wavelengths = spectral_data.wavelength_dict.keys() # Find index of the band with the closest wavelengths band_index = _find_closest(np.array([float(i) for i in all_wavelengths]), wavelength) # Print which wavelength will be used wl_dict = spectral_data.wavelength_dict print("The closest band found to " + str(wavelength) + spectral_data.wavelength_units + " is: " + str(list(wl_dict.keys())[band_index])) # Reshape index_array_raw = spectral_data.array_data[:, :, [band_index]] index_array_raw = np.transpose(np.transpose(index_array_raw)[0]) # Resulting array is float 32 from -1 to 1, transform into uint8 for plotting all_positive = np.add(index_array_raw, np.ones(np.shape(index_array_raw))) normalized = all_positive.astype(np.float64) / 2 # normalize the data to 0 - 1 index_array = (255 * normalized).astype(np.uint8) # scale to 255 # Plot out grayscale image if params.debug == "plot": plot_image(index_array) elif params.debug == "print": print_image(index_array, os.path.join(params.debug_outdir, str(params.device) + str(wavelength) + "_index.png")) # Find array min and max values max_pixel = float(np.amax(index_array_raw)) min_pixel = float(np.amin(index_array_raw)) # Make a spectral data instance index_array = Spectral_data(array_data=index_array_raw, max_wavelength=wavelength, min_wavelength=wavelength, max_value=max_pixel, min_value=min_pixel, d_type=np.uint8, wavelength_dict={}, samples=spectral_data.samples, lines=spectral_data.lines, interleave=spectral_data.interleave, wavelength_units=spectral_data.wavelength_units, array_type="index_" + str(wavelength), pseudo_rgb=None, filename=spectral_data.filename, default_bands=None) return index_array
def _package_index(hsi, raw_index, method): """Private function to package raw index array as a Spectral_data object. Inputs: hsi = hyperspectral data (Spectral_data object) raw_index = raw index array method = index method (e.g. NDVI) Returns: index = index image as a Spectral_data object. :params hsi: __main__.Spectral_data :params raw_index: np.array :params method: str :params index: __main__.Spectral_data """ params.device += 1 # Store debug mode debug = params.debug params.debug = None # Resulting array is float 32 from varying natural ranges, transform into uint8 for plotting all_positive = np.add(raw_index, 2 * np.ones(np.shape(raw_index))) scaled = rescale(all_positive) # Find array min and max values obs_max_pixel = float(np.nanmax(raw_index)) obs_min_pixel = float(np.nanmin(raw_index)) index = Spectral_data(array_data=raw_index, max_wavelength=0, min_wavelength=0, max_value=obs_max_pixel, min_value=obs_min_pixel, d_type=np.uint8, wavelength_dict={}, samples=hsi.samples, lines=hsi.lines, interleave=hsi.interleave, wavelength_units=hsi.wavelength_units, array_type="index_" + method.lower(), pseudo_rgb=scaled, filename=hsi.filename, default_bands=None) # Restore debug mode params.debug = debug if params.debug == "plot": plot_image(index.pseudo_rgb) elif params.debug == "print": print_image(index.pseudo_rgb, os.path.join(params.debug_outdir, str(params.device) + method + "_index.png")) return index
def extract_index(array, index="NDVI", distance=20): """Pull out indices of interest from a hyperspectral datacube. Inputs: array = hyperspectral data instance index = index of interest, either "ndvi", "gdvi", or "savi" distance = how lenient to be if the required wavelengths are not available Returns: index_array = Index data as a Spectral_data instance :param array: __main__.Spectral_data :param index: str :param distance: int :return index_array: __main__.Spectral_data """ params.device += 1 # Min and max available wavelength will be used to determine if an index can be extracted max_wavelength = float(array.max_wavelength) min_wavelength = float(array.min_wavelength) # Dictionary of wavelength and it's index in the list wavelength_dict = array.wavelength_dict.copy() array_data = array.array_data.copy() if index.upper() == "NDVI": if (max_wavelength + distance) >= 800 and (min_wavelength - distance) <= 670: # Obtain index that best represents NIR and red bands nir_index = _find_closest( np.array([float(i) for i in wavelength_dict.keys()]), 800) red_index = _find_closest( np.array([float(i) for i in wavelength_dict.keys()]), 670) nir = (array_data[:, :, [nir_index]]) red = (array_data[:, :, [red_index]]) # Naturally ranges from -1 to 1 index_array_raw = (nir - red) / (nir + red) else: fatal_error( "Available wavelengths are not suitable for calculating NDVI. Try increasing fudge factor." ) elif index.upper() == "GDVI": # Green Difference Vegetation Index [Sripada et al. (2006)] if (max_wavelength + distance) >= 800 and (min_wavelength - distance) <= 680: nir_index = _find_closest( np.array([float(i) for i in wavelength_dict.keys()]), 800) red_index = _find_closest( np.array([float(i) for i in wavelength_dict.keys()]), 680) nir = (array_data[:, :, [nir_index]]) red = (array_data[:, :, [red_index]]) # Naturally ranges from -2 to 2 index_array_raw = nir - red else: fatal_error( "Available wavelengths are not suitable for calculating GDVI. Try increasing fudge factor." ) elif index.upper() == "SAVI": # Soil Adjusted Vegetation Index [Huete et al. (1988)] if (max_wavelength + distance) >= 800 and (min_wavelength - distance) <= 680: nir_index = _find_closest( np.array([float(i) for i in wavelength_dict.keys()]), 800) red_index = _find_closest( np.array([float(i) for i in wavelength_dict.keys()]), 680) nir = (array_data[:, :, [nir_index]]) red = (array_data[:, :, [red_index]]) # Naturally ranges from -1.2 to 1.2 index_array_raw = (1.5 * (nir - red)) / (red + nir + 0.5) else: fatal_error( "Available wavelengths are not suitable for calculating SAVI. Try increasing fudge factor." ) else: fatal_error( index + " is not one of the currently available indices for this function." ) # Reshape array into hyperspectral datacube shape index_array_raw = np.transpose(np.transpose(index_array_raw)[0]) # Store debug mode debug = params.debug params.debug = None # Resulting array is float 32 from varying natural ranges, transform into uint8 for plotting all_positive = np.add(index_array_raw, 2 * np.ones(np.shape(index_array_raw))) scaled = rescale(all_positive) index_array = Spectral_data(array_data=index_array_raw, max_wavelength=0, min_wavelength=0, d_type=np.uint8, wavelength_dict={}, samples=array.samples, lines=array.lines, interleave=array.interleave, wavelength_units=array.wavelength_units, array_type="index_" + index.lower(), pseudo_rgb=scaled, filename=array.filename, default_bands=None) # Restore debug mode params.debug = debug if params.debug == "plot": plot_image(index_array.pseudo_rgb) elif params.debug == "print": print_image( index_array.pseudo_rgb, os.path.join(params.debug_outdir, str(params.device) + index + "_index.png")) return index_array
def extract_index(array, index="NDVI", distance=20): """Pull out indices of interest from a hyperspectral datacube. Inputs: array = hyperspectral data instance index = index of interest, either "ndvi", "gdvi", or "savi" distance = how lenient to be if the required wavelengths are not available Returns: index_array = Index data as a Spectral_data instance :param array: __main__.Spectral_data :param index: str :param distance: int :return index_array: __main__.Spectral_data """ params.device += 1 # Min and max available wavelength will be used to determine if an index can be extracted max_wavelength = float(array.max_wavelength) min_wavelength = float(array.min_wavelength) # Dictionary of wavelength and it's index in the list wavelength_dict = array.wavelength_dict.copy() array_data = array.array_data.copy() if index.upper() == "NDVI": if (max_wavelength + distance) >= 800 and (min_wavelength - distance) <= 670: # Obtain index that best represents NIR and red bands nir_index = _find_closest(np.array([float(i) for i in wavelength_dict.keys()]), 800) red_index = _find_closest(np.array([float(i) for i in wavelength_dict.keys()]), 670) nir = (array_data[:, :, [nir_index]]) red = (array_data[:, :, [red_index]]) # Naturally ranges from -1 to 1 index_array_raw = (nir - red) / (nir + red) else: fatal_error("Available wavelengths are not suitable for calculating NDVI. Try increasing distance.") elif index.upper() == "GDVI": # Green Difference Vegetation Index [Sripada et al. (2006)] if (max_wavelength + distance) >= 800 and (min_wavelength - distance) <= 680: nir_index = _find_closest(np.array([float(i) for i in wavelength_dict.keys()]), 800) red_index = _find_closest(np.array([float(i) for i in wavelength_dict.keys()]), 680) nir = (array_data[:, :, [nir_index]]) red = (array_data[:, :, [red_index]]) # Naturally ranges from -2 to 2 index_array_raw = nir - red else: fatal_error("Available wavelengths are not suitable for calculating GDVI. Try increasing distance.") elif index.upper() == "SAVI": # Soil Adjusted Vegetation Index [Huete et al. (1988)] if (max_wavelength + distance) >= 800 and (min_wavelength - distance) <= 680: nir_index = _find_closest(np.array([float(i) for i in wavelength_dict.keys()]), 800) red_index = _find_closest(np.array([float(i) for i in wavelength_dict.keys()]), 680) nir = (array_data[:, :, [nir_index]]) red = (array_data[:, :, [red_index]]) # Naturally ranges from -1.2 to 1.2 index_array_raw = (1.5 * (nir - red)) / (red + nir + 0.5) else: fatal_error("Available wavelengths are not suitable for calculating SAVI. Try increasing distance.") elif index.upper() == "PRI": # Photochemical Reflectance Index (https://doi.org/10.1111/j.1469-8137.1995.tb03064.x) if (max_wavelength + distance) >= 570 and (min_wavelength - distance) <= 531: # Obtain index that best approximates 570 and 531 nm bands pri570_index = _find_closest(np.array([float(i) for i in wavelength_dict.keys()]), 570) pri531_index = _find_closest(np.array([float(i) for i in wavelength_dict.keys()]), 531) pri570 = (array_data[:, :, [pri570_index]]) pri531 = (array_data[:, :, [pri531_index]]) # PRI = (R531− R570)/(R531+ R570)) denominator = pri531 + pri570 # Avoid dividing by zero index_array_raw = np.where(denominator == 0, 0, ((pri531 - pri570) / denominator)) else: fatal_error("Available wavelengths are not suitable for calculating PRI. Try increasing distance.") elif index.upper() == "ACI": # Van den Berg et al. 2005 if (max_wavelength + distance) >= 800 and (min_wavelength - distance) <= 560: green_index = _find_closest(np.array([float(i) for i in wavelength_dict.keys()]), 560) nir_index = _find_closest(np.array([float(i) for i in wavelength_dict.keys()]), 800) green = (array_data[:, :, [green_index]]) nir = (array_data[:, :, [nir_index]]) # Naturally ranges from -1.0 to 1.0 index_array_raw = green/nir else: fatal_error("Available wavelengths are not suitable for calculating ACI. Try increasing distance.") elif index.upper() == "ARI": # Gitelson et al., 2001 if (max_wavelength + distance) >= 700 and (min_wavelength - distance) <= 550: ari550_indes = _find_closest(np.array([float(i) for i in wavelength_dict.keys()]), 550) ari700_index = _find_closest(np.array([float(i) for i in wavelength_dict.keys()]), 700) ari550 = (array_data[:, :, [ari550_indes]]) ari700 = (array_data[:, :, [ari700_index]]) index_array_raw = (1/ari550)-(1/ari700) else: fatal_error("Available wavelengths are not suitable for calculating ARI. Try increasing distance.") else: fatal_error(index + " is not one of the currently available indices for this function. Please open an issue " + "on the PlantCV GitHub account so we can add more handy indicies!") # Reshape array into hyperspectral datacube shape index_array_raw = np.transpose(np.transpose(index_array_raw)[0]) # Store debug mode debug = params.debug params.debug = None # Resulting array is float 32 from varying natural ranges, transform into uint8 for plotting all_positive = np.add(index_array_raw, 2 * np.ones(np.shape(index_array_raw))) scaled = rescale(all_positive) # Find array min and max values obs_max_pixel = float(np.nanmax(index_array_raw)) obs_min_pixel = float(np.nanmin(index_array_raw)) index_array = Spectral_data(array_data=index_array_raw, max_wavelength=0, min_wavelength=0, max_value=obs_max_pixel, min_value=obs_min_pixel, d_type=np.uint8, wavelength_dict={}, samples=array.samples, lines=array.lines, interleave=array.interleave, wavelength_units=array.wavelength_units, array_type="index_" + index.lower(), pseudo_rgb=scaled, filename=array.filename, default_bands=None) # Restore debug mode params.debug = debug if params.debug == "plot": plot_image(index_array.pseudo_rgb) elif params.debug == "print": print_image(index_array.pseudo_rgb, os.path.join(params.debug_outdir, str(params.device) + index + "_index.png")) return index_array
def extract_index(array, index="NDVI", distance=20): """Pull out indices of interest from a hyperspectral datacube. Inputs: array = hyperspectral data instance index = index of interest, "ndvi", "gdvi", "savi", "pri", "aci", "ari", "cari", "ci_rededge", "cri1", "cri2", "evi", "mari", "mcari", "mtci", "ndre", "psnd_chla", "psnd_chlb", "psnd_car", "psri", "pssr1", "pssr2", "pssr3", "rgri", "rvsi", "sipi", "sr", "vari", "vi_green", "wbi". distance = how lenient to be if the required wavelengths are not available Returns: index_array = Index data as a Spectral_data instance :param array: __main__.Spectral_data :param index: str :param distance: int :return index_array: __main__.Spectral_data """ params.device += 1 # Min and max available wavelength will be used to determine if an index can be extracted max_wavelength = float(array.max_wavelength) min_wavelength = float(array.min_wavelength) # Dictionary of wavelength and it's index in the list wavelength_dict = array.wavelength_dict array_data = array.array_data if index.upper() == "NDVI": if (max_wavelength + distance) >= 800 and (min_wavelength - distance) <= 670: # Obtain index that best represents NIR and red bands nir_index = _find_closest( np.array([float(i) for i in wavelength_dict.keys()]), 800) red_index = _find_closest( np.array([float(i) for i in wavelength_dict.keys()]), 670) nir = (array_data[:, :, [nir_index]]) red = (array_data[:, :, [red_index]]) # Naturally ranges from -1 to 1 index_array_raw = (nir - red) / (nir + red) else: fatal_error( "Available wavelengths are not suitable for calculating NDVI. Try increasing distance." ) elif index.upper() == "GDVI": # Green Difference Vegetation Index [Sripada et al. (2006)] if (max_wavelength + distance) >= 800 and (min_wavelength - distance) <= 680: nir_index = _find_closest( np.array([float(i) for i in wavelength_dict.keys()]), 800) red_index = _find_closest( np.array([float(i) for i in wavelength_dict.keys()]), 680) nir = (array_data[:, :, [nir_index]]) red = (array_data[:, :, [red_index]]) # Naturally ranges from -2 to 2 index_array_raw = nir - red else: fatal_error( "Available wavelengths are not suitable for calculating GDVI. Try increasing distance." ) elif index.upper() == "SAVI": # Soil Adjusted Vegetation Index [Huete et al. (1988)] if (max_wavelength + distance) >= 800 and (min_wavelength - distance) <= 680: nir_index = _find_closest( np.array([float(i) for i in wavelength_dict.keys()]), 800) red_index = _find_closest( np.array([float(i) for i in wavelength_dict.keys()]), 680) nir = (array_data[:, :, [nir_index]]) red = (array_data[:, :, [red_index]]) # Naturally ranges from -1.2 to 1.2 index_array_raw = (1.5 * (nir - red)) / (red + nir + 0.5) else: fatal_error( "Available wavelengths are not suitable for calculating SAVI. Try increasing distance." ) elif index.upper() == "PRI": # Photochemical Reflectance Index (https://doi.org/10.1111/j.1469-8137.1995.tb03064.x) if (max_wavelength + distance) >= 570 and (min_wavelength - distance) <= 531: # Obtain index that best approximates 570 and 531 nm bands pri570_index = _find_closest( np.array([float(i) for i in wavelength_dict.keys()]), 570) pri531_index = _find_closest( np.array([float(i) for i in wavelength_dict.keys()]), 531) pri570 = (array_data[:, :, [pri570_index]]) pri531 = (array_data[:, :, [pri531_index]]) # PRI = (R531- R570)/(R531+ R570)) denominator = pri531 + pri570 # Avoid dividing by zero index_array_raw = np.where(denominator == 0, 0, ((pri531 - pri570) / denominator)) else: fatal_error( "Available wavelengths are not suitable for calculating PRI. Try increasing distance." ) elif index.upper() == "ACI": # Van den Berg et al. 2005 if (max_wavelength + distance) >= 800 and (min_wavelength - distance) <= 560: green_index = _find_closest( np.array([float(i) for i in wavelength_dict.keys()]), 560) nir_index = _find_closest( np.array([float(i) for i in wavelength_dict.keys()]), 800) green = (array_data[:, :, [green_index]]) nir = (array_data[:, :, [nir_index]]) # Naturally ranges from -1.0 to 1.0 index_array_raw = green / nir else: fatal_error( "Available wavelengths are not suitable for calculating ACI. Try increasing distance." ) elif index.upper() == "ARI": # Gitelson et al., 2001 if (max_wavelength + distance) >= 700 and (min_wavelength - distance) <= 550: ari550_indes = _find_closest( np.array([float(i) for i in wavelength_dict.keys()]), 550) ari700_index = _find_closest( np.array([float(i) for i in wavelength_dict.keys()]), 700) ari550 = (array_data[:, :, [ari550_indes]]) ari700 = (array_data[:, :, [ari700_index]]) index_array_raw = (1 / ari550) - (1 / ari700) else: fatal_error( "Available wavelengths are not suitable for calculating ARI. Try increasing distance." ) elif index.upper() == 'CARI': # Chlorophyll absorption in reflectance index (Giteson et al., 2003a) if (max_wavelength + distance) >= 700 and (min_wavelength - distance) <= 550: cari550_index = _find_closest( np.array([float(i) for i in wavelength_dict.keys()]), 550) cari700_index = _find_closest( np.array([float(i) for i in wavelength_dict.keys()]), 700) cari550 = (array_data[:, :, [cari550_index]]) cari700 = (array_data[:, :, [cari700_index]]) # Naturally ranges from -inf to inf index_array_raw = (1 / cari550) - (1 / cari700) else: fatal_error( "Available wavelengths are not suitable for calculating CARI. Try increasing distance." ) elif index.upper() == 'CI_REDEDGE': # Chlorophyll index red edge (Giteson et al., 2003a) if (max_wavelength + distance) >= 800 and (min_wavelength - distance) <= 750: rededge_index = _find_closest( np.array([float(i) for i in wavelength_dict.keys()]), 750) nir_index = _find_closest( np.array([float(i) for i in wavelength_dict.keys()]), 800) rededge = (array_data[:, :, [rededge_index]]) nir = (array_data[:, :, [nir_index]]) # Naturally ranges from -1 to inf index_array_raw = nir / rededge - 1 else: fatal_error( "Available wavelengths are not suitable for calculating CI_rededge. Try increasing distance." ) elif index.upper() == 'CRI1': # Carotenoid reflectance index (Gitelson et al., 2002b) (note: part 1 of 2) if (max_wavelength + distance) >= 550 and (min_wavelength - distance) <= 510: cri1510_index = _find_closest( np.array([float(i) for i in wavelength_dict.keys()]), 510) cri1550_index = _find_closest( np.array([float(i) for i in wavelength_dict.keys()]), 550) cri1510 = (array_data[:, :, [cri1510_index]]) cri1550 = (array_data[:, :, [cri1550_index]]) # Naturally ranges from -inf to inf index_array_raw = 1 / cri1510 - 1 / cri1550 else: fatal_error( "Available wavelengths are not suitable for calculating CRI1. Try increasing distance." ) elif index.upper() == 'CRI2': # Carotenoid reflectance index (Gitelson et al., 2002b) (note: part 1 of 2) if (max_wavelength + distance) >= 700 and (min_wavelength - distance) <= 510: cri1510_index = _find_closest( np.array([float(i) for i in wavelength_dict.keys()]), 510) cri1700_index = _find_closest( np.array([float(i) for i in wavelength_dict.keys()]), 700) cri1510 = (array_data[:, :, [cri1510_index]]) cri1700 = (array_data[:, :, [cri1700_index]]) # Naturally ranges from -inf to inf index_array_raw = 1 / cri1510 - 1 / cri1700 else: fatal_error( "Available wavelengths are not suitable for calculating CRI2. Try increasing distance." ) elif index.upper() == 'EVI': # Enhanced Vegetation index (Huete et al., 1997) if (max_wavelength + distance) >= 800 and (min_wavelength - distance) <= 470: blue_index = _find_closest( np.array([float(i) for i in wavelength_dict.keys()]), 470) red_index = _find_closest( np.array([float(i) for i in wavelength_dict.keys()]), 670) nir_index = _find_closest( np.array([float(i) for i in wavelength_dict.keys()]), 800) blue = (array_data[:, :, [blue_index]]) red = (array_data[:, :, [red_index]]) nir = (array_data[:, :, [nir_index]]) # Naturally ranges from -inf to inf index_array_raw = 2.5 * (nir - red) / (nir + 6 * red - 7.5 * blue + 1) else: fatal_error( "Available wavelengths are not suitable for calculating EVI. Try increasing distance." ) elif index.upper() == 'MARI': # Modified anthocyanin reflectance index (Gitelson et al., 2001) if (max_wavelength + distance) >= 800 and (min_wavelength - distance) <= 550: mari550_index = _find_closest( np.array([float(i) for i in wavelength_dict.keys()]), 550) mari700_index = _find_closest( np.array([float(i) for i in wavelength_dict.keys()]), 700) nir_index = _find_closest( np.array([float(i) for i in wavelength_dict.keys()]), 800) mari550 = (array_data[:, :, [mari550_index]]) mari700 = (array_data[:, :, [mari700_index]]) nir = (array_data[:, :, [nir_index]]) # Naturally ranges from -inf to inf index_array_raw = ((1 / mari550) - (1 / mari700)) * nir else: fatal_error( "Available wavelengths are not suitable for calculating MARI. Try increasing distance." ) elif index.upper() == 'MCARI': # Modified chlorophyll absorption in reflectance index (Daughtry et al., 2000) if (max_wavelength + distance) >= 700 and (min_wavelength - distance) <= 550: mcari550_index = _find_closest( np.array([float(i) for i in wavelength_dict.keys()]), 550) mcari670_index = _find_closest( np.array([float(i) for i in wavelength_dict.keys()]), 670) mcari700_index = _find_closest( np.array([float(i) for i in wavelength_dict.keys()]), 700) mcari550 = (array_data[:, :, [mcari550_index]]) mcari670 = (array_data[:, :, [mcari670_index]]) mcari700 = (array_data[:, :, [mcari700_index]]) # Naturally ranges from -inf to inf index_array_raw = ((mcari700 - mcari670) - 0.2 * (mcari700 - mcari550)) * (mcari700 / mcari670) else: fatal_error( "Available wavelengths are not suitable for calculating MCARI. Try increasing distance." ) elif index.upper() == 'MTCI': # MERIS terrestrial chlorophyll index (Dash and Curran, 2004) if (max_wavelength + distance) >= 753.75 and (min_wavelength - distance) <= 681.25: mtci68125_index = _find_closest( np.array([float(i) for i in wavelength_dict.keys()]), 681.25) mtci70875_index = _find_closest( np.array([float(i) for i in wavelength_dict.keys()]), 708.75) mtci75375_index = _find_closest( np.array([float(i) for i in wavelength_dict.keys()]), 753.75) mtci68125 = (array_data[:, :, [mtci68125_index]]) mtci70875 = (array_data[:, :, [mtci70875_index]]) mtci75375 = (array_data[:, :, [mtci75375_index]]) # Naturally ranges from -inf to inf index_array_raw = (mtci75375 - mtci70875) / (mtci70875 - mtci68125) else: fatal_error( "Available wavelengths are not suitable for calculating MTCI. Try increasing distance." ) elif index.upper() == 'NDRE': # Normalized difference red edge (Barnes et al., 2000) if (max_wavelength + distance) >= 790 and (min_wavelength - distance) <= 720: ndre720_index = _find_closest( np.array([float(i) for i in wavelength_dict.keys()]), 720) ndre790_index = _find_closest( np.array([float(i) for i in wavelength_dict.keys()]), 790) ndre790 = (array_data[:, :, [ndre790_index]]) ndre720 = (array_data[:, :, [ndre720_index]]) # Naturally ranges from -1 to 1 index_array_raw = (ndre790 - ndre720) / (ndre790 + ndre720) else: fatal_error( "Available wavelengths are not suitable for calculating NDRE. Try increasing distance." ) elif index.upper() == 'PSND_CHLA': # Pigment sensitive normalized difference (Blackburn, 1998) note: chl_a if (max_wavelength + distance) >= 800 and (min_wavelength - distance) <= 675: psndchla675_index = _find_closest( np.array([float(i) for i in wavelength_dict.keys()]), 675) psndchla800_index = _find_closest( np.array([float(i) for i in wavelength_dict.keys()]), 800) psndchla675 = (array_data[:, :, [psndchla675_index]]) psndchla800 = (array_data[:, :, [psndchla800_index]]) # Naturally ranges from -1 to 1 index_array_raw = (psndchla800 - psndchla675) / (psndchla800 + psndchla675) else: fatal_error( "Available wavelengths are not suitable for calculating PSND_CHLA. Try increasing distance." ) elif index.upper() == 'PSND_CHLB': # Pigment sensitive normalized difference (Blackburn, 1998) note: chl_b (part 1 of 3) if (max_wavelength + distance) >= 800 and (min_wavelength - distance) <= 650: psndchlb650_index = _find_closest( np.array([float(i) for i in wavelength_dict.keys()]), 650) psndchlb800_index = _find_closest( np.array([float(i) for i in wavelength_dict.keys()]), 800) psndchlb650 = (array_data[:, :, [psndchlb650_index]]) psndchlb800 = (array_data[:, :, [psndchlb800_index]]) # Naturally ranges from -1 to 1 index_array_raw = (psndchlb800 - psndchlb650) / (psndchlb800 + psndchlb650) else: fatal_error( "Available wavelengths are not suitable for calculating PSND_CHLB. Try increasing distance." ) elif index.upper() == 'PSND_CAR': # Pigment sensitive normalized difference (Blackburn, 1998) note: chl_b (part 1 of 3) if (max_wavelength + distance) >= 800 and (min_wavelength - distance) <= 500: psndcar500_index = _find_closest( np.array([float(i) for i in wavelength_dict.keys()]), 500) psndcar800_index = _find_closest( np.array([float(i) for i in wavelength_dict.keys()]), 800) psndcar500 = (array_data[:, :, [psndcar500_index]]) psndcar800 = (array_data[:, :, [psndcar800_index]]) # Naturally ranges from -1 to 1 index_array_raw = (psndcar800 - psndcar500) / (psndcar800 + psndcar500) else: fatal_error( "Available wavelengths are not suitable for calculating PSND_CAR. Try increasing distance." ) elif index.upper() == 'PSRI': # Plant senescence reflectance index (Merzlyak et al., 1999) note: car (part 1 of 3) if (max_wavelength + distance) >= 750 and (min_wavelength - distance) <= 500: psri500_index = _find_closest( np.array([float(i) for i in wavelength_dict.keys()]), 500) psri678_index = _find_closest( np.array([float(i) for i in wavelength_dict.keys()]), 678) psri750_index = _find_closest( np.array([float(i) for i in wavelength_dict.keys()]), 750) psri500 = (array_data[:, :, [psri500_index]]) psri678 = (array_data[:, :, [psri678_index]]) psri750 = (array_data[:, :, [psri750_index]]) # Naturally ranges from -inf to inf index_array_raw = (psri678 - psri500) / psri750 else: fatal_error( "Available wavelengths are not suitable for calculating PSRI. Try increasing distance." ) elif index.upper() == 'PSSR1': # Pigment-specific spectral ration (Blackburn, 1998) note: part 1 of 3 if (max_wavelength + distance) >= 800 and (min_wavelength - distance) <= 675: pssr1_800_index = _find_closest( np.array([float(i) for i in wavelength_dict.keys()]), 800) pssr1_675_index = _find_closest( np.array([float(i) for i in wavelength_dict.keys()]), 675) pssr1_800 = (array_data[:, :, [pssr1_800_index]]) pssr1_675 = (array_data[:, :, [pssr1_675_index]]) # Naturally ranges from 0 to inf index_array_raw = pssr1_800 / pssr1_675 else: fatal_error( "Available wavelengths are not suitable for calculating PSSR1. Try increasing distance." ) elif index.upper() == 'PSSR2': # Pigment-specific spectral ration (Blackburn, 1998) note: part 2 of 3 if (max_wavelength + distance) >= 800 and (min_wavelength - distance) <= 650: pssr2_800_index = _find_closest( np.array([float(i) for i in wavelength_dict.keys()]), 800) pssr2_650_index = _find_closest( np.array([float(i) for i in wavelength_dict.keys()]), 650) pssr2_800 = (array_data[:, :, [pssr2_800_index]]) pssr2_650 = (array_data[:, :, [pssr2_650_index]]) # Naturally ranges from 0 to inf index_array_raw = pssr2_800 / pssr2_650 else: fatal_error( "Available wavelengths are not suitable for calculating PSSR2. Try increasing distance." ) elif index.upper() == 'PSSR3': # Pigment-specific spectral ration (Blackburn, 1998) note: part 3 of 3 if (max_wavelength + distance) >= 800 and (min_wavelength - distance) <= 500: pssr3_800_index = _find_closest( np.array([float(i) for i in wavelength_dict.keys()]), 800) pssr3_500_index = _find_closest( np.array([float(i) for i in wavelength_dict.keys()]), 500) pssr3_800 = (array_data[:, :, [pssr3_800_index]]) pssr3_500 = (array_data[:, :, [pssr3_500_index]]) # Naturally ranges from 0 to inf index_array_raw = pssr3_800 / pssr3_500 else: fatal_error( "Available wavelengths are not suitable for calculating PSSR3. Try increasing distance." ) elif index.upper() == 'RGRI': # Red/green ratio index (Gamon and Surfus, 1999) if (max_wavelength + distance) >= 670 and (min_wavelength - distance) <= 560: red_index = _find_closest( np.array([float(i) for i in wavelength_dict.keys()]), 670) green_index = _find_closest( np.array([float(i) for i in wavelength_dict.keys()]), 560) red = (array_data[:, :, [red_index]]) green = (array_data[:, :, [green_index]]) # Naturally ranges from 0 to inf index_array_raw = red / green else: fatal_error( "Available wavelengths are not suitable for calculating RGRI. Try increasing distance." ) elif index.upper() == 'RVSI': # Red-edge vegetation stress index (Metron and Huntington, 1999) if (max_wavelength + distance) >= 752 and (min_wavelength - distance) <= 714: rvsi714_index = _find_closest( np.array([float(i) for i in wavelength_dict.keys()]), 714) rvsi733_index = _find_closest( np.array([float(i) for i in wavelength_dict.keys()]), 733) rvsi752_index = _find_closest( np.array([float(i) for i in wavelength_dict.keys()]), 752) rvsi714 = (array_data[:, :, [rvsi714_index]]) rvsi733 = (array_data[:, :, [rvsi733_index]]) rvsi752 = (array_data[:, :, [rvsi752_index]]) # Naturally ranges from -1 to 1 index_array_raw = (rvsi714 + rvsi752) / 2 - rvsi733 else: fatal_error( "Available wavelengths are not suitable for calculating RVSI. Try increasing distance." ) elif index.upper() == 'SIPI': # Structure-intensitive pigment index (Penuelas et al., 1995) if (max_wavelength + distance) >= 800 and (min_wavelength - distance) <= 445: sipi445_index = _find_closest( np.array([float(i) for i in wavelength_dict.keys()]), 445) sipi680_index = _find_closest( np.array([float(i) for i in wavelength_dict.keys()]), 680) sipi800_index = _find_closest( np.array([float(i) for i in wavelength_dict.keys()]), 800) sipi445 = (array_data[:, :, [sipi445_index]]) sipi680 = (array_data[:, :, [sipi680_index]]) sipi800 = (array_data[:, :, [sipi800_index]]) # Naturally ranges from -inf to inf index_array_raw = (sipi800 - sipi445) / (sipi800 - sipi680) else: fatal_error( "Available wavelengths are not suitable for calculating SIPI. Try increasing distance." ) elif index.upper() == 'SR': # Simple ratio (Jordan, 1969) if (max_wavelength + distance) >= 800 and (min_wavelength - distance) <= 675: sr675_index = _find_closest( np.array([float(i) for i in wavelength_dict.keys()]), 675) sr800_index = _find_closest( np.array([float(i) for i in wavelength_dict.keys()]), 800) sr675 = (array_data[:, :, [sr675_index]]) sr800 = (array_data[:, :, [sr800_index]]) # Naturally ranges from 0 to inf index_array_raw = sr800 / sr675 else: fatal_error( "Available wavelengths are not suitable for calculating SR. Try increasing distance." ) elif index.upper() == 'VARI': # Visible atmospherically resistant index (Gitelson et al., 2002a) if (max_wavelength + distance) >= 670 and (min_wavelength - distance) <= 470: red_index = _find_closest( np.array([float(i) for i in wavelength_dict.keys()]), 670) green_index = _find_closest( np.array([float(i) for i in wavelength_dict.keys()]), 560) blue_index = _find_closest( np.array([float(i) for i in wavelength_dict.keys()]), 470) red = (array_data[:, :, [red_index]]) green = (array_data[:, :, [green_index]]) blue = (array_data[:, :, [blue_index]]) # Naturally ranges from -inf to inf index_array_raw = (green - red) / (green + red - blue) else: fatal_error( "Available wavelengths are not suitable for calculating VARI. Try increasing distance." ) elif index.upper() == 'VI_GREEN': # Vegetation index using green band (Gitelson et al., 2002a) if (max_wavelength + distance) >= 670 and (min_wavelength - distance) <= 560: red_index = _find_closest( np.array([float(i) for i in wavelength_dict.keys()]), 670) green_index = _find_closest( np.array([float(i) for i in wavelength_dict.keys()]), 560) red = (array_data[:, :, [red_index]]) green = (array_data[:, :, [green_index]]) # Naturally ranges from -1 to 1 index_array_raw = (green - red) / (green + red) else: fatal_error( "Available wavelengths are not suitable for calculating VI_green. Try increasing distance." ) elif index.upper() == 'WBI': # Water band index (Peñuelas et al., 1997) if (max_wavelength + distance) >= 800 and (min_wavelength - distance) <= 675: red_index = _find_closest( np.array([float(i) for i in wavelength_dict.keys()]), 670) green_index = _find_closest( np.array([float(i) for i in wavelength_dict.keys()]), 560) red = (array_data[:, :, [red_index]]) green = (array_data[:, :, [green_index]]) # Naturally ranges from -1 to 1 index_array_raw = (green - red) / (green + red) else: fatal_error( "Available wavelengths are not suitable for calculating VI_green. Try increasing distance." ) else: fatal_error( index + " is not one of the currently available indices for this function. Please open an issue " + "on the PlantCV GitHub account so we can add more handy indicies!") # Reshape array into hyperspectral datacube shape index_array_raw = np.transpose(np.transpose(index_array_raw)[0]) # Store debug mode debug = params.debug params.debug = None # Resulting array is float 32 from varying natural ranges, transform into uint8 for plotting all_positive = np.add(index_array_raw, 2 * np.ones(np.shape(index_array_raw))) scaled = rescale(all_positive) # Find array min and max values obs_max_pixel = float(np.nanmax(index_array_raw)) obs_min_pixel = float(np.nanmin(index_array_raw)) index_array = Spectral_data(array_data=index_array_raw, max_wavelength=0, min_wavelength=0, max_value=obs_max_pixel, min_value=obs_min_pixel, d_type=np.uint8, wavelength_dict={}, samples=array.samples, lines=array.lines, interleave=array.interleave, wavelength_units=array.wavelength_units, array_type="index_" + index.lower(), pseudo_rgb=scaled, filename=array.filename, default_bands=None) # Restore debug mode params.debug = debug if params.debug == "plot": plot_image(index_array.pseudo_rgb) elif params.debug == "print": print_image( index_array.pseudo_rgb, os.path.join(params.debug_outdir, str(params.device) + index + "_index.png")) return index_array
def read_data(filename): """Read hyperspectral image data from file. Inputs: filename = Name of image file Returns: spectral_array = Hyperspectral data instance :param filename: str :return spectral_array: __main__.Spectral_data """ # Initialize dictionary header_dict = {} headername = filename + ".hdr" with open(headername, "r") as f: # Replace characters for easier parsing hdata = f.read() hdata = hdata.replace(",\n", ",") hdata = hdata.replace("\n,", ",") hdata = hdata.replace("{\n", "{") hdata = hdata.replace("\n}", "}") hdata = hdata.replace(" \n ", "") hdata = hdata.replace(";", "") hdata = hdata.split("\n") # Loop through and create a dictionary from the header file for i, string in enumerate(hdata): if ' = ' in string: header_data = string.split(" = ") header_dict.update( {header_data[0].rstrip(): header_data[1].rstrip()}) elif ' : ' in string: header_data = string.split(" : ") header_dict.update( {header_data[0].rstrip(): header_data[1].rstrip()}) # Reformat wavelengths header_dict["wavelength"] = header_dict["wavelength"].replace("{", "") header_dict["wavelength"] = header_dict["wavelength"].replace("}", "") header_dict["wavelength"] = header_dict["wavelength"].replace(" ", "") header_dict["wavelength"] = header_dict["wavelength"].split(",") # Create dictionary of wavelengths wavelength_dict = {} for j, wavelength in enumerate(header_dict["wavelength"]): wavelength_dict.update({float(wavelength): float(j)}) # Replace datatype ID number with the numpy datatype dtype_dict = { "1": np.uint8, "2": np.int16, "3": np.int32, "4": np.float32, "5": np.float64, "6": np.complex64, "9": np.complex128, "12": np.uint16, "13": np.uint32, "14": np.uint64, "15": np.uint64 } header_dict["data type"] = dtype_dict[header_dict["data type"]] # Read in the data from the file raw_data = np.fromfile(filename, header_dict["data type"], -1) # Reshape the raw data into a datacube array array_data = raw_data.reshape(int(header_dict["lines"]), int(header_dict["bands"]), int(header_dict["samples"])).transpose( (0, 2, 1)) # Check for default bands (that get used to make pseudo_rgb image) default_bands = None if "default bands" in header_dict: header_dict["default bands"] = header_dict["default bands"].replace( "{", "") header_dict["default bands"] = header_dict["default bands"].replace( "}", "") default_bands = header_dict["default bands"].split(",") # Find array min and max values max_pixel = float(np.amax(array_data)) min_pixel = float(np.amin(array_data)) # Create an instance of the spectral_data class spectral_array = Spectral_data( array_data=array_data, max_wavelength=float(str(header_dict["wavelength"][-1]).rstrip()), min_wavelength=float(str(header_dict["wavelength"][0]).rstrip()), max_value=max_pixel, min_value=min_pixel, d_type=header_dict["data type"], wavelength_dict=wavelength_dict, samples=int(header_dict["samples"]), lines=int(header_dict["lines"]), interleave=header_dict["interleave"], wavelength_units=header_dict["wavelength units"], array_type="datacube", pseudo_rgb=None, filename=filename, default_bands=default_bands) # Make pseudo-rgb image and replace it inside the class instance object pseudo_rgb = _make_pseudo_rgb(spectral_array) spectral_array.pseudo_rgb = pseudo_rgb if params.debug == "plot": plot_image(pseudo_rgb) elif params.debug == "print": print_image( pseudo_rgb, os.path.join(params.debug_outdir, str(params.device) + "_pseudo_rgb.png")) return spectral_array