# -*- coding: utf-8 -*-
"""
| ----------------------------------------------------------------------------------------------------------------------
| Date : June 2020
| Copyright : © 2020 by Arthur Maenhout (Locus) and Ann Crabbé (KU Leuven)
| Email : acrabbe.foss@gmail.com
| Acknowledgements : Jeroen Degerickx (KU Leuven). Documentation: https://doi.org/10.3390/rs9060565.
|
| This file is part of the Spectral Libraries QGIS plugin and python package.
|
| This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public
| License as published by the Free Software Foundation, either version 3 of the License, or any later version.
|
| This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
| warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
| You should have received a copy of the GNU General Public License (COPYING.txt). If not see www.gnu.org/licenses.
| ----------------------------------------------------------------------------------------------------------------------
"""
import numpy as np
from spectral_libraries.core.music import Music
[docs]class Amuses:
"""
AMUSES Prunes a spectral library with regard to an image and internal redundancy.
Degerickx, J.; Okujeni, A.; Iordache, M.-D.; Hermy, M.; van der Linden, S.; Somers, B.
A Novel Spectral Library Pruning Technique for Spectral Unmixing of Urban Land Cover.
Remote Sens. 2017, 9, 565.
"""
def __init__(self):
pass
[docs] @staticmethod
def execute(image: np.array, library: np.array, music_pct: float = 0.9, amuses_pct: float = 0.95, min_eig: int = 15,
thresholds: tuple = (0.0002, 0.02), log: callable = print, set_progress: callable = None) -> dict:
"""
AMUSES Prunes a spectral library with regard to an image and internal redundancy.
:param image: image used for pruning (bands x rows x columns), reflectance, no bad bands
:param library: the original spectral library to be pruned (bands x spectra), reflectance, no bad bands
:param music_pct: final pruned library size for the MUSIC algorithm (in pct)
:param amuses_pct: library size for the AMUSES algorithm (in pct)
:param min_eig: minimum number of eigenvectors to be retained from the image to calculate MUSIC distances,
:param thresholds: low and high threshold for the AMUSES algorithm
:param log: log function
:param set_progress: communicate progress (refer to the progress bar in case of GUI; otherwise print to console)
:return: dictionary with each output metric as a numpy array in a key-value pair
- music_indices: library indices after pruning with the MUSIC algorithm
- music_eigenvectors: first kf eigenvectors of image subspace (needed for sparse unmixing)
kf = estimated number of eigenvectors in the image estimated by Hysime algorithm
- music_distances: MUSIC distances of the pruned library spectra to the image, sorted from low to high
- amuses_indices: library indices after pruning with the AMUSES algorithm
"""
log = log if log else print
set_progress = set_progress if set_progress else printProgress
music_size = int(np.round(library.shape[1] * music_pct))
amuses_first_index = int(np.round(library.shape[1] * (1-amuses_pct)))
set_progress(2)
# run music
result = Music().execute(image=image, library=library, library_size=music_size, min_eig=min_eig, log=log)
music_distances = result['music_distances'][amuses_first_index:]
music_indices = result['music_indices']
set_progress(50)
# set threshold
min_distance = min(music_distances)
max_distance = max(music_distances)
threshold = (((music_distances - min_distance) / (max_distance - min_distance)) * thresholds[1]) + thresholds[0]
threshold = np.hstack((np.tile(thresholds[0], amuses_first_index), threshold))
set_progress(52)
# start by selecting the first music spectrum
log('AMUSES: Iteratively select most similar spectra')
amuses_indices = np.array([music_indices[0]])
progress = 52
# run through MUSIC result and select only spectra with similarity > threshold
for j in range(1, music_size):
stop = 0
check = 0
i = 0
while not stop:
# Combination of Jeffries-Matusita Distance and Spectral Angle
# Padma, 2014, JM based mixed-measure for improved spectral matching in hyperspectral image analysis
x1 = library[:, music_indices[j]]
x2 = library[:, amuses_indices[i]]
jmd = np.sqrt(np.sum((np.sqrt(x1 / np.sum(x1)) - np.sqrt(x2 / np.sum(x2))) ** 2))
sam = np.arccos(np.dot(x1.T, x2) / (np.linalg.norm(x1) * np.linalg.norm(x2)))
jmd_sam = jmd * np.tan(sam)
# if threshold is reached: stop loop and do not set check flag
stop = (jmd_sam < threshold[j])
i = i + 1
# if threshold is never reached for selected indices: stop loop and set check flag
if (i >= len(amuses_indices)) and (stop == 0):
check = 1
stop = 1
if check:
amuses_indices = np.hstack((amuses_indices, music_indices[j]))
new_progress = 52 + int(j / music_size * 0.4 * 100)
if new_progress >= progress + 5:
progress = new_progress
set_progress(progress)
log('AMUSES: ' + str(len(amuses_indices)) + ' spectra selected')
result['amuses_indices'] = amuses_indices
set_progress(95)
return result
[docs]def printProgress(value: int):
""" Replacement for the GUI progress bar """
print('progress: {} %'.format(value))