├── .gitignore ├── LICENSE ├── MANIFEST.in ├── README.md ├── demos ├── BatchPlotSpectra │ ├── __init__.py │ ├── an_hsi_image_sub_for_demo.mat │ ├── batchplotspectra.py │ └── batchplotspectra_demo.py ├── DEMO_spectral_indices.py ├── __init__.py ├── a_hsi_img_for_VI_demo.mat ├── an_hsi_image_sub_for_demo.mat ├── an_hsi_img_for_class_demo.mat ├── an_hsi_img_for_tgt_det_demo.mat ├── demoVCA.py ├── demo_anomaly_detector.py ├── demo_classification.py ├── demo_dim_reduction.py ├── demo_signature_det.py └── spectral_indicies_demo_results │ ├── ACI.png │ ├── ARI.png │ ├── ARVI.png │ ├── CAI.png │ ├── CARI.png │ ├── CIrededge.png │ ├── CRI1.png │ ├── CRI2.png │ ├── EVI.png │ ├── MARI.png │ ├── MCARI.png │ ├── MTCI.png │ ├── NDII.png │ ├── NDLI.png │ ├── NDNI.png │ ├── NDRE.png │ ├── NDVI.png │ ├── NDWI.png │ ├── PRI.png │ ├── PSND Car.png │ ├── PSND ChlA.png │ ├── PSND ChlB.png │ ├── PSRI.png │ ├── PSSR1.png │ ├── PSSR2.png │ ├── PSSR3.png │ ├── REP.png │ ├── RVSI.png │ ├── SIPI.png │ ├── SR.png │ ├── VARI.png │ ├── VIgreen.png │ ├── WBI.png │ └── WDVI.png ├── hsi_toolkit ├── __init__.py ├── anomaly_detectors │ ├── README.md │ ├── __init__.py │ ├── cbad_anomaly.py │ ├── csd_anomaly.py │ ├── gmm_anomaly.py │ ├── md_anomaly.py │ └── rx_anomaly.py ├── classifiers │ ├── README.md │ ├── __init__.py │ ├── fuzzy_knn_classifier.py │ ├── knn_classifier.py │ └── poss_knn_classifier.py ├── dev │ ├── __init__.py │ ├── anomaly_detectors │ │ ├── __init__.py │ │ ├── beta_anomaly.py │ │ ├── fcbad_anomaly.py │ │ ├── gmrx_anomaly.py │ │ └── ssrx_anomaly.py │ ├── dim_reduction │ │ ├── __init__.py │ │ └── mnf.py │ └── signature_detectors │ │ ├── __init__.py │ │ ├── ftmf_detector.py │ │ ├── mtmf_statistic.py │ │ ├── qmf_detector.py │ │ └── spsmf_detector.py ├── dim_reduction │ ├── README.md │ ├── __init__.py │ └── hdr.py ├── endmember_extraction │ ├── VCA.py │ └── __init__.py ├── signature_detectors │ ├── README.md │ ├── __init__.py │ ├── abd_detector.py │ ├── ace_detector.py │ ├── ace_local_detector.py │ ├── ace_rt_detector.py │ ├── ace_rt_max_detector.py │ ├── ace_ss_detector.py │ ├── amsd_detector.py │ ├── ccmf_detector.py │ ├── cem_detector.py │ ├── ctmf_detector.py │ ├── fam_statistic.py │ ├── ha_detector.py │ ├── hsd_detector.py │ ├── hsd_local_detector.py │ ├── hua_detector.py │ ├── osp_detector.py │ ├── palm_detector.py │ ├── sam_detector.py │ ├── smf_detector.py │ ├── smf_local_detector.py │ └── smf_max_detector.py ├── spectral_indices │ ├── README.md │ ├── __init__.py │ └── utilities_VI.py └── util │ ├── README.md │ ├── __init__.py │ ├── get_RGB.py │ ├── get_hsi_bands.py │ ├── hsi_gui.py │ ├── hsi_gui_class.py │ ├── hsi_gui_mask.py │ ├── img_det.py │ ├── img_seg.py │ ├── pca.py │ ├── rx_det.py │ └── unmix.py ├── requirements.txt ├── setup.cfg └── setup.py /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | # C extensions 6 | *.so 7 | # Distribution / packaging 8 | .Python 9 | build/ 10 | develop-eggs/ 11 | dist/ 12 | downloads/ 13 | eggs/ 14 | .eggs/ 15 | lib/ 16 | lib64/ 17 | parts/ 18 | sdist/ 19 | var/ 20 | wheels/ 21 | *.egg-info/ 22 | .installed.cfg 23 | *.egg 24 | MANIFEST 25 | .DS_Store 26 | # PyInstaller 27 | # Usually these files are written by a python script from a template 28 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 29 | *.manifest 30 | *.spec 31 | # Installer logs 32 | pip-log.txt 33 | pip-delete-this-directory.txt 34 | # Unit test / coverage reports 35 | htmlcov/ 36 | .tox/ 37 | .coverage 38 | .coverage.* 39 | .cache 40 | nosetests.xml 41 | coverage.xml 42 | *.cover 43 | .hypothesis/ 44 | .pytest_cache/ 45 | # Translations 46 | *.mo 47 | *.pot 48 | # Django stuff: 49 | *.log 50 | local_settings.py 51 | db.sqlite3 52 | # Flask stuff: 53 | instance/ 54 | .webassets-cache 55 | # Scrapy stuff: 56 | .scrapy 57 | # Sphinx documentation 58 | docs/_build/ 59 | # PyBuilder 60 | target/ 61 | # Jupyter Notebook 62 | .ipynb_checkpoints 63 | # pyenv 64 | .python-version 65 | # celery beat schedule file 66 | celerybeat-schedule 67 | # SageMath parsed files 68 | *.sage.py 69 | # Environments 70 | .env 71 | .venv 72 | env/ 73 | venv/ 74 | ENV/ 75 | env.bak/ 76 | venv.bak/ 77 | # Spyder project settings 78 | .spyderproject 79 | .spyproject 80 | # Rope project settings 81 | .ropeproject 82 | # mkdocs documentation 83 | /site 84 | 85 | # mypy 86 | .mypy_cache/ 87 | 88 | .idea/* 89 | .idea_modules/ -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 GatorSense 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include requirements.txt 2 | include LICENSE 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # GatorSense Hyperspectral Image Analysis Toolkit - Python Implementation 2 | 3 | Cite this code when using it: [![DOI](https://zenodo.org/badge/DOI/10.5281/zenodo.2638117.svg)](https://doi.org/10.5281/zenodo.2638117) 4 | 5 | Alina Zare, Susan Meerdink, Yutai Zhou, Caleb Robey, Ron Fick, John Henning, & Paul Gader. (2019, April 12). GatorSense/hsi_toolkit_py: Initial Release (Version v1.0). Zenodo. http://doi.org/10.5281/zenodo.2638117 6 | 7 | ## Contents: 8 | This repo contains algorithms for the following cateogories. To find out more please see readme documents inside each folder. 9 | * Anomaly Detectors 10 | * Classifiers 11 | * Dimensionality Reduction 12 | * Endmember Extraction 13 | * Signature Detectors 14 | * Spectral Indices 15 | 16 | ## Inputs: 17 | Each folder contains a hyperspectral image to run the demos. The hyperspectral image format for our functions is height x width x bands. 18 | 19 | ## Questions? 20 | Contact: Dr. Alina Zare, azare@ufl.edu 21 | -------------------------------------------------------------------------------- /demos/BatchPlotSpectra/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GatorSense/hsi_toolkit_py/70fd8fb8db164f1b84d7324ffa690b7451938b6f/demos/BatchPlotSpectra/__init__.py -------------------------------------------------------------------------------- /demos/BatchPlotSpectra/an_hsi_image_sub_for_demo.mat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GatorSense/hsi_toolkit_py/70fd8fb8db164f1b84d7324ffa690b7451938b6f/demos/BatchPlotSpectra/an_hsi_image_sub_for_demo.mat -------------------------------------------------------------------------------- /demos/BatchPlotSpectra/batchplotspectra.py: -------------------------------------------------------------------------------- 1 | #function [SpecDist] = PlotSpectraDistribution(Spectra, WaveLengths,SampStuff, FigNum); 2 | ### 3 | ###################################################################### 4 | ### A FUNCTION THAT CREATES & DISPLAYS SPECTRA AS A 2D HISTOGRAM ### 5 | ### SPECTRA ARE ASSUMED REFLECTANCES OR EMISSIVITIES IN [0,1] ### 6 | ### SPECTRA ARE MAPPED TO INTEGERS BETWEEN 0 AND 100 (OR < 100) ### 7 | ###################################################################### 8 | ### 9 | ### INPUTS: 10 | ### I1. Spectra IS A NUMBER SPECTRA x NUMBER BANDS... ### 11 | ### ...ARRAY OF REFLECTANCES OR EMISSIVITIES ### 12 | ### I2. WaveLengths IS A VECTOR OF THE SPECTRAL WAVELENGTHS ### 13 | ### I3. SampStuff IS A VECTOR CONTAINING ### 14 | ### SampInt: FRACTIONAL SIZE OF HISTOGRAM BINS ### 15 | ### IntSampInt: INT VERSION OF SampInt ### 16 | ### IntTopReflect: INT VALUE OF MAX REF/EMIS BIN ### 17 | ### I4. FigNum IS THE INDEX OF THE FIGURE TO USE FOR DISPLAY ### 18 | ### IF FigNum < 1, DO NOT DISPLAY ANYTHING ### 19 | ### 20 | ### OUTPUTS: 21 | ### O1. SpecDist IS THE 2D HISTOGRAM ### 22 | ################################################################## 23 | ### ### 24 | ### MATLAB AUTHOR: Darth Gader ### 25 | ### PYTHON AUTHOR: Ron Fick ### 26 | ### LAST UPDATE: 021519 ### 27 | ### ### 28 | ################################################################## 29 | def PlotSpectraDistribution(Spectra, WaveLengths, SampStuff, FigNum): 30 | 31 | import numpy as np 32 | from scipy import signal 33 | import cv2 34 | import matplotlib.pyplot as plt 35 | from mpl_toolkits.mplot3d import Axes3D 36 | from matplotlib import cm 37 | 38 | ## 39 | ### INITIALIZE PARAMETERS ### 40 | 41 | SampInt = SampStuff[0] 42 | IntSampInt = SampStuff[1] 43 | IntTopReflect = SampStuff[2] 44 | SMOOTHSIZE = [3,3] 45 | NumWave = np.size(Spectra, 1) 46 | SpecDist = np.zeros((IntTopReflect, NumWave)) 47 | assert NumWave == np.size(WaveLengths), 'Wavelength sizes don''t match' 48 | 49 | ### MAP SPECTRA TO [0, 100] ### 50 | MappedSpectra = np.minimum(100, (Spectra*99)+1) 51 | MappedSpectra = np.maximum(1, np.round(MappedSpectra/SampInt)*SampInt) 52 | 53 | ## 54 | ### MAKE A HISTOGRAM FOR EACH WAVELENGTH ### 55 | for k in range(NumWave): 56 | SpecDist[:, k] = np.histogram(MappedSpectra[:, k], np.arange(0, IntTopReflect+IntSampInt, IntSampInt))[0] 57 | 58 | ### SMOOTH BY TAKING A LOCAL MAX FOLLOWED BY A LOCAL AVERAGE ### 59 | SpecDist = cv2.dilate(SpecDist, np.ones((3,3)), iterations=1) 60 | SpecDist = signal.convolve2d(SpecDist, (1/np.prod(SMOOTHSIZE))*np.ones(SMOOTHSIZE), 'same') 61 | 62 | ## 63 | ### DISPLAY AS MESH ### 64 | if(FigNum > 0): 65 | XAxis = WaveLengths; 66 | YAxis = np.arange(0, IntTopReflect, IntSampInt).T 67 | X, Y = np.meshgrid(XAxis, YAxis) 68 | fig = plt.figure(FigNum) 69 | ax = fig.gca(projection='3d') 70 | surf = ax.plot_surface(X, Y, SpecDist, cmap=cm.coolwarm, linewidth=0, antialiased=False) 71 | fig.colorbar(surf, shrink=0.5, aspect=5) 72 | plt.title('Spectra Histogram') 73 | plt.xlabel('Wavelength (nm)') 74 | plt.ylabel('Reflectance') 75 | plt.show() 76 | 77 | ### END OF FUNCTION ### 78 | ####################### -------------------------------------------------------------------------------- /demos/BatchPlotSpectra/batchplotspectra_demo.py: -------------------------------------------------------------------------------- 1 | import scipy.io as sio 2 | import numpy as np 3 | from batchplotspectra import PlotSpectraDistribution 4 | 5 | an_hsi_image_sub_for_demo = sio.loadmat('an_hsi_image_sub_for_demo.mat') 6 | hsi_img_sub = an_hsi_image_sub_for_demo['hsi_img_sub'] 7 | wavelengths = an_hsi_image_sub_for_demo['wavelengths'] 8 | mask_sub = an_hsi_image_sub_for_demo['mask_sub'] 9 | 10 | x_dims, y_dims, band_dims = hsi_img_sub.shape 11 | 12 | mat_data = np.reshape(hsi_img_sub, (x_dims*y_dims, band_dims)) 13 | 14 | mask_reshaped = np.reshape(mask_sub, (x_dims*y_dims)) 15 | 16 | masked_data = mat_data[mask_reshaped == 1] 17 | 18 | PlotSpectraDistribution(masked_data, wavelengths, [1,1,100], 1) -------------------------------------------------------------------------------- /demos/DEMO_spectral_indices.py: -------------------------------------------------------------------------------- 1 | from scipy.io import loadmat 2 | import matplotlib.pyplot as plt 3 | from hsi_toolkit.util import get_RGB 4 | from hsi_toolkit.spectral_indices import * 5 | import os 6 | 7 | """ 8 | Demo script that runs all spectral indices in hsi_toolkit_py and visualizes the results. 9 | 10 | Inputs: 11 | hsi_sub - n_row x n_col x n_band hyperspectral image 12 | wavelengths - n_band x 1 vector listing wavelength values for hsi_sub in nm 13 | mask - binary image limiting detector operation to pixels where mask is true 14 | if not present or empty, no mask restrictions are used 15 | Outputs: 16 | det_out - dictionary of RGB image and spectral indices outputs 17 | 18 | 04/2020 - Susan Meerdink 19 | """ 20 | 21 | # Load data 22 | an_hsi_img_for_VI_demo = loadmat('a_hsi_img_for_VI_demo.mat') 23 | hsi_sub = an_hsi_img_for_VI_demo['hsi_img'] 24 | wavelengths = an_hsi_img_for_VI_demo['wavelengths'] 25 | 26 | # Set up True Color or Red/Green/Blue Image 27 | det_out = {} 28 | det_out['RGB'] = get_RGB(hsi_sub, wavelengths) 29 | 30 | # Call Spectral Indices 31 | det_out['ACI'] = aci_vi(hsi_sub, wavelengths) 32 | det_out['ARI'] = ari_vi(hsi_sub, wavelengths) 33 | det_out['ARVI'] = arvi_vi(hsi_sub, wavelengths) 34 | det_out['CAI'] = cai_vi(hsi_sub, wavelengths) 35 | det_out['CARI'] = cari_vi(hsi_sub, wavelengths) 36 | det_out['CIrededge'] = cirededge_vi(hsi_sub, wavelengths) 37 | det_out['CRI1'] = cri1_vi(hsi_sub, wavelengths) 38 | det_out['CRI2'] = cri2_vi(hsi_sub, wavelengths) 39 | det_out['EVI'] = evi_vi(hsi_sub, wavelengths) 40 | det_out['MARI'] = mari_vi(hsi_sub, wavelengths) 41 | det_out['MCARI'] = mcari_vi(hsi_sub, wavelengths) 42 | det_out['MTCI'] = mtci_vi(hsi_sub, wavelengths) 43 | det_out['NDII'] = ndii_vi(hsi_sub, wavelengths) 44 | det_out['NDLI'] = ndli_vi(hsi_sub, wavelengths) 45 | det_out['NDNI'] = ndni_vi(hsi_sub, wavelengths) 46 | det_out['NDRE'] = ndre_vi(hsi_sub, wavelengths) 47 | det_out['NDVI'] = ndvi_vi(hsi_sub, wavelengths) 48 | det_out['NDWI'] = ndwi_vi(hsi_sub, wavelengths) 49 | det_out['PRI'] = pri_vi(hsi_sub, wavelengths) 50 | det_out['PSND ChlA'] = psnd_chlA_vi(hsi_sub, wavelengths) 51 | det_out['PSND ChlB'] = psnd_chlB_vi(hsi_sub, wavelengths) 52 | det_out['PSND Car'] = psnd_car_vi(hsi_sub, wavelengths) 53 | det_out['PSRI'] = psri_vi(hsi_sub, wavelengths) 54 | det_out['PSSR1'] = pssr1_vi(hsi_sub, wavelengths) 55 | det_out['PSSR2'] = pssr2_vi(hsi_sub, wavelengths) 56 | det_out['PSSR3'] = pssr3_vi(hsi_sub, wavelengths) 57 | det_out['REP'] = rep_vi(hsi_sub, wavelengths) 58 | det_out['RVSI'] = rvsi_vi(hsi_sub, wavelengths) 59 | det_out['SIPI'] = sipi_vi(hsi_sub, wavelengths) 60 | det_out['SR'] = sr_vi(hsi_sub, wavelengths) 61 | det_out['VARI'] = vari_vi(hsi_sub, wavelengths) 62 | det_out['VIgreen'] = vigreen_vi(hsi_sub, wavelengths) 63 | det_out['WDVI'] = wdvi_vi(hsi_sub, wavelengths) 64 | det_out['WBI'] = wbi_vi(hsi_sub, wavelengths) 65 | 66 | # Visualization with ALL indices 67 | plt.figure(figsize=(10, 15)) 68 | plt.subplots_adjust(hspace=0.5) 69 | n_row = 6; n_col = 6 70 | i = 1 71 | for key, value in det_out.items(): 72 | plt.subplot(n_row, n_col, i) 73 | plt.imshow(value) 74 | plt.yticks([]) 75 | plt.xticks([]) 76 | plt.title(key) 77 | i += 1 78 | plt.show() 79 | 80 | # Visualization with individual indices 81 | # Figures are saved in /hsi_toolkit_py/spectral_indices/Results/ 82 | value_rgb = det_out['RGB'] 83 | dirout = os.getcwd() 84 | for key, value in det_out.items(): 85 | if key != 'RGB': 86 | plt.figure(figsize=(10,5)) 87 | plt.subplot(1,2,1) 88 | plt.imshow(value_rgb) 89 | plt.colorbar(shrink=0.75) # only adding to make figures the same size 90 | plt.yticks([]) 91 | plt.xticks([]) 92 | plt.title('RGB') 93 | plt.subplot(1,2,2) 94 | plt.imshow(value) 95 | plt.colorbar(shrink=0.75) 96 | plt.yticks([]) 97 | plt.xticks([]) 98 | plt.title(key) 99 | plt.savefig((dirout + '/spectral_indicies_demo_results/' + key + '.png'), format='png', dpi=200) 100 | plt.close() 101 | -------------------------------------------------------------------------------- /demos/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GatorSense/hsi_toolkit_py/70fd8fb8db164f1b84d7324ffa690b7451938b6f/demos/__init__.py -------------------------------------------------------------------------------- /demos/a_hsi_img_for_VI_demo.mat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GatorSense/hsi_toolkit_py/70fd8fb8db164f1b84d7324ffa690b7451938b6f/demos/a_hsi_img_for_VI_demo.mat -------------------------------------------------------------------------------- /demos/an_hsi_image_sub_for_demo.mat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GatorSense/hsi_toolkit_py/70fd8fb8db164f1b84d7324ffa690b7451938b6f/demos/an_hsi_image_sub_for_demo.mat -------------------------------------------------------------------------------- /demos/an_hsi_img_for_class_demo.mat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GatorSense/hsi_toolkit_py/70fd8fb8db164f1b84d7324ffa690b7451938b6f/demos/an_hsi_img_for_class_demo.mat -------------------------------------------------------------------------------- /demos/an_hsi_img_for_tgt_det_demo.mat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GatorSense/hsi_toolkit_py/70fd8fb8db164f1b84d7324ffa690b7451938b6f/demos/an_hsi_img_for_tgt_det_demo.mat -------------------------------------------------------------------------------- /demos/demoVCA.py: -------------------------------------------------------------------------------- 1 | """ 2 | Demo script that runs the VCA algorithm using example sub MUUFL Gulfport data 3 | 4 | Inputs: 5 | hsi_img_sub - n_row x n_col x n_band hyperspectral image 6 | wavelengths - n_band x 1 vector listing wavelength values for hsi_img in nm 7 | mask_sub - n_row x n_col binary image limiting detector operation to pixels where mask is true 8 | if not present or empty, no mask restrictions are used 9 | M - number of endmembers to compute 10 | Outputs: 11 | E - n_band x M matrix of endmembers 12 | IdxOfE - M vector indexing the endmembers in masked_data 13 | Xpca - M x masked_data size matrix of data projected to M dimensions 14 | 15 | 1/17/2019 - Ronald Fick 16 | """ 17 | 18 | import numpy as np 19 | import matplotlib.pyplot as plt 20 | from hsi_toolkit.endmember_extraction import VCA 21 | 22 | import scipy.io as sio 23 | 24 | an_hsi_image_sub_for_demo = sio.loadmat('an_hsi_image_sub_for_demo.mat') 25 | hsi_img_sub = an_hsi_image_sub_for_demo['hsi_img_sub'] 26 | wavelengths = an_hsi_image_sub_for_demo['wavelengths'] 27 | mask_sub = an_hsi_image_sub_for_demo['mask_sub'] 28 | 29 | x_dims, y_dims, band_dims = hsi_img_sub.shape 30 | 31 | mat_data = np.reshape(hsi_img_sub, (x_dims*y_dims, band_dims)) 32 | 33 | mask_reshaped = np.reshape(mask_sub, (x_dims*y_dims)) 34 | 35 | masked_data = mat_data[mask_reshaped == 1] 36 | 37 | M = 3 38 | 39 | E, IdxOfE, Xpca = VCA(np.transpose(masked_data), M=M) 40 | 41 | fig = plt.figure() 42 | ax = fig.gca(projection='3d') 43 | 44 | nonendmembers = np.delete(np.arange(Xpca.shape[1]), IdxOfE) 45 | ax.scatter(Xpca[0,nonendmembers], Xpca[1,nonendmembers], Xpca[2,nonendmembers], s=5, c='b') 46 | ax.scatter(Xpca[0,IdxOfE], Xpca[1,IdxOfE], Xpca[2,IdxOfE], s=40, c='r') 47 | plt.title('Gulfport Data Projected to 3D - Endmembers in Red') 48 | 49 | plt.figure() 50 | plt.plot(wavelengths, E) 51 | plt.title('Estimated Endmembers from Gulfport Data') 52 | plt.xlabel('Wavelength (nm)') 53 | plt.ylabel('Reflectance') 54 | plt.show() 55 | -------------------------------------------------------------------------------- /demos/demo_anomaly_detector.py: -------------------------------------------------------------------------------- 1 | from scipy.io import loadmat 2 | import matplotlib.pyplot as plt 3 | from hsi_toolkit.anomaly_detectors import * 4 | from hsi_toolkit.util import * get_RGB 5 | """ 6 | Demo script that runs all anomaly detectors in hsi_toolkit_py using example sub MUUFL Gulfport data 7 | 8 | Inputs: 9 | hsi_img_sub - n_row x n_col x n_band hyperspectral image 10 | wavelengths - n_band x 1 vector listing wavelength values for hsi_img in nm 11 | mask_sub - n_row x n_col binary image limiting detector operation to pixels where mask is true 12 | if not present or empty, no mask restrictions are used 13 | Outputs: 14 | det_out - dictionary of detector output images 15 | 16 | 5/5/2018 - Alina Zare 17 | 10/1/2018 - Python Implementation by Yutai Zhou 18 | """ 19 | an_hsi_image_sub_for_demo = loadmat('an_hsi_image_sub_for_demo.mat') 20 | hsi_img_sub = an_hsi_image_sub_for_demo['hsi_img_sub'] 21 | wavelengths = an_hsi_image_sub_for_demo['wavelengths'] 22 | mask_sub = an_hsi_image_sub_for_demo['mask_sub'] 23 | 24 | det_out = {} 25 | det_out['RGB'] = get_RGB(hsi_img_sub, wavelengths) 26 | det_out['Valid Mask'] = mask_sub 27 | 28 | # init detector args 29 | guard_win = 3; bg_win = 3; n_dim_ss = 3 30 | 31 | # call detectors 32 | # beta_out = beta_anomaly(hsi_img_sub, mask_sub) <--- moved into dev 33 | # det_out['Beta Anomaly'] = beta_out 34 | cbad_out, _ = cbad_anomaly(hsi_img_sub, 8, mask_sub) 35 | det_out['CBAD Anomaly'] = cbad_out 36 | csd_out = csd_anomaly(hsi_img_sub, 3, None, True) 37 | det_out['CSD Anomaly'] = csd_out 38 | # fcbad_out, _ = fcbad_anomaly(hsi_img_sub, 8, mask_sub) 39 | # det_out['FCBAD Anomaly'] = fcbad_out 40 | gmm_out = gmm_anomaly(hsi_img_sub, n_comp = 8, mask = mask_sub) 41 | det_out['GMM Anomaly'] = gmm_out 42 | # gmrx_out = gmrx_anomaly(hsi_img_sub, n_comp = 8, mask = mask_sub) <--- moved into dev 43 | # det_out['GMRX Anomaly'] = gmrx_out 44 | md_out = md_anomaly(hsi_img_sub, mask_sub) 45 | det_out['MD Anomaly'] = md_out 46 | rx_out = rx_anomaly(hsi_img_sub, guard_win, bg_win, mask_sub) 47 | det_out['RX Anomaly'] = rx_out 48 | # ssrx_out = ssrx_anomaly(hsi_img_sub, n_dim_ss, guard_win, bg_win) <--- moved into dev 49 | # det_out['SSRX Anomaly'] = ssrx_out 50 | 51 | # visualization 52 | plt.figure(figsize=(10, 15)) 53 | plt.subplots_adjust(hspace=.5) 54 | n_row = 4; n_col = 3 55 | 56 | i = 1 57 | for key, value in det_out.items(): 58 | plt.subplot(n_row, n_col, i) 59 | plt.imshow(value); plt.title(key) 60 | i += 1 61 | # plt.imshow(gmrx_out) 62 | plt.show() 63 | -------------------------------------------------------------------------------- /demos/demo_classification.py: -------------------------------------------------------------------------------- 1 | from scipy.io import loadmat 2 | import matplotlib.pyplot as plt 3 | from hsi_toolkit.classifiers import * 4 | from hsi_toolkit.util import get_RGB 5 | """ 6 | Demo script that runs all classifiers in hsi_toolkit_py 7 | 8 | Inputs: 9 | hsi_img - n_row x n_col x n_band hyperspectral image 10 | train_data - structure containing training data, train_data(i).Spectra 11 | is matrix containing spectra from class i, train_data(i).name is the 12 | name of the ith class label 13 | mask - binary image limiting detector operation to pixels where mask is true 14 | if not present or empty, no mask restrictions are used 15 | wavelength - 1 x n_band vector listing wavelength values for hsi_img in nm 16 | 17 | Outputs: 18 | class_out - dictionary of rgb image and classifier outputs 19 | 20 | 6/3/2018 - Alina Zare 21 | 10/2018 - Python Implementation by Yutai Zhou 22 | """ 23 | an_hsi_img_for_class_demo = loadmat('an_hsi_img_for_class_demo.mat') 24 | hsi_img = an_hsi_img_for_class_demo['hsi_sub'] 25 | train_data = an_hsi_img_for_class_demo['train_data'] 26 | wavelength = an_hsi_img_for_class_demo['wavlength'] 27 | 28 | class_out = {} 29 | class_out['RGB'] = get_RGB(hsi_img, wavelength) 30 | class_out['KNN'] = knn_classifier(hsi_img, train_data, K = 3) 31 | class_out['FKNN'] = fuzzy_knn_classifier(hsi_img, train_data, K = 3, m = 2.1) 32 | class_out['PKNN'] = poss_knn_classifier(hsi_img, train_data, K = 3, eta = 0.01, m = 2.1) 33 | 34 | # KNN Results 35 | fig, ax = plt.subplots(1,2,figsize=(15, 8)) 36 | plt.subplots_adjust(hspace=.5) 37 | plt.suptitle('KNN Results') 38 | 39 | ax[0].imshow(class_out['RGB']); ax[0].set_title('RGB') 40 | cax = ax[1].imshow(class_out['KNN']); ax[1].set_title('KNN') 41 | cbar = fig.colorbar(cax, ticks=[i for i in range(len(train_data[0]))], orientation='vertical') 42 | cbar.ax.set_yticklabels([i[0] for i in train_data[0][:]['name']]) 43 | 44 | # Fuzzy KNN Results 45 | plt.figure(figsize=(10, 15)) 46 | plt.subplots_adjust(hspace=.5) 47 | n_row = 2; n_col = 3 48 | plt.suptitle('Fuzzy KNN Results') 49 | plt.subplot(n_row, n_col, 1) 50 | plt.imshow(class_out['RGB']); plt.title('RGB') 51 | for i in range(0, len(train_data[0])): 52 | plt.subplot(n_row, n_col, i + 2) 53 | plt.imshow(class_out['FKNN'][:,:,i]); 54 | plt.title(train_data['name'][0][i][0]) 55 | 56 | # Possibilistic KNN Results 57 | plt.figure(figsize=(10, 15)) 58 | plt.subplots_adjust(hspace=.5) 59 | n_row = 2; n_col = 3 60 | plt.suptitle('Possibilistic KNN Results') 61 | plt.subplot(n_row, n_col, 1) 62 | plt.imshow(class_out['RGB']); plt.title('RGB') 63 | for i in range(0, len(train_data[0])): 64 | plt.subplot(n_row, n_col, i + 2) 65 | plt.imshow(class_out['PKNN'][:,:,i]); 66 | plt.title(train_data['name'][0][i][0]) 67 | plt.show() 68 | -------------------------------------------------------------------------------- /demos/demo_dim_reduction.py: -------------------------------------------------------------------------------- 1 | from scipy.io import loadmat 2 | from hsi_toolkit.dim_reduction import * 3 | from hsi_toolkit.dev.dim_reduction.mnf import * 4 | from hsi_toolkit.util import get_RGB 5 | # Demo script that runs all dimensionality reduction methods in hsi_toolkit_py 6 | # 7 | # Inputs: 8 | # img: hyperspectral data cube (n_row x n_col x n_bands) 9 | # wavelengths: vector containing wavelengths of each HSI band (n_bands x 1) 10 | # 11 | # Outputs: 12 | # im_reduced: cell array containing reduced data results 13 | # 14 | # Author: Alina Zare 15 | # Email Address: azare@ufl.edu 16 | # Created: June 3, 2018 17 | 18 | # load data 19 | an_hsi_image_sub_for_demo = loadmat('an_hsi_image_sub_for_demo.mat') 20 | img = an_hsi_image_sub_for_demo['hsi_img_sub'] 21 | wavelengths = an_hsi_image_sub_for_demo['wavelengths'] 22 | 23 | # run hdr 24 | hdr_out = dimReduction(img) 25 | # run mnf 26 | mnf_out = mnf(img, 0.999)[0] 27 | mnf_out = (mnf_out - np.min(mnf_out)) / np.max(mnf_out) 28 | 29 | # visualize 30 | plt.subplot(1,3,1) 31 | plt.title("RGB Image") 32 | plt.imshow(get_RGB(img, wavelengths)) 33 | plt.subplot(1,3,2) 34 | plt.title("HDR Image") 35 | plt.imshow(hdr_out[:,:,:3]) 36 | plt.subplot(1,3,3) 37 | plt.title("MNF Image") 38 | plt.imshow(mnf_out[:,:,:3]) 39 | plt.show() 40 | -------------------------------------------------------------------------------- /demos/demo_signature_det.py: -------------------------------------------------------------------------------- 1 | from scipy.io import loadmat 2 | import matplotlib.pyplot as plt 3 | from hsi_toolkit.signature_detectors import * 4 | from hsi_toolkit.util import get_RGB 5 | 6 | """ 7 | Demo script that runs all signature detectors in hsi_toolkit_py 8 | 9 | Inputs: 10 | hsi_sub - n_row x n_col x n_band hyperspectral image 11 | tgt_spectra - n_band x 1 target signature vector 12 | wavelengths - n_band x 1 vector listing wavelength values for hsi_sub in nm 13 | gt_img_sub - n_row x n_col ground truths 14 | mask - binary image limiting detector operation to pixels where mask is true 15 | if not present or empty, no mask restrictions are used 16 | Outputs: 17 | det_out - dictionary of RGB image, ground truth image, and detector outputs 18 | 19 | 6/2/2018 - Alina Zare 20 | 10/12/2018 - Python Implementation by Yutai Zhou 21 | """ 22 | # Load data 23 | an_hsi_img_for_tgt_det_demo = loadmat('an_hsi_img_for_tgt_det_demo.mat') 24 | hsi_sub = an_hsi_img_for_tgt_det_demo['hsi_sub'] 25 | tgt_spectra = an_hsi_img_for_tgt_det_demo['tgt_spectra'] 26 | tgt_spectra = tgt_spectra.squeeze() 27 | wavelengths = an_hsi_img_for_tgt_det_demo['wavelengths'] 28 | gt_img_sub = an_hsi_img_for_tgt_det_demo['gtImg_sub'] 29 | 30 | det_out = {} 31 | det_out['RGB'] = get_RGB(hsi_sub, wavelengths) 32 | det_out['Ground Truth'] = gt_img_sub 33 | 34 | # init detector args 35 | guard_win = 1; bg_win = 3; beta = 0.001; n_dim_ss = 10 36 | ems = hsi_sub[:3,1,:].T # need to provide background endmembers (can get them using SPICE unmixing) 37 | 38 | # call detectors 39 | abd_out = abd_detector(hsi_sub, tgt_spectra, ems) 40 | det_out['ABD'] = abd_out 41 | ace_out, _, _ = ace_detector(hsi_sub, tgt_spectra) 42 | det_out['ACE Squared'] = ace_out 43 | ace_local_out, _ = ace_local_detector(hsi_sub, tgt_spectra, guard_win = guard_win, bg_win = bg_win, beta = beta) 44 | det_out['ACE Local Squared'] = ace_local_out 45 | ace_ss_out = ace_ss_detector(hsi_sub, tgt_spectra) 46 | det_out['ACE SS'] = ace_ss_out 47 | ace_rt_out, _, _ = ace_rt_detector(hsi_sub, tgt_spectra) 48 | det_out['ACE RT'] = ace_rt_out 49 | ace_rt_max_out, _, _ = ace_rt_max_detector(hsi_sub, tgt_spectra) 50 | det_out['ACE RT Max'] = ace_rt_max_out 51 | amsd_out= amsd_detector(hsi_sub, tgt_spectra, n_dim_tgt = 1, n_dim_bg = 3) 52 | det_out['AMSD'] = amsd_out 53 | ccmf_out, _ = ccmf_detector(hsi_sub, tgt_spectra, n_comp = 2) 54 | det_out['CCMF'] = ccmf_out 55 | cem_out, w = cem_detector(hsi_sub, tgt_spectra) 56 | det_out['CEM'] = cem_out 57 | ctmf_out, _ = ctmf_detector(hsi_sub, tgt_spectra, n_cluster = 2) 58 | det_out['CTMF'] = ctmf_out 59 | #vftmf_out = ftmf_detector(hsi_sub, tgt_spectra, gamma = 1) <--- moved into dev 60 | # det_out['FTMF'] = ftmf_out 61 | ha_out = ha_detector(hsi_sub, tgt_spectra, ems, n_comp = 2) 62 | det_out['HA'] = ha_out 63 | hsd_out, _ = hsd_detector(hsi_sub, tgt_spectra, ems) 64 | det_out['HSD'] = hsd_out 65 | hsd_local_out = hsd_local_detector(hsi_sub, tgt_spectra, ems, guard_win = guard_win, bg_win = bg_win, beta = beta) 66 | det_out['HSD Local'] = hsd_local_out 67 | hua_out = hua_detector(hsi_sub, tgt_spectra, ems, n_comp = 2) 68 | det_out['HUA'] = hua_out 69 | # mtmf_out,_ = mtmf_statistic(hsi_sub, tgt_spectra) <--- moved into dev 70 | # det_out['MTMF'] = mtmf_out 71 | smf_out, _, _ = smf_detector(hsi_sub, tgt_spectra) 72 | det_out['SMF'] = smf_out 73 | smf_local_out = smf_local_detector(hsi_sub, tgt_spectra, guard_win = guard_win, bg_win = bg_win) 74 | det_out['SMF Local'] = smf_local_out 75 | smf_max_out = smf_max_detector(hsi_sub, tgt_spectra) 76 | det_out['SMF Max'] = smf_max_out 77 | fam_statistic_out = fam_statistic(hsi_sub, tgt_spectra) 78 | det_out['FAM Statistic'] = fam_statistic_out 79 | osp_out = osp_detector(hsi_sub, tgt_spectra, n_dim_ss = 10) 80 | det_out['OSP'] = osp_out 81 | # qmf_out = qmf_detector(hsi_sub, tgt_spectra, 0.1 * np.eye(hsi_sub.shape[2])) <--- moved into dev 82 | # det_out['QMF'] = qmf_out 83 | sam_out = sam_detector(hsi_sub, tgt_spectra) 84 | det_out['SAM'] = sam_out 85 | # spsmf_out = spsmf_detector(hsi_sub, tgt_spectra) <--- moved into dev 86 | # det_out['SPSMF'] = spsmf_out 87 | palm_out = palm_detector(hsi_sub, tgt_spectra, n_comp = 5) 88 | det_out['PALM'] = palm_out 89 | 90 | 91 | # Segmented Detector Examples 92 | 93 | # # # get Segments (using K-means here, but better ways to do this in general, see context-dependent methods for detection) 94 | # n_cluster = 3 95 | # n_row, n_col, n_band = hsi_sub.shape 96 | # idx = KMeans(n_clusters = n_cluster, n_init = 1).fit(hsi_sub.reshape((n_row * n_col, n_band), order='F')).labels_ 97 | # idx_img = idx.reshape((n_row,n_col), order='F') 98 | 99 | # segments = np.zeros((n_cluster,n_row,n_col)) 100 | # for i in range(n_cluster): 101 | # segments[i,:,:] = idx_img == i 102 | 103 | # # Segmented Spectral Angle Mapper 104 | # seg_sam_out = img_seg(sam_detector,hsi_sub, tgt_spectra, segments) 105 | # det_out['Seg SAM'] = seg_sam_out 106 | 107 | # # Segmented Spectral Angle Mapper 108 | # seg_ace_out,_,_ = img_seg(ace_detector,hsi_sub, tgt_spectra, segments) 109 | # det_out['Seg ACE'] = seg_ace_out 110 | # plt.imshow(seg_ace_out) 111 | # plt.show() 112 | 113 | 114 | # # visualization 115 | plt.figure(figsize=(10, 15)) 116 | plt.subplots_adjust(hspace=.5) 117 | n_row = 5; n_col = 7 118 | # 119 | i = 1 120 | for key, value in det_out.items(): 121 | plt.subplot(n_row, n_col, i); 122 | plt.imshow(value); plt.title(key) 123 | i += 1 124 | plt.show() 125 | -------------------------------------------------------------------------------- /demos/spectral_indicies_demo_results/ACI.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GatorSense/hsi_toolkit_py/70fd8fb8db164f1b84d7324ffa690b7451938b6f/demos/spectral_indicies_demo_results/ACI.png -------------------------------------------------------------------------------- /demos/spectral_indicies_demo_results/ARI.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GatorSense/hsi_toolkit_py/70fd8fb8db164f1b84d7324ffa690b7451938b6f/demos/spectral_indicies_demo_results/ARI.png -------------------------------------------------------------------------------- /demos/spectral_indicies_demo_results/ARVI.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GatorSense/hsi_toolkit_py/70fd8fb8db164f1b84d7324ffa690b7451938b6f/demos/spectral_indicies_demo_results/ARVI.png -------------------------------------------------------------------------------- /demos/spectral_indicies_demo_results/CAI.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GatorSense/hsi_toolkit_py/70fd8fb8db164f1b84d7324ffa690b7451938b6f/demos/spectral_indicies_demo_results/CAI.png -------------------------------------------------------------------------------- /demos/spectral_indicies_demo_results/CARI.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GatorSense/hsi_toolkit_py/70fd8fb8db164f1b84d7324ffa690b7451938b6f/demos/spectral_indicies_demo_results/CARI.png -------------------------------------------------------------------------------- /demos/spectral_indicies_demo_results/CIrededge.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GatorSense/hsi_toolkit_py/70fd8fb8db164f1b84d7324ffa690b7451938b6f/demos/spectral_indicies_demo_results/CIrededge.png -------------------------------------------------------------------------------- /demos/spectral_indicies_demo_results/CRI1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GatorSense/hsi_toolkit_py/70fd8fb8db164f1b84d7324ffa690b7451938b6f/demos/spectral_indicies_demo_results/CRI1.png -------------------------------------------------------------------------------- /demos/spectral_indicies_demo_results/CRI2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GatorSense/hsi_toolkit_py/70fd8fb8db164f1b84d7324ffa690b7451938b6f/demos/spectral_indicies_demo_results/CRI2.png -------------------------------------------------------------------------------- /demos/spectral_indicies_demo_results/EVI.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GatorSense/hsi_toolkit_py/70fd8fb8db164f1b84d7324ffa690b7451938b6f/demos/spectral_indicies_demo_results/EVI.png -------------------------------------------------------------------------------- /demos/spectral_indicies_demo_results/MARI.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GatorSense/hsi_toolkit_py/70fd8fb8db164f1b84d7324ffa690b7451938b6f/demos/spectral_indicies_demo_results/MARI.png -------------------------------------------------------------------------------- /demos/spectral_indicies_demo_results/MCARI.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GatorSense/hsi_toolkit_py/70fd8fb8db164f1b84d7324ffa690b7451938b6f/demos/spectral_indicies_demo_results/MCARI.png -------------------------------------------------------------------------------- /demos/spectral_indicies_demo_results/MTCI.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GatorSense/hsi_toolkit_py/70fd8fb8db164f1b84d7324ffa690b7451938b6f/demos/spectral_indicies_demo_results/MTCI.png -------------------------------------------------------------------------------- /demos/spectral_indicies_demo_results/NDII.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GatorSense/hsi_toolkit_py/70fd8fb8db164f1b84d7324ffa690b7451938b6f/demos/spectral_indicies_demo_results/NDII.png -------------------------------------------------------------------------------- /demos/spectral_indicies_demo_results/NDLI.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GatorSense/hsi_toolkit_py/70fd8fb8db164f1b84d7324ffa690b7451938b6f/demos/spectral_indicies_demo_results/NDLI.png -------------------------------------------------------------------------------- /demos/spectral_indicies_demo_results/NDNI.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GatorSense/hsi_toolkit_py/70fd8fb8db164f1b84d7324ffa690b7451938b6f/demos/spectral_indicies_demo_results/NDNI.png -------------------------------------------------------------------------------- /demos/spectral_indicies_demo_results/NDRE.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GatorSense/hsi_toolkit_py/70fd8fb8db164f1b84d7324ffa690b7451938b6f/demos/spectral_indicies_demo_results/NDRE.png -------------------------------------------------------------------------------- /demos/spectral_indicies_demo_results/NDVI.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GatorSense/hsi_toolkit_py/70fd8fb8db164f1b84d7324ffa690b7451938b6f/demos/spectral_indicies_demo_results/NDVI.png -------------------------------------------------------------------------------- /demos/spectral_indicies_demo_results/NDWI.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GatorSense/hsi_toolkit_py/70fd8fb8db164f1b84d7324ffa690b7451938b6f/demos/spectral_indicies_demo_results/NDWI.png -------------------------------------------------------------------------------- /demos/spectral_indicies_demo_results/PRI.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GatorSense/hsi_toolkit_py/70fd8fb8db164f1b84d7324ffa690b7451938b6f/demos/spectral_indicies_demo_results/PRI.png -------------------------------------------------------------------------------- /demos/spectral_indicies_demo_results/PSND Car.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GatorSense/hsi_toolkit_py/70fd8fb8db164f1b84d7324ffa690b7451938b6f/demos/spectral_indicies_demo_results/PSND Car.png -------------------------------------------------------------------------------- /demos/spectral_indicies_demo_results/PSND ChlA.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GatorSense/hsi_toolkit_py/70fd8fb8db164f1b84d7324ffa690b7451938b6f/demos/spectral_indicies_demo_results/PSND ChlA.png -------------------------------------------------------------------------------- /demos/spectral_indicies_demo_results/PSND ChlB.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GatorSense/hsi_toolkit_py/70fd8fb8db164f1b84d7324ffa690b7451938b6f/demos/spectral_indicies_demo_results/PSND ChlB.png -------------------------------------------------------------------------------- /demos/spectral_indicies_demo_results/PSRI.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GatorSense/hsi_toolkit_py/70fd8fb8db164f1b84d7324ffa690b7451938b6f/demos/spectral_indicies_demo_results/PSRI.png -------------------------------------------------------------------------------- /demos/spectral_indicies_demo_results/PSSR1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GatorSense/hsi_toolkit_py/70fd8fb8db164f1b84d7324ffa690b7451938b6f/demos/spectral_indicies_demo_results/PSSR1.png -------------------------------------------------------------------------------- /demos/spectral_indicies_demo_results/PSSR2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GatorSense/hsi_toolkit_py/70fd8fb8db164f1b84d7324ffa690b7451938b6f/demos/spectral_indicies_demo_results/PSSR2.png -------------------------------------------------------------------------------- /demos/spectral_indicies_demo_results/PSSR3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GatorSense/hsi_toolkit_py/70fd8fb8db164f1b84d7324ffa690b7451938b6f/demos/spectral_indicies_demo_results/PSSR3.png -------------------------------------------------------------------------------- /demos/spectral_indicies_demo_results/REP.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GatorSense/hsi_toolkit_py/70fd8fb8db164f1b84d7324ffa690b7451938b6f/demos/spectral_indicies_demo_results/REP.png -------------------------------------------------------------------------------- /demos/spectral_indicies_demo_results/RVSI.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GatorSense/hsi_toolkit_py/70fd8fb8db164f1b84d7324ffa690b7451938b6f/demos/spectral_indicies_demo_results/RVSI.png -------------------------------------------------------------------------------- /demos/spectral_indicies_demo_results/SIPI.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GatorSense/hsi_toolkit_py/70fd8fb8db164f1b84d7324ffa690b7451938b6f/demos/spectral_indicies_demo_results/SIPI.png -------------------------------------------------------------------------------- /demos/spectral_indicies_demo_results/SR.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GatorSense/hsi_toolkit_py/70fd8fb8db164f1b84d7324ffa690b7451938b6f/demos/spectral_indicies_demo_results/SR.png -------------------------------------------------------------------------------- /demos/spectral_indicies_demo_results/VARI.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GatorSense/hsi_toolkit_py/70fd8fb8db164f1b84d7324ffa690b7451938b6f/demos/spectral_indicies_demo_results/VARI.png -------------------------------------------------------------------------------- /demos/spectral_indicies_demo_results/VIgreen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GatorSense/hsi_toolkit_py/70fd8fb8db164f1b84d7324ffa690b7451938b6f/demos/spectral_indicies_demo_results/VIgreen.png -------------------------------------------------------------------------------- /demos/spectral_indicies_demo_results/WBI.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GatorSense/hsi_toolkit_py/70fd8fb8db164f1b84d7324ffa690b7451938b6f/demos/spectral_indicies_demo_results/WBI.png -------------------------------------------------------------------------------- /demos/spectral_indicies_demo_results/WDVI.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GatorSense/hsi_toolkit_py/70fd8fb8db164f1b84d7324ffa690b7451938b6f/demos/spectral_indicies_demo_results/WDVI.png -------------------------------------------------------------------------------- /hsi_toolkit/__init__.py: -------------------------------------------------------------------------------- 1 | from hsi_toolkit import anomaly_detectors 2 | from hsi_toolkit import classifiers 3 | from hsi_toolkit import endmember_extraction 4 | from hsi_toolkit import signature_detectors 5 | from hsi_toolkit import spectral_indices 6 | from hsi_toolkit import dim_reduction 7 | from hsi_toolkit import util 8 | -------------------------------------------------------------------------------- /hsi_toolkit/anomaly_detectors/README.md: -------------------------------------------------------------------------------- 1 | # Gatorsense hsitoolkit anomaly detectors Python version 2 | hsi_toolkit_py/anomaly_detectors 3 | 4 | Current suite of anomaly detectors: 5 | - rx_anomaly: local anomaly detector, Widowed Reed-Xiaoli anomaly detector uses local mean and covariance to determine pixel to background distance 6 | - gmm_anomaly: global anomaly detector, fits GMM assuming entire image is background computes negative log likelihood of each pixel in the fit model 7 | - md_anomaly: global anomaly detector, Mahalanobis Distance anomaly detector uses global image mean and covariance as background estimates 8 | - cbad_anomaly: global/cluster-based anomaly detector, Cluster Based Anomaly Detection (CBAD) 9 | - csd_anomaly: global anomaly detector, Complementary Subspace Detector 10 | 11 | 12 | Suite of Anomaly Detectors in progress: 13 | - ssrx_anomaly: local anomaly detector, eliminate leading subspace as background, then use local mean and covariance to determine pixel to background distance 14 | - beta_anomaly: global anomaly detector, fits beta distribution to each band assuming entire image is background computes negative log likelihood of each pixel in the model 15 | - fcbad_anomaly: global/cluster-based anomaly detector, Fuzzy Cluster Based Anomaly Detection (FCBAD) 16 | - !gmrx_anomaly: global/cluster-based anomaly detector, fits GMM assuming entire image is background assigns pixels to highest posterior probability mixture component computes pixel Mahlanobis distance to component mean 17 | 18 | Contact: Alina Zare, azare@ufl.edu 19 | -------------------------------------------------------------------------------- /hsi_toolkit/anomaly_detectors/__init__.py: -------------------------------------------------------------------------------- 1 | from hsi_toolkit.anomaly_detectors.cbad_anomaly import * 2 | from hsi_toolkit.anomaly_detectors.csd_anomaly import * 3 | from hsi_toolkit.anomaly_detectors.gmm_anomaly import * 4 | from hsi_toolkit.anomaly_detectors.md_anomaly import * 5 | from hsi_toolkit.anomaly_detectors.rx_anomaly import * 6 | -------------------------------------------------------------------------------- /hsi_toolkit/anomaly_detectors/cbad_anomaly.py: -------------------------------------------------------------------------------- 1 | from hsi_toolkit.util import img_det 2 | import numpy as np 3 | from sklearn.cluster import KMeans 4 | 5 | def cbad_anomaly(hsi_img, n_cluster, mask = None): 6 | """ 7 | Cluster Based Anomaly Detection (CBAD) 8 | Ref: Carlotto, Mark J. "A cluster-based approach for detecting man-made objects and changes in imagery." IEEE Transactions on Geoscience and Remote Sensing 43.2 (2005): 374-387. 9 | 10 | Inputs: 11 | hsi_image - n_row x n_col x n_band hyperspectral image 12 | mask - binary image limiting detector operation to pixels where mask is true 13 | if not present or empty, no mask restrictions are used 14 | n_cluster - number of clusters to use 15 | 16 | Outputs: 17 | cbad_out - detector output image 18 | cluster_img - cluster label image 19 | 20 | 8/7/2012 - Taylor C. Glenn 21 | 5/5/2018 - Edited by Alina Zare 22 | 11/2018 - Python Implementation by Yutai Zhou 23 | """ 24 | cbad_out, kwargsout = img_det(cbad_out_helper, hsi_img, None, mask, n_cluster = n_cluster) 25 | cluster_img = kwargsout['idx'] 26 | return cbad_out, cluster_img 27 | 28 | def cbad_out_helper(hsi_data, tgt_sig, kwargs): 29 | n_cluster = kwargs['n_cluster'] 30 | n_bands, n_pixel = hsi_data.shape 31 | # cluster the data 32 | kmeans = KMeans(n_clusters = n_cluster, n_init = 1).fit(hsi_data.T) 33 | # get cluster stats 34 | mu = np.zeros((n_bands, n_cluster)) 35 | sig_inv = np.zeros((n_bands, n_bands, n_cluster)) 36 | 37 | for i in range(n_cluster): 38 | z = hsi_data[:, kmeans.labels_ == i] 39 | mu[:,i] = np.mean(z, 1) 40 | sig_inv[:,:,i] = np.linalg.pinv(np.cov(z.T, rowvar=False)) 41 | 42 | cbad_data = np.zeros(n_pixel) 43 | for i in range(n_pixel): 44 | z = hsi_data[:,i] - mu[:,kmeans.labels_[i]] 45 | cbad_data[i] = z.T @ sig_inv[:,:,kmeans.labels_[i]] @ z 46 | return cbad_data, {'idx': kmeans.labels_} 47 | -------------------------------------------------------------------------------- /hsi_toolkit/anomaly_detectors/csd_anomaly.py: -------------------------------------------------------------------------------- 1 | from hsi_toolkit.util import pca 2 | import numpy as np 3 | 4 | def csd_anomaly(hsi_img, n_dim_bg, n_dim_tgt, tgt_orth): 5 | """ 6 | Complementary Subspace Detector 7 | assumes background and target are complementary subspaces 8 | of PCA variance ranked space 9 | Ref: A. Schaum, "Joint subspace detection of hyperspectral targets," 2004 IEEE Aerospace Conference Proceedings (IEEE Cat. No.04TH8720), 2004, pp. 1824 Vol.3. doi: 10.1109/AERO.2004.1367963 10 | 11 | inputs: 12 | hsi_image - n_row x n_col x n_band 13 | n_dim_bg - number of leading dimensions to assign to background subspace 14 | n_dim_tgt - number of dimensions to assign to target subspace 15 | use empty matrix, [], to use all remaining after background assignment 16 | tgt_orth - True/False, set target subspace orthogonal to background subspace 17 | 18 | 8/7/2012 - Taylor C. Glenn 19 | 5/5/2018 - Edited by Alina Zare 20 | 11/2018 - Python Implementation by Yutai Zhou 21 | """ 22 | n_row, n_col, n_band = hsi_img.shape 23 | n_pixel = n_row * n_col 24 | 25 | hsi_data = hsi_img.reshape((n_pixel, n_band), order='F').T 26 | 27 | # PCA rotation, no reduction 28 | pca_data, _, evecs, evals, _ = pca(hsi_data, 1) 29 | 30 | # whiten the data so that later steps are equivalent to Mahalanobis distance 31 | z = np.diag(1 / np.sqrt(evals)) @ pca_data 32 | 33 | # figure out background and target subspaces 34 | bg_rg = np.array(range(0,n_dim_bg)) 35 | 36 | if tgt_orth: 37 | # set target to orthogonal complement of background 38 | if n_dim_tgt is None: 39 | n_dim_tgt = n_band - n_dim_bg 40 | tgt_rg = np.array(range(n_dim_bg, n_dim_tgt)) 41 | else: 42 | # target and background overlap 43 | if n_dim_tgt is None: 44 | n_dim_tgt = n_band 45 | tgt_rg = np.array(range(0, n_dim_tgt)) 46 | 47 | # set background and target subspaces 48 | B = evecs[:, bg_rg] 49 | S = evecs[:, tgt_rg] 50 | 51 | # run the detector 52 | csd_data = np.zeros(n_pixel) 53 | 54 | for i in range(n_pixel): 55 | Sz = S.T @ z[:,i] 56 | Bz = B.T @ z[:,i] 57 | 58 | csd_data[i] = Sz.T @ Sz - Bz.T @ Bz 59 | 60 | csd_out = csd_data.reshape(n_row, n_col, order = 'F') 61 | 62 | return csd_out 63 | -------------------------------------------------------------------------------- /hsi_toolkit/anomaly_detectors/gmm_anomaly.py: -------------------------------------------------------------------------------- 1 | from hsi_toolkit.util import img_det 2 | from sklearn.mixture import GaussianMixture 3 | 4 | def gmm_anomaly(hsi_img, n_comp, mask = None): 5 | """ 6 | Gaussian Mixture Model Anomaly Detector 7 | fits GMM assuming entire image is background 8 | computes negative log likelihood of each pixel in the fit model 9 | 10 | Inputs: 11 | hsi_image - n_row x n_col x n_band hyperspectral image 12 | mask - binary image limiting detector operation to pixels where mask is true 13 | if not present or empty, no mask restrictions are used 14 | n_comp - number of Gaussian components to use 15 | 16 | Outputs: 17 | gmm_out - detector output image 18 | 19 | 8/7/2012 - Taylor C. Glenn 20 | 5/5/2018 - Edited by Alina Zare 21 | 11/2018 - Python Implementation by Yutai Zhou 22 | """ 23 | gmm_out, kwargsout = img_det(gmm_helper, hsi_img, None, mask, n_comp = n_comp) 24 | return gmm_out 25 | 26 | def gmm_helper(hsi_data, tgt_sig, kwargs): 27 | n_comp = kwargs['n_comp'] 28 | gmm = GaussianMixture(n_components = n_comp, max_iter = 1, init_params = 'random').fit(hsi_data.T) 29 | gmm_data = -gmm.score_samples(hsi_data.T) 30 | return gmm_data, {} 31 | -------------------------------------------------------------------------------- /hsi_toolkit/anomaly_detectors/md_anomaly.py: -------------------------------------------------------------------------------- 1 | from hsi_toolkit.util import img_det 2 | import numpy as np 3 | 4 | def md_anomaly(hsi_img, mask = None): 5 | """ 6 | Mahalanobis Distance anomaly detector 7 | uses global image mean and covariance as background estimates 8 | 9 | Inputs: 10 | hsi_image - n_row x n_col x n_band hyperspectral image 11 | mask - binary image limiting detector operation to pixels where mask is true 12 | if not present or empty, no mask restrictions are used 13 | 14 | Outputs: 15 | dist_img - detector output image 16 | 17 | 8/7/2012 - Taylor C. Glenn 18 | 5/5/2018 - Edited by Alina Zare 19 | 11/2018 - Python Implementation by Yutai Zhou 20 | """ 21 | dist_img, kwargsout = img_det(md_helper, hsi_img, None, mask) 22 | return dist_img 23 | 24 | def md_helper(hsi_data, tgt_sig, kwargs): 25 | n_pixel = hsi_data.shape[1] 26 | 27 | mu = np.mean(hsi_data, 1) 28 | sigma = np.cov(hsi_data.T, rowvar = False) 29 | 30 | z = hsi_data - mu[:,np.newaxis] 31 | sig_inv = np.linalg.pinv(sigma) 32 | 33 | dist_data = np.zeros(n_pixel) 34 | for i in range(n_pixel): 35 | dist_data[i] = z[:,i].T @ sig_inv @ z[:,i] 36 | return dist_data, {} 37 | -------------------------------------------------------------------------------- /hsi_toolkit/anomaly_detectors/rx_anomaly.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | 3 | def rx_anomaly(hsi_img, guard_win, bg_win, mask = None): 4 | """ 5 | Widowed Reed-Xiaoli anomaly detector 6 | use local mean and covariance to determine pixel to background distance 7 | 8 | Inputs: 9 | hsi_image - n_row x n_col x n_band 10 | mask - binary image limiting detector operation to pixels where mask is true 11 | if not present or empty, no mask restrictions are used 12 | guard_win - guard window radius (square,symmetric about pixel of interest) 13 | bg_win - background window radius 14 | 15 | 8/7/2012 - Taylor C. Glenn - tcg@cise.ufl.edu 16 | 5/5/2018 - Edited by Alina Zare 17 | 10/1/2018 - Python Implementation by Yutai Zhou 18 | """ 19 | n_row, n_col, n_band = hsi_img.shape 20 | n_pixels = n_row * n_col 21 | hsi_data = np.reshape(hsi_img, (n_pixels, n_band), order='F').T 22 | 23 | # Create the mask 24 | mask_width = 1 + 2 * guard_win + 2 * bg_win 25 | b_mask = np.ones((mask_width, mask_width), dtype=bool) 26 | b_mask[bg_win:b_mask.shape[0] - bg_win, bg_win:b_mask.shape[0] - bg_win] = 0 27 | 28 | # run the detector (only on fully valid points) 29 | mask = np.ones((n_row, n_col)) if mask is None else mask.astype(bool) 30 | rx_img = np.zeros((n_row, n_col)) 31 | half_width = guard_win + bg_win 32 | 33 | for i in range(n_col - mask_width + 1): 34 | for j in range(n_row - mask_width + 1): 35 | row = j + half_width 36 | col = i + half_width 37 | 38 | if mask[row, col] == 0: continue 39 | 40 | b_mask_img = np.zeros((n_row, n_col)) 41 | b_mask_img[j:mask_width + j, i:mask_width + i] = b_mask 42 | b_mask_list = np.reshape(b_mask_img, -1, order='F') 43 | 44 | # pull out background points 45 | bg = hsi_data[:, b_mask_list == 1] 46 | 47 | # Mahalanobis distance 48 | covariance = np.cov(bg.T, rowvar=False) 49 | s = np.float32(np.linalg.svd(covariance, compute_uv=False)) 50 | rcond = np.max(covariance.shape)*np.spacing(np.float32(np.linalg.norm(s, ord=np.inf))) 51 | # pinv differs from MATLAB 52 | sig_inv = np.linalg.pinv(covariance, rcond=rcond) 53 | 54 | mu = np.mean(bg, 1) 55 | z = hsi_img[row, col, :] - mu 56 | z = np.reshape(z, (len(z), 1), order='F') 57 | 58 | rx_img[row, col] = z.T @ sig_inv @ z 59 | 60 | return rx_img 61 | -------------------------------------------------------------------------------- /hsi_toolkit/classifiers/README.md: -------------------------------------------------------------------------------- 1 | # Gatorsense hsitoolkit classifiers Python version 2 | hsi_toolkit_py/classifiers 3 | 4 | Current suite of classifiers: 5 | 6 | -knn_classifier: simple K-Nearest Neighbors classifier 7 | 8 | -fuzzy_knn_classifier: the Fuzzy K-Nearest Neighbors classifier 9 | 10 | -poss_knn_classifier: the Possibilistic K-Nearest Neighbors classifier 11 | 12 | 13 | Contact: Alina Zare, azare@ufl.edu 14 | -------------------------------------------------------------------------------- /hsi_toolkit/classifiers/__init__.py: -------------------------------------------------------------------------------- 1 | from hsi_toolkit.classifiers.fuzzy_knn_classifier import * 2 | from hsi_toolkit.classifiers.knn_classifier import * 3 | from hsi_toolkit.classifiers.poss_knn_classifier import * 4 | -------------------------------------------------------------------------------- /hsi_toolkit/classifiers/fuzzy_knn_classifier.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from sklearn.neighbors import NearestNeighbors 3 | 4 | def fuzzy_knn_classifier(hsi_img, train_data, K, m = 2): 5 | """ 6 | Fuzzy K nearest neighbors classifier 7 | 8 | Ref: Keller, J. M., Gray, M. R., & Givens, J. A. (1985). A fuzzy k-nearest neighbor algorithm. IEEE transactions on systems, man, and cybernetics, (4), 580-585. 9 | 10 | Inputs: 11 | hsi_img: hyperspectral data cube (n_rows x n_cols x n_bands) 12 | train_data - numpy void structure containing training data 13 | train_data['Spectra'][0, i]: matrix containing training data from class i 14 | train_data['name'][0, i]: matrix containing name of class i 15 | K: number of neighbors to use during classification 16 | m: fuzzifier (usually = 2) 17 | 18 | Outputs: 19 | fknn_img: class membership matrix (n_row x n_col x n_class) 20 | 21 | 6/3/2018 - Alina Zare 22 | 10/2018 - Python Implementation by Yutai Zhou 23 | """ 24 | n_row, n_col, n_band = hsi_img.shape 25 | n_pixels = n_row * n_col 26 | hsi_data = np.reshape(hsi_img, (n_pixels, n_band), order='F').T 27 | 28 | # concatenate the training data 29 | train_data = train_data.squeeze() 30 | train = np.hstack([class_data for class_data in train_data['Spectra']]) 31 | n_train = train.shape[1] 32 | 33 | labels = np.zeros((n_train, 1)) 34 | n_class = train_data.size 35 | 36 | last = -1 37 | for i in range(n_class): 38 | nt = train_data[i]['Spectra'].shape[1] 39 | labels[(last + 1):(last + nt + 1)] = i 40 | last += nt 41 | 42 | n_pix = hsi_data.shape[1] 43 | 44 | #compute mu weights for training data 45 | knn_mu = NearestNeighbors(n_neighbors=K) 46 | knn_mu.fit(train.T) 47 | idx_train = knn_mu.kneighbors(train.T)[1] 48 | idx_labels = labels[idx_train].squeeze() 49 | mu = np.zeros((n_train, n_class)) 50 | 51 | for i in range(n_train): 52 | unique_labels = np.unique(idx_labels[i, :]) 53 | for j in range(len(unique_labels)): 54 | count = np.sum(idx_labels[i,:] == unique_labels[j]) 55 | if unique_labels[j] == labels[i,0]: 56 | mu[i, int(unique_labels[j])] = 0.51 + count * 0.49 / K 57 | else: 58 | mu[i, int(unique_labels[j])] = count * 0.49 / K 59 | 60 | # classify with weighted KNN 61 | fknn_out = np.zeros((n_pix, n_class)) 62 | knn = NearestNeighbors(n_neighbors=K) 63 | knn.fit(train.T) 64 | distance, idx = knn.kneighbors(hsi_data.T) 65 | weights = 1 / (distance ** (2 / (m - 1)) + np.finfo(float).eps) 66 | weights = weights / (np.tile(np.sum(weights,1)[:,np.newaxis],(1,K))) 67 | 68 | for i in range(n_pix): 69 | fknn_out[i,:] = np.sum(np.tile(weights[i,:][np.newaxis,:].T, (1,n_class)) * mu[idx[i,:],:],0) 70 | 71 | fknn_img = np.zeros((n_row, n_col, n_class)) 72 | for i in range(n_class): 73 | fknn_img[:,:,i] = np.reshape(fknn_out[:,i], (n_row, n_col), order='F') 74 | return fknn_img 75 | -------------------------------------------------------------------------------- /hsi_toolkit/classifiers/knn_classifier.py: -------------------------------------------------------------------------------- 1 | from hsi_toolkit.util import img_det 2 | import numpy as np 3 | from sklearn.neighbors import NearestNeighbors 4 | 5 | def knn_classifier(hsi_img, train_data, K, mask = None): 6 | """ 7 | A simple K nearest neighbors classifier 8 | 9 | Inputs: 10 | hsi_img - hyperspectral data cube (n_rows x n_cols x n_bands) 11 | train_data - numpy void structure containing training data 12 | train_data['Spectra'][0, i]: matrix containing training data from class i 13 | train_data['name'][0, i]: matrix containing name of class i 14 | mask - binary image indicating where to apply classifier 15 | K - number of neighbors to use during classification 16 | 17 | 10/31/2012 - Taylor C. Glenn 18 | 05/12/2018 - Edited by Alina Zare 19 | 10/2018 - Python Implementation by Yutai Zhou 20 | """ 21 | knn_out, kwargsout = img_det(knn_cfr, hsi_img, train_data, mask = mask, K = K); 22 | return knn_out 23 | 24 | def knn_cfr(hsi_data, train_data, kwargs): 25 | K = kwargs['K'] 26 | train_data = train_data.squeeze() 27 | # concatenate the training data 28 | train = np.hstack([class_data for class_data in train_data['Spectra']]) 29 | n_train = train.shape[1] 30 | 31 | labels = np.zeros((n_train, 1)) 32 | n_class = train_data.size 33 | 34 | last = -1 35 | for i in range(n_class): 36 | nt = train_data[i]['Spectra'].shape[1] 37 | labels[(last + 1):(last + nt + 1)] = i 38 | last += nt 39 | 40 | n_pix = hsi_data.shape[1] 41 | 42 | # classify by majority of K nearest neighbors 43 | knn_out = np.zeros((n_pix,1)) 44 | knn = NearestNeighbors(n_neighbors=K) 45 | knn.fit(train.T) 46 | idx = knn.kneighbors(hsi_data.T)[1] 47 | 48 | for i in range(n_pix): 49 | counts = np.zeros((n_class, 1)) 50 | for j in range(K): 51 | counts[int(labels[idx[i,j]])] = counts[int(labels[idx[i,j]])] + 1 52 | max_i = np.argmax(counts) 53 | knn_out[i,0] = max_i 54 | return knn_out.squeeze(), {} 55 | -------------------------------------------------------------------------------- /hsi_toolkit/classifiers/poss_knn_classifier.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from sklearn.neighbors import NearestNeighbors 3 | 4 | def poss_knn_classifier(hsi_img, train_data, K, eta, m = 2): 5 | """ 6 | Possibilistic K nearest neighbors classifier 7 | 8 | Ref: Frigui, Hichem, and Paul Gader. "Detection and discrimination of land mines in ground-penetrating radar based on edge histogram descriptors and a possibilistic $ k $-nearest neighbor classifier." IEEE Transactions on Fuzzy Systems 17.1 (2009): 185-199. 9 | 10 | Inputs: 11 | hsi_img: hyperspectral data cube (n_rows x n_cols x n_bands) 12 | train_data - numpy void structure containing training data 13 | train_data['Spectra'][0, i]: matrix containing training data from class i 14 | train_data['name'][0, i]: matrix containing name of class i 15 | K: number of neighbors to use during classification 16 | m: fuzzifier (usually = 2) 17 | eta: eta parameter to determine what is an outlier 18 | 19 | Outputs: 20 | pknn_img: class membership matrix (n_row x n_col x n_class) 21 | 22 | 6/3/2018 - Alina Zare 23 | 10/2018 - Python Implementation by Yutai Zhou 24 | """ 25 | n_row, n_col, n_band = hsi_img.shape 26 | n_pixels = n_row * n_col 27 | hsi_data = np.reshape(hsi_img, (n_pixels, n_band), order='F').T 28 | 29 | # concatenate the training data 30 | train_data = train_data.squeeze() 31 | train = np.hstack([class_data for class_data in train_data['Spectra']]) 32 | n_train = train.shape[1] 33 | 34 | labels = np.zeros((n_train, 1)) 35 | n_class = train_data.size 36 | 37 | last = -1 38 | for i in range(n_class): 39 | nt = train_data[i]['Spectra'].shape[1] 40 | labels[(last + 1):(last + nt + 1)] = i 41 | last += nt 42 | 43 | n_pix = hsi_data.shape[1] 44 | 45 | #compute mu weights for training data 46 | knn_mu = NearestNeighbors(n_neighbors=K) 47 | knn_mu.fit(train.T) 48 | idx_train = knn_mu.kneighbors(train.T)[1] 49 | idx_labels = labels[idx_train].squeeze() 50 | mu = np.zeros((n_train, n_class)) 51 | 52 | for i in range(n_train): 53 | unique_labels = np.unique(idx_labels[i, :]) 54 | for j in range(len(unique_labels)): 55 | count = np.sum(idx_labels[i,:] == unique_labels[j]) 56 | if unique_labels[j] == labels[i,0]: 57 | mu[i, int(unique_labels[j])] = 0.51 + count * 0.49 / K 58 | else: 59 | mu[i, int(unique_labels[j])] = count * 0.49 / K 60 | 61 | # classify with weighted KNN 62 | pknn_out = np.zeros((n_pix, n_class)) 63 | knn = NearestNeighbors(n_neighbors=K) 64 | knn.fit(train.T) 65 | distance, idx = knn.kneighbors(hsi_data.T) 66 | weights = distance - eta 67 | weights[weights < 0] = 0 68 | weights = 1 / (1 + (weights ** (2/(m-1))) + np.finfo(float).eps) 69 | 70 | for i in range(n_pix): 71 | pknn_out[i,:] = np.sum(np.tile(weights[i,:][np.newaxis,:].T, (1,n_class)) * mu[idx[i,:],:],0) / K 72 | 73 | pknn_img = np.zeros((n_row, n_col, n_class)) 74 | for i in range(n_class): 75 | pknn_img[:,:,i] = np.reshape(pknn_out[:,i], (n_row, n_col), order='F') 76 | 77 | return pknn_img 78 | -------------------------------------------------------------------------------- /hsi_toolkit/dev/__init__.py: -------------------------------------------------------------------------------- 1 | from hsi_toolkit.dev import anomaly_detectors 2 | from hsi_toolkit.dev import dim_reduction 3 | from hsi_toolkit.dev import signature_detectors 4 | -------------------------------------------------------------------------------- /hsi_toolkit/dev/anomaly_detectors/__init__.py: -------------------------------------------------------------------------------- 1 | from hsi_toolkit.dev.anomaly_detectors.beta_anomaly import * 2 | from hsi_toolkit.dev.anomaly_detectors.fcbad_anomaly import * 3 | from hsi_toolkit.dev.anomaly_detectors.gmrx_anomaly import * 4 | from hsi_toolkit.dev.anomaly_detectors.ssrx_anomaly import * 5 | -------------------------------------------------------------------------------- /hsi_toolkit/dev/anomaly_detectors/beta_anomaly.py: -------------------------------------------------------------------------------- 1 | from hsi_toolkit.util import img_det 2 | import numpy as np 3 | from scipy.stats import beta 4 | 5 | def beta_anomaly(hsi_img, mask): 6 | """ 7 | Beta Distribution Anomaly Detector 8 | fits beta distribution to each band assuming entire image is background 9 | computes negative log likelihood of each pixel in the model 10 | 11 | Inputs: 12 | hsi_image - n_row x n_col x n_band hyperspectral image 13 | mask - binary image limiting detector operation to pixels where mask is true 14 | if not present or empty, no mask restrictions are used 15 | 16 | Outputs: 17 | beta_out - detector output image 18 | 19 | 8/24/2012 - Taylor C. Glenn 20 | 5/5/2018 - Edited by Alina Zare 21 | 11/2018 - Python Implementation by Yutai Zhou 22 | """ 23 | beta_out, kwargsout = img_det(beta_helper, hsi_img, None, mask) 24 | return beta_out 25 | 26 | def beta_helper(hsi_data, tgt_sig, kwargs): 27 | n_band, n_pixel = hsi_data.shape 28 | hsi_data[hsi_data <= 0] = 1e-6 29 | hsi_data[hsi_data >= 1] = 1 - 1e-6 30 | 31 | alphas = np.zeros(n_band) 32 | betas = np.zeros(n_band) 33 | 34 | # fit the model 35 | loc = 0; scale = 1 36 | for i in range(n_band): 37 | params = beta.fit(hsi_data[i,:]) 38 | alphas[i] = params[0] 39 | betas[i] = params[1] 40 | 41 | # compute likelihood of each pixel 42 | likelihood = np.zeros((n_band, n_pixel)) 43 | for i in range(n_band): 44 | likelihood[i,:] = beta.logpdf(hsi_data[i,:], alphas[i], betas[i]) 45 | beta_data = - np.sum(likelihood, 0) 46 | return beta_data, {} 47 | -------------------------------------------------------------------------------- /hsi_toolkit/dev/anomaly_detectors/fcbad_anomaly.py: -------------------------------------------------------------------------------- 1 | from hsi_toolkit.util import img_det 2 | import numpy as np 3 | import skfuzzy as fuzz 4 | 5 | def fcbad_anomaly(hsi_img, n_cluster, mask = None): 6 | """ 7 | Fuzzy Cluster Based Anomaly Detection (FCBAD) 8 | Ref: Hytla, Patrick C., et al. "Anomaly detection in hyperspectral imagery: comparison of methods using diurnal and seasonal data." Journal of Applied Remote Sensing 3.1 (2009): 033546 9 | 10 | This algorithm requires skfuzz! https://pythonhosted.org/scikit-fuzzy/install.html 11 | 12 | Inputs: 13 | hsi_image - n_row x n_col x n_band hyperspectral image 14 | mask - binary image limiting detector operation to pixels where mask is true 15 | if not present or empty, no mask restrictions are used 16 | n_cluster - number of clusters to use 17 | 18 | Outputs: 19 | fcbad_out - detector output image 20 | cluster_img - cluster label image 21 | 22 | 8/8/2012 - Taylor C. Glenn 23 | 5/5/2018 - Edited by Alina Zare 24 | 11/2018 - Python Implementation by Yutai Zhou 25 | """ 26 | fcbad_out, kwargsout = img_det(fcbad_out_helper, hsi_img, None, mask, n_cluster = n_cluster) 27 | cluster_img = kwargsout['idx'] 28 | return fcbad_out, cluster_img 29 | 30 | def fcbad_out_helper(hsi_data, tgt_sig, kwargs): 31 | n_cluster = kwargs['n_cluster'] 32 | n_band, n_pixel = hsi_data.shape 33 | 34 | options = {'c': n_cluster, # number of clusters 35 | 'm': 2.0, # exponent for the partition matrix 36 | 'error': 1e-6, # minimum amount of improvement 37 | 'maxiter': 500} # max number of iterations 38 | 39 | C, U, _, _, _, _, _ = fuzz.cluster.cmeans(hsi_data, **options) 40 | idx = np.max(U,0) 41 | 42 | # Cluster stats 43 | mu = np.zeros((n_band, n_cluster)) 44 | sig_inv = np.zeros((n_band, n_band, n_cluster)) 45 | 46 | for i in range(n_cluster): 47 | mu[:,i] = C[i,:].T 48 | 49 | #computer membership weighted covariance for the cluster 50 | sigma = np.zeros((n_band, n_band)) 51 | for j in range(n_pixel): 52 | z = hsi_data[:,j] - mu[:,i] 53 | sigma += U[i,j] * (z @ z.T) 54 | 55 | sigma /= np.sum(U[i,:]) 56 | s = np.float32(np.linalg.svd(sigma, compute_uv=False)) 57 | rcond = np.max(sigma.shape)*np.spacing(np.float64(np.linalg.norm(s, ord=np.inf))) 58 | sig_inv[:,:,i] = np.linalg.pinv(sigma, rcond=rcond) 59 | 60 | # compute total membership weighted Mahalanobis Distance 61 | fcbad_data = np.zeros(n_pixel) 62 | 63 | for j in range(n_pixel): 64 | m_dists = np.zeros(n_cluster) 65 | 66 | for i in range(n_cluster): 67 | z = hsi_data[:,j] - mu[:,i] 68 | m_dists[i] = z.T @ sig_inv[:,:,i] @ z 69 | 70 | fcbad_data[j] = U[:,j].T @ m_dists 71 | return fcbad_data, {'idx': idx} 72 | -------------------------------------------------------------------------------- /hsi_toolkit/dev/anomaly_detectors/gmrx_anomaly.py: -------------------------------------------------------------------------------- 1 | from hsi_toolkit.util import img_det 2 | import numpy as np 3 | from sklearn.mixture import GaussianMixture 4 | 5 | def gmrx_anomaly(hsi_img, n_comp, mask = None): 6 | """ 7 | Gaussian Mixture RX Anomaly Detector 8 | fits GMM assuming entire image is background 9 | assigns pixels to highest posterior probability mixture component 10 | computes pixel Mahlanobis distance to component mean 11 | 12 | Inputs: 13 | hsi_image - n_row x n_col x n_band hyperspectral image 14 | mask - binary image limiting detector operation to pixels where mask is true 15 | if not present or empty, no mask restrictions are used 16 | n_comp - number of Gaussian components to use 17 | 18 | Outputs: 19 | gmrx_out - detector output image 20 | 21 | 8/7/2012 - Taylor C. Glenn - tcg@cise.ufl.edu 22 | 5/5/2018 - Edited by Alina Zare 23 | 11/2018 - Python Implementation by Yutai Zhou 24 | """ 25 | gmm_out, kwargsout = img_det(gmrx_helper, hsi_img, None, mask, n_comp = n_comp) 26 | return gmm_out 27 | 28 | def gmrx_helper(hsi_data, tgt_sig, kwargs): 29 | n_comp = kwargs['n_comp'] 30 | n_pixel = hsi_data.shape[1] 31 | gmm = GaussianMixture(n_components = n_comp, max_iter = 1, init_params = 'random').fit(hsi_data.T) 32 | 33 | # cluster/assign mixture component to each pixel, get Mahalanobis distance to components 34 | idx = gmm.predict(hsi_data.T) 35 | 36 | gmrx_data = np.zeros(n_pixel) 37 | # for i in range(n_pixel): 38 | # gmrx_data[i] = 39 | -------------------------------------------------------------------------------- /hsi_toolkit/dev/anomaly_detectors/ssrx_anomaly.py: -------------------------------------------------------------------------------- 1 | from hsi_toolkit.util import pca 2 | import numpy as np 3 | 4 | def ssrx_anomaly(hsi_img, n_dim_ss, guard_win, bg_win): 5 | """ 6 | function ssrx_img = ssrx_anomaly(hsi_img,n_dim_ss,guard_win,bg_win) 7 | 8 | Subspace Reed-Xiaoli anomaly detector 9 | eliminate leading subspace as background, then 10 | use local mean and covariance to determine pixel to background distance 11 | 12 | Inputs: 13 | hsi_image - n_row x n_col x n_band 14 | n_dim_ss - number of leading dimensions to use in the background subspace 15 | guard_win - guard window radius (square,symmetric about pixel of interest) 16 | bg_win - background window radius 17 | 18 | 8/7/2012 - Taylor C. Glenn 19 | 5/5/2018 - Edited by Alina Zare 20 | 11/2018 - Python Implementation by Yutai Zhou 21 | """ 22 | n_row, n_col, n_band = hsi_img.shape 23 | n_pixels = n_row * n_col 24 | hsi_data = np.reshape(hsi_img, (n_pixels, n_band), order='F').T 25 | 26 | # PCA with no dim deduction 27 | pca_data, _, evecs, evals, _ = pca(hsi_data, 1) 28 | 29 | pca_img = np.reshape(pca_data.T, (n_row, n_col, n_band), order='F') 30 | proj = np.eye(n_band) - evecs[:, :n_dim_ss] @ evecs[:, :n_dim_ss].T 31 | # Create the mask 32 | mask_width = 1 + 2 * guard_win + 2 * bg_win 33 | half_width = guard_win + bg_win 34 | mask_rg = np.array(range(mask_width)) - 1 35 | 36 | b_mask = np.ones((mask_width, mask_width), dtype=bool) 37 | b_mask[bg_win:b_mask.shape[0] - bg_win, bg_win:b_mask.shape[0] - bg_win] = 0 38 | 39 | # run the detector (only on fully valid points) 40 | ssrx_img = np.zeros((n_row, n_col)) 41 | 42 | for i in range(n_col - mask_width + 1): 43 | for j in range(n_row - mask_width + 1): 44 | row = j + half_width 45 | col = i + half_width 46 | 47 | b_mask_img = np.zeros((n_row, n_col)) 48 | b_mask_img[j:mask_width + j, i:mask_width + i] = b_mask 49 | b_mask_list = np.reshape(b_mask_img, -1, order='F') 50 | # pull out background points 51 | bg = pca_data[:, b_mask_list == 1] 52 | 53 | # Mahalanobis distance 54 | covariance = np.cov(bg.T, rowvar=False) 55 | 56 | s = np.float32(np.linalg.svd(covariance, compute_uv=False)) 57 | rcond = np.max(covariance.shape)*np.spacing(np.float32(np.linalg.norm(s, ord=np.inf))) 58 | # pinv differs from MATLAB 59 | sig_inv = np.linalg.pinv(covariance) 60 | mu = np.mean(bg, 1) 61 | z = proj @ pca_img[row, col, :].squeeze() - proj @ mu 62 | ssrx_img[row, col] = z.T @ sig_inv @ z 63 | 64 | return ssrx_img 65 | -------------------------------------------------------------------------------- /hsi_toolkit/dev/dim_reduction/__init__.py: -------------------------------------------------------------------------------- 1 | from hsi_toolkit.dev.dim_reduction.mnf import * 2 | -------------------------------------------------------------------------------- /hsi_toolkit/dev/dim_reduction/mnf.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | def mnf(in_img, eigval_retain = 1): 3 | """ 4 | Maximum Noise Fraction code 5 | 6 | Ref: Green, A. A., Berman, M., Switzer, P., & Craig, M. D. (1988). A transformation for ordering multispectral data in terms of image quality with implications for noise removal. IEEE Transactions on geoscience and remote sensing, 26(1), 65-74. 7 | 8 | Inputs: 9 | in_img: hyperspectral data cube (n_row x n_cols x n_bands) 10 | eigval_retain: percentage of eigenvalue to retain durig dimensionality reduction step. If 1, no reduction is done. 11 | 12 | Outputs: 13 | out_img: noise ordered and dimensionality reduced data 14 | 15 | Author: Alina Zare 16 | Email Address: azare@ufl.edu 17 | Created: 2008 18 | Latest Revision: June 3, 2018 19 | Python Implementation by Yutai Zhou on 12/2018 20 | """ 21 | # get the noise covariance 22 | # assumes neighbor pixels are essentially the same except for noise 23 | # use a simple mask of neighbor pixels to the right and below 24 | n_row, n_col, n_band = in_img.shape 25 | n_pixel = n_row * n_col 26 | 27 | hsi_data = in_img.reshape((n_pixel, n_band), order='F').T 28 | 29 | mu = np.mean(hsi_data,1)[:,np.newaxis] 30 | running_cov = np.zeros((n_band, n_band)) 31 | 32 | for i in range(n_col - 1): 33 | for j in range(n_row - 1): 34 | 35 | diff1 = (in_img[j,i+1,:] - in_img[j,i,:])[:,np.newaxis] 36 | diff2 = (in_img[j+1,i,:] - in_img[j,i,:])[np.newaxis,:] 37 | running_cov = running_cov + diff1 @ diff1.T + diff2 @ diff2.T 38 | 39 | noise_cov = 1 / (2 * (n_row - 1) * (n_col - 1) - 1) * running_cov 40 | U_noise, S_noise, _ = np.linalg.svd(noise_cov) 41 | S_noise = np.diag(S_noise) 42 | 43 | # align and whiten noise 44 | hsi_prime = np.linalg.pinv(np.sqrt(S_noise)) @ U_noise @ (hsi_data - mu) 45 | 46 | # PCA the noise whitened data 47 | U, S, _ = np.linalg.svd(np.cov(hsi_prime.T, rowvar=False)) 48 | 49 | hsi_mnf = U @ hsi_prime 50 | 51 | out_img = hsi_mnf.T.reshape((n_row, n_col, n_band), order = 'F') 52 | 53 | A = U @ np.linalg.pinv(np.sqrt(S_noise)) @ U_noise 54 | 55 | n_dim = n_band 56 | if eigval_retain < 1: 57 | pcts = np.cumsum(S) / np.sum(S) 58 | cut_ind = np.where(pcts >= eigval_retain)[0] 59 | 60 | out_img = out_img[:,:,:cut_ind[0]+1] 61 | n_dim = cut_ind[0]+1 62 | # mu = mu[:cut_ind[0]+1,0][:,np.newaxis] 63 | # A = A[:,:cut_ind[0]+1] 64 | A = A[:cut_ind[0]+1,:] 65 | 66 | eig_vals = np.diag(S[:n_dim]) 67 | 68 | # print(n_dim,mu.shape, A.shape) 69 | return out_img, n_dim, A, eig_vals, mu 70 | -------------------------------------------------------------------------------- /hsi_toolkit/dev/signature_detectors/__init__.py: -------------------------------------------------------------------------------- 1 | from hsi_toolkit.dev.signature_detectors.ftmf_detector import * 2 | from hsi_toolkit.dev.signature_detectors.mtmf_statistic import * 3 | from hsi_toolkit.dev.signature_detectors.qmf_detector import * 4 | from hsi_toolkit.dev.signature_detectors.spsmf_detector import * 5 | -------------------------------------------------------------------------------- /hsi_toolkit/dev/signature_detectors/ftmf_detector.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | 3 | def ftmf_detector(hsi_img, tgt_sig, gamma = 1): 4 | """ 5 | Finite Target Matched Filter 6 | 7 | Inputs: 8 | hsi_image - n_row x n_col x n_band hyperspectral image 9 | tgt_sig - target signature (n_band x 1 - column vector) 10 | gamma - scale factor of background variance to model target variance (V_t = gamma^2*V_bg) 11 | 12 | Outputs: 13 | ftmf_out - detector image 14 | 15 | 8/12/2012 - Taylor C. Glenn 16 | 12/2018 - Python Implementation by Yutai Zhou 17 | 18 | makes the simplifying assumption that target variance 19 | is a scaled version of bg variance 20 | Eismann pp 681 21 | """ 22 | if tgt_sig.ndim == 1: 23 | tgt_sig = tgt_sig[:, np.newaxis] 24 | 25 | n_row, n_col, n_band = hsi_img.shape 26 | n_pixel = n_row * n_col 27 | 28 | hsi_data = hsi_img.reshape((n_pixel,n_band), order='F').T 29 | 30 | mu = np.mean(hsi_data,1) 31 | mu = mu[:,np.newaxis] 32 | sigma = np.cov(hsi_data.T, rowvar=False) 33 | sig_inv = np.linalg.pinv(sigma) 34 | 35 | s = tgt_sig - mu 36 | z = hsi_data - mu 37 | f = s.T @ sig_inv 38 | 39 | #signal to cluster ratio 40 | scr = s.T @ sig_inv @ s 41 | 42 | ftmf_data = np.zeros(n_pixel) 43 | g2p1 = gamma ** 2 + 1 44 | for i in range(n_pixel): 45 | md = z[:,i] @ sig_inv @ z[:,i] # mahalanobis distance 46 | mf = f @ z[:,i] # matched filter 47 | A = n_band * g2p1 ** 2 48 | B = (mf - 3 * n_band) * g2p1 ** 2 - scr 49 | C = -md * g2p1 + n_band * gamma ** 2 + 3 * n_band + scr 50 | D = -n_band - mf + md 51 | 52 | r = np.real(np.roots([A, B, C, D])) 53 | r_ind = np.where(np.bitwise_and(r>=0, r<=1))[0] 54 | if r_ind.shape[0] == 0: 55 | alpha = 1 56 | else: 57 | alpha = r[r_ind[0]] 58 | mu_a = alpha * s + (1 - alpha) * mu 59 | sigma_a = (alpha ** 2 * gamma ** 2 + (1 - alpha)**2) * sigma 60 | sig_inv_a = np.linalg.pinv(sigma_a) 61 | 62 | x_mu_a = hsi_data[:,i][:,np.newaxis] - mu_a 63 | 64 | ftmf_data[i] = (md - x_mu_a.T @ sig_inv_a @ x_mu_a - np.linalg.slogdet(sigma_a)[1]).squeeze() 65 | return ftmf_data.reshape((n_row, n_col), order='F') 66 | -------------------------------------------------------------------------------- /hsi_toolkit/dev/signature_detectors/mtmf_statistic.py: -------------------------------------------------------------------------------- 1 | from hsi_toolkit.util import img_det 2 | from hsi_toolkit.dev.dim_reduction import mnf 3 | import numpy as np 4 | 5 | def mtmf_statistic(hsi_img,tgt_sig, mask = None): 6 | """ 7 | Mixture Tuned Matched Filter Infeasibility Statistic 8 | 9 | Inputs: 10 | hsi_image - n_row x n_col x n_band hyperspectral image 11 | tgt_sig - target signature (n_band x 1 - column vector) 12 | mask - binary image limiting detector operation to pixels where mask is true 13 | if not present or empty, no mask restrictions are used 14 | 15 | Outputs: 16 | mtmf_out - MTMF infeasibility statistic 17 | alpha - matched filter output 18 | 19 | 8/12/2012 - Taylor C. Glenn - tcg@cise.ufl.edu 20 | 12/2018 - Python Implementation by Yutai Zhou 21 | """ 22 | if tgt_sig.ndim == 1: 23 | tgt_sig = tgt_sig[:, np.newaxis] 24 | 25 | mnf_img, n_dim, mnf_vecs, mnf_eigvals, mnf_mu = mnf(hsi_img,1); 26 | # tgt_sig = tgt_sig[:n_dim,0][:,np.newaxis] 27 | s = mnf_vecs @ (tgt_sig - mnf_mu) 28 | 29 | mtmf_out, kwargsout = img_det(mtmf_helper, mnf_img, s, mnf_eigvals = mnf_eigvals) 30 | 31 | return mtmf_out, kwargsout['alpha'] 32 | 33 | def mtmf_helper(hsi_data, tgt_sig, kwargs): 34 | mnf_eigvals = kwargs['mnf_eigvals'] 35 | n_band, n_pixel = hsi_data.shape 36 | 37 | z = hsi_data 38 | s = tgt_sig 39 | sts = s.T @ s 40 | 41 | alpha = np.zeros(n_pixel) 42 | mtmf_data = np.zeros(n_pixel) 43 | ev = np.sqrt(mnf_eigvals) 44 | one = np.ones(n_band) 45 | 46 | for i in range(n_pixel): 47 | # print(s.shape,z.shape, z[:,i].shape) 48 | a = (s.T @ z[:,i][:,np.newaxis] / sts).squeeze() 49 | alpha[i] = np.max((0, np.min((1, a)))) 50 | # print(alpha[i]) 51 | sig_inv = 1 / ((ev * (1 - alpha[i]) - one) ** 2) 52 | 53 | mtmf_data[i] = z[:,i][np.newaxis,:] @ sig_inv @ z[:,i][:,np.newaxis] 54 | 55 | return mtmf_data, {'alpha': alpha} 56 | -------------------------------------------------------------------------------- /hsi_toolkit/dev/signature_detectors/qmf_detector.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | def qmf_detector(hsi_img, tgt_sig, tgt_cov): 3 | """ 4 | Quadratic Spectral Matched Filter 5 | 6 | Inputs: 7 | hsi_image - n_row x n_col x n_band hyperspectral image 8 | tgt_sig - mean target signature (n_band x 1 - column vector) 9 | tgt_cov - covariance matrix for target 10 | 11 | Outputs: 12 | qmf_out - detector image 13 | 14 | 8/9/2012 - Taylor C. Glenn 15 | 10/2018 - Python Implementation by Yutai Zhou 16 | """ 17 | if tgt_sig.ndim == 1: 18 | tgt_sig = tgt_sig[:, np.newaxis] 19 | 20 | n_row, n_col, n_band = hsi_img.shape 21 | n_pixel = n_row * n_col 22 | 23 | hsi_data = hsi_img.reshape((n_pixel, n_band), order ='F').T 24 | 25 | #get an estimate of the noise covariance of the image 26 | running_cov = np.zeros((n_band, n_band)) 27 | for i in range(n_col - 1): 28 | for j in range(n_row - 1): 29 | 30 | diff1 = (hsi_img[j,i+1,:] - hsi_img[j,i,:])[:,np.newaxis] 31 | diff2 = (hsi_img[j+1,i,:] - hsi_img[j,i,:])[np.newaxis,:] 32 | running_cov = running_cov + diff1 @ diff1.T + diff2 @ diff2.T 33 | 34 | noise_cov = running_cov / (2 * n_pixel - 1) 35 | # precompute other stats 36 | mu = np.mean(hsi_data,1) 37 | sigma = np.cov(hsi_data.T, rowvar=False) 38 | 39 | sig_inv_bn = np.linalg.pinv(sigma + noise_cov) 40 | sig_inv_sn = np.linalg.pinv(tgt_cov + noise_cov) 41 | 42 | z = hsi_data - mu[:, np.newaxis] 43 | w = hsi_data - tgt_sig 44 | 45 | # run the filter 46 | qmf_data = np.zeros(n_pixel) 47 | 48 | for i in range(n_pixel): 49 | qmf_data[i] = z[:,i].T @ sig_inv_bn @ z[:,i] - w[:,i].T @ sig_inv_sn @ w[:,i] + np.log(np.linalg.det(sigma + noise_cov)/np.linalg.det(tgt_cov + noise_cov)) 50 | 51 | qmf_out = qmf_data.reshape((n_row, n_col), order = 'F') 52 | 53 | return qmf_out 54 | -------------------------------------------------------------------------------- /hsi_toolkit/dev/signature_detectors/spsmf_detector.py: -------------------------------------------------------------------------------- 1 | from hsi_toolkit.util import img_det 2 | import numpy as np 3 | 4 | def spsmf_detector(hsi_img, tgt_sig, mask = None, mu = None, sig_inv = None): 5 | """ 6 | Subpixel Spectral Matched Filter 7 | matched filter derived from a subpixel mixing model 8 | H0: x = b 9 | H1: x = alpha*s + beta*b 10 | Ref: formulation from Eismann's book, pp 664 11 | 12 | Inputs: 13 | hsi_image - n_row x n_col x n_band hyperspectral image 14 | tgt_sig - target signature (n_band x 1 - column vector) 15 | mask - binary image limiting detector operation to pixels where mask is true 16 | if not present or empty, no mask restrictions are used 17 | mu - background mean (n_band x 1 column vector) 18 | siginv - background inverse covariance (n_band x n_band matrix) 19 | 20 | Outputs: 21 | spsmf_out - detector image 22 | 23 | 8/8/2012 - Taylor C. Glenn 24 | 6/2/2018 - Edited by Alina Zare 25 | 12/2018 - Python Implementation by Yutai Zhou 26 | """ 27 | if tgt_sig.ndim == 1: 28 | tgt_sig = tgt_sig[:, np.newaxis] 29 | 30 | spsmf_out, kwargsout = img_det(spsmf_helper, hsi_img, tgt_sig, mask, mu = mu, sig_inv = sig_inv) 31 | 32 | return spsmf_out 33 | 34 | def spsmf_helper(hsi_data, tgt_sig, kwargs): 35 | mu = np.mean(hsi_data, axis = 1) if kwargs['mu'] is None else kwargs['mu'] 36 | mu = mu[:, np.newaxis] 37 | sig_inv = np.linalg.pinv(np.cov(hsi_data.T, rowvar = False)) if kwargs['sig_inv'] is None else kwargs['sig_inv'] 38 | 39 | n_band, n_pixel = hsi_data.shape 40 | s = tgt_sig # 72 x 1 41 | st_sig_inv = s.T @ sig_inv # 1 x 72 42 | st_sig_inv_s = s.T @ sig_inv @ s # 1 x 1 43 | K = n_band 44 | 45 | spsmf_data = np.zeros(n_pixel) 46 | 47 | for i in range(n_pixel): 48 | x = hsi_data[:,i][:, np.newaxis] # 72x1 49 | st_sig_inv_x = st_sig_inv @ x # 1 x 1 50 | a0 = (x.T @ sig_inv @ x) * st_sig_inv_s - st_sig_inv_x ** 2 51 | a1 = st_sig_inv_x * (s.T @ sig_inv @ mu) - st_sig_inv_s * (mu.T @ sig_inv @ x) 52 | a2 = -K * st_sig_inv_s 53 | 54 | beta = (-a1 + np.sqrt(a1 ** 2 - 4 * a2 * a0)) / (2 * a2) 55 | alpha = st_sig_inv @ (x - beta * mu) / st_sig_inv_s 56 | z1 = x - mu 57 | z2 = x - alpha * s - beta * mu 58 | 59 | spsmf_data[i] = z1.T @ sig_inv @ z1 - (z2.T @ sig_inv @ z2) / (beta ** 2) - 2 * K * np.log(np.abs(beta)) 60 | 61 | return spsmf_data, {} 62 | -------------------------------------------------------------------------------- /hsi_toolkit/dim_reduction/README.md: -------------------------------------------------------------------------------- 1 | # Gatorsense hsitoolkit dimenstionality reduction methods Python version 2 | hsitoolkit/dim_reduction 3 | 4 | Suite of dimensionality reduction methods implemented so far: 5 | -hierarchicalDimensionalityReduction: Dimensionality reduction by averaging wavelengths with similar distribution of pixel values based on KL-divergence and hierarchical clustering 6 | 7 | Suite of dimensionality reduction methods in progress: 8 | -MNF: maximum noise fraction (whitening work, reduction may not?), also need to edit comments on output 9 | 10 | Contact: Alina Zare, azare@ufl.edu 11 | -------------------------------------------------------------------------------- /hsi_toolkit/dim_reduction/__init__.py: -------------------------------------------------------------------------------- 1 | from hsi_toolkit.dim_reduction.hdr import * 2 | -------------------------------------------------------------------------------- /hsi_toolkit/dim_reduction/hdr.py: -------------------------------------------------------------------------------- 1 | 2 | """ 3 | function [dimReductionStruct] = dimReduction(img, Parameters) 4 | 5 | Hierarchical Dimensionality Reduction 6 | Computes KL-divergence between every pair of bands in image and merges 7 | bands that are similar based on KL-divergence. 8 | 9 | Inputs: 10 | img: hyperspectral data cube (n_row x n_col x n_bands) 11 | Parameters: parameter structure defined by dimReductionParameters.m 12 | 13 | Author: Alina Zare 14 | Email Address: azare@ufl.edu 15 | Created: September 12, 2008 16 | Latest Revision: October 15, 2018 17 | Translation to Python: Caleb Robey 18 | """ 19 | import numpy as np 20 | import matplotlib.pyplot as plt 21 | from scipy.spatial.distance import squareform, pdist 22 | import scipy.cluster.hierarchy as sch 23 | 24 | 25 | class dimReductionParameters(): 26 | def __init__(self): 27 | self.numBands = 7 # Reduced dimensionality Size 28 | self.type = 'complete' # Type of hierarchical clustering used 29 | self.showH = 0 # Set to 1 to show clustering, 0 otherwise 30 | self.NumCenters = 255 # Number of centers used in computing KL-divergence 31 | 32 | 33 | def dimReduction(img, Parameters=None): 34 | 35 | numRows, numCols, numDims = img.shape 36 | 37 | if Parameters is None: 38 | Parameters = dimReductionParameters() 39 | 40 | type = Parameters.type # Type of Hierarchy 41 | showH = Parameters.showH # Set to 1 to show clustering, 0 otherwise 42 | maxNumClusters = Parameters.numBands 43 | NumCenters = Parameters.NumCenters 44 | 45 | InputData = np.reshape(img, (numRows * numCols, numDims)) 46 | _, KLDivergencesList, _ = computeKLDivergencesBetweenBands(InputData, NumCenters); 47 | 48 | Hierarchy = sch.linkage(KLDivergencesList, type) 49 | 50 | band_clusters = sch.fcluster(Hierarchy, t=maxNumClusters, criterion='maxclust') 51 | if (showH): 52 | # 'mtica' gives matlab behavior 53 | D = sch.dendrogram(Hierarchy, 0, 'mtica') 54 | plt.show() 55 | 56 | mergedData = np.zeros((maxNumClusters, (numRows * numCols))) 57 | 58 | for i in range(1, maxNumClusters+1): 59 | mergedData[i-1, :] = np.mean(InputData[:, band_clusters == i], 1) 60 | 61 | mergedData = np.reshape(mergedData.T, (numRows, numCols, maxNumClusters)) 62 | return mergedData 63 | 64 | 65 | def computeKLDivergencesBetweenBands(InputData, NumCenters): 66 | 67 | DataList = InputData / InputData.max(1).max(0) 68 | 69 | # compute the histograms 70 | Centers = np.arange(1/(2*NumCenters), 1 + 1/NumCenters, 1/NumCenters) 71 | 72 | hists = np.zeros((NumCenters, DataList.shape[0])) 73 | 74 | for count in range(DataList.shape[0]): 75 | hists[:, count], t = np.histogram(DataList.T[:, count], Centers) 76 | 77 | # Add an epsilon term to the histograms 78 | hists = hists + np.spacing(1) 79 | 80 | # compute KL Divergence 81 | lim = InputData.shape[1] 82 | KLDivergences = np.zeros((lim, lim)) 83 | for i in range(DataList.shape[1]): 84 | for j in range(DataList.shape[1]): 85 | KLDivergences[i, j] = (hists[i, :] * np.log(hists[i, :] / hists[j, :])).sum() \ 86 | + (hists[j, :] * np.log(hists[j, :] / hists[i, :])).sum() 87 | 88 | temp = KLDivergences - np.diag(np.diag(KLDivergences)) 89 | KLDivergencesList = pdist(temp) 90 | 91 | return KLDivergences, KLDivergencesList, hists 92 | -------------------------------------------------------------------------------- /hsi_toolkit/endmember_extraction/VCA.py: -------------------------------------------------------------------------------- 1 | # [E, IdxOfE, Xpca] = VCA(X, M, r, v) 2 | # 3 | ############################################################################### 4 | # 5 | # A FUNCTION TO CALCULATE ENDMEMBERS USING Vertex Component Analysis (VCA) 6 | # REFERENCE: 7 | # José M. P. Nascimento and José M. B. Dias 8 | # "Vertex Component Analysis: A Fast Algorithm to Unmix Hyperspectral Data" 9 | # IEEE Trans. Geosci. Remote Sensing, 10 | # April, 2005 11 | ############################################################################### 12 | ### 13 | ### INPUTS: 14 | ### X: DATA MATRIX B WAVELENGTHS x N PIXELS 15 | ### M: NUMBER OF ENDMEMBERS TO ESTIMATE 16 | ### OUTPUTS: 17 | ### E: B x M MATRIX OF ESTIMATED ENDMEMBERS 18 | ### IdxOfEinX: INDICES OF ENDMEMBERS IN DATA X. 19 | ### (THE ENDMEMBERS ARE SELECTED FROM X) 20 | ### XM: X PROJECTED ONTO FIRST M COMPONENTS OF PCA 21 | # 22 | ### OPTIONAL INPUTS: 23 | ### 'SNR' r: ESTIMATED SIGNAL TO NOISE RATIO IN dB 24 | ### 'verbose' v: logical TOGGLE TO TURN DISPLAYS ON AND OFF 25 | ############################################################################### 26 | ### 27 | ### Authors: José Nascimento (zen@isel.pt) 28 | ### José Bioucas Dias (bioucas@lx.it.pt) 29 | ### Copyright (c) 30 | ### version: 2.1 (7-May-2004) 31 | ############################################################################### 32 | ### 33 | ### ORIGINALLY MODIFED BY Darth Gader OCTOBER 2018 34 | ### TRANSLATED TO PYTHON BY Ronald Fick November 2018 35 | ############################################################################### 36 | 37 | import numpy as np 38 | import numpy.matlib 39 | from scipy.sparse.linalg import svds 40 | import math 41 | import matplotlib.pyplot as plt 42 | 43 | def VCA(X, M=2, r=-1, verbose=True): 44 | ## 45 | ############################################# 46 | # Initializations 47 | ############################################# 48 | 49 | if(X.size == 0): 50 | raise ValueError('There is no data') 51 | else: 52 | B, N=X.shape 53 | 54 | if (M<0 or M>B or M!=int(M)): 55 | raise ValueError('ENDMEMBER parameter must be integer between 1 and B') 56 | 57 | ## 58 | #################### 59 | ### ESTIMATE SNR ### 60 | #################### 61 | if(r==-1): 62 | ############################################################ 63 | ### THE USER DID NOT INPUT AN SNR SO SNR IS CALCULATED HERE 64 | ############################################################ 65 | 66 | ############################# 67 | ### CALCULATE PCA AND PCT ### 68 | MuX = np.mean(X, axis=1) #axis 1 is the second dimension 69 | MuX.shape = (MuX.size,1) 70 | Xz = X - np.matlib.repmat(MuX, 1, N) 71 | SigmaX = np.cov(X) 72 | U,S,V = np.linalg.svd(SigmaX) 73 | Xpca = np.matmul(np.transpose(U[:,0:M]), Xz) 74 | ProjComputed = True 75 | 76 | ### ESTIMATE SIGNAL TO NOISE ### 77 | SNR = EstSNR(X,MuX,Xpca) 78 | 79 | ### PRINT SNR ### 80 | if verbose: 81 | print('Estimated SNR = %g[dB]'%SNR) 82 | else: 83 | ############################################################ 84 | ### THE USER DID INPUT AN SNR SO NO SNR CALCUATION NEEDED 85 | ############################################################ 86 | 87 | ProjComputed = False 88 | if verbose: 89 | print('Input SNR = %g[dB]'%SNR) 90 | 91 | ### SET THRESHOLD TO DETERMINE IF NOISE LEVEL IS LOW OR HIGH ### 92 | SNRThresh = 15 + 10*math.log10(M) 93 | 94 | if verbose: 95 | print('SNRThresh= %f SNR= %f Difference= %f'%(SNR,SNRThresh,SNRThresh-SNR)) 96 | 97 | ########################### 98 | ### END OF ESTIMATE SNR ### 99 | ########################### 100 | ## 101 | ################################################################## 102 | ### PROJECTION. ### 103 | ### SELECT AND CALCULATE PROJECTION ONTO M DIMS ### 104 | ### ### 105 | ### IF SNR IS LOW,IT IS ASSUMED THAT THERE IS STILL NOISE IN ### 106 | ### THE SIGNAL SO REDUCE DIM A BIT MORE. ### 107 | ### ADD A CONSTANT VECTOR TO KEEP IN DIM M. ### 108 | ### ### 109 | ### IF SNR IS HIGH, PROJECT TO SIMPLEX IN DIM M-1 TO REDUCE ### 110 | ### EFFECTS OF VARIABLE ILLUMINATION ### 111 | ################################################################## 112 | 113 | if(SNR < SNRThresh): 114 | ########################## 115 | ### BEGIN LOW SNR CASE ### 116 | ########################## 117 | 118 | ### PRINT MESSAGE ### 119 | if verbose: 120 | print('Low SNR so Project onto Dimension M-1.') 121 | 122 | ### REDUCE SIZE OF PCT MATRIX TO M-1 ### 123 | Dim = M-1 124 | MuX = np.mean(X, axis=1) 125 | MuX.shape = (MuX.size, 1) 126 | BigMuX = np.matlib.repmat(MuX, 1, N) 127 | if ProjComputed: 128 | U= U[:,0:Dim] 129 | else: 130 | Xz = X - BigMuX 131 | SigmaX = np.cov(np.transpose(X)) 132 | U,S,V = np.linalg.svd(SigmaX) 133 | #U,S,V = np.linalg.svd(SigmaX, full_matrices=True) 134 | Xpca = np.matmul(np.transpose(U), Xz) 135 | 136 | ### REDUCE DIMENSIONALITY IN PCA DOMAIN ### 137 | XpcaReduced = Xpca[0:Dim,:] 138 | 139 | ### RECONSTRUCT X "WITHOUT NOISE" BY PROJECTING BACK TO ORIGINAL SPACE ### 140 | XNoiseFree = np.matmul(U, XpcaReduced) + BigMuX 141 | 142 | ### CONCATENATE CONSTANT VEC = MAX NORM OF ALL DATA POINTS ### 143 | BiggestNorm = np.sqrt(max(np.sum(np.square(XpcaReduced),1))) 144 | 145 | YpcaReduced = np.concatenate([XpcaReduced, BiggestNorm*np.ones((1,N))]) 146 | ######################## 147 | ### END LOW SNR CASE ### 148 | ######################## 149 | else: 150 | ########################### 151 | ### BEGIN HIGH SNR CASE ### 152 | ########################### 153 | if verbose: 154 | print('High SNR so project onto dimension M') 155 | 156 | ### CONTRUCT "PCA-LIKE" MATRIX BY DIAGONALIZING CORRELATION MATRIX ### 157 | ### IF SUBTRACT THE MEAN, THEN MUPCA WILL BE 0, SO NO MEAN SUBTRACTION ### 158 | # xb = a: solve b.T x.T = a.T instead 159 | U,S,V = np.linalg.svd(np.matmul(X,np.transpose(X))/N) 160 | 161 | ### CALC PCT WITHOUT SUBTRACTING MEAN AND THEN RECONSTRUCT ### 162 | XpcaReduced = np.matmul(U.T,X) 163 | 164 | ### RECONSTRUCT X VIA INVERSE XFORM ON REDUCED DIM PCA DATA ### 165 | ### XXX PDG NOT SURE IF THIS IS CORRECT IN ORIGINAL CODE OR HERE. ### 166 | ### I THINK THE MEAN SHOULD BE ADDED LIKE IN LOW SNR CASE ### 167 | XNoiseFree = np.matmul(U, XpcaReduced[0:B,:]) # again in dimension L (note that x_p has no null mean) 168 | 169 | ################################################################# 170 | ### CALCULATE NORMALIZED PROJECTION ### 171 | ### SEE LAST PARAGRAPH OF SECTION A AND FIGURE 4 IN VCA PAPER.### 172 | ### MEAN OF THE PROJECT DATA IS USED AS u FROM THAT SECTION ### 173 | Mupca = np.mean(XpcaReduced,1) 174 | Mupca.shape = (Mupca.size, 1) 175 | Denom = np.sum(np.multiply(XpcaReduced, np.tile(Mupca,[1, N]))) 176 | YpcaReduced = np.divide(XpcaReduced, np.tile(Denom, [B, 1])) 177 | 178 | ## 179 | ########################################################### 180 | # VCA ALGORITHM 181 | ########################################################### 182 | ### 183 | ### INITIALIZE ARRAY OF INDICES OF ENDMEMBERS, IdxOfE 184 | ### INITIALIZE MATRIX OF ENDMEMBERS IN PCA SPACE, Epca 185 | ### DO M TIMES 186 | ### PICK A RANDOM VECTOR w 187 | ### USE w TO FIND f IN NULL SPACE OF Epca 188 | ### PROJECT f ONTO ALL PCA DATA PTS 189 | ### FIND INDEX OF PCA DATA PT WITH MAX PROJECTION 190 | ### USE INDEX TO ADD PCA DATA PT TO Epca 191 | ### END DO 192 | ### 193 | ### USE INDICES TO SELECT ENDMEMBERS IN SPECTRAL SPACE 194 | ########################################################### 195 | 196 | VCAsize = YpcaReduced.shape[0] 197 | 198 | IdxOfE = np.zeros(VCAsize) 199 | IdxOfE = IdxOfE.astype(int) 200 | Epca = np.zeros((VCAsize,VCAsize)) 201 | Epca[VCAsize-1,0] = 1 202 | for m in range(0,VCAsize): 203 | w = np.random.rand(VCAsize,1) 204 | f = w - np.matmul(np.matmul(Epca,np.linalg.pinv(Epca)), w) 205 | f = f / np.sqrt(np.sum(np.square(f))) 206 | v = np.matmul(f.T,YpcaReduced) 207 | absV = np.abs(v) 208 | absV = np.squeeze(absV) 209 | IdxOfE[m] = np.argmax(absV) 210 | v_max = absV[IdxOfE[m]] 211 | Epca[:,m] = YpcaReduced[:,IdxOfE[m]] 212 | 213 | E = XNoiseFree[:,IdxOfE] 214 | 215 | return E[:,0:M], IdxOfE[0:M], Xpca 216 | ############################################# 217 | ### END OF VCA FUNCTION 218 | ############################################# 219 | ############################################# 220 | ## 221 | ############################################# 222 | ############################################# 223 | ### FUNCTION TO ESTIMATE SNR 224 | ############################################# 225 | 226 | def EstSNR(X,MuX,Xpca): 227 | ##################################################### 228 | ### THE ESTIMATED SNR IS EQUIVALENT TO THE RATIO OF 229 | ### POWER OF SIGNAL TO POWER OF NOISE 230 | ### BUT IS COMPUTED AS 231 | ### POWER OF (SIGNAL + NOISE) TO POWER OF NOISE 232 | ##################################################### 233 | B, N = X.shape 234 | M, N = Xpca.shape 235 | 236 | ### POWER OF SIGNAL + NOISE, SIGNAL, AND NOISE, RESP. ### 237 | Psn = np.sum(np.square(X.flatten()))/N 238 | Ps = np.sum(np.square(Xpca.flatten()))/N + np.matmul(np.transpose(MuX),MuX) 239 | Pn = Psn - Ps 240 | SNR = 10*np.log10(Ps/Pn) 241 | 242 | ### OLD SNR CODE AS IN VCA PAPER. NOT MUCH DIFFERENT ### 243 | ### BECAUSE (M/B) IS SMALL ### 244 | ### SNR = 10*log10( (Ps - M/B*Psn)/(Psn - Ps) ); 245 | ### fprintf('SNR= %8.4f RatSNR= %8.4f\n', SNR, RatSNR); 246 | 247 | return SNR 248 | ############### 249 | ### THE END ### 250 | ############### -------------------------------------------------------------------------------- /hsi_toolkit/endmember_extraction/__init__.py: -------------------------------------------------------------------------------- 1 | from hsi_toolkit.endmember_extraction.VCA import * 2 | import SPICE # allow users to call hsi_toolkit.endmember_extraction.SPICE.___ 3 | -------------------------------------------------------------------------------- /hsi_toolkit/signature_detectors/README.md: -------------------------------------------------------------------------------- 1 | # Gatorsense hsitoolkit signature detectors Python version 2 | Review and references for many of these signature detectors can be found in the literature review in Taylor Glenn's Ph.D. thesis: 3 | Glenn, Taylor C. Context-dependent detection in hyperspectral imagery. Diss. University of Florida, 2013. 4 | 5 | Contact: Alina Zare, azare@ufl.edu 6 | 7 | ## Inputs: 8 | Each function takes similar inputs starting with hyperspectral image, target signatures, an optional mask (if you want to exclude pixels from analysis). Some functions require other inputs such as mean or background covariance. 9 | 10 | ## Demo: 11 | This repo contains a demo (demo_signature_det.py) and hyperspectral image (an_hsi_img_for_tgt_det_demo.mat) to run all signature detectors that are available. The demo will calculate all the signature detectors and display them together for the user. 12 | 13 | ## Current suite of signature detectors: 14 | - abd_detector: Abundance of target signature when unmixed using target signature and background endmembers assuming the linear mixing model 15 | - ace_detector: Squared Adaptive Cosine/Coherence Estimator (Squared ACE), cosine of vector angle between target and pixel spectra after whitening based on background statistics, squared 16 | - ace_local_detector: Adaptive Cosine/Coherence Estimator where background statistics are estimated from local window 17 | - ace_ss_detector: Squared Adaptive Cosine/Coherence Estimator Subspace Formulation 18 | - ace_rt_detector: Adaptive Cosine/Coherence Estimator (ACE), cosine of vector angle between target and pixel spectra after whitening based on background statistics 19 | - ace_rt_max_detector: ACE given multiple target signatures. Confidence value for each pixel is max ACE score over all target signatures. 20 | - amsd_detector: Adaptive Matched Subspace Detector 21 | - ccmf_detector: Class Conditional Matched Filter, Segment data using a Gaussian Mixture Model and then apply SMF to each component 22 | - cem_detector: Constrained Energy Minimization Detector 23 | - ctmf_detector: Cluster Tuned Matched Filter 24 | - fam_statistic: False Alarm Mitigation Statistic 25 | - ha_detector: Hybrid Abundance Detector, unmix using background endmembers as well as using background and target endmembers, model proportions with a Gaussian mixture, compute pixel-wise likelihood ratios 26 | - hsd_detector: Likelihood ratio after unmixing with background and unmixing with background and target signature (Broadwater and Chellappa's method) 27 | - hsd_local_detector: Hybrid Structured Detector using local background estimation 28 | - hua_detector: Hybrid Unstructured Abundance Detector 29 | - osp_detector: Orthogonal Subspace Projection Detector 30 | - palm_detector: Pairwise Adaptive Linear Matched Filter 31 | - sam_detector: Spectral Angle Mapper, calculates vector angle between target signature and each pixel spectrum 32 | - smf_detector: Spectral Matched Filter, inner product between target and pixel spectra after whitening based on background statistics 33 | - smf_local_detector: Spectral Matched Filter using local background statistics 34 | - smf_max_detector: Spectral Matched Filter given multiple target signatures. Confidence value for each pixel is max SMF score over all target signatures. 35 | 36 | ## Suite of signature detectors under development: 37 | - ftmf_statistic: Finite Target Matched Filter 38 | - mtmf_statistic: Mixture Tuned Matched Filter Infeasibility Statistic 39 | - qmf_detector: Quadratic Spectral Matched Filter 40 | - spsmf_detector: Subpixel Spectral Matched Filter 41 | 42 | ## Suite of signature detectors to be implemented (these use Quadratic programming active set method): 43 | - hs_detector: Hybrid Subpixel Detector 44 | - hsa_detector: Hybrid Structured/Abundance Detector 45 | - hud_detector: Hybrid Unstructured Detector (Broadwater and Chellappa's method) 46 | 47 | ## Segmented Mode 48 | Signature detectors can also be ran in *Segmented* mode with util/img_seg.py as shown in the demo script. 49 | Segmented mode is where a detector is applied to segments of the imagery separately (i.e., background statistics computed from segment rather than full image). 50 | -------------------------------------------------------------------------------- /hsi_toolkit/signature_detectors/__init__.py: -------------------------------------------------------------------------------- 1 | from hsi_toolkit.signature_detectors.abd_detector import * 2 | from hsi_toolkit.signature_detectors.ace_detector import * 3 | from hsi_toolkit.signature_detectors.ace_local_detector import * 4 | from hsi_toolkit.signature_detectors.ace_rt_detector import * 5 | from hsi_toolkit.signature_detectors.ace_rt_max_detector import * 6 | from hsi_toolkit.signature_detectors.ace_ss_detector import * 7 | from hsi_toolkit.signature_detectors.amsd_detector import * 8 | from hsi_toolkit.signature_detectors.ccmf_detector import * 9 | from hsi_toolkit.signature_detectors.cem_detector import * 10 | from hsi_toolkit.signature_detectors.ctmf_detector import * 11 | from hsi_toolkit.signature_detectors.fam_statistic import * 12 | from hsi_toolkit.signature_detectors.ha_detector import * 13 | from hsi_toolkit.signature_detectors.hsd_detector import * 14 | from hsi_toolkit.signature_detectors.hsd_local_detector import * 15 | from hsi_toolkit.signature_detectors.hua_detector import * 16 | from hsi_toolkit.signature_detectors.osp_detector import * 17 | from hsi_toolkit.signature_detectors.palm_detector import * 18 | from hsi_toolkit.signature_detectors.sam_detector import * 19 | from hsi_toolkit.signature_detectors.smf_detector import * 20 | from hsi_toolkit.signature_detectors.smf_local_detector import * 21 | from hsi_toolkit.signature_detectors.smf_max_detector import * 22 | -------------------------------------------------------------------------------- /hsi_toolkit/signature_detectors/abd_detector.py: -------------------------------------------------------------------------------- 1 | from hsi_toolkit.util import img_det 2 | from hsi_toolkit.util import unmix 3 | import numpy as np 4 | 5 | def abd_detector(hsi_img, tgt_sig, ems, mask = None): 6 | """ 7 | Abundance of Target when unmixed with background endmembers 8 | 9 | Inputs: 10 | hsi_image - n_row x n_col x n_band hyperspectral image 11 | tgt_sig - target signature (n_band x 1 - column vector) 12 | mask - binary image limiting detector operation to pixels where mask is true 13 | if not present or empty, no mask restrictions are used 14 | ems - background endmembers 15 | 16 | Outputs: 17 | abd_out - detector image 18 | 19 | 8/19/2012 - Taylor C. Glenn 20 | 6/2/2018 - Edited by Alina Zare 21 | 12/2018 - Python Implementation by Yutai Zhou 22 | """ 23 | if tgt_sig.ndim == 1: 24 | tgt_sig = tgt_sig[:, np.newaxis] 25 | 26 | abd_out, kwargsout = img_det(abd_helper,hsi_img,tgt_sig,mask,ems = ems); 27 | 28 | return abd_out 29 | 30 | def abd_helper(hsi_data, tgt_sig, kwargs): 31 | ems = kwargs['ems'] 32 | # unmix data with target signature and background 33 | targ_P = unmix(hsi_data, np.hstack((tgt_sig, ems))) 34 | 35 | abd_data = targ_P[:,0] 36 | 37 | return abd_data, {} 38 | -------------------------------------------------------------------------------- /hsi_toolkit/signature_detectors/ace_detector.py: -------------------------------------------------------------------------------- 1 | from hsi_toolkit.util import img_det 2 | import numpy as np 3 | 4 | def ace_detector(hsi_img, tgt_sig, mask = None, mu = None, sig_inv = None): 5 | """ 6 | Squared Adaptive Cosine/Coherence Estimator 7 | 8 | Inputs: 9 | hsi_image - n_row x n_col x n_band hyperspectral image 10 | tgt_sig - target signature (n_band x 1 - column vector) 11 | mask - binary image limiting detector operation to pixels where mask is true 12 | if not present or empty, no mask restrictions are used 13 | mu - background mean (n_band x 1 column vector) 14 | sig_inv - background inverse covariance (n_band x n_band matrix) 15 | 16 | Outputs: 17 | ace_out - detector image 18 | mu - mean of input data 19 | sig_inv - inverse covariance of input data 20 | 21 | 8/8/2012 - Taylor C. Glenn 22 | 6/2/2018 - Edited by Alina Zare 23 | 10/2018 - Python Implementation by Yutai Zhou 24 | """ 25 | if tgt_sig.ndim == 1: 26 | tgt_sig = tgt_sig[:, np.newaxis] 27 | 28 | ace_out, kwargsout = img_det(ace_det_helper, hsi_img, tgt_sig, mask, mu = mu, sig_inv = sig_inv) 29 | return ace_out, kwargsout['mu'], kwargsout['sig_inv'] 30 | 31 | def ace_det_helper(hsi_data, tgt_sig, kwargs): 32 | mu = np.mean(hsi_data, axis = 1) if kwargs['mu'] is None else kwargs['mu'] 33 | sig_inv = np.linalg.pinv(np.cov(hsi_data.T, rowvar = False)) if kwargs['sig_inv'] is None else kwargs['sig_inv'] 34 | 35 | mu = np.reshape(mu, (len(mu), 1), order='F') 36 | s = tgt_sig - mu 37 | z = hsi_data - mu 38 | 39 | st_sig_inv = s.T @ sig_inv 40 | st_sig_inv_s = s.T @ sig_inv @ s 41 | 42 | A = np.sum(st_sig_inv @ z, 0) 43 | B = st_sig_inv_s 44 | C = np.sum(z * (sig_inv @ z), 0) 45 | 46 | ace_data = A * A / (B * C) 47 | 48 | return ace_data.T.squeeze(), {'mu':mu, 'sig_inv': sig_inv} 49 | -------------------------------------------------------------------------------- /hsi_toolkit/signature_detectors/ace_local_detector.py: -------------------------------------------------------------------------------- 1 | from hsi_toolkit.util import rx_det 2 | import numpy as np 3 | 4 | def ace_local_detector(hsi_img, tgt_sig, mask = None, guard_win = 2, bg_win = 4, beta = 0): 5 | """ 6 | Adaptive Cosine/Coherence Estimator with RX style local background estimation 7 | 8 | Inputs: 9 | hsi_image - n_row x n_col x n_band hyperspectral image 10 | tgt_sig - target signature (n_band x 1 - column vector) 11 | mask - binary image limiting detector operation to pixels where mask is true 12 | if not present or empty, no mask restrictions are used 13 | guard_win - guard window radius (square,symmetric about pixel of interest) 14 | bg_win - background window radius 15 | beta - scalar value used to diagonal load covariance 16 | 17 | Outputs: 18 | out - detector image 19 | 20 | 10/25/2012 - Taylor C. Glenn 21 | 6/2/2018 - Edited by Alina Zare 22 | 10/2018 - Python Implementation by Yutai Zhou 23 | """ 24 | n_row, n_col, n_band = hsi_img.shape 25 | mask = np.ones([n_row, n_col]) if mask is None else mask 26 | reg = beta * np.eye(n_band) 27 | 28 | if tgt_sig.ndim == 1: 29 | tgt_sig = tgt_sig[:, np.newaxis] 30 | 31 | 32 | out, kwargsout = rx_det(ace_local_helper, hsi_img, tgt_sig, mask = mask, guard_win = guard_win, bg_win = bg_win, reg = reg) 33 | return out, kwargsout 34 | 35 | def ace_local_helper(x, ind, bg, b_mask_list, args, kwargs): 36 | if bg is None: 37 | sig_inv = args['global_sig_inv'] 38 | mu = args['mu'] 39 | else: 40 | sig_inv = np.linalg.pinv(np.cov(bg.T, rowvar = False) + kwargs['reg']) 41 | mu = np.mean(bg, 1) 42 | 43 | z = x - mu 44 | s = args['tgt_sig'] - np.tile(mu, (1, args['n_sig'])).T 45 | 46 | sig_out = np.zeros(args['n_sig']) 47 | 48 | for k in range(args['n_sig']): 49 | st_sig_inv = s[:,k].T @ sig_inv 50 | st_sig_inv_s = s[:,k].T @ sig_inv @ s[:,k] 51 | sig_out[k] = ((st_sig_inv @ z) ** 2) / (st_sig_inv_s * (z.T @ sig_inv @ z)) 52 | 53 | sig_index = np.argmax(sig_out, 0) 54 | 55 | return sig_out[sig_index], {'sig_index': sig_index} 56 | -------------------------------------------------------------------------------- /hsi_toolkit/signature_detectors/ace_rt_detector.py: -------------------------------------------------------------------------------- 1 | from hsi_toolkit.util import img_det 2 | import numpy as np 3 | 4 | def ace_rt_detector(hsi_img, tgt_sig, mask = None, mu = None, sig_inv = None): 5 | """ 6 | Adaptive Cosine/Coherence Estimator 7 | 8 | Inputs: 9 | hsi_image - n_row x n_col x n_band hyperspectral image 10 | tgt_sig - target signature (n_band x 1 - column vector) 11 | mask - binary image limiting detector operation to pixels where mask is true 12 | if not present or empty, no mask restrictions are used 13 | mu - background mean (n_band x 1 column vector) 14 | siginv - background inverse covariance (n_band x n_band matrix) 15 | 16 | Outputs: 17 | ace_out - detector image 18 | mu - mean of background 19 | siginv - inverse covariance of background 20 | 21 | 8/8/2012 - Taylor C. Glenn 22 | 6/2/2018 - Edited by Alina Zare 23 | 11/2018 - Python Implementation by Yutai Zhou 24 | """ 25 | if tgt_sig.ndim == 1: 26 | tgt_sig = tgt_sig[:, np.newaxis] 27 | 28 | ace_rt_out, kwargsout = img_det(ace_rt_helper, hsi_img, tgt_sig, mask, mu = mu, sig_inv = sig_inv) 29 | return ace_rt_out, kwargsout['mu'], kwargsout['sig_inv'] 30 | 31 | def ace_rt_helper(hsi_data, tgt_sig, kwargs): 32 | mu = np.mean(hsi_data, axis = 1) if kwargs['mu'] is None else kwargs['mu'] 33 | sig_inv = np.linalg.pinv(np.cov(hsi_data.T, rowvar = False)) if kwargs['sig_inv'] is None else kwargs['sig_inv'] 34 | mu = mu[:, np.newaxis] 35 | s = tgt_sig - mu 36 | z = hsi_data - mu 37 | 38 | st_sig_inv = s.T @ sig_inv 39 | st_sig_inv_s = s.T @ sig_inv @ s 40 | 41 | A = np.sum(st_sig_inv @ z, 0) 42 | B = np.sqrt(st_sig_inv_s) 43 | C = np.sqrt(np.sum(z * (sig_inv @ z), 0)) 44 | 45 | ace_data = A / (B * C) 46 | 47 | return ace_data.T.squeeze(), {'mu':mu, 'sig_inv': sig_inv} 48 | -------------------------------------------------------------------------------- /hsi_toolkit/signature_detectors/ace_rt_max_detector.py: -------------------------------------------------------------------------------- 1 | from hsi_toolkit.util import img_det 2 | import numpy as np 3 | 4 | def ace_rt_max_detector(hsi_img, tgt_sig, mask = None, mu = None, sig_inv = None): 5 | """ 6 | Adaptive Cosine/Coherence Estimator given Multiple Target Signatures. 7 | Confidence value is the max ace score over all target signatures. 8 | 9 | Inputs: 10 | hsi_image - n_row x n_col x n_band hyperspectral image 11 | tgt_sig - target signature (n_band x n_sig - column vector) 12 | mask - binary image limiting detector operation to pixels where mask is true 13 | if not present or empty, no mask restrictions are used 14 | mu - background mean (n_band x 1 column vector) 15 | siginv - background inverse covariance (n_band x n_band matrix) 16 | 17 | Outputs: 18 | ace_out - detector image 19 | mu - mean of input data 20 | siginv - inverse covariance of input data 21 | 22 | 8/8/2012 - Taylor C. Glenn 23 | 6/2/2018 - Edited by Alina Zare 24 | 11/2018 - Python Implementation by Yutai Zhou 25 | """ 26 | if tgt_sig.ndim == 1: 27 | tgt_sig = tgt_sig[:, np.newaxis] 28 | 29 | ace_rt_max_out, kwargsout = img_det(ace_rt_max_helper, hsi_img, tgt_sig, mask, mu = mu, sig_inv = sig_inv) 30 | return ace_rt_max_out, kwargsout['mu'], kwargsout['sig_inv'] 31 | 32 | def ace_rt_max_helper(hsi_data, tgt_sig, kwargs): 33 | mu = np.mean(hsi_data, axis = 1) if kwargs['mu'] is None else kwargs['mu'] 34 | sig_inv = np.linalg.pinv(np.cov(hsi_data.T, rowvar = False)) if kwargs['sig_inv'] is None else kwargs['sig_inv'] 35 | mu = mu[:, np.newaxis] 36 | 37 | n_sigs = tgt_sig.shape[1] 38 | n_pixel = hsi_data.shape[1] 39 | 40 | S = tgt_sig - mu 41 | z = hsi_data - mu 42 | det = np.zeros((n_sigs, n_pixel)) 43 | 44 | for i in range(n_sigs): 45 | s = S[:,i][:,np.newaxis] 46 | st_sig_inv = s.T @ sig_inv 47 | st_sig_inv_s = s.T @ sig_inv @ s 48 | 49 | A = np.sum(st_sig_inv @ z, 0) 50 | B = np.sqrt(st_sig_inv_s) 51 | C = np.sqrt(np.sum(z * (sig_inv @ z), 0)) 52 | 53 | det[i,:] = A / (B * C) 54 | 55 | ace_rt_data = np.max(det, 0) 56 | return ace_rt_data, {'mu':mu, 'sig_inv': sig_inv} 57 | -------------------------------------------------------------------------------- /hsi_toolkit/signature_detectors/ace_ss_detector.py: -------------------------------------------------------------------------------- 1 | from hsi_toolkit.util import img_det 2 | import numpy as np 3 | 4 | def ace_ss_detector(hsi_img, tgt_sig, mask = None, mu = None, sig_inv = None): 5 | """ 6 | Adaptive Cosine/Coherence Estimator - Subspace Formulation 7 | 8 | Inputs: 9 | hsi_image - n_row x n_col x n_band hyperspectral image 10 | tgt_sigs - target signatures (n_band x M - column vector) 11 | mask - binary image limiting detector operation to pixels where mask is true 12 | if not present or empty, no mask restrictions are used 13 | 14 | Outputs: 15 | ace_ss_out - detector image 16 | 17 | 8/8/2012 - Taylor C. Glenn 18 | 6/2/2018 - Edited by Alina Zare 19 | 11/2018 - Python Implementation by Yutai Zhou 20 | """ 21 | if tgt_sig.ndim == 1: 22 | tgt_sig = tgt_sig[:, np.newaxis] 23 | 24 | ace_ss_out, kwargsout = img_det(ace_ss_helper, hsi_img, tgt_sig, mask, mu = mu, sig_inv = sig_inv) 25 | return ace_ss_out 26 | 27 | def ace_ss_helper(hsi_data, tgt_sig, kwargs): 28 | mu = np.mean(hsi_data, axis = 1) if kwargs['mu'] is None else kwargs['mu'] 29 | mu = mu[:, np.newaxis] 30 | sig_inv = np.linalg.pinv(np.cov(hsi_data.T, rowvar = False)) if kwargs['sig_inv'] is None else kwargs['sig_inv'] 31 | S = tgt_sig - mu 32 | z = hsi_data - mu 33 | 34 | G = sig_inv @ S @ np.linalg.pinv(S.T @ sig_inv @ S) @ S.T @ sig_inv 35 | 36 | A = np.sum(z * (G @ z),0) 37 | B = np.sum(z * (sig_inv @ z),0) 38 | 39 | out = A / B 40 | 41 | return out, {} 42 | -------------------------------------------------------------------------------- /hsi_toolkit/signature_detectors/amsd_detector.py: -------------------------------------------------------------------------------- 1 | from hsi_toolkit.util import img_det 2 | import numpy as np 3 | 4 | def amsd_detector(hsi_img, tgt_sig, mask = None, n_dim_tgt = 1, n_dim_bg = 5): 5 | """ 6 | Adaptive Matched Subspace Detector 7 | 8 | Reference: 9 | Hyperspectral subpixel target detection using the linear mixing model (article) 10 | Manolakis, D. and Siracusa, C. and Shaw, G. 11 | Geoscience and Remote Sensing, IEEE Transactions on 12 | 2001 Volume 39 Number 7 Pages 1392 -1409 Month jul 13 | 14 | Inputs: 15 | hsi_image - n_row x n_col x n_band hyperspectral image 16 | tgt_sigs - target signature(s) (n_band x n_sig - column vectors) 17 | mask - binary image limiting detector operation to pixels where mask is true 18 | if not present or empty, no mask restrictions are used 19 | n_dim_tgt - number of dimensions to use for target subspace, 20 | if argument is 0, use the target sigs themselves 21 | n_dim_bg - number of dimensions to use for background subspace 22 | 23 | Outputs: 24 | amsd_out - detector image 25 | 26 | 8/22/2012 - Taylor C. Glenn 27 | 6/02/2018 - Edited by Alina Zare 28 | 12/2018 - Python Implementation by Yutai Zhou 29 | """ 30 | if tgt_sig.ndim == 1: 31 | tgt_sig = tgt_sig[:, np.newaxis] 32 | 33 | amsd_out, kwargsout = img_det(amsd_helper, hsi_img, tgt_sig, mask, n_dim_tgt = n_dim_tgt, n_dim_bg = n_dim_bg) 34 | return amsd_out 35 | 36 | def amsd_helper(hsi_data, tgt_sig, kwargs): 37 | n_dim_tgt = kwargs['n_dim_tgt'] 38 | n_dim_bg = kwargs['n_dim_bg'] 39 | 40 | n_band, n_pixel = hsi_data.shape 41 | n_sigs = tgt_sig.shape[1] 42 | 43 | # find target and background subspace 44 | corr_bg = np.zeros((n_band, n_band)) 45 | for i in range(n_pixel): 46 | corr_bg = corr_bg + hsi_data[:,i][:,np.newaxis] @ hsi_data[:,i][np.newaxis,:] 47 | 48 | corr_bg = corr_bg / n_pixel 49 | U_bg,_,_ = np.linalg.svd(corr_bg) 50 | 51 | S_bg = U_bg[:,:n_dim_bg] 52 | 53 | if n_dim_tgt > 0: 54 | corr_tgt = np.zeros((n_band, n_band)) 55 | for i in range(n_sigs): 56 | corr_tgt = corr_tgt + tgt_sig[:,i][:,np.newaxis] @ tgt_sig[:,i][np.newaxis,:] 57 | corr_tgt = corr_tgt / n_sigs 58 | U_t,_,_ = np.linalg.svd(corr_tgt) 59 | 60 | S_t = U_t[:,:n_dim_tgt] 61 | 62 | else: 63 | S_t = tgt_sig 64 | 65 | # find the projection matrices 66 | S = np.hstack((S_t,S_bg)) 67 | P_S = S @ np.linalg.pinv(S.T @ S) @ S.T 68 | 69 | P_b = S_bg @ np.linalg.pinv(S_bg.T @ S_bg) @ S_bg.T 70 | 71 | # find perpendicular subspaces 72 | P_perp_S = np.eye(n_band) - P_S 73 | P_perp_b = np.eye(n_band) - P_b 74 | 75 | PZ = P_perp_b - P_perp_S 76 | amsd_data = np.zeros(n_pixel) 77 | 78 | for i in range(n_pixel): 79 | x = hsi_data[:,i][:,np.newaxis] 80 | amsd_data[i] = (x.T @ PZ @ x) / (x.T @ P_perp_S @ x) 81 | 82 | return amsd_data, {} 83 | -------------------------------------------------------------------------------- /hsi_toolkit/signature_detectors/ccmf_detector.py: -------------------------------------------------------------------------------- 1 | from hsi_toolkit.util import img_det 2 | import numpy as np 3 | from sklearn.mixture import GaussianMixture 4 | 5 | def ccmf_detector(hsi_img, tgt_sig, mask = None, n_comp = 5, gmm = None): 6 | """ 7 | Class Conditional Matched Filters 8 | 9 | inputs: 10 | hsi_image - n_row x n_col x n_band hyperspectral image 11 | tgt_sig - target signature (n_band x 1 - column vector) 12 | mask - binary image limiting detector operation to pixels where mask is true 13 | if not present or empty, no mask restrictions are used 14 | n_comp - number of Gaussian components to use 15 | gmm - optional mixture model structure from previous training data 16 | 17 | outputs: 18 | ccmf_out - detector image 19 | gmm - mixture model learned from input image 20 | 21 | 8/8/2012 - Taylor C. Glenn 22 | 6/2/2018 - Edited by Alina Zare 23 | 12/2018 - Python Implementation by Yutai Zhou 24 | """ 25 | if tgt_sig.ndim == 1: 26 | tgt_sig = tgt_sig[:, np.newaxis] 27 | 28 | ccmf_out, kwargsout = img_det(ccmf_helper, hsi_img, tgt_sig, mask, n_comp = n_comp, gmm = gmm) 29 | 30 | return ccmf_out, kwargsout['gmm'] 31 | 32 | def ccmf_helper(hsi_data, tgt_sig, kwargs): 33 | n_comp = kwargs['n_comp'] 34 | gmm = kwargs['gmm'] 35 | 36 | if gmm is None: 37 | gmm = GaussianMixture(n_components = n_comp, max_iter = 1, init_params = 'random').fit(hsi_data.T) 38 | 39 | n_pixel = hsi_data.shape[1] 40 | 41 | # make a matched filter for each background class 42 | means = gmm.means_ 43 | covariances = gmm.covariances_ 44 | sig_inv = np.zeros(covariances.shape) 45 | filt = np.zeros(means.shape) 46 | 47 | for i in range(n_comp): 48 | sig_inv[i,:,:] = np.linalg.pinv(covariances[i,:,:]) 49 | 50 | s = tgt_sig - means[i,:][:,np.newaxis] 51 | filt[i,:] = s.T @ sig_inv[i,:,:] / np.sqrt(s.T @ sig_inv[i,:,:] @ s) 52 | 53 | # run the appropriate filter for class of each pixels 54 | idx = gmm.predict(hsi_data.T) 55 | 56 | ccmf_data = np.zeros(n_pixel) 57 | for i in range(n_pixel): 58 | ccmf_data[i] = filt[idx[i],:] @ (hsi_data[:,i] - means[idx[i],:]) 59 | 60 | return ccmf_data, {'gmm': gmm} 61 | -------------------------------------------------------------------------------- /hsi_toolkit/signature_detectors/cem_detector.py: -------------------------------------------------------------------------------- 1 | from hsi_toolkit.util import img_det 2 | import numpy as np 3 | 4 | def cem_detector(hsi_img, tgt_sig, mask = None): 5 | """ 6 | Constrained Energy Minimization Detector 7 | solution to filter with minimum energy projected into background space 8 | 9 | Ref: J. C. Harsanyi, ?Detection and classification of subpixel spectral signatures in hyperspectral image sequences,? Ph.D. dissertation, University of Maryland Baltimore County, 1993. 10 | 11 | Inputs: 12 | hsi_image - n_row x n_col x n_band hyperspectral image 13 | tgt_sigs - target signatures (n_band x n_sigs) 14 | mask - binary image limiting detector operation to pixels where mask is true 15 | if not present or empty, no mask restrictions are used 16 | 17 | Outputs: 18 | cem_out - detector image 19 | w - cem filter 20 | 21 | 8/8/2012 - Taylor C. Glenn 22 | 6/2/2018 - Edited by Alina Zare 23 | 12/2018 - Python Implementation by Yutai Zhou 24 | """ 25 | if tgt_sig.ndim == 1: 26 | tgt_sig = tgt_sig[:, np.newaxis] 27 | 28 | cem_out, kwargsout = img_det(cem_helper, hsi_img, tgt_sig, mask) 29 | 30 | return cem_out, kwargsout['w'] 31 | 32 | def cem_helper(hsi_data, tgt_sig, kwarg): 33 | n_pixel = hsi_data.shape[1] 34 | n_sigs = tgt_sig.shape[1] 35 | 36 | R = np.cov(hsi_data.T, rowvar=False) 37 | mu = np.mean(hsi_data,1) 38 | mu = mu[:,np.newaxis] 39 | 40 | z = hsi_data - mu 41 | M = tgt_sig - mu 42 | R_inv = np.linalg.pinv(R) 43 | 44 | w = R_inv @ M @ np.linalg.pinv(M.T @ R_inv @ M) * np.ones((n_sigs,1)) 45 | 46 | cem_data = w.T @ z 47 | return cem_data.squeeze(), {'w':w} 48 | -------------------------------------------------------------------------------- /hsi_toolkit/signature_detectors/ctmf_detector.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from sklearn.cluster import KMeans 3 | 4 | def ctmf_detector(hsi_img, tgt_sig, n_cluster = 2): 5 | """ 6 | Cluster Tuned Matched Filter 7 | k-means cluster all spectra, make a matched filter for each cluster 8 | 9 | Inputs: 10 | hsi_image - n_row x n_col x n_band hyperspectral image 11 | tgt_sig - target signature (n_band x 1 - column vector) 12 | n_cluster - number of clusters to use 13 | 14 | Outputs: 15 | ctmf_out - detector output image 16 | cluster_img - cluster label image 17 | 18 | 8/15/2012 - Taylor C. Glenn 19 | 6/02/2018 - Edited by Alina Zare 20 | 12/2018 - Python Implementation by Yutai Zhou 21 | """ 22 | if tgt_sig.ndim == 1: 23 | tgt_sig = tgt_sig[:, np.newaxis] 24 | 25 | n_row, n_col, n_band = hsi_img.shape 26 | n_pixel = n_row * n_col 27 | 28 | hsi_data = hsi_img.reshape((n_pixel,n_band), order='F').T 29 | 30 | # cluster the data 31 | idx = KMeans(n_clusters = n_cluster, n_init = 1, max_iter=100).fit(hsi_data.T).labels_ 32 | 33 | cluster_img = idx.reshape((n_row, n_col), order = 'F') 34 | 35 | # get cluster stats, create match filters 36 | mu = np.zeros((n_band,n_cluster)) 37 | sig_inv = np.zeros((n_band, n_band, n_cluster)) 38 | f = np.zeros((n_band,n_cluster)) 39 | 40 | for i in range(n_cluster): 41 | z = hsi_data[:, idx == i] 42 | 43 | mu[:,i] = np.mean(z,1) 44 | sig_inv[:,:,i] = np.linalg.pinv(np.cov(z.T, rowvar=False)) 45 | 46 | s = tgt_sig - mu[:,i][:,np.newaxis] 47 | f[:,i] = s.T @ sig_inv[:,:,i] / np.sqrt(s.T @ sig_inv[:,:,i] @ s) 48 | 49 | # compute matched filter output of each point 50 | ctmf_data = np.zeros(n_pixel) 51 | 52 | for i in range(n_pixel): 53 | z = hsi_data[:,i] - mu[:,idx[i]] 54 | ctmf_data[i] = f[:,idx[i]] @ z 55 | 56 | return ctmf_data.reshape([n_row, n_col], order='F'), cluster_img 57 | -------------------------------------------------------------------------------- /hsi_toolkit/signature_detectors/fam_statistic.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | def fam_statistic(hsi_img, tgt_sig, mu = None, sig_inv = None): 3 | """ 4 | False Alarm Mitigation Statistic from Subpixel Replacement Model 5 | 6 | Inputs: 7 | hsi_image - n_row x n_col x n_band hyperspectral image 8 | tgt_sig - target signature (n_band x 1 - column vector) 9 | mu - background mean (n_band x 1 column vector) 10 | siginv - background inverse covariance (n_band x n_band matrix) 11 | 12 | Outputs: 13 | fam_out - false alarm mitigation statistic 14 | 15 | 8/8/2012 - Taylor C. Glenn 16 | 6/2/2018 - Edited by Alina Zare 17 | 11/2018 - Python Implementation by Yutai Zhou 18 | """ 19 | if tgt_sig.ndim == 1: 20 | tgt_sig = tgt_sig[:, np.newaxis] 21 | 22 | # assume target variance same as background variance 23 | n_row, n_col, n_band = hsi_img.shape 24 | n_pixel = n_row * n_col 25 | 26 | hsi_data = hsi_img.reshape((n_pixel, n_band), order='F').T 27 | if mu is None: 28 | mu = np.mean(hsi_data, axis = 1) 29 | if sig_inv is None: 30 | sig_inv = np.linalg.pinv(np.cov(hsi_data.T, rowvar = False)) 31 | 32 | mu = mu[:, np.newaxis] 33 | s = tgt_sig 34 | sts = s.T @ s 35 | s_mu = s - mu 36 | 37 | z = hsi_data - mu 38 | fam_data = np.zeros(n_pixel) 39 | 40 | for i in range(n_pixel): 41 | alpha = s.T @ hsi_data[:,i] / sts 42 | w = z[:,i][:, np.newaxis] - alpha * s_mu 43 | fam_data[i] = w.T @ sig_inv @ w 44 | 45 | fam_out = fam_data.reshape((n_row, n_col), order = 'F') 46 | return fam_out 47 | -------------------------------------------------------------------------------- /hsi_toolkit/signature_detectors/ha_detector.py: -------------------------------------------------------------------------------- 1 | from hsi_toolkit.util import img_det 2 | from hsi_toolkit.util import unmix 3 | import numpy as np 4 | from sklearn.mixture import GaussianMixture 5 | 6 | def ha_detector(hsi_img, tgt_sig, ems, mask = None, n_comp = 2): 7 | """ 8 | Hybrid Abundance Detector 9 | 10 | Inputs: 11 | hsi_image - n_row x n_col x n_band hyperspectral image 12 | tgt_sig - target signature (n_band x 1 - column vector) 13 | mask - binary image limiting detector operation to pixels where mask is true 14 | if not present or empty, no mask restrictions are used 15 | ems - background endmembers 16 | n_comp - number of mixture components for abundance mixtures 17 | 18 | Outputs: 19 | ha_out - detector image 20 | 21 | 8/27/2012 - Taylor C. Glenn 22 | 6/2/2018 - Edited by Alina Zare 23 | 12/2018 - Python Implementation by Yutai Zhou 24 | """ 25 | if tgt_sig.ndim == 1: 26 | tgt_sig = tgt_sig[:, np.newaxis] 27 | 28 | ha_out, kwargsout = img_det(ha_helper, hsi_img, tgt_sig, mask, ems = ems, n_comp = n_comp) 29 | return ha_out 30 | 31 | def ha_helper(hsi_data, tgt_sig, kwargs): 32 | ems = kwargs['ems'] 33 | n_comp = kwargs['n_comp'] 34 | 35 | n_pixel = hsi_data.shape[1] 36 | 37 | # unmix data with only background endmembers 38 | P = unmix(hsi_data, ems) 39 | 40 | # unmix data with target signature as well 41 | targ_P = unmix(hsi_data, np.hstack((tgt_sig, ems))) 42 | 43 | gmm_bg = GaussianMixture(n_components = n_comp, max_iter = 1, init_params = 'random').fit(P) 44 | 45 | # compute mixture likelihood ratio of each pixel 46 | n_endmeber = ems.shape[1] 47 | ll_bg = np.log(np.max(gmm_bg.predict_proba(P),1)) / (n_endmeber * n_comp) 48 | ll_tgt = targ_P[:,0] > 0.05 49 | 50 | hs_data = np.zeros(n_pixel) 51 | 52 | for i in range(n_pixel): 53 | z = hsi_data[:,i] - ems @ P[i,:].T 54 | w = hsi_data[:,i] - np.hstack((tgt_sig, ems)) @ targ_P[i,:].T 55 | 56 | hs_data[i] = z[np.newaxis,:] @ z[:,np.newaxis] / (w[np.newaxis,:] @ w[:,np.newaxis]) 57 | 58 | ha_data = hs_data + ll_tgt - ll_bg 59 | return ha_data, {} 60 | -------------------------------------------------------------------------------- /hsi_toolkit/signature_detectors/hsd_detector.py: -------------------------------------------------------------------------------- 1 | from hsi_toolkit.util import img_det 2 | from hsi_toolkit.util import unmix 3 | import numpy as np 4 | 5 | def hsd_detector(hsi_img, tgt_sig, ems, mask = None, sig_inv = None): 6 | """ 7 | Hybrid Structured Detector 8 | 9 | Ref: 10 | Hybrid Detectors for Subpixel Targets 11 | Broadwater, J. and Chellappa, R. 12 | Pattern Analysis and Machine Intelligence, IEEE Transactions on 13 | 2007 Volume 29 Number 11 Pages 1891 -1903 Month nov. 14 | 15 | Inputs: 16 | hsi_image - n_row x n_col x n_band hyperspectral image 17 | tgt_sig - target signature (n_band x 1 - column vector) 18 | mask - binary image limiting detector operation to pixels where mask is true 19 | if not present or empty, no mask restrictions are used 20 | ems - background endmembers 21 | siginv - background inverse covariance (n_band x n_band matrix) 22 | 23 | Outputs: 24 | hsd_out - detector image 25 | tgt_p - target proportion in unmixing 26 | 27 | 8/19/2012 - Taylor C. Glenn 28 | 6/2/2018 - Edited by Alina Zare 29 | 12/2018 - Python Implementation by Yutai Zhou 30 | """ 31 | if tgt_sig.ndim == 1: 32 | tgt_sig = tgt_sig[:, np.newaxis] 33 | 34 | hsd_out, kwargsout = img_det(hsd_helper, hsi_img, tgt_sig, mask, ems = ems, sig_inv = sig_inv) 35 | return hsd_out, kwargsout['tgt_p'] 36 | 37 | def hsd_helper(hsi_data, tgt_sig, kwargs): 38 | ems = kwargs['ems'] 39 | sig_inv = np.linalg.pinv(np.cov(hsi_data.T, rowvar = False)) if kwargs['sig_inv'] is None else kwargs['sig_inv'] 40 | 41 | n_pixel = hsi_data.shape[1] 42 | 43 | # unmix data with only background endmembers 44 | P = unmix(hsi_data, ems) 45 | 46 | # unmix data with target signature as well 47 | targ_P = unmix(hsi_data, np.hstack((tgt_sig, ems))) 48 | 49 | hsd_data = np.zeros(n_pixel) 50 | 51 | for i in range(n_pixel): 52 | z = hsi_data[:,i] - ems @ P[i,:].T 53 | w = hsi_data[:,i] - np.hstack((tgt_sig, ems)) @ targ_P[i,:].T 54 | hsd_data[i] = z[np.newaxis,:] @ sig_inv @ z[:,np.newaxis] / (w[np.newaxis,:] @ sig_inv @ w[:,np.newaxis]) 55 | 56 | tgt_p = targ_P[:,:tgt_sig.shape[1]] 57 | return hsd_data, {'tgt_p': tgt_p.squeeze()} 58 | -------------------------------------------------------------------------------- /hsi_toolkit/signature_detectors/hsd_local_detector.py: -------------------------------------------------------------------------------- 1 | from hsi_toolkit.util import rx_det 2 | from hsi_toolkit.util import unmix 3 | import numpy as np 4 | 5 | def hsd_local_detector(hsi_img, tgt_sig, ems, mask = None, guard_win = 2, bg_win = 4, beta = 0): 6 | """ 7 | Hybrid Subpixel Detector with RX style local background estimation 8 | 9 | Inputs: 10 | hsi_image - n_row x n_col x n_band hyperspectral image 11 | tgt_sig - target signature (n_band x 1 - column vector) 12 | mask - binary image limiting detector operation to pixels where mask is true 13 | if not present or empty, no mask restrictions are used 14 | guard_win - guard window radius (square,symmetric about pixel of interest) 15 | bg_win - background window radius 16 | beta - scalar value used to diagonal load covariance 17 | 18 | Outputs: 19 | out - detector image 20 | 21 | 1/25/2013 - Taylor C. Glenn 22 | 6/3/2018 - Edited by Alina Zare 23 | 12/2018 - Python Implementation by Yutai Zhou 24 | """ 25 | if tgt_sig.ndim == 1: 26 | tgt_sig = tgt_sig[:, np.newaxis] 27 | 28 | n_row, n_col, n_band = hsi_img.shape 29 | hsi_data = hsi_img.reshape((n_row * n_col, n_band), order='F').T 30 | 31 | reg = beta * np.eye(n_band) 32 | 33 | # unmix data with only background endmembers 34 | P = unmix(hsi_data, ems) 35 | 36 | # unmix data with target signature as well 37 | targ_P = unmix(hsi_data, np.hstack((tgt_sig, ems))) 38 | 39 | out, kwargsout = rx_det(hsd_local_helper, hsi_img, tgt_sig, mask, guard_win, bg_win, ems = ems, reg = reg, P = P, targ_P = targ_P) 40 | return out 41 | 42 | def hsd_local_helper(x, ind, bg, b_mask_list, args, kwargs): 43 | if bg is not None: 44 | sigma = np.cov(bg.T, rowvar=False) 45 | sig_inv = np.linalg.pinv(sigma + kwargs['reg']) 46 | else: 47 | sig_inv = args['global_siginv'] 48 | 49 | z = x - kwargs['ems'] @ kwargs['P'][ind,:] 50 | w = x - np.hstack((args['tgt_sig'], kwargs['ems'])) @ kwargs['targ_P'][ind,:] 51 | r = (z[np.newaxis,:] @ sig_inv @ z[:,np.newaxis]) / (w[np.newaxis,:] @ sig_inv @ w[:,np.newaxis]) 52 | 53 | return r, {} 54 | -------------------------------------------------------------------------------- /hsi_toolkit/signature_detectors/hua_detector.py: -------------------------------------------------------------------------------- 1 | from hsi_toolkit.util import img_det 2 | from hsi_toolkit.util import unmix 3 | import numpy as np 4 | from sklearn.mixture import GaussianMixture 5 | 6 | def hua_detector(hsi_img, tgt_sig, ems, mask = None, n_comp = 2, sig_inv = None): 7 | """ 8 | Hybrid Unstructured Abundance Detector 9 | 10 | Ref: 11 | Hybrid Detectors for Subpixel Targets 12 | Broadwater, J. and Chellappa, R. 13 | Pattern Analysis and Machine Intelligence, IEEE Transactions on 14 | 2007 Volume 29 Number 11 Pages 1891 -1903 Month nov. 15 | 16 | Inputs: 17 | hsi_image - n_row x n_col x n_band hyperspectral image 18 | tgt_sig - target signature (n_band x 1 - column vector) 19 | mask - binary image limiting detector operation to pixels where mask is true 20 | if not present or empty, no mask restrictions are used 21 | ems - background endmembers 22 | siginv - background inverse covariance (n_band x n_band matrix) 23 | 24 | Outputs: 25 | hua_out - detector image 26 | 27 | 8/19/2012 - Taylor C. Glenn 28 | 6/3/2018 - Edited by Alina Zare 29 | 12/2018 - Python Implementation by Yutai Zhou 30 | """ 31 | if tgt_sig.ndim == 1: 32 | tgt_sig = tgt_sig[:, np.newaxis] 33 | 34 | hua_out, kwargsout = img_det(hua_helper, hsi_img, tgt_sig, mask, ems = ems, n_comp = n_comp, sig_inv = sig_inv) 35 | return hua_out 36 | 37 | def hua_helper(hsi_data, tgt_sig, kwargs): 38 | ems = kwargs['ems'] 39 | n_comp = kwargs['n_comp'] 40 | sig_inv = np.linalg.pinv(np.cov(hsi_data.T, rowvar = False)) if kwargs['sig_inv'] is None else kwargs['sig_inv'] 41 | 42 | n_pixel = hsi_data.shape[1] 43 | 44 | # unmix data with only background endmembers 45 | P = unmix(hsi_data, ems) 46 | 47 | # unmix data with target signature as well 48 | targ_P = unmix(hsi_data, np.hstack((tgt_sig, ems))) 49 | 50 | gmm_bg = GaussianMixture(n_components = n_comp, max_iter = 1, init_params = 'random').fit(P) 51 | 52 | # compute mixture likelihood ratio of each pixel 53 | n_endmeber = ems.shape[1] 54 | ll_bg = np.log(np.max(gmm_bg.predict_proba(P),1)) / (n_endmeber * n_comp) 55 | ll_tgt = targ_P[:,0] > 0.05 56 | 57 | hua_data = np.zeros(n_pixel) 58 | 59 | for i in range(n_pixel): 60 | s = tgt_sig * targ_P[i,0] 61 | x = hsi_data[:,i] 62 | hua_data[i] = x[np.newaxis,:] @ sig_inv @ s / (x[np.newaxis,:] @ sig_inv @ x[:,np.newaxis]) 63 | 64 | hud_rg = np.max(hua_data) - np.min(hua_data) 65 | bg_rg = np.max(ll_bg) - np.min(ll_bg) 66 | hua_data = hua_data + ll_tgt * (hud_rg/3) - ll_bg * (hud_rg/(3*bg_rg)) 67 | 68 | return hua_data, {} 69 | -------------------------------------------------------------------------------- /hsi_toolkit/signature_detectors/osp_detector.py: -------------------------------------------------------------------------------- 1 | from hsi_toolkit.util import pca 2 | from hsi_toolkit.util import img_det 3 | import numpy as np 4 | 5 | def osp_detector(hsi_img, tgt_sig, mask = None, n_dim_ss = 2): 6 | """ 7 | Orthogonal Subspace Projection Detector 8 | 9 | Inputs: 10 | hsi_image - n_row x n_col x n_band hyperspectral image 11 | tgt_sig - target signature (n_band x 1 - column vector) 12 | mask - binary image limiting detector operation to pixels where mask is true 13 | if not present or empty, no mask restrictions are used 14 | n_dim_ss - number of dimensions to use in the background subspace 15 | 16 | Outputs: 17 | osp_out - detector image 18 | 19 | 8/8/2012 - Taylor C. Glenn 20 | 6/2/2018 - Edited by Alina Zare 21 | 11/2018 - Python Implementation by Yutai Zhou 22 | """ 23 | if tgt_sig.ndim == 1: 24 | tgt_sig = tgt_sig[:, np.newaxis] 25 | 26 | osp_out, kwargsout = img_det(osp_helper, hsi_img, tgt_sig, mask, n_dim_ss = n_dim_ss) 27 | 28 | return osp_out 29 | 30 | def osp_helper(hsi_data, tgt_sig, kwargs): 31 | n_dim_ss = kwargs['n_dim_ss'] 32 | # see Eismann, pp670 33 | n_band, n_pixel = hsi_data.shape 34 | mu = np.mean(hsi_data, 1) 35 | mu = mu[:, np.newaxis] 36 | x = hsi_data - mu 37 | 38 | # get PCA rotation, no dim reduction 39 | _, _, evecs, _, _ = pca(hsi_data, 1) 40 | s = tgt_sig - mu 41 | 42 | # get a subspace that theoretically encompasses the background 43 | B = evecs[:, :n_dim_ss] 44 | 45 | PB = B @ np.linalg.pinv(B.T @ B) @ B.T 46 | PperpB = np.eye(n_band) - PB 47 | 48 | f = s.T @ PperpB 49 | 50 | osp_data = np.zeros(n_pixel) 51 | 52 | for i in range(n_pixel): 53 | osp_data[i] = f @ x[:,i] 54 | return osp_data, {} 55 | -------------------------------------------------------------------------------- /hsi_toolkit/signature_detectors/palm_detector.py: -------------------------------------------------------------------------------- 1 | from hsi_toolkit.util import img_det 2 | import numpy as np 3 | from sklearn.mixture import GaussianMixture 4 | 5 | def palm_detector(hsi_img, tgt_sig, mask = None, n_comp = 5): 6 | """ 7 | Pairwise Adaptive Linear Matched Filter 8 | 9 | Inputs: 10 | hsi_image - n_row x n_col x n_band hyperspectral image 11 | tgt_sig - target signature (n_band x 1 - column vector) 12 | mask - binary image limiting detector operation to pixels where mask is true 13 | if not present or empty, no mask restrictions are used 14 | n_comp - number of Gaussian components to use 15 | 16 | Outputs: 17 | palm_out - detector image 18 | 19 | 8/8/2012 - Taylor C. Glenn 20 | 6/2/2018 - Edited by Alina Zare 21 | 12/2018 - Python Implementation by Yutai Zhou 22 | """ 23 | if tgt_sig.ndim == 1: 24 | tgt_sig = tgt_sig[:, np.newaxis] 25 | 26 | palm_out, kwargsout = img_det(palm_helper, hsi_img, tgt_sig, mask, n_comp = n_comp) 27 | return palm_out 28 | 29 | def palm_helper(hsi_data, tgt_sig, kwargs): 30 | n_comp = kwargs['n_comp'] 31 | n_pixel = hsi_data.shape[1] 32 | 33 | # fit the model 34 | gmm = GaussianMixture(n_components = n_comp, max_iter = 1, init_params = 'random').fit(hsi_data.T) 35 | means = gmm.means_ 36 | covariances = gmm.covariances_ 37 | sig_inv = np.zeros(covariances.shape) 38 | filt = np.zeros(means.shape) 39 | 40 | for i in range(n_comp): 41 | sig_inv[i,:,:] = np.linalg.pinv(covariances[i,:,:]) 42 | 43 | s = tgt_sig - means[i,:][:,np.newaxis] 44 | filt[i,:] = s.T @ sig_inv[i,:,:] / np.sqrt(s.T @ sig_inv[i,:,:] @ s) 45 | 46 | palm_data = np.zeros(n_pixel) 47 | 48 | for i in range(n_pixel): 49 | dists = np.zeros(n_comp) 50 | for j in range(n_comp): 51 | dists[j] = (filt[j,:] @ (hsi_data[:,i] - means[j,:])) ** 2 52 | 53 | palm_data[i] = np.min(dists) 54 | 55 | return palm_data, {} 56 | -------------------------------------------------------------------------------- /hsi_toolkit/signature_detectors/sam_detector.py: -------------------------------------------------------------------------------- 1 | from hsi_toolkit.util import img_det 2 | import numpy as np 3 | 4 | def sam_detector(hsi_img, tgt_sig, mask = None): 5 | """ 6 | Spectral Angle Mapper 7 | 8 | Inputs: 9 | hsi_image - n_row x n_col x n_band hyperspectral image 10 | tgt_sig - target signature (n_band x 1 - column vector) 11 | mask - binary image limiting detector operation to pixels where mask is true 12 | if not present or empty, no mask restrictions are used 13 | 14 | Outputs: 15 | sam_out - detector image 16 | 17 | 8/8/2012 - Taylor C. Glenn 18 | 6/2/2018 - Edited by Alina Zare 19 | 12/2018 - Python Implementation by Yutai Zhou 20 | """ 21 | if tgt_sig.ndim == 1: 22 | tgt_sig = tgt_sig[:, np.newaxis] 23 | 24 | sam_out, kwargsout = img_det(sam_helper, hsi_img, tgt_sig, mask) 25 | 26 | return sam_out 27 | 28 | def sam_helper(hsi_data, tgt_sig, kwargs): 29 | prod = tgt_sig.T @ hsi_data 30 | mag = np.sqrt(tgt_sig.T @ tgt_sig * np.sum(hsi_data ** 2, 0)) 31 | sam_data = prod / mag 32 | return sam_data.squeeze(), {} 33 | -------------------------------------------------------------------------------- /hsi_toolkit/signature_detectors/smf_detector.py: -------------------------------------------------------------------------------- 1 | from hsi_toolkit.util import img_det 2 | import numpy as np 3 | 4 | def smf_detector(hsi_img, tgt_sig, mask = None, mu = None, sig_inv = None): 5 | """ 6 | Spectral Matched Filter 7 | 8 | Inputs: 9 | hsi_image - n_row x n_col x n_band hyperspectral image 10 | tgt_sig - target signature (n_band x 1 - column vector) 11 | mask - binary image limiting detector operation to pixels where mask is true 12 | if not present or empty, no mask restrictions are used 13 | mu - (optional) mean for filter (if not provided, computed from image) 14 | siginv - (optional) inverse covariance for filter (if not provided, computed from image) 15 | 16 | Outputs: 17 | smf_out - detector image 18 | mu - mean of background 19 | sig_inv - inverse covariance of background 20 | 21 | 8/8/2012 - Taylor C. Glenn 22 | 6/2/2018 - Edited by Alina Zare 23 | 10/2018 - Python Implementation by Yutai Zhou 24 | """ 25 | smf_out, kwargsout = img_det(smf_det_array_helper, hsi_img, tgt_sig, mask, mu = mu, sig_inv = sig_inv) 26 | return smf_out, kwargsout['mu'], kwargsout['sig_inv'] 27 | 28 | def smf_det_array_helper(hsi_data, tgt_sig, kwargs): 29 | """ 30 | Spectral Matched Filter for array (not image) data 31 | 32 | Inputs: 33 | hsi_data - n_spectra x n_band array of hyperspectral data 34 | tgt_sig - target signature (n_band x 1 - column vector) 35 | mu - (optional) mean for filter 36 | sig_inv - (optional) inverse covariance for filter 37 | 38 | Outputs: 39 | smf_data - detector output per spectra 40 | mu - mean of background 41 | sig_inv - inverse covariance of background 42 | 43 | 8/19/2012 - Taylor C. Glenn 44 | 6/2/2018 - Edited by Alina Zare 45 | 10/2018 - Python Implementation by Yutai Zhou 46 | 47 | alternative formulation from Eismann's book, pp 653 48 | subtract mean from target to reduce effect of additive model 49 | on hyperspectral (non additive) data 50 | also take positive square root of filter 51 | """ 52 | mu = np.mean(hsi_data, axis = 1) if kwargs['mu'] is None else kwargs['mu'] 53 | sig_inv = np.linalg.pinv(np.cov(hsi_data.T, rowvar = False)) if kwargs['sig_inv'] is None else kwargs['sig_inv'] 54 | 55 | if tgt_sig.ndim == 1: 56 | tgt_sig = tgt_sig[:, np.newaxis] 57 | 58 | mu = np.reshape(mu, (len(mu), 1), order='F') 59 | 60 | s = tgt_sig - mu 61 | z = hsi_data - mu 62 | f = (s.T @ sig_inv) / np.sqrt(s.T @ sig_inv @ s) 63 | 64 | smf_data = f @ z 65 | return smf_data.T.squeeze(), {'mu':mu, 'sig_inv': sig_inv} 66 | -------------------------------------------------------------------------------- /hsi_toolkit/signature_detectors/smf_local_detector.py: -------------------------------------------------------------------------------- 1 | from hsi_toolkit.util import rx_det 2 | import numpy as np 3 | 4 | def smf_local_detector(hsi_img, tgt_sig, mask = None, guard_win = 2, bg_win = 4): 5 | """ 6 | Spectral Matched Filter with RX style local background estimation 7 | 8 | Inputs: 9 | hsi_image - n_row x n_col x n_band hyperspectral image 10 | tgt_sig - target signature (n_band x 1 - column vector) 11 | mask - binary image limiting detector operation to pixels where mask is true 12 | if not present or empty, no mask restrictions are used 13 | guard_win - guard window radius (square,symmetric about pixel of interest) 14 | bg_win - background window radius 15 | 16 | Outputs: 17 | out - detector image 18 | 19 | 10/25/2012 - Taylor C. Glenn 20 | 10/2018 - Python Implementation by Yutai Zhou 21 | """ 22 | if tgt_sig.ndim == 1: 23 | tgt_sig = tgt_sig[:, np.newaxis] 24 | 25 | out, kwargsout = rx_det(smf_local_helper, hsi_img, tgt_sig, mask = mask, guard_win = guard_win, bg_win = bg_win) 26 | return out 27 | 28 | def smf_local_helper(x, ind, bg, b_mask_list, args, kwargs): 29 | if bg is None: 30 | sig_inv = args['global_sig_inv'] 31 | mu = args['mu'] 32 | else: 33 | sig_inv = np.linalg.pinv(np.cov(bg.T, rowvar = False)) 34 | mu = np.mean(bg, 1) 35 | 36 | s = args['tgt_sig'] - np.reshape(mu, (-1,1)) 37 | z = np.reshape(x, (-1,1)) - np.reshape(mu, (-1,1)) 38 | f = (s.T @ sig_inv) / np.sqrt(s.T @ sig_inv @ s) 39 | 40 | smf_data = f @ z 41 | return smf_data, {} 42 | -------------------------------------------------------------------------------- /hsi_toolkit/signature_detectors/smf_max_detector.py: -------------------------------------------------------------------------------- 1 | from hsi_toolkit.util import img_det 2 | from hsi_toolkit.signature_detectors import smf_det_array_helper 3 | import numpy as np 4 | 5 | def smf_max_detector(hsi_img, tgt_sig, mask = None, mu = None, sig_inv = None): 6 | """ 7 | Spectral Matched Filter, Max over targets 8 | 9 | Inputs: 10 | hsi_image - n_row x n_col x n_band hyperspectral image 11 | tgt_sigs - target signatures (n_band x n_signatures) 12 | mask - binary image limiting detector operation to pixels where mask is true 13 | if not present or empty, no mask restrictions are used 14 | 15 | Outputs: 16 | smf_out - detector image 17 | 18 | 8/8/2012 - Taylor C. Glenn 19 | 12/2018 - Python Implementation by Yutai Zhou 20 | """ 21 | if tgt_sig.ndim == 1: 22 | tgt_sig = tgt_sig[:, np.newaxis] 23 | 24 | n_sig = tgt_sig.shape[1] 25 | n_row, n_col, n_band = hsi_img.shape 26 | 27 | sig_out = np.zeros((n_row, n_col, n_sig)) 28 | 29 | for i in range(n_sig): 30 | sig_out[:,:,i], kwargsout = img_det(smf_det_array_helper, hsi_img, tgt_sig[:,i][:,np.newaxis], mask, mu = mu, sig_inv = mu) 31 | 32 | smf_out = np.max(sig_out, 2) 33 | return smf_out 34 | -------------------------------------------------------------------------------- /hsi_toolkit/spectral_indices/README.md: -------------------------------------------------------------------------------- 1 | # Gatorsense HSI Toolkit - Spectral Indices 2 | 3 | Location: hsi_toolkit_py/spectral_indices 4 | 5 | This code base contains common spectral indices for vegetation analysis. Also, referred to as Vegetation Indices (VIs) in the literature. These indices have been designed for hyperspectral imagery, but can be used on multi-spectral imagery also. Some indices were originally designed for multi-spectral data and narrow bands have been selected to calculate them. Those indices are noted below with *. Each function contains citation for original spectral index calculation. A demo (DEMO_spectral_indices.py) has been created that will calculate all VIs listed below. 6 | 7 | More information about VIs can be found in this book chapter: 8 | Roberts, D.A., Roth, K.L, Wetherley, E.B., Meerdink, S.K., & Perroy, R.L. (2019). Chapter 1: Hyperspectral Vegetation Indices, in: Hyperspectral Remote Sensing of Vegetation (second edition), CRC Press, New York. 9 | 10 | ## Inputs: 11 | Each function takes similar inputs starting with hyperspectral image, wavelengths (in nanometers or nm), an optional mask (if you want to exclude pixels from analysis), and an optional band index. All functions will calculate the index based on literature suggested bands, but IF the user wants to input their own bands that is possible by specifying which band index should be used with the optional band variable. 12 | 13 | ## Demo: 14 | This repo contains a demo to run all spectral indices that are available. The demo hyperspectral image used is from the AVIRIS sensor (https://aviris.jpl.nasa.gov/) collected on April 16, 2014 over the Santa Barbara area. Specifically the image contains the La Cumbre Country Club and features a golf course with lake and residental areas surrounding. This should provide a mix of vegetation - trees and irrigated grasses.The demo will calculate all the indices, display them together for the user, and will save individual png files of each index with the RGB image in the Results folder. Keep in mind, the result images that are generated use the default value range and may need to be adjust to emphasize the range of values for vegetation. 15 | 16 | ## Current suite of VIs: 17 | * aci_vi: calculates the Anthocyanin Content Index ** 18 | * ari_vi: calculates the Anthocyanin Reflectance Index 19 | * arvi_vi: calculates the Atmospherically Resistant Vegetation Index ** 20 | * cai_vi: calculates the Cellulose Absorption Index *** 21 | * cari_vi: calculates the Chlorophyll Absorption in Reflectance Index 22 | * cirededge_vi: calculates the Chlorophyll Index Red Edge ** 23 | * cri1_vi: calculates the Carotenoid Reflectance Index 1 24 | * cri2_vi: calculates the Carotenoid Reflectance Index 2 25 | * evi_vi: calculates the Enhanced Vegetation Index ** 26 | * mari_vi: calculates the Modified Anthocyanin Reflectance Index 27 | * mcari_vi: calculates the Modified Anthocyanin Reflectance Index 28 | * msi_vi: calculates the Moisture Stress Index ** & *** 29 | * mtci_vi: calculates the MERIS Terrestrial Chlorophyll Index 30 | * ndii_vi: calculates the Normalized Difference Infrared Index ** & *** 31 | * ndli_vi: calculates the Normalized Difference Lignin Index *** 32 | * ndni_vi: calculates the Normalized Difference Nitrogen Index *** 33 | * ndre_vi: calculates the Normalized Difference Red Edge Index 34 | * ndvi_vi: calculates the Normalized Difference Vegetation Index ** 35 | * ndwi_vi: calculates the Normalized Difference Water Index *** 36 | * pri_vi: calculates the Photochemical Reflectance Index 37 | * psnd_chlA_vi: calculates the Pigment Sensitive Normalized Difference for Chlorophyll A 38 | * psnd_chlB_vi: calculates the Pigment Sensitive Normalized Difference for Chlorophyll B 39 | * psnd_car_vi: calculates the Pigment Sensitive Normalized Difference for Carotenoids 40 | * psri_vi: calculates the Plant Senescence Reflectance Index 41 | * pssr1_vi: calculates the Pigment-Specific Spectral Ratio 1 42 | * pssr2_vi: calculates the Pigment-Specific Spectral Ratio 2 43 | * pssr3_vi: calculates the Pigment-Specific Spectral Ratio 3 44 | * rep_vi: calculates the Red-edge Position 45 | * rgri_vi: calculates the Red/Green Ratio Index ** 46 | * rvsi_vi: calculates the Red-edge Vegetation Stress Index 47 | * savi_vi: calculates the Soil Adjusted Vegetation Index 48 | * sipi_vi: calculates the Structure Insensitive Pigment Index 49 | * sr_vi: calculates the Simple Ratio 50 | * vari_vi: calculates the Visible Atmospherically Resistant Index ** 51 | * vigreen_vi: calculates the Vegetation Index using green band ** 52 | * wdvi_vi: calculates the Weighted Difference Vegetation Index ** 53 | * wbi_vi: calculates the Water Band Index 54 | 55 | ** Spectral index original designed for multi-spectral data. We have selected narrow bands to calculate index based on hyperspectral literature. The citations with used hyperspectral bands are noted in each function description. 56 | 57 | *** This spectral index requires the shortwave infrared (1200 - 2500 nm). Not all hyperspectral systems measure the entire visible shortwave infrared spectrum. 58 | 59 | Questions? Contact: Alina Zare, azare@ufl.edu 60 | -------------------------------------------------------------------------------- /hsi_toolkit/spectral_indices/__init__.py: -------------------------------------------------------------------------------- 1 | from hsi_toolkit.spectral_indices.utilities_VI import * 2 | -------------------------------------------------------------------------------- /hsi_toolkit/util/README.md: -------------------------------------------------------------------------------- 1 | # Information for using files in hsi_toolkit/util 2 | ## hsi_gui_mask 3 | Use this file if you have binary masks for your hyperspectral image and have aligned the VNIR-E and SWIR data. 4 | You will need to change some code from lines 73 to 118: 5 | * Change file paths for SWIR data, VNIR-E data, and binary mask 6 | * You may need to change how the data cube is uploaded 7 | * You may need to change the RGB bands to construct the RGB image 8 | 9 | Note: In this file, SWIR data is the aligned data and VNIR-E data is normal hsi data. You can edit the code to make it vice versa. 10 | It will take a minute for the window to show the plots and the RGB image. Please click on the 'Help!' button and read all of the functionalities before interacting with the GUI. 11 | 12 | ## hsi_gui 13 | Use this file if you want to visualize VNIR-E or SWIR hyperspectral data. 14 | You will need to change some of the code from lines 69-87: 15 | * Change file paths for hyperspectral data and RGB image 16 | * You may need to change how the data cube is uploaded 17 | 18 | It will take a minute for the window to show the plots and the RGB image. Please click on the 'Help!' button and read all of the functionalities before interacting with the GUI. 19 | 20 | ## hsi_gui_class 21 | Use this file if you want to compare 2 hyperspectral images with binary masks at the same time. 22 | You will need to change some of the code from lines 76 to 176 23 | * Change file paths for SWIR data, VNIR-E data, and binary mask for both hsi images 24 | * You may need to change how the data cube is uploaded for both hsi images 25 | * You may need to change the RGB bands to construct the RGB image for both hsi images 26 | * You can change the color of the lines being plotted and the variable alpha -------------------------------------------------------------------------------- /hsi_toolkit/util/__init__.py: -------------------------------------------------------------------------------- 1 | from hsi_toolkit.util.get_hsi_bands import * 2 | from hsi_toolkit.util.get_RGB import * 3 | from hsi_toolkit.util.img_det import * 4 | from hsi_toolkit.util.img_seg import * 5 | from hsi_toolkit.util.pca import * 6 | from hsi_toolkit.util.rx_det import * 7 | from hsi_toolkit.util.unmix import * 8 | from hsi_toolkit.util.hsi_gui_mask import * 9 | from hsi_toolkit.util.hsi_gui import * 10 | -------------------------------------------------------------------------------- /hsi_toolkit/util/get_RGB.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from hsi_toolkit.util import get_hsi_bands 3 | 4 | def get_RGB(hsi_img, wavelengths): 5 | """ 6 | Creates an RGB image from a hyperspectral image 7 | 8 | inputs: 9 | hsi_img - n_row x n_col x n_band hyperspectral image 10 | wavelengths - 1 x n_band vector listing wavelength values for hsi_img in nm 11 | outputs: 12 | RGB_img - n_row x n_col x 3 RGB image 13 | 14 | 5/5/2018 - Alina Zare 15 | 10/5/2018 - Yutai Zhou 16 | """ 17 | n_row, n_col, n_band = hsi_img.shape 18 | red_wavelengths = list(range(619,659)) 19 | green_wavelengths = list(range(549,570)) 20 | blue_wavelengths = list(range(449,495)) 21 | 22 | RGB_img = np.zeros((n_row, n_col, 3)) 23 | RGB_img[:,:,0] = np.mean(get_hsi_bands(hsi_img, wavelengths, red_wavelengths), axis=2); 24 | RGB_img[:,:,1] = np.mean(get_hsi_bands(hsi_img, wavelengths, green_wavelengths), axis=2); 25 | RGB_img[:,:,2] = np.mean(get_hsi_bands(hsi_img, wavelengths, blue_wavelengths), axis=2); 26 | 27 | RGB_img = ((RGB_img - np.min(RGB_img.flatten())) / (np.max(RGB_img.flatten()) - np.min(RGB_img.flatten()))) ** (1/1.5) 28 | return RGB_img 29 | -------------------------------------------------------------------------------- /hsi_toolkit/util/get_hsi_bands.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | def get_hsi_bands(hsi_img, wavelengths, wavelengths_to_get): 3 | """ 4 | Return hsi stack with bands that are closest to desired wavelengths 5 | 6 | inputs: 7 | hsi_img - n_row x n_col x n_band hyperspectral image 8 | wavelengths - 1 x n_band vector listing wavelength values for hsi_img 9 | wavengths_to_get - 1 x n_band_new vector listing desired wavelengths 10 | outputs: 11 | hsi_out - n_row x n_col x n_band_new image 12 | 13 | 5/5/2018 - Alina Zare 14 | 10/5/2018 - Yutai Zhou 15 | """ 16 | waves = [] 17 | for i in range(len(wavelengths_to_get)): 18 | min_index = np.argmin(np.abs(wavelengths - wavelengths_to_get[i])) 19 | waves.append(min_index) 20 | waves=np.array(waves) 21 | hsi_out = hsi_img[:,:,[int(m) for m in waves]]; 22 | return hsi_out 23 | -------------------------------------------------------------------------------- /hsi_toolkit/util/hsi_gui.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Use this GUI if you want to visualize either VNIR-E data or SWIR data 3 | You will need to edit the code from line 69 to line 87. 4 | Give it a minute once you finished selecting the files 5 | Click on the 'Help!' button and read the guide to understand the features 6 | ''' 7 | 8 | import tkinter as tk 9 | from tkinter import ttk 10 | from tkinter import messagebox, Scrollbar 11 | from PIL import Image, ImageTk, ImageDraw 12 | from tkinter import filedialog 13 | import numpy as np 14 | from spectral import imshow, view_cube 15 | import matplotlib.pyplot as plt 16 | from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg 17 | import spectral.io.envi as envi 18 | import cv2 19 | import pandas as pd 20 | 21 | # Create a Tkinter window; Zoomed means full screen 22 | root = tk.Tk() 23 | root.state('zoomed') 24 | root.title('Pixel Information') 25 | root.pack_propagate(False) # root will not resize itself 26 | 27 | # Create a frame to contain the canvas and scrollbar 28 | main_frame = tk.Frame(root) 29 | main_frame.pack(fill=tk.BOTH, expand=True) 30 | 31 | # Create a canvas widget 32 | canvas = tk.Canvas(main_frame, bg="white") 33 | canvas.pack(side=tk.LEFT, fill=tk.BOTH, expand=True) 34 | 35 | # Create a vertical scrollbar linked to the canvas 36 | vsb = tk.Scrollbar(main_frame, orient="vertical", command=canvas.yview) 37 | vsb.pack(side=tk.RIGHT, fill=tk.Y) 38 | 39 | # Configure canvas to work with scrollbar 40 | canvas.configure(yscrollcommand=vsb.set) 41 | 42 | canvasFrame = tk.Frame(canvas, bg = 'white') 43 | canvas.create_window((0, 0), window=canvasFrame, anchor="nw") 44 | 45 | # Divide the canvas into two parts 46 | canvasFrame.columnconfigure(0, weight=1) 47 | canvasFrame.columnconfigure(1, weight=3) 48 | 49 | # Sets the frames dimensions based on window screen 50 | frame1_width = (root.winfo_screenwidth() * 2) // 5 51 | frame1_height = root.winfo_screenheight() 52 | frame2_width = (root.winfo_screenwidth() * 3) // 5 53 | frame2_height = root.winfo_screenheight() 54 | 55 | 56 | # Create a frame and a canvas for the two parts 57 | canvas1 = tk.Canvas(canvasFrame, bg="white", width=frame1_width, height=frame1_height) 58 | frame2 = tk.Frame(canvasFrame, bg="lightgrey", width=frame2_width, height=frame2_height) 59 | 60 | # Place the frames in the divided parts 61 | canvas1.grid(row=0, column=0, sticky="nsew") #nsew means frame expands to all sides 62 | frame2.grid(row=0, column=1, sticky="nsew") 63 | 64 | 65 | canvasFrame.update_idletasks() # Ensure the canvas dimensions are updated 66 | canvas.config(scrollregion=canvas.bbox("all")) 67 | 68 | ''' 69 | USER NEEDS TO CHANGE: FILE PATH LOCATION AND HOW THEY UPLOAD THEIR DATA 70 | ''' 71 | # Opens Image Path. This is to get the rgb image and display it on the canvas 72 | imagePath = filedialog.askopenfilename(initialdir = '/anika/HSI image', title = "Image", filetypes = (('png files', '*.png'),('all files', '*.*'),)) 73 | 74 | # Opens data paths 75 | data_hdr = filedialog.askopenfilename(initialdir = '/anika/HSI image', title = "Data.hdr", filetypes = (('hdr files', '*.hdr'),('all files', '*.*'),)) 76 | data = filedialog.askopenfilename(initialdir = '/anika/HSI image', title = "Data", filetypes = (('all files', '*.*'),)) 77 | hsi_ref = envi.open(data_hdr,data) # change how you upload your data if necessary 78 | hsi_np = np.copy(hsi_ref.asarray()) 79 | wholeCube = (hsi_np) 80 | 81 | #Creates image and data cube 82 | image = Image.open(imagePath) 83 | tk_image = ImageTk.PhotoImage(image) 84 | ImageArr = np.array(wholeCube) 85 | 86 | ''' 87 | USER SHOULD NOT NEED TO CHANGE ANY CODE AFTER THIS POINT 88 | ''' 89 | 90 | #determines x axis range based on if swir data or vnir data was chosen 91 | index = np.linspace(900,2500,wholeCube.shape[2]) 92 | if wholeCube.shape[-1] == 372: 93 | index = np.linspace(400,1000,wholeCube.shape[2]) 94 | 95 | # Creates canvas3 to create a scollable area for image and creates canvas 2 to hold the image 96 | canvas3 = tk.Canvas(canvas1, width = frame1_width, height = 730) 97 | canvas3.grid(row = 0, column= 0, sticky = 'nsew') 98 | canvas2 = tk.Canvas(canvas3, width = image.width, height = image.height) 99 | canvas2.grid(row = 0, column= 0, sticky = 'nsew') 100 | canvas2.create_image(0,0, anchor = tk.NW, image = tk_image) 101 | canvas2.config(scrollregion=canvas2.bbox(tk.ALL)) 102 | 103 | # Configure scrollbars for canvas3 (this is to view entire image if image is to big to fit on screen) 104 | vscrollbar2 = ttk.Scrollbar(canvas1, orient=tk.VERTICAL, command=canvas3.yview) 105 | vscrollbar2.grid(row=0, column=3, sticky="ns") 106 | canvas3.configure(yscrollcommand=vscrollbar2.set) 107 | hscrollbar2 = ttk.Scrollbar(canvas1, orient=tk.HORIZONTAL, command=canvas3.xview) 108 | hscrollbar2.grid(row=1, column=0, sticky="ew") 109 | canvas3.configure(xscrollcommand=hscrollbar2.set) 110 | 111 | # Configure canvas3 to scroll with canvas2 inside it 112 | canvas3.create_window((0, 0), window=canvas2, anchor="nw") 113 | 114 | # Configure scroll region for canvas2 and canvas3 115 | canvas2.update_idletasks() # Update canvas2 to get correct bbox 116 | canvas3.config(scrollregion=canvas2.bbox(tk.ALL)) 117 | canvas1.config(scrollregion=(0, 0,frame1_width, 730)) 118 | 119 | # Create a Matplotlib figure and display a plot in the second frame 120 | fig, axs = plt.subplots(4, 1, figsize=(frame2_width/100 , frame2_height/70 - 10)) 121 | axs[0].set_title('Random Pixels') 122 | axs[1].set_title('Average Pixel') 123 | axs[2].set_title('Selected Pixels') 124 | axs[3].set_title('All Pixels') 125 | 126 | for i in range(0,4): 127 | axs[i].set_ylim(0,1.2) 128 | axs[i].set_yticks(np.linspace(0,1,5)) 129 | axs[i].set_xlabel('Spectral Band (nm)') 130 | axs[i].set_ylabel('Reflectance') 131 | plt.subplots_adjust(hspace= 0.8) # adds spacing between the subplots 132 | 133 | 134 | # to place the plots on tkinter canvas 135 | canvas_matplotlib = FigureCanvasTkAgg(fig, master=frame2) 136 | canvas_matplotlib.draw() 137 | canvas_matplotlib.get_tk_widget().pack(fill = tk.BOTH, expand=True) 138 | 139 | ''' 140 | THIS IS ALL THE CODE FOR THE AVERAGE PIXEL GRAPH (SECOND PLOT) 141 | ''' 142 | #Plots the mean spectra 143 | mean2 = np.mean(wholeCube,axis= (0,1)) 144 | axs[1].plot(index, mean2) 145 | 146 | ''' 147 | THIS IS ALL THE CODE FOR THE RANDOM PIXEL GRAPH (FIRST PLOT) 148 | ''' 149 | randomNum = 6 150 | randLineTracker = {} 151 | randData = [] 152 | coordinates = [] 153 | 154 | # THIS FUNCTION WILL PLOT RANDOM PIXELS AND STORE INFROMATION IN LISTS/DICTS DEFINED BELOW 155 | def randomPixels(ImageShape): 156 | global randomNum # number of pixels to be randomly plotted 157 | global randList 158 | global randData 159 | randList = [] # stores pixel coordinates 160 | global ColorList 161 | ColorList = [] # keeps track of the line colors 162 | count = 1 163 | for pixel in range (0,int(randomNum)): 164 | pixelW = np.random.randint(0,ImageShape[0]) 165 | pixelH = np.random.randint(0,ImageShape[1]) 166 | data = ImageArr[pixelW, pixelH] 167 | randData.append(data) 168 | randList.append([pixelH,pixelW]) 169 | #gives you a line 2D object with properties of the line 170 | line, = axs[0].plot(index, data) 171 | color = line.get_color() 172 | axs[0].annotate(str(count), (2.05,data[-1]), color = color) # adds the numbers to the end of the lines plotted 173 | randLineTracker[count] = line 174 | count += 1 175 | ColorList.append(color) 176 | fig.canvas.draw() 177 | 178 | # Calls the function intially 179 | randomPixels(wholeCube.shape) 180 | 181 | # CLEARS THE RANDOM PIXEL PLOT AND GRAPHS NEW PIXELS 182 | def updatePlotforRand(): 183 | axs[0].clear() 184 | axs[0].set_title('Random Pixels') 185 | axs[0].set_ylim(0,1.2) 186 | axs[0].set_yticks(np.linspace(0,1,5)) 187 | axs[0].set_xlabel('Spectral Band (nm)') 188 | axs[0].set_ylabel('Reflectance') 189 | global randData 190 | randData = [] 191 | global randLineTracker 192 | randLineTracker = {} 193 | fig.canvas.draw() 194 | randomPixels(wholeCube.shape) 195 | 196 | # These 2 dicts hold the information on dots and numbers plotted on image 197 | randDots = {} 198 | textInfo2 = {} 199 | 200 | # GRAPHS THE DOTS AND NUMBERS OFTHE RANDOM PIXELS ON THE IMAGE 201 | def showRandPixels(): 202 | global textInfo2 203 | global randDots 204 | #Hides the selected pixel information 205 | canvas2.itemconfigure("selectedDots", state = "hidden") 206 | canvas2.itemconfigure("text", state = "hidden") 207 | for coordinates in all_Dots: 208 | canvas2.delete(all_Dots[coordinates]) 209 | for textID, (textX, textY) in all_text.items(): 210 | canvas2.delete(textID) 211 | #if there are random pixels already on the image, it will delete them 212 | if randDots != {} and textInfo2 != {}: 213 | for coordinates in randDots: 214 | canvas2.delete(randDots[coordinates]) 215 | for textID, (textX, textY) in textInfo2.items(): 216 | canvas2.delete(textID) 217 | #This will plot the new dots and numbers on the image 218 | Count = 1 219 | for pixel in randList: 220 | x = pixel[0] 221 | y = pixel[1] 222 | rand_dot = canvas2.create_oval(x-5,y-5,x+5, y+5, fill = ColorList[Count-1], tag = "random_selectedDots") 223 | randDots[(x,y)] = rand_dot 224 | rand_textID = canvas2.create_text(x+15, y, text = str(Count), fill = ColorList[Count-1], tag = "random_text") 225 | textInfo2[rand_textID] = (x+15,y) 226 | Count += 1 227 | 228 | # FINDS WHICH PIXEL CORRESPONDS TO THE LINE THE USER CLICKED ON 229 | def whichRandomPixel(): 230 | global randDots #contains the pixel coordinate and the dots drawn 231 | global yCoords2 #contains rgb values of the selected pixels 232 | 233 | for coordinates in all_Dots: 234 | canvas2.delete(all_Dots[coordinates]) 235 | for textID, (textX, textY) in all_text.items(): 236 | canvas2.delete(textID) 237 | for keys in randDots.keys(): 238 | x,y = keys 239 | pixelkey = y,x 240 | dataPixel = ImageArr[y][x] 241 | #trying to find the pixel clicked on and enlarge the dot and number 242 | if np.array_equal(dataPixel,yCoords2): 243 | global selectedDot2 244 | selectedDot2 = canvas2.create_oval(x-5,y-5,x+5, y+5, fill = "white") 245 | PixelText.insert(tk.END, "Pixel Coordinates = " + str(pixelkey) ) 246 | 247 | #CHECKS TO SEE IF YOU CLICKED ON A LINE IN THE RANDOM PIXEL PLOT 248 | def clickRand_Selected(event): 249 | # global clicked 250 | global randData 251 | if event.inaxes == axs[0]: #checks to see if you clicked in the first plot 252 | for key, lineOb in randLineTracker.items(): 253 | contains, _ = lineOb.contains(event) #checks to see if an event happened near a line 254 | #contains is a true or false value based on if event happened on a line 255 | if contains: 256 | global yCoords2 # has rdg values of that line 257 | yCoords2 = randData[key-1] 258 | whichRandomPixel() #function will draw a dot and print the pixel coordinate and rgb values 259 | break 260 | 261 | #DELETES THE WHITE DOT AND NUMBER 262 | def NotRand_Selected(event): 263 | global selectedDot2 264 | global showText2 265 | canvas2.delete(selectedDot2) 266 | PixelText.delete('1.0', tk.END) 267 | 268 | #Gets the user input for number of random pixels to be graphed 269 | def get_input(): 270 | global randomNum 271 | randomNum = entry.get() 272 | updatePlotforRand() 273 | 274 | ''' 275 | THIS IS ALL THE CODE FOR THE SELECTED PIXEL GRAPH (THRID PLOT) 276 | ''' 277 | ycoordList = [] #list stores all rgb values that are plotted 278 | lineTracker = {} #stores line properties 279 | 280 | #GRAPHS PIXEL THE USER CLICKS ON 281 | def get_pixel_rgb(event): 282 | global dotCount 283 | x, y = event.x, event.y 284 | data = ImageArr[y,x] 285 | ycoordList.append(data) 286 | global color 287 | line, = axs[2].plot(index,data) 288 | lineTracker[dotCount] = line 289 | color = line.get_color() 290 | axs[2].annotate(str(dotCount), (2.05,data[-1]), color = color) 291 | fig.canvas.draw() 292 | 293 | #THIS FUNCTION CLEARS THE SELECTED PIXEL PLOTS 294 | def updateSelected4Dots(): 295 | axs[2].clear() 296 | global ycoordList 297 | ycoordList = [] 298 | global lineTracker 299 | lineTracker = {} 300 | #relabeling plot 301 | axs[2].set_title('Selected Pixels') 302 | axs[2].set_ylim(0,1.2) 303 | axs[2].set_yticks(np.linspace(0,1,5)) 304 | axs[2].set_xlabel('Spectral Band (nm)') 305 | axs[2].set_ylabel('Reflectance') 306 | fig.canvas.draw() 307 | 308 | #THIS WILL UPDATE THE GRAPH IF THE USER UNSELECTS A PIXEL 309 | def redraw_pixel_rgb(list_to_redraw): 310 | updateSelected4Dots() 311 | global dotCount 312 | dotCount = 1 313 | for coordinate in list_to_redraw: 314 | x,y = coordinate 315 | data = ImageArr[y,x] 316 | ycoordList.append(data) 317 | line, = axs[2].plot(index,data) 318 | lineTracker[dotCount] = line 319 | color = line.get_color() 320 | axs[2].annotate(str(dotCount), (2.05,data[-1]), color = color) 321 | dotCount += 1 322 | fig.canvas.draw() 323 | 324 | 325 | imagedots = {} #stores coordinates and dot properties 326 | textInfo={} #stores text properties and coordinates 327 | dotCount = 1 #keeps track of how mmany dots are plotted 328 | 329 | #CHECKS TO SEE IF PIXEL ALREADY HAS A DOT AND NUMBER PLOTTED 330 | #IF NOT IT GRAPHS THE DOT AND NUMBER. IF IT DOES, IT WILL DELETE THE DOT AND NUMBER 331 | def dots(event): 332 | global dotCount 333 | x,y = event.x, event.y 334 | coordinates = (x,y) 335 | if coordinates in imagedots: 336 | canvas2.delete(imagedots[coordinates]) 337 | for textID, (textX, textY) in textInfo.items(): 338 | if textX == x+10 and textY == y: 339 | canvas2.delete(textID) 340 | del textInfo[textID] 341 | break 342 | for keys in lineTracker.keys(): 343 | if keys == imagedots[coordinates]: 344 | del lineTracker[dotCount] 345 | dotCount += -1 346 | del imagedots[coordinates] 347 | listRedraw = list(imagedots.keys()) 348 | redraw_pixel_rgb(listRedraw) #update graph if user unselects a pixel 349 | else: 350 | global color 351 | dot = canvas2.create_oval(x-3,y-3,x+2, y+2, fill = color, tag = "selectedDots") 352 | imagedots[coordinates] = dot 353 | textID = canvas2.create_text(x+10, y, text = str(dotCount), fill = color, tag = "text") 354 | textInfo[textID] = (x+10,y) 355 | dotCount += 1 356 | 357 | #ENLARGES THE DOT THAT CORRESPONDS TO THE LINE THE USER CLICKED ON 358 | global selectedDot 359 | def whichPixel(): 360 | for keys in imagedots.keys(): 361 | x,y = keys 362 | pixelkey = y,x 363 | dataPixel = ImageArr[y][x] 364 | if np.array_equal(dataPixel,yCoords): 365 | global selectedDot 366 | for key,item in textInfo.items(): 367 | if item == (x+10,y): 368 | text_value = canvas2.itemcget(key,'text') 369 | global showText 370 | showText = canvas2.create_text(x+10, y, text = str(text_value), fill = 'white') 371 | selectedDot = canvas2.create_oval(x-5,y-5,x+5, y+5, fill = "white") 372 | PixelText.insert(tk.END, "Pixel Coordinates = " + str(pixelkey)) 373 | 374 | #FINDS THE RGB VALUES OF THE LINE THE USER CLICKED ON 375 | def clickSelected(event): 376 | global clicked 377 | clicked = 1 378 | if event.inaxes == axs[2]: 379 | for key, lineOb in lineTracker.items(): 380 | contains, _ = lineOb.contains(event) 381 | if contains: 382 | global yCoords 383 | yCoords = ycoordList[key-1] 384 | whichPixel() 385 | break 386 | 387 | #REMOVES THE SELECTED DOT AND TEXT AND REMOVES THE TEXT 388 | def NotSelected(event): 389 | global selectedDot 390 | global showText 391 | canvas2.delete(selectedDot) 392 | canvas2.delete(showText) 393 | PixelText.delete('1.0', tk.END) 394 | 395 | #THIS CLEARS THE SELECTED DOTS GRAPH AND EMPTIES ALL LISTS/DICTS 396 | def updatePlot(): 397 | #updates the plots labeling 398 | axs[2].clear() 399 | axs[2].set_title('Selected Pixels') 400 | axs[2].set_ylim(0,1.2) 401 | axs[2].set_yticks(np.linspace(0,1,5)) 402 | axs[2].set_xlabel('Spectral Band (nm)') 403 | axs[2].set_ylabel('Reflectance') 404 | # Redraws the image so its empty 405 | global image,tk_image 406 | image = Image.open(imagePath) 407 | tk_image = ImageTk.PhotoImage(image) 408 | canvas2.create_image(0, 0, anchor=tk.NW, image=tk_image) 409 | # empties all lists and dicts that keep track of selected pixels 410 | global ycoordList 411 | global coordinates 412 | coordinates = [] 413 | ycoordList = [] 414 | global lineTracker 415 | lineTracker = {} 416 | global dotCount 417 | dotCount = 1 418 | fig.canvas.draw() 419 | 420 | #THIS SHOWS THE SELECTED DOTS ON THE IMAGE AND DELETES THE RANDOM DOTS 421 | def showSelectedPixels(): 422 | global textInfo2 423 | canvas2.itemconfigure("selectedDots", state = "normal") 424 | canvas2.itemconfigure("text", state = "normal") 425 | for coordinates in randDots: 426 | canvas2.delete(randDots[coordinates]) 427 | for textID, (textX, textY) in textInfo2.items(): 428 | canvas2.delete(textID) 429 | for coordinates in all_Dots: 430 | canvas2.delete(all_Dots[coordinates]) 431 | for textID, (textX, textY) in all_text.items(): 432 | canvas2.delete(textID) 433 | textInfo2 = {} 434 | 435 | 436 | #PLOTS THE DOTS AND NUMBER BASED ON WHAT LINE THE USER CLICKS ON 437 | def combinedFunctions(event): 438 | get_pixel_rgb(event) 439 | dots(event) 440 | 441 | 442 | #Functions for click and Drag 443 | last_x, last_y = None, None 444 | pen_color = "red" # Change this to your desired color 445 | pen_size = 1 # initial pen value 446 | 447 | #gets the pen size value that the user selects 448 | def penValue(value): 449 | global pen_size 450 | pen_size = int(value) 451 | 452 | #Allows the user to draw on image based on a right click and drag motion 453 | def on_button_press(event): 454 | global last_x,last_y 455 | last_x, last_y = event.x, event.y 456 | 457 | def on_button_motion( event): 458 | global last_x,last_y 459 | if last_x is not None and last_y is not None: 460 | # Draw on the image 461 | draw_on_image(event.x, event.y) 462 | last_x, last_y = event.x, event.y 463 | global coordinates 464 | coordinates.append((event.x,event.y)) 465 | 466 | colors = [] #keeps track of line colors for pixels the user drew on 467 | #Plots the spectra for pixels that were drew over 468 | def graphDraggedPixel(list_to_redraw): 469 | updateSelected4Dots() 470 | global dotCount 471 | dotCount = 1 472 | for coordinate in list_to_redraw: 473 | x,y = coordinate 474 | data = ImageArr[y,x] 475 | ycoordList.append(data) 476 | line, = axs[2].plot(index,data) 477 | lineTracker[dotCount] = line 478 | color = line.get_color() 479 | colors.append(color) 480 | axs[2].annotate(str(dotCount), (2.05,data[-1]), color = color) 481 | dotCount += 1 482 | fig.canvas.draw() 483 | 484 | # Creates dots on the pixels that the user drew over 485 | def on_button_release(event): 486 | global last_x,last_y 487 | global dotCount 488 | last_x, last_y = None, None 489 | global coordinates 490 | graphDraggedPixel(coordinates) 491 | colorCount = 0 492 | for x,y in coordinates: 493 | if (x,y) not in imagedots: 494 | coord = (x,y) 495 | dot = canvas2.create_oval(x-3,y-3,x+2, y+2, fill = colors[colorCount], tag = "selectedDots") 496 | imagedots[coord] = dot 497 | textID = canvas2.create_text(x+10, y, text = str(dotCount), fill = colors[colorCount], tag = "text") 498 | textInfo[textID] = (x+10,y) 499 | dotCount += 1 500 | colorCount+=1 501 | 502 | # Allows the user to draw on the image 503 | def draw_on_image( x, y): 504 | global tk_image, image 505 | global last_x,last_y, pen_size 506 | # Draw on the image using PIL 507 | draw = ImageDraw.Draw(image) 508 | draw.line([last_x, last_y, x, y], fill=pen_color, width=pen_size) 509 | # Update the canvas 510 | tk_image = ImageTk.PhotoImage(image) 511 | canvas2.create_image(0, 0, anchor=tk.NW, image=tk_image) 512 | 513 | # Allows user to interact with image 514 | canvas2.bind("", combinedFunctions) #left click to select a simple pixel 515 | 516 | # Right click and drag to draw on image 517 | canvas2.bind("", on_button_press) 518 | canvas2.bind("", on_button_motion) 519 | canvas2.bind("", on_button_release) 520 | 521 | ''' 522 | THIS SECTION IS FOR THE ALL PIXELS PLOT 523 | ''' 524 | 525 | # THIS FUNCTION GRAPHS EVERY PIXEL ON THE 4th PLOT 526 | def everyPixel(): 527 | #empties all lists and dicts that keep track of pixel info 528 | global stepsize, PixelList, alldata, allLinetracker, colorTracker 529 | alldata = [] 530 | allLinetracker = {} 531 | colorTracker = [] 532 | PixelList= [] 533 | all_count = 1 534 | #resets the 4th graph 535 | axs[3].clear() 536 | axs[3].set_title('All Pixels') 537 | axs[3].set_ylim(0,1.2) 538 | axs[3].set_yticks(np.linspace(0,1,5)) 539 | axs[3].set_xlabel('Spectral Band (nm)') 540 | axs[3].set_ylabel('Reflectance') 541 | fig.canvas.draw() 542 | 543 | #graphs every pixels 544 | for pixelR in range(0, ImageArr.shape[0],int(int(stepsize)/2)): 545 | for pixelC in range(0,ImageArr.shape[1],int(int(stepsize)/2)): 546 | PixelList.append([pixelC,pixelR]) 547 | rgb = ImageArr[pixelR,pixelC] 548 | alldata.append(rgb) 549 | all_line, = axs[3].plot(index, rgb) 550 | allLinetracker[all_count] = all_line 551 | all_color = all_line.get_color() 552 | colorTracker.append(all_color) 553 | axs[3].annotate(str(all_count), (2.05,rgb[-1]), color = all_color) # adds the numbers to the end of the lines plotted 554 | all_count += 1 555 | fig.canvas.draw() 556 | 557 | # These 2 dicts hold the information on dots and numbers plotted on image 558 | all_Dots = {} 559 | all_text = {} 560 | 561 | def showAllPixels(): 562 | global all_text 563 | global all_Dots 564 | #Hides the selected pixel information 565 | canvas2.itemconfigure("selectedDots", state = "hidden") 566 | canvas2.itemconfigure("text", state = "hidden") 567 | canvas2.itemconfigure('random_selectedDots', state = 'hidden') 568 | canvas2.itemconfigure('random_text', state = 'hidden') 569 | if randDots != {} and textInfo2!= {}: 570 | for coordinates in randDots: 571 | canvas2.delete(randDots[coordinates]) 572 | for textID, (textX, textY) in textInfo2.items(): 573 | canvas2.delete(textID) 574 | #if there are random pixels already on the image, it will delete them 575 | if all_Dots != {} and all_text != {}: 576 | for coordinates in all_Dots: 577 | canvas2.delete(all_Dots[coordinates]) 578 | for textID, (textX, textY) in all_text.items(): 579 | canvas2.delete(textID) 580 | #This will plot the new dots and numbers on the image 581 | Count = 1 582 | for pixel in PixelList: 583 | x = pixel[0] 584 | y = pixel[1] 585 | all_dot = canvas2.create_oval(x-5,y-5,x+5, y+5, fill = colorTracker[Count-1], tag = "all_selectedDots") 586 | all_Dots[(x,y)] = all_dot 587 | all_textID = canvas2.create_text(x+15, y, text = str(Count), fill = colorTracker[Count-1], tag = "all_text") 588 | all_text[all_textID] = (x+15,y) 589 | Count += 1 590 | 591 | canvas_matplotlib.mpl_connect('button_press_event', clickAll_Selected) 592 | canvas_matplotlib.mpl_connect('button_release_event', NotAll_Selected) 593 | 594 | # FINDS WHICH PIXEL CORRESPONDS TO THE LINE THE USER CLICKED ON 595 | def whichAllPixel(): 596 | global all_Dots #contains the pixel coordinate and the dots drawn 597 | global selectedPixel #contains rgb values of the selected pixels 598 | global showTextAll 599 | for keys in all_Dots.keys(): 600 | x,y = keys 601 | pixelkey = y,x 602 | dataPixel = ImageArr[y][x] 603 | #trying to find the pixel clicked on and enlarge the dot and number 604 | if np.array_equal(dataPixel,selectedPixel): 605 | global selectedDotAll 606 | selectedDotAll = canvas2.create_oval(x-5,y-5,x+5, y+5, fill = "white") 607 | PixelText.insert(tk.END, "Pixel Coordinates = " + str(pixelkey) ) 608 | 609 | 610 | #CHECKS TO SEE IF YOU CLICKED ON A LINE IN THE ALL PIXEL PLOT 611 | def clickAll_Selected(event): 612 | global clickedAll 613 | global allLinetracker 614 | global alldata 615 | if event.inaxes == axs[3]: #checks to see if you clicked in the first plot 616 | for key, lineOb in allLinetracker.items(): 617 | contains, _ = lineOb.contains(event) #checks to see if an event happened near a line 618 | #contains is a true or false value based on if event happened on a line 619 | if contains: 620 | global selectedPixel # has rdg values of that line 621 | selectedPixel = alldata[key-1] 622 | whichAllPixel() #function will draw a dot and print the pixel coordinate and rgb values 623 | break 624 | 625 | #DELETES THE WHITE DOT AND NUMBER 626 | def NotAll_Selected(event): 627 | global selectedDotAll 628 | global showTextAll 629 | canvas2.delete(selectedDotAll) 630 | # canvas2.delete(showTextAll) 631 | PixelText.delete('1.0', tk.END) 632 | 633 | #Gets the user input for every pixel to be graphed 634 | def inputForStep(): 635 | global stepsize 636 | stepsize = entry2.get() 637 | everyPixel() 638 | 639 | #help messagebox that lists all of the feature information 640 | def help(): 641 | messagebox.showinfo('Guide' , "'Clear Selected Pixels' : Removes the dots that the user selected"+'\n' + '\n' +"'Generate Random Pixels' : Plots new random pixel spectras"+'\n' + '\n' +"'Show Random Pixels' : Only displays dots corresponding to the random pixel plot" 642 | +'\n' + '\n' + "'Show Selected Pixels' : Only displays dots for the Selected Pixels plot " +'\n' + '\n' + "'Show All Pixels': Only displays dots for every X Pixels"+'\n' + '\n' + "'Graph Every Pixels' : Plots every X number of pixels" 643 | +'\n' + '\n' + "'Enter Random Number of Pixels' : User can choose the number of pixels to be plotted in the Random Pixels plot"+'\n' + '\n' + "'Export' : Create an excel sheet with the selected pixel coordinates and HSI data"+'\n' + '\n' + 644 | "'Slider' : Chooses the pen's thickness" + '\n' + 'To select a region of pixels, right click and drag.' +'\n' + '\n' + "'Clear Image' : Remove Drawings and dots from the image" 645 | +'\n' + '\n' + 'Left click on the image to graph the corresponding pixel spectra. You can click on any spectra in the 1st, 3rd, and 4th plots to get the pixel coordinates' 646 | +'\n' + '\n' + 'Use the scroll bars to view the image if the image is too big to fit the canvas') 647 | 648 | # THIS ALLOWS THE USER TO INTERACT WITH THE GRAPHS 649 | canvas_matplotlib.mpl_connect('button_press_event', clickSelected) 650 | canvas_matplotlib.mpl_connect('button_release_event', NotSelected) 651 | canvas_matplotlib.mpl_connect('button_press_event', clickRand_Selected) 652 | canvas_matplotlib.mpl_connect('button_release_event', NotRand_Selected) 653 | 654 | # Function that saves pixel coordinates and data in Excel 655 | def export(): 656 | data = [] 657 | for coordinates in imagedots.keys(): 658 | pixelData = wholeCube[coordinates[1], coordinates[0], :] 659 | info = (coordinates[1], coordinates[0], *pixelData) 660 | data.append(info) 661 | df = pd.DataFrame(data) 662 | columns = ['Row', 'Column', 'Data'] + [''] * (df.shape[1] - 3) 663 | df.columns = columns 664 | file_path = filedialog.asksaveasfilename(defaultextension=".xlsx", 665 | filetypes=[("Excel files", "*.xlsx"), 666 | ("All files", "*.*")]) 667 | if file_path: 668 | # Save the DataFrame to an Excel file 669 | df.to_excel(file_path, index=False) 670 | 671 | #Clears image of all dots and pixel selections 672 | def clear(): 673 | global image,tk_image 674 | image = Image.open(imagePath) 675 | tk_image = ImageTk.PhotoImage(image) 676 | canvas2.create_image(0, 0, anchor=tk.NW, image=tk_image) 677 | 678 | # All of the Displays and their commands 679 | frame_grid = tk.Frame(canvas1, bg = 'white') 680 | frame_grid.grid(row=2, column=0, columnspan=4, sticky="ew") 681 | 682 | button = tk.Button(frame_grid, text="Clear Selected Pixels", command = updatePlot ) 683 | button.grid(row = 0, column=0, pady = 1) 684 | 685 | button2 = tk.Button(frame_grid, text="Generate Random Pixels", command = updatePlotforRand) 686 | button2.grid(row = 0, column=1, pady = 1) 687 | 688 | button4 = tk.Button(frame_grid, text="Show Random Pixels",command = showRandPixels) 689 | button4.grid(row = 0, column=2, pady = 1) 690 | 691 | entry2 = tk.Entry(frame_grid, width = 20) 692 | entry2.grid(row = 1, column=0, pady = 5) 693 | 694 | button3 = tk.Button(frame_grid, text= "Graph Every Pixel", command = inputForStep) 695 | button3.grid(row = 1, column=1, pady = 1) 696 | 697 | export_button = tk.Button(frame_grid, text= "Export File", command = export) 698 | export_button.grid(row=2, column=0) 699 | 700 | entry = tk.Entry(frame_grid, width=20) 701 | entry.grid(row = 1, column=2, pady = 5) 702 | 703 | slider = tk.Scale(frame_grid, from_= 1, to_= 8, orient=tk.HORIZONTAL, command = penValue) 704 | slider.grid (row = 2, column=1, pady = 5) 705 | 706 | clearbutton = tk.Button(frame_grid, text="Clear Image", command=clear) 707 | clearbutton.grid(row = 1, column=4, pady = 5) 708 | 709 | button5 = tk.Button(frame_grid, text="Enter Random Pixel Number", command=get_input) 710 | button5.grid(row = 1, column=3, pady = 5) 711 | 712 | button6 = tk.Button(frame_grid, text="Show Selected Pixels", command = showSelectedPixels) 713 | button6.grid(row = 0, column=3, pady = 5) 714 | 715 | button7 = tk.Button(frame_grid, text="Show All Pixels", command = showAllPixels) 716 | button7.grid(row = 0, column=4, pady = 5) 717 | 718 | help = tk.Button(frame_grid, text="Help!", command = help) 719 | help.grid(row = 2, column=2, pady = 5) 720 | 721 | PixelText = tk.Text(canvas1, height = 1, width = 40) 722 | PixelText.grid(row = 3, column=0) 723 | 724 | root.mainloop() 725 | 726 | -------------------------------------------------------------------------------- /hsi_toolkit/util/img_det.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | 3 | def img_det(det_func, hsi_img, tgt_sig, mask = None, **kwargs): 4 | """ 5 | Wrapper to use array based detector as a image based detector with the given mask 6 | 7 | Taylor C. Glenn 8 | 5/5/2018 - Edited by Alina Zare 9 | 10/2018 - Python Implementation by Yutai Zhou 10 | """ 11 | n_row, n_col, n_band = hsi_img.shape 12 | n_pixels = n_row * n_col 13 | if mask is None: 14 | mask = np.ones(n_row * n_col, dtype=bool) 15 | else: 16 | mask_rows, mask_cols = mask.shape 17 | mask = mask.reshape(mask_rows * mask_cols, order ='F').astype(bool) 18 | hsi_data = np.reshape(hsi_img, (n_pixels, n_band), order='F').T 19 | 20 | # Linearize image-like inputs 21 | # Mask linearized (n x n) pixel arguments 22 | for key, val in kwargs.items(): 23 | if type(val) == np.ndarray: # CHECK ME 24 | sz = val.shape 25 | if len(sz) == 2 and sz == (n_row, n_col): 26 | val = np.reshape(val,(1, n_pixels), order='F') 27 | val = val[mask == 1] 28 | kwargs[key] = val 29 | print('image input to img_det!1') 30 | 31 | elif len(sz) == 3 and sz[:2] == (n_row, n_col): 32 | val = np.reshape(val,(n_pix, sz[-1]), order='F').T 33 | val = val[:, mask == 1] 34 | kwargs[key] = val 35 | print('image input to img_det!2') 36 | 37 | det_data = np.empty(n_pixels) 38 | det_data[mask == 1], kwargsout = det_func(hsi_data[:, mask == 1], tgt_sig, kwargs) 39 | 40 | if len(kwargsout) > 0: 41 | # Reshape image-like flattened outputs back into images 42 | for key, val in kwargsout.items(): 43 | if type(val) is np.ndarray: 44 | if val.squeeze().ndim == 1 and val.size == np.sum(mask): 45 | tmp = np.empty(n_pixels) 46 | tmp[mask == 1] = val 47 | kwargsout[key] = np.reshape(tmp, (n_row, n_col), order='F') 48 | 49 | return np.reshape(det_data, (n_row, n_col), order='F'), kwargsout 50 | -------------------------------------------------------------------------------- /hsi_toolkit/util/img_seg.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | def img_seg(det_func, hsi_img, tgt_sig, segments, **kwargs): 3 | """ 4 | Segmented Detector Wrapper 5 | uses any detector with the signature detector(img,tgt_sig,mask,args...) 6 | as a segmented detector over the given segments 7 | 8 | Inputs: 9 | det_func - function handle for wrapped detector 10 | hsi_img - n_row x n_col x n_band hyperspectral image 11 | tgt_sig - target signature (n_band x 1 - column vector) 12 | segments - cell array of segment masks, n_row x n_col binary images 13 | varargin - variable array of arguments passed to the detector function 14 | 15 | Outputs: 16 | det_out - detector output image, concatenation of outputs from each segment 17 | NaN valued in pixels not contained by a segment 18 | varargout - other outputs, assumed to be images 19 | 20 | 8/20/2012 - Taylor C. Glenn 21 | 12/2018 - Python Implementation by Yutai Zhou 22 | """ 23 | n_seg, n_row, n_col = segments.shape 24 | det_out = np.empty((n_row, n_col)) 25 | segments = segments.astype(bool) 26 | 27 | for i in range(n_seg): 28 | out = det_func(hsi_img, tgt_sig, segments[i,:,:], **kwargs) 29 | if type(out) is tuple: 30 | out = list(out) 31 | seg_out = out[0] 32 | det_out[segments[i,:,:]] = seg_out[segments[i,:,:]] 33 | out[0] = det_out 34 | else: 35 | det_out[segments[i,:,:]] = out[segments[i,:,:]] 36 | out = det_out 37 | return out 38 | -------------------------------------------------------------------------------- /hsi_toolkit/util/pca.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import scipy as scp 3 | def pca(X, frac, mask = None): 4 | """ 5 | function [y,n_dim,vecs,vals,mu] = pca(x,frac,mask) 6 | 7 | Principal Components Analysis 8 | transforms input data x using PCA, reduces dimensionality 9 | to capture fraction of magnitude of eigenvalues 10 | 11 | inputs: 12 | x - input data M dimensions by N samples 13 | frac - [0-1] fractional amount of total eigenvalue magnitude to retain, 1 = no dimensionality reduction 14 | mask - binary mask of samples to include in covariance computation (use to mask off invalid pixels) 15 | leave off the argument or use [] for no mask 16 | 17 | outputs: 18 | y - PCA transformed dimensionality reduced data 19 | n_dim - number of dimensions in the output data 20 | vecs - full set of eigenvectors of covariance matrix (column vectors) 21 | vals - eigenvalues of covariance matrix 22 | mu - mean of input data, subtracted before rotating 23 | 24 | 8/7/2012 - Taylor C. Glenn - tcg@cise.ufl.edu 25 | 11/2018 - Python Implementation by Yutai Zhou 26 | """ 27 | n_dim, n_sample = X.shape 28 | if n_dim > 210: 29 | input('That is a lot of dimensions. It may crash. Press ctrl + c to stop execution, or enter to continue') 30 | 31 | mask = np.ones(n_sample, dtype=bool) if mask is None else mask 32 | mu = np.mean(X[:, mask == 1], 1) 33 | sigma = np.cov(X[:, mask == 1].T, rowvar=False) 34 | 35 | z = X - mu[:, np.newaxis] 36 | U, S, V = scp.linalg.svd(sigma, lapack_driver = 'gesdd') 37 | 38 | evecs = U 39 | 40 | mag = np.sum(S) 41 | ind = np.where(np.cumsum(S) / mag >= frac)[0][0] 42 | n_dim = ind 43 | y = evecs[:, :ind + 1].T @ z # d x 72 * 72 x 4488 44 | return y, n_dim, evecs, S, mu 45 | -------------------------------------------------------------------------------- /hsi_toolkit/util/rx_det.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | 3 | def rx_det(det_func, hsi_img, tgt_sig, mask = None, guard_win = 2, bg_win = 4, **kwargs): 4 | """ 5 | Wrapper to make an RX style sliding window detector given the local detection function 6 | 7 | Inputs: 8 | det_fun - detection function 9 | hsi_image - n_row x n_col x n_band hyperspectral image 10 | tgt_sig - target signature (n_band x 1 - column vector) 11 | mask - binary image limiting detector operation to pixels where mask is true 12 | if not present or empty, no mask restrictions are used 13 | guard_win - guard window radius (square,symmetric about pixel of interest) 14 | bg_win - background window radius 15 | 16 | Outputs: 17 | det_out - detector image 18 | 19 | 1/27/2013 - Taylor C. Glenn 20 | 10/2018 - Python Implementation by Yutai Zhou 21 | """ 22 | n_row, n_col, n_band = hsi_img.shape 23 | n_pixel = n_row * n_col 24 | 25 | mask = np.ones([n_row, n_col], dtype= bool) if mask is None else mask.astype(bool) 26 | 27 | # create the mask 28 | mask_width = 1 + 2 * guard_win + 2 * bg_win 29 | half_width = guard_win + bg_win 30 | 31 | b_mask = np.ones((mask_width, mask_width), dtype= bool) 32 | b_mask[bg_win:b_mask.shape[0] - bg_win, bg_win:b_mask.shape[0] - bg_win] = 0 33 | 34 | hsi_data = np.reshape(hsi_img, (n_pixel, n_band), order='F').T 35 | 36 | # get global image/segment statistics in case we need to fall back on them 37 | global_mu = np.mean(hsi_data[:, np.reshape(mask,-1,order='F') == 1], 1) 38 | global_cov = np.cov(hsi_data[:, np.reshape(mask,-1,order='F') == 1].T, rowvar = False) 39 | # s = np.float32(np.linalg.svd(global_cov, compute_uv=False)) 40 | # rcond = np.max(global_cov.shape)*np.spacing(np.float32(np.linalg.norm(s, ord=np.inf))) 41 | global_sig_inv = np.linalg.pinv(global_cov) 42 | 43 | args = { 44 | 'hsi_data': hsi_data, 45 | 'global_mu': global_mu, 46 | 'global_sig_inv': global_sig_inv, 47 | 'tgt_sig': tgt_sig, 48 | 'n_sig': tgt_sig.shape[1]} 49 | 50 | # run the detector (only on fully valid points) 51 | ind_img = np.reshape(np.array(range(n_pixel)), (n_row, n_col), order='F') 52 | out = np.empty((n_row, n_col)) 53 | det_stat = np.empty((n_row, n_col)) 54 | 55 | 56 | for i in range(n_col - mask_width + 1): 57 | if i % 10 == 0: 58 | print('.') 59 | 60 | for j in range(n_row - mask_width + 1): 61 | row = j + half_width 62 | col = i + half_width 63 | 64 | if mask[row, col] == 0: continue 65 | 66 | b_mask_img = np.zeros((n_row, n_col)) 67 | b_mask_img[j:mask_width + j, i:mask_width + i] = b_mask 68 | b_mask_img = np.logical_and(b_mask_img, mask) 69 | b_mask_list = np.reshape(b_mask_img, (b_mask_img.size), order='F') 70 | # pull out background and foreground points 71 | bg = hsi_data[:, b_mask_list == 1] 72 | ind = ind_img[row, col] 73 | x = hsi_data[:, ind] 74 | 75 | # compute detection statistic 76 | out[row, col], kwargout = det_func(x, ind, bg, b_mask_list, args, kwargs) 77 | 78 | if 'sig_index' in kwargout: 79 | det_stat[row, col] = kwargout['sig_index'] 80 | 81 | print('\n') 82 | return out, det_stat 83 | -------------------------------------------------------------------------------- /hsi_toolkit/util/unmix.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from cvxopt import matrix 3 | from cvxopt import solvers 4 | 5 | def unmix(data, endmembers): 6 | X = data 7 | 8 | n_endmember = endmembers.shape[1] 9 | n_pixel = X.shape[1] 10 | 11 | # equality constraint A * x = b 12 | # all values must sum to 1 (X1 + X2 + ... + XM = 1) 13 | A = matrix(np.ones((1,n_endmember)), tc='d') 14 | b = matrix(1.0, tc='d') 15 | 16 | # boundary constraints lb >= x >= ub 17 | # All values must be greater than 0 (0 < X1, 0 < X2 ... 0 < XM) 18 | G = np.zeros((2 * n_endmember, n_endmember)) 19 | G[:n_endmember,:] = -np.eye(n_endmember) 20 | G[n_endmember:,:] = np.eye(n_endmember) 21 | h = np.zeros(2 * n_endmember) 22 | h[n_endmember:] = 1.0 23 | 24 | G = matrix(G, tc='d') 25 | h = matrix(h, tc='d') 26 | 27 | H = matrix(np.float64(2 * (endmembers.T @ endmembers))) 28 | P = np.zeros((n_pixel,n_endmember)) 29 | 30 | solvers.options['show_progress'] = False 31 | for i in range(n_pixel): 32 | F = matrix(np.float64((-2 * X[:,i][np.newaxis,:] @ endmembers).T), tc='d') 33 | qp_out = solvers.qp(H, F, G, h, A, b) 34 | P[i,:] = np.array(qp_out['x']).T 35 | # print(np.array(qp_out['x']).shape) 36 | P[P<0] = 0 37 | 38 | return P 39 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | numpy 2 | scipy 3 | scikit-learn 4 | scikit-fuzzy 5 | cvxopt 6 | descartes 7 | SPICE-HSI 8 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [metadata] 2 | description-file = README.md 3 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | """A setuptools based setup module. 2 | See: 3 | https://packaging.python.org/en/latest/distributing.html 4 | https://github.com/pypa/sampleproject 5 | """ 6 | 7 | import sys 8 | import setuptools 9 | #from setuptools.command.test import test as TestCommand 10 | from codecs import open 11 | from os import path 12 | 13 | 14 | # class PyTest(TestCommand): 15 | # def initialize_options(self): 16 | # TestCommand.initialize_options(self) 17 | # self.pytest_args = ["--verbose", "tests/tests.py"] 18 | # 19 | # def finalize_options(self): 20 | # TestCommand.finalize_options(self) 21 | # self.test_args = [] 22 | # self.test_suite = True 23 | # 24 | # def run_tests(self): 25 | # import pytest 26 | # errno = pytest.main(self.pytest_args) 27 | # sys.exit(errno) 28 | 29 | 30 | here = path.abspath(path.dirname(__file__)) 31 | 32 | # Get the long description from the README file 33 | with open(path.join(here, 'README.md'), encoding='utf-8') as f: 34 | long_description = f.read() 35 | 36 | requirements_f = open('requirements.txt', 'r') 37 | dependencies = [ req for req in requirements_f.readlines() ] 38 | 39 | setuptools.setup( 40 | name='hsi_toolkit', 41 | 42 | # Versions should comply with PEP440. For a discussion on single-sourcing 43 | # the version across setup.py and the project code, see 44 | # https://packaging.python.org/en/latest/single_source_version.html 45 | 46 | description='GatorSense Hyperspectral Image Analysis Toolkit - Python Implementation', 47 | long_description=long_description, 48 | long_description_content_type='text/markdown', 49 | 50 | # The project's main homepage. 51 | url='https://faculty.eng.ufl.edu/machine-learning/', 52 | 53 | # Author details 54 | author='The GatorSense team', 55 | author_email='azare@ufl.edu', 56 | 57 | # Choose your license 58 | license='MIT', 59 | 60 | # Supported platforms 61 | platforms=['Any'], 62 | 63 | # Version number 64 | version=1.2, 65 | 66 | # See https://pypi.python.org/pypi?%3Aaction=list_classifiers 67 | classifiers=[ 68 | # How mature is this project? Common values are 69 | # 3 - Alpha 70 | # 4 - Beta 71 | # 5 - Production/Stable 72 | 'Development Status :: 5 - Production/Stable', 73 | 74 | # Indicate who your project is intended for 75 | 'Intended Audience :: Science/Research', 76 | 'Topic :: Scientific/Engineering :: Bio-Informatics', 77 | 78 | # Pick your license as you wish (should match "license" above) 79 | 'License :: OSI Approved :: MIT License', 80 | 81 | # Specify the Python versions you support here. In particular, ensure 82 | # that you indicate whether you support Python 2, Python 3 or both. 83 | 'Programming Language :: Python :: 3', 84 | 'Programming Language :: Python :: 3.6', 85 | 'Programming Language :: Python :: 3.7' 86 | ], 87 | 88 | # What does your project relate to? 89 | keywords='plant phenotyping bioinformatics hyperspectral machine-learning sensing', 90 | 91 | # You can just specify the packages manually here if your project is 92 | # simple. Or you can use find_packages(). 93 | packages=setuptools.find_packages(), 94 | 95 | # Alternatively, if you want to distribute just a my_module.py, uncomment 96 | # this: 97 | # py_modules=["my_module"], 98 | 99 | # List run-time dependencies here. These will be installed by pip when 100 | # your project is installed. For an analysis of "install_requires" vs pip's 101 | # requirements files see: 102 | # https://packaging.python.org/en/latest/requirements.html 103 | install_requires=dependencies, 104 | 105 | # List additional groups of dependencies here (e.g. development 106 | # dependencies). You can install these using the following syntax, 107 | # for example: 108 | # $ pip install -e .[dev,test] 109 | # extras_require={ 110 | # 'test': ['pytest-runner', 'pytest'], 111 | # }, 112 | setup_requires=["pytest-runner"], 113 | tests_require=['pytest'], 114 | ############ scripts=["plantcv-train.py", "plantcv-utils.py", "plantcv-workflow.py"] 115 | 116 | # If there are data files included in your packages that need to be 117 | # installed, specify them here. If using Python 2.6 or less, then these 118 | # have to be included in MANIFEST.in as well. 119 | # package_data={ 120 | # 'sample': ['package_data.dat'], 121 | # }, 122 | 123 | # Although 'package_data' is the preferred approach, in some case you may 124 | # need to place data files outside of your packages. See: 125 | # http://docs.python.org/3.4/distutils/setupscript.html#installing-additional-files # noqa 126 | # In this case, 'data_file' will be installed into '/my_data' 127 | # data_files=[('my_data', ['data/data_file'])], 128 | 129 | # To provide executable scripts, use entry points in preference to the 130 | # "scripts" keyword. Entry points provide cross-platform support and allow 131 | # pip to create the appropriate form of executable for the target platform. 132 | # entry_points={ 133 | # 'console_scripts': [ 134 | # 'sample=sample:main', 135 | # ], 136 | # }, 137 | ) --------------------------------------------------------------------------------