Skip to content

Commit

Permalink
Merge branch 'enhancement/add_jsonclf' into 'master'
Browse files Browse the repository at this point in the history
JSON-able classifiers

See merge request geomultisens/spechomo!16
  • Loading branch information
danschef committed Dec 15, 2020
2 parents bf370a1 + fab1813 commit 43780a9
Show file tree
Hide file tree
Showing 4 changed files with 42 additions and 21 deletions.
7 changes: 7 additions & 0 deletions HISTORY.rst
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,13 @@
History
=======

0.9.2 (2020-12-15)
------------------

* Cluster classifiers can now be saved as JSON files.
* Added attributes 'spechomo_version' and 'spechomo_versionalias' to ClusterLearner.


0.9.1 (2020-12-11)
------------------

Expand Down
45 changes: 28 additions & 17 deletions spechomo/classifier.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,10 @@ def __init__(self, dict_clust_MLinstances, global_classifier):
self.tgt_wavelengths = sample_MLinst.tgt_wavelengths
self.n_clusters = sample_MLinst.n_clusters
self.cluster_centers = np.array([cc.cluster_center for cc in self.MLdict.values()])
self.spechomo_version = \
sample_MLinst.spechomo_version if hasattr(sample_MLinst, 'spechomo_version') else 'NA'
self.spechomo_versionalias = \
sample_MLinst.spechomo_versionalias if hasattr(sample_MLinst, 'spechomo_versionalias') else 'NA'

@classmethod
def from_disk(cls, classifier_rootDir, method, n_clusters,
Expand Down Expand Up @@ -252,7 +256,7 @@ def predict_weighted_averages(self, im_src, cmap_3D, weights_3D=None, nodataVal=
NOTE: This version of the prediction function uses the prediction coefficients of multiple spectral clusters
and computes the result as weighted average of them. Therefore, the classification map must assign
multiple spectral cluster to each input pixel.
multiple spectral clusters to each input pixel.
# NOTE: At unclassified pixels (cmap_3D[y,x,z>0] == -1) the prediction result using global coefficients
# is ignored in the weighted average. In that case the prediction result is based on the found valid
Expand Down Expand Up @@ -280,26 +284,35 @@ def predict_weighted_averages(self, im_src, cmap_3D, weights_3D=None, nodataVal=

for band in range(cmap_3D.shape[2]):
ims_pred_temp.append(
self.predict(im_src, cmap_3D[:, :, band],
self.predict(im_src,
cmap_3D[:, :, band],
nodataVal=nodataVal,
cmap_nodataVal=cmap_nodataVal,
cmap_unclassifiedVal=cmap_unclassifiedVal
))

# merge classification results by weighted averaging
nsamp, nbandpred, nbandscmap = np.dot(*weights_3D.shape[:2]), ims_pred_temp[0].shape[2], weights_3D.shape[2]
nsamp = np.dot(*weights_3D.shape[:2])
nbandpred = ims_pred_temp[0].shape[2]
nbandscmap = weights_3D.shape[2]

weights = \
np.ones((nsamp, nbandpred, nbandscmap)) if weights_3D is None else \
np.tile(weights_3D.reshape(nsamp, 1, nbandscmap), (1, nbandpred, 1)) # nclust x n_tgt_bands x n_cmap_bands
np.tile(weights_3D.reshape((nsamp, 1, nbandscmap)),
(1, nbandpred, 1)) # nclust x n_tgt_bands x n_cmap_bands

# set weighting of unclassified pixel positions to zero (except from the first cmap band)
# -> see NOTE #2 in the docstring
# mask_unclassif = np.tile(cmap_3D.reshape(nsamp, 1, nbandscmap), (1, nbandpred, 1)) == cmap_unclassifiedVal
# mask_unclassif[:, :, :1] = False # if all other clusters are invalid, at least the first one is used for prediction # noqa
# weights[mask_unclassif] = 0

spectra_pred = np.average(np.dstack([im2spectra(im) for im in ims_pred_temp]), weights=weights, axis=2)
im_pred = spectra2im(spectra_pred, tgt_rows=im_src.shape[0], tgt_cols=im_src.shape[1])
spectra_pred = np.average(np.dstack([im2spectra(im) for im in ims_pred_temp]),
weights=weights,
axis=2)
im_pred = spectra2im(spectra_pred,
tgt_rows=im_src.shape[0],
tgt_cols=im_src.shape[1])

return im_pred

Expand Down Expand Up @@ -396,7 +409,8 @@ def print_stats(self):
def to_jsonable_dict(self):
"""Create a dictionary containing a JSONable replicate of the current Cluster_Learner instance."""
common_meta_keys = ['src_satellite', 'src_sensor', 'tgt_satellite', 'tgt_sensor', 'src_LBA', 'tgt_LBA',
'src_n_bands', 'tgt_n_bands', 'src_wavelengths', 'tgt_wavelengths', 'n_clusters']
'src_n_bands', 'tgt_n_bands', 'src_wavelengths', 'tgt_wavelengths', 'n_clusters',
'spechomo_version', 'spechomo_versionalias']
jsonable_dict = dict()
decode_types_dict = dict()

Expand All @@ -422,16 +436,13 @@ def to_jsonable_dict(self):

return jsonable_dict

# def save_to_json(self, filepath):
# dict2save = dict(
# cluster_centers=self.cluster_centers.tolist(),
#
# )
#
# # Create json and save to file
# json_txt = json.dumps(dict2save, indent=4)
# with open(filepath, 'w') as file:
# file.write(json_txt)
def save_to_json(self, filepath):
jsonable_dict = self.to_jsonable_dict()

# Create json and save to file
json_txt = json.dumps(jsonable_dict, sort_keys=True, indent=4)
with open(filepath, 'w') as file:
file.write(json_txt)


class ClassifierCollection(object):
Expand Down
3 changes: 3 additions & 0 deletions spechomo/classifier_creation.py
Original file line number Diff line number Diff line change
Expand Up @@ -521,6 +521,9 @@ def mean_absolute_percentage_error(y_true, y_pred):
ML.rmse_per_band = list(rmse)
ML.mae_per_band = list(mae)
ML.mape_per_band = list(mape)
from .version import __version__, __versionalias__
ML.spechomo_version = __version__
ML.spechomo_versionalias = __versionalias__

# convert float64 attributes to float32 to save memory (affects <0,05% of homogenized pixels by 1 DN)
for attr in ['coef_', 'intercept_', 'singular_', '_residues']:
Expand Down
8 changes: 4 additions & 4 deletions tests/test_classifier.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@
import os
import json
from unittest import TestCase
# from tempfile import TemporaryDirectory
from tempfile import TemporaryDirectory

from spechomo.classifier import Cluster_Learner
from spechomo import __path__
Expand Down Expand Up @@ -72,9 +72,9 @@ def test_to_jsonable_dict(self):
outstr = json.dumps(jsonable_dict, sort_keys=True, indent=4)
self.assertIsInstance(outstr, str)

# def test_save_to_json(self):
# with TemporaryDirectory() as tmpDir:
# self.clf.save_to_json(os.path.join(tmpDir, 'clf.json'))
def test_save_to_json(self):
with TemporaryDirectory() as tmpDir:
self.clf.save_to_json(os.path.join(tmpDir, 'clf.json'))


# class Test_ClassifierCollection(TestCase):
Expand Down

0 comments on commit 43780a9

Please sign in to comment.