sct-asar-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_asar_reader/__init__.py +6 -0
- sct_asar_reader/core/__init__.py +4 -0
- sct_asar_reader/core/common.py +187 -0
- sct_asar_reader/core/reader.py +215 -0
- sct_asar_reader/core/utilities.py +1035 -0
- sct_asar_reader/interface.py +36 -0
- sct_asar_reader/protocol_implementation.py +832 -0
- sct_asar_reader-1.0.0.dist-info/METADATA +94 -0
- sct_asar_reader-1.0.0.dist-info/RECORD +12 -0
- sct_asar_reader-1.0.0.dist-info/WHEEL +4 -0
- sct_asar_reader-1.0.0.dist-info/entry_points.txt +2 -0
- sct_asar_reader-1.0.0.dist-info/licenses/LICENSE.txt +674 -0
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
# SPDX-FileCopyrightText: Aresys S.r.l. <info@aresys.it>
|
|
2
|
+
# SPDX-License-Identifier: GPLv3+
|
|
3
|
+
|
|
4
|
+
"""Common Enum, dataclasses and other utilities."""
|
|
5
|
+
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
from dataclasses import dataclass, field
|
|
9
|
+
from enum import Enum, auto
|
|
10
|
+
from typing import Literal
|
|
11
|
+
|
|
12
|
+
import numpy as np
|
|
13
|
+
from numpy.polynomial import Polynomial
|
|
14
|
+
from perseo_core.timing import PreciseDateTime
|
|
15
|
+
from scipy.interpolate import CubicSpline
|
|
16
|
+
|
|
17
|
+
LookingDirection = Literal["LEFT", "RIGHT"]
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class SARRadiometricQuantity(Enum):
|
|
21
|
+
"""Enum class for radiometric analysis input/output quantity types"""
|
|
22
|
+
|
|
23
|
+
BETA_NOUGHT = auto()
|
|
24
|
+
SIGMA_NOUGHT = auto()
|
|
25
|
+
GAMMA_NOUGHT = auto()
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class SARPolarization(Enum):
|
|
29
|
+
"""Polarization enum class"""
|
|
30
|
+
|
|
31
|
+
HH = "H/H"
|
|
32
|
+
VV = "V/V"
|
|
33
|
+
HV = "H/V"
|
|
34
|
+
VH = "V/H"
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class SARProjection(Enum):
|
|
38
|
+
"""Enum class for managing swath projection of product folder"""
|
|
39
|
+
|
|
40
|
+
SLANT_RANGE = "SLANT RANGE"
|
|
41
|
+
GROUND_RANGE = "GROUND RANGE"
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class OrbitDirection(Enum):
|
|
45
|
+
"""Orbit direction: ascending or descending"""
|
|
46
|
+
|
|
47
|
+
ASCENDING = "ascending"
|
|
48
|
+
DESCENDING = "descending"
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
class StandardSARAcquisitionMode(Enum):
|
|
52
|
+
"""Standard cross-package SAR acquisition mode definition"""
|
|
53
|
+
|
|
54
|
+
SCANSAR = auto()
|
|
55
|
+
SPOTLIGHT = auto()
|
|
56
|
+
STRIPMAP = auto()
|
|
57
|
+
TOPSAR = auto()
|
|
58
|
+
WAVE = auto()
|
|
59
|
+
UNKNOWN = auto()
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
@dataclass
|
|
63
|
+
class PulseInfo:
|
|
64
|
+
"""Chirp pulse info"""
|
|
65
|
+
|
|
66
|
+
length_s: float
|
|
67
|
+
bandwidth_hz: float
|
|
68
|
+
sampling_rate_hz: float
|
|
69
|
+
energy_j: float
|
|
70
|
+
start_frequency_hz: float
|
|
71
|
+
start_phase: float
|
|
72
|
+
direction: str
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
@dataclass
|
|
76
|
+
class DatasetInfo:
|
|
77
|
+
"""Dataset Info"""
|
|
78
|
+
|
|
79
|
+
fc_hz: float
|
|
80
|
+
acquisition_mode: str
|
|
81
|
+
sensor_name: str
|
|
82
|
+
image_type: str
|
|
83
|
+
projection: str
|
|
84
|
+
side_looking: LookingDirection
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
@dataclass
|
|
88
|
+
class RasterInfo:
|
|
89
|
+
"""Product Raster Info"""
|
|
90
|
+
|
|
91
|
+
lines: RasterInfoAxis
|
|
92
|
+
samples: RasterInfoAxis
|
|
93
|
+
data_type: str | None = None
|
|
94
|
+
raster_name: str | None = None
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
@dataclass
|
|
98
|
+
class RasterInfoAxis:
|
|
99
|
+
"""Axis representation for RasterInfo"""
|
|
100
|
+
|
|
101
|
+
length: int
|
|
102
|
+
step: float
|
|
103
|
+
start: float | PreciseDateTime
|
|
104
|
+
step_unit: str
|
|
105
|
+
axis: np.ndarray = field(init=False)
|
|
106
|
+
|
|
107
|
+
def __post_init__(self):
|
|
108
|
+
# generating axis array from inputs
|
|
109
|
+
self.axis = np.arange(0, self.length, 1) * self.step + self.start
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
@dataclass
|
|
113
|
+
class SARSamplingFrequencies:
|
|
114
|
+
"""SAR signal sampling frequencies"""
|
|
115
|
+
|
|
116
|
+
range_freq_hz: float
|
|
117
|
+
range_bandwidth_freq_hz: float
|
|
118
|
+
azimuth_freq_hz: float
|
|
119
|
+
azimuth_bandwidth_freq_hz: float
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
@dataclass
|
|
123
|
+
class ConversionFunction:
|
|
124
|
+
"""Generic conversion function wrapper"""
|
|
125
|
+
|
|
126
|
+
azimuth_reference_time: PreciseDateTime
|
|
127
|
+
origin: float
|
|
128
|
+
function: Polynomial | CubicSpline
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
@dataclass
|
|
132
|
+
class ConversionPolynomial:
|
|
133
|
+
"""Generic conversion polynomial wrapper"""
|
|
134
|
+
|
|
135
|
+
azimuth_reference_time: PreciseDateTime
|
|
136
|
+
origin: float
|
|
137
|
+
polynomial: Polynomial | CubicSpline
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
@dataclass
|
|
141
|
+
class DopplerEvaluator:
|
|
142
|
+
"""Doppler function (rate/centroid) evaluator"""
|
|
143
|
+
|
|
144
|
+
functions: list[ConversionFunction] | None = None
|
|
145
|
+
azimuth_reference_times: np.ndarray | None = None
|
|
146
|
+
|
|
147
|
+
def evaluate(self, azimuth_time: PreciseDateTime, slant_range: np.ndarray) -> np.ndarray:
|
|
148
|
+
"""Evaluate function at given inputs.
|
|
149
|
+
|
|
150
|
+
Parameters
|
|
151
|
+
----------
|
|
152
|
+
azimuth_time : PreciseDateTime
|
|
153
|
+
azimuth time to select the proper function
|
|
154
|
+
slant_range : np.ndarray
|
|
155
|
+
slant range value(s) in meters
|
|
156
|
+
|
|
157
|
+
Returns
|
|
158
|
+
-------
|
|
159
|
+
np.ndarray
|
|
160
|
+
Doppler functions values at slant range
|
|
161
|
+
"""
|
|
162
|
+
poly_index = detect_right_polynomial_index(
|
|
163
|
+
azimuth_time=azimuth_time, reference_azimuth_times=self.azimuth_reference_times
|
|
164
|
+
)
|
|
165
|
+
poly = self.functions[poly_index]
|
|
166
|
+
return poly.function(slant_range - poly.origin)
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
def detect_right_polynomial_index(azimuth_time: PreciseDateTime, reference_azimuth_times: np.ndarray) -> int:
|
|
170
|
+
"""Detecting the index of the right polynomial to be used given an input azimuth time.
|
|
171
|
+
The polynomial to be used is the one with reference azimuth time closest to the input value but with
|
|
172
|
+
reference_azimuth_time < input_azimuth_time.
|
|
173
|
+
|
|
174
|
+
Parameters
|
|
175
|
+
----------
|
|
176
|
+
azimuth_time : PreciseDateTime
|
|
177
|
+
selected azimuth time
|
|
178
|
+
reference_azimuth_times : np.ndarray
|
|
179
|
+
array of reference azimuth times, in PreciseDateTime format
|
|
180
|
+
|
|
181
|
+
Returns
|
|
182
|
+
-------
|
|
183
|
+
int
|
|
184
|
+
index corresponding to the polynomial to be used
|
|
185
|
+
"""
|
|
186
|
+
diff = np.array(azimuth_time - reference_azimuth_times).astype("float")
|
|
187
|
+
return np.ma.masked_where(diff < 0, diff).argmin()
|
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
# SPDX-FileCopyrightText: Aresys S.r.l. <info@aresys.it>
|
|
2
|
+
# SPDX-License-Identifier: GPLv3+
|
|
3
|
+
|
|
4
|
+
"""Envisat & ERS product format reader."""
|
|
5
|
+
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
|
|
10
|
+
import epr
|
|
11
|
+
import numpy as np
|
|
12
|
+
|
|
13
|
+
import sct_asar_reader.core.utilities as support
|
|
14
|
+
from sct_asar_reader.core.common import SARRadiometricQuantity, StandardSARAcquisitionMode
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def read_channel_metadata(file_path: Path | str, channel_id: str) -> support.ASARChannelMetadata:
|
|
18
|
+
"""Reading metadata for current ASAR input product related to the selected channel.
|
|
19
|
+
|
|
20
|
+
Parameters
|
|
21
|
+
----------
|
|
22
|
+
file_path : Path | str
|
|
23
|
+
Path to the ASAR binary product
|
|
24
|
+
channel_id : str
|
|
25
|
+
channel of choice
|
|
26
|
+
|
|
27
|
+
Returns
|
|
28
|
+
-------
|
|
29
|
+
support.ASARChannelMetadata
|
|
30
|
+
metadata for the selected channel
|
|
31
|
+
"""
|
|
32
|
+
file_path = Path(file_path)
|
|
33
|
+
mph, sph = support.read_product_headers(product=file_path)
|
|
34
|
+
acquisition_mode = support.ASARAcquisitionMode.from_str(file_path.name)
|
|
35
|
+
if acquisition_mode == support.ASARAcquisitionMode.STRIPMAP:
|
|
36
|
+
acquisition_mode_std = StandardSARAcquisitionMode.STRIPMAP
|
|
37
|
+
else:
|
|
38
|
+
acquisition_mode_std = StandardSARAcquisitionMode.WAVE
|
|
39
|
+
product_type = support.ASARProductType.from_str(sph.sample_type)
|
|
40
|
+
projection = support.get_projection_from_product_type(product_type)
|
|
41
|
+
|
|
42
|
+
product = epr.open(str(file_path))
|
|
43
|
+
main_params_dataset = product.get_dataset("MAIN_PROCESSING_PARAMS_ADS")
|
|
44
|
+
doppler_centroid_coeffs_dataset = product.get_dataset("DOP_CENTROID_COEFFS_ADS")
|
|
45
|
+
geolocation_grid_dataset = product.get_dataset("GEOLOCATION_GRID_ADS")
|
|
46
|
+
|
|
47
|
+
# mds1_sq_dataset = product.get_dataset("MDS1_SQ_ADS")
|
|
48
|
+
# mds1_dataset = product.get_dataset("MDS1")
|
|
49
|
+
# chirp_params_dataset = product.get_dataset("CHIRP_PARAMS_ADS")
|
|
50
|
+
|
|
51
|
+
raster_info = support.raster_info_from_record(
|
|
52
|
+
main_params_dataset=main_params_dataset,
|
|
53
|
+
geolocation_record=geolocation_grid_dataset.read_record(0),
|
|
54
|
+
product_type=product_type,
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
state_vectors = support.ASARStateVectors.from_metadata(
|
|
58
|
+
main_params_dataset=main_params_dataset, orbit_direction=sph.orbit_direction
|
|
59
|
+
)
|
|
60
|
+
orbit = support.orbit_from_state_vectors(state_vectors=state_vectors)
|
|
61
|
+
|
|
62
|
+
doppler_centroid_poly = support.doppler_centroid_poly_from_dataset(
|
|
63
|
+
dc_params_dataset=doppler_centroid_coeffs_dataset
|
|
64
|
+
)
|
|
65
|
+
doppler_rate_poly = support.doppler_rate_poly_from_dataset(
|
|
66
|
+
main_params_dataset=main_params_dataset,
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
coordinate_conversion = None
|
|
70
|
+
if "SR_GR_ADS" in product.get_dataset_names():
|
|
71
|
+
sr_gr_dataset = product.get_dataset("SR_GR_ADS")
|
|
72
|
+
coordinate_conversion = support.ASARCoordinateConversions.from_dataset(
|
|
73
|
+
slant_to_ground_dataset=sr_gr_dataset, raster_info=raster_info
|
|
74
|
+
)
|
|
75
|
+
else:
|
|
76
|
+
coordinate_conversion = support.ASARCoordinateConversions.from_orbit(orbit=orbit, raster_info=raster_info)
|
|
77
|
+
|
|
78
|
+
ds_channel_index = [p.name.lower() for p in sph.mds_polarizations].index(channel_id.split("_")[-1])
|
|
79
|
+
for ds_id, main_processing_params in enumerate(main_params_dataset):
|
|
80
|
+
if not ds_id == ds_channel_index:
|
|
81
|
+
continue
|
|
82
|
+
|
|
83
|
+
burst_info = support.ASARBurstInfo(
|
|
84
|
+
num=1,
|
|
85
|
+
lines_per_burst=raster_info.lines.length,
|
|
86
|
+
samples_per_burst=raster_info.samples.length,
|
|
87
|
+
azimuth_start_times=np.array([raster_info.lines.start]),
|
|
88
|
+
range_start_times=np.array([raster_info.samples.start]),
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
dataset_info = support.dataset_info_from_record(
|
|
92
|
+
product_type=product_type,
|
|
93
|
+
acquisition_mode=acquisition_mode,
|
|
94
|
+
main_params_record=main_processing_params,
|
|
95
|
+
mph=mph,
|
|
96
|
+
sph=sph,
|
|
97
|
+
)
|
|
98
|
+
sampling_constants = support.sampling_constants_from_record(main_params_record=main_processing_params)
|
|
99
|
+
swath_info = support.ASARSwathInfo.from_record(main_params_record=main_processing_params)
|
|
100
|
+
general_info = support.ASARGeneralChannelInfo(
|
|
101
|
+
product_name=mph.product,
|
|
102
|
+
channel_id=channel_id,
|
|
103
|
+
swath=sph.swath,
|
|
104
|
+
product_type=product_type,
|
|
105
|
+
polarization=sph.mds_polarizations[ds_id],
|
|
106
|
+
projection=projection,
|
|
107
|
+
acquisition_mode=acquisition_mode,
|
|
108
|
+
acquisition_mode_std=acquisition_mode_std,
|
|
109
|
+
orbit_direction=sph.orbit_direction,
|
|
110
|
+
signal_frequency=dataset_info.fc_hz,
|
|
111
|
+
acq_start_time=mph.sensing_start,
|
|
112
|
+
acq_stop_time=mph.sensing_stop,
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
calibration_factor = support.get_calibration_factor(index=ds_id, main_params_record=main_processing_params)
|
|
116
|
+
|
|
117
|
+
pulse_info = support.pulse_info_from_record(main_params_record=main_processing_params)
|
|
118
|
+
|
|
119
|
+
product.close()
|
|
120
|
+
return support.ASARChannelMetadata(
|
|
121
|
+
general_info=general_info,
|
|
122
|
+
orbit=orbit,
|
|
123
|
+
image_calibration_factor=calibration_factor,
|
|
124
|
+
image_radiometric_quantity=SARRadiometricQuantity.BETA_NOUGHT,
|
|
125
|
+
raster_info=raster_info,
|
|
126
|
+
burst_info=burst_info,
|
|
127
|
+
dataset_info=dataset_info,
|
|
128
|
+
swath_info=swath_info,
|
|
129
|
+
sampling_constants=sampling_constants,
|
|
130
|
+
doppler_centroid_poly=doppler_centroid_poly,
|
|
131
|
+
doppler_rate_poly=doppler_rate_poly,
|
|
132
|
+
pulse=pulse_info,
|
|
133
|
+
coordinate_conversions=coordinate_conversion,
|
|
134
|
+
state_vectors=state_vectors,
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
def read_channel_data(raster_file: str | Path, block_to_read: list[int] | None = None, scaling_conversion: float = 1):
|
|
139
|
+
"""Reading ASAR data file from binary.
|
|
140
|
+
|
|
141
|
+
NOTE: the range axis is actually reversed. When reading data, the ROI range index should be provided relative to
|
|
142
|
+
the end of the raster range axis and not to the start, i.e. range_roi_index = samples - range_roi_index.
|
|
143
|
+
Data portion is the flip alongside range direction and returned to the user.
|
|
144
|
+
|
|
145
|
+
Parameters
|
|
146
|
+
----------
|
|
147
|
+
raster_file : str | Path
|
|
148
|
+
Path to .N1 binary file
|
|
149
|
+
block_to_read : list[int] | None, optional
|
|
150
|
+
data block to be read, to be specified as a list of 4 integers, in the form:
|
|
151
|
+
|
|
152
|
+
0. first line to be read
|
|
153
|
+
1. first sample to be read
|
|
154
|
+
2. total number of lines to be read
|
|
155
|
+
3. total number of samples to be read
|
|
156
|
+
|
|
157
|
+
by default None
|
|
158
|
+
scaling_conversion : float, optional
|
|
159
|
+
scaling conversion to be applied to the data read
|
|
160
|
+
|
|
161
|
+
Returns
|
|
162
|
+
-------
|
|
163
|
+
np.ndarray
|
|
164
|
+
numpy array containing the data read from raster file, with shape (lines, samples)
|
|
165
|
+
"""
|
|
166
|
+
|
|
167
|
+
product = epr.open(str(raster_file))
|
|
168
|
+
if "proc_data" not in product.get_band_names():
|
|
169
|
+
i_data = product.get_band("i")
|
|
170
|
+
q_data = product.get_band("q")
|
|
171
|
+
if block_to_read is not None:
|
|
172
|
+
i_data_area = i_data.read_as_array(
|
|
173
|
+
block_to_read[3], block_to_read[2], xoffset=block_to_read[1], yoffset=block_to_read[0]
|
|
174
|
+
)
|
|
175
|
+
q_data_area = q_data.read_as_array(
|
|
176
|
+
block_to_read[3], block_to_read[2], xoffset=block_to_read[1], yoffset=block_to_read[0]
|
|
177
|
+
)
|
|
178
|
+
else:
|
|
179
|
+
i_data_area = i_data.read_as_array()
|
|
180
|
+
q_data_area = q_data.read_as_array()
|
|
181
|
+
target_area = q_data_area + 1j * i_data_area
|
|
182
|
+
else:
|
|
183
|
+
proc_data = product.get_band("proc_data")
|
|
184
|
+
if block_to_read is not None:
|
|
185
|
+
target_area = proc_data.read_as_array(
|
|
186
|
+
block_to_read[3], block_to_read[2], xoffset=block_to_read[1], yoffset=block_to_read[0]
|
|
187
|
+
)
|
|
188
|
+
else:
|
|
189
|
+
target_area = proc_data.read_as_array()
|
|
190
|
+
|
|
191
|
+
product.close()
|
|
192
|
+
# NOTE: flipping along range due to reversed range raster direction
|
|
193
|
+
target_area = np.flip(target_area, axis=1)
|
|
194
|
+
|
|
195
|
+
return target_area * scaling_conversion
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
def open_product(pf_path: str | Path) -> support.ASARProduct:
|
|
199
|
+
"""Open a ASAR product.
|
|
200
|
+
|
|
201
|
+
Parameters
|
|
202
|
+
----------
|
|
203
|
+
pf_path : str | Path
|
|
204
|
+
Path to the ASAR product
|
|
205
|
+
|
|
206
|
+
Returns
|
|
207
|
+
-------
|
|
208
|
+
ASARProduct
|
|
209
|
+
ASARProduct object corresponding to the input ASAR product
|
|
210
|
+
"""
|
|
211
|
+
|
|
212
|
+
if not support.is_asar_product(product=pf_path):
|
|
213
|
+
raise support.InvalidASARProductError(f"{pf_path}")
|
|
214
|
+
|
|
215
|
+
return support.ASARProduct(path=pf_path)
|