metadata.py 12.2 KB
Newer Older
1
# -*- coding: utf-8 -*-
2
"""EnPT metadata modules. All object and functions regarding EnMAP metadata are implemented here."""
3
4
5
6
7
8
9
10
11
12

from datetime import datetime
from xml.etree import ElementTree
import logging
import numpy as np
from scipy.interpolate import interp2d

L1B_product_props = dict(
    xml_detector_label=dict(
        VNIR='detector1',
Daniel Scheffler's avatar
Daniel Scheffler committed
13
        SWIR='detector2'),
14
15
    fn_detector_suffix=dict(
        VNIR='D1',
Daniel Scheffler's avatar
Daniel Scheffler committed
16
        SWIR='D2')
17
18
19
20
21
22
23
24
)


##############
# BASE CLASSES
##############


25
class _EnMAP_Metadata_L1B_Detector_SensorGeo(object):
26
    """Base class for all EnMAP metadata associated with a single EnMAP detector in sensor geometry.
27
28

    NOTE:
29
30
31
        - 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.
32
33

    """
Daniel Scheffler's avatar
Daniel Scheffler committed
34

35
    def __init__(self, detector_name: str, logger: logging.Logger=None):
Daniel Scheffler's avatar
Daniel Scheffler committed
36
        """Get a metadata object containing the metadata of a single EnMAP detector in sensor geometry.
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58

        :param detector_name: Name of the detector (VNIR or SWIR)
        :param logger:
        """
        self.detector_name = detector_name  # type: str
        self.logger = logger or logging.getLogger()

        self.fwhm = None  # type: np.ndarray  # Full width half maximmum for each EnMAP band
        self.wvl_center = None  # type: np.ndarray  # Center wavelengths for each EnMAP band
        self.nwvl = None  # type: int  # Number of wave bands
        self.nrows = None  # type: int  # number of rows
        self.ncols = None  # type: int  # number of columns
        self.smile_coef = None  # type: np.ndarray  # smile coefficients needed for smile computation
        self.nsmile_coef = None  # type: int  # number of smile coefficients
        self.smile = None  # type: np.ndarray  # smile for each EnMAP image column
        self.l_min = None  # type: np.ndarray
        self.l_max = None  # type: np.ndarray
        self.geom_view_zenith = None  # type: float  # viewing zenith angle
        self.geom_view_azimuth = None  # type: float  # viewing azimuth angle
        self.geom_sun_zenith = None  # type: float  # sun zenith angle
        self.geom_sun_azimuth = None  # type: float  # sun azimuth angle
        self.mu_sun = None  # type: float  # earth-sun distance # TODO doc correct?
Daniel Scheffler's avatar
Daniel Scheffler committed
59
60
        self.lat_UL_UR_LL_LR = None  # type:  list  # latitude coordinates for UL, UR, LL, LR
        self.lon_UL_UR_LL_LR = None  # type:  list  # longitude coordinates for UL, UR, LL, LR
61
62
63
64
65
        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.snr = None  # type: np.ndarray  # Signal to noise ratio as computed from radiance data

66
    def _read_metadata(self, path_xml, detector_label_xml, lon_lat_smpl=(15, 15), nsmile_coef=5):
67
        """Read the metadata of a specific EnMAP detector in sensor geometry.
68
69
70
71
72
73

        :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
        """
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
        xml = ElementTree.parse(path_xml).getroot()
        lbl = detector_label_xml
        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)
        self.wvl_center = np.array(
            xml.findall("%s/centre_wavelength" % lbl)[0].text.replace("\n", "").split(), dtype=np.float)
        self.nwvl = len(self.wvl_center)
        self.nrows = np.int(xml.findall("%s/rows" % lbl)[0].text)
        self.ncols = np.int(xml.findall("%s/columns" % lbl)[0].text)
        self.smile_coef = np.array(xml.findall("%s/smile" % lbl)[0].text.replace("\n", "").split(), dtype=np.float) \
                            .reshape((-1, nsmile_coef + 1))[:, 1:]
        self.nsmile_coef = nsmile_coef
        self.smile = self.calc_smile()
        self.l_min = np.array(xml.findall("%s/L_min" % lbl)[0].text.split(), dtype=np.float)
        self.l_max = np.array(xml.findall("%s/L_max" % lbl)[0].text.split(), dtype=np.float)
        self.geom_view_zenith = np.float(
            xml.findall("%s/observation_geometry/zenith_angle" % lbl)[0].text.split()[0])
        self.geom_view_azimuth = np.float(
Daniel Scheffler's avatar
Daniel Scheffler committed
93
            xml.findall("%s/observation_geometry/azimuth_angle" % lbl)[0].text.split()[0])
94
95
        self.geom_sun_zenith = np.float(
            xml.findall("%s/illumination_geometry/zenith_angle" % lbl)[0].text.split()[0])
Daniel Scheffler's avatar
Daniel Scheffler committed
96
97
        self.geom_sun_azimuth = np.float(
            xml.findall("%s/illumination_geometry/azimuth_angle" % lbl)[0].text.split()[0])
98
99
        self.mu_sun = np.cos(np.deg2rad(self.geom_sun_zenith))
        self.lat_UL_UR_LL_LR = \
Daniel Scheffler's avatar
Daniel Scheffler committed
100
101
            [float(xml.findall("%s/geometry/bounding_box/%s_northing" % (lbl, corner))[0].text.split()[0])
             for corner in ("UL", "UR", "LL", "LR")]
102
        self.lon_UL_UR_LL_LR = \
Daniel Scheffler's avatar
Daniel Scheffler committed
103
104
            [float(xml.findall("%s/geometry/bounding_box/%s_easting" % (lbl, corner))[0].text.split()[0])
             for corner in ("UL", "UR", "LL", "LR")]
105
106
        self.lats = self.interpolate_corners(*self.lat_UL_UR_LL_LR, *lon_lat_smpl)
        self.lons = self.interpolate_corners(*self.lon_UL_UR_LL_LR, *lon_lat_smpl)
107
108
        self.unit = 'none'  # '" ".join(xml.findall("%s/radiance_unit" % lbl)[0].text.split())
        self.unitcode = 'DN'
109
110
111

    def calc_smile(self):
        """Compute smile for each EnMAP column.
Daniel Scheffler's avatar
Daniel Scheffler committed
112

113
114
115
116
117
118
119
120
121
122
123
124
        The sum in (1) is expressed as inner product of two arrays with inner dimension as the polynomial smile
        coefficients shape = (ncols, nsmile_coef) of polynomial x

        :return:
        """
        # smile(icol, iwvl) = sum_(p=0)^(nsmile_coef-1) smile_coef[iwvl, p] * icol**p (1)
        return np.inner(
            np.array([[icol ** p for p in np.arange(self.nsmile_coef)] for icol in np.arange(self.ncols)]),
            self.smile_coef  # shape = (nwvl, nsmile_coef)
        )  # shape = (ncols, nwvl)

    def calc_snr(self, data: np.ndarray):
Daniel Scheffler's avatar
Daniel Scheffler committed
125
        """Compute EnMAP SNR from radiance data.
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146

        :param data: Numpy array with radiance for scene
        """
        self.logger.info("Compute snr for: %s" % self.detector_name)
        self.logger.warning("SNR model missing -> const. value of 500 is returned")
        return 500 * np.ones(data.shape, dtype=np.float)

    @staticmethod
    def interpolate_corners(ul: float, ur: float, ll: float, lr: float, nx: int, ny: int):
        """Compute interpolated field from corner values of a scalar field given at: ul, ur, ll, lr.

        :param nx, ny: final shape
        """
        ff = interp2d(x=[0, 1], y=[0, 1], z=[[ul, ur], [ll, lr]], kind='linear')
        rr = np.zeros((nx, ny), dtype=np.float)
        for i, x in enumerate(np.linspace(0, 1, nx)):
            for j, y in enumerate(np.linspace(0, 1, ny)):
                rr[i, j] = ff(x, y)
        return rr


147
148
149
#########################################################
# EnPT metadata objects for EnMAP data in sensor geometry
#########################################################
150
151


152
class EnMAP_Metadata_L1B_SensorGeo(object):
Daniel Scheffler's avatar
Daniel Scheffler committed
153
    """EnMAP Metadata class holding the metadata of the complete EnMAP product in sensor geometry incl. VNIR and SWIR.
154
155
156
157

    Attributes:
        - logger(logging.Logger):  None or logging instance
        - observation_datetime(datetime.datetime):  datetime of observation time (currently missing in metadata)
158
159
        - vnir(EnMAP_Metadata_VNIR_SensorGeo)
        - swir(EnMAP_Metadata_SWIR_SensorGeo)
Daniel Scheffler's avatar
Daniel Scheffler committed
160

161
    """
Daniel Scheffler's avatar
Daniel Scheffler committed
162

163
    def __init__(self, path_metaxml, logger=None):
164
165
166
167
168
        """Get a metadata object instance for 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
        """
169
        self.logger = logger or logging.getLogger()
Daniel Scheffler's avatar
Daniel Scheffler committed
170
        self._path_xml = path_metaxml
171

172
        # defaults
173
        self.observation_datetime = None  # type: datetime  # Date and Time of image observation
174
175
        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
176
        self.detector_attrNames = ['vnir', 'swir']
177

Daniel Scheffler's avatar
Daniel Scheffler committed
178
    def read_common_meta(self, observation_time: datetime=None):
179
180
181
182
        """Read the metadata belonging to both, the VNIR and SWIR detector of the EnMAP L1B product in sensor geometry.

        :param observation_time:  date and time of image observation (datetime.datetime)
        """
183
184
185
        # FIXME observation time is currently missing in the XML
        self.observation_datetime = observation_time

Daniel Scheffler's avatar
Daniel Scheffler committed
186
    def read_metadata(self, observation_time: datetime=None, lon_lat_smpl=(15, 15), nsmile_coef=4):
187
188
189
190
191
192
        """Read the metadata of the whole EnMAP L1B product in sensor geometry.

        :param observation_time:  date and time of image observation (datetime.datetime)
        :param lon_lat_smpl:  number if sampling points in lon, lat fields
        :param nsmile_coef:  number of polynomial coefficients for smile
        """
Daniel Scheffler's avatar
Daniel Scheffler committed
193
        self.read_common_meta(observation_time)
194
        self.vnir = EnMAP_Metadata_L1B_VNIR_SensorGeo(self._path_xml)
195
        self.vnir.read_metadata(lon_lat_smpl=lon_lat_smpl, nsmile_coef=nsmile_coef)
196
        self.swir = EnMAP_Metadata_L1B_SWIR_SensorGeo(self._path_xml)
197
198
199
        self.swir.read_metadata(lon_lat_smpl=lon_lat_smpl, nsmile_coef=nsmile_coef)


200
class EnMAP_Metadata_L1B_VNIR_SensorGeo(_EnMAP_Metadata_L1B_Detector_SensorGeo):
201
    """EnMAP Metadata class holding the metadata of the VNIR detector in sensor geometry.
202
203

    NOTE:
204
        - inherits all attributes from base class _EnMAP_Metadata_Detector_SensorGeo.
205
    """
Daniel Scheffler's avatar
Daniel Scheffler committed
206

207
    def __init__(self, path_metaxml, logger=None):
208
209
210
211
212
        """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
        """
213
        # get all attributes from base class '_EnMAP_Metadata_Detector_SensorGeo'
214
        super(EnMAP_Metadata_L1B_VNIR_SensorGeo, self).__init__('VNIR', logger=logger)
Daniel Scheffler's avatar
Daniel Scheffler committed
215
        self._path_xml = path_metaxml
216
217
        self.detector_label = L1B_product_props['xml_detector_label']['VNIR']

218
    def read_metadata(self, lon_lat_smpl=(15, 15), nsmile_coef=5):
219
220
221
222
223
224
        """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)\
Daniel Scheffler's avatar
Daniel Scheffler committed
225
            ._read_metadata(self._path_xml, self.detector_label, lon_lat_smpl=lon_lat_smpl, nsmile_coef=nsmile_coef)
226
227


228
class EnMAP_Metadata_L1B_SWIR_SensorGeo(_EnMAP_Metadata_L1B_Detector_SensorGeo):
229
    """EnMAP Metadata class holding the metadata of the SWIR detector in sensor geometry.
230
231

    NOTE:
232
        - inherits all attributes from base class _EnMAP_Metadata_Detector_SensorGeo.
233
    """
Daniel Scheffler's avatar
Daniel Scheffler committed
234

235
    def __init__(self, path_metaxml, logger=None):
236
237
238
239
240
        """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
        """
241
        # get all attributes from base class '_EnMAP_Metadata_Detector_SensorGeo'
242
        super(EnMAP_Metadata_L1B_SWIR_SensorGeo, self).__init__('SWIR', logger=logger)
Daniel Scheffler's avatar
Daniel Scheffler committed
243
        self._path_xml = path_metaxml
244
245
        self.detector_label = L1B_product_props['xml_detector_label']['SWIR']

246
    def read_metadata(self, lon_lat_smpl=(15, 15), nsmile_coef=5):
247
        """Read the metadata of the SWIR detector in sensor geometry.
248
249
250
251

        :param lon_lat_smpl: number if sampling points in lon, lat fields
        :param nsmile_coef: number of polynomial coefficients for smile
        """
252
        super(EnMAP_Metadata_L1B_SWIR_SensorGeo, self)\
Daniel Scheffler's avatar
Daniel Scheffler committed
253
            ._read_metadata(self._path_xml, self.detector_label, lon_lat_smpl=lon_lat_smpl, nsmile_coef=nsmile_coef)