Coverage for enpt/io/reader.py: 84%
58 statements
« prev ^ index » next coverage.py v7.4.1, created at 2024-03-07 11:39 +0000
« prev ^ index » next coverage.py v7.4.1, created at 2024-03-07 11:39 +0000
1# -*- coding: utf-8 -*-
3# EnPT, EnMAP Processing Tool - A Python package for pre-processing of EnMAP Level-1B data
4#
5# Copyright (C) 2018-2024 Karl Segl (GFZ Potsdam, segl@gfz-potsdam.de), Daniel Scheffler
6# (GFZ Potsdam, danschef@gfz-potsdam.de), Niklas Bohn (GFZ Potsdam, nbohn@gfz-potsdam.de),
7# Stéphane Guillaso (GFZ Potsdam, stephane.guillaso@gfz-potsdam.de)
8#
9# This software was developed within the context of the EnMAP project supported
10# by the DLR Space Administration with funds of the German Federal Ministry of
11# Economic Affairs and Energy (on the basis of a decision by the German Bundestag:
12# 50 EE 1529) and contributions from DLR, GFZ and OHB System AG.
13#
14# This program is free software: you can redistribute it and/or modify it under
15# the terms of the GNU General Public License as published by the Free Software
16# Foundation, either version 3 of the License, or (at your option) any later
17# version. Please note the following exception: `EnPT` depends on tqdm, which
18# is distributed under the Mozilla Public Licence (MPL) v2.0 except for the files
19# "tqdm/_tqdm.py", "setup.py", "README.rst", "MANIFEST.in" and ".gitignore".
20# Details can be found here: https://github.com/tqdm/tqdm/blob/master/LICENCE.
21#
22# This program is distributed in the hope that it will be useful, but WITHOUT
23# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
24# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
25# details.
26#
27# You should have received a copy of the GNU Lesser General Public License along
28# with this program. If not, see <https://www.gnu.org/licenses/>.
30"""Reader module for reading all kinds of EnMAP images."""
32import logging
33import os
34from fnmatch import filter
35import numpy as np
37from ..model.images import EnMAPL1Product_SensorGeo
38from ..options.config import EnPTConfig
40__author__ = 'Daniel Scheffler'
43class L1B_Reader(object):
44 """Reader for EnMAP Level-1B products."""
46 def __init__(self,
47 config: EnPTConfig,
48 logger: logging.Logger = None,
49 root_dir_main: str = None,
50 root_dir_ext: str = None,
51 n_line_ext: int = None):
52 """Get an instance of L1B_Reader.
54 :param config: instance of EnPTConfig class
55 :param logger: instance of logging.Logger (NOTE: This logger is only used to log messages within L1B_Reader.
56 It is not appended to the read L1B EnMAP object).
57 :param root_dir_main: Root directory of EnMAP Level-1B product (the main image)
58 :param root_dir_ext: Root directory of EnMAP Level-1B product (to extend the main image)
59 :param n_line_ext: [Optional] add number of line to be added to the main image from the extended image
60 """
61 self.cfg = config
62 self.logger = logger or logging.getLogger(__name__)
64 # read data if root_dir_main is given or not
65 if root_dir_main is not None:
66 self.read_inputdata(root_dir_main, root_dir_ext, n_line_ext)
68 def read_inputdata(self,
69 root_dir_main,
70 root_dir_ext: str = None,
71 n_line_ext: int = None,
72 compute_snr: bool = True) -> EnMAPL1Product_SensorGeo:
73 """Read L1B EnMAP data. Extend the image by adding a second image (entire, partial).
75 :param root_dir_main: Root directory of the main EnMAP Level-1B product
76 :param root_dir_ext: Root directory of the extended EnMAP Level-1B product (optional)
77 :param n_line_ext: Number of lines to be added to the main image (if None, use the whole image)
78 :param compute_snr: whether to compute SNR or not (default: True)
79 :return: instance of EnMAPL1Product_SensorGeo
80 """
81 self.validate_input(root_dir_main, root_dir_ext)
82 self.logger.info("Reading Input Data")
84 # Get a new instance of the EnMAPL1Product_SensorGeo for the main image (TOA radiance)
85 l1b_main_obj = EnMAPL1Product_SensorGeo(root_dir_main, config=self.cfg, logger=self.logger)
87 # append a second EnMAP L1B product below if given
88 if root_dir_ext:
89 l1b_main_obj.append_new_image(root_dir_ext, n_line_ext)
91 # compute SNR
92 if compute_snr:
93 l1b_main_obj.calc_snr_from_radiance()
95 # compute geolayer if not already done
96 if l1b_main_obj.meta.vnir.lons is None or l1b_main_obj.meta.vnir.lats is None:
97 l1b_main_obj.meta.vnir.lons, l1b_main_obj.meta.vnir.lats = \
98 l1b_main_obj.meta.vnir.compute_geolayer_for_cube()
100 if l1b_main_obj.meta.swir.lons is None or l1b_main_obj.meta.swir.lats is None:
101 l1b_main_obj.meta.swir.lons, l1b_main_obj.meta.swir.lats = \
102 l1b_main_obj.meta.swir.compute_geolayer_for_cube()
104 # l1b_main_obj.correct_VNIR_SWIR_shift()
106 # Validate and return the l1b_main_obj
107 self.validate_output()
108 return l1b_main_obj
110 def validate_input(self, root_dir_main, root_dir_ext):
111 """Validate user inputs."""
112 if not self.cfg.is_dummy_dataformat:
113 self._validate_enmap_l1b_rootdir(root_dir_main)
114 if root_dir_ext:
115 self._validate_enmap_l1b_rootdir(root_dir_ext)
117 @staticmethod
118 def _validate_enmap_l1b_rootdir(rootdir_l1b):
119 """Check for valid EnMAP L1B root directory."""
120 if not os.path.isdir(rootdir_l1b):
121 raise NotADirectoryError(rootdir_l1b, 'EnMAP images have to be provided either as zip-archives or as '
122 'a directory containing all extracted files.')
124 files = os.listdir(rootdir_l1b)
126 if not files:
127 raise RuntimeError("The root directory of the EnMAP image %s is empty." % rootdir_l1b)
129 for pattern in [
130 # '*-HISTORY.XML', # only included in internal DLR test data, not in the zip archive on enmap.org
131 # '*-LOG.XML', # only included in internal DLR test data, not in the zip archive on enmap.org
132 '*-METADATA.XML',
133 '*-QL_PIXELMASK_SWIR.?',
134 '*-QL_PIXELMASK_VNIR.?',
135 '*-QL_QUALITY_CIRRUS.?',
136 '*-QL_QUALITY_CLASSES.?',
137 '*-QL_QUALITY_CLOUD.TIF',
138 '*-QL_QUALITY_CLOUDSHADOW.?',
139 '*-QL_QUALITY_HAZE.?',
140 '*-QL_QUALITY_SNOW.?',
141 '*-QL_QUALITY_TESTFLAGS_SWIR.?',
142 '*-QL_QUALITY_TESTFLAGS_VNIR.?',
143 '*-QL_SWIR.?',
144 '*-QL_VNIR.?',
145 '*-SPECTRAL_IMAGE_SWIR.?',
146 '*-SPECTRAL_IMAGE_VNIR.?',
147 ]:
148 matches = []
149 for ext in ['', '.TIF', '.GEOTIFF', '.BSQ', '.BIL', '.BIP', 'JPEG2000', '.JP2', '.jp2']:
150 matches.extend(filter(files, pattern.replace('.?', ext)))
152 if matches:
153 break
155 if not matches:
156 raise FileNotFoundError('The root directory of the EnMAP image %s misses a file with the pattern %s.'
157 % (rootdir_l1b, pattern))
159 def validate_output(self):
160 """Validate outputs of L1B_Reader."""
161 pass
164def read_solar_irradiance(path_solar_irr_model: str, resol_nm: float = None, wvl_min_nm: float = None,
165 wvl_max_nm: float = None) -> np.ndarray:
166 """Read the given solar irradiance file and return an array of irradiances.
168 :param path_solar_irr_model: file path to solar irradiance model
170 -> must be arranged like that:
171 col0 = Wavelength[nm]; col1 = Solar Irradiance [W/m2/µm]
172 :param resol_nm: spectral resolution for returned irradiances [nanometers]
173 :param wvl_min_nm: minimum wavelength of returned irradiances [nanometers]
174 :param wvl_max_nm: maximum wavelength of returned irradiances [nanometers]
175 :return:
176 """
177 sol_irr = np.loadtxt(path_solar_irr_model, skiprows=1)
178 if resol_nm is not None and isinstance(resol_nm, (float, int)):
179 wvl_min = (np.min(sol_irr[:, 0]) if wvl_min_nm is None else wvl_min_nm)
180 wvl_max = (np.max(sol_irr[:, 0]) if wvl_max_nm is None else wvl_max_nm)
181 wvl_rsp = np.arange(wvl_min, wvl_max, resol_nm)
182 sol_irr = np.interp(wvl_rsp, sol_irr[:, 0], sol_irr[:, 1])
183 return sol_irr