Commit 3d5179f7 authored by Daniel Scheffler's avatar Daniel Scheffler

Merge branch 'enhancement/improve_nodata_value_handling' into 'master'

Enhancement/improve nodata value handling

See merge request !3
parents 8d362c57 f930d2bc
Pipeline #3923 passed with stages
in 1 minute and 24 seconds
......@@ -52,14 +52,17 @@ __author__ = 'Daniel Scheffler'
class GeoArray(object):
"""
This class creates a fast Python interface for geodata - either on disk or in memory. It can be instanced
with a file path or with a numpy array and the corresponding geoinformation. Instances can always be indexed
like normal numpy arrays, no matter if GeoArray has been instanced from file or from an in-memory array.
GeoArray provides a wide range of geo-related attributes belonging to the dataset as well as some functions for
quickly visualizing the data as a map, a simple image or an interactive image.
"""
def __init__(self, path_or_array, geotransform=None, projection=None, bandnames=None, nodata=None, progress=True,
q=False):
# type: (Union[str, np.ndarray], tuple, str, list, float, bool, bool) -> None
"""This class creates a fast Python interface for geodata - either on disk or in memory. It can be instanced with
a file path or with a numpy array and the corresponding geoinformation. Instances can always be indexed like
normal numpy arrays, no matter if GeoArray has been instanced from file or from an in-memory array. GeoArray
provides a wide range of geo-related attributes belonging to the dataset as well as some functions for quickly
visualizing the data as a map, a simple image or an interactive image.
"""Get an instance of GeoArray.
:param path_or_array: a numpy.ndarray or a valid file path
:param geotransform: GDAL geotransform of the given array or file on disk
......@@ -354,7 +357,7 @@ class GeoArray(object):
if not self.is_inmem:
self.set_gdalDataset_meta()
if self._nodata is None:
self._nodata = self.find_noDataVal()
self.find_noDataVal()
if self._nodata == 'ambiguous':
warnings.warn('Nodata value could not be clearly identified. It has been set to None.')
self._nodata = None
......@@ -369,6 +372,9 @@ class GeoArray(object):
# type: (Union[int, None]) -> None
self._nodata = value
if self._metadata and value is not None:
self.metadata.global_meta.update({'data ignore value': str(value)})
@property
def mask_nodata(self):
"""
......@@ -515,7 +521,7 @@ class GeoArray(object):
if self._metadata is not None:
return self._metadata
else:
default = GDAL_Metadata(nbands=self.bands)
default = GDAL_Metadata(nbands=self.bands, nodata_allbands=self._nodata)
self._metadata = default
if not self.is_inmem:
......@@ -705,6 +711,7 @@ class GeoArray(object):
else:
nodata = None
self.nodata = nodata
return nodata
def set_gdalDataset_meta(self):
......@@ -736,12 +743,12 @@ class GeoArray(object):
if 'nodata' not in self._initParams or self._initParams['nodata'] is None:
band = ds.GetRasterBand(1)
# FIXME this does not support different nodata values within the same file
self._nodata = band.GetNoDataValue()
self.nodata = band.GetNoDataValue()
# set metadata attribute
if self.is_inmem or not self.filePath:
# metadata cannot be read from disk -> set it to the default
self._metadata = GDAL_Metadata(nbands=self.bands)
self._metadata = GDAL_Metadata(nbands=self.bands, nodata_allbands=self._nodata)
else:
self._metadata = GDAL_Metadata(filePath=self.filePath)
......@@ -977,27 +984,27 @@ class GeoArray(object):
gdal.Unlink(out_path + '.aux.xml')
elif self.metadata.all_meta:
# set global domain metadata
if self.metadata.global_meta:
ds_out.SetMetadata(dict((k, repr(v)) for k, v in self.metadata.global_meta.items()))
# set global domain metadata
if self.metadata.global_meta:
ds_out.SetMetadata(dict((k, repr(v)) for k, v in self.metadata.global_meta.items()))
if 'description' in envi_metadict:
ds_out.SetDescription(envi_metadict['description'])
if 'description' in envi_metadict:
ds_out.SetDescription(envi_metadict['description'])
# set band domain metadata
bandmeta_dict = self.metadata.to_DataFrame().astype(str).to_dict()
# set band domain metadata
bandmeta_dict = self.metadata.to_DataFrame().astype(str).to_dict()
for bidx in range(self.bands):
band = ds_out.GetRasterBand(bidx + 1)
bandmeta = bandmeta_dict[bidx]
# meta2write = dict((k, repr(v)) for k, v in self.metadata.band_meta.items() if v is not np.nan)
band.SetMetadata(bandmeta)
for bidx in range(self.bands):
band = ds_out.GetRasterBand(bidx + 1)
bandmeta = bandmeta_dict[bidx]
# meta2write = dict((k, repr(v)) for k, v in self.metadata.band_meta.items() if v is not np.nan)
band.SetMetadata(bandmeta)
if 'band_names' in envi_metadict:
band.SetDescription(self.metadata.band_meta['band_names'][bidx].strip())
if 'band_names' in envi_metadict:
band.SetDescription(self.metadata.band_meta['band_names'][bidx].strip())
band.FlushCache()
del band
band.FlushCache()
del band
ds_out.FlushCache()
del ds_out
......@@ -1507,7 +1514,7 @@ class GeoArray(object):
def get_subset(self, xslice=None, yslice=None, zslice=None, return_GeoArray=True,
reset_bandnames=False):
# type: (slice, slice, slice, bool, bool) -> GeoArray
"""Returns a new instance of GeoArray representing a subset of the initial one wit respect to given array position.
"""Return a new GeoArray instance representing a subset of the initial one wit respect to given array position.
:param xslice: a slice providing the X-position for the subset in the form slice(xstart, xend, xstep)
:param yslice: a slice providing the Y-position for the subset in the form slice(ystart, yend, ystep)
......
......@@ -4,6 +4,7 @@ import os
from pprint import pformat
from copy import deepcopy
from typing import Union # noqa F401 # flake8 issue
from collections import OrderedDict
from geopandas import GeoDataFrame, GeoSeries
import numpy as np
......@@ -27,18 +28,22 @@ autohandled_meta = [
class GDAL_Metadata(object):
def __init__(self, filePath='', nbands=1):
def __init__(self, filePath='', nbands=1, nodata_allbands=None):
# privates
self._global_meta = dict()
self._band_meta = dict()
self._global_meta = OrderedDict()
self._band_meta = OrderedDict()
self.bands = nbands
self.filePath = filePath
self.fileFormat = ''
self.nodata_allbands = nodata_allbands
if filePath:
self.read_from_file(filePath)
if nodata_allbands is not None:
self.global_meta.update({'data ignore value': str(nodata_allbands)})
@classmethod
def from_file(cls, filePath):
return GDAL_Metadata(filePath=filePath)
......@@ -73,8 +78,8 @@ class GDAL_Metadata(object):
@band_meta.setter
def band_meta(self, meta_dict):
if not isinstance(meta_dict, dict):
raise TypeError("Expected type 'dict', received '%s'." % type(meta_dict))
if not isinstance(meta_dict, (dict, OrderedDict)):
raise TypeError("Expected type 'dict'/'OrderedDict', received '%s'." % type(meta_dict))
for k, v in meta_dict.items():
if not isinstance(v, list):
......@@ -83,11 +88,11 @@ class GDAL_Metadata(object):
raise ValueError("The length of the given lists must be equal to the number of bands. "
"Received a list with %d items for '%s'." % (len(v), k))
self._band_meta = meta_dict # TODO convert strings to useful types
self._band_meta = OrderedDict(meta_dict) # TODO convert strings to useful types
@property
def all_meta(self):
all_meta = self.global_meta.copy()
all_meta = OrderedDict(self.global_meta.copy())
all_meta.update(self.band_meta)
return all_meta
......@@ -190,8 +195,8 @@ class GDAL_Metadata(object):
return 'Metadata: \n\n' + pformat(self.all_meta)
def to_ENVI_metadict(self):
return dict(zip(self.all_meta.keys(),
[self._convert_param_to_ENVI_str(i) for i in self.all_meta.values()]))
return OrderedDict(zip(self.all_meta.keys(),
[self._convert_param_to_ENVI_str(i) for i in self.all_meta.values()]))
def get_subset(self, bands2extract=None, keys2extract=None):
# type: (Union[slice, list, np.ndarray], Union[str, list]) -> 'GDAL_Metadata'
......
__version__ = '0.8.13'
__versionalias__ = '20190329.02'
__version__ = '0.8.14'
__versionalias__ = '20190329.03'
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