Commit 6cc20662 authored by Daniel Scheffler's avatar Daniel Scheffler
Browse files

Merge branch 'master' into feature/separate_ac_land_water

# Conflicts:
#	HISTORY.rst
parents e4514499 eb9e6cf1
Pipeline #22672 passed with stage
in 12 minutes and 43 seconds
......@@ -7,6 +7,11 @@ History
* Added documentation for pixel value 3 of land/water mask (#73).
* 'make lint' now directly prints errors instead of only logging them to logfiles.
* EnPTConfig and EnPT_Controller are now importable directly from the top level of EnPT.
* Added config parameters to run EnPT in 3 AC modes: 'land', 'water', 'combined'.
* Added some boilerplate code in atmospheric_correction.py which is to be replaced by separate AC calls for water and
land surfaces later.
0.17.2 (2021-03-04)
......@@ -36,10 +41,6 @@ History
* EnPT is now compatible with the latest SICOR algorithm which fixes EnMAP/sicor#40
([EnMAP] SWIR parameter retrieval results are applied to wrong VNIR coordinates).
* Added config parameters to run EnPT in 3 AC modes: 'land', 'water', 'combined'.
* Added some boilerplate code in atmospheric_correction.py which is to be replaced by separate AC calls for water and
land surfaces later.
0.16.4 (2020-12-10)
-------------------
......
......@@ -29,13 +29,18 @@
"""EnMAP processing tool (EnPT) software package developed by GFZ."""
from .version import __version__, __versionalias__ # noqa (E402 + F401)
import sensormapgeo as __smg # noqa (E402 + F401) # only to avoid later import error due to static TLS
from .version import __version__, __versionalias__ # noqa (E402 + F401)
from .options.config import EnPTConfig
from .execution.controller import EnPT_Controller
__author__ = """Daniel Scheffler"""
__email__ = 'danschef@gfz-potsdam.de'
__all__ = ['__version__',
'__versionalias__',
'__author__',
'__email__'
'__email__',
'EnPTConfig',
'EnPT_Controller'
]
......@@ -71,11 +71,11 @@ class L1B_Reader(object):
root_dir_ext: str = None,
n_line_ext: int = None,
compute_snr: bool = True) -> EnMAPL1Product_SensorGeo:
"""Read L1B EnMAP data. Extend the image by adding a second image [entire, partial]
"""Read L1B EnMAP data. Extend the image by adding a second image (entire, partial).
:param root_dir_main: Root directory of the main EnMAP Level-1B product
:param root_dir_ext: Root directory of the extended EnMAP Level-1B product [optional]
:param n_line_ext: Number of lines to be added to the main image [if None, use the whole image]
:param root_dir_ext: Root directory of the extended EnMAP Level-1B product (optional)
:param n_line_ext: Number of lines to be added to the main image (if None, use the whole image)
:param compute_snr: whether to compute SNR or not (default: True)
:return: instance of EnMAPL1Product_SensorGeo
"""
......@@ -159,8 +159,9 @@ def Solar_Irradiance_reader(path_solar_irr_model: str, resol_nm: float = None, w
"""Read the given solar irradiance file and return an array of irradiances.
:param path_solar_irr_model: file path to solar irradiance model
-> must be arranged like that:
col0 = Wavelength[nm]; col1 = Solar Irradiance [W/m2/µm])
col0 = Wavelength[nm]; col1 = Solar Irradiance [W/m2/µm])
:param resol_nm: spectral resolution for returned irradiances [nanometers]
:param wvl_min_nm: minimum wavelength of returned irradiances [nanometers]
:param wvl_max_nm: maximum wavelength of returned irradiances [nanometers]
......
......@@ -92,10 +92,11 @@ class _EnMAP_Image(object):
Bundled with all the corresponding metadata.
Attributes and functions (most important; for a full list check help(self.data)!):
- ALL attributes of numpy.ndarray!
- is_inmem(bool):
True if the image data are completely loaded into memory; False if GeoArray only holds a link to a file
on disk.
True if the image data are completely loaded into memory; False if GeoArray only holds a link to a file
on disk.
- arr: np.ndarray holding the pixel values (if is_mem is True)
- rows(int)
- cols(int)
......@@ -103,19 +104,19 @@ class _EnMAP_Image(object):
- shape(tuple)
- gt(list): GDAL geotransform: contains the geocoding
- prj(str): WKT projection string
- show(*args, **kwargs): plot the image
- show_map(*args, **kwargs): plot a map of the image (based on cartopy library)
- reproject_to_new_grid(*args, **kwargs)
- show(): plot the image
- show_map(): plot a map of the image (based on cartopy library)
- reproject_to_new_grid()
Usage (there will soon be detailed instructions on usage at https://git.gfz-potsdam.de/danschef/geoarray):
- Use self.data like a normal numpy.ndarray!
- NOTE: Operators like *, /, + , - will soon be implemented. In the meanwhile use:
result = self.data[:] *10
result = self.data[:] *10
- How to set self.data?
- Link an image file to self.data -> all raster data is read into memory ON DEMAND:
self.data = '/path/to/image.tif' # sets self.data to GeoArray('/path/to/image.tif')
self.data = '/path/to/image.tif' # sets self.data to GeoArray('/path/to/image.tif')
- Link a numpy.ndarray instance with self.data (remaining attributes like geocoding, projection, etc.
are copied from the previous self.data attribute.
......
......@@ -126,6 +126,7 @@ class EnMAPL2Product_MapGeo(_EnMAP_Image):
- meta:
- instance of EnMAP_Metadata_SensorGeo class
"""
def __init__(self, config: EnPTConfig, logger=None):
# protected attributes
self._logger = None
......@@ -200,9 +201,9 @@ class EnMAPL2Product_MapGeo(_EnMAP_Image):
return L2_obj
def get_paths(self, l2a_outdir: str):
"""
Get all file paths associated with the current instance of EnMAP_Detector_SensorGeo
These information are read from the detector_meta.
"""Get all file paths associated with the current instance of EnMAP_Detector_SensorGeo.
NOTE: This information is read from the detector_meta.
:param l2a_outdir: output directory of EnMAP Level-2A dataset
:return: paths as SimpleNamespace
......@@ -225,8 +226,8 @@ class EnMAPL2Product_MapGeo(_EnMAP_Image):
return paths
def save(self, outdir: str, suffix="") -> str:
"""
Save the product to disk using almost the same input format
"""Save the product to disk using almost the same input format.
:param outdir: path to the output directory
:param suffix: suffix to be appended to the output filename (???)
:return: root path (root directory) where products were written
......
......@@ -92,9 +92,10 @@ class EnMAP_Detector_SensorGeo(_EnMAP_Image):
self.detector_meta: EnMAP_Metadata_L1B_Detector_SensorGeo = meta
def get_paths(self) -> SimpleNamespace:
"""
Get all file paths associated with the current instance of EnMAP_Detector_SensorGeo
These information are read from the detector_meta.
"""Get all file paths associated with the current instance of EnMAP_Detector_SensorGeo.
NOTE: This information is read from the detector_meta.
:return: paths
"""
self.paths.root_dir = self._root_dir
......@@ -362,11 +363,13 @@ class EnMAP_Detector_SensorGeo(_EnMAP_Image):
) -> np.ndarray:
"""Transform the given input raster from SWIR to VNIR or from SWIR to VNIR sensor geometry.
NOTE: - The transformation target is always the EnMAP_Detector_SensorGeo instance sensor geometry
(e.g., VNIR sensorgeo if self.detector_name == 'VNIR').
- In case a 3D array is given and the array has the exact dimensions of the source detector,
the full geolayer is only used if 'respect_keystone' is set to True. This saves computation time
for input arrays where the keystone uncertainty does not matter.
NOTE:
- The transformation target is always the EnMAP_Detector_SensorGeo instance sensor geometry
(e.g., VNIR sensorgeo if self.detector_name == 'VNIR').
- In case a 3D array is given and the array has the exact dimensions of the source detector,
the full geolayer is only used if 'respect_keystone' is set to True. This saves computation time
for input arrays where the keystone uncertainty does not matter.
:param array: input array to be transformed (2- or 3-dimensional)
......@@ -429,9 +432,7 @@ class EnMAP_VNIR_SensorGeo(EnMAP_Detector_SensorGeo):
super().__init__(detector_name='VNIR', root_dir=root_dir, config=config, logger=logger, meta=meta)
def read_masks(self):
"""
Read the L1B masks.
"""
"""Read the L1B masks."""
self.logger.info('Reading image masks in VNIR sensor geometry.')
# water mask (0=backgr.; 1=land; 2=water)
......@@ -603,9 +604,8 @@ class EnMAPL1Product_SensorGeo(object):
# compute radiance
self.DN2TOARadiance()
def get_paths(self):
"""
Get all file paths associated with the current instance of EnMAPL1Product_SensorGeo
def get_paths(self) -> SimpleNamespace:
"""Get all file paths associated with the current instance of EnMAPL1Product_SensorGeo.
:return: paths.SimpleNamespace()
"""
......
......@@ -62,7 +62,7 @@ class EnMAP_Metadata_L2A_MapGeo(object):
:param meta_l1b: metadata object of the L1B dataset in sensor geometry
:param wvls_l2a: list of center wavelengths included in the L2A product
:param dims_mapgeo: dimensions of the EnMAP raster data in map geometry, e.g., (1024, 1000, 218)
:param grid_res_l2a Coordinate grid resolution of the L2A product (x, y)
:param grid_res_l2a: Coordinate grid resolution of the L2A product (x, y)
:param logger: instance of logging.logger or subclassed
"""
self.cfg = config
......@@ -228,9 +228,7 @@ class EnMAP_Metadata_L2A_MapGeo(object):
self.fileinfos.append(fileinfo_dict)
def to_XML(self) -> str:
"""
Generate an XML metadata string from the L2A metadata.
"""
"""Generate an XML metadata string from the L2A metadata."""
# use an XML parser that creates properly indented XML files even if new SubElements have been added
parser = ElementTree.XMLParser(remove_blank_text=True)
......
......@@ -113,8 +113,7 @@ class EnMAP_Metadata_L1B_Detector_SensorGeo(object):
self.snr: Optional[np.ndarray] = None # Signal to noise ratio as computed from radiance data
def read_metadata(self, path_xml):
"""
Read the metadata of a specific EnMAP detector in sensor geometry
"""Read the metadata of a specific EnMAP detector in sensor geometry.
:param path_xml: file path of the metadata file
:return: None
......@@ -312,17 +311,22 @@ class EnMAP_Metadata_L1B_Detector_SensorGeo(object):
"""Compute EnMAP SNR from radiance data for the given detector.
SNR equation: SNR = p0 + p1 * LTOA + p2 * LTOA ^ 2 [W/(m^2 sr nm)].
NOTE: The SNR model files (SNR_D1.bsq/SNR_D2.bsq) contain polynomial coefficients needed to compute SNR.
SNR_D1.bsq: SNR model for EnMAP SWIR detector (contains high gain and low gain model coefficients)
- 1000 columns (for 1000 EnMAP columns)
- 88 bands for 88 EnMAP VNIR bands
- 7 lines: - 3 lines: high gain coefficients
- 3 lines: low gain coefficients
- 1 line: threshold needed to decide about high gain or low gain
- 1000 columns (for 1000 EnMAP columns)
- 88 bands for 88 EnMAP VNIR bands
- 7 lines: - 3 lines: high gain coefficients
- 3 lines: low gain coefficients
- 1 line: threshold needed to decide about high gain or low gain
SNR_D2.bsq: SNR model for EnMAP SWIR detector
- 1000 columns (for 1000 EnMAP columns)
- x bands for x EnMAP SWIR bands
- 3 lines for 3 coefficients
- 1000 columns (for 1000 EnMAP columns)
- x bands for x EnMAP SWIR bands
- 3 lines for 3 coefficients
:param rad_data: image radiance data of EnMAP_Detector_SensorGeo
:param dir_snr_models: root directory where SNR model data is stored (must contain SNR_D1.bsq/SNR_D2.bsq)
......@@ -485,15 +489,15 @@ class EnMAP_Metadata_L1B_SensorGeo(object):
return self._scene_basename
# Read common metadata method
def read_common_meta(self, path_xml):
"""Read the common metadata, principally stored in General Info
"""Read the common metadata, principally stored in General Info.
- the acquisition time
- the geometrical observation and illumination
:param path_xml: path to the main xml file
:return: None
"""
# load the metadata xml file
xml = ElementTree.parse(path_xml).getroot()
......@@ -542,7 +546,7 @@ class EnMAP_Metadata_L1B_SensorGeo(object):
self.mu_sun = np.cos(np.deg2rad(self.geom_sun_zenith))
def get_earth_sun_distance(self, acqDate: datetime):
"""Get earth sun distance (requires file of pre calculated earth sun distance per day)
"""Get earth sun distance (requires file of pre calculated earth sun distance per day).
:param acqDate:
"""
......@@ -564,12 +568,7 @@ class EnMAP_Metadata_L1B_SensorGeo(object):
return float(EA_dist_dict[acqDate.strftime('%Y-%m-%d')])
def read_metadata(self):
"""
Read the metadata of the entire EnMAP L1B product in sensor geometry
:return: None
"""
"""Read the metadata of the entire EnMAP L1B product in sensor geometry."""
# first read common metadata
self.read_common_meta(self.path_xml)
......@@ -582,9 +581,7 @@ class EnMAP_Metadata_L1B_SensorGeo(object):
self.swir.read_metadata(self.path_xml)
def to_XML(self) -> str:
"""
Generate an XML metadata string from the L1B metadata.
"""
"""Generate an XML metadata string from the L1B metadata."""
from . import L1B_product_props, L1B_product_props_DLR
xml = ElementTree.parse(self.path_xml).getroot()
......
......@@ -334,7 +334,6 @@ class EnPTConfig(object):
:key target_coord_grid:
Custom target coordinate grid where the output is resampled to ([x0, x1, y0, y1], e.g., [0, 30, 0, 30])
"""
# fixed attributes
self.version = __version__
self.versionalias = __versionalias__
......@@ -622,7 +621,7 @@ def python_to_json(value):
class EnPTValidator(Validator):
def __init__(self, *args, **kwargs):
"""
"""Get an instance of EnPTValidator.
:param args: Arguments to be passed to cerberus.Validator
:param kwargs: Keyword arguments to be passed to cerberus.Validator
......@@ -641,7 +640,6 @@ def get_options(target: str, validation: bool = True):
:param validation: True / False, whether to validate options read from files or not
:return: dictionary with options
"""
if os.path.isfile(target):
with open(target, "r") as fl:
options = json_to_python(json.loads(jsmin(fl.read())))
......
......@@ -188,8 +188,7 @@ parameter_mapping = dict(
def get_updated_schema(source_schema, key2update, new_value):
def deep_update(schema, key2upd, new_val):
"""Return true if update, else false"""
"""Return true if update, else false."""
for key in schema:
if key == key2upd:
schema[key] = new_val
......
......@@ -46,7 +46,8 @@ __author__ = 'Daniel Scheffler'
class Dead_Pixel_Corrector(object):
"""
"""EnPT Dead Pixel Correction class.
The EnPT dead pixel correction uses the pixel masks provided by DLR and interpolates the EnMAP image
data at the indicated dead pixel positions. It supports two interpolation algorithms:
......
......@@ -27,6 +27,7 @@
# You should have received a copy of the GNU Lesser General Public License along
# with this program. If not, see <http://www.gnu.org/licenses/>.
"""
EnPT module 'orthorectification' for transforming an EnMAP image from sensor to map geometry
based on a pixel- and band-wise coordinate-layer (geolayer).
......
......@@ -27,11 +27,11 @@
# You should have received a copy of the GNU Lesser General Public License along
# with this program. If not, see <http://www.gnu.org/licenses/>.
"""
EnPT module 'orthorectification' for transforming an EnMAP image from sensor to map geometry
"""EnPT module 'orthorectification' for transforming an EnMAP image from sensor to map geometry
based on a pixel- and band-wise coordinate-layer (geolayer).
"""
from typing import Tuple, Union # noqa: F401
from types import SimpleNamespace
......
......@@ -50,7 +50,7 @@ from ..spatial_transform import Geometry_Transformer, Geometry_Transformer_3D
class Spatial_Optimizer(object):
def __init__(self, config: EnPTConfig):
"""Create an instance of Spatial_Optimizer"""
"""Create an instance of Spatial_Optimizer."""
self.cfg = config
self._ref_Im: Optional[GeoArray, None] = GeoArray(self.cfg.path_reference_image)
......
......@@ -265,9 +265,8 @@ def move_extent_to_coord_grid(extent_utm: Tuple[float, float, float, float],
class RPC_Geolayer_Generator(object):
"""
Class for creating pixel-wise longitude/latitude arrays based on rational polynomial coefficients (RPC).
"""
"""Class for creating pixel-wise longitude/latitude arrays based on rational polynomial coefficients (RPC)."""
def __init__(self,
rpc_coeffs: dict,
elevation: Union[str, GeoArray, int, float],
......@@ -523,9 +522,8 @@ global_dem_sensorgeo: Optional[GeoArray] = None
class RPC_3D_Geolayer_Generator(object):
"""
Class for creating band- AND pixel-wise longitude/latitude arrays based on rational polynomial coefficients (RPC).
"""
"""Class for creating band- AND pixel-wise longitude/latitude arrays based on rational polynomial coeff. (RPC)."""
def __init__(self,
rpc_coeffs_per_band: dict,
elevation: Union[str, GeoArray, int, float],
......
......@@ -125,7 +125,7 @@ class EnPT_Logger(logging.Logger):
return self.__dict__
def __setstate__(self, ObjDict):
"""Defines how the attributes of EnPT_Logger are unpickled."""
"""Define how the attributes of EnPT_Logger are unpickled."""
self.__init__(ObjDict['name_logfile'], fmt_suffix=ObjDict['fmt_suffix'], path_logfile=ObjDict['path_logfile'],
log_level=ObjDict['log_level'], append=True)
ObjDict = self.__dict__
......
......@@ -44,6 +44,7 @@ __author__ = 'Daniel Scheffler'
class PathGenL1BProduct(object):
"""Path generator class for generating file pathes corresponding to the EnMAP L1B product."""
# TODO update this class
def __init__(self, root_dir: str, detector_name: str):
......@@ -86,7 +87,7 @@ class PathGenL1BProduct(object):
def get_path_ac_options() -> str:
"""Returns the path of the options json file needed for atmospheric correction."""
"""Return the path of the options json file needed for atmospheric correction."""
from sicor import options
path_ac = os.path.join(os.path.dirname(options.__file__), 'enmap_options.json')
# FIXME temporarily disabled because not implemented at the moment:
......
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