Commit 3cf1177f authored by Daniel Scheffler's avatar Daniel Scheffler
Browse files

Added SID_Classifier + tests.

parent 738014bd
Pipeline #3251 failed with stage
in 18 minutes and 15 seconds
......@@ -241,6 +241,7 @@ class SpectralHomogenizer(object):
'MinDist': Minimum Distance (Nearest Centroid) Classification
'kNN': k-Nearest-Neighbor Classification
'SAM': Spectral Angle Mapping
'SID': spectral information divergence
:param kNN_n_neighbors: The number of neighbors to be considered in case 'classif_alg' is set to 'kNN'.
Otherwise, this parameter is ignored.
:param nodataVal: no data value
......@@ -1547,6 +1548,7 @@ class RSImage_ClusterPredictor(object):
'MinDist': Minimum Distance (Nearest Centroid)
'kNN': k-nearest-neighbour
'SAM': spectral angle mapping
'SID': spectral information divergence
:param kNN_n_neighbors: The number of neighbors to be considered in case 'classif_alg' is set to
'kNN'. Otherwise, this parameter is ignored.
:param classifier_rootDir: root directory where machine learning classifiers are stored.
......
......@@ -163,10 +163,10 @@ class SAM_Classifier(_ImageClassifier):
return tilepos, cmap
@staticmethod
def _calc_sam(s1, s2, axis=0):
def _calc_sam(s1_norm, s2_norm, axis=0):
"""Compute spectral angle between two vectors or images (in radians)."""
upper = np.sum(s1 * s2, axis=axis)
lower = np.sqrt(np.sum(s1 * s1, axis=axis)) * np.sqrt(np.sum(s2 * s2, axis=axis))
upper = np.sum(s1_norm * s2_norm, axis=axis)
lower = np.sqrt(np.sum(s1_norm * s1_norm, axis=axis)) * np.sqrt(np.sum(s2_norm * s2_norm, axis=axis))
if lower.ndim > 1:
lower[lower == 0] = 1e-10
......@@ -179,6 +179,59 @@ class SAM_Classifier(_ImageClassifier):
return np.arccos(quotient)
class SID_Classifier(_ImageClassifier):
def __init__(self, train_spectra, CPUs=1):
# type: (np.ndarray, Union[int, None]) -> None
super(SID_Classifier, self).__init__(train_spectra, np.array(range(train_spectra.shape[0])), CPUs=CPUs)
self.clf_name = 'spectral information divergence (SID)'
def _predict(self, tilepos):
assert global_shared_endmembers is not None and global_shared_im2classify is not None
(rS, rE), (cS, cE) = tilepos
tileimdata = global_shared_im2classify[rS: rE + 1, cS: cE + 1, :]
endmembers = global_shared_endmembers # type: np.ndarray
if not tileimdata.shape[2] == self.train_spectra.shape[1]:
raise RuntimeError('Matrix dimensions are not aligned. Input image has %d bands but input spectra '
'have %d.' % (tileimdata.shape[2], self.train_spectra.shape[1]))
# normalize input data because SID asserts only data between -1 and 1
train_spectra_norm, tileimdata_norm = normalize_endmembers_image(endmembers, tileimdata)
sid = np.zeros((tileimdata.shape[0], tileimdata.shape[1], self.n_samples), np.float)
# if np.std(tileimdata) == 0: # skip tiles that only contain the same value
for n_sample in range(self.n_samples):
train_spectrum = train_spectra_norm[n_sample, :].reshape(1, 1, self.n_features)
sid[:, :, n_sample] = self._calc_sid(tileimdata_norm,
train_spectrum,
axis=2)
cmap = np.argmin(sid, axis=2).astype(np.int16)
return tilepos, cmap
@staticmethod
def _calc_sid(s1_norm, s2_norm, axis=0):
"""Compute the spectral information divergence between two vectors or images."""
def get_sum(x, axis=0):
s = np.sum(x, axis=axis)
s[s == 0] = 1e-10
return s
if s1_norm.ndim == 3 and s2_norm.ndim == 3:
p = (s1_norm / get_sum(s1_norm, axis=axis)[:, :, np.newaxis]) + np.spacing(1)
q = (s2_norm / get_sum(s1_norm, axis=axis)[:, :, np.newaxis]) + np.spacing(1)
else:
p = (s1_norm / get_sum(s1_norm, axis=axis)) + np.spacing(1)
q = (s2_norm / get_sum(s1_norm, axis=axis)) + np.spacing(1)
return np.sum(p * np.log(p / q) + q * np.log(q / p), axis=axis)
def classify_image(image, train_spectra, train_labels, classif_alg,
kNN_n_neighbors=10, nodataVal=None, tiledims=(1000, 1000), CPUs=None):
# type: (Union[np.ndarray, GeoArray], np.ndarray, Union[np.ndarray, List[int]], str, int, ...) -> GeoArray
......@@ -192,6 +245,7 @@ def classify_image(image, train_spectra, train_labels, classif_alg,
'MinDist': Minimum Distance (Nearest Centroid)
'kNN': k-nearest-neighbour
'SAM': spectral angle mapping
'SID': spectral information divergence
:param kNN_n_neighbors: The number of neighbors to be considered in case 'classif_alg' is set to
'kNN'. Otherwise, this parameter is ignored.
:param nodataVal:
......@@ -216,8 +270,13 @@ def classify_image(image, train_spectra, train_labels, classif_alg,
train_spectra,
CPUs=CPUs)
elif classif_alg == 'SID':
clf = SID_Classifier(
train_spectra,
CPUs=CPUs)
else:
raise NotImplementedError("Currently only the methods 'kNN', 'MinDist' and 'SAM' are implemented.")
raise NotImplementedError("Currently only the methods 'kNN', 'MinDist', 'SAM' and 'SID' are implemented.")
cmap = clf.classify(image, nodataVal=nodataVal, tiledims=tiledims)
......
......@@ -126,7 +126,7 @@ gms_schema_input = dict(
delete_output=dict(type='boolean', required=False),
spechomo_method=dict(type='string', required=False, allowed=['LI', 'LR', 'RR', 'QR', 'RFR']),
spechomo_n_clusters=dict(type='integer', required=False, allowed=[1, 5, 10, 15, 20, 30, 40, 50]),
spechomo_classif_alg=dict(type='string', required=False, allowed=['MinDist', 'kNN', 'SAM']),
spechomo_classif_alg=dict(type='string', required=False, allowed=['MinDist', 'kNN', 'SAM', 'SID']),
spechomo_kNN_n_neighbors=dict(type='integer', required=False, min=0),
spechomo_estimate_accuracy=dict(type='boolean', required=False),
spechomo_bandwise_accuracy=dict(type='boolean', required=False),
......
......@@ -20,6 +20,7 @@ from gms_preprocessing import set_config
from gms_preprocessing.algorithms.classification import MinimumDistance_Classifier
from gms_preprocessing.algorithms.classification import kNN_Classifier
from gms_preprocessing.algorithms.classification import SAM_Classifier
from gms_preprocessing.algorithms.classification import SID_Classifier
from . import db_host
......@@ -82,3 +83,18 @@ class Test_SAM_Classifier(unittest.TestCase):
self.assertEqual(cmap_mp.shape, (1010, 1010))
self.assertTrue(np.array_equal(cmap_sp, cmap_mp))
class Test_SID_Classifier(unittest.TestCase):
def test_classify(self):
SC = SID_Classifier(cluster_centers, CPUs=1)
cmap_sp = SC.classify(test_gA, nodataVal=-9999, tiledims=(400, 200))
self.assertIsInstance(cmap_sp, np.ndarray)
self.assertEqual(cmap_sp.shape, (1010, 1010))
SC = SID_Classifier(cluster_centers, CPUs=None)
cmap_mp = SC.classify(test_gA, nodataVal=-9999, tiledims=(400, 200))
self.assertIsInstance(cmap_mp, np.ndarray)
self.assertEqual(cmap_mp.shape, (1010, 1010))
self.assertTrue(np.array_equal(cmap_sp, cmap_mp))
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment