s1-denoise 0.0.0.dev0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- s1_denoise/__init__.py +18 -0
- s1_denoise/__main__.py +36 -0
- s1_denoise/auxiliary_files.py +481 -0
- s1_denoise/baq_decoder.py +203 -0
- s1_denoise/cli.py +118 -0
- s1_denoise/error_calculation.py +262 -0
- s1_denoise/jo_parser.py +295 -0
- s1_denoise/l0_noise_power_calculation.py +399 -0
- s1_denoise/logger.py +81 -0
- s1_denoise/noise_norm_factor.py +291 -0
- s1_denoise/noise_profiles.py +292 -0
- s1_denoise/output.py +551 -0
- s1_denoise/s1safe.py +1415 -0
- s1_denoise/thermal_noise.py +753 -0
- s1_denoise/utils.py +282 -0
- s1_denoise/xml_reader.py +571 -0
- s1_denoise-0.0.0.dev0.dist-info/METADATA +147 -0
- s1_denoise-0.0.0.dev0.dist-info/RECORD +21 -0
- s1_denoise-0.0.0.dev0.dist-info/WHEEL +4 -0
- s1_denoise-0.0.0.dev0.dist-info/entry_points.txt +2 -0
- s1_denoise-0.0.0.dev0.dist-info/licenses/LICENSE +21 -0
s1_denoise/s1safe.py
ADDED
|
@@ -0,0 +1,1415 @@
|
|
|
1
|
+
# SPDX-FileCopyrightText: Aresys S.r.l. <info@aresys.it>
|
|
2
|
+
# SPDX-License-Identifier: MIT
|
|
3
|
+
|
|
4
|
+
"""
|
|
5
|
+
Module providing classes and utilities for reading and handling Sentinel-1 SAFE products.
|
|
6
|
+
|
|
7
|
+
Defines S1Safe, S1Channel, and S1Roi as the primary abstractions for accessing
|
|
8
|
+
product metadata, annotation, noise, calibration, RFI, and measurement files across
|
|
9
|
+
TopSAR acquisition modes (IW, EW). Also provides functions for noise map
|
|
10
|
+
computation, calibration vector interpolation, and SAFE filename parsing.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from pathlib import Path
|
|
14
|
+
from datetime import datetime
|
|
15
|
+
import os
|
|
16
|
+
import xml.etree.ElementTree as ET
|
|
17
|
+
from PIL import Image
|
|
18
|
+
import numpy as np
|
|
19
|
+
import rasterio
|
|
20
|
+
from rasterio.windows import Window
|
|
21
|
+
|
|
22
|
+
from perseo_core.timing import PreciseDateTime
|
|
23
|
+
|
|
24
|
+
_subswaths = {'EW': ('EW1', 'EW2', 'EW3', 'EW4', 'EW5'),
|
|
25
|
+
'IW': ('IW1', 'IW2', 'IW3'),
|
|
26
|
+
'WV': ('WV1', 'WV2')}
|
|
27
|
+
|
|
28
|
+
_pols = {'DV': ('VV', 'VH'),
|
|
29
|
+
'DH': ('HH', 'HV'),
|
|
30
|
+
'SH': ('HH',),
|
|
31
|
+
'SV': ('VV',)}
|
|
32
|
+
|
|
33
|
+
calibration_types = ('dn', 'betaNought', 'gamma', 'sigmaNought')
|
|
34
|
+
|
|
35
|
+
def ch_idx(pol, swath_id):
|
|
36
|
+
"""
|
|
37
|
+
Return the key identifying a channel in the channel dictionary of ``S1Safe``.
|
|
38
|
+
|
|
39
|
+
Parameters
|
|
40
|
+
----------
|
|
41
|
+
pol : str
|
|
42
|
+
Polarization (e.g. ``VV``).
|
|
43
|
+
swath_id : str
|
|
44
|
+
Swath identifier (e.g. ``IW1``).
|
|
45
|
+
|
|
46
|
+
Returns
|
|
47
|
+
-------
|
|
48
|
+
tuple of str
|
|
49
|
+
The ``(pol, swath_id)`` pair.
|
|
50
|
+
"""
|
|
51
|
+
return pol, swath_id
|
|
52
|
+
|
|
53
|
+
def read_calibration_vectors(calibration_et_root, calibration):
|
|
54
|
+
"""
|
|
55
|
+
Read the calibration vectors of a given type from a calibration annotation.
|
|
56
|
+
|
|
57
|
+
Parameters
|
|
58
|
+
----------
|
|
59
|
+
calibration_et_root : xml.etree.ElementTree.ElementTree or xml.etree.ElementTree.Element
|
|
60
|
+
Parsed calibration annotation XML.
|
|
61
|
+
calibration : str
|
|
62
|
+
Calibration type, one of ``dn``, ``betaNought``, ``gamma``, ``sigmaNought``.
|
|
63
|
+
|
|
64
|
+
Returns
|
|
65
|
+
-------
|
|
66
|
+
list of dict
|
|
67
|
+
One dictionary per ``calibrationVector`` with keys ``azimuthTime`` (PreciseDateTime), ``line`` (int),
|
|
68
|
+
``pixel`` (numpy.ndarray of int) and ``<calibration>`` (numpy.ndarray of float, calibration LUT values).
|
|
69
|
+
|
|
70
|
+
Raises
|
|
71
|
+
------
|
|
72
|
+
ValueError
|
|
73
|
+
If ``calibration`` is not a supported calibration type.
|
|
74
|
+
"""
|
|
75
|
+
if calibration not in calibration_types:
|
|
76
|
+
raise ValueError('Calibration type must be one of the following:' + ', '.join(calibration_types))
|
|
77
|
+
cal_vector_list = list()
|
|
78
|
+
for cal_vector in calibration_et_root.findall('calibrationVectorList/calibrationVector'):
|
|
79
|
+
cal_vector_dict = dict()
|
|
80
|
+
cal_vector_dict['azimuthTime'] = PreciseDateTime.fromisoformat(cal_vector.find('azimuthTime').text)
|
|
81
|
+
cal_vector_dict['line'] = int(cal_vector.find('line').text)
|
|
82
|
+
cal_vector_dict['pixel'] = np.fromstring(cal_vector.find('pixel').text, sep=' ', dtype=int)
|
|
83
|
+
cal_vector_dict[calibration] = np.fromstring(cal_vector.find(calibration).text, sep=' ', dtype=float)
|
|
84
|
+
cal_vector_list.append(cal_vector_dict)
|
|
85
|
+
return cal_vector_list
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def calibrate(annotation_et_root, calibration_et_root, data, roi, calibration=None):
|
|
89
|
+
"""
|
|
90
|
+
Apply radiometric calibration to data over a region of interest.
|
|
91
|
+
|
|
92
|
+
The calibration LUT is interpolated linearly in range on the ROI pixels and linearly in azimuth time between
|
|
93
|
+
the two calibration vectors bracketing each line (extrapolating outside the annotated vectors). The output
|
|
94
|
+
is ``|data|**2 / lut**2``.
|
|
95
|
+
|
|
96
|
+
Parameters
|
|
97
|
+
----------
|
|
98
|
+
annotation_et_root : xml.etree.ElementTree.ElementTree or xml.etree.ElementTree.Element
|
|
99
|
+
Parsed product annotation XML, used to build the azimuth time axis.
|
|
100
|
+
calibration_et_root : xml.etree.ElementTree.ElementTree or xml.etree.ElementTree.Element
|
|
101
|
+
Parsed calibration annotation XML.
|
|
102
|
+
data : numpy.ndarray
|
|
103
|
+
Data over the ROI, with shape ``(roi.num_azi_lines, roi.num_range_samples)``.
|
|
104
|
+
roi : S1Roi
|
|
105
|
+
Region of interest covered by ``data``.
|
|
106
|
+
calibration : str, optional
|
|
107
|
+
Calibration type, one of ``dn``, ``betaNought``, ``gamma``, ``sigmaNought``. Default is ``None``, in which
|
|
108
|
+
case ``data`` is returned unchanged.
|
|
109
|
+
|
|
110
|
+
Returns
|
|
111
|
+
-------
|
|
112
|
+
numpy.ndarray
|
|
113
|
+
Calibrated data in linear power units, or ``data`` itself if ``calibration`` is ``None``.
|
|
114
|
+
|
|
115
|
+
Raises
|
|
116
|
+
------
|
|
117
|
+
ValueError
|
|
118
|
+
If ``calibration`` is not a supported calibration type.
|
|
119
|
+
"""
|
|
120
|
+
if calibration is None:
|
|
121
|
+
return data
|
|
122
|
+
# Build the azimuth time axis
|
|
123
|
+
cal_vectors = read_calibration_vectors(calibration_et_root, calibration)
|
|
124
|
+
start_line_time = PreciseDateTime.fromisoformat(annotation_et_root.find('imageAnnotation/imageInformation/productFirstLineUtcTime').text)
|
|
125
|
+
line_time_step = float(annotation_et_root.find('imageAnnotation/imageInformation/azimuthTimeInterval').text)
|
|
126
|
+
azi_time_axis = np.array([start_line_time + ii * line_time_step for ii in range(roi.azi_start_idx, roi.azi_start_idx + roi.num_azi_lines)])
|
|
127
|
+
|
|
128
|
+
# Resample all the calibration vectors on the same range axis
|
|
129
|
+
rng_pixel_axis = np.arange(roi.range_start_idx, roi.range_start_idx+roi.num_range_samples)
|
|
130
|
+
for cal_vec in cal_vectors:
|
|
131
|
+
cal_vec[calibration] = np.interp(rng_pixel_axis, cal_vec['pixel'], cal_vec[calibration])
|
|
132
|
+
|
|
133
|
+
# For each azimuth time, linearly interpolate the vectors before and after it.
|
|
134
|
+
cal_mat = np.zeros((roi.num_azi_lines, roi.num_range_samples))
|
|
135
|
+
for azi_idx, azi_time in enumerate(azi_time_axis):
|
|
136
|
+
# Search an azimuth time lower than the current line being processed
|
|
137
|
+
cal_vec_idx = 1
|
|
138
|
+
while (cal_vec_idx < len(cal_vectors) - 1) and (cal_vectors[cal_vec_idx]['azimuthTime'] <= azi_time):
|
|
139
|
+
cal_vec_idx += 1
|
|
140
|
+
# Linear interpolation of the calibration vector
|
|
141
|
+
cal_mat[azi_idx, :] = (cal_vectors[cal_vec_idx - 1][calibration] +
|
|
142
|
+
(cal_vectors[cal_vec_idx][calibration] - cal_vectors[cal_vec_idx - 1][calibration]) /
|
|
143
|
+
(cal_vectors[cal_vec_idx]['azimuthTime'] - cal_vectors[cal_vec_idx - 1]['azimuthTime']) *
|
|
144
|
+
(azi_time - cal_vectors[cal_vec_idx - 1]['azimuthTime']))
|
|
145
|
+
return np.abs(data)**2 / cal_mat**2
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
def find_azi_block_from_line(azi_noise_vec_list, line_idx):
|
|
149
|
+
"""
|
|
150
|
+
Return the index of the azimuth noise vector whose line interval contains a given line.
|
|
151
|
+
|
|
152
|
+
Parameters
|
|
153
|
+
----------
|
|
154
|
+
azi_noise_vec_list : list of dict
|
|
155
|
+
Azimuth noise vectors of one swath, as returned by ``read_azimuth_noise_vectors``.
|
|
156
|
+
line_idx : int
|
|
157
|
+
Azimuth line index.
|
|
158
|
+
|
|
159
|
+
Returns
|
|
160
|
+
-------
|
|
161
|
+
int or None
|
|
162
|
+
Index of the first vector with ``firstAzimuthLine <= line_idx <= lastAzimuthLine``, or ``None`` if none.
|
|
163
|
+
"""
|
|
164
|
+
for vec_idx, azi_noise_vec in enumerate(azi_noise_vec_list):
|
|
165
|
+
if (azi_noise_vec['firstAzimuthLine'] <= line_idx) and (azi_noise_vec['lastAzimuthLine'] >= line_idx):
|
|
166
|
+
return vec_idx
|
|
167
|
+
return None
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
def read_range_noise_vectors(noise_et_root):
|
|
171
|
+
"""
|
|
172
|
+
Read the range noise vectors from a noise annotation.
|
|
173
|
+
|
|
174
|
+
Parameters
|
|
175
|
+
----------
|
|
176
|
+
noise_et_root : xml.etree.ElementTree.ElementTree or xml.etree.ElementTree.Element
|
|
177
|
+
Parsed noise annotation XML.
|
|
178
|
+
|
|
179
|
+
Returns
|
|
180
|
+
-------
|
|
181
|
+
list of dict
|
|
182
|
+
One dictionary per ``noiseRangeVector`` with keys ``azimuthTime`` (PreciseDateTime), ``line`` (int),
|
|
183
|
+
``pixel`` (numpy.ndarray of int) and ``noiseRangeLut`` (numpy.ndarray of float, linear power).
|
|
184
|
+
"""
|
|
185
|
+
noise_rng_vec_list = list()
|
|
186
|
+
for noise_rng_vec in noise_et_root.findall('noiseRangeVectorList/noiseRangeVector'):
|
|
187
|
+
noise_rng_vec_dict = dict()
|
|
188
|
+
noise_rng_vec_dict['azimuthTime'] = PreciseDateTime.fromisoformat(noise_rng_vec.find('azimuthTime').text)
|
|
189
|
+
noise_rng_vec_dict['line'] = int(noise_rng_vec.find('line').text)
|
|
190
|
+
noise_rng_vec_dict['pixel'] = np.fromstring(noise_rng_vec.find('pixel').text, sep=' ', dtype=int)
|
|
191
|
+
noise_rng_vec_dict['noiseRangeLut'] = np.fromstring(noise_rng_vec.find('noiseRangeLut').text, sep=' ',
|
|
192
|
+
dtype=float)
|
|
193
|
+
noise_rng_vec_list.append(noise_rng_vec_dict)
|
|
194
|
+
return noise_rng_vec_list
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
def read_azimuth_noise_vectors(noise_et_root):
|
|
198
|
+
"""
|
|
199
|
+
Read the azimuth noise vectors from a noise annotation, grouped by swath.
|
|
200
|
+
|
|
201
|
+
Parameters
|
|
202
|
+
----------
|
|
203
|
+
noise_et_root : xml.etree.ElementTree.ElementTree or xml.etree.ElementTree.Element
|
|
204
|
+
Parsed noise annotation XML.
|
|
205
|
+
|
|
206
|
+
Returns
|
|
207
|
+
-------
|
|
208
|
+
dict of str to list of dict
|
|
209
|
+
Mapping from swath identifier to its azimuth noise vectors. Each vector has keys ``swath``,
|
|
210
|
+
``firstAzimuthLine``, ``firstRangeSample``, ``lastAzimuthLine``, ``lastRangeSample`` (int), ``line``
|
|
211
|
+
(numpy.ndarray of int) and ``noiseAzimuthLut`` (numpy.ndarray of float, linear). Empty if the annotation
|
|
212
|
+
has no azimuth noise vectors.
|
|
213
|
+
"""
|
|
214
|
+
noise_azi_vec_dict = dict()
|
|
215
|
+
for noise_azi_vec in noise_et_root.findall('noiseAzimuthVectorList/noiseAzimuthVector'):
|
|
216
|
+
noise_azi_vec_curr_swath = dict()
|
|
217
|
+
curr_swath = noise_azi_vec.find('swath').text
|
|
218
|
+
noise_azi_vec_curr_swath['swath'] = curr_swath
|
|
219
|
+
noise_azi_vec_curr_swath['firstAzimuthLine'] = int(noise_azi_vec.find('firstAzimuthLine').text)
|
|
220
|
+
noise_azi_vec_curr_swath['firstRangeSample'] = int(noise_azi_vec.find('firstRangeSample').text)
|
|
221
|
+
noise_azi_vec_curr_swath['lastAzimuthLine'] = int(noise_azi_vec.find('lastAzimuthLine').text)
|
|
222
|
+
noise_azi_vec_curr_swath['lastRangeSample'] = int(noise_azi_vec.find('lastRangeSample').text)
|
|
223
|
+
noise_azi_vec_curr_swath['line'] = np.fromstring(noise_azi_vec.find('line').text, sep=' ', dtype=int)
|
|
224
|
+
noise_azi_vec_curr_swath['noiseAzimuthLut'] = np.fromstring(noise_azi_vec.find('noiseAzimuthLut').text,
|
|
225
|
+
sep=' ', dtype=float)
|
|
226
|
+
if curr_swath not in noise_azi_vec_dict:
|
|
227
|
+
noise_azi_vec_dict[curr_swath] = [noise_azi_vec_curr_swath]
|
|
228
|
+
else:
|
|
229
|
+
noise_azi_vec_dict[curr_swath].append(noise_azi_vec_curr_swath)
|
|
230
|
+
return noise_azi_vec_dict
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
def compute_noise_map(noise_et_root, data_size, roi):
|
|
234
|
+
"""
|
|
235
|
+
Compute a 2D noise map over a region of interest from the annotated noise vectors.
|
|
236
|
+
|
|
237
|
+
Range LUTs are interpolated to every range sample and linearly in azimuth between the range vectors bracketing
|
|
238
|
+
each line (restricted to the vectors falling in the current azimuth block, or in the whole swath as fallback).
|
|
239
|
+
The result is multiplied by the azimuth LUT interpolated on the lines of each azimuth block. If the annotation
|
|
240
|
+
has no azimuth vectors, a unit azimuth LUT over the whole image is used. Pixels not covered by any azimuth
|
|
241
|
+
block are left to zero.
|
|
242
|
+
|
|
243
|
+
Parameters
|
|
244
|
+
----------
|
|
245
|
+
noise_et_root : xml.etree.ElementTree.ElementTree or xml.etree.ElementTree.Element
|
|
246
|
+
Parsed noise annotation XML.
|
|
247
|
+
data_size : tuple of int
|
|
248
|
+
Image size as ``(num_azimuth_lines, num_range_samples)``.
|
|
249
|
+
roi : tuple of int
|
|
250
|
+
Region of interest as ``(start_sample, start_line, width, height)``, i.e. ``S1Roi.as_tuple()``.
|
|
251
|
+
|
|
252
|
+
Returns
|
|
253
|
+
-------
|
|
254
|
+
numpy.ndarray
|
|
255
|
+
Noise map of shape ``(height, width)``, square root of the interpolated noise power LUT product (linear
|
|
256
|
+
amplitude scale).
|
|
257
|
+
|
|
258
|
+
Raises
|
|
259
|
+
------
|
|
260
|
+
ValueError
|
|
261
|
+
If the ROI exceeds the image size, or if no range noise vector is found for a swath.
|
|
262
|
+
"""
|
|
263
|
+
|
|
264
|
+
n_az, n_rg = data_size
|
|
265
|
+
start_sample, start_line, width, height = roi
|
|
266
|
+
end_sample = start_sample + width - 1
|
|
267
|
+
end_line = start_line + height - 1
|
|
268
|
+
|
|
269
|
+
if end_sample >= n_rg or end_line >= n_az:
|
|
270
|
+
raise ValueError("ROI outside of bounds")
|
|
271
|
+
|
|
272
|
+
range_noise = read_range_noise_vectors(noise_et_root)
|
|
273
|
+
azimuth_noise = read_azimuth_noise_vectors(noise_et_root)
|
|
274
|
+
|
|
275
|
+
# Handle missing azimuth noise
|
|
276
|
+
if not azimuth_noise:
|
|
277
|
+
swath = noise_et_root.find("adsHeader/swath").text
|
|
278
|
+
azimuth_noise[swath] = [{
|
|
279
|
+
"swath": swath,
|
|
280
|
+
"firstAzimuthLine": 0,
|
|
281
|
+
"lastAzimuthLine": n_az - 1,
|
|
282
|
+
"firstRangeSample": 0,
|
|
283
|
+
"lastRangeSample": n_rg - 1,
|
|
284
|
+
"line": np.array([0, n_az - 1]),
|
|
285
|
+
"noiseAzimuthLut": np.array([1.0, 1.0]),
|
|
286
|
+
}]
|
|
287
|
+
|
|
288
|
+
# Precompute swath limits
|
|
289
|
+
min_line = {k: min(v["firstAzimuthLine"] for v in lst) for k, lst in azimuth_noise.items()}
|
|
290
|
+
max_line = {k: max(v["lastAzimuthLine"] for v in lst) for k, lst in azimuth_noise.items()}
|
|
291
|
+
|
|
292
|
+
# Pre-interpolate range LUTs to full pixel resolution
|
|
293
|
+
pixels = np.arange(n_rg)
|
|
294
|
+
for rvec in range_noise:
|
|
295
|
+
if rvec:
|
|
296
|
+
rvec["noiseRangeLut"] = np.interp(pixels, rvec["pixel"], rvec["noiseRangeLut"])
|
|
297
|
+
|
|
298
|
+
# Pre-cache line values for range noise vectors
|
|
299
|
+
line_arr_rng = np.array([rvec["line"] for rvec in range_noise])
|
|
300
|
+
noise_map = np.zeros((height, width), dtype=float)
|
|
301
|
+
|
|
302
|
+
for swath_name, azi_list in azimuth_noise.items():
|
|
303
|
+
for azi_vec in azi_list:
|
|
304
|
+
first_line, last_line = azi_vec["firstAzimuthLine"], azi_vec["lastAzimuthLine"]
|
|
305
|
+
|
|
306
|
+
# Lines overlapping ROI
|
|
307
|
+
valid_lines = np.arange(max(first_line, start_line), min(last_line, end_line) + 1)
|
|
308
|
+
if valid_lines.size == 0:
|
|
309
|
+
continue
|
|
310
|
+
|
|
311
|
+
# Interpolate azimuth LUT over valid lines
|
|
312
|
+
line_arr = azi_vec["line"]
|
|
313
|
+
azi_lut = azi_vec["noiseAzimuthLut"]
|
|
314
|
+
azi_interp = np.interp(valid_lines, line_arr, azi_lut) if len(line_arr) > 1 else np.full(valid_lines.shape, azi_lut[0])
|
|
315
|
+
|
|
316
|
+
# Valid range samples
|
|
317
|
+
valid_rg = np.arange(max(azi_vec["firstRangeSample"], start_sample),
|
|
318
|
+
min(azi_vec["lastRangeSample"], end_sample) + 1)
|
|
319
|
+
if valid_rg.size == 0:
|
|
320
|
+
continue
|
|
321
|
+
|
|
322
|
+
# Find range noise vectors once per azimuth segment
|
|
323
|
+
in_swath = (line_arr_rng >= first_line) & (line_arr_rng <= last_line)
|
|
324
|
+
valid_idx = np.where(in_swath)[0]
|
|
325
|
+
|
|
326
|
+
if valid_idx.size == 0:
|
|
327
|
+
# fallback to sub-swath range
|
|
328
|
+
in_subswath = (line_arr_rng >= min_line[swath_name]) & (line_arr_rng <= max_line[swath_name])
|
|
329
|
+
valid_idx = np.where(in_subswath)[0]
|
|
330
|
+
if valid_idx.size == 0:
|
|
331
|
+
raise ValueError(f"{swath_name}: no valid range vectors")
|
|
332
|
+
|
|
333
|
+
# Process each azimuth line
|
|
334
|
+
for i, line in enumerate(valid_lines):
|
|
335
|
+
if valid_idx.size == 1:
|
|
336
|
+
rng_lut = range_noise[valid_idx[0]]["noiseRangeLut"][valid_rg]
|
|
337
|
+
else:
|
|
338
|
+
# find bracket vectors
|
|
339
|
+
idx = np.searchsorted(line_arr_rng[valid_idx], line) - 1
|
|
340
|
+
idx = np.clip(idx, 0, len(valid_idx) - 2)
|
|
341
|
+
|
|
342
|
+
vec1 = range_noise[valid_idx[idx]]
|
|
343
|
+
vec2 = range_noise[valid_idx[idx + 1]]
|
|
344
|
+
lut1, lut2 = vec1["noiseRangeLut"][valid_rg], vec2["noiseRangeLut"][valid_rg]
|
|
345
|
+
w = (line - vec1["line"]) / (vec2["line"] - vec1["line"])
|
|
346
|
+
rng_lut = lut1 + w * (lut2 - lut1)
|
|
347
|
+
|
|
348
|
+
noise_map[line - start_line, valid_rg - start_sample] = rng_lut * azi_interp[i]
|
|
349
|
+
|
|
350
|
+
return np.sqrt(noise_map)
|
|
351
|
+
|
|
352
|
+
|
|
353
|
+
def subswaths_from_mode(mode_name):
|
|
354
|
+
"""
|
|
355
|
+
Return the swath identifiers of an acquisition mode.
|
|
356
|
+
|
|
357
|
+
Parameters
|
|
358
|
+
----------
|
|
359
|
+
mode_name : str
|
|
360
|
+
Acquisition mode (``IW``, ``EW``, ``WV`` or a stripmap mode ``S1``..``S6``).
|
|
361
|
+
|
|
362
|
+
Returns
|
|
363
|
+
-------
|
|
364
|
+
tuple of str
|
|
365
|
+
Swath identifiers (e.g. ``('IW1', 'IW2', 'IW3')``); for stripmap modes, the mode itself.
|
|
366
|
+
|
|
367
|
+
Raises
|
|
368
|
+
------
|
|
369
|
+
KeyError
|
|
370
|
+
If the mode is not supported.
|
|
371
|
+
"""
|
|
372
|
+
if is_stripmap_mode(mode_name):
|
|
373
|
+
return (mode_name,)
|
|
374
|
+
return _subswaths[mode_name]
|
|
375
|
+
|
|
376
|
+
|
|
377
|
+
def mode_from_safe_name(safe_name):
|
|
378
|
+
"""
|
|
379
|
+
Return the acquisition mode from a SAFE product name.
|
|
380
|
+
|
|
381
|
+
Parameters
|
|
382
|
+
----------
|
|
383
|
+
safe_name : str
|
|
384
|
+
SAFE product name (e.g. ``S1A_IW_SLC__1SDV_...``).
|
|
385
|
+
|
|
386
|
+
Returns
|
|
387
|
+
-------
|
|
388
|
+
str
|
|
389
|
+
Two-character acquisition mode (e.g. ``IW``).
|
|
390
|
+
"""
|
|
391
|
+
return safe_name[4:6]
|
|
392
|
+
|
|
393
|
+
|
|
394
|
+
def product_type_from_safe_name(safe_name):
|
|
395
|
+
"""
|
|
396
|
+
Return the product type from a SAFE product name.
|
|
397
|
+
|
|
398
|
+
Parameters
|
|
399
|
+
----------
|
|
400
|
+
safe_name : str
|
|
401
|
+
SAFE product name.
|
|
402
|
+
|
|
403
|
+
Returns
|
|
404
|
+
-------
|
|
405
|
+
str
|
|
406
|
+
Product type with underscores removed (e.g. ``SLC``, ``GRDH``).
|
|
407
|
+
"""
|
|
408
|
+
tmp = safe_name[6:11]
|
|
409
|
+
tmp = tmp.replace('_', '')
|
|
410
|
+
return tmp
|
|
411
|
+
|
|
412
|
+
|
|
413
|
+
def polarizations_from_safe_name(safe_name):
|
|
414
|
+
"""
|
|
415
|
+
Return the polarizations from a SAFE product name.
|
|
416
|
+
|
|
417
|
+
Parameters
|
|
418
|
+
----------
|
|
419
|
+
safe_name : str
|
|
420
|
+
SAFE product name.
|
|
421
|
+
|
|
422
|
+
Returns
|
|
423
|
+
-------
|
|
424
|
+
tuple of str
|
|
425
|
+
Polarizations of the product (e.g. ``('VV', 'VH')`` for ``DV``).
|
|
426
|
+
|
|
427
|
+
Raises
|
|
428
|
+
------
|
|
429
|
+
KeyError
|
|
430
|
+
If the polarization code is not supported.
|
|
431
|
+
"""
|
|
432
|
+
return _pols[safe_name[14:16]]
|
|
433
|
+
|
|
434
|
+
|
|
435
|
+
def start_time_str_from_safe_name(safe_name):
|
|
436
|
+
"""
|
|
437
|
+
Return the sensing start time string from a SAFE product name.
|
|
438
|
+
|
|
439
|
+
Parameters
|
|
440
|
+
----------
|
|
441
|
+
safe_name : str
|
|
442
|
+
SAFE product name.
|
|
443
|
+
|
|
444
|
+
Returns
|
|
445
|
+
-------
|
|
446
|
+
str
|
|
447
|
+
Start time in the format ``YYYYMMDDTHHMMSS``.
|
|
448
|
+
"""
|
|
449
|
+
return safe_name[17:32]
|
|
450
|
+
|
|
451
|
+
|
|
452
|
+
def stop_time_str_from_safe_name(safe_name):
|
|
453
|
+
"""
|
|
454
|
+
Return the sensing stop time string from a SAFE product name.
|
|
455
|
+
|
|
456
|
+
Parameters
|
|
457
|
+
----------
|
|
458
|
+
safe_name : str
|
|
459
|
+
SAFE product name.
|
|
460
|
+
|
|
461
|
+
Returns
|
|
462
|
+
-------
|
|
463
|
+
str
|
|
464
|
+
Stop time in the format ``YYYYMMDDTHHMMSS``.
|
|
465
|
+
"""
|
|
466
|
+
return safe_name[33:48]
|
|
467
|
+
|
|
468
|
+
|
|
469
|
+
def is_topsar_mode(mode_name):
|
|
470
|
+
"""
|
|
471
|
+
Check whether an acquisition mode is a TOPSAR mode.
|
|
472
|
+
|
|
473
|
+
Parameters
|
|
474
|
+
----------
|
|
475
|
+
mode_name : str
|
|
476
|
+
Acquisition mode.
|
|
477
|
+
|
|
478
|
+
Returns
|
|
479
|
+
-------
|
|
480
|
+
bool
|
|
481
|
+
``True`` for ``IW`` and ``EW``, ``False`` otherwise.
|
|
482
|
+
"""
|
|
483
|
+
if mode_name == 'EW' or mode_name == 'IW':
|
|
484
|
+
return True
|
|
485
|
+
return False
|
|
486
|
+
|
|
487
|
+
|
|
488
|
+
def is_topsar(safe_name):
|
|
489
|
+
"""
|
|
490
|
+
Check whether a SAFE product was acquired in a TOPSAR mode (IW or EW).
|
|
491
|
+
|
|
492
|
+
Parameters
|
|
493
|
+
----------
|
|
494
|
+
safe_name : str
|
|
495
|
+
SAFE product name.
|
|
496
|
+
|
|
497
|
+
Returns
|
|
498
|
+
-------
|
|
499
|
+
bool
|
|
500
|
+
``True`` if the product is IW or EW.
|
|
501
|
+
"""
|
|
502
|
+
mode = mode_from_safe_name(safe_name)
|
|
503
|
+
return is_topsar_mode(mode)
|
|
504
|
+
|
|
505
|
+
|
|
506
|
+
def is_stripmap_mode(mode_name):
|
|
507
|
+
"""
|
|
508
|
+
Check whether an acquisition mode is a stripmap mode.
|
|
509
|
+
|
|
510
|
+
Parameters
|
|
511
|
+
----------
|
|
512
|
+
mode_name : str
|
|
513
|
+
Acquisition mode.
|
|
514
|
+
|
|
515
|
+
Returns
|
|
516
|
+
-------
|
|
517
|
+
bool
|
|
518
|
+
``True`` if the mode starts with ``S`` (``S1``..``S6``).
|
|
519
|
+
"""
|
|
520
|
+
if mode_name[:1] == 'S':
|
|
521
|
+
return True
|
|
522
|
+
return False
|
|
523
|
+
|
|
524
|
+
|
|
525
|
+
def is_stripmap(safe_name):
|
|
526
|
+
"""
|
|
527
|
+
Check whether a SAFE product was acquired in a stripmap mode.
|
|
528
|
+
|
|
529
|
+
Parameters
|
|
530
|
+
----------
|
|
531
|
+
safe_name : str
|
|
532
|
+
SAFE product name.
|
|
533
|
+
|
|
534
|
+
Returns
|
|
535
|
+
-------
|
|
536
|
+
bool
|
|
537
|
+
``True`` if the product is a stripmap product.
|
|
538
|
+
"""
|
|
539
|
+
mode = mode_from_safe_name(safe_name)
|
|
540
|
+
return is_stripmap_mode(mode)
|
|
541
|
+
|
|
542
|
+
|
|
543
|
+
def is_wave(safe_name):
|
|
544
|
+
"""
|
|
545
|
+
Check whether a SAFE product was acquired in wave mode.
|
|
546
|
+
|
|
547
|
+
Parameters
|
|
548
|
+
----------
|
|
549
|
+
safe_name : str
|
|
550
|
+
SAFE product name.
|
|
551
|
+
|
|
552
|
+
Returns
|
|
553
|
+
-------
|
|
554
|
+
bool or None
|
|
555
|
+
``True`` if the mode is ``WV``, ``None`` otherwise.
|
|
556
|
+
"""
|
|
557
|
+
mode = mode_from_safe_name(safe_name)
|
|
558
|
+
if mode == 'WV':
|
|
559
|
+
return True
|
|
560
|
+
|
|
561
|
+
|
|
562
|
+
def is_grd(safe_name):
|
|
563
|
+
"""
|
|
564
|
+
Check whether a SAFE product is a GRD product.
|
|
565
|
+
|
|
566
|
+
Parameters
|
|
567
|
+
----------
|
|
568
|
+
safe_name : str
|
|
569
|
+
SAFE product name.
|
|
570
|
+
|
|
571
|
+
Returns
|
|
572
|
+
-------
|
|
573
|
+
bool
|
|
574
|
+
``True`` if the product type is ``GR`` followed by two characters (e.g. ``GRDH``, ``GRDM``).
|
|
575
|
+
"""
|
|
576
|
+
prod = product_type_from_safe_name(safe_name)
|
|
577
|
+
if prod[:-2] == 'GR':
|
|
578
|
+
return True
|
|
579
|
+
return False
|
|
580
|
+
|
|
581
|
+
|
|
582
|
+
def measurement_file_from_safe_path(safe_path, pol, swath, img_num=None):
|
|
583
|
+
"""
|
|
584
|
+
Return the measurement TIFF file of a channel inside a SAFE product.
|
|
585
|
+
|
|
586
|
+
Parameters
|
|
587
|
+
----------
|
|
588
|
+
safe_path : str or pathlib.Path
|
|
589
|
+
Path to the SAFE product directory.
|
|
590
|
+
pol : str
|
|
591
|
+
Polarization (e.g. ``VV``, ``VH``).
|
|
592
|
+
swath : str
|
|
593
|
+
Swath identifier (e.g. ``IW1``, ``EW3``, ``WV1``).
|
|
594
|
+
img_num : str, optional
|
|
595
|
+
Image number, used only for wave mode swaths (``WV1``, ``WV2``). Default is ``None``.
|
|
596
|
+
|
|
597
|
+
Returns
|
|
598
|
+
-------
|
|
599
|
+
pathlib.Path
|
|
600
|
+
Path to the first matching file in ``measurement``.
|
|
601
|
+
|
|
602
|
+
Raises
|
|
603
|
+
------
|
|
604
|
+
StopIteration
|
|
605
|
+
If no matching file is found (e.g. for annotation-only products).
|
|
606
|
+
"""
|
|
607
|
+
safe_path = Path(safe_path)
|
|
608
|
+
if (swath in ['WV1', 'WV2']) and (img_num is not None):
|
|
609
|
+
return next(safe_path.glob('measurement/s1*-{}-*-{}-*-{}.tiff'.format(swath.lower(), pol.lower(), img_num)))
|
|
610
|
+
else:
|
|
611
|
+
return next(safe_path.glob('measurement/s1*-{}-*-{}-*.tiff'.format(swath.lower(), pol.lower())))
|
|
612
|
+
|
|
613
|
+
|
|
614
|
+
def calibration_file_from_safe_path(safe_path, pol, swath, img_num=None):
|
|
615
|
+
"""
|
|
616
|
+
Return the calibration annotation file of a channel inside a SAFE product.
|
|
617
|
+
|
|
618
|
+
The file is searched in ``annotation/calibration`` by swath, product type and polarization. Product type ``SL1`` is
|
|
619
|
+
searched as ``SLC``.
|
|
620
|
+
|
|
621
|
+
Parameters
|
|
622
|
+
----------
|
|
623
|
+
safe_path : str or pathlib.Path
|
|
624
|
+
Path to the SAFE product directory.
|
|
625
|
+
pol : str
|
|
626
|
+
Polarization (e.g. ``VV``, ``VH``).
|
|
627
|
+
swath : str
|
|
628
|
+
Swath identifier (e.g. ``IW1``, ``EW3``, ``WV1``).
|
|
629
|
+
img_num : str, optional
|
|
630
|
+
Image number, used only for wave mode swaths (``WV1``, ``WV2``). Default is ``None``.
|
|
631
|
+
|
|
632
|
+
Returns
|
|
633
|
+
-------
|
|
634
|
+
pathlib.Path
|
|
635
|
+
Path to the first matching calibration annotation file.
|
|
636
|
+
|
|
637
|
+
Raises
|
|
638
|
+
------
|
|
639
|
+
StopIteration
|
|
640
|
+
If no matching file is found.
|
|
641
|
+
"""
|
|
642
|
+
safe_path = Path(safe_path)
|
|
643
|
+
safe_name = safe_path.name
|
|
644
|
+
prod = product_type_from_safe_name(safe_name)
|
|
645
|
+
if prod == 'SL1':
|
|
646
|
+
prod = 'SLC'
|
|
647
|
+
|
|
648
|
+
if (swath in ['WV1', 'WV2']) and (img_num is not None):
|
|
649
|
+
return next(safe_path.glob('annotation/calibration/calibration*-{}-{}-{}-*-{}.xml'.format(swath.lower(), prod[:3].lower(), pol.lower(), img_num)))
|
|
650
|
+
else:
|
|
651
|
+
return next(safe_path.glob('annotation/calibration/calibration*-{}-{}-{}-*.xml'.format(swath.lower(), prod[:3].lower(), pol.lower())))
|
|
652
|
+
|
|
653
|
+
|
|
654
|
+
def noise_file_from_safe_path(safe_path, pol, swath, img_num=None):
|
|
655
|
+
"""
|
|
656
|
+
Return the noise annotation file of a channel inside a SAFE product.
|
|
657
|
+
|
|
658
|
+
The file is searched in ``annotation/calibration`` by swath, product type and polarization. Product type ``SL1`` is
|
|
659
|
+
searched as ``SLC``.
|
|
660
|
+
|
|
661
|
+
Parameters
|
|
662
|
+
----------
|
|
663
|
+
safe_path : str or pathlib.Path
|
|
664
|
+
Path to the SAFE product directory.
|
|
665
|
+
pol : str
|
|
666
|
+
Polarization (e.g. ``VV``, ``VH``).
|
|
667
|
+
swath : str
|
|
668
|
+
Swath identifier (e.g. ``IW1``, ``EW3``, ``WV1``).
|
|
669
|
+
img_num : str, optional
|
|
670
|
+
Image number, used only for wave mode swaths (``WV1``, ``WV2``). Default is ``None``.
|
|
671
|
+
|
|
672
|
+
Returns
|
|
673
|
+
-------
|
|
674
|
+
pathlib.Path
|
|
675
|
+
Path to the first matching noise annotation file.
|
|
676
|
+
|
|
677
|
+
Raises
|
|
678
|
+
------
|
|
679
|
+
StopIteration
|
|
680
|
+
If no matching file is found.
|
|
681
|
+
"""
|
|
682
|
+
safe_path = Path(safe_path)
|
|
683
|
+
safe_name = safe_path.name
|
|
684
|
+
prod = product_type_from_safe_name(safe_name)
|
|
685
|
+
if prod == 'SL1':
|
|
686
|
+
prod = 'SLC'
|
|
687
|
+
|
|
688
|
+
if (swath in ['WV1', 'WV2']) and (img_num is not None):
|
|
689
|
+
return next(safe_path.glob('annotation/calibration/noise*-{}-{}-{}-*-{}.xml'.format(swath.lower(), prod[:3].lower(), pol.lower(), img_num)))
|
|
690
|
+
else:
|
|
691
|
+
return next(safe_path.glob('annotation/calibration/noise*-{}-{}-{}-*.xml'.format(swath.lower(), prod[:3].lower(), pol.lower())))
|
|
692
|
+
|
|
693
|
+
|
|
694
|
+
def rfi_file_from_safe_path(safe_path, pol, swath, img_num=None):
|
|
695
|
+
"""
|
|
696
|
+
Return the RFI annotation file of a channel inside a SAFE product.
|
|
697
|
+
|
|
698
|
+
The file is searched in ``annotation/rfi`` by swath, product type and polarization. Product type ``SL1`` is
|
|
699
|
+
searched as ``SLC``.
|
|
700
|
+
|
|
701
|
+
Parameters
|
|
702
|
+
----------
|
|
703
|
+
safe_path : str or pathlib.Path
|
|
704
|
+
Path to the SAFE product directory.
|
|
705
|
+
pol : str
|
|
706
|
+
Polarization (e.g. ``VV``, ``VH``).
|
|
707
|
+
swath : str
|
|
708
|
+
Swath identifier (e.g. ``IW1``, ``EW3``, ``WV1``).
|
|
709
|
+
img_num : str, optional
|
|
710
|
+
Image number, used only for wave mode swaths (``WV1``, ``WV2``). Default is ``None``.
|
|
711
|
+
|
|
712
|
+
Returns
|
|
713
|
+
-------
|
|
714
|
+
pathlib.Path
|
|
715
|
+
Path to the first matching RFI annotation file.
|
|
716
|
+
|
|
717
|
+
Raises
|
|
718
|
+
------
|
|
719
|
+
StopIteration
|
|
720
|
+
If no matching file is found.
|
|
721
|
+
"""
|
|
722
|
+
safe_path = Path(safe_path)
|
|
723
|
+
safe_name = safe_path.name
|
|
724
|
+
prod = product_type_from_safe_name(safe_name)
|
|
725
|
+
if prod == 'SL1':
|
|
726
|
+
prod = 'SLC'
|
|
727
|
+
|
|
728
|
+
if (swath in ['WV1', 'WV2']) and (img_num is not None):
|
|
729
|
+
return next(safe_path.glob('annotation/rfi/rfi*-{}-{}-{}-*-{}.xml'.format(swath.lower(), prod[:3].lower(), pol.lower(), img_num)))
|
|
730
|
+
else:
|
|
731
|
+
return next(safe_path.glob('annotation/rfi/rfi*-{}-{}-{}-*.xml'.format(swath.lower(), prod[:3].lower(), pol.lower())))
|
|
732
|
+
|
|
733
|
+
|
|
734
|
+
def annotation_file_from_safe_path(safe_path, pol, swath, img_num=None):
|
|
735
|
+
"""
|
|
736
|
+
Return the product annotation file of a channel inside a SAFE product.
|
|
737
|
+
|
|
738
|
+
The file is searched in ``annotation`` by swath, product type and polarization. Product type ``SL1`` is
|
|
739
|
+
searched as ``SLC``.
|
|
740
|
+
|
|
741
|
+
Parameters
|
|
742
|
+
----------
|
|
743
|
+
safe_path : str or pathlib.Path
|
|
744
|
+
Path to the SAFE product directory.
|
|
745
|
+
pol : str
|
|
746
|
+
Polarization (e.g. ``VV``, ``VH``).
|
|
747
|
+
swath : str
|
|
748
|
+
Swath identifier (e.g. ``IW1``, ``EW3``, ``WV1``).
|
|
749
|
+
img_num : str, optional
|
|
750
|
+
Image number, used only for wave mode swaths (``WV1``, ``WV2``). Default is ``None``.
|
|
751
|
+
|
|
752
|
+
Returns
|
|
753
|
+
-------
|
|
754
|
+
pathlib.Path
|
|
755
|
+
Path to the first matching product annotation file.
|
|
756
|
+
|
|
757
|
+
Raises
|
|
758
|
+
------
|
|
759
|
+
StopIteration
|
|
760
|
+
If no matching file is found.
|
|
761
|
+
"""
|
|
762
|
+
safe_path = Path(safe_path)
|
|
763
|
+
safe_name = safe_path.name
|
|
764
|
+
prod = product_type_from_safe_name(safe_name)
|
|
765
|
+
if prod == 'SL1':
|
|
766
|
+
prod = 'SLC'
|
|
767
|
+
|
|
768
|
+
if (swath in ['WV1', 'WV2']) and (img_num is not None):
|
|
769
|
+
return next(safe_path.glob('annotation/*-{}-{}-{}*{}.xml'.format(swath.lower(), prod[:3].lower(), pol.lower(), img_num)))
|
|
770
|
+
else:
|
|
771
|
+
return next(safe_path.glob('annotation/*-{}-{}-{}-*.xml'.format(swath.lower(), prod[:3].lower(), pol.lower())))
|
|
772
|
+
|
|
773
|
+
|
|
774
|
+
def string_to_datetime(safe_datetime):
|
|
775
|
+
"""
|
|
776
|
+
Convert a SAFE datetime string to a datetime object.
|
|
777
|
+
|
|
778
|
+
Parameters
|
|
779
|
+
----------
|
|
780
|
+
safe_datetime : str
|
|
781
|
+
Datetime string in the format ``YYYYMMDDTHHMMSS`` (e.g. ``'20211112T230710'``).
|
|
782
|
+
|
|
783
|
+
Returns
|
|
784
|
+
-------
|
|
785
|
+
datetime.datetime
|
|
786
|
+
The corresponding naive datetime.
|
|
787
|
+
"""
|
|
788
|
+
dt = datetime(year=int(safe_datetime[:4]),
|
|
789
|
+
month=int(safe_datetime[4:6]),
|
|
790
|
+
day=int(safe_datetime[6:8]),
|
|
791
|
+
hour=int(safe_datetime[9:11]),
|
|
792
|
+
minute=int(safe_datetime[11:13]),
|
|
793
|
+
second=int(safe_datetime[13:15]))
|
|
794
|
+
return dt
|
|
795
|
+
|
|
796
|
+
|
|
797
|
+
def datetime_to_string(dt):
|
|
798
|
+
"""
|
|
799
|
+
Convert a datetime object to a SAFE datetime string.
|
|
800
|
+
|
|
801
|
+
Parameters
|
|
802
|
+
----------
|
|
803
|
+
dt : datetime.datetime
|
|
804
|
+
Datetime to convert.
|
|
805
|
+
|
|
806
|
+
Returns
|
|
807
|
+
-------
|
|
808
|
+
str
|
|
809
|
+
String ``YYYYMMDDTHHMMSS`` built from the datetime fields.
|
|
810
|
+
|
|
811
|
+
Notes
|
|
812
|
+
-----
|
|
813
|
+
Fields are not zero-padded, so the result matches the SAFE format only when month, day, hour, minute and
|
|
814
|
+
second have two digits.
|
|
815
|
+
"""
|
|
816
|
+
safe_datetime_str = '{}{}{}T{}{}{}'.format(dt.year, dt.month, dt.day, dt.hour, dt.minute, dt.second)
|
|
817
|
+
return safe_datetime_str
|
|
818
|
+
|
|
819
|
+
|
|
820
|
+
def time_from_safe_name(safe_name):
|
|
821
|
+
"""
|
|
822
|
+
Return the sensing start and stop times from a SAFE product name.
|
|
823
|
+
|
|
824
|
+
Parameters
|
|
825
|
+
----------
|
|
826
|
+
safe_name : str
|
|
827
|
+
SAFE product name.
|
|
828
|
+
|
|
829
|
+
Returns
|
|
830
|
+
-------
|
|
831
|
+
time_start : datetime.datetime
|
|
832
|
+
Sensing start time.
|
|
833
|
+
time_stop : datetime.datetime
|
|
834
|
+
Sensing stop time.
|
|
835
|
+
"""
|
|
836
|
+
time_start = string_to_datetime(start_time_str_from_safe_name(safe_name))
|
|
837
|
+
time_stop = string_to_datetime(stop_time_str_from_safe_name(safe_name))
|
|
838
|
+
return time_start, time_stop
|
|
839
|
+
|
|
840
|
+
|
|
841
|
+
def start_time_str_from_orbit_name(orbit_name):
|
|
842
|
+
"""
|
|
843
|
+
Return the validity start time string from an orbit file name.
|
|
844
|
+
|
|
845
|
+
Parameters
|
|
846
|
+
----------
|
|
847
|
+
orbit_name : str
|
|
848
|
+
Orbit file name (e.g. ``S1A_OPER_AUX_POEORB_OPOD_<creation>_V<start>_<stop>.EOF``).
|
|
849
|
+
|
|
850
|
+
Returns
|
|
851
|
+
-------
|
|
852
|
+
str
|
|
853
|
+
Validity start time in the format ``YYYYMMDDTHHMMSS``.
|
|
854
|
+
"""
|
|
855
|
+
return orbit_name[42:57]
|
|
856
|
+
|
|
857
|
+
|
|
858
|
+
def stop_time_str_from_orbit_name(orbit_name):
|
|
859
|
+
"""
|
|
860
|
+
Return the validity stop time string from an orbit file name.
|
|
861
|
+
|
|
862
|
+
Parameters
|
|
863
|
+
----------
|
|
864
|
+
orbit_name : str
|
|
865
|
+
Orbit file name (e.g. ``S1A_OPER_AUX_POEORB_OPOD_<creation>_V<start>_<stop>.EOF``).
|
|
866
|
+
|
|
867
|
+
Returns
|
|
868
|
+
-------
|
|
869
|
+
str
|
|
870
|
+
Validity stop time in the format ``YYYYMMDDTHHMMSS``.
|
|
871
|
+
"""
|
|
872
|
+
return orbit_name[58:73]
|
|
873
|
+
|
|
874
|
+
|
|
875
|
+
def get_time_from_orbit_name(orbit_name):
|
|
876
|
+
"""
|
|
877
|
+
Return the validity start and stop times from an orbit file name.
|
|
878
|
+
|
|
879
|
+
Parameters
|
|
880
|
+
----------
|
|
881
|
+
orbit_name : str
|
|
882
|
+
Orbit file name.
|
|
883
|
+
|
|
884
|
+
Returns
|
|
885
|
+
-------
|
|
886
|
+
time_start : datetime.datetime
|
|
887
|
+
Validity start time.
|
|
888
|
+
time_stop : datetime.datetime
|
|
889
|
+
Validity stop time.
|
|
890
|
+
"""
|
|
891
|
+
time_start = string_to_datetime(start_time_str_from_orbit_name(orbit_name))
|
|
892
|
+
time_stop = string_to_datetime(stop_time_str_from_orbit_name(orbit_name))
|
|
893
|
+
return time_start, time_stop
|
|
894
|
+
|
|
895
|
+
|
|
896
|
+
class S1Safe:
|
|
897
|
+
"""
|
|
898
|
+
Sentinel-1 SAFE product reader.
|
|
899
|
+
|
|
900
|
+
Parses the manifest and builds one ``S1Channel`` per polarization and swath (or per wave mode image) from the
|
|
901
|
+
annotation, noise, calibration, RFI and measurement files of the product.
|
|
902
|
+
|
|
903
|
+
Parameters
|
|
904
|
+
----------
|
|
905
|
+
safe_path : str or pathlib.Path
|
|
906
|
+
Path to the SAFE product directory.
|
|
907
|
+
img_num : str, optional
|
|
908
|
+
Unused. Default is ``None``.
|
|
909
|
+
|
|
910
|
+
Attributes
|
|
911
|
+
----------
|
|
912
|
+
path : pathlib.Path
|
|
913
|
+
Path to the SAFE product directory.
|
|
914
|
+
name : str
|
|
915
|
+
SAFE product name.
|
|
916
|
+
manifest : xml.etree.ElementTree.ElementTree
|
|
917
|
+
Parsed ``manifest.safe``.
|
|
918
|
+
mode : str
|
|
919
|
+
Acquisition mode (e.g. ``IW``, ``EW``).
|
|
920
|
+
product_type : str
|
|
921
|
+
Product type (e.g. ``SLC``, ``GRDH``).
|
|
922
|
+
ads_header : list
|
|
923
|
+
Placeholder for the ADS header, currently always empty.
|
|
924
|
+
|
|
925
|
+
Raises
|
|
926
|
+
------
|
|
927
|
+
ValueError
|
|
928
|
+
If ``safe_path`` does not exist.
|
|
929
|
+
StopIteration
|
|
930
|
+
If an annotation, noise, calibration or RFI file of a channel is missing.
|
|
931
|
+
"""
|
|
932
|
+
def __init__(self, safe_path, img_num=None):
|
|
933
|
+
self.path = Path(safe_path)
|
|
934
|
+
if not self.path.exists():
|
|
935
|
+
raise ValueError('Path does not exist: {}'.format(safe_path))
|
|
936
|
+
self.name = self.path.name
|
|
937
|
+
self.manifest = ET.parse(self.path / 'manifest.safe')
|
|
938
|
+
self.mode = mode_from_safe_name(self.name)
|
|
939
|
+
self.product_type = product_type_from_safe_name(self.name)
|
|
940
|
+
self._channels = self._build_channels_dict()
|
|
941
|
+
self.ads_header = self.get_ads_header()
|
|
942
|
+
|
|
943
|
+
def _build_channels_dict(self):
|
|
944
|
+
"""
|
|
945
|
+
Build the channels of the product.
|
|
946
|
+
|
|
947
|
+
GRD products have a single swath named after the mode. Wave mode channels are discovered from the measurement
|
|
948
|
+
files. A missing measurement file (annotation-only product) results in a channel without measurement.
|
|
949
|
+
|
|
950
|
+
Returns
|
|
951
|
+
-------
|
|
952
|
+
dict
|
|
953
|
+
Mapping from ``(pol, swath_id)``, or ``(pol, swath_id, img_num)`` for wave mode, to ``S1Channel``.
|
|
954
|
+
"""
|
|
955
|
+
channels = dict()
|
|
956
|
+
pol_list = polarizations_from_safe_name(self.name)
|
|
957
|
+
if self.is_grd():
|
|
958
|
+
swath_id_list = [self.mode]
|
|
959
|
+
else:
|
|
960
|
+
swath_id_list = subswaths_from_mode(self.mode)
|
|
961
|
+
|
|
962
|
+
|
|
963
|
+
if self.mode == 'WV':
|
|
964
|
+
for filename in os.listdir(os.path.join(self.path, 'measurement')):
|
|
965
|
+
swath_id = filename.split('-')[1].upper()
|
|
966
|
+
pol = filename.split('-')[3].upper()
|
|
967
|
+
img_num = (filename.split('.')[0]).split('-')[8]
|
|
968
|
+
|
|
969
|
+
annot_file = annotation_file_from_safe_path(self.path, pol, swath_id, img_num)
|
|
970
|
+
noise_file = noise_file_from_safe_path(self.path, pol, swath_id, img_num)
|
|
971
|
+
cal_file = calibration_file_from_safe_path(self.path, pol, swath_id, img_num)
|
|
972
|
+
rfi_file = rfi_file_from_safe_path(self.path, pol, swath_id, img_num)
|
|
973
|
+
|
|
974
|
+
try:
|
|
975
|
+
meas_file = measurement_file_from_safe_path(self.path, pol, swath_id, img_num)
|
|
976
|
+
except StopIteration:
|
|
977
|
+
#In case of L1 Annotation product
|
|
978
|
+
meas_file = None
|
|
979
|
+
channels[(pol, swath_id, img_num)] = S1Channel(pol, swath_id, annot_file, noise_file, cal_file, rfi_file, meas_file)
|
|
980
|
+
|
|
981
|
+
else:
|
|
982
|
+
for pol in pol_list:
|
|
983
|
+
for swath_id in swath_id_list:
|
|
984
|
+
annot_file = annotation_file_from_safe_path(self.path, pol, swath_id)
|
|
985
|
+
noise_file = noise_file_from_safe_path(self.path, pol, swath_id)
|
|
986
|
+
cal_file = calibration_file_from_safe_path(self.path, pol, swath_id)
|
|
987
|
+
rfi_file = rfi_file_from_safe_path(self.path, pol, swath_id)
|
|
988
|
+
try:
|
|
989
|
+
meas_file = measurement_file_from_safe_path(self.path, pol, swath_id)
|
|
990
|
+
except StopIteration:
|
|
991
|
+
#In case of L1 Annotation product
|
|
992
|
+
meas_file = None
|
|
993
|
+
channels[ch_idx(pol, swath_id)] = S1Channel(pol, swath_id, annot_file, noise_file, cal_file, rfi_file, meas_file)
|
|
994
|
+
|
|
995
|
+
return channels
|
|
996
|
+
|
|
997
|
+
def get_ads_header(self):
|
|
998
|
+
"""
|
|
999
|
+
Return the ADS header of the product.
|
|
1000
|
+
|
|
1001
|
+
Returns
|
|
1002
|
+
-------
|
|
1003
|
+
list
|
|
1004
|
+
Always an empty list (placeholder).
|
|
1005
|
+
"""
|
|
1006
|
+
ads_header = []
|
|
1007
|
+
return ads_header
|
|
1008
|
+
|
|
1009
|
+
def is_grd(self):
|
|
1010
|
+
"""
|
|
1011
|
+
Check whether the product is a GRD product.
|
|
1012
|
+
|
|
1013
|
+
Returns
|
|
1014
|
+
-------
|
|
1015
|
+
bool
|
|
1016
|
+
``True`` if the product is GRD.
|
|
1017
|
+
"""
|
|
1018
|
+
return is_grd(self.name)
|
|
1019
|
+
|
|
1020
|
+
def is_topsar(self):
|
|
1021
|
+
"""
|
|
1022
|
+
Check whether the product was acquired in a TOPSAR mode (IW or EW).
|
|
1023
|
+
|
|
1024
|
+
Returns
|
|
1025
|
+
-------
|
|
1026
|
+
bool
|
|
1027
|
+
``True`` if the product is IW or EW.
|
|
1028
|
+
"""
|
|
1029
|
+
return is_topsar(self.name)
|
|
1030
|
+
|
|
1031
|
+
def is_stripmap(self):
|
|
1032
|
+
"""
|
|
1033
|
+
Check whether the product was acquired in a stripmap mode.
|
|
1034
|
+
|
|
1035
|
+
Returns
|
|
1036
|
+
-------
|
|
1037
|
+
bool
|
|
1038
|
+
``True`` if the product is a stripmap product.
|
|
1039
|
+
"""
|
|
1040
|
+
return is_stripmap(self.name)
|
|
1041
|
+
|
|
1042
|
+
def is_wave(self):
|
|
1043
|
+
"""
|
|
1044
|
+
Check whether the product was acquired in wave mode.
|
|
1045
|
+
|
|
1046
|
+
Returns
|
|
1047
|
+
-------
|
|
1048
|
+
bool or None
|
|
1049
|
+
``True`` if the mode is ``WV``, ``None`` otherwise.
|
|
1050
|
+
"""
|
|
1051
|
+
return is_wave(self.name)
|
|
1052
|
+
|
|
1053
|
+
def channel(self, pol, swath_id, img_num=None):
|
|
1054
|
+
"""
|
|
1055
|
+
Return the channel of a given polarization and swath.
|
|
1056
|
+
|
|
1057
|
+
Parameters
|
|
1058
|
+
----------
|
|
1059
|
+
pol : str
|
|
1060
|
+
Polarization (e.g. ``VV``).
|
|
1061
|
+
swath_id : str
|
|
1062
|
+
Swath identifier (e.g. ``IW1``).
|
|
1063
|
+
img_num : str, optional
|
|
1064
|
+
Image number, used only for wave mode products. Default is ``None``.
|
|
1065
|
+
|
|
1066
|
+
Returns
|
|
1067
|
+
-------
|
|
1068
|
+
S1Channel
|
|
1069
|
+
The requested channel.
|
|
1070
|
+
|
|
1071
|
+
Raises
|
|
1072
|
+
------
|
|
1073
|
+
KeyError
|
|
1074
|
+
If the channel does not exist in the product.
|
|
1075
|
+
"""
|
|
1076
|
+
|
|
1077
|
+
if self.mode == 'WV' and (img_num is not None):
|
|
1078
|
+
return self._channels[(pol, swath_id, img_num)]
|
|
1079
|
+
else:
|
|
1080
|
+
return self._channels[ch_idx(pol, swath_id)]
|
|
1081
|
+
|
|
1082
|
+
def polarization_list(self):
|
|
1083
|
+
"""
|
|
1084
|
+
Return the polarizations available in the product.
|
|
1085
|
+
|
|
1086
|
+
Returns
|
|
1087
|
+
-------
|
|
1088
|
+
list of str
|
|
1089
|
+
Unique polarizations, in channel order.
|
|
1090
|
+
"""
|
|
1091
|
+
pol_list = [k[0] for k in self._channels.keys()]
|
|
1092
|
+
return list(dict.fromkeys(pol_list))
|
|
1093
|
+
|
|
1094
|
+
def swath_id_list(self):
|
|
1095
|
+
"""
|
|
1096
|
+
Return the swath identifiers available in the product.
|
|
1097
|
+
|
|
1098
|
+
Returns
|
|
1099
|
+
-------
|
|
1100
|
+
list of str
|
|
1101
|
+
Unique swath identifiers, in channel order.
|
|
1102
|
+
"""
|
|
1103
|
+
pol_list = [k[1] for k in self._channels.keys()]
|
|
1104
|
+
return list(dict.fromkeys(pol_list))
|
|
1105
|
+
|
|
1106
|
+
def read_quicklook(self):
|
|
1107
|
+
"""
|
|
1108
|
+
Read the quick-look image of the product.
|
|
1109
|
+
|
|
1110
|
+
Returns
|
|
1111
|
+
-------
|
|
1112
|
+
numpy.ndarray or None
|
|
1113
|
+
Content of ``preview/quick-look.png``, or ``None`` if the file does not exist.
|
|
1114
|
+
"""
|
|
1115
|
+
ql_path = self.path / 'preview/quick-look.png'
|
|
1116
|
+
if not ql_path.exists():
|
|
1117
|
+
return None
|
|
1118
|
+
ql_image = Image.open(ql_path)
|
|
1119
|
+
return np.asarray(ql_image)
|
|
1120
|
+
|
|
1121
|
+
class S1Channel:
|
|
1122
|
+
"""
|
|
1123
|
+
Single channel (polarization and swath) of a Sentinel-1 SAFE product.
|
|
1124
|
+
|
|
1125
|
+
Parameters
|
|
1126
|
+
----------
|
|
1127
|
+
pol : str
|
|
1128
|
+
Polarization (e.g. ``VV``).
|
|
1129
|
+
swath_id : str
|
|
1130
|
+
Swath identifier (e.g. ``IW1``).
|
|
1131
|
+
annnot_file : str or pathlib.Path
|
|
1132
|
+
Product annotation XML file.
|
|
1133
|
+
noise_file : str or pathlib.Path
|
|
1134
|
+
Noise annotation XML file.
|
|
1135
|
+
calibration_file : str or pathlib.Path
|
|
1136
|
+
Calibration annotation XML file.
|
|
1137
|
+
rfi_file : str or pathlib.Path
|
|
1138
|
+
RFI annotation XML file.
|
|
1139
|
+
meas_file : str or pathlib.Path, optional
|
|
1140
|
+
Measurement TIFF file. Default is ``None`` (annotation-only product).
|
|
1141
|
+
|
|
1142
|
+
Attributes
|
|
1143
|
+
----------
|
|
1144
|
+
pol : str
|
|
1145
|
+
Polarization.
|
|
1146
|
+
swath_id : str
|
|
1147
|
+
Swath identifier.
|
|
1148
|
+
annotation : xml.etree.ElementTree.ElementTree
|
|
1149
|
+
Parsed product annotation.
|
|
1150
|
+
noise : xml.etree.ElementTree.ElementTree
|
|
1151
|
+
Parsed noise annotation.
|
|
1152
|
+
calibration : xml.etree.ElementTree.ElementTree
|
|
1153
|
+
Parsed calibration annotation.
|
|
1154
|
+
rfi : xml.etree.ElementTree.ElementTree
|
|
1155
|
+
Parsed RFI annotation.
|
|
1156
|
+
measurement : pathlib.Path or None
|
|
1157
|
+
Measurement TIFF file, or ``None`` if not available.
|
|
1158
|
+
"""
|
|
1159
|
+
def __init__(self, pol, swath_id, annnot_file, noise_file, calibration_file, rfi_file, meas_file=None):
|
|
1160
|
+
self.swath_id = swath_id
|
|
1161
|
+
self.pol = pol
|
|
1162
|
+
self.annotation = ET.parse(annnot_file)
|
|
1163
|
+
self.noise = ET.parse(noise_file)
|
|
1164
|
+
self.calibration = ET.parse(calibration_file)
|
|
1165
|
+
self.rfi = ET.parse(rfi_file)
|
|
1166
|
+
if meas_file is not None:
|
|
1167
|
+
meas_file = Path(meas_file)
|
|
1168
|
+
if not meas_file.exists():
|
|
1169
|
+
ValueError('File does not exist: {}'.format(meas_file))
|
|
1170
|
+
self.measurement = meas_file
|
|
1171
|
+
else:
|
|
1172
|
+
self.measurement = None
|
|
1173
|
+
|
|
1174
|
+
def size(self):
|
|
1175
|
+
"""
|
|
1176
|
+
Return the image size from the product annotation.
|
|
1177
|
+
|
|
1178
|
+
Returns
|
|
1179
|
+
-------
|
|
1180
|
+
num_azi : int
|
|
1181
|
+
Number of azimuth lines.
|
|
1182
|
+
num_rng : int
|
|
1183
|
+
Number of range samples.
|
|
1184
|
+
"""
|
|
1185
|
+
num_rng = int(self.annotation.find('imageAnnotation/imageInformation/numberOfSamples').text)
|
|
1186
|
+
num_azi = int(self.annotation.find('imageAnnotation/imageInformation/numberOfLines').text)
|
|
1187
|
+
return num_azi, num_rng
|
|
1188
|
+
|
|
1189
|
+
@property
|
|
1190
|
+
def num_bursts(self):
|
|
1191
|
+
"""Number of bursts listed in ``swathTiming/burstList`` of the product annotation."""
|
|
1192
|
+
return len(self.annotation.find('swathTiming/burstList'))
|
|
1193
|
+
|
|
1194
|
+
def roi_from_burst(self, burst_idx):
|
|
1195
|
+
"""
|
|
1196
|
+
Return the region of interest covering a burst.
|
|
1197
|
+
|
|
1198
|
+
Parameters
|
|
1199
|
+
----------
|
|
1200
|
+
burst_idx : int
|
|
1201
|
+
Zero-based burst index.
|
|
1202
|
+
|
|
1203
|
+
Returns
|
|
1204
|
+
-------
|
|
1205
|
+
S1Roi
|
|
1206
|
+
ROI spanning ``linesPerBurst`` lines from ``burst_idx * linesPerBurst`` and ``samplesPerBurst`` samples
|
|
1207
|
+
from range sample 0.
|
|
1208
|
+
|
|
1209
|
+
Raises
|
|
1210
|
+
------
|
|
1211
|
+
ValueError
|
|
1212
|
+
If ``burst_idx`` is not lower than the number of bursts.
|
|
1213
|
+
"""
|
|
1214
|
+
num_bursts = self.num_bursts
|
|
1215
|
+
if burst_idx >= num_bursts:
|
|
1216
|
+
raise ValueError('Invalid burst index: {}. Number of bursts is {}'.format(burst_idx, num_bursts))
|
|
1217
|
+
lines_per_burst = int(self.annotation.find('swathTiming/linesPerBurst').text)
|
|
1218
|
+
samples_per_burst = int(self.annotation.find('swathTiming/samplesPerBurst').text)
|
|
1219
|
+
return S1Roi(range_start_idx=0,
|
|
1220
|
+
azi_start_idx=burst_idx * lines_per_burst,
|
|
1221
|
+
num_range_samples=samples_per_burst,
|
|
1222
|
+
num_azi_lines=lines_per_burst)
|
|
1223
|
+
|
|
1224
|
+
def read_swath_bounds(self):
|
|
1225
|
+
"""
|
|
1226
|
+
Read the swath merging bounds from the product annotation (GRD products).
|
|
1227
|
+
|
|
1228
|
+
Returns
|
|
1229
|
+
-------
|
|
1230
|
+
dict of str to list of dict
|
|
1231
|
+
Mapping from swath identifier to its list of bounds, each with keys ``firstAzimuthLine``,
|
|
1232
|
+
``lastAzimuthLine``, ``firstRangeSample`` and ``lastRangeSample`` (int).
|
|
1233
|
+
"""
|
|
1234
|
+
swath_merge_dict = dict()
|
|
1235
|
+
for swathMerge in self.annotation.findall('swathMerging/swathMergeList/swathMerge'):
|
|
1236
|
+
swath_id = swathMerge.find('swath').text
|
|
1237
|
+
swath_merge_dict[swath_id] = list()
|
|
1238
|
+
for swathBound in swathMerge.findall('swathBoundsList/swathBounds'):
|
|
1239
|
+
swath_bounds_dict = dict()
|
|
1240
|
+
swath_bounds_dict['firstAzimuthLine'] = int(swathBound.findall('firstAzimuthLine')[0].text)
|
|
1241
|
+
swath_bounds_dict['lastAzimuthLine'] = int(swathBound.findall('lastAzimuthLine')[0].text)
|
|
1242
|
+
swath_bounds_dict['firstRangeSample'] = int(swathBound.findall('firstRangeSample')[0].text)
|
|
1243
|
+
swath_bounds_dict['lastRangeSample'] = int(swathBound.findall('lastRangeSample')[0].text)
|
|
1244
|
+
swath_merge_dict[swath_id].append(swath_bounds_dict)
|
|
1245
|
+
return swath_merge_dict
|
|
1246
|
+
|
|
1247
|
+
def read_range_noise_vectors(self):
|
|
1248
|
+
"""
|
|
1249
|
+
Read the range noise vectors of the channel.
|
|
1250
|
+
|
|
1251
|
+
Returns
|
|
1252
|
+
-------
|
|
1253
|
+
list of dict
|
|
1254
|
+
Range noise vectors, see ``read_range_noise_vectors``.
|
|
1255
|
+
"""
|
|
1256
|
+
return read_range_noise_vectors(self.noise)
|
|
1257
|
+
|
|
1258
|
+
def read_azimuth_noise_vectors(self):
|
|
1259
|
+
"""
|
|
1260
|
+
Read the azimuth noise vectors of the channel, grouped by swath.
|
|
1261
|
+
|
|
1262
|
+
Returns
|
|
1263
|
+
-------
|
|
1264
|
+
dict of str to list of dict
|
|
1265
|
+
Azimuth noise vectors, see ``read_azimuth_noise_vectors``.
|
|
1266
|
+
"""
|
|
1267
|
+
return read_azimuth_noise_vectors(self.noise)
|
|
1268
|
+
|
|
1269
|
+
def read_calibration_vectors(self, calibration):
|
|
1270
|
+
"""
|
|
1271
|
+
Read the calibration vectors of a given type for the channel.
|
|
1272
|
+
|
|
1273
|
+
Parameters
|
|
1274
|
+
----------
|
|
1275
|
+
calibration : str
|
|
1276
|
+
Calibration type, one of ``dn``, ``betaNought``, ``gamma``, ``sigmaNought``.
|
|
1277
|
+
|
|
1278
|
+
Returns
|
|
1279
|
+
-------
|
|
1280
|
+
list of dict
|
|
1281
|
+
Calibration vectors, see ``read_calibration_vectors``.
|
|
1282
|
+
"""
|
|
1283
|
+
return read_calibration_vectors(self.calibration, calibration)
|
|
1284
|
+
|
|
1285
|
+
def calibrate(self, data, roi, calibration=None):
|
|
1286
|
+
"""
|
|
1287
|
+
Apply radiometric calibration to channel data over a region of interest.
|
|
1288
|
+
|
|
1289
|
+
Parameters
|
|
1290
|
+
----------
|
|
1291
|
+
data : numpy.ndarray
|
|
1292
|
+
Data over the ROI, with shape ``(roi.num_azi_lines, roi.num_range_samples)``.
|
|
1293
|
+
roi : S1Roi
|
|
1294
|
+
Region of interest covered by ``data``.
|
|
1295
|
+
calibration : str, optional
|
|
1296
|
+
Calibration type, one of ``dn``, ``betaNought``, ``gamma``, ``sigmaNought``. Default is ``None`` (no
|
|
1297
|
+
calibration).
|
|
1298
|
+
|
|
1299
|
+
Returns
|
|
1300
|
+
-------
|
|
1301
|
+
numpy.ndarray
|
|
1302
|
+
Calibrated data in linear power units, or ``data`` unchanged if ``calibration`` is ``None``.
|
|
1303
|
+
"""
|
|
1304
|
+
return calibrate(self.annotation, self.calibration, data, roi, calibration)
|
|
1305
|
+
|
|
1306
|
+
def read_data(self, roi=None, calibration=None):
|
|
1307
|
+
"""
|
|
1308
|
+
Read measurement data over a region of interest, optionally calibrated.
|
|
1309
|
+
|
|
1310
|
+
Parameters
|
|
1311
|
+
----------
|
|
1312
|
+
roi : S1Roi, optional
|
|
1313
|
+
Region of interest. Default is ``None`` (whole image).
|
|
1314
|
+
calibration : str, optional
|
|
1315
|
+
Calibration type, one of ``dn``, ``betaNought``, ``gamma``, ``sigmaNought``. Default is ``None`` (no
|
|
1316
|
+
calibration).
|
|
1317
|
+
|
|
1318
|
+
Returns
|
|
1319
|
+
-------
|
|
1320
|
+
numpy.ndarray
|
|
1321
|
+
Data of the first raster band over the ROI (integer data are converted to float), or calibrated power if
|
|
1322
|
+
``calibration`` is given.
|
|
1323
|
+
"""
|
|
1324
|
+
with rasterio.open(self.measurement) as dataset:
|
|
1325
|
+
if roi is None:
|
|
1326
|
+
roi = S1Roi(0, 0, dataset.width, dataset.height)
|
|
1327
|
+
data = dataset.read(1, window=roi.as_window())
|
|
1328
|
+
if issubclass(data.dtype.type, np.integer):
|
|
1329
|
+
data = data.astype(float)
|
|
1330
|
+
return self.calibrate(data, roi, calibration)
|
|
1331
|
+
|
|
1332
|
+
def read_noise(self, roi, calibration=None):
|
|
1333
|
+
"""
|
|
1334
|
+
Compute the annotated noise over a region of interest, optionally calibrated.
|
|
1335
|
+
|
|
1336
|
+
Parameters
|
|
1337
|
+
----------
|
|
1338
|
+
roi : S1Roi or None
|
|
1339
|
+
Region of interest; ``None`` means the whole image.
|
|
1340
|
+
calibration : str, optional
|
|
1341
|
+
Calibration type, one of ``dn``, ``betaNought``, ``gamma``, ``sigmaNought``. Default is ``None`` (no
|
|
1342
|
+
calibration).
|
|
1343
|
+
|
|
1344
|
+
Returns
|
|
1345
|
+
-------
|
|
1346
|
+
numpy.ndarray
|
|
1347
|
+
Noise amplitude map (see ``compute_noise_map``), or calibrated noise power if ``calibration`` is given.
|
|
1348
|
+
"""
|
|
1349
|
+
data_size = self.size()
|
|
1350
|
+
if roi is None:
|
|
1351
|
+
roi = S1Roi(0, 0, data_size[1], data_size[0])
|
|
1352
|
+
noise = compute_noise_map(self.noise, data_size, roi=roi.as_tuple())
|
|
1353
|
+
return self.calibrate(noise, roi, calibration)
|
|
1354
|
+
|
|
1355
|
+
class S1Roi:
|
|
1356
|
+
"""
|
|
1357
|
+
Rectangular region of interest in image coordinates.
|
|
1358
|
+
|
|
1359
|
+
Parameters
|
|
1360
|
+
----------
|
|
1361
|
+
range_start_idx : int
|
|
1362
|
+
First range sample.
|
|
1363
|
+
azi_start_idx : int
|
|
1364
|
+
First azimuth line.
|
|
1365
|
+
num_range_samples : int
|
|
1366
|
+
Number of range samples.
|
|
1367
|
+
num_azi_lines : int
|
|
1368
|
+
Number of azimuth lines.
|
|
1369
|
+
|
|
1370
|
+
Attributes
|
|
1371
|
+
----------
|
|
1372
|
+
range_start_idx : int
|
|
1373
|
+
First range sample.
|
|
1374
|
+
azi_start_idx : int
|
|
1375
|
+
First azimuth line.
|
|
1376
|
+
num_range_samples : int
|
|
1377
|
+
Number of range samples.
|
|
1378
|
+
num_azi_lines : int
|
|
1379
|
+
Number of azimuth lines.
|
|
1380
|
+
"""
|
|
1381
|
+
def __init__(self, range_start_idx, azi_start_idx, num_range_samples, num_azi_lines):
|
|
1382
|
+
self.range_start_idx = range_start_idx
|
|
1383
|
+
self.azi_start_idx = azi_start_idx
|
|
1384
|
+
self.num_range_samples = num_range_samples
|
|
1385
|
+
self.num_azi_lines = num_azi_lines
|
|
1386
|
+
|
|
1387
|
+
def __repr__(self):
|
|
1388
|
+
"""Return a string listing the ROI fields."""
|
|
1389
|
+
return 'range_start_idx:{} azi_start_idx:{} num_range_samples:{} num_azi_lines:{}'.format(self.range_start_idx,
|
|
1390
|
+
self.azi_start_idx,
|
|
1391
|
+
self.num_range_samples,
|
|
1392
|
+
self.num_azi_lines)
|
|
1393
|
+
|
|
1394
|
+
def as_tuple(self):
|
|
1395
|
+
"""
|
|
1396
|
+
Return the ROI as a tuple.
|
|
1397
|
+
|
|
1398
|
+
Returns
|
|
1399
|
+
-------
|
|
1400
|
+
tuple of int
|
|
1401
|
+
``(range_start_idx, azi_start_idx, num_range_samples, num_azi_lines)``.
|
|
1402
|
+
"""
|
|
1403
|
+
return self.range_start_idx, self.azi_start_idx, self.num_range_samples, self.num_azi_lines
|
|
1404
|
+
|
|
1405
|
+
def as_window(self):
|
|
1406
|
+
"""
|
|
1407
|
+
Return the ROI as a rasterio window.
|
|
1408
|
+
|
|
1409
|
+
Returns
|
|
1410
|
+
-------
|
|
1411
|
+
rasterio.windows.Window
|
|
1412
|
+
Window with column offset, row offset, width and height taken from ``as_tuple``.
|
|
1413
|
+
"""
|
|
1414
|
+
return Window(*self.as_tuple())
|
|
1415
|
+
|