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/xml_reader.py
ADDED
|
@@ -0,0 +1,571 @@
|
|
|
1
|
+
# SPDX-FileCopyrightText: Aresys S.r.l. <info@aresys.it>
|
|
2
|
+
# SPDX-License-Identifier: MIT
|
|
3
|
+
|
|
4
|
+
"""
|
|
5
|
+
Parse and extract geophysical parameters from Sentinel-1 L1 annotation XML files.
|
|
6
|
+
|
|
7
|
+
Provides readers for noise power, antenna patterns (elevation pattern, incidence angles), PG correction, azimuth FM
|
|
8
|
+
rate and orbit state vectors, as well as helpers to build the slant range time axis and the orbit trajectory, and to
|
|
9
|
+
convert the azimuth antenna steering rate into a Doppler rate.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
import numpy as np
|
|
13
|
+
import xml.etree.ElementTree as ET
|
|
14
|
+
import xmltodict
|
|
15
|
+
from scipy.constants import speed_of_light
|
|
16
|
+
from perseo_core.timing import PreciseDateTime
|
|
17
|
+
from perseo_core.geometry.navigation import CubicSplineTrajectory
|
|
18
|
+
|
|
19
|
+
from s1_denoise.utils import ground2slant, ensure_list, get_range_boundaries
|
|
20
|
+
from s1_denoise.logger import denoise_logger
|
|
21
|
+
|
|
22
|
+
def read_metadata_from_safe_annotation(annotation):
|
|
23
|
+
"""
|
|
24
|
+
Convert a parsed L1 annotation XML tree into a nested dictionary.
|
|
25
|
+
|
|
26
|
+
Parameters
|
|
27
|
+
----------
|
|
28
|
+
annotation : xml.etree.ElementTree.ElementTree
|
|
29
|
+
Parsed annotation XML of one channel.
|
|
30
|
+
|
|
31
|
+
Returns
|
|
32
|
+
-------
|
|
33
|
+
dict
|
|
34
|
+
Nested dictionary produced by ``xmltodict``, rooted at the ``product`` key.
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
xml_data = annotation.getroot()
|
|
38
|
+
xmlstr = ET.tostring(xml_data, encoding='utf-8', method='xml')
|
|
39
|
+
|
|
40
|
+
return dict(xmltodict.parse(xmlstr))
|
|
41
|
+
|
|
42
|
+
def create_slant_range_times(metadata_dict, subswath = None, az_t0 = None):
|
|
43
|
+
"""
|
|
44
|
+
Create the two-way slant range time axis of the image range samples.
|
|
45
|
+
|
|
46
|
+
Parameters
|
|
47
|
+
----------
|
|
48
|
+
metadata_dict : dict
|
|
49
|
+
Annotation dictionary, as returned by `read_metadata_from_safe_annotation`.
|
|
50
|
+
subswath : str, optional
|
|
51
|
+
Subswath identifier (e.g. ``"IW1"``), to be given for GRD products only. Default is None (SLC case).
|
|
52
|
+
az_t0 : PreciseDateTime, optional
|
|
53
|
+
Azimuth time used to select the swath bounds and the ground-to-slant range polynomial. Required when
|
|
54
|
+
`subswath` is given. Default is None.
|
|
55
|
+
|
|
56
|
+
Returns
|
|
57
|
+
-------
|
|
58
|
+
numpy.ndarray
|
|
59
|
+
Two-way slant range times [s], one per range sample.
|
|
60
|
+
|
|
61
|
+
Notes
|
|
62
|
+
-----
|
|
63
|
+
In the SLC case, ``numberOfSamples`` times are generated starting from ``slantRangeTime`` with a step of
|
|
64
|
+
``2 * rangePixelSpacing / c``. In the GRD case, the ground range pixels between the first and last range samples of
|
|
65
|
+
the subswath (from the swath merging bounds valid at `az_t0`) are converted to ground range distances using
|
|
66
|
+
``rangePixelSpacing``, then to slant range with the GRSR polynomial closest in azimuth to `az_t0`, and finally to
|
|
67
|
+
two-way slant range times.
|
|
68
|
+
"""
|
|
69
|
+
|
|
70
|
+
delta_t = 2 * (float(metadata_dict['product']['imageAnnotation']['imageInformation']['rangePixelSpacing'])) / speed_of_light
|
|
71
|
+
start_sr = float(metadata_dict['product']['imageAnnotation']['imageInformation']['slantRangeTime'])
|
|
72
|
+
samples_num = int(metadata_dict['product']['imageAnnotation']['imageInformation']['numberOfSamples'])
|
|
73
|
+
|
|
74
|
+
# SLC case
|
|
75
|
+
if subswath is None:
|
|
76
|
+
return np.arange(samples_num) * delta_t + start_sr # [s]
|
|
77
|
+
# GRD case
|
|
78
|
+
else:
|
|
79
|
+
swath_merges = ensure_list(metadata_dict['product']['swathMerging']['swathMergeList']['swathMerge'])
|
|
80
|
+
cc = metadata_dict['product']['coordinateConversion']['coordinateConversionList']['coordinateConversion']
|
|
81
|
+
|
|
82
|
+
start_sample, end_sample = get_range_boundaries(swath_merges, subswath, az_t0)
|
|
83
|
+
|
|
84
|
+
# GRD pixel indices for the subswath
|
|
85
|
+
indices = np.arange(start_sample, end_sample + 1)
|
|
86
|
+
|
|
87
|
+
# ground range times [s]
|
|
88
|
+
t_grd = indices * delta_t
|
|
89
|
+
|
|
90
|
+
# ground range [m]
|
|
91
|
+
gr_m = t_grd * speed_of_light / 2
|
|
92
|
+
|
|
93
|
+
# slant range [m]
|
|
94
|
+
sr_m = ground2slant(start_az_time = az_t0.isoformat(sep="T", timespec="microseconds").rstrip("Z"),
|
|
95
|
+
gr_array = gr_m,
|
|
96
|
+
coordinate_conversions = cc)
|
|
97
|
+
|
|
98
|
+
# slant range times [s]
|
|
99
|
+
t_slant = sr_m * 2 / speed_of_light
|
|
100
|
+
|
|
101
|
+
return t_slant
|
|
102
|
+
|
|
103
|
+
def read_pg_correction_list(annotation, swath_id):
|
|
104
|
+
"""
|
|
105
|
+
Read the PG product amplitudes annotated in the replica information of a swath.
|
|
106
|
+
|
|
107
|
+
Parameters
|
|
108
|
+
----------
|
|
109
|
+
annotation : xml.etree.ElementTree.ElementTree
|
|
110
|
+
Parsed annotation XML of one channel.
|
|
111
|
+
swath_id : str
|
|
112
|
+
Swath identifier (e.g. ``"IW1"``).
|
|
113
|
+
|
|
114
|
+
Returns
|
|
115
|
+
-------
|
|
116
|
+
pg_correction : numpy.ndarray
|
|
117
|
+
``pgProductAmplitude`` values, one per replica record.
|
|
118
|
+
az_t : numpy.ndarray
|
|
119
|
+
Azimuth times of the replica records, as PreciseDateTime objects.
|
|
120
|
+
"""
|
|
121
|
+
|
|
122
|
+
pg_correction = []
|
|
123
|
+
az_t = []
|
|
124
|
+
|
|
125
|
+
data = annotation.findall('generalAnnotation/replicaInformationList/replicaInformation/swath')
|
|
126
|
+
for ind, element in enumerate(data):
|
|
127
|
+
if element.text == swath_id:
|
|
128
|
+
|
|
129
|
+
pg_correction_list = annotation.findall('generalAnnotation/replicaInformationList/replicaInformation[{}]/replicaList/replica/pgProductAmplitude'.format(ind+1))
|
|
130
|
+
az_t_list = annotation.findall('generalAnnotation/replicaInformationList/replicaInformation[{}]/replicaList/replica/azimuthTime'.format(ind+1))
|
|
131
|
+
|
|
132
|
+
for t_ind, t in enumerate(az_t_list):
|
|
133
|
+
pg_correction.append(float(pg_correction_list[t_ind].text))
|
|
134
|
+
az_t.append(t.text)
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
return np.array(pg_correction), np.array(convert_az_times(az_t))
|
|
138
|
+
|
|
139
|
+
def read_noise_power_correction_from_annotation(metadata_dict, swath_id):
|
|
140
|
+
"""
|
|
141
|
+
Read the noise power annotated in the L1 product for a swath, discarding biased rank echo measurements.
|
|
142
|
+
|
|
143
|
+
Parameters
|
|
144
|
+
----------
|
|
145
|
+
metadata_dict : dict
|
|
146
|
+
Annotation dictionary, as returned by `read_metadata_from_safe_annotation`.
|
|
147
|
+
swath_id : str
|
|
148
|
+
Swath identifier (e.g. ``"IW1"``).
|
|
149
|
+
|
|
150
|
+
Returns
|
|
151
|
+
-------
|
|
152
|
+
final_noise_pwr : numpy.ndarray
|
|
153
|
+
Retained ``noisePowerCorrectionFactor`` values (linear scale).
|
|
154
|
+
final_times : numpy.ndarray
|
|
155
|
+
Azimuth times of the retained values, as PreciseDateTime objects.
|
|
156
|
+
|
|
157
|
+
Notes
|
|
158
|
+
-----
|
|
159
|
+
The same mission-specific selection applied to L0N rank echoes (see
|
|
160
|
+
`s1_denoise.l0_noise_power_calculation.filter_rank_echoes`) is used. For S1A/B: IW1 keeps the first value, the
|
|
161
|
+
odd-indexed values and the last value; IW2/IW3 keep the even-indexed values and the last value; EW1 drops the last
|
|
162
|
+
value. For the other missions, the last EW2 value is dropped. All other swaths are kept unchanged.
|
|
163
|
+
"""
|
|
164
|
+
|
|
165
|
+
data = metadata_dict['product']['generalAnnotation']['noiseList']['noise']
|
|
166
|
+
mission_id = metadata_dict['product']['adsHeader']['missionId']
|
|
167
|
+
|
|
168
|
+
filtered = [el for el in data if el['swath'] == swath_id]
|
|
169
|
+
noise_power_correction = np.array([float(el['noisePowerCorrectionFactor']) for el in filtered])
|
|
170
|
+
az_t = np.array([el['azimuthTime'] for el in filtered])
|
|
171
|
+
|
|
172
|
+
if mission_id in ["S1A", "S1B"]:
|
|
173
|
+
if swath_id == "IW1":
|
|
174
|
+
final_noise_pwr = np.concatenate([[noise_power_correction[0]], noise_power_correction[1::2], [noise_power_correction[-1]]])
|
|
175
|
+
final_times = np.concatenate([[az_t[0]], az_t[1::2], [az_t[-1]]])
|
|
176
|
+
elif swath_id in ["IW2", "IW3"]:
|
|
177
|
+
final_noise_pwr = np.concatenate([noise_power_correction[0::2], [noise_power_correction[-1]]])
|
|
178
|
+
final_times = np.concatenate([az_t[0::2], [az_t[-1]]])
|
|
179
|
+
elif swath_id == "EW1":
|
|
180
|
+
final_noise_pwr = noise_power_correction[:-1]
|
|
181
|
+
final_times = az_t[:-1]
|
|
182
|
+
else:
|
|
183
|
+
final_times = az_t
|
|
184
|
+
final_noise_pwr = noise_power_correction
|
|
185
|
+
else:
|
|
186
|
+
if swath_id == "EW2":
|
|
187
|
+
final_times = az_t[:-1]
|
|
188
|
+
final_noise_pwr = noise_power_correction[:-1]
|
|
189
|
+
else:
|
|
190
|
+
final_times = az_t
|
|
191
|
+
final_noise_pwr = noise_power_correction
|
|
192
|
+
|
|
193
|
+
return final_noise_pwr, convert_az_times(final_times)
|
|
194
|
+
|
|
195
|
+
def read_antenna_patterns(metadata_dict, swath_id):
|
|
196
|
+
"""
|
|
197
|
+
Parse the antenna pattern records of a swath.
|
|
198
|
+
|
|
199
|
+
Parameters
|
|
200
|
+
----------
|
|
201
|
+
metadata_dict : dict
|
|
202
|
+
Annotation dictionary, as returned by `read_metadata_from_safe_annotation`.
|
|
203
|
+
swath_id : str
|
|
204
|
+
Swath identifier (e.g. ``"IW1"``).
|
|
205
|
+
|
|
206
|
+
Returns
|
|
207
|
+
-------
|
|
208
|
+
dict
|
|
209
|
+
Dictionary with one element per antenna pattern record of the swath, under the keys:
|
|
210
|
+
|
|
211
|
+
- ``"azimuth_times"``: numpy.ndarray of PreciseDateTime record azimuth times;
|
|
212
|
+
- ``"elevation_pattern_gain"``: list of numpy.ndarray, magnitude of the annotated complex elevation pattern;
|
|
213
|
+
- ``"incidence_angles"``: list of numpy.ndarray, incidence angles [deg];
|
|
214
|
+
- ``"slant_range_t"``: list of numpy.ndarray, slant range times [s] at which the above are given.
|
|
215
|
+
"""
|
|
216
|
+
|
|
217
|
+
data = ensure_list(metadata_dict['product']['antennaPattern']['antennaPatternList']['antennaPattern'])
|
|
218
|
+
|
|
219
|
+
antenna_patterns = {
|
|
220
|
+
"azimuth_times": [],
|
|
221
|
+
"elevation_pattern_gain": [],
|
|
222
|
+
"incidence_angles": [],
|
|
223
|
+
"slant_range_t": []
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
for element in data:
|
|
227
|
+
if element['swath'] == swath_id:
|
|
228
|
+
|
|
229
|
+
antenna_patterns["azimuth_times"].append(PreciseDateTime.from_utc_string(element['azimuthTime']))
|
|
230
|
+
|
|
231
|
+
elevation = np.array(element['elevationPattern']['#text'].split(' '), dtype=float)
|
|
232
|
+
antenna_patterns["elevation_pattern_gain"].append(abs(elevation[::2] + elevation[1::2] * 1j))
|
|
233
|
+
|
|
234
|
+
antenna_patterns["incidence_angles"].append(np.array(element['incidenceAngle']['#text'].split(' '), dtype=float))
|
|
235
|
+
antenna_patterns["slant_range_t"].append(np.array(element['slantRangeTime']['#text'].split(' '), dtype=float))
|
|
236
|
+
|
|
237
|
+
antenna_patterns["azimuth_times"] = np.array(antenna_patterns["azimuth_times"])
|
|
238
|
+
|
|
239
|
+
return antenna_patterns
|
|
240
|
+
|
|
241
|
+
def closest_antenna_pattern_index(antenna_patterns, az_t):
|
|
242
|
+
"""
|
|
243
|
+
Return the index of the antenna pattern record closest in azimuth time to `az_t`.
|
|
244
|
+
|
|
245
|
+
Parameters
|
|
246
|
+
----------
|
|
247
|
+
antenna_patterns : dict
|
|
248
|
+
Antenna patterns, as returned by `read_antenna_patterns`.
|
|
249
|
+
az_t : PreciseDateTime
|
|
250
|
+
Reference azimuth time.
|
|
251
|
+
|
|
252
|
+
Returns
|
|
253
|
+
-------
|
|
254
|
+
int
|
|
255
|
+
Index of the closest record.
|
|
256
|
+
"""
|
|
257
|
+
|
|
258
|
+
return np.argmin(abs(antenna_patterns["azimuth_times"] - az_t))
|
|
259
|
+
|
|
260
|
+
def extract_AR_info(metadata_dict, az_t, range_times, az_steering_rate, orbit = None):
|
|
261
|
+
"""
|
|
262
|
+
Compute the Doppler rate induced by the azimuth antenna steering, constant over the range axis.
|
|
263
|
+
|
|
264
|
+
Parameters
|
|
265
|
+
----------
|
|
266
|
+
metadata_dict : dict
|
|
267
|
+
Annotation dictionary, as returned by `read_metadata_from_safe_annotation`.
|
|
268
|
+
az_t : PreciseDateTime
|
|
269
|
+
Azimuth time at which the satellite velocity is evaluated.
|
|
270
|
+
range_times : numpy.ndarray
|
|
271
|
+
Slant range times [s]; only their number is used.
|
|
272
|
+
az_steering_rate : float
|
|
273
|
+
Azimuth antenna steering rate of the subswath [rad/s], from AUX_INS.
|
|
274
|
+
orbit : CubicSplineTrajectory, optional
|
|
275
|
+
Orbit trajectory, to avoid rebuilding it from the annotation at each call. Default is None (built with
|
|
276
|
+
`build_orbit`).
|
|
277
|
+
|
|
278
|
+
Returns
|
|
279
|
+
-------
|
|
280
|
+
numpy.ndarray
|
|
281
|
+
Steering Doppler rate ``2 * v / lambda * az_steering_rate`` [Hz/s], repeated for each range time.
|
|
282
|
+
"""
|
|
283
|
+
|
|
284
|
+
# Derive AR info
|
|
285
|
+
AR_vec = angles2freq_conversion(metadata_dict, az_t, orbit) * az_steering_rate * np.ones(len(range_times))
|
|
286
|
+
|
|
287
|
+
return AR_vec
|
|
288
|
+
|
|
289
|
+
def extract_FR_info(metadata_dict, range_times, burst_idx = None, is_grd: bool = False, subswath = None):
|
|
290
|
+
"""
|
|
291
|
+
Evaluate the azimuth FM rate over the range axis.
|
|
292
|
+
|
|
293
|
+
Parameters
|
|
294
|
+
----------
|
|
295
|
+
metadata_dict : dict
|
|
296
|
+
Annotation dictionary, as returned by `read_metadata_from_safe_annotation`.
|
|
297
|
+
range_times : numpy.ndarray
|
|
298
|
+
Two-way slant range times [s] at which the FM rate polynomial is evaluated.
|
|
299
|
+
burst_idx : int, optional
|
|
300
|
+
Index of the azimuth FM rate record to use (SLC only). Default is None (first record).
|
|
301
|
+
is_grd : bool, optional
|
|
302
|
+
Whether the product is GRD. Default is False.
|
|
303
|
+
subswath : str, optional
|
|
304
|
+
Subswath identifier, required when `is_grd` is True. Default is None.
|
|
305
|
+
|
|
306
|
+
Returns
|
|
307
|
+
-------
|
|
308
|
+
FR : numpy.ndarray
|
|
309
|
+
Azimuth FM rate [Hz/s] at each range time.
|
|
310
|
+
az_mid_time : PreciseDateTime
|
|
311
|
+
Azimuth time of the FM rate record used (time-shifted for GRD products, see `extract_FMrate`).
|
|
312
|
+
|
|
313
|
+
Notes
|
|
314
|
+
-----
|
|
315
|
+
The first record returned by `extract_FMrate` is used. For GRD products `burst_idx` is ignored, so the first
|
|
316
|
+
annotated record is always used.
|
|
317
|
+
"""
|
|
318
|
+
|
|
319
|
+
# Extract FMrate info
|
|
320
|
+
burst_times, FM_poly_coeffs = extract_FMrate(metadata_dict = metadata_dict,
|
|
321
|
+
burst_index = burst_idx,
|
|
322
|
+
is_grd = is_grd,
|
|
323
|
+
subswath = subswath)
|
|
324
|
+
|
|
325
|
+
FR = FM_poly_coeffs[0].evaluate(range_times) # burst_idx is handled inside extract_FMrate
|
|
326
|
+
az_mid_time = burst_times[0]
|
|
327
|
+
|
|
328
|
+
return FR, az_mid_time
|
|
329
|
+
|
|
330
|
+
def build_orbit(metadata_dict):
|
|
331
|
+
"""
|
|
332
|
+
Build the orbit trajectory from the annotated orbit times, positions and velocities.
|
|
333
|
+
|
|
334
|
+
Parameters
|
|
335
|
+
----------
|
|
336
|
+
metadata_dict : dict
|
|
337
|
+
Annotation dictionary, as returned by `read_metadata_from_safe_annotation`.
|
|
338
|
+
|
|
339
|
+
Returns
|
|
340
|
+
-------
|
|
341
|
+
CubicSplineTrajectory
|
|
342
|
+
Orbit trajectory interpolating the annotated state vectors.
|
|
343
|
+
"""
|
|
344
|
+
|
|
345
|
+
date_time_vec, positions, velocities = extract_info_orbita(metadata_dict)
|
|
346
|
+
|
|
347
|
+
return CubicSplineTrajectory(times=date_time_vec, positions=positions, velocities=velocities)
|
|
348
|
+
|
|
349
|
+
def angles2freq_conversion(metadata_dict, az_t, orbit = None):
|
|
350
|
+
"""
|
|
351
|
+
Return the factor converting an azimuth angular rate into a Doppler rate.
|
|
352
|
+
|
|
353
|
+
Parameters
|
|
354
|
+
----------
|
|
355
|
+
metadata_dict : dict
|
|
356
|
+
Annotation dictionary, as returned by `read_metadata_from_safe_annotation`.
|
|
357
|
+
az_t : PreciseDateTime
|
|
358
|
+
Azimuth time at which the satellite velocity is evaluated.
|
|
359
|
+
orbit : CubicSplineTrajectory, optional
|
|
360
|
+
Orbit trajectory. Default is None (built with `build_orbit`).
|
|
361
|
+
|
|
362
|
+
Returns
|
|
363
|
+
-------
|
|
364
|
+
float
|
|
365
|
+
Conversion factor ``2 * v / lambda`` [Hz/rad], with ``v`` the satellite velocity norm at `az_t` and ``lambda``
|
|
366
|
+
the radar wavelength derived from ``radarFrequency``.
|
|
367
|
+
"""
|
|
368
|
+
|
|
369
|
+
fc_hz = float(metadata_dict['product']['generalAnnotation']['productInformation']['radarFrequency'])
|
|
370
|
+
|
|
371
|
+
if orbit is None:
|
|
372
|
+
orbit = build_orbit(metadata_dict)
|
|
373
|
+
velocity = np.linalg.norm(orbit.velocity(az_t))
|
|
374
|
+
|
|
375
|
+
return 2 * velocity / (speed_of_light / fc_hz)
|
|
376
|
+
|
|
377
|
+
def convert_az_times(times):
|
|
378
|
+
"""
|
|
379
|
+
Convert a sequence of UTC time strings into PreciseDateTime objects.
|
|
380
|
+
|
|
381
|
+
Parameters
|
|
382
|
+
----------
|
|
383
|
+
times : list of str
|
|
384
|
+
UTC time strings, as annotated.
|
|
385
|
+
|
|
386
|
+
Returns
|
|
387
|
+
-------
|
|
388
|
+
numpy.ndarray
|
|
389
|
+
Array of PreciseDateTime objects.
|
|
390
|
+
"""
|
|
391
|
+
|
|
392
|
+
date_time_vec = np.ndarray((len(times),), dtype=PreciseDateTime)
|
|
393
|
+
for idx, date in enumerate(times):
|
|
394
|
+
date_time_vec[idx] = PreciseDateTime.from_utc_string(date)
|
|
395
|
+
|
|
396
|
+
return date_time_vec
|
|
397
|
+
|
|
398
|
+
def extract_FMrate(metadata_dict, burst_index=None, is_grd: bool = False, subswath = None):
|
|
399
|
+
"""
|
|
400
|
+
Read the azimuth FM rate records from the annotation.
|
|
401
|
+
|
|
402
|
+
Parameters
|
|
403
|
+
----------
|
|
404
|
+
metadata_dict : dict
|
|
405
|
+
Annotation dictionary, as returned by `read_metadata_from_safe_annotation`.
|
|
406
|
+
burst_index : int, optional
|
|
407
|
+
If given (and `is_grd` is False), only the record at this index is returned. Default is None (all records).
|
|
408
|
+
is_grd : bool, optional
|
|
409
|
+
Whether the product is GRD. Default is False.
|
|
410
|
+
subswath : str, optional
|
|
411
|
+
Subswath identifier (e.g. ``"IW2"``), required when `is_grd` is True. Default is None.
|
|
412
|
+
|
|
413
|
+
Returns
|
|
414
|
+
-------
|
|
415
|
+
burst_times_vec : numpy.ndarray
|
|
416
|
+
Azimuth times of the records, as PreciseDateTime objects.
|
|
417
|
+
polynomials : list of PolynomialCoefficients2
|
|
418
|
+
FM rate polynomials [Hz/s] in two-way slant range time, one per record.
|
|
419
|
+
|
|
420
|
+
Raises
|
|
421
|
+
------
|
|
422
|
+
KeyError
|
|
423
|
+
If `is_grd` is True and `subswath` is not an IW/EW subswath.
|
|
424
|
+
|
|
425
|
+
Notes
|
|
426
|
+
-----
|
|
427
|
+
The ``t0`` of the first selected record is used as reference time for all polynomials. For GRD products, the record
|
|
428
|
+
times are shifted by ``k * (t[1] - t[0]) / n_swaths``, where ``k`` is the zero-based subswath index and ``n_swaths``
|
|
429
|
+
the number of swaths in the swath merge list; no shift is applied if only one record is present.
|
|
430
|
+
"""
|
|
431
|
+
|
|
432
|
+
fmrate_list = ensure_list(metadata_dict['product']['generalAnnotation']['azimuthFmRateList']['azimuthFmRate'])
|
|
433
|
+
tot_swath_number = int(metadata_dict['product']['swathMerging']['swathMergeList']['@count'])
|
|
434
|
+
|
|
435
|
+
# Handle burst_index if provided
|
|
436
|
+
if burst_index is not None and not is_grd:
|
|
437
|
+
fmrate_list = [fmrate_list[burst_index]]
|
|
438
|
+
|
|
439
|
+
# Extract t0
|
|
440
|
+
t0 = float(fmrate_list[0]['t0'])
|
|
441
|
+
|
|
442
|
+
times = []
|
|
443
|
+
polynomials = []
|
|
444
|
+
|
|
445
|
+
for element in fmrate_list:
|
|
446
|
+
times.append(PreciseDateTime.from_utc_string(element['azimuthTime']))
|
|
447
|
+
|
|
448
|
+
poly_values = element['azimuthFmRatePolynomial']['#text'].split(' ')
|
|
449
|
+
coefficients = PolynomialCoefficients2(
|
|
450
|
+
x0=t0,
|
|
451
|
+
termine_noto=float(poly_values[0]),
|
|
452
|
+
coeff_lineare=float(poly_values[1]),
|
|
453
|
+
coeff_quadratico=float(poly_values[2])
|
|
454
|
+
)
|
|
455
|
+
polynomials.append(coefficients)
|
|
456
|
+
|
|
457
|
+
# Convert times to numpy array
|
|
458
|
+
burst_times_vec = np.array(times, dtype=object)
|
|
459
|
+
delta = 0
|
|
460
|
+
|
|
461
|
+
if is_grd:
|
|
462
|
+
swath_in_swaths = {
|
|
463
|
+
"IW1": 0,
|
|
464
|
+
"EW1": 0,
|
|
465
|
+
"IW2": 1,
|
|
466
|
+
"EW2": 1,
|
|
467
|
+
"IW3": 2,
|
|
468
|
+
"EW3": 2,
|
|
469
|
+
"EW4": 3,
|
|
470
|
+
"EW5": 4,
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
current_swath = swath_in_swaths[subswath]
|
|
474
|
+
if burst_times_vec.size > 1:
|
|
475
|
+
delta = current_swath * (burst_times_vec[1] - burst_times_vec[0]) / tot_swath_number
|
|
476
|
+
else:
|
|
477
|
+
denoise_logger.warning(f"Only one azimuth FM rate record found: no time shift applied for subswath {subswath}")
|
|
478
|
+
|
|
479
|
+
return burst_times_vec + delta, polynomials
|
|
480
|
+
|
|
481
|
+
class PolynomialCoefficients2():
|
|
482
|
+
"""
|
|
483
|
+
Second-order polynomial in ``(x - x0)``.
|
|
484
|
+
|
|
485
|
+
Parameters
|
|
486
|
+
----------
|
|
487
|
+
x0 : float
|
|
488
|
+
Reference abscissa.
|
|
489
|
+
termine_noto : float
|
|
490
|
+
Constant term.
|
|
491
|
+
coeff_lineare : float
|
|
492
|
+
Linear coefficient.
|
|
493
|
+
coeff_quadratico : float
|
|
494
|
+
Quadratic coefficient.
|
|
495
|
+
|
|
496
|
+
Attributes
|
|
497
|
+
----------
|
|
498
|
+
x0 : float
|
|
499
|
+
Reference abscissa.
|
|
500
|
+
termine_noto : float
|
|
501
|
+
Constant term.
|
|
502
|
+
coeff_lineare : float
|
|
503
|
+
Linear coefficient.
|
|
504
|
+
coeff_quadratico : float
|
|
505
|
+
Quadratic coefficient.
|
|
506
|
+
"""
|
|
507
|
+
x0: float
|
|
508
|
+
termine_noto: float
|
|
509
|
+
coeff_lineare: float
|
|
510
|
+
coeff_quadratico: float
|
|
511
|
+
|
|
512
|
+
def __init__(self, x0, termine_noto, coeff_lineare, coeff_quadratico):
|
|
513
|
+
self.x0 = x0
|
|
514
|
+
self.termine_noto = termine_noto
|
|
515
|
+
self.coeff_lineare = coeff_lineare
|
|
516
|
+
self.coeff_quadratico = coeff_quadratico
|
|
517
|
+
|
|
518
|
+
def evaluate(self, x):
|
|
519
|
+
"""
|
|
520
|
+
Evaluate the polynomial.
|
|
521
|
+
|
|
522
|
+
Parameters
|
|
523
|
+
----------
|
|
524
|
+
x : float or numpy.ndarray
|
|
525
|
+
Abscissa value(s).
|
|
526
|
+
|
|
527
|
+
Returns
|
|
528
|
+
-------
|
|
529
|
+
float or numpy.ndarray
|
|
530
|
+
Polynomial value(s) at `x`.
|
|
531
|
+
"""
|
|
532
|
+
return self.termine_noto + self.coeff_lineare * (x - self.x0) + self.coeff_quadratico * ((x - self.x0) **2)
|
|
533
|
+
|
|
534
|
+
|
|
535
|
+
def extract_info_orbita(metadata_dict):
|
|
536
|
+
"""
|
|
537
|
+
Read the annotated orbit state vectors.
|
|
538
|
+
|
|
539
|
+
Parameters
|
|
540
|
+
----------
|
|
541
|
+
metadata_dict : dict
|
|
542
|
+
Annotation dictionary, as returned by `read_metadata_from_safe_annotation`.
|
|
543
|
+
|
|
544
|
+
Returns
|
|
545
|
+
-------
|
|
546
|
+
times : numpy.ndarray
|
|
547
|
+
Orbit state vector times, as PreciseDateTime objects, shape ``(n,)``.
|
|
548
|
+
positions : numpy.ndarray
|
|
549
|
+
Positions [m], shape ``(n, 3)``.
|
|
550
|
+
velocities : numpy.ndarray
|
|
551
|
+
Velocities [m/s], shape ``(n, 3)``.
|
|
552
|
+
"""
|
|
553
|
+
|
|
554
|
+
data_orbita = metadata_dict['product']['generalAnnotation']['orbitList']['orbit']
|
|
555
|
+
|
|
556
|
+
times, x, y, z, vx, vy, vz = [], [], [], [], [], [], []
|
|
557
|
+
for element in data_orbita:
|
|
558
|
+
times.append(element['time'])
|
|
559
|
+
x.append(float(element['position']['x']))
|
|
560
|
+
y.append(float(element['position']['y']))
|
|
561
|
+
z.append(float(element['position']['z']))
|
|
562
|
+
vx.append(float(element['velocity']['x']))
|
|
563
|
+
vy.append(float(element['velocity']['y']))
|
|
564
|
+
vz.append(float(element['velocity']['z']))
|
|
565
|
+
|
|
566
|
+
# Convert times
|
|
567
|
+
date_time_vec = np.ndarray((len(times),), dtype=PreciseDateTime)
|
|
568
|
+
for idx, date in enumerate(times):
|
|
569
|
+
date_time_vec[idx] = PreciseDateTime.from_utc_string(date)
|
|
570
|
+
|
|
571
|
+
return np.array(date_time_vec), np.c_[x, y, z], np.c_[vx, vy, vz]
|