Commit cbcbc87f authored by Daniel Scheffler's avatar Daniel Scheffler
Browse files

Revised nodata value handling of RSImage_ClusterPredictor.predict() and added...

Revised nodata value handling of RSImage_ClusterPredictor.predict() and added possibility to statically set the nodata value of the predicted image.
parent 7edd346e
Pipeline #3253 failed with stage
in 18 minutes and 46 seconds
......@@ -293,7 +293,7 @@ class SpectralHomogenizer(object):
if cls:
self.logger.info('Performing spectral homogenization using %s. Target is %s %s %s.'
% (method, tgt_satellite, tgt_sensor, tgt_LBA))
im_homo = PR.predict(arrcube, classifier=cls, nodataVal=nodataVal, cmap_nodataVal=nodataVal)
im_homo = PR.predict(arrcube, classifier=cls, in_nodataVal=nodataVal, cmap_nodataVal=nodataVal)
if compute_errors:
errors = PR.compute_prediction_errors(im_homo, cls, nodataVal=nodataVal, cmap_nodataVal=nodataVal)
......@@ -1627,23 +1627,27 @@ class RSImage_ClusterPredictor(object):
return Cluster_Learner(dict_clust_MLinstances)
def predict(self, image, classifier, nodataVal=None, cmap_nodataVal=None, CPUs=1):
# type: (Union[np.ndarray, GeoArray], Cluster_Learner, Union[None, float], Union[None, float], int) -> GeoArray
def predict(self, image, classifier, in_nodataVal=None, out_nodataVal=None, cmap_nodataVal=None, CPUs=1):
# type: (Union[np.ndarray, GeoArray], Cluster_Learner, float, float, float, int) -> GeoArray
"""Apply the prediction function of the given specifier to the given remote sensing image.
# NOTE: The 'nodataVal' is written
:param image: 3D array representing the input image
:param classifier: the classifier instance
:param nodataVal: no data value of the input image
:param in_nodataVal: no data value of the input image
(auto-computed if not given or contained in image GeoArray)
:param out_nodataVal: no data value written into the predicted image
(copied from the input image if not given)
:param cmap_nodataVal: no data value for the classification map
in case more than one sub-classes are used for prediction
:param CPUs: CPUs to use (default: 1)
:return: 3D array representing the predicted spectral image cube
"""
image = image if isinstance(image, GeoArray) else GeoArray(image, nodata=nodataVal)
image.nodata = nodataVal if nodataVal is not None else image.nodata # might be auto-computed here
image = image if isinstance(image, GeoArray) else GeoArray(image, nodata=in_nodataVal)
# ensure image.nodata is present (important for classify_image() -> overwrites cmap at nodata positions)
image.nodata = in_nodataVal if in_nodataVal is not None else image.nodata # might be auto-computed here
# assign each input pixel to a cluster (compute classfication with cluster centers as endmembers)
if not self.classif_map:
......@@ -1654,7 +1658,8 @@ class RSImage_ClusterPredictor(object):
classifier.cluster_pixVals,
classif_alg=self.classif_alg,
kNN_n_neighbors=self.kNN_n_neighbors,
nodataVal=cmap_nodataVal, # written into classif_map at nodata pixels
in_nodataVal=image.nodata,
cmap_nodataVal=cmap_nodataVal, # written into classif_map at nodata
CPUs=self.CPUs)
self.logger.info('Total classification time: %s'
% time.strftime("%H:%M:%S", time.gmtime(time.time() - t0)))
......@@ -1668,8 +1673,9 @@ class RSImage_ClusterPredictor(object):
# apply prediction
# NOTE: prediction is applied in 1000 x 1000 tiles to save memory (because classifier.predict returns float32)
out_nodataVal = out_nodataVal if out_nodataVal is not None else image.nodata
image_predicted = GeoArray(np.empty((image.rows, image.cols, classifier.tgt_n_bands), dtype=image.dtype),
geotransform=image.gt, projection=image.prj, nodata=image.nodata,
geotransform=image.gt, projection=image.prj, nodata=out_nodataVal,
bandnames=GMS_object.LBA2bandnames(classifier.tgt_LBA))
t0 = time.time()
......@@ -1679,15 +1685,16 @@ class RSImage_ClusterPredictor(object):
classif_map_tile = self.classif_map[rS: rE+1, cS: cE+1] # integer array
# predict!
im_tile_pred = classifier.predict(im_tile, classif_map_tile,
nodataVal=nodataVal, cmap_nodataVal=cmap_nodataVal).astype(image.dtype)
im_tile_pred = \
classifier.predict(im_tile, classif_map_tile,
nodataVal=out_nodataVal, cmap_nodataVal=cmap_nodataVal).astype(image.dtype)
image_predicted[rS:rE + 1, cS:cE + 1] = im_tile_pred
self.logger.info('Total prediction time: %s' % time.strftime("%H:%M:%S", time.gmtime(time.time()-t0)))
# re-apply nodata values to predicted result
if image.nodata is not None:
image_predicted[image.mask_nodata[:] == 0] = image.nodata
image_predicted[image.mask_nodata[:] == 0] = out_nodataVal
# copy mask_nodata
image_predicted.mask_nodata = image.mask_nodata
......@@ -1707,6 +1714,7 @@ class RSImage_ClusterPredictor(object):
:param cluster_classifier: instance of Cluster_Learner
:param nodataVal: no data value of the input image
(auto-computed if not given or contained in im_predicted GeoArray)
NOTE: The value is also used as output nodata value for the errors array.
:param cmap_nodataVal: no data value for the classification map
in case more than one sub-classes are used for prediction
:return: 3D array (int16) representing prediction errors per band and pixel
......
......@@ -44,15 +44,23 @@ class _ImageClassifier(object):
def _predict(self, tilepos):
raise NotImplementedError('This method has to be implemented by the subclass.')
def classify(self, image_cube, nodataVal=None, tiledims=(250, 250)):
image_cube_gA = GeoArray(image_cube, nodata=nodataVal)
def classify(self, image_cube, in_nodataVal=None, cmap_nodataVal=None, tiledims=(250, 250)):
"""
:param image_cube:
:param in_nodataVal:
:param cmap_nodataVal: written into classif_map at nodata pixels
:param tiledims:
:return:
"""
image_cube_gA = GeoArray(image_cube, nodata=in_nodataVal)
image_cube_gA.to_mem()
bounds_alltiles = get_array_tilebounds(image_cube_gA.shape, tiledims)
# use a local variable to avoid pickling in multiprocessing
cmap = GeoArray(np.empty((image_cube_gA.rows, image_cube_gA.cols),
dtype=np.array(self.train_labels).dtype), nodata=nodataVal)
dtype=np.array(self.train_labels).dtype), nodata=cmap_nodataVal)
print('Performing %s image classification...' % self.clf_name)
if self.CPUs is None or self.CPUs > 1:
......@@ -68,8 +76,8 @@ class _ImageClassifier(object):
# print('Performing classification for tile ((%s, %s), (%s, %s))...' % (rS, rE, cS, cE))
cmap[rS: rE + 1, cS: cE + 1] = self._predict(((rS, rE), (cS, cE)))[1]
if nodataVal is not None:
cmap[image_cube_gA.mask_nodata.astype(np.int8) == 0] = nodataVal
if cmap_nodataVal is not None:
cmap[image_cube_gA.mask_nodata.astype(np.int8) == 0] = cmap_nodataVal
self.cmap = cmap.astype(image_cube.dtype)
......@@ -231,7 +239,7 @@ class SID_Classifier(_ImageClassifier):
def classify_image(image, train_spectra, train_labels, classif_alg,
kNN_n_neighbors=10, nodataVal=None, tiledims=(1000, 1000), CPUs=None):
kNN_n_neighbors=10, in_nodataVal=None, cmap_nodataVal=None, tiledims=(1000, 1000), CPUs=None):
# type: (Union[np.ndarray, GeoArray], np.ndarray, Union[np.ndarray, List[int]], str, int, ...) -> GeoArray
"""Classify image to find the cluster each spectrum belongs to.
......@@ -246,7 +254,7 @@ def classify_image(image, train_spectra, train_labels, classif_alg,
'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:
:param in_nodataVal:
:param tiledims:
:param CPUs: number of CPUs to be used for classification
"""
......@@ -276,7 +284,7 @@ def classify_image(image, train_spectra, train_labels, classif_alg,
else:
raise NotImplementedError("Currently only the methods 'kNN', 'MinDist', 'SAM' and 'SID' are implemented.")
cmap = clf.classify(image, nodataVal=nodataVal, tiledims=tiledims)
cmap = clf.classify(image, in_nodataVal=in_nodataVal, cmap_nodataVal=cmap_nodataVal, tiledims=tiledims)
return cmap
......
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