1
- from __future__ import annotations
2
-
3
- import functools
4
1
import logging
5
- from typing import Any , Dict , Optional , Tuple , TypeVar , Union
2
+ from typing import Any , Callable , Dict , Optional , Tuple
6
3
from unittest import mock
7
4
8
- import attrs
9
5
import numpy as np
10
6
import rioxarray
11
7
import xarray as xr
12
- import xarray_sentinel
13
8
14
- from . import chunking , geocoding , orbit , radiometry , scene
9
+ from . import chunking , datamodel , geocoding , orbit , radiometry , scene
15
10
16
11
logger = logging .getLogger (__name__ )
17
12
18
13
19
- T_SarProduct = TypeVar ("T_SarProduct" , bound = "SarProduct" )
20
-
21
-
22
- @attrs .define (slots = False )
23
- class SarProduct :
24
- product_urlpath : str
25
- measurement_group : str
26
- measurement_chunks : int = 2048
27
- kwargs : Dict [str , Any ] = {}
28
-
29
- @functools .cached_property
30
- def measurement (self ) -> xr .Dataset :
31
- ds , self .kwargs = open_dataset_autodetect (
32
- self .product_urlpath ,
33
- group = self .measurement_group ,
34
- chunks = self .measurement_chunks ,
35
- ** self .kwargs ,
36
- )
37
- return ds
38
-
39
- @functools .cached_property
40
- def orbit (self ) -> xr .Dataset :
41
- ds , self .kwargs = open_dataset_autodetect (
42
- self .product_urlpath , group = f"{ self .measurement_group } /orbit" , ** self .kwargs
43
- )
44
- return ds
45
-
46
- @functools .cached_property
47
- def calibration (self ) -> xr .Dataset :
48
- ds , self .kwargs = open_dataset_autodetect (
49
- self .product_urlpath ,
50
- group = f"{ self .measurement_group } /calibration" ,
51
- ** self .kwargs ,
52
- )
53
- return ds
54
-
55
- @functools .cached_property
56
- def coordinate_conversion (self ) -> xr .Dataset :
57
- ds , self .kwargs = open_dataset_autodetect (
58
- self .product_urlpath ,
59
- group = f"{ self .measurement_group } /coordinate_conversion" ,
60
- ** self .kwargs ,
61
- )
62
- return ds
63
-
64
-
65
- def open_dataset_autodetect (
66
- product_urlpath : str ,
67
- group : Optional [str ] = None ,
68
- chunks : Optional [Union [int , Dict [str , int ]]] = None ,
69
- ** kwargs : Any ,
70
- ) -> Tuple [xr .Dataset , Dict [str , Any ]]:
71
- kwargs .setdefault ("engine" , "sentinel-1" )
72
- try :
73
- ds = xr .open_dataset (product_urlpath , group = group , chunks = chunks , ** kwargs )
74
- except FileNotFoundError :
75
- # re-try with Planetary Computer option
76
- kwargs [
77
- "override_product_files"
78
- ] = "{dirname}/{prefix}{swath}-{polarization}{ext}"
79
- ds = xr .open_dataset (product_urlpath , group = group , chunks = chunks , ** kwargs )
80
- return ds , kwargs
81
-
82
-
83
- def product_info (
84
- product_urlpath : str ,
85
- ** kwargs : Any ,
86
- ) -> Dict [str , Any ]:
87
- """Get information about the Sentinel-1 product."""
88
- root_ds = xr .open_dataset (
89
- product_urlpath , engine = "sentinel-1" , check_files_exist = True , ** kwargs
90
- )
91
-
92
- measurement_groups = [g for g in root_ds .attrs ["subgroups" ] if g .count ("/" ) == 1 ]
93
-
94
- gcp_group = measurement_groups [0 ] + "/gcp"
95
-
96
- gcp , kwargs = open_dataset_autodetect (product_urlpath , group = gcp_group , ** kwargs )
97
-
98
- bbox = [
99
- gcp .attrs ["geospatial_lon_min" ],
100
- gcp .attrs ["geospatial_lat_min" ],
101
- gcp .attrs ["geospatial_lon_max" ],
102
- gcp .attrs ["geospatial_lat_max" ],
103
- ]
104
-
105
- product_attrs = [
106
- "product_type" ,
107
- "mode" ,
108
- "swaths" ,
109
- "transmitter_receiver_polarisations" ,
110
- ]
111
- product_info = {attr_name : root_ds .attrs [attr_name ] for attr_name in product_attrs }
112
- product_info .update (
113
- {
114
- "measurement_groups" : measurement_groups ,
115
- "geospatial_bounds" : gcp .attrs ["geospatial_bounds" ],
116
- "geospatial_bbox" : bbox ,
117
- }
118
- )
119
-
120
- return product_info
121
-
122
-
123
14
def simulate_acquisition (
124
15
dem_ecef : xr .DataArray ,
125
16
position_ecef : xr .DataArray ,
126
- coordinate_conversion : Optional [xr .Dataset ] = None ,
17
+ slant_range_time_to_ground_range : Callable [
18
+ [xr .DataArray , xr .DataArray ], xr .DataArray
19
+ ],
127
20
correct_radiometry : Optional [str ] = None ,
128
21
) -> xr .Dataset :
129
22
"""Compute the image coordinates of the DEM given the satellite orbit."""
@@ -139,13 +32,12 @@ def simulate_acquisition(
139
32
140
33
acquisition ["slant_range_time" ] = slant_range_time
141
34
142
- if coordinate_conversion is not None :
143
- ground_range = xarray_sentinel .slant_range_time_to_ground_range (
144
- acquisition .azimuth_time ,
145
- slant_range_time ,
146
- coordinate_conversion ,
147
- )
148
- acquisition ["ground_range" ] = ground_range .drop_vars ("azimuth_time" )
35
+ maybe_ground_range = slant_range_time_to_ground_range (
36
+ acquisition .azimuth_time ,
37
+ slant_range_time ,
38
+ )
39
+ if maybe_ground_range is not None :
40
+ acquisition ["ground_range" ] = maybe_ground_range .drop_vars ("azimuth_time" )
149
41
if correct_radiometry is not None :
150
42
gamma_area = radiometry .compute_gamma_area (
151
43
dem_ecef , acquisition .dem_distance / slant_range
@@ -157,22 +49,8 @@ def simulate_acquisition(
157
49
return acquisition
158
50
159
51
160
- def calibrate_measurement (
161
- measurement_ds : xr .Dataset , beta_nought_lut : xr .DataArray
162
- ) -> xr .DataArray :
163
- measurement = measurement_ds .measurement
164
- if measurement .attrs ["product_type" ] == "SLC" and measurement .attrs ["mode" ] == "IW" :
165
- measurement = xarray_sentinel .mosaic_slc_iw (measurement )
166
-
167
- beta_nought = xarray_sentinel .calibrate_intensity (measurement , beta_nought_lut )
168
- beta_nought = beta_nought .drop_vars (["pixel" , "line" ])
169
-
170
- return beta_nought
171
-
172
-
173
52
def terrain_correction (
174
- product_urlpath : str ,
175
- measurement_group : str ,
53
+ product : datamodel .SarProduct ,
176
54
dem_urlpath : str ,
177
55
output_urlpath : Optional [str ] = "GTC.tif" ,
178
56
simulated_urlpath : Optional [str ] = None ,
@@ -181,17 +59,14 @@ def terrain_correction(
181
59
grouping_area_factor : Tuple [float , float ] = (3.0 , 3.0 ),
182
60
open_dem_raster_kwargs : Dict [str , Any ] = {},
183
61
chunks : Optional [int ] = 1024 ,
184
- measurement_chunks : int = 1024 ,
185
62
radiometry_chunks : int = 2048 ,
186
63
radiometry_bound : int = 128 ,
187
64
enable_dask_distributed : bool = False ,
188
65
client_kwargs : Dict [str , Any ] = {"processes" : False },
189
- ** kwargs : Any ,
190
66
) -> xr .DataArray :
191
67
"""Apply the terrain-correction to sentinel-1 SLC and GRD products.
192
68
193
- :param product_urlpath: input product path or url
194
- :param measurement_group: group of the measurement to be used, for example: "IW/VV"
69
+ :param product: SarProduct instance representing the input data
195
70
:param dem_urlpath: dem path or url
196
71
:param orbit_group: overrides the orbit group name
197
72
:param calibration_group: overrides the calibration group name
@@ -214,7 +89,6 @@ def terrain_correction(
214
89
Be aware that `grouping_area_factor` too high may degrade the final result
215
90
:param open_dem_raster_kwargs: additional keyword arguments passed on to ``xarray.open_dataset``
216
91
to open the `dem_urlpath`
217
- :param kwargs: additional keyword arguments passed on to ``xarray.open_dataset`` to open the `product_urlpath`
218
92
"""
219
93
# rioxarray must be imported explicitly or accesses to `.rio` may fail in dask
220
94
assert rioxarray .__version__
@@ -244,15 +118,11 @@ def terrain_correction(
244
118
dem_urlpath , chunks = chunks , ** open_dem_raster_kwargs
245
119
)
246
120
247
- logger .info (f"open data product { product_urlpath !r} " )
248
-
249
- product = SarProduct (
250
- product_urlpath , measurement_group , measurement_chunks , ** kwargs
251
- )
252
- product_type = product .measurement .attrs ["product_type" ]
253
121
allowed_product_types = ["GRD" , "SLC" ]
254
- if product_type not in allowed_product_types :
255
- raise ValueError (f"{ product_type = } . Must be one of: { allowed_product_types } " )
122
+ if product .product_type not in allowed_product_types :
123
+ raise ValueError (
124
+ f"{ product .product_type = } . Must be one of: { allowed_product_types } "
125
+ )
256
126
257
127
logger .info ("pre-process DEM" )
258
128
@@ -270,30 +140,26 @@ def terrain_correction(
270
140
"azimuth_time" : template_raster .astype ("datetime64[ns]" ),
271
141
}
272
142
)
273
- acquisition_kwargs = {
274
- "position_ecef" : product .orbit .position ,
275
- "correct_radiometry" : correct_radiometry ,
276
- }
277
- if product_type == "GRD" :
143
+ if product .product_type == "GRD" :
278
144
acquisition_template ["ground_range" ] = template_raster
279
- acquisition_kwargs ["coordinate_conversion" ] = product .coordinate_conversion
280
145
if correct_radiometry is not None :
281
146
acquisition_template ["gamma_area" ] = template_raster
282
147
283
148
acquisition = xr .map_blocks (
284
149
simulate_acquisition ,
285
150
dem_ecef ,
286
- kwargs = acquisition_kwargs ,
151
+ kwargs = {
152
+ "position_ecef" : product .state_vectors (),
153
+ "slant_range_time_to_ground_range" : product .slant_range_time_to_ground_range ,
154
+ "correct_radiometry" : correct_radiometry ,
155
+ },
287
156
template = acquisition_template ,
288
157
)
289
158
290
159
if correct_radiometry is not None :
291
160
logger .info ("simulate radiometry" )
292
161
293
- grid_parameters = radiometry .azimuth_slant_range_grid (
294
- product .measurement .attrs ,
295
- grouping_area_factor ,
296
- )
162
+ grid_parameters = product .grid_parameters (grouping_area_factor )
297
163
298
164
if correct_radiometry == "gamma_bilinear" :
299
165
gamma_weights = radiometry .gamma_weights_bilinear
@@ -339,13 +205,11 @@ def terrain_correction(
339
205
340
206
logger .info ("calibrate image" )
341
207
342
- beta_nought = calibrate_measurement (
343
- product .measurement , product .calibration .betaNought
344
- )
208
+ beta_nought = product .beta_nought ()
345
209
346
210
logger .info ("terrain-correct image" )
347
211
348
- if product_type == "GRD" :
212
+ if product . product_type == "GRD" :
349
213
interp_kwargs = {"ground_range" : acquisition .ground_range }
350
214
else :
351
215
interp_kwargs = {"slant_range_time" : acquisition .slant_range_time }
0 commit comments