sct-eos04-reader 1.0.0__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.
- sct_eos04_reader/__init__.py +6 -0
- sct_eos04_reader/interface.py +36 -0
- sct_eos04_reader/protocol_implementation.py +831 -0
- sct_eos04_reader-1.0.0.dist-info/METADATA +107 -0
- sct_eos04_reader-1.0.0.dist-info/RECORD +8 -0
- sct_eos04_reader-1.0.0.dist-info/WHEEL +4 -0
- sct_eos04_reader-1.0.0.dist-info/entry_points.txt +2 -0
- sct_eos04_reader-1.0.0.dist-info/licenses/LICENSE.txt +21 -0
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# SPDX-FileCopyrightText: Aresys S.r.l. <info@aresys.it>
|
|
2
|
+
# SPDX-License-Identifier: MIT
|
|
3
|
+
|
|
4
|
+
"""EOS-4 SCT plugin interface."""
|
|
5
|
+
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
from typing import TYPE_CHECKING, Callable
|
|
10
|
+
|
|
11
|
+
from sct_eos04_reader import __version__
|
|
12
|
+
|
|
13
|
+
if TYPE_CHECKING:
|
|
14
|
+
from sct.io.extended_protocols import ALECorrectionFunctionType, SCTInputProduct
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class EOS04ProductPlugin:
|
|
18
|
+
"""Plugin for EOS-04 product format"""
|
|
19
|
+
|
|
20
|
+
version = __version__
|
|
21
|
+
|
|
22
|
+
@classmethod
|
|
23
|
+
def get_manager(cls) -> type[SCTInputProduct]:
|
|
24
|
+
from sct_eos04_reader.protocol_implementation import EOS04ProductManager
|
|
25
|
+
|
|
26
|
+
return EOS04ProductManager
|
|
27
|
+
|
|
28
|
+
@classmethod
|
|
29
|
+
def get_detector(cls) -> Callable[[str | Path], bool]:
|
|
30
|
+
from eo_products.eos04.utilities import is_eos04_product
|
|
31
|
+
|
|
32
|
+
return is_eos04_product
|
|
33
|
+
|
|
34
|
+
@classmethod
|
|
35
|
+
def get_ale_corrector(cls) -> ALECorrectionFunctionType:
|
|
36
|
+
return None
|
|
@@ -0,0 +1,831 @@
|
|
|
1
|
+
# SPDX-FileCopyrightText: Aresys S.r.l. <info@aresys.it>
|
|
2
|
+
# SPDX-License-Identifier: MIT
|
|
3
|
+
|
|
4
|
+
"""EOS-04 format reader protocol-compliant wrapper for PERSEO-quality."""
|
|
5
|
+
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
from itertools import product
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
|
|
11
|
+
import numpy as np
|
|
12
|
+
from eo_products.common.utilities import DopplerEvaluator
|
|
13
|
+
from eo_products.eos04.reader import open_product, read_channel_data, read_product_metadata
|
|
14
|
+
from eo_products.eos04.utilities import EOS04ChannelMetadata
|
|
15
|
+
from numpy.typing import ArrayLike
|
|
16
|
+
from perseo_core.geometry import compute_ground_velocity, compute_incidence_angles, compute_look_angles
|
|
17
|
+
from perseo_core.geometry.geocoding import inverse_geocoding_monostatic
|
|
18
|
+
from perseo_core.geometry.navigation import Trajectory
|
|
19
|
+
from perseo_core.timing import PreciseDateTime
|
|
20
|
+
from perseo_quality.core.custom_errors import (
|
|
21
|
+
CoordinatesOutOfBounds,
|
|
22
|
+
)
|
|
23
|
+
from perseo_quality.core.generic_dataclasses import (
|
|
24
|
+
LocationData,
|
|
25
|
+
SARAcquisitionMode,
|
|
26
|
+
SARImageType,
|
|
27
|
+
SAROrbitDirection,
|
|
28
|
+
SARPolarization,
|
|
29
|
+
SARProjection,
|
|
30
|
+
SARRadiometricQuantity,
|
|
31
|
+
SARSamplingFrequencies,
|
|
32
|
+
SARSideLooking,
|
|
33
|
+
)
|
|
34
|
+
from perseo_quality.core.signal_processing import radiometric_correction
|
|
35
|
+
from perseo_quality.io.protocol_utilities import roi_validation
|
|
36
|
+
from scipy.constants import speed_of_light
|
|
37
|
+
from shapely import Polygon
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class EOS04DopplerPolynomial:
|
|
41
|
+
"""PERSEO-quality Doppler Function protocol compliant EOS04 doppler polynomial wrapper"""
|
|
42
|
+
|
|
43
|
+
def __init__(self, evaluator: DopplerEvaluator) -> None:
|
|
44
|
+
self._evaluator = evaluator
|
|
45
|
+
|
|
46
|
+
def evaluate(self, azimuth_time: PreciseDateTime, range_time: float) -> float:
|
|
47
|
+
"""Evaluate the Doppler Polynomial at given azimuth and range times.
|
|
48
|
+
|
|
49
|
+
Parameters
|
|
50
|
+
----------
|
|
51
|
+
azimuth_time : PreciseDateTime
|
|
52
|
+
azimuth time at which evaluate the polynomial
|
|
53
|
+
range_time : float
|
|
54
|
+
range time at which evaluate the polynomial
|
|
55
|
+
|
|
56
|
+
Returns
|
|
57
|
+
-------
|
|
58
|
+
float
|
|
59
|
+
doppler centroid at that time
|
|
60
|
+
"""
|
|
61
|
+
return self._evaluator.evaluate(azimuth_time, range_time)
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
class EOS04ProductManager:
|
|
65
|
+
"""SCTInputProduct protocol compliant EOS04 wrapper"""
|
|
66
|
+
|
|
67
|
+
def __init__(self, path: str | Path, **kwargs) -> None:
|
|
68
|
+
self._path = Path(path)
|
|
69
|
+
self._name = self._path.name
|
|
70
|
+
self._product = open_product(path)
|
|
71
|
+
self._metadata = read_product_metadata(
|
|
72
|
+
xml_path=self._product.metadata_file, channels=self._product.channels_list
|
|
73
|
+
)
|
|
74
|
+
region_corners = list(product(self._product.footprint[:2], self._product.footprint[2:]))
|
|
75
|
+
self._footprint = Polygon(region_corners).convex_hull
|
|
76
|
+
|
|
77
|
+
@property
|
|
78
|
+
def path(self) -> Path:
|
|
79
|
+
"""Get product path"""
|
|
80
|
+
return self._path
|
|
81
|
+
|
|
82
|
+
@property
|
|
83
|
+
def name(self) -> str:
|
|
84
|
+
"""Get product name"""
|
|
85
|
+
return self._name
|
|
86
|
+
|
|
87
|
+
@property
|
|
88
|
+
def footprint(self) -> Polygon:
|
|
89
|
+
"""Get product footprint"""
|
|
90
|
+
return self._footprint
|
|
91
|
+
|
|
92
|
+
@property
|
|
93
|
+
def channels_list(self) -> list[str]:
|
|
94
|
+
"""Get list of available channels for this product"""
|
|
95
|
+
return self._product.channels_list
|
|
96
|
+
|
|
97
|
+
def get_channel_data(self, channel_id: str) -> EOS04ChannelManager:
|
|
98
|
+
"""Gathering all the information that are channel dependent and storing them in a protocol compliant object.
|
|
99
|
+
|
|
100
|
+
Parameters
|
|
101
|
+
----------
|
|
102
|
+
channel_id : int
|
|
103
|
+
selected channel identifier
|
|
104
|
+
|
|
105
|
+
Returns
|
|
106
|
+
-------
|
|
107
|
+
EOS04ChannelManager
|
|
108
|
+
ChannelData-compliant object containing data corresponding to the selected channel
|
|
109
|
+
"""
|
|
110
|
+
return EOS04ChannelManager(
|
|
111
|
+
channel_metadata=self._metadata[channel_id],
|
|
112
|
+
channel_raster_path=self._product.get_raster_file_from_channel_name(channel_id),
|
|
113
|
+
channel_name=channel_id,
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
class EOS04ChannelManager:
|
|
118
|
+
"""PERSEO-quality ChannelData protocol compliant EOS04 channel wrapper"""
|
|
119
|
+
|
|
120
|
+
def __init__(
|
|
121
|
+
self,
|
|
122
|
+
channel_metadata: EOS04ChannelMetadata,
|
|
123
|
+
channel_raster_path: Path,
|
|
124
|
+
channel_name: str,
|
|
125
|
+
) -> None:
|
|
126
|
+
"""Creating a ChannelManager object compliant with the ChannelData protocol.
|
|
127
|
+
|
|
128
|
+
Parameters
|
|
129
|
+
----------
|
|
130
|
+
channel_metadata : EOS04ChannelMetadata
|
|
131
|
+
channel metadata dataclass for the current channel
|
|
132
|
+
channel_raster_path : int
|
|
133
|
+
Path to the channel raster file
|
|
134
|
+
channel_name : int
|
|
135
|
+
name of current channel
|
|
136
|
+
"""
|
|
137
|
+
|
|
138
|
+
self._channel_id = channel_name
|
|
139
|
+
self._raster_file = channel_raster_path
|
|
140
|
+
self._channel = channel_metadata
|
|
141
|
+
self._sensor_name = (
|
|
142
|
+
"" if self._channel.dataset_info.sensor_name is None else self._channel.dataset_info.sensor_name
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
self._radiometric_quantity = SARRadiometricQuantity[self._channel.image_radiometric_quantity.name]
|
|
146
|
+
self._polarization = SARPolarization(self._channel.general_info.polarization.value)
|
|
147
|
+
self._projection = SARProjection(self._channel.general_info.projection.value)
|
|
148
|
+
self._orbit_direction = SAROrbitDirection[self._channel.general_info.orbit_direction.name]
|
|
149
|
+
self._acquisition_mode = SARAcquisitionMode[self._channel.general_info.acquisition_mode_std.name]
|
|
150
|
+
self._looking_side = SARSideLooking(self._channel.dataset_info.side_looking)
|
|
151
|
+
|
|
152
|
+
self._range_step_m = self._compute_range_step_m()
|
|
153
|
+
self._image_type = self._channel.general_info.product_type
|
|
154
|
+
|
|
155
|
+
# compute axes
|
|
156
|
+
self._azimuth_axis = self._compute_azimuth_axis()
|
|
157
|
+
self._az_time_half_swath = self._azimuth_axis[self._azimuth_axis.size // 2]
|
|
158
|
+
self._range_axis = (
|
|
159
|
+
np.arange(0, self._channel.raster_info.samples.length, 1) * self._channel.raster_info.samples.step
|
|
160
|
+
+ self._channel.raster_info.samples.start
|
|
161
|
+
)
|
|
162
|
+
self._slant_range_axis = self._compute_slant_range_axis()
|
|
163
|
+
rng_time_half_swath = (
|
|
164
|
+
self._channel.raster_info.samples.start
|
|
165
|
+
+ (self._channel.raster_info.samples.length - 1) * self._channel.raster_info.samples.step / 2
|
|
166
|
+
)
|
|
167
|
+
if self._projection == SARProjection.GROUND_RANGE:
|
|
168
|
+
rng_time_half_swath = self._channel.coordinate_conversions.evaluate_ground_to_slant(
|
|
169
|
+
azimuth_time=self._az_time_half_swath, ground_range=np.floor(rng_time_half_swath)
|
|
170
|
+
)
|
|
171
|
+
self._rng_time_half_swath = rng_time_half_swath
|
|
172
|
+
|
|
173
|
+
# lines per burst array
|
|
174
|
+
if self._channel.burst_info.num > 0:
|
|
175
|
+
self._lines_per_burst_array = np.repeat(
|
|
176
|
+
self._channel.burst_info.lines_per_burst, self._channel.burst_info.num
|
|
177
|
+
)
|
|
178
|
+
else:
|
|
179
|
+
# should be a 1D array
|
|
180
|
+
self._lines_per_burst_array = np.repeat(self._channel.raster_info.lines.length, 1)
|
|
181
|
+
|
|
182
|
+
# lines per burst array
|
|
183
|
+
if self._channel.burst_info.num > 0:
|
|
184
|
+
self._lines_per_burst_array = np.repeat(
|
|
185
|
+
self._channel.burst_info.lines_per_burst, self._channel.burst_info.num
|
|
186
|
+
)
|
|
187
|
+
else:
|
|
188
|
+
# should be a 1D array
|
|
189
|
+
self._lines_per_burst_array = np.repeat(self._channel.raster_info.lines.length, 1)
|
|
190
|
+
|
|
191
|
+
# prf
|
|
192
|
+
self._prf = self._channel.swath_info.prf
|
|
193
|
+
|
|
194
|
+
# steering rate
|
|
195
|
+
self._steering_rate_poly_coeff = self._channel.swath_info.azimuth_steering_rate_poly
|
|
196
|
+
|
|
197
|
+
# trajectory
|
|
198
|
+
self._trajectory_rx = self._channel.orbit
|
|
199
|
+
self._trajectory_tx = None
|
|
200
|
+
|
|
201
|
+
# generating doppler centroid wrappers
|
|
202
|
+
self._doppler_centroid_poly = EOS04DopplerPolynomial(evaluator=self._channel.doppler_centroid_poly)
|
|
203
|
+
|
|
204
|
+
# get burst boundaries
|
|
205
|
+
self._burst_az_boundaries, self._burst_rng_boundaries = self._get_raster_layout()
|
|
206
|
+
|
|
207
|
+
def _compute_range_step_m(self) -> float:
|
|
208
|
+
"""Computing step along range direction, in meters"""
|
|
209
|
+
if self._projection == SARProjection.GROUND_RANGE:
|
|
210
|
+
return self._channel.raster_info.samples.step
|
|
211
|
+
|
|
212
|
+
return self._channel.raster_info.samples.step * speed_of_light / 2
|
|
213
|
+
|
|
214
|
+
def _compute_slant_range_axis(self) -> np.ndarray:
|
|
215
|
+
"""Computing slant range full axis.
|
|
216
|
+
|
|
217
|
+
Returns
|
|
218
|
+
-------
|
|
219
|
+
np.ndarray
|
|
220
|
+
slant range axis
|
|
221
|
+
"""
|
|
222
|
+
slant_rng_axis = self._range_axis
|
|
223
|
+
if self._projection == SARProjection.GROUND_RANGE:
|
|
224
|
+
slant_rng_axis = self._channel.coordinate_conversions.evaluate_ground_to_slant(
|
|
225
|
+
azimuth_time=self._az_time_half_swath, ground_range=self._range_axis
|
|
226
|
+
)
|
|
227
|
+
|
|
228
|
+
return slant_rng_axis
|
|
229
|
+
|
|
230
|
+
def _compute_azimuth_axis(self) -> np.ndarray:
|
|
231
|
+
"""Compute azimuth full axis.
|
|
232
|
+
|
|
233
|
+
Returns
|
|
234
|
+
-------
|
|
235
|
+
np.ndarray
|
|
236
|
+
azimuth axis
|
|
237
|
+
"""
|
|
238
|
+
az_axis = (
|
|
239
|
+
np.arange(0, self._channel.raster_info.lines.length, 1) * self._channel.raster_info.lines.step
|
|
240
|
+
+ self._channel.raster_info.lines.start
|
|
241
|
+
)
|
|
242
|
+
if self._channel.burst_info.num > 0:
|
|
243
|
+
az_axis = []
|
|
244
|
+
for brst in range(self._channel.burst_info.num):
|
|
245
|
+
# TODO: check if the valid/invalid samples/lines should be taken into account here
|
|
246
|
+
az_axis.append(
|
|
247
|
+
self._channel.burst_info.azimuth_start_times[brst]
|
|
248
|
+
+ np.arange(0, self._channel.burst_info.lines_per_burst, 1) * self._channel.raster_info.lines.step
|
|
249
|
+
)
|
|
250
|
+
az_axis = np.concatenate(az_axis)
|
|
251
|
+
return az_axis
|
|
252
|
+
|
|
253
|
+
def _get_raster_layout(self) -> tuple[list[PreciseDateTime], list[float]]:
|
|
254
|
+
"""Evaluating raster boundaries taking into account the bursts, if needed.
|
|
255
|
+
|
|
256
|
+
Returns
|
|
257
|
+
-------
|
|
258
|
+
tuple[list[list[PreciseDateTime, PreciseDateTime]], list[list[float, float]]]
|
|
259
|
+
azimuth raster boundaries (azimuth start, azimuth stop),
|
|
260
|
+
range raster boundaries (range start, range stop)
|
|
261
|
+
"""
|
|
262
|
+
|
|
263
|
+
if self._channel.burst_info.num > 0:
|
|
264
|
+
az_times = self._channel.burst_info.azimuth_start_times
|
|
265
|
+
rng_times = np.repeat(self._channel.raster_info.samples.start, az_times.size)
|
|
266
|
+
burst_az_boundaries = []
|
|
267
|
+
for az_time in az_times:
|
|
268
|
+
burst_az_boundaries.append(
|
|
269
|
+
[az_time, az_time + self._channel.burst_info.lines_per_burst * self._channel.raster_info.lines.step]
|
|
270
|
+
)
|
|
271
|
+
burst_rng_boundaries = []
|
|
272
|
+
for rng_time in rng_times:
|
|
273
|
+
burst_rng_boundaries.append(
|
|
274
|
+
[
|
|
275
|
+
rng_time,
|
|
276
|
+
rng_time + self._channel.raster_info.samples.length * self._channel.raster_info.samples.step,
|
|
277
|
+
]
|
|
278
|
+
)
|
|
279
|
+
else:
|
|
280
|
+
burst_az_boundaries = [
|
|
281
|
+
[
|
|
282
|
+
self._channel.raster_info.lines.start,
|
|
283
|
+
self._channel.raster_info.lines.start
|
|
284
|
+
+ self._channel.raster_info.lines.length * self._channel.raster_info.lines.step,
|
|
285
|
+
]
|
|
286
|
+
]
|
|
287
|
+
burst_rng_boundaries = [
|
|
288
|
+
[
|
|
289
|
+
self._channel.raster_info.samples.start,
|
|
290
|
+
self._channel.raster_info.samples.start
|
|
291
|
+
+ self._channel.raster_info.samples.length * self._channel.raster_info.samples.step,
|
|
292
|
+
]
|
|
293
|
+
]
|
|
294
|
+
|
|
295
|
+
return burst_az_boundaries, burst_rng_boundaries
|
|
296
|
+
|
|
297
|
+
@property
|
|
298
|
+
def sensor_name(self) -> str:
|
|
299
|
+
"""Name of the sensor"""
|
|
300
|
+
return self._sensor_name
|
|
301
|
+
|
|
302
|
+
@property
|
|
303
|
+
def swath_name(self) -> str:
|
|
304
|
+
"""Name of the swath being analyzed"""
|
|
305
|
+
return self._channel.general_info.swath
|
|
306
|
+
|
|
307
|
+
@property
|
|
308
|
+
def channel_id(self) -> str:
|
|
309
|
+
"""Identifier of current channel data"""
|
|
310
|
+
return self._channel_id
|
|
311
|
+
|
|
312
|
+
@property
|
|
313
|
+
def prf(self) -> float:
|
|
314
|
+
"""Sensor Pulse Repetition Frequency (PRF)"""
|
|
315
|
+
return self._prf
|
|
316
|
+
|
|
317
|
+
@property
|
|
318
|
+
def range_step_m(self) -> float:
|
|
319
|
+
"""Step along range direction, in meters"""
|
|
320
|
+
return self._range_step_m
|
|
321
|
+
|
|
322
|
+
@property
|
|
323
|
+
def azimuth_step_s(self) -> float:
|
|
324
|
+
"""Step along azimuth direction, in seconds"""
|
|
325
|
+
return self._channel.raster_info.lines.step
|
|
326
|
+
|
|
327
|
+
@property
|
|
328
|
+
def projection(self) -> SARProjection:
|
|
329
|
+
"""Channel data projection"""
|
|
330
|
+
return self._projection
|
|
331
|
+
|
|
332
|
+
@property
|
|
333
|
+
def polarization(self) -> SARPolarization:
|
|
334
|
+
"""Channel data polarization"""
|
|
335
|
+
return self._polarization
|
|
336
|
+
|
|
337
|
+
@property
|
|
338
|
+
def acquisition_mode(self) -> SARAcquisitionMode:
|
|
339
|
+
"""Channel data acquisition mode"""
|
|
340
|
+
return self._acquisition_mode
|
|
341
|
+
|
|
342
|
+
@property
|
|
343
|
+
def orbit_direction(self) -> SAROrbitDirection:
|
|
344
|
+
"""Channel data orbit direction"""
|
|
345
|
+
return self._orbit_direction
|
|
346
|
+
|
|
347
|
+
@property
|
|
348
|
+
def image_type(self) -> SARImageType:
|
|
349
|
+
"""Channel raster image type"""
|
|
350
|
+
return self._image_type
|
|
351
|
+
|
|
352
|
+
@property
|
|
353
|
+
def sampling_constants(self) -> SARSamplingFrequencies:
|
|
354
|
+
"""Channel data signal sampling frequencies"""
|
|
355
|
+
return self._channel.sampling_constants
|
|
356
|
+
|
|
357
|
+
@property
|
|
358
|
+
def looking_side(self) -> SARSideLooking:
|
|
359
|
+
"""Sensor look direction for this channel"""
|
|
360
|
+
return self._looking_side
|
|
361
|
+
|
|
362
|
+
@property
|
|
363
|
+
def carrier_frequency(self) -> float:
|
|
364
|
+
"""Signal carrier frequency"""
|
|
365
|
+
return self._channel.dataset_info.fc_hz
|
|
366
|
+
|
|
367
|
+
@property
|
|
368
|
+
def mid_azimuth_time(self) -> PreciseDateTime:
|
|
369
|
+
"""Azimuth time at half swath"""
|
|
370
|
+
return self._az_time_half_swath
|
|
371
|
+
|
|
372
|
+
@property
|
|
373
|
+
def trajectory(self) -> Trajectory:
|
|
374
|
+
"""Channel trajectory rx 3D curve"""
|
|
375
|
+
return self._trajectory_rx
|
|
376
|
+
|
|
377
|
+
@property
|
|
378
|
+
def attitude(self) -> None:
|
|
379
|
+
"""Channel attitude defined in ECEF Reference Frame"""
|
|
380
|
+
return None
|
|
381
|
+
|
|
382
|
+
@property
|
|
383
|
+
def doppler_centroid(self) -> EOS04DopplerPolynomial:
|
|
384
|
+
"""Channel doppler centroid polynomial wrapper"""
|
|
385
|
+
return self._doppler_centroid_poly
|
|
386
|
+
|
|
387
|
+
@property
|
|
388
|
+
def doppler_rate(self) -> None:
|
|
389
|
+
"""Channel doppler rate polynomial wrapper"""
|
|
390
|
+
return None
|
|
391
|
+
|
|
392
|
+
@property
|
|
393
|
+
def mid_range_time(self) -> float:
|
|
394
|
+
"""Range time at half swath"""
|
|
395
|
+
return self._rng_time_half_swath
|
|
396
|
+
|
|
397
|
+
@property
|
|
398
|
+
def range_axis(self) -> np.ndarray:
|
|
399
|
+
"""Range axis"""
|
|
400
|
+
return self._range_axis
|
|
401
|
+
|
|
402
|
+
@property
|
|
403
|
+
def slant_range_axis(self) -> np.ndarray:
|
|
404
|
+
"""Range axis"""
|
|
405
|
+
return self._slant_range_axis
|
|
406
|
+
|
|
407
|
+
@property
|
|
408
|
+
def azimuth_axis(self) -> np.ndarray:
|
|
409
|
+
"""Azimuth axis, PreciseDateTime format"""
|
|
410
|
+
return self._azimuth_axis
|
|
411
|
+
|
|
412
|
+
@property
|
|
413
|
+
def lines_per_burst(self) -> np.ndarray:
|
|
414
|
+
"""Lines per burst, for each burst in the swath"""
|
|
415
|
+
return self._lines_per_burst_array
|
|
416
|
+
|
|
417
|
+
@property
|
|
418
|
+
def radiometric_quantity(self) -> np.ndarray:
|
|
419
|
+
"""Product radiometric quantity"""
|
|
420
|
+
return self._radiometric_quantity
|
|
421
|
+
|
|
422
|
+
def get_mid_burst_times(self, burst: int) -> tuple[PreciseDateTime, float]:
|
|
423
|
+
"""Compute mid azimuth and range times for a given burst.
|
|
424
|
+
|
|
425
|
+
Returns
|
|
426
|
+
-------
|
|
427
|
+
PreciseDateTime
|
|
428
|
+
azimuth mid burst time
|
|
429
|
+
float
|
|
430
|
+
range mid burst time
|
|
431
|
+
"""
|
|
432
|
+
az_mid_burst = self.mid_azimuth_time
|
|
433
|
+
rng_mid_burst = self.mid_range_time
|
|
434
|
+
if self._channel.burst_info.num > 0:
|
|
435
|
+
az_time_boundaries, rng_time_boundaries = self._get_raster_layout()
|
|
436
|
+
az_mid_burst = (az_time_boundaries[burst][1] - az_time_boundaries[burst][0]) / 2 + az_time_boundaries[
|
|
437
|
+
burst
|
|
438
|
+
][0]
|
|
439
|
+
rng_mid_burst = (rng_time_boundaries[burst][1] - rng_time_boundaries[burst][0]) / 2 + rng_time_boundaries[
|
|
440
|
+
burst
|
|
441
|
+
][0]
|
|
442
|
+
|
|
443
|
+
return az_mid_burst, rng_mid_burst
|
|
444
|
+
|
|
445
|
+
def get_steering_rate(self, azimuth_time: PreciseDateTime, burst: int) -> float:
|
|
446
|
+
"""Compute steering rate at a given azimuth time and for a given burst.
|
|
447
|
+
|
|
448
|
+
Parameters
|
|
449
|
+
----------
|
|
450
|
+
azimuth_time : PreciseDateTime
|
|
451
|
+
azimuth time
|
|
452
|
+
burst : int
|
|
453
|
+
burst corresponding to the input time
|
|
454
|
+
|
|
455
|
+
Returns
|
|
456
|
+
-------
|
|
457
|
+
float
|
|
458
|
+
azimuth steering rate
|
|
459
|
+
"""
|
|
460
|
+
if self._channel.burst_info.num > 0 and burst is not None:
|
|
461
|
+
time_rel = azimuth_time - self._channel.burst_info.azimuth_start_times[burst]
|
|
462
|
+
else:
|
|
463
|
+
time_rel = azimuth_time - self._channel.raster_info.lines.start
|
|
464
|
+
return (
|
|
465
|
+
self._steering_rate_poly_coeff[0]
|
|
466
|
+
+ self._steering_rate_poly_coeff[1] * time_rel
|
|
467
|
+
+ self._steering_rate_poly_coeff[2] * time_rel**2
|
|
468
|
+
)
|
|
469
|
+
|
|
470
|
+
def get_location_data(self, azimuth_time: PreciseDateTime, range_time: float) -> LocationData:
|
|
471
|
+
"""Generating a LocationData object containing data and info derived from the current NovaSAR1ChannelManager
|
|
472
|
+
and declined to the specific azimuth and range times selected.
|
|
473
|
+
|
|
474
|
+
Parameters
|
|
475
|
+
----------
|
|
476
|
+
azimuth_time : PreciseDateTime
|
|
477
|
+
selected azimuth time
|
|
478
|
+
range_time : float
|
|
479
|
+
selected range time
|
|
480
|
+
|
|
481
|
+
Returns
|
|
482
|
+
-------
|
|
483
|
+
LocationData
|
|
484
|
+
LocationData instance related to the selected location
|
|
485
|
+
"""
|
|
486
|
+
|
|
487
|
+
incidence_angle = compute_incidence_angles(
|
|
488
|
+
trajectory=self.trajectory,
|
|
489
|
+
azimuth_time=azimuth_time,
|
|
490
|
+
range_times=range_time,
|
|
491
|
+
look_direction=self.looking_side.value,
|
|
492
|
+
)
|
|
493
|
+
look_angle = compute_look_angles(
|
|
494
|
+
trajectory=self.trajectory,
|
|
495
|
+
azimuth_time=azimuth_time,
|
|
496
|
+
range_times=self.mid_range_time,
|
|
497
|
+
look_direction=self.looking_side.value,
|
|
498
|
+
)
|
|
499
|
+
v_ground = compute_ground_velocity(
|
|
500
|
+
trajectory=self.trajectory, azimuth_time=azimuth_time, look_angles_rad=look_angle
|
|
501
|
+
)
|
|
502
|
+
azimuth_step_m = self.azimuth_step_s * v_ground
|
|
503
|
+
|
|
504
|
+
if self.projection == SARProjection.SLANT_RANGE:
|
|
505
|
+
ground_range_step_m: float = self.range_step_m / np.sin(incidence_angle)
|
|
506
|
+
range_step_m = self.range_step_m
|
|
507
|
+
elif self.projection == SARProjection.GROUND_RANGE:
|
|
508
|
+
ground_range_step_m: float = self.range_step_m
|
|
509
|
+
range_step_m = self.range_step_m * np.sin(incidence_angle)
|
|
510
|
+
|
|
511
|
+
return LocationData(
|
|
512
|
+
abs_azimuth_time=azimuth_time,
|
|
513
|
+
abs_range_time=range_time,
|
|
514
|
+
incidence_angle=incidence_angle,
|
|
515
|
+
look_angle=look_angle,
|
|
516
|
+
ground_velocity=v_ground,
|
|
517
|
+
azimuth_step_m=azimuth_step_m,
|
|
518
|
+
range_step_m=range_step_m,
|
|
519
|
+
ground_range_step_m=ground_range_step_m,
|
|
520
|
+
)
|
|
521
|
+
|
|
522
|
+
def pixel_to_times_conversion(
|
|
523
|
+
self, azimuth_index: float, range_index: float, burst: int = None
|
|
524
|
+
) -> tuple[PreciseDateTime, float]:
|
|
525
|
+
"""Converting input raster pixel coordinates (azimuth_index and range index) to corresponding absolute times,
|
|
526
|
+
azimuth and range.
|
|
527
|
+
|
|
528
|
+
Parameters
|
|
529
|
+
----------
|
|
530
|
+
azimuth_index : float
|
|
531
|
+
azimuth pixel index, subpixel precision
|
|
532
|
+
range_index : float
|
|
533
|
+
range pixel index, subpixel precision
|
|
534
|
+
burst : int, optional
|
|
535
|
+
burst index, by default None
|
|
536
|
+
|
|
537
|
+
Returns
|
|
538
|
+
-------
|
|
539
|
+
PreciseDateTime
|
|
540
|
+
azimuth time
|
|
541
|
+
float
|
|
542
|
+
range time
|
|
543
|
+
"""
|
|
544
|
+
|
|
545
|
+
start_time_rng = self._channel.raster_info.samples.start
|
|
546
|
+
if self._channel.burst_info.num > 0 and burst is not None:
|
|
547
|
+
start_time_az = self._channel.burst_info.azimuth_start_times[burst]
|
|
548
|
+
az_time = (
|
|
549
|
+
azimuth_index - self._channel.burst_info.lines_per_burst * burst
|
|
550
|
+
) * self._channel.raster_info.lines.step + start_time_az
|
|
551
|
+
else:
|
|
552
|
+
start_time_az = self._channel.raster_info.lines.start
|
|
553
|
+
az_time = azimuth_index * self._channel.raster_info.lines.step + start_time_az
|
|
554
|
+
|
|
555
|
+
rng_time = range_index * self._channel.raster_info.samples.step + start_time_rng
|
|
556
|
+
|
|
557
|
+
if self.projection == SARProjection.GROUND_RANGE:
|
|
558
|
+
rng_time = self._channel.coordinate_conversions.evaluate_ground_to_slant(
|
|
559
|
+
azimuth_time=self.mid_azimuth_time, ground_range=rng_time
|
|
560
|
+
)
|
|
561
|
+
|
|
562
|
+
return az_time, rng_time
|
|
563
|
+
|
|
564
|
+
def times_to_pixel_conversion(
|
|
565
|
+
self, azimuth_time: PreciseDateTime, range_time: float, burst: int = None
|
|
566
|
+
) -> tuple[float, float]:
|
|
567
|
+
"""Converting azimuth and range times to raster image pixels indexes with subpixel precision.
|
|
568
|
+
|
|
569
|
+
Parameters
|
|
570
|
+
----------
|
|
571
|
+
azimuth_time : PreciseDateTime
|
|
572
|
+
azimuth time
|
|
573
|
+
range_time : float
|
|
574
|
+
range time
|
|
575
|
+
burst : int
|
|
576
|
+
burst number corresponding to these times
|
|
577
|
+
|
|
578
|
+
Returns
|
|
579
|
+
-------
|
|
580
|
+
float
|
|
581
|
+
pixel corresponding to azimuth time
|
|
582
|
+
float
|
|
583
|
+
pixel corresponding to range time
|
|
584
|
+
"""
|
|
585
|
+
|
|
586
|
+
rng_value = range_time
|
|
587
|
+
if self.projection == SARProjection.GROUND_RANGE:
|
|
588
|
+
# if projection is GROUND RANGE, range info are expressed in meters, so it must be converted
|
|
589
|
+
rng_value = self._channel.coordinate_conversions.evaluate_slant_to_ground(
|
|
590
|
+
azimuth_time=azimuth_time, slant_range=range_time
|
|
591
|
+
)
|
|
592
|
+
|
|
593
|
+
rng_idx = (rng_value - self._channel.raster_info.samples.start) / self._channel.raster_info.samples.step
|
|
594
|
+
if self._channel.burst_info.num > 0:
|
|
595
|
+
if burst is None:
|
|
596
|
+
burst = self.times_to_burst_association([azimuth_time])[0]
|
|
597
|
+
azmth_idx = (
|
|
598
|
+
azimuth_time - self._channel.burst_info.azimuth_start_times[burst]
|
|
599
|
+
) / self._channel.raster_info.lines.step + self._channel.burst_info.lines_per_burst * burst
|
|
600
|
+
else:
|
|
601
|
+
azmth_idx = (azimuth_time - self._channel.raster_info.lines.start) / self._channel.raster_info.lines.step
|
|
602
|
+
|
|
603
|
+
return azmth_idx, rng_idx
|
|
604
|
+
|
|
605
|
+
def ground_points_to_burst_association(self, coordinates: ArrayLike) -> list[list[int] | None]:
|
|
606
|
+
"""Determining the burst (or bursts) where the input coordinates lie. If no association can be found (i.e. the
|
|
607
|
+
point is not visible in the scene), None is returned.
|
|
608
|
+
|
|
609
|
+
Parameters
|
|
610
|
+
----------
|
|
611
|
+
coordinates : ArrayLike
|
|
612
|
+
array of coordinates, in the form (N, 3)
|
|
613
|
+
|
|
614
|
+
Returns
|
|
615
|
+
-------
|
|
616
|
+
list[list[int] | None]
|
|
617
|
+
list containing the burst association for each input point, None if no association was found
|
|
618
|
+
"""
|
|
619
|
+
|
|
620
|
+
coordinates = np.atleast_2d(coordinates)
|
|
621
|
+
|
|
622
|
+
t_azmth, t_rng = [], []
|
|
623
|
+
for coord in coordinates:
|
|
624
|
+
try:
|
|
625
|
+
t_azmth_i, t_rng_i = inverse_geocoding_monostatic(
|
|
626
|
+
trajectory=self.trajectory,
|
|
627
|
+
ground_points=coord,
|
|
628
|
+
doppler_frequencies=0,
|
|
629
|
+
wavelength=1,
|
|
630
|
+
az_initial_time_guesses=self.mid_azimuth_time,
|
|
631
|
+
)
|
|
632
|
+
t_azmth.append(t_azmth_i)
|
|
633
|
+
t_rng.append(t_rng_i)
|
|
634
|
+
except Exception:
|
|
635
|
+
t_azmth.append(np.nan)
|
|
636
|
+
t_rng.append(np.nan)
|
|
637
|
+
|
|
638
|
+
t_azmth = np.asarray(t_azmth)
|
|
639
|
+
t_rng = np.asarray(t_rng)
|
|
640
|
+
|
|
641
|
+
az_check = [
|
|
642
|
+
(
|
|
643
|
+
[(t < az[1] and t > az[0]) for az in self._burst_az_boundaries]
|
|
644
|
+
if isinstance(t, PreciseDateTime)
|
|
645
|
+
else [False]
|
|
646
|
+
)
|
|
647
|
+
for t in t_azmth
|
|
648
|
+
]
|
|
649
|
+
rng_check = [
|
|
650
|
+
[(t < rng[1] and t > rng[0]) for rng in self._burst_rng_boundaries] if ~np.isnan(t) else [False]
|
|
651
|
+
for t in t_rng
|
|
652
|
+
]
|
|
653
|
+
check = [np.logical_and(az_check[c], rng_check[c]) for c in range(len(az_check))]
|
|
654
|
+
|
|
655
|
+
bursts = [list(np.where(c)[0]) if c.any() else None for c in check]
|
|
656
|
+
|
|
657
|
+
return bursts
|
|
658
|
+
|
|
659
|
+
def times_to_burst_association(self, azimuth_times: ArrayLike) -> list[int]:
|
|
660
|
+
"""Associate the right burst to a given input time point. This function returns 1 association for each
|
|
661
|
+
input time.
|
|
662
|
+
Associating time only to the first burst containing it.
|
|
663
|
+
|
|
664
|
+
Parameters
|
|
665
|
+
----------
|
|
666
|
+
azimuth_times : ArrayLike
|
|
667
|
+
azimuth time array in PreciseDateTime format
|
|
668
|
+
|
|
669
|
+
Returns
|
|
670
|
+
-------
|
|
671
|
+
list[int]
|
|
672
|
+
burst associated with a given time
|
|
673
|
+
|
|
674
|
+
Raises
|
|
675
|
+
------
|
|
676
|
+
CoordinatesOutOfBounds
|
|
677
|
+
if input time exceeds tme boundaries of the swath
|
|
678
|
+
"""
|
|
679
|
+
if self._channel.burst_info is None:
|
|
680
|
+
return [0] * len(azimuth_times)
|
|
681
|
+
|
|
682
|
+
bursts_start_times = self._channel.burst_info.azimuth_start_times
|
|
683
|
+
last_time = (
|
|
684
|
+
bursts_start_times[0]
|
|
685
|
+
+ self._channel.burst_info.num
|
|
686
|
+
* self._channel.burst_info.lines_per_burst
|
|
687
|
+
* self._channel.raster_info.lines.step
|
|
688
|
+
)
|
|
689
|
+
|
|
690
|
+
bursts = []
|
|
691
|
+
for time in azimuth_times:
|
|
692
|
+
if time < bursts_start_times[0] or time > last_time:
|
|
693
|
+
raise CoordinatesOutOfBounds(f"{time} is out of the recorded timeline")
|
|
694
|
+
|
|
695
|
+
time_diff = time - bursts_start_times
|
|
696
|
+
time_mask = np.ma.masked_less(time_diff.astype("float64"), 0)
|
|
697
|
+
# associating time only to the first burst containing it
|
|
698
|
+
bursts.append(time_mask.argmin())
|
|
699
|
+
|
|
700
|
+
return bursts
|
|
701
|
+
|
|
702
|
+
def pixel_to_burst_association(self, azimuth_px_indexes: ArrayLike) -> list[int]:
|
|
703
|
+
"""Associate the azimuth pixel value to the right burst. This function returns 1 association for each
|
|
704
|
+
input time.
|
|
705
|
+
|
|
706
|
+
Parameters
|
|
707
|
+
----------
|
|
708
|
+
azimuth_px_indexes : ArrayLike
|
|
709
|
+
azimuth pixel indexes array
|
|
710
|
+
|
|
711
|
+
Returns
|
|
712
|
+
-------
|
|
713
|
+
list[int]
|
|
714
|
+
burst associated with a given pixel index
|
|
715
|
+
|
|
716
|
+
Raises
|
|
717
|
+
------
|
|
718
|
+
CoordinatesOutOfBounds
|
|
719
|
+
if input time exceeds tme boundaries of the swath
|
|
720
|
+
"""
|
|
721
|
+
if self._channel.burst_info is None:
|
|
722
|
+
return [0] * len(azimuth_px_indexes)
|
|
723
|
+
|
|
724
|
+
bursts_lines = np.repeat(self._channel.burst_info.lines_per_burst, self._channel.burst_info.num)
|
|
725
|
+
burst_boundaries = np.array([0] + [sum(bursts_lines[: t + 1]) for t, _ in enumerate(bursts_lines)])
|
|
726
|
+
|
|
727
|
+
bursts = []
|
|
728
|
+
for coord in azimuth_px_indexes:
|
|
729
|
+
if coord > burst_boundaries[-1]:
|
|
730
|
+
raise CoordinatesOutOfBounds(f"{coord} pixel exceeds swath's bounds")
|
|
731
|
+
|
|
732
|
+
px_diff = coord - burst_boundaries
|
|
733
|
+
px_mask = np.ma.masked_less(px_diff, 0)
|
|
734
|
+
|
|
735
|
+
bursts.append(px_mask.argmin())
|
|
736
|
+
|
|
737
|
+
return bursts
|
|
738
|
+
|
|
739
|
+
def read_data(
|
|
740
|
+
self,
|
|
741
|
+
azimuth_index: float,
|
|
742
|
+
range_index: float,
|
|
743
|
+
cropping_size: tuple[int, int] = (150, 150),
|
|
744
|
+
output_radiometric_quantity: SARRadiometricQuantity = SARRadiometricQuantity.BETA_NOUGHT,
|
|
745
|
+
burst: int | None = None,
|
|
746
|
+
) -> np.ndarray:
|
|
747
|
+
"""Extracting the swath portion centered to the provided target position and of size cropping_size by
|
|
748
|
+
cropping_size. Target position is provided via its azimuth and range indexes in the swath array.
|
|
749
|
+
|
|
750
|
+
Parameters
|
|
751
|
+
----------
|
|
752
|
+
azimuth_index : float
|
|
753
|
+
index of azimuth time in swath array
|
|
754
|
+
range_index : float
|
|
755
|
+
index of range time in swath array
|
|
756
|
+
cropping_size : tuple[int, int], optional
|
|
757
|
+
size in pixel of the swath portion to be read (number of samples, number of lines), by default (150, 150)
|
|
758
|
+
output_radiometric_quantity : SARRadiometricQuantity, optional
|
|
759
|
+
selected output radiometric quantity to convert the read data to, if needed,
|
|
760
|
+
by default SARRadiometricQuantity.BETA_NOUGHT
|
|
761
|
+
burst : int, optional
|
|
762
|
+
if burst is provided, the roi extraction gives error if the boundaries exceed the burst boundaries,
|
|
763
|
+
by default None
|
|
764
|
+
|
|
765
|
+
Returns
|
|
766
|
+
-------
|
|
767
|
+
np.ndarray
|
|
768
|
+
cropped swath array centered to the input target coordinates, output array is (samples, lines)
|
|
769
|
+
by default the output radiometric quantity is BETA_NOUGHT, unless specified otherwise
|
|
770
|
+
|
|
771
|
+
Raises
|
|
772
|
+
------
|
|
773
|
+
AzimuthExceedsBoundariesError
|
|
774
|
+
azimuth index exceeds swath boundaries
|
|
775
|
+
RangeExceedsBoundariesError
|
|
776
|
+
range index exceeds swath boundaries
|
|
777
|
+
"""
|
|
778
|
+
|
|
779
|
+
# creating the target block identifier for partial swath reading
|
|
780
|
+
# [start line, start sample, number of lines, number of samples]
|
|
781
|
+
target_block = [
|
|
782
|
+
azimuth_index - np.floor(cropping_size[1] / 2).astype(int),
|
|
783
|
+
range_index - np.floor(cropping_size[0] / 2).astype(int),
|
|
784
|
+
cropping_size[1],
|
|
785
|
+
cropping_size[0],
|
|
786
|
+
]
|
|
787
|
+
|
|
788
|
+
# full raster boundaries and burst boundaries, if applicable
|
|
789
|
+
raster_boundaries = [0, 0, self._channel.raster_info.lines.length, self._channel.raster_info.samples.length]
|
|
790
|
+
burst_boundaries = None
|
|
791
|
+
# if burst is provided, it means that the ROI to be read must be inside of this burst, otherwise the extracted
|
|
792
|
+
# data are not meaningful with respect to times, acquisition consistency and IRF
|
|
793
|
+
if burst is not None:
|
|
794
|
+
burst_boundaries = [
|
|
795
|
+
sum(self.lines_per_burst[:burst]),
|
|
796
|
+
0,
|
|
797
|
+
self.lines_per_burst[burst],
|
|
798
|
+
self._channel.raster_info.samples.length,
|
|
799
|
+
]
|
|
800
|
+
|
|
801
|
+
# validating target block extraction with respect to raster boundaries and burst boundaries
|
|
802
|
+
roi_validation(
|
|
803
|
+
roi=target_block,
|
|
804
|
+
raster_boundaries=raster_boundaries,
|
|
805
|
+
burst_boundaries=burst_boundaries,
|
|
806
|
+
)
|
|
807
|
+
|
|
808
|
+
# reading data portion and switching to convention (samples, lines) with transpose
|
|
809
|
+
data = read_channel_data(
|
|
810
|
+
raster_file=self._raster_file,
|
|
811
|
+
block_to_read=target_block,
|
|
812
|
+
scaling_conversion=self._channel.image_calibration_factor,
|
|
813
|
+
).T
|
|
814
|
+
|
|
815
|
+
# converting to beta nought if radiometric quantity is different
|
|
816
|
+
if self._radiometric_quantity != output_radiometric_quantity:
|
|
817
|
+
azimuth_time, _ = self.pixel_to_times_conversion(azimuth_index=azimuth_index, range_index=range_index)
|
|
818
|
+
incidence_angles_rad = compute_incidence_angles(
|
|
819
|
+
trajectory=self.trajectory,
|
|
820
|
+
azimuth_time=azimuth_time,
|
|
821
|
+
range_times=self.slant_range_axis[target_block[1] : target_block[1] + target_block[3]],
|
|
822
|
+
look_direction=self.looking_side.value,
|
|
823
|
+
)
|
|
824
|
+
data = radiometric_correction(
|
|
825
|
+
data=data,
|
|
826
|
+
incidence_angle=incidence_angles_rad,
|
|
827
|
+
input_quantity=self._radiometric_quantity,
|
|
828
|
+
output_quantity=output_radiometric_quantity,
|
|
829
|
+
)
|
|
830
|
+
|
|
831
|
+
return data
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: sct_eos04_reader
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: SCT (SAR Calibration Toolbox) plugin for reading EOS-04 Level 1 product format.
|
|
5
|
+
Project-URL: Homepage, https://github.com/aresys-srl/sct_plugins
|
|
6
|
+
Project-URL: Repository, https://github.com/aresys-srl/sct_plugins
|
|
7
|
+
Project-URL: Documentation, https://aresys-srl.github.io/sct_plugins
|
|
8
|
+
Author-email: "Aresys S.R.L." <info@aresys.it>
|
|
9
|
+
License: MIT License
|
|
10
|
+
|
|
11
|
+
Copyright (C) Aresys S.r.l. <info@aresys.it>
|
|
12
|
+
|
|
13
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
14
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
15
|
+
in the Software without restriction, including without limitation the rights
|
|
16
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
17
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
18
|
+
furnished to do so, subject to the following conditions:
|
|
19
|
+
|
|
20
|
+
The above copyright notice and this permission notice shall be included in all
|
|
21
|
+
copies or substantial portions of the Software.
|
|
22
|
+
|
|
23
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
24
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
25
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
26
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
27
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
28
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
29
|
+
SOFTWARE.
|
|
30
|
+
License-File: LICENSE.txt
|
|
31
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
32
|
+
Classifier: Intended Audience :: Developers
|
|
33
|
+
Classifier: Intended Audience :: Education
|
|
34
|
+
Classifier: Intended Audience :: Science/Research
|
|
35
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
36
|
+
Classifier: Natural Language :: English
|
|
37
|
+
Classifier: Operating System :: OS Independent
|
|
38
|
+
Classifier: Programming Language :: Python
|
|
39
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
40
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
41
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
42
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
43
|
+
Classifier: Topic :: Scientific/Engineering
|
|
44
|
+
Classifier: Topic :: Software Development :: Libraries
|
|
45
|
+
Classifier: Topic :: Utilities
|
|
46
|
+
Classifier: Typing :: Typed
|
|
47
|
+
Requires-Python: >=3.11
|
|
48
|
+
Requires-Dist: eo-products==1.0.0
|
|
49
|
+
Requires-Dist: numpy>2
|
|
50
|
+
Requires-Dist: perseo-core==1.0.0
|
|
51
|
+
Requires-Dist: perseo-quality==1.0.0
|
|
52
|
+
Requires-Dist: scipy
|
|
53
|
+
Requires-Dist: sct
|
|
54
|
+
Requires-Dist: shapely>=2.1.0
|
|
55
|
+
Provides-Extra: dev
|
|
56
|
+
Requires-Dist: nox; extra == 'dev'
|
|
57
|
+
Requires-Dist: pylint; extra == 'dev'
|
|
58
|
+
Requires-Dist: ruff; extra == 'dev'
|
|
59
|
+
Provides-Extra: docs
|
|
60
|
+
Requires-Dist: mkdocstrings-python; extra == 'docs'
|
|
61
|
+
Requires-Dist: zensical; extra == 'docs'
|
|
62
|
+
Provides-Extra: test
|
|
63
|
+
Requires-Dist: pytest; extra == 'test'
|
|
64
|
+
Requires-Dist: pytest-cov; extra == 'test'
|
|
65
|
+
Requires-Dist: sct[graphs]; extra == 'test'
|
|
66
|
+
Description-Content-Type: text/markdown
|
|
67
|
+
|
|
68
|
+
# SCT Plugin: EOS-04 format reader
|
|
69
|
+
|
|
70
|
+
[](https://pypi.org/project/sct-eos04-reader/)
|
|
71
|
+
[](https://python.org)
|
|
72
|
+
[](LICENSE.txt)
|
|
73
|
+
|
|
74
|
+
[](https://github.com/aresys-srl/sct_plugins/actions/workflows/eos04.yml)
|
|
75
|
+
|
|
76
|
+
[SCT (SAR Calibration Toolbox)](https://github.com/aresys-srl/sct) plugin for reading EOS-04
|
|
77
|
+
Level 1 products, both L1A (SLC) and L1B (GRD). This package integrates with SCT through its
|
|
78
|
+
input products plugin system, enabling all SCT analyses on EOS-04 data.
|
|
79
|
+
|
|
80
|
+
## Supported Acquisition Modes
|
|
81
|
+
|
|
82
|
+
- **Scansar**
|
|
83
|
+
|
|
84
|
+
## Installation
|
|
85
|
+
|
|
86
|
+
```bash
|
|
87
|
+
pip install sct-eos04-reader
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
SCT is automatically installed as a dependency.
|
|
91
|
+
|
|
92
|
+
## Compatibility
|
|
93
|
+
|
|
94
|
+
This plugin must be installed in the same Python environment as SCT. Once installed,
|
|
95
|
+
the plugin is automatically discovered and registered by SCT through its entry-point
|
|
96
|
+
based plugin system; no additional configuration is required.
|
|
97
|
+
|
|
98
|
+
## Documentation
|
|
99
|
+
|
|
100
|
+
- [SCT documentation](https://aresys-srl.github.io/sct/)
|
|
101
|
+
- [Plugins documentation](https://aresys-srl.github.io/sct_plugins)
|
|
102
|
+
|
|
103
|
+
## License
|
|
104
|
+
|
|
105
|
+
This project is licensed under the MIT License. See the [LICENSE.txt](LICENSE.txt) file for details.
|
|
106
|
+
|
|
107
|
+
Copyright © 2026-present Aresys S.r.l. <info@aresys.it>
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
sct_eos04_reader/__init__.py,sha256=_EBlCezaQ2zFDbyns494eM-FgTaaJQL3h13FGW5TIwg,160
|
|
2
|
+
sct_eos04_reader/interface.py,sha256=CDHJ4LFqWAKV59NvCbsx1Q90fZ9GthXM4UTAlqvy_uE,932
|
|
3
|
+
sct_eos04_reader/protocol_implementation.py,sha256=3gzGZrik-0VcTOAdi4ih7MkXTFknWHvidDLx7yjKJ14,30764
|
|
4
|
+
sct_eos04_reader-1.0.0.dist-info/METADATA,sha256=8mz8tfLBaRLmP-2X4GTngLbIehm8DVipe4VLZ2VXTWQ,4563
|
|
5
|
+
sct_eos04_reader-1.0.0.dist-info/WHEEL,sha256=mffPy8wBnZQn2VnJUU5jE99KsxaSfiyMHV9Yt0aLVxs,87
|
|
6
|
+
sct_eos04_reader-1.0.0.dist-info/entry_points.txt,sha256=zp_B8B-ghGk6D7IRtVE4rkfeiRKVx0Ek5BLtVS7U5KM,75
|
|
7
|
+
sct_eos04_reader-1.0.0.dist-info/licenses/LICENSE.txt,sha256=5-eKUp948IoydappLufHc2oAqasNGKhXpIB3qqQvXlY,1082
|
|
8
|
+
sct_eos04_reader-1.0.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (C) Aresys S.r.l. <info@aresys.it>
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|