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/utils.py
ADDED
|
@@ -0,0 +1,282 @@
|
|
|
1
|
+
# SPDX-FileCopyrightText: Aresys S.r.l. <info@aresys.it>
|
|
2
|
+
# SPDX-License-Identifier: MIT
|
|
3
|
+
|
|
4
|
+
"""
|
|
5
|
+
General-purpose utilities for the denoising pipeline.
|
|
6
|
+
|
|
7
|
+
Provides helpers for linear/dB and ISO timestamp conversions, swath range boundary lookup, slant-to-ground and
|
|
8
|
+
ground-to-slant range conversions, outlier rejection of noise pulses in noise power series, and list normalisation.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
import numpy as np
|
|
12
|
+
|
|
13
|
+
from perseo_core.timing import PreciseDateTime
|
|
14
|
+
|
|
15
|
+
from s1_denoise import NOISE_LOWER_BOUND, NOISE_UPPER_BOUND
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def linear_to_db(value, is_amplitude=False):
|
|
19
|
+
"""
|
|
20
|
+
Convert values from linear scale to dB.
|
|
21
|
+
|
|
22
|
+
Parameters
|
|
23
|
+
----------
|
|
24
|
+
value : float or array_like
|
|
25
|
+
Value(s) in linear scale.
|
|
26
|
+
is_amplitude : bool, optional
|
|
27
|
+
If True, `value` is an amplitude and ``20 * log10`` is used, otherwise ``10 * log10`` (power). Default is False.
|
|
28
|
+
|
|
29
|
+
Returns
|
|
30
|
+
-------
|
|
31
|
+
float or numpy.ndarray
|
|
32
|
+
Value(s) in dB; a float for scalar input.
|
|
33
|
+
"""
|
|
34
|
+
|
|
35
|
+
factor = 20 if is_amplitude else 10
|
|
36
|
+
db = factor * np.log10(np.asarray(value, dtype=float))
|
|
37
|
+
|
|
38
|
+
return float(db) if db.ndim == 0 else db
|
|
39
|
+
|
|
40
|
+
def db_to_linear(value, is_amplitude=False):
|
|
41
|
+
"""
|
|
42
|
+
Convert values from dB to linear scale.
|
|
43
|
+
|
|
44
|
+
Parameters
|
|
45
|
+
----------
|
|
46
|
+
value : float or array_like
|
|
47
|
+
Value(s) in dB.
|
|
48
|
+
is_amplitude : bool, optional
|
|
49
|
+
If True, an amplitude is returned (``10 ** (x / 20)``), otherwise a power (``10 ** (x / 10)``). Default is False.
|
|
50
|
+
|
|
51
|
+
Returns
|
|
52
|
+
-------
|
|
53
|
+
float or numpy.ndarray
|
|
54
|
+
Value(s) in linear scale; a float for scalar input.
|
|
55
|
+
"""
|
|
56
|
+
|
|
57
|
+
factor = 20 if is_amplitude else 10
|
|
58
|
+
linear = 10 ** (np.asarray(value, dtype=float) / factor)
|
|
59
|
+
|
|
60
|
+
return float(linear) if linear.ndim == 0 else linear
|
|
61
|
+
|
|
62
|
+
def iso_to_datetime(iso_str: str) -> PreciseDateTime:
|
|
63
|
+
"""
|
|
64
|
+
Convert an ISO timestamp string to a PreciseDateTime object.
|
|
65
|
+
|
|
66
|
+
Parameters
|
|
67
|
+
----------
|
|
68
|
+
iso_str : str
|
|
69
|
+
ISO 8601 timestamp.
|
|
70
|
+
|
|
71
|
+
Returns
|
|
72
|
+
-------
|
|
73
|
+
PreciseDateTime
|
|
74
|
+
Corresponding time.
|
|
75
|
+
"""
|
|
76
|
+
return PreciseDateTime.fromisoformat(iso_str)
|
|
77
|
+
|
|
78
|
+
def get_range_boundaries(swath_merges, subswath, az_time: str | PreciseDateTime):
|
|
79
|
+
"""
|
|
80
|
+
Return the first and last GRD range samples of a subswath at a given azimuth time.
|
|
81
|
+
|
|
82
|
+
The swath bounds record used is the latest one with azimuth time not after `az_time`, or the first record if none
|
|
83
|
+
precedes it.
|
|
84
|
+
|
|
85
|
+
Parameters
|
|
86
|
+
----------
|
|
87
|
+
swath_merges : list of dict
|
|
88
|
+
``swathMerge`` records of the annotation.
|
|
89
|
+
subswath : str
|
|
90
|
+
Subswath identifier (e.g. ``"IW2"``).
|
|
91
|
+
az_time : str or PreciseDateTime
|
|
92
|
+
Azimuth time (UTC string or PreciseDateTime).
|
|
93
|
+
|
|
94
|
+
Returns
|
|
95
|
+
-------
|
|
96
|
+
first_range_sample : int
|
|
97
|
+
First range sample of the subswath.
|
|
98
|
+
last_range_sample : int
|
|
99
|
+
Last range sample of the subswath.
|
|
100
|
+
|
|
101
|
+
Raises
|
|
102
|
+
------
|
|
103
|
+
TypeError
|
|
104
|
+
If `az_time` is neither a str nor a PreciseDateTime.
|
|
105
|
+
"""
|
|
106
|
+
|
|
107
|
+
if isinstance(az_time, str):
|
|
108
|
+
az_time = PreciseDateTime.from_utc_string(az_time)
|
|
109
|
+
elif not isinstance(az_time, PreciseDateTime):
|
|
110
|
+
raise TypeError("az_time must be str or PreciseDateTime")
|
|
111
|
+
|
|
112
|
+
for swath_merge in swath_merges:
|
|
113
|
+
|
|
114
|
+
if swath_merge["swath"] == subswath:
|
|
115
|
+
|
|
116
|
+
swath_bounds = ensure_list(swath_merge["swathBoundsList"]["swathBounds"])
|
|
117
|
+
|
|
118
|
+
closest_swath_merge = max(
|
|
119
|
+
(k for k in swath_bounds
|
|
120
|
+
if PreciseDateTime.fromisoformat(k["azimuthTime"]) <= az_time),
|
|
121
|
+
key=lambda k: PreciseDateTime.fromisoformat(k["azimuthTime"]),
|
|
122
|
+
default=swath_bounds[0]
|
|
123
|
+
)
|
|
124
|
+
|
|
125
|
+
first_range_sample = int(closest_swath_merge["firstRangeSample"])
|
|
126
|
+
last_range_sample = int(closest_swath_merge["lastRangeSample"])
|
|
127
|
+
|
|
128
|
+
return first_range_sample, last_range_sample
|
|
129
|
+
|
|
130
|
+
def ground2slant(start_az_time, gr_array, coordinate_conversions):
|
|
131
|
+
"""
|
|
132
|
+
Convert ground range distances to slant range distances.
|
|
133
|
+
|
|
134
|
+
The GRSR polynomial of the coordinate conversion record closest in azimuth to `start_az_time` is evaluated in
|
|
135
|
+
``(gr - gr0)``.
|
|
136
|
+
|
|
137
|
+
Parameters
|
|
138
|
+
----------
|
|
139
|
+
start_az_time : str
|
|
140
|
+
ISO azimuth time used to select the coordinate conversion record.
|
|
141
|
+
gr_array : array_like
|
|
142
|
+
Ground range distances [m].
|
|
143
|
+
coordinate_conversions : list of dict
|
|
144
|
+
``coordinateConversion`` records of the annotation (with ``azimuthTime``, ``gr0`` and ``grsrCoefficients``).
|
|
145
|
+
|
|
146
|
+
Returns
|
|
147
|
+
-------
|
|
148
|
+
numpy.ndarray
|
|
149
|
+
Slant range distances [m].
|
|
150
|
+
"""
|
|
151
|
+
gr_array = np.asarray(gr_array, dtype=float)
|
|
152
|
+
|
|
153
|
+
# select closest GRSR polynomial in azimuth
|
|
154
|
+
cc = min(
|
|
155
|
+
coordinate_conversions,
|
|
156
|
+
key=lambda cc: abs(iso_to_datetime(cc["azimuthTime"]) - iso_to_datetime(start_az_time))
|
|
157
|
+
)
|
|
158
|
+
|
|
159
|
+
sr0 = float(cc["sr0"])
|
|
160
|
+
gr0 = float(cc["gr0"])
|
|
161
|
+
grsr_coeffs = [float(c) for c in cc["grsrCoefficients"]['#text'].split(' ')]
|
|
162
|
+
|
|
163
|
+
delta_gr = gr_array - gr0
|
|
164
|
+
sr_array = np.polyval(grsr_coeffs[::-1], delta_gr) # np.polyval usa coeff in ordine decrescente
|
|
165
|
+
|
|
166
|
+
return sr_array
|
|
167
|
+
|
|
168
|
+
def slant2ground(start_az_time, sim_noise_sr, inc_angle_sr, coordinate_conversions, sr_distances):
|
|
169
|
+
"""
|
|
170
|
+
Resample slant range data onto a uniform ground range grid.
|
|
171
|
+
|
|
172
|
+
The slant range distances are converted to ground range with the SRGR polynomial of the coordinate conversion record
|
|
173
|
+
closest in azimuth to `start_az_time`, as ``gr0 + P(sr - sr0)``. Both inputs are then linearly interpolated onto a
|
|
174
|
+
uniform ground range grid spanning the converted distances, with the same number of samples (NaN outside).
|
|
175
|
+
|
|
176
|
+
Parameters
|
|
177
|
+
----------
|
|
178
|
+
start_az_time : str
|
|
179
|
+
ISO azimuth time used to select the coordinate conversion record.
|
|
180
|
+
sim_noise_sr : array_like
|
|
181
|
+
Simulated noise on the slant range grid.
|
|
182
|
+
inc_angle_sr : array_like
|
|
183
|
+
Incidence-angle-related values on the slant range grid (the GRD pipeline passes their sine).
|
|
184
|
+
coordinate_conversions : list of dict
|
|
185
|
+
Coordinate conversion records with ``azimuthTime``, ``sr0``, ``gr0`` and ``srgrCoeffs`` (list of float).
|
|
186
|
+
sr_distances : array_like
|
|
187
|
+
Slant range distances [m] of the samples of `sim_noise_sr` and `inc_angle_sr`.
|
|
188
|
+
|
|
189
|
+
Returns
|
|
190
|
+
-------
|
|
191
|
+
sim_noise_gr : numpy.ndarray
|
|
192
|
+
Simulated noise on the uniform ground range grid.
|
|
193
|
+
inc_angle_gr : numpy.ndarray
|
|
194
|
+
`inc_angle_sr` resampled on the uniform ground range grid.
|
|
195
|
+
"""
|
|
196
|
+
sim_noise_sr = np.asarray(sim_noise_sr, dtype=float)
|
|
197
|
+
inc_angle_sr = np.asarray(inc_angle_sr, dtype=float)
|
|
198
|
+
sr_distances = np.asarray(sr_distances, dtype=float)
|
|
199
|
+
|
|
200
|
+
# select closest SRGR polynomial in azimuth
|
|
201
|
+
cc = min(
|
|
202
|
+
coordinate_conversions,
|
|
203
|
+
key=lambda cc: abs(iso_to_datetime(cc["azimuthTime"]) - iso_to_datetime(start_az_time))
|
|
204
|
+
)
|
|
205
|
+
|
|
206
|
+
sr0 = float(cc["sr0"])
|
|
207
|
+
gr0 = float(cc["gr0"])
|
|
208
|
+
sr_to_gr_polynomial = cc["srgrCoeffs"]
|
|
209
|
+
|
|
210
|
+
# from SR distances to GR distances (metres)
|
|
211
|
+
sr_norm = sr_distances - sr0
|
|
212
|
+
gr = gr0 + np.polyval(sr_to_gr_polynomial[::-1], sr_norm)
|
|
213
|
+
|
|
214
|
+
# resample onto a uniform GR grid of same length
|
|
215
|
+
gr_grid = np.linspace(gr.min(), gr.max(), sim_noise_sr.size)
|
|
216
|
+
|
|
217
|
+
sim_noise_gr = np.interp(gr_grid, gr, sim_noise_sr, left=np.nan, right=np.nan)
|
|
218
|
+
inc_angle_gr = np.interp(gr_grid, gr, inc_angle_sr, left=np.nan, right=np.nan)
|
|
219
|
+
|
|
220
|
+
return sim_noise_gr, inc_angle_gr
|
|
221
|
+
|
|
222
|
+
def remove_outliers(azimuth_times, noise_pulses, expected_noise_value):
|
|
223
|
+
"""
|
|
224
|
+
Remove the noise pulses falling outside an interval around the expected noise power, with their azimuth times.
|
|
225
|
+
|
|
226
|
+
A pulse is retained if it lies within ``[P_exp - NOISE_LOWER_BOUND dB, P_exp + NOISE_UPPER_BOUND dB]``, with
|
|
227
|
+
``P_exp`` the expected noise power.
|
|
228
|
+
|
|
229
|
+
Parameters
|
|
230
|
+
----------
|
|
231
|
+
azimuth_times : array_like
|
|
232
|
+
Azimuth times of the noise pulses, same length as `noise_pulses`.
|
|
233
|
+
noise_pulses : array_like
|
|
234
|
+
Noise power of each pulse (linear scale).
|
|
235
|
+
expected_noise_value : float
|
|
236
|
+
Expected noise power from AUX_INS (linear scale).
|
|
237
|
+
|
|
238
|
+
Returns
|
|
239
|
+
-------
|
|
240
|
+
azimuth_times_clean : numpy.ndarray
|
|
241
|
+
Azimuth times of the retained pulses.
|
|
242
|
+
noise_pulses_clean : numpy.ndarray
|
|
243
|
+
Noise power of the retained pulses.
|
|
244
|
+
outlier_indices : list of list
|
|
245
|
+
Removed pulses, as ``[index, azimuth_time, noise_pulse]``, sorted by index.
|
|
246
|
+
"""
|
|
247
|
+
|
|
248
|
+
azimuth_times = np.array(azimuth_times)
|
|
249
|
+
noise_pulses = np.array(noise_pulses, dtype=float)
|
|
250
|
+
min_value, max_value = expected_noise_value * db_to_linear(-NOISE_LOWER_BOUND), expected_noise_value * db_to_linear(NOISE_UPPER_BOUND)
|
|
251
|
+
|
|
252
|
+
# Detect outliers
|
|
253
|
+
mask = (noise_pulses < min_value) | (noise_pulses > max_value)
|
|
254
|
+
indices = np.where(mask)[0]
|
|
255
|
+
|
|
256
|
+
# Collect outlier info (already sorted by original index)
|
|
257
|
+
outlier_indices = [[idx, azimuth_times[idx], noise_pulses[idx]] for idx in indices]
|
|
258
|
+
|
|
259
|
+
# Remove outliers
|
|
260
|
+
azimuth_times_clean = azimuth_times[~mask]
|
|
261
|
+
noise_pulses_clean = noise_pulses[~mask]
|
|
262
|
+
|
|
263
|
+
return azimuth_times_clean, noise_pulses_clean, outlier_indices
|
|
264
|
+
|
|
265
|
+
def ensure_list(x) -> list:
|
|
266
|
+
"""
|
|
267
|
+
Return the input as a list.
|
|
268
|
+
|
|
269
|
+
If `x` is already a list it is returned unchanged, otherwise it is wrapped in a single-element list.
|
|
270
|
+
|
|
271
|
+
Parameters
|
|
272
|
+
----------
|
|
273
|
+
x : object
|
|
274
|
+
Input value.
|
|
275
|
+
|
|
276
|
+
Returns
|
|
277
|
+
-------
|
|
278
|
+
list
|
|
279
|
+
`x` itself, or ``[x]``.
|
|
280
|
+
"""
|
|
281
|
+
|
|
282
|
+
return x if isinstance(x, list) else [x]
|