diff --git a/enpt/model/images.py b/enpt/model/images.py index 7238bb2d41f565acc15ae1e1c7f19fd0e072f445..f2873485bdd49961062d6982d289da78c048b997 100644 --- a/enpt/model/images.py +++ b/enpt/model/images.py @@ -9,8 +9,7 @@ from geoarray import GeoArray, NoDataMask, CloudMask from ..utils.path_generator import PathGenL1BProduct from ..utils.logging import EnPT_Logger -from ..model.metadata import \ - EnMAP_Metadata_L1B_SensorGeo, EnMAP_Metadata_L1B_VNIR_SensorGeo, EnMAP_Metadata_L1B_SWIR_SensorGeo +from ..model.metadata import EnMAP_Metadata_L1B_SensorGeo, EnMAP_Metadata_L1B_Detector_SensorGeo ############## @@ -175,41 +174,6 @@ class _EnMAP_Image(object): def data(self): self._arr = None - @property - def mask_nodata(self): - """Return the no data mask. - - Bundled with all the corresponding metadata. - - For usage instructions and a list of attributes refer to help(self.data). - self.mask_nodata works in the same way. - - :return: instance of geoarray.NoDataMask - """ - if self._mask_nodata is None and isinstance(self.data, GeoArray): - self.logger.info('Calculating nodata mask...') - self._mask_nodata = self.data.mask_nodata # calculates mask nodata if not already present - - return self._mask_nodata - - @mask_nodata.setter - def mask_nodata(self, *geoArr_initArgs): - if geoArr_initArgs[0] is not None: - nd = NoDataMask(*geoArr_initArgs) - if nd.shape[:2] != self.data.shape[:2]: - raise ValueError("The 'mask_nodata' GeoArray can only be instanced with an array of the " - "same dimensions like _EnMAP_Image.arr. Got %s." % str(nd.shape)) - nd.nodata = False - nd.gt = self.data.gt - nd.prj = self.data.prj - self._mask_nodata.prj = nd - else: - del self.mask_nodata - - @mask_nodata.deleter - def mask_nodata(self): - self._mask_nodata = None - @property def mask_clouds(self): """Return the cloud mask. @@ -367,29 +331,18 @@ class _EnMAP_Image(object): def deadpixelmap(self): self._deadpixelmap = None - def calc_mask_nodata(self, fromBand=None, overwrite=False): - """Calculate a no data mask with (values: 0=nodata; 1=data). - - :param fromBand: index of the band to be used (if None, all bands are used) - :param overwrite: whether to overwrite existing nodata mask that has already been calculated - :return: - """ - self.logger.info('Calculating nodata mask...') - if self._mask_nodata is None or overwrite: - self.data.calc_mask_nodata(fromBand=fromBand, overwrite=overwrite) - self.mask_nodata = self.data.mask_nodata - return self.mask_nodata +####################################### +# EnPT EnMAP objects in sensor geometry +####################################### -class _EnMAP_Detector_SensorGeo(_EnMAP_Image): - """Base class representing a single detector of an EnMAP image (as sensor geometry). +class EnMAP_Detector_SensorGeo(_EnMAP_Image): + """Class representing a single detector of an EnMAP image (as sensor geometry). NOTE: - Inherits all attributes from _EnMAP_Image class. - All functionality that VNIR and SWIR detectors (sensor geometry) have in common is to be implemented here. - - All EnMAP image subclasses representing a specific EnMAP detector (sensor geometry) should inherit from - _EnMAP_Detector_SensorGeo. Attributes: - to be listed here. Check help(_EnMAP_Detector_SensorGeo) in the meanwhile! @@ -411,12 +364,10 @@ class _EnMAP_Detector_SensorGeo(_EnMAP_Image): self.logger = logger or logging.getLogger() # get all attributes of base class "_EnMAP_Image" - super(_EnMAP_Detector_SensorGeo, self).__init__() + super(EnMAP_Detector_SensorGeo, self).__init__() self.paths = self.get_paths() # instance an empty metadata object - self.detector_meta = \ - EnMAP_Metadata_L1B_VNIR_SensorGeo(self.paths.metaxml, logger=logger) if self.detector_name == 'VNIR' else \ - EnMAP_Metadata_L1B_SWIR_SensorGeo(self.paths.metaxml, logger=logger) + self.detector_meta = EnMAP_Metadata_L1B_Detector_SensorGeo(self.detector_name, logger=logger) def get_paths(self): """Get all file paths associated with the current instance of _EnMAP_Detector_SensorGeo. @@ -454,62 +405,6 @@ class _EnMAP_Detector_SensorGeo(_EnMAP_Image): self.detector_meta.unitcode = "TOARad" -class _EnMAP_Detector_MapGeo(_EnMAP_Image): - """Base class representing a single detector of an EnMAP image (as map geometry). - - NOTE: - - Inherits all attributes from _EnMAP_Image class. - - All functionality that VNIR and SWIR detectors (map geometry) have in common is to be implemented here. - - All EnMAP image subclasses representing a specific EnMAP detector (sensor geometry) should inherit from - _EnMAP_Detector_SensorGeo. - - Attributes: - - to be listed here. Check help(_EnMAP_Detector_SensorGeo) in the meanwhile! - - """ - - def __init__(self, detector_name: str, logger=None): - """Get an instance of _EnMAP_Detector_MapGeo. - - :param detector_name: 'VNIR' or 'SWIR' - :param logger: - """ - self.detector_name = detector_name - self.logger = logger or logging.getLogger() - - # get all attributes of base class "_EnMAP_Image" - super(_EnMAP_Detector_MapGeo, self).__init__() - - -####################################### -# EnPT EnMAP objects in sensor geometry -####################################### - - -class EnMAP_VNIR_SensorGeo(_EnMAP_Detector_SensorGeo): - """Class for EnPT EnMAP VNIR object in sensor geometry.""" - - def __init__(self, root_dir: str): - """Get an instance of the VNIR of an EnMAP data Level-1B product. - - :param root_dir: Root directory of EnMAP Level-1B product - """ - # get all attributes of base class "_EnMAP_Detector" - super(EnMAP_VNIR_SensorGeo, self).__init__('VNIR', root_dir) - - -class EnMAP_SWIR_SensorGeo(_EnMAP_Detector_SensorGeo): - """Class for EnPT EnMAP SWIR object in sensor geometry.""" - - def __init__(self, root_dir: str): - """Get an instance of the SWIR of an EnMAP data Level-1B product. - - :param root_dir: Root directory of EnMAP Level-1B product - """ - # get all attributes of base class "_EnMAP_Detector" - super(EnMAP_SWIR_SensorGeo, self).__init__('SWIR', root_dir) - - class EnMAPL1Product_SensorGeo(object): """Class for EnPT EnMAP object in sensor geometry. @@ -536,8 +431,8 @@ class EnMAPL1Product_SensorGeo(object): :param logger: None or logging instance """ self.logger = logger or logging.getLogger(__name__) - self.vnir = EnMAP_VNIR_SensorGeo(root_dir) - self.swir = EnMAP_SWIR_SensorGeo(root_dir) + self.vnir = EnMAP_Detector_SensorGeo('VNIR', root_dir, logger=logger) + self.swir = EnMAP_Detector_SensorGeo('SWIR', root_dir, logger=logger) self.paths = self.get_paths() self.meta = EnMAP_Metadata_L1B_SensorGeo(self.paths.metaxml, logger=logger) self.detector_attrNames = ['vnir', 'swir'] @@ -569,25 +464,77 @@ class EnMAPL1Product_SensorGeo(object): #################################### -class EnMAP_VNIR_MapGeo(_EnMAP_Detector_MapGeo): - """Class for EnMAP VNIR image in map geometry including all metadata and associated aux-data (masks, DEM, etc.). +class EnMAP_Detector_MapGeo(_EnMAP_Image): + """Base class representing a single detector of an EnMAP image (as map geometry). + + NOTE: + - Inherits all attributes from _EnMAP_Image class. + - All functionality that VNIR and SWIR detectors (map geometry) have in common is to be implemented here. + - All EnMAP image subclasses representing a specific EnMAP detector (sensor geometry) should inherit from + _EnMAP_Detector_SensorGeo. + + Attributes: + - to be listed here. Check help(_EnMAP_Detector_SensorGeo) in the meanwhile! - All attributes commonly used among different EnMAP images are inherited from the _EnMAP_Detector_MapGeo class. - All VNIR_MapGeo specific modifications are to be implemented here. """ - def __init__(self, logger=None): - """Get an instance of the VNIR of an EnMAP data Level-1B product (map geometry).""" - super(EnMAP_VNIR_MapGeo, self).__init__('VNIR', logger=logger) + def __init__(self, detector_name: str, logger=None): + """Get an instance of _EnMAP_Detector_MapGeo. + + :param detector_name: 'VNIR' or 'SWIR' + :param logger: + """ + self.detector_name = detector_name + self.logger = logger or logging.getLogger() + + # get all attributes of base class "_EnMAP_Image" + super(EnMAP_Detector_MapGeo, self).__init__() + @property + def mask_nodata(self): + """Return the no data mask. -class EnMAP_SWIR_MapGeo(_EnMAP_Detector_MapGeo): - """Class for EnMAP SWIR image in map geometry including all metadata and associated aux-data (masks, DEM, etc.). + Bundled with all the corresponding metadata. - All attributes commonly used among different EnMAP images are inherited from the _EnMAP_Detector_MapGeo class. - All SWIR_MapGeo specific modifications are to be implemented here. - """ + For usage instructions and a list of attributes refer to help(self.data). + self.mask_nodata works in the same way. + + :return: instance of geoarray.NoDataMask + """ + if self._mask_nodata is None and isinstance(self.data, GeoArray): + self.logger.info('Calculating nodata mask...') + self._mask_nodata = self.data.mask_nodata # calculates mask nodata if not already present + + return self._mask_nodata + + @mask_nodata.setter + def mask_nodata(self, *geoArr_initArgs): + if geoArr_initArgs[0] is not None: + nd = NoDataMask(*geoArr_initArgs) + if nd.shape[:2] != self.data.shape[:2]: + raise ValueError("The 'mask_nodata' GeoArray can only be instanced with an array of the " + "same dimensions like _EnMAP_Image.arr. Got %s." % str(nd.shape)) + nd.nodata = False + nd.gt = self.data.gt + nd.prj = self.data.prj + self._mask_nodata.prj = nd + else: + del self.mask_nodata + + @mask_nodata.deleter + def mask_nodata(self): + self._mask_nodata = None + + def calc_mask_nodata(self, fromBand=None, overwrite=False): + """Calculate a no data mask with (values: 0=nodata; 1=data). - def __init__(self, logger=None): - """Get an instance of the SWIR of an EnMAP data Level-1B product (map geometry).""" - super(EnMAP_SWIR_MapGeo, self).__init__('SWIR', logger=logger) + :param fromBand: index of the band to be used (if None, all bands are used) + :param overwrite: whether to overwrite existing nodata mask that has already been calculated + :return: + """ + self.logger.info('Calculating nodata mask...') + + if self._mask_nodata is None or overwrite: + self.data.calc_mask_nodata(fromBand=fromBand, overwrite=overwrite) + self.mask_nodata = self.data.mask_nodata + return self.mask_nodata diff --git a/enpt/model/metadata.py b/enpt/model/metadata.py index b884eed9ff9643ea3318fcc6115efb5d81fb487d..011602d9645f1089c7cacb86be6741e6c63f5014 100644 --- a/enpt/model/metadata.py +++ b/enpt/model/metadata.py @@ -17,18 +17,16 @@ L1B_product_props = dict( ) -############## -# BASE CLASSES -############## +######################################################### +# EnPT metadata objects for EnMAP data in sensor geometry +######################################################### -class _EnMAP_Metadata_L1B_Detector_SensorGeo(object): - """Base class for all EnMAP metadata associated with a single EnMAP detector in sensor geometry. +class EnMAP_Metadata_L1B_Detector_SensorGeo(object): + """Class for all EnMAP metadata associated with a single EnMAP detector in sensor geometry. NOTE: - All metadata that have VNIR and SWIR detector in sensor geometry in common should be included here. - - All classes representing VNIR or SWIR specific metadata should inherit from - _EnMAP_Metadata_Detector_SensorGeo. """ @@ -39,6 +37,7 @@ class _EnMAP_Metadata_L1B_Detector_SensorGeo(object): :param logger: """ self.detector_name = detector_name # type: str + self.detector_label = L1B_product_props['xml_detector_label'][detector_name] self.logger = logger or logging.getLogger() self.fwhm = None # type: np.ndarray # Full width half maximmum for each EnMAP band @@ -61,18 +60,18 @@ class _EnMAP_Metadata_L1B_Detector_SensorGeo(object): self.lats = None # type: np.ndarray # 2D array of latitude coordinates according to given lon/lat sampling self.lons = None # type: np.ndarray # 2D array of longitude coordinates according to given lon/lat sampling self.unit = '' # type: str # radiometric unit of pixel values + self.unitcode = '' # type: str # code of radiometric unit self.snr = None # type: np.ndarray # Signal to noise ratio as computed from radiance data - def _read_metadata(self, path_xml, detector_label_xml, lon_lat_smpl=(15, 15), nsmile_coef=5): + def read_metadata(self, path_xml, lon_lat_smpl, nsmile_coef): """Read the metadata of a specific EnMAP detector in sensor geometry. :param path_xml: file path of the metadata XML file - :param detector_label_xml: :param lon_lat_smpl: number if sampling points in lon, lat fields :param nsmile_coef: number of polynomial coefficients for smile """ xml = ElementTree.parse(path_xml).getroot() - lbl = detector_label_xml + lbl = self.detector_label self.logger.info("Load data for: %s" % lbl) self.fwhm = np.array(xml.findall("%s/fwhm" % lbl)[0].text.replace("\n", "").split(), dtype=np.float) @@ -144,11 +143,6 @@ class _EnMAP_Metadata_L1B_Detector_SensorGeo(object): return rr -######################################################### -# EnPT metadata objects for EnMAP data in sensor geometry -######################################################### - - class EnMAP_Metadata_L1B_SensorGeo(object): """EnMAP Metadata class holding the metadata of the complete EnMAP product in sensor geometry incl. VNIR and SWIR. @@ -171,8 +165,8 @@ class EnMAP_Metadata_L1B_SensorGeo(object): # defaults self.observation_datetime = None # type: datetime # Date and Time of image observation - self.vnir = None # type: EnMAP_Metadata_L1B_VNIR_SensorGeo # metadata of VNIR only - self.swir = None # type: EnMAP_Metadata_L1B_SWIR_SensorGeo # metadata of SWIR only + self.vnir = None # type: EnMAP_Metadata_L1B_Detector_SensorGeo # metadata of VNIR only + self.swir = None # type: EnMAP_Metadata_L1B_Detector_SensorGeo # metadata of SWIR only self.detector_attrNames = ['vnir', 'swir'] def read_common_meta(self, observation_time: datetime=None): @@ -183,7 +177,7 @@ class EnMAP_Metadata_L1B_SensorGeo(object): # FIXME observation time is currently missing in the XML self.observation_datetime = observation_time - def read_metadata(self, observation_time: datetime=None, lon_lat_smpl=(15, 15), nsmile_coef=4): + def read_metadata(self, observation_time: datetime, lon_lat_smpl, nsmile_coef): """Read the metadata of the whole EnMAP L1B product in sensor geometry. :param observation_time: date and time of image observation (datetime.datetime) @@ -191,63 +185,7 @@ class EnMAP_Metadata_L1B_SensorGeo(object): :param nsmile_coef: number of polynomial coefficients for smile """ self.read_common_meta(observation_time) - self.vnir = EnMAP_Metadata_L1B_VNIR_SensorGeo(self._path_xml) - self.vnir.read_metadata(lon_lat_smpl=lon_lat_smpl, nsmile_coef=nsmile_coef) - self.swir = EnMAP_Metadata_L1B_SWIR_SensorGeo(self._path_xml) - self.swir.read_metadata(lon_lat_smpl=lon_lat_smpl, nsmile_coef=nsmile_coef) - - -class EnMAP_Metadata_L1B_VNIR_SensorGeo(_EnMAP_Metadata_L1B_Detector_SensorGeo): - """EnMAP Metadata class holding the metadata of the VNIR detector in sensor geometry. - - NOTE: - - inherits all attributes from base class _EnMAP_Metadata_Detector_SensorGeo. - """ - - def __init__(self, path_metaxml, logger=None): - """Get a metadata object instance for the VNIR detector of the given EnMAP L1B product in sensor geometry. - - :param path_metaxml: file path of the EnMAP L1B metadata XML file - :param logger: instance of logging.logger or subclassed - """ - # get all attributes from base class '_EnMAP_Metadata_Detector_SensorGeo' - super(EnMAP_Metadata_L1B_VNIR_SensorGeo, self).__init__('VNIR', logger=logger) - self._path_xml = path_metaxml - self.detector_label = L1B_product_props['xml_detector_label']['VNIR'] - - def read_metadata(self, lon_lat_smpl=(15, 15), nsmile_coef=5): - """Read the metadata of the VNIR detector in sensor geometry. - - :param lon_lat_smpl: number if sampling points in lon, lat fields - :param nsmile_coef: number of polynomial coefficients for smile - """ - super(EnMAP_Metadata_L1B_VNIR_SensorGeo, self)\ - ._read_metadata(self._path_xml, self.detector_label, lon_lat_smpl=lon_lat_smpl, nsmile_coef=nsmile_coef) - - -class EnMAP_Metadata_L1B_SWIR_SensorGeo(_EnMAP_Metadata_L1B_Detector_SensorGeo): - """EnMAP Metadata class holding the metadata of the SWIR detector in sensor geometry. - - NOTE: - - inherits all attributes from base class _EnMAP_Metadata_Detector_SensorGeo. - """ - - def __init__(self, path_metaxml, logger=None): - """Get a metadata object instance for the SWIR detector of the given EnMAP L1B product in sensor geometry. - - :param path_metaxml: file path of the EnMAP L1B metadata XML file - :param logger: instance of logging.logger or subclassed - """ - # get all attributes from base class '_EnMAP_Metadata_Detector_SensorGeo' - super(EnMAP_Metadata_L1B_SWIR_SensorGeo, self).__init__('SWIR', logger=logger) - self._path_xml = path_metaxml - self.detector_label = L1B_product_props['xml_detector_label']['SWIR'] - - def read_metadata(self, lon_lat_smpl=(15, 15), nsmile_coef=5): - """Read the metadata of the SWIR detector in sensor geometry. - - :param lon_lat_smpl: number if sampling points in lon, lat fields - :param nsmile_coef: number of polynomial coefficients for smile - """ - super(EnMAP_Metadata_L1B_SWIR_SensorGeo, self)\ - ._read_metadata(self._path_xml, self.detector_label, lon_lat_smpl=lon_lat_smpl, nsmile_coef=nsmile_coef) + self.vnir = EnMAP_Metadata_L1B_Detector_SensorGeo('VNIR', logger=self.logger) + self.vnir.read_metadata(self._path_xml, lon_lat_smpl=lon_lat_smpl, nsmile_coef=nsmile_coef) + self.swir = EnMAP_Metadata_L1B_Detector_SensorGeo('SWIR', logger=self.logger) + self.swir.read_metadata(self._path_xml, lon_lat_smpl=lon_lat_smpl, nsmile_coef=nsmile_coef)