grdwindinversion 0.2.3.post15__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.
- grdwindinversion/.github/ISSUE_TEMPLATE.md +15 -0
- grdwindinversion/.gitignore +106 -0
- grdwindinversion/.travis.yml +28 -0
- grdwindinversion/__init__.py +14 -0
- grdwindinversion/config_prod.yaml +45 -0
- grdwindinversion/config_prod_recal.yaml +45 -0
- grdwindinversion/data_config.yaml +5 -0
- grdwindinversion/inversion.py +1002 -0
- grdwindinversion/load_config.py +24 -0
- grdwindinversion/main.py +59 -0
- grdwindinversion/streaks.py +79 -0
- grdwindinversion/utils.py +42 -0
- grdwindinversion-0.2.3.post15.dist-info/AUTHORS.rst +14 -0
- grdwindinversion-0.2.3.post15.dist-info/LICENSE +22 -0
- grdwindinversion-0.2.3.post15.dist-info/METADATA +80 -0
- grdwindinversion-0.2.3.post15.dist-info/RECORD +19 -0
- grdwindinversion-0.2.3.post15.dist-info/WHEEL +5 -0
- grdwindinversion-0.2.3.post15.dist-info/entry_points.txt +2 -0
- grdwindinversion-0.2.3.post15.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,1002 @@
|
|
|
1
|
+
import traceback
|
|
2
|
+
|
|
3
|
+
import xsar
|
|
4
|
+
import xsarsea
|
|
5
|
+
from xsarsea import windspeed
|
|
6
|
+
import grdwindinversion
|
|
7
|
+
import xarray as xr
|
|
8
|
+
import numpy as np
|
|
9
|
+
import sys
|
|
10
|
+
import datetime
|
|
11
|
+
import os
|
|
12
|
+
import yaml
|
|
13
|
+
from scipy.ndimage import binary_dilation
|
|
14
|
+
|
|
15
|
+
import re
|
|
16
|
+
import string
|
|
17
|
+
import os
|
|
18
|
+
from grdwindinversion.streaks import get_streaks
|
|
19
|
+
from grdwindinversion.load_config import getConf
|
|
20
|
+
# optional debug messages
|
|
21
|
+
import logging
|
|
22
|
+
logging.basicConfig()
|
|
23
|
+
logging.getLogger('xsarsea.windspeed').setLevel(
|
|
24
|
+
logging.INFO) # or .setLevel(logging.INFO)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def getSensorMetaDataset(filename):
|
|
28
|
+
"""
|
|
29
|
+
Find the sensor name and the corresponding meta and dataset functions
|
|
30
|
+
|
|
31
|
+
Parameters
|
|
32
|
+
----------
|
|
33
|
+
filename : str
|
|
34
|
+
input filename
|
|
35
|
+
|
|
36
|
+
Returns
|
|
37
|
+
-------
|
|
38
|
+
tuple
|
|
39
|
+
sensor name, sensor long name, meta function, dataset function
|
|
40
|
+
"""
|
|
41
|
+
if ("S1A" in filename):
|
|
42
|
+
return "S1A", "SENTINEL-1 A", xsar.Sentinel1Meta, xsar.Sentinel1Dataset
|
|
43
|
+
elif ("S1B" in filename):
|
|
44
|
+
return "S1B", "SENTINEL-1 B", xsar.Sentinel1Meta, xsar.Sentinel1Dataset
|
|
45
|
+
elif ("RS2" in filename):
|
|
46
|
+
return "RS2", "RADARSAT-2", xsar.RadarSat2Meta, xsar.RadarSat2Dataset
|
|
47
|
+
elif ("RCM" in filename):
|
|
48
|
+
return "RCM", "RADARSAT Constellation", xsar.RcmMeta, xsar.RcmDataset
|
|
49
|
+
else:
|
|
50
|
+
raise ValueError("must be S1A|S1B|RS2|RCM, got filename %s" % filename)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def getOutputName2(input_file, outdir, sensor, meta, subdir=True):
|
|
54
|
+
"""
|
|
55
|
+
Create output filename for L2-GRD product
|
|
56
|
+
|
|
57
|
+
Parameters
|
|
58
|
+
----------
|
|
59
|
+
input_file : str
|
|
60
|
+
input filename
|
|
61
|
+
outdir : str
|
|
62
|
+
output folder
|
|
63
|
+
sensor : str
|
|
64
|
+
sensor name
|
|
65
|
+
meta : obj `xsar.BaseMeta` (one of the supported SAR mission)
|
|
66
|
+
meta object
|
|
67
|
+
|
|
68
|
+
Returns
|
|
69
|
+
-------
|
|
70
|
+
outfile : str
|
|
71
|
+
output filename
|
|
72
|
+
"""
|
|
73
|
+
basename = os.path.basename(input_file)
|
|
74
|
+
basename_match = basename
|
|
75
|
+
meta_start_date = meta.start_date.split(".")[0].replace(
|
|
76
|
+
"-", "").replace(":", "").replace(" ", "t").replace("Z", "")
|
|
77
|
+
meta_stop_date = meta.stop_date.split(".")[0].replace(
|
|
78
|
+
"-", "").replace(":", "").replace(" ", "t").replace("Z", "")
|
|
79
|
+
|
|
80
|
+
if sensor == 'S1A' or sensor == 'S1B':
|
|
81
|
+
regex = re.compile(
|
|
82
|
+
"(...)_(..)_(...)(.)_(.)(.)(..)_(........T......)_(........T......)_(......)_(......)_(....).SAFE")
|
|
83
|
+
template = string.Template(
|
|
84
|
+
"${MISSIONID}_${BEAM}_${PRODUCT}${RESOLUTION}_${LEVEL}${CLASS}${POL}_${STARTDATE}_${STOPDATE}_${ORBIT}_${TAKEID}_${PRODID}.SAFE")
|
|
85
|
+
match = regex.match(basename_match)
|
|
86
|
+
MISSIONID, BEAM, PRODUCT, RESOLUTION, LEVEL, CLASS, POL, STARTDATE, STOPDATE, ORBIT, TAKEID, PRODID = match.groups()
|
|
87
|
+
new_format = f"{MISSIONID.lower()}-{BEAM.lower()}-owi-xx-{STARTDATE.lower()}-{STOPDATE.lower()}-{ORBIT}-{TAKEID}.nc"
|
|
88
|
+
elif sensor == 'RS2':
|
|
89
|
+
regex = re.compile(
|
|
90
|
+
"(RS2)_OK([0-9]+)_PK([0-9]+)_DK([0-9]+)_(....)_(........)_(......)_(.._?.?.?)_(S.F)")
|
|
91
|
+
template = string.Template(
|
|
92
|
+
"${MISSIONID}_OK${DATA1}_PK${DATA2}_DK${DATA3}_${DATA4}_${DATE}_${TIME}_${POLARIZATION}_${LAST}")
|
|
93
|
+
match = regex.match(basename_match)
|
|
94
|
+
MISSIONID, DATA1, DATA2, DATA3, DATA4, DATE, TIME, POLARIZATION, LAST = match.groups()
|
|
95
|
+
new_format = f"{MISSIONID.lower()}--owi-xx-{meta_start_date.lower()}-{meta_stop_date.lower()}-_____-_____.nc"
|
|
96
|
+
elif sensor == 'RCM':
|
|
97
|
+
regex = re.compile(
|
|
98
|
+
"([A-Z0-9]+)_OK([0-9]+)_PK([0-9]+)_(.*?)_(.*?)_(.*?)_(.*?)_(.*?)_(.*?)_(.*?)")
|
|
99
|
+
template = string.Template(
|
|
100
|
+
"${MISSIONID}_OK${DATA1}_PK${DATA2}_${DATA3}_${DATA4}_${DATE}_${TIME}_${POLARIZATION1}_${POLARIZATION2}_${PRODUCT}")
|
|
101
|
+
match = regex.match(basename_match)
|
|
102
|
+
MISSIONID, DATA1, DATA2, DATA3, DATA4, DATE, TIME, POLARIZATION1, POLARIZATION2, LAST = match.groups()
|
|
103
|
+
new_format = f"{MISSIONID.lower()}--owi-xx-{meta_start_date.lower()}-{meta_stop_date.lower()}-_____-_____.nc"
|
|
104
|
+
else:
|
|
105
|
+
raise ValueError(
|
|
106
|
+
"sensor must be S1A|S1B|RS2|RCM, got sensor %s" % sensor)
|
|
107
|
+
|
|
108
|
+
if subdir:
|
|
109
|
+
out_file = os.path.join(outdir, basename, new_format)
|
|
110
|
+
else:
|
|
111
|
+
out_file = os.path.join(outdir, new_format)
|
|
112
|
+
return out_file
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def getAncillary(meta, ancillary_name='ecmwf'):
|
|
116
|
+
"""
|
|
117
|
+
Map ancillary wind from ECMWF or ERA5.
|
|
118
|
+
This function is used to check if the model files are available and to map the model to the SAR data.
|
|
119
|
+
|
|
120
|
+
Parameters
|
|
121
|
+
----------
|
|
122
|
+
meta: obj `xsar.BaseMeta` (one of the supported SAR mission)
|
|
123
|
+
|
|
124
|
+
Returns
|
|
125
|
+
-------
|
|
126
|
+
dict
|
|
127
|
+
map model to SAR data
|
|
128
|
+
"""
|
|
129
|
+
|
|
130
|
+
if ancillary_name == 'ecmwf':
|
|
131
|
+
|
|
132
|
+
logging.debug('conf: %s', getConf())
|
|
133
|
+
ec01 = getConf()['ecmwf_0100_1h']
|
|
134
|
+
ec0125 = getConf()['ecmwf_0125_1h']
|
|
135
|
+
logging.debug('ec01 : %s', ec01)
|
|
136
|
+
meta.set_raster('ecmwf_0100_1h', ec01)
|
|
137
|
+
meta.set_raster('ecmwf_0125_1h', ec0125)
|
|
138
|
+
|
|
139
|
+
map_model = None
|
|
140
|
+
# only keep best ecmwf (FIXME: it's hacky, and xsar should provide a better method to handle this)
|
|
141
|
+
for ecmwf_name in ['ecmwf_0125_1h', 'ecmwf_0100_1h']:
|
|
142
|
+
ecmwf_infos = meta.rasters.loc[ecmwf_name]
|
|
143
|
+
try:
|
|
144
|
+
ecmwf_file = ecmwf_infos['get_function'](ecmwf_infos['resource'],
|
|
145
|
+
date=datetime.datetime.strptime(meta.start_date,
|
|
146
|
+
'%Y-%m-%d %H:%M:%S.%f'))[1]
|
|
147
|
+
# temporary for RCM issue https://github.com/umr-lops/xarray-safe-rcm/issues/34
|
|
148
|
+
except Exception as e:
|
|
149
|
+
ecmwf_file = ecmwf_infos['get_function'](ecmwf_infos['resource'],
|
|
150
|
+
date=datetime.datetime.strptime(meta.start_date,
|
|
151
|
+
'%Y-%m-%d %H:%M:%S'))[1]
|
|
152
|
+
if not os.path.isfile(ecmwf_file):
|
|
153
|
+
# temporary
|
|
154
|
+
# if repro does not exist we look at not repro folder (only one will exist after)
|
|
155
|
+
"""
|
|
156
|
+
if ecmwf_name == "ecmwf_0100_1h":
|
|
157
|
+
ecmwf_infos['resource'] = ecmwf_infos['resource'].replace(
|
|
158
|
+
"netcdf_light_REPRO_tree", "netcdf_light")
|
|
159
|
+
try:
|
|
160
|
+
ecmwf_file = ecmwf_infos['get_function'](ecmwf_infos['resource'],
|
|
161
|
+
date=datetime.datetime.strptime(meta.start_date,
|
|
162
|
+
'%Y-%m-%d %H:%M:%S.%f'))[1]
|
|
163
|
+
except Exception as e:
|
|
164
|
+
ecmwf_file = ecmwf_infos['get_function'](ecmwf_infos['resource'],
|
|
165
|
+
date=datetime.datetime.strptime(meta.start_date,
|
|
166
|
+
'%Y-%m-%d %H:%M:%S'))[1]
|
|
167
|
+
|
|
168
|
+
if not os.path.isfile(ecmwf_file):
|
|
169
|
+
meta.rasters = meta.rasters.drop([ecmwf_name])
|
|
170
|
+
else:
|
|
171
|
+
map_model = {'%s_%s' % (ecmwf_name, uv): 'model_%s' % uv for uv in [
|
|
172
|
+
'U10', 'V10']}
|
|
173
|
+
|
|
174
|
+
else:
|
|
175
|
+
"""
|
|
176
|
+
meta.rasters = meta.rasters.drop([ecmwf_name])
|
|
177
|
+
else:
|
|
178
|
+
map_model = {'%s_%s' % (ecmwf_name, uv): 'model_%s' %
|
|
179
|
+
uv for uv in ['U10', 'V10']}
|
|
180
|
+
|
|
181
|
+
return map_model
|
|
182
|
+
|
|
183
|
+
elif ancillary_name == 'era5':
|
|
184
|
+
era5_name = "era5_0250_1h"
|
|
185
|
+
logging.debug('conf: %s', getConf())
|
|
186
|
+
era0250 = getConf()[era5_name]
|
|
187
|
+
logging.debug('%s : %s', (era5_name, era0250))
|
|
188
|
+
meta.set_raster(era5_name, era0250)
|
|
189
|
+
|
|
190
|
+
era5_infos = meta.rasters.loc[era5_name]
|
|
191
|
+
try:
|
|
192
|
+
era5_file = era5_infos['get_function'](era5_infos['resource'],
|
|
193
|
+
date=datetime.datetime.strptime(meta.start_date,
|
|
194
|
+
'%Y-%m-%d %H:%M:%S.%f'))[1]
|
|
195
|
+
except Exception as e:
|
|
196
|
+
era5_file = era5_infos['get_function'](era5_infos['resource'],
|
|
197
|
+
date=datetime.datetime.strptime(meta.start_date,
|
|
198
|
+
'%Y-%m-%d %H:%M:%S'))[1]
|
|
199
|
+
if not os.path.isfile(era5_file):
|
|
200
|
+
raise ValueError(f"era5 file {era5_file} not found")
|
|
201
|
+
|
|
202
|
+
map_model = {'%s_%s' % (era5_name, uv): 'model_%s' %
|
|
203
|
+
uv for uv in ['U10', 'V10']}
|
|
204
|
+
return map_model
|
|
205
|
+
|
|
206
|
+
else:
|
|
207
|
+
raise ValueError("ancillary_name must be ecmwf/era5, got %s" %
|
|
208
|
+
ancillary_name)
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
def inverse(dual_pol, inc, sigma0, sigma0_dual, ancillary_wind, dsig_cr, model_vv, model_vh, **kwargs):
|
|
212
|
+
"""
|
|
213
|
+
Invert sigma0 to retrieve wind using model (lut or gmf).
|
|
214
|
+
|
|
215
|
+
Parameters
|
|
216
|
+
----------
|
|
217
|
+
dual_pol: bool
|
|
218
|
+
True if dualpol, False if singlepol
|
|
219
|
+
inc: xarray.DataArray
|
|
220
|
+
incidence angle
|
|
221
|
+
sigma0: xarray.DataArray
|
|
222
|
+
sigma0 to be inverted
|
|
223
|
+
sigma0_dual: xarray.DataArray
|
|
224
|
+
sigma0 to be inverted for dualpol
|
|
225
|
+
ancillary_wind=: xarray.DataArray (numpy.complex28)
|
|
226
|
+
ancillary wind
|
|
227
|
+
| (for example ecmwf winds), in **GMF convention** (-np.conj included),
|
|
228
|
+
dsig_cr=: float or xarray.DataArray
|
|
229
|
+
parameters used for
|
|
230
|
+
|
|
231
|
+
| `Jsig_cr=((sigma0_gmf - sigma0) / dsig_cr) ** 2`
|
|
232
|
+
model_vv=: str
|
|
233
|
+
model to use for VV or HH polarization.
|
|
234
|
+
model_vh=: str
|
|
235
|
+
model to use for VH or HV polarization.
|
|
236
|
+
|
|
237
|
+
Returns
|
|
238
|
+
-------
|
|
239
|
+
xarray.DataArray or tuple
|
|
240
|
+
inverted wind in **gmf convention** .
|
|
241
|
+
|
|
242
|
+
See Also
|
|
243
|
+
--------
|
|
244
|
+
xsarsea documentation
|
|
245
|
+
https://cyclobs.ifremer.fr/static/sarwing_datarmor/xsarsea/examples/windspeed_inversion.html
|
|
246
|
+
"""
|
|
247
|
+
logging.debug("inversion")
|
|
248
|
+
|
|
249
|
+
list_mods = windspeed.available_models().index.tolist(
|
|
250
|
+
) + windspeed.available_models().alias.tolist() + [None]
|
|
251
|
+
if model_vv not in list_mods:
|
|
252
|
+
raise ValueError(
|
|
253
|
+
f"model_vv {model_vv} not in windspeed.available_models() : not going further")
|
|
254
|
+
if model_vh not in list_mods:
|
|
255
|
+
raise ValueError(
|
|
256
|
+
f"model_vh {model_vh} not in windspeed.available_models() : not going further")
|
|
257
|
+
|
|
258
|
+
winds = windspeed.invert_from_model(
|
|
259
|
+
inc,
|
|
260
|
+
sigma0,
|
|
261
|
+
sigma0_dual,
|
|
262
|
+
ancillary_wind=ancillary_wind,
|
|
263
|
+
dsig_cr=dsig_cr,
|
|
264
|
+
model=(model_vv, model_vh),
|
|
265
|
+
**kwargs)
|
|
266
|
+
|
|
267
|
+
if dual_pol:
|
|
268
|
+
wind_co, wind_dual = winds
|
|
269
|
+
|
|
270
|
+
wind_cross = windspeed.invert_from_model(
|
|
271
|
+
inc.values,
|
|
272
|
+
sigma0_dual.values,
|
|
273
|
+
dsig_cr=dsig_cr.values,
|
|
274
|
+
model=model_vh,
|
|
275
|
+
**kwargs)
|
|
276
|
+
|
|
277
|
+
return wind_co, wind_dual, wind_cross
|
|
278
|
+
else:
|
|
279
|
+
wind_co = winds
|
|
280
|
+
|
|
281
|
+
return wind_co, None, None
|
|
282
|
+
|
|
283
|
+
|
|
284
|
+
def makeL2asOwi(xr_dataset, dual_pol, copol, crosspol, add_streaks):
|
|
285
|
+
"""
|
|
286
|
+
Rename xr_dataset variables and attributes to match naming convention.
|
|
287
|
+
|
|
288
|
+
Parameters
|
|
289
|
+
----------
|
|
290
|
+
xr_dataset: xarray.Dataset
|
|
291
|
+
dataset to rename
|
|
292
|
+
dual_pol: bool
|
|
293
|
+
True if dualpol, False if singlepol
|
|
294
|
+
copol: str
|
|
295
|
+
copolarization name
|
|
296
|
+
crosspol: str
|
|
297
|
+
crosspolarization name
|
|
298
|
+
|
|
299
|
+
Returns
|
|
300
|
+
-------
|
|
301
|
+
xarray.Dataset
|
|
302
|
+
final dataset
|
|
303
|
+
dict
|
|
304
|
+
encoding dict
|
|
305
|
+
|
|
306
|
+
See Also
|
|
307
|
+
--------
|
|
308
|
+
"""
|
|
309
|
+
|
|
310
|
+
xr_dataset = xr_dataset.rename({
|
|
311
|
+
'longitude': 'owiLon',
|
|
312
|
+
'latitude': 'owiLat',
|
|
313
|
+
'incidence': 'owiIncidenceAngle',
|
|
314
|
+
'elevation': 'owiElevationAngle',
|
|
315
|
+
'ground_heading': 'owiHeading',
|
|
316
|
+
'land_mask': 'owiLandFlag',
|
|
317
|
+
'mask': 'owiMask',
|
|
318
|
+
'windspeed_co': 'owiWindSpeed_co',
|
|
319
|
+
'winddir_co': 'owiWindDirection_co',
|
|
320
|
+
'ancillary_wind_speed': 'owiAncillaryWindSpeed',
|
|
321
|
+
'ancillary_wind_direction': 'owiAncillaryWindDirection',
|
|
322
|
+
'sigma0_detrend': 'owiNrcs_detrend'
|
|
323
|
+
})
|
|
324
|
+
|
|
325
|
+
if "offboresight" in xr_dataset:
|
|
326
|
+
xr_dataset = xr_dataset.rename(
|
|
327
|
+
{"offboresight": "owiOffboresightAngle"})
|
|
328
|
+
|
|
329
|
+
xr_dataset.owiLon.attrs["units"] = "degrees_east"
|
|
330
|
+
xr_dataset.owiLon.attrs["long_name"] = "Longitude at wind cell center"
|
|
331
|
+
xr_dataset.owiLon.attrs["standard_name"] = "longitude"
|
|
332
|
+
|
|
333
|
+
xr_dataset.owiLat.attrs["units"] = "degrees_north"
|
|
334
|
+
xr_dataset.owiLat.attrs["long_name"] = "Latitude at wind cell center"
|
|
335
|
+
xr_dataset.owiLat.attrs["standard_name"] = "latitude"
|
|
336
|
+
|
|
337
|
+
xr_dataset.owiIncidenceAngle.attrs["units"] = "degrees"
|
|
338
|
+
xr_dataset.owiIncidenceAngle.attrs["long_name"] = "Incidence angle at wind cell center"
|
|
339
|
+
xr_dataset.owiIncidenceAngle.attrs["standard_name"] = "incidence"
|
|
340
|
+
|
|
341
|
+
xr_dataset.owiElevationAngle.attrs["units"] = "degrees"
|
|
342
|
+
xr_dataset.owiElevationAngle.attrs["long_name"] = "Elevation angle at wind cell center"
|
|
343
|
+
xr_dataset.owiElevationAngle.attrs["standard_name"] = "elevation"
|
|
344
|
+
|
|
345
|
+
xr_dataset['owiNrcs'] = xr_dataset['sigma0_ocean'].sel(pol=copol)
|
|
346
|
+
xr_dataset.owiNrcs.attrs = xr_dataset.sigma0_ocean.attrs
|
|
347
|
+
xr_dataset.owiNrcs.attrs['units'] = 'm^2 / m^2'
|
|
348
|
+
xr_dataset.owiNrcs.attrs['long_name'] = 'Normalized Radar Cross Section'
|
|
349
|
+
xr_dataset.owiNrcs.attrs['definition'] = 'owiNrcs_no_noise_correction - owiNesz'
|
|
350
|
+
|
|
351
|
+
xr_dataset['owiMask_Nrcs'] = xr_dataset['sigma0_mask'].sel(pol=copol)
|
|
352
|
+
xr_dataset.owiMask_Nrcs.attrs = xr_dataset.sigma0_mask.attrs
|
|
353
|
+
|
|
354
|
+
# NESZ & DSIG
|
|
355
|
+
xr_dataset = xr_dataset.assign(
|
|
356
|
+
owiNesz=(['line', 'sample'], xr_dataset.nesz.sel(pol=copol).values))
|
|
357
|
+
xr_dataset.owiNesz.attrs['units'] = 'm^2 / m^2'
|
|
358
|
+
xr_dataset.owiNesz.attrs['long_name'] = 'Noise Equivalent SigmaNaught'
|
|
359
|
+
|
|
360
|
+
xr_dataset['owiNrcs_no_noise_correction'] = xr_dataset['sigma0_ocean_raw'].sel(
|
|
361
|
+
pol=copol)
|
|
362
|
+
xr_dataset.owiNrcs_no_noise_correction.attrs = xr_dataset.sigma0_ocean_raw.attrs
|
|
363
|
+
xr_dataset.owiNrcs_no_noise_correction.attrs['units'] = 'm^2 / m^2'
|
|
364
|
+
xr_dataset.owiNrcs_no_noise_correction.attrs[
|
|
365
|
+
'long_name'] = 'Normalized Radar Cross Section ; no noise correction applied'
|
|
366
|
+
xr_dataset.owiNrcs_no_noise_correction.attrs[
|
|
367
|
+
'comment'] = 'owiNrcs_no_noise_correction ; no recalibration'
|
|
368
|
+
|
|
369
|
+
if 'swath_number' in xr_dataset:
|
|
370
|
+
xr_dataset = xr_dataset.rename({
|
|
371
|
+
'swath_number': 'owiSwathNumber',
|
|
372
|
+
'swath_number_flag': 'owiSwathNumberFlag'
|
|
373
|
+
})
|
|
374
|
+
|
|
375
|
+
xr_dataset["owiSwathNumber"].attrs["standart_name"] = "swath number"
|
|
376
|
+
|
|
377
|
+
# sigma0_raw__corrected cross
|
|
378
|
+
if "sigma0_raw__corrected" in xr_dataset:
|
|
379
|
+
xr_dataset['owiNrcs_no_noise_correction_recalibrated'] = xr_dataset['sigma0_raw__corrected'].sel(
|
|
380
|
+
pol=copol)
|
|
381
|
+
xr_dataset.owiNrcs_no_noise_correction_recalibrated.attrs = xr_dataset.sigma0_raw__corrected.attrs
|
|
382
|
+
xr_dataset.owiNrcs_no_noise_correction_recalibrated.attrs['units'] = 'm^2 / m^2'
|
|
383
|
+
xr_dataset.owiNrcs_no_noise_correction_recalibrated.attrs[
|
|
384
|
+
'long_name'] = 'Normalized Radar Cross Section, no noise correction applied'
|
|
385
|
+
xr_dataset.owiNrcs_no_noise_correction_recalibrated.attrs[
|
|
386
|
+
'comment'] = 'owiNrcs_no_noise_correction ; recalibrated with kersten method'
|
|
387
|
+
|
|
388
|
+
xr_dataset.owiNrcs.attrs['definition'] = 'owiNrcs_no_noise_correction_recalibrated - owiNesz'
|
|
389
|
+
|
|
390
|
+
if dual_pol:
|
|
391
|
+
|
|
392
|
+
xr_dataset = xr_dataset.rename({
|
|
393
|
+
'dsig_cross': 'owiDsig_cross',
|
|
394
|
+
'nesz_cross_final': 'owiNesz_cross_final',
|
|
395
|
+
'winddir_cross': 'owiWindDirection_cross',
|
|
396
|
+
'winddir_dual': 'owiWindDirection',
|
|
397
|
+
'windspeed_cross': 'owiWindSpeed_cross',
|
|
398
|
+
'windspeed_dual': 'owiWindSpeed',
|
|
399
|
+
'sigma0_detrend_cross': 'owiNrcs_detrend_cross'
|
|
400
|
+
})
|
|
401
|
+
# nrcs cross
|
|
402
|
+
xr_dataset['owiNrcs_cross'] = xr_dataset['sigma0_ocean'].sel(
|
|
403
|
+
pol=crosspol)
|
|
404
|
+
|
|
405
|
+
xr_dataset.owiNrcs_cross.attrs['units'] = 'm^2 / m^2'
|
|
406
|
+
xr_dataset.owiNrcs_cross.attrs['long_name'] = 'Normalized Radar Cross Section'
|
|
407
|
+
xr_dataset.owiNrcs_cross.attrs['definition'] = 'owiNrcs_cross_no_noise_correction - owiNesz_cross'
|
|
408
|
+
|
|
409
|
+
xr_dataset['owiMask_Nrcs_cross'] = xr_dataset['sigma0_mask'].sel(
|
|
410
|
+
pol=crosspol)
|
|
411
|
+
xr_dataset.owiMask_Nrcs_cross.attrs = xr_dataset.sigma0_mask.attrs
|
|
412
|
+
|
|
413
|
+
# nesz cross
|
|
414
|
+
xr_dataset = xr_dataset.assign(owiNesz_cross=(
|
|
415
|
+
['line', 'sample'], xr_dataset.nesz.sel(pol=crosspol).values)) # no flattening
|
|
416
|
+
xr_dataset.owiNesz_cross.attrs['units'] = 'm^2 / m^2'
|
|
417
|
+
xr_dataset.owiNesz_cross.attrs['long_name'] = 'Noise Equivalent SigmaNaught'
|
|
418
|
+
|
|
419
|
+
xr_dataset['owiNrcs_cross_no_noise_correction'] = xr_dataset['sigma0_ocean_raw'].sel(
|
|
420
|
+
pol=crosspol)
|
|
421
|
+
|
|
422
|
+
xr_dataset.owiNrcs_cross_no_noise_correction.attrs['units'] = 'm^2 / m^2'
|
|
423
|
+
xr_dataset.owiNrcs_cross_no_noise_correction.attrs[
|
|
424
|
+
'long_name'] = 'Normalized Radar Cross Section, no noise correction applied'
|
|
425
|
+
|
|
426
|
+
# sigma0_raw__corrected cross
|
|
427
|
+
if "sigma0_raw__corrected" in xr_dataset:
|
|
428
|
+
xr_dataset['owiNrcs_cross_no_noise_correction_recalibrated'] = xr_dataset['sigma0_raw__corrected'].sel(
|
|
429
|
+
pol=crosspol)
|
|
430
|
+
xr_dataset.owiNrcs_cross_no_noise_correction_recalibrated.attrs = xr_dataset.sigma0_raw__corrected.attrs
|
|
431
|
+
xr_dataset.owiNrcs_cross_no_noise_correction_recalibrated.attrs['units'] = 'm^2 / m^2'
|
|
432
|
+
xr_dataset.owiNrcs_cross_no_noise_correction_recalibrated.attrs[
|
|
433
|
+
'long_name'] = 'Normalized Radar Cross Section ; no noise correction applied'
|
|
434
|
+
xr_dataset.owiNrcs_cross_no_noise_correction_recalibrated.attrs[
|
|
435
|
+
'comment'] = 'owiNrcs_cross_no_noise_correction ; recalibrated with kersten method'
|
|
436
|
+
|
|
437
|
+
xr_dataset.owiNrcs_cross.attrs['definition'] = 'owiNrcs_cross_no_noise_correction_recalibrated - owiNesz_cross'
|
|
438
|
+
|
|
439
|
+
if add_streaks:
|
|
440
|
+
xr_dataset = xr_dataset.rename({
|
|
441
|
+
'streaks_direction': 'owiStreaksDirection',
|
|
442
|
+
})
|
|
443
|
+
|
|
444
|
+
# other variables
|
|
445
|
+
|
|
446
|
+
xr_dataset['owiWindQuality'] = xr.full_like(xr_dataset.owiNrcs, 0)
|
|
447
|
+
xr_dataset['owiWindQuality'].attrs[
|
|
448
|
+
'long_name'] = "Quality flag taking into account the consistency_between_wind_inverted_and_NRCS_and_Doppler_measured"
|
|
449
|
+
xr_dataset['owiWindQuality'].attrs['valid_range'] = np.array([0, 3])
|
|
450
|
+
xr_dataset['owiWindQuality'].attrs['flag_values'] = np.array([
|
|
451
|
+
0, 1, 2, 3])
|
|
452
|
+
xr_dataset['owiWindQuality'].attrs['flag_meanings'] = "good medium low poor"
|
|
453
|
+
xr_dataset['owiWindQuality'].attrs['comment'] = 'NOT COMPUTED YET'
|
|
454
|
+
|
|
455
|
+
xr_dataset['owiWindFilter'] = xr.full_like(xr_dataset.owiNrcs, 0)
|
|
456
|
+
xr_dataset['owiWindFilter'].attrs['long_name'] = "Quality flag taking into account the local heterogeneity"
|
|
457
|
+
xr_dataset['owiWindFilter'].attrs['valid_range'] = np.array([0, 3])
|
|
458
|
+
xr_dataset['owiWindFilter'].attrs['flag_values'] = np.array([
|
|
459
|
+
0, 1, 2, 3])
|
|
460
|
+
xr_dataset['owiWindFilter'].attrs[
|
|
461
|
+
'flag_meanings'] = "homogeneous_NRCS, heterogeneous_from_co-polarization_NRCS, heterogeneous_from_cross-polarization_NRCS, heterogeneous_from_dual-polarization_NRCS"
|
|
462
|
+
xr_dataset['owiWindFilter'].attrs['comment'] = 'NOT COMPUTED YET'
|
|
463
|
+
|
|
464
|
+
xr_dataset = xr_dataset.rename(
|
|
465
|
+
{"line": "owiAzSize", "sample": "owiRaSize"})
|
|
466
|
+
|
|
467
|
+
xr_dataset = xr_dataset.drop_vars(
|
|
468
|
+
['sigma0_ocean', 'sigma0', 'sigma0_ocean_raw', 'sigma0_raw', 'ancillary_wind', 'nesz', 'spatial_ref'])
|
|
469
|
+
if 'sigma0_raw__corrected' in xr_dataset:
|
|
470
|
+
xr_dataset = xr_dataset.drop_vars(["sigma0_raw__corrected"])
|
|
471
|
+
xr_dataset = xr_dataset.drop_dims(['pol'])
|
|
472
|
+
|
|
473
|
+
xr_dataset.compute()
|
|
474
|
+
|
|
475
|
+
table_fillValue = {
|
|
476
|
+
"owiWindQuality": -1,
|
|
477
|
+
"owiHeading": 9999.99,
|
|
478
|
+
"owiWindDirection_IPF": -9999.0,
|
|
479
|
+
"owiWindSpeed_IPF": -9999.0,
|
|
480
|
+
"owiWindDirection": -9999.0,
|
|
481
|
+
"owiPBright": 999.99,
|
|
482
|
+
"owiWindFilter": -1,
|
|
483
|
+
"owiWindSpeed": -9999.0,
|
|
484
|
+
"owiWindSpeed_co": -9999.0,
|
|
485
|
+
"owiWindSpeed_cross": -9999.0,
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
encoding = {}
|
|
489
|
+
for var in list(set(xr_dataset.coords.keys()) | set(xr_dataset.keys())):
|
|
490
|
+
encoding[var] = {}
|
|
491
|
+
try:
|
|
492
|
+
encoding[var].update({'_FillValue': table_fillValue[var]})
|
|
493
|
+
except:
|
|
494
|
+
if (var in ["owiWindSpeed_co", "owiWindSpeed_cross", "owiWindSpeed"]):
|
|
495
|
+
encoding[var].update({'_FillValue': -9999.0})
|
|
496
|
+
else:
|
|
497
|
+
encoding[var].update({'_FillValue': None})
|
|
498
|
+
|
|
499
|
+
return xr_dataset, encoding
|
|
500
|
+
|
|
501
|
+
|
|
502
|
+
def preprocess(filename, outdir, config_path, overwrite=False, add_streaks=False, resolution='1000m'):
|
|
503
|
+
"""
|
|
504
|
+
Main function to generate L2 product.
|
|
505
|
+
|
|
506
|
+
Parameters
|
|
507
|
+
----------
|
|
508
|
+
filename : str
|
|
509
|
+
input filename
|
|
510
|
+
outdir : str
|
|
511
|
+
output folder
|
|
512
|
+
config_path : str
|
|
513
|
+
configuration file path
|
|
514
|
+
overwrite : bool, optional
|
|
515
|
+
overwrite existing file
|
|
516
|
+
resolution : str, optional
|
|
517
|
+
working resolution
|
|
518
|
+
|
|
519
|
+
Returns
|
|
520
|
+
-------
|
|
521
|
+
xarray.Dataset
|
|
522
|
+
final dataset
|
|
523
|
+
"""
|
|
524
|
+
|
|
525
|
+
sensor, sensor_longname, fct_meta, fct_dataset = getSensorMetaDataset(
|
|
526
|
+
filename)
|
|
527
|
+
|
|
528
|
+
if os.path.exists(config_path):
|
|
529
|
+
with open(config_path, 'r') as file:
|
|
530
|
+
config_base = yaml.load(
|
|
531
|
+
file,
|
|
532
|
+
Loader=yaml.FullLoader
|
|
533
|
+
)
|
|
534
|
+
try:
|
|
535
|
+
config = config_base[sensor]
|
|
536
|
+
except Exception:
|
|
537
|
+
raise KeyError("sensor %s not in this config" % sensor)
|
|
538
|
+
else:
|
|
539
|
+
raise FileNotFoundError(
|
|
540
|
+
'config_path do not exists, got %s ' % config_path)
|
|
541
|
+
|
|
542
|
+
recalibration = config["recalibration"]
|
|
543
|
+
meta = fct_meta(filename)
|
|
544
|
+
|
|
545
|
+
no_subdir_cfg = config_base.get("no_subdir", False)
|
|
546
|
+
out_file = getOutputName2(filename, outdir, sensor,
|
|
547
|
+
meta, subdir=not no_subdir_cfg)
|
|
548
|
+
|
|
549
|
+
if os.path.exists(out_file) and overwrite is False:
|
|
550
|
+
raise FileExistsError("outfile %s already exists" % out_file)
|
|
551
|
+
|
|
552
|
+
ancillary_name = config["ancillary"]
|
|
553
|
+
map_model = getAncillary(meta, ancillary_name)
|
|
554
|
+
if map_model is None:
|
|
555
|
+
raise Exception(
|
|
556
|
+
f"the weather model is not set `map_model` is None -> you probably don't have access to {ancillary_name} archive")
|
|
557
|
+
|
|
558
|
+
try:
|
|
559
|
+
if ((recalibration) & ("SENTINEL" in sensor_longname)):
|
|
560
|
+
logging.info(
|
|
561
|
+
f'recalibration is {recalibration} : Kersten formula is applied')
|
|
562
|
+
xsar_dataset = fct_dataset(
|
|
563
|
+
meta, resolution=resolution, recalibration=recalibration)
|
|
564
|
+
xr_dataset = xsar_dataset.datatree['measurement'].to_dataset()
|
|
565
|
+
xr_dataset = xr_dataset.merge(xsar_dataset.datatree["recalibration"].to_dataset()[
|
|
566
|
+
['swath_number', 'swath_number_flag', 'sigma0_raw__corrected']])
|
|
567
|
+
|
|
568
|
+
else:
|
|
569
|
+
logging.info(
|
|
570
|
+
f'recalibration is {recalibration} : Kersten formula is not applied')
|
|
571
|
+
if ("SENTINEL" in sensor_longname):
|
|
572
|
+
xsar_dataset = fct_dataset(
|
|
573
|
+
meta, resolution=resolution, recalibration=recalibration)
|
|
574
|
+
xr_dataset = xsar_dataset.datatree['measurement'].to_dataset()
|
|
575
|
+
xr_dataset = xr_dataset.merge(xsar_dataset.datatree["recalibration"].to_dataset()[
|
|
576
|
+
['swath_number', 'swath_number_flag']])
|
|
577
|
+
|
|
578
|
+
else:
|
|
579
|
+
xsar_dataset = fct_dataset(meta, resolution=resolution)
|
|
580
|
+
xr_dataset = xsar_dataset.datatree['measurement'].to_dataset()
|
|
581
|
+
|
|
582
|
+
xr_dataset = xr_dataset.rename(map_model)
|
|
583
|
+
xr_dataset.attrs = xsar_dataset.dataset.attrs
|
|
584
|
+
|
|
585
|
+
except Exception as e:
|
|
586
|
+
logging.info('%s', traceback.format_exc())
|
|
587
|
+
logging.error(e)
|
|
588
|
+
sys.exit(-1)
|
|
589
|
+
|
|
590
|
+
# defining dual_pol, and gmfs by channel
|
|
591
|
+
if len(xr_dataset.pol.values) == 2:
|
|
592
|
+
dual_pol = True
|
|
593
|
+
else:
|
|
594
|
+
dual_pol = False
|
|
595
|
+
|
|
596
|
+
if 'VV' in xr_dataset.pol.values:
|
|
597
|
+
copol = 'VV'
|
|
598
|
+
crosspol = 'VH'
|
|
599
|
+
copol_gmf = 'VV'
|
|
600
|
+
crosspol_gmf = 'VH'
|
|
601
|
+
else:
|
|
602
|
+
logging.warning('for now this processor does not support entirely HH+HV acquisitions\n '
|
|
603
|
+
'it wont crash but it will use HH+VH GMF for wind inversion -> wrong hypothesis\n '
|
|
604
|
+
'!! dual WIND SPEED IS NOT USABLE !! But co WIND SPEED IS USABLE !!')
|
|
605
|
+
copol = 'HH'
|
|
606
|
+
crosspol = 'HV'
|
|
607
|
+
copol_gmf = 'HH'
|
|
608
|
+
crosspol_gmf = 'VH'
|
|
609
|
+
|
|
610
|
+
model_vv = config["GMF_"+copol_gmf+"_NAME"]
|
|
611
|
+
model_vh = config["GMF_"+crosspol_gmf+"_NAME"]
|
|
612
|
+
|
|
613
|
+
# need to load gmfs before inversion
|
|
614
|
+
gmfs_impl = [x for x in [model_vv, model_vh] if "gmf_" in x]
|
|
615
|
+
windspeed.gmfs.GmfModel.activate_gmfs_impl(gmfs_impl)
|
|
616
|
+
sarwings_luts = [x for x in [model_vv, model_vh]
|
|
617
|
+
if x.startswith("sarwing_lut_")]
|
|
618
|
+
|
|
619
|
+
if len(sarwings_luts) > 0:
|
|
620
|
+
windspeed.register_sarwing_luts(getConf()["sarwing_luts_path"])
|
|
621
|
+
|
|
622
|
+
nc_luts = [x for x in [model_vv, model_vh] if x.startswith("nc_lut")]
|
|
623
|
+
|
|
624
|
+
if len(nc_luts) > 0:
|
|
625
|
+
windspeed.register_nc_luts(getConf()["nc_luts_path"])
|
|
626
|
+
|
|
627
|
+
if (model_vv == "gmf_cmod7"):
|
|
628
|
+
windspeed.register_cmod7(getConf()["lut_cmod7_path"])
|
|
629
|
+
# Step 2 - clean and prepare dataset
|
|
630
|
+
|
|
631
|
+
# variables to not keep in the L2
|
|
632
|
+
black_list = ['digital_number', 'gamma0_raw', 'negz',
|
|
633
|
+
'azimuth_time', 'slant_range_time', 'velocity', 'range_ground_spacing',
|
|
634
|
+
'gamma0', 'time', 'nd_co', 'nd_cr', 'gamma0_lut', 'sigma0_lut', "noise_lut_range", "lineSpacing",
|
|
635
|
+
"sampleSpacing", "noise_lut", "noise_lut_azi",
|
|
636
|
+
'nebz', 'beta0_raw', 'lines_flipped', 'samples_flipped', "altitude", "beta0"]
|
|
637
|
+
variables = list(set(xr_dataset) - set(black_list))
|
|
638
|
+
xr_dataset = xr_dataset[variables]
|
|
639
|
+
|
|
640
|
+
# lon/lat
|
|
641
|
+
xr_dataset.longitude.attrs["units"] = "degrees_east"
|
|
642
|
+
xr_dataset.longitude.attrs["long_name"] = "Longitude at wind cell center"
|
|
643
|
+
xr_dataset.longitude.attrs["standard_name"] = "longitude"
|
|
644
|
+
|
|
645
|
+
xr_dataset.latitude.attrs["units"] = "degrees_north"
|
|
646
|
+
xr_dataset.latitude.attrs["long_name"] = "Latitude at wind cell center"
|
|
647
|
+
xr_dataset.latitude.attrs["standard_name"] = "latitude"
|
|
648
|
+
|
|
649
|
+
# incidence
|
|
650
|
+
xr_dataset.incidence.attrs["units"] = "degrees"
|
|
651
|
+
xr_dataset.incidence.attrs["long_name"] = "Incidence angle at wind cell center"
|
|
652
|
+
xr_dataset.incidence.attrs["standard_name"] = "incidence"
|
|
653
|
+
|
|
654
|
+
# elevation
|
|
655
|
+
xr_dataset.elevation.attrs["units"] = "degrees"
|
|
656
|
+
xr_dataset.elevation.attrs["long_name"] = "Elevation angle at wind cell center"
|
|
657
|
+
xr_dataset.elevation.attrs["standard_name"] = "elevation"
|
|
658
|
+
|
|
659
|
+
# offboresight
|
|
660
|
+
# TOREMOVE
|
|
661
|
+
if "offboresight" in xr_dataset:
|
|
662
|
+
xr_dataset.offboresight.attrs["units"] = "degrees"
|
|
663
|
+
xr_dataset.offboresight.attrs["long_name"] = "Offboresight angle at wind cell center"
|
|
664
|
+
xr_dataset.elevation.attrs["standard_name"] = "offboresight"
|
|
665
|
+
|
|
666
|
+
# masks (no ice / no_valid)
|
|
667
|
+
xr_dataset.land_mask.values = binary_dilation(xr_dataset['land_mask'].values.astype('uint8'),
|
|
668
|
+
structure=np.ones((3, 3), np.uint8), iterations=3)
|
|
669
|
+
xr_dataset.land_mask.attrs['long_name'] = 'Mask of data'
|
|
670
|
+
xr_dataset.land_mask.attrs['valid_range'] = np.array([0, 1])
|
|
671
|
+
xr_dataset.land_mask.attrs['flag_values'] = np.array([0, 1])
|
|
672
|
+
xr_dataset.land_mask.attrs['flag_meanings'] = 'valid no_valid'
|
|
673
|
+
|
|
674
|
+
logging.debug("mask is a copy of land_mask")
|
|
675
|
+
|
|
676
|
+
xr_dataset['mask'] = xr.DataArray(xr_dataset.land_mask)
|
|
677
|
+
xr_dataset.mask.attrs = {}
|
|
678
|
+
xr_dataset.mask.attrs['long_name'] = 'Mask of data'
|
|
679
|
+
xr_dataset.mask.attrs['valid_range'] = np.array([0, 3])
|
|
680
|
+
xr_dataset.mask.attrs['flag_values'] = np.array([0, 1, 2, 3])
|
|
681
|
+
xr_dataset.mask.attrs['flag_meanings'] = 'valid land ice no_valid'
|
|
682
|
+
|
|
683
|
+
# ancillary
|
|
684
|
+
xr_dataset['ancillary_wind_direction'] = (
|
|
685
|
+
90. - np.rad2deg(np.arctan2(xr_dataset.model_V10, xr_dataset.model_U10)) + 180) % 360
|
|
686
|
+
|
|
687
|
+
xr_dataset['ancillary_wind_direction'] = xr.where(xr_dataset['mask'], np.nan,
|
|
688
|
+
xr_dataset['ancillary_wind_direction'].compute()).transpose(
|
|
689
|
+
*xr_dataset['ancillary_wind_direction'].dims)
|
|
690
|
+
xr_dataset['ancillary_wind_direction'].attrs = {}
|
|
691
|
+
xr_dataset['ancillary_wind_direction'].attrs['units'] = 'degrees_north'
|
|
692
|
+
xr_dataset['ancillary_wind_direction'].attrs[
|
|
693
|
+
'long_name'] = f'{ancillary_name} wind direction (meteorological convention)'
|
|
694
|
+
xr_dataset['ancillary_wind_direction'].attrs['standart_name'] = 'wind_direction'
|
|
695
|
+
|
|
696
|
+
xr_dataset['ancillary_wind_speed'] = np.sqrt(
|
|
697
|
+
xr_dataset['model_U10']**2+xr_dataset['model_V10']**2)
|
|
698
|
+
xr_dataset['ancillary_wind_speed'] = xr.where(xr_dataset['mask'], np.nan,
|
|
699
|
+
xr_dataset['ancillary_wind_speed'].compute()).transpose(
|
|
700
|
+
*xr_dataset['ancillary_wind_speed'].dims)
|
|
701
|
+
xr_dataset['ancillary_wind_speed'].attrs = {}
|
|
702
|
+
xr_dataset['ancillary_wind_speed'].attrs['units'] = 'm s^-1'
|
|
703
|
+
xr_dataset['ancillary_wind_speed'].attrs[
|
|
704
|
+
'long_name'] = f'{ancillary_name} wind speed'
|
|
705
|
+
xr_dataset['ancillary_wind_speed'].attrs['standart_name'] = 'wind_speed'
|
|
706
|
+
|
|
707
|
+
xr_dataset['ancillary_wind'] = xr.where(xr_dataset['mask'], np.nan,
|
|
708
|
+
(xr_dataset.ancillary_wind_speed * np.exp(1j * xsarsea.dir_geo_to_sample(xr_dataset.ancillary_wind_direction, xr_dataset.ground_heading))).compute()).transpose(
|
|
709
|
+
*xr_dataset['ancillary_wind_speed'].dims)
|
|
710
|
+
|
|
711
|
+
xr_dataset.attrs['ancillary_source'] = xr_dataset['model_U10'].attrs['history'].split('decoded: ')[
|
|
712
|
+
1].strip()
|
|
713
|
+
xr_dataset = xr_dataset.drop_vars(['model_U10', 'model_V10'])
|
|
714
|
+
|
|
715
|
+
# nrcs processing
|
|
716
|
+
xr_dataset['sigma0_ocean'] = xr.where(xr_dataset['mask'], np.nan,
|
|
717
|
+
xr_dataset['sigma0'].compute()).transpose(*xr_dataset['sigma0'].dims)
|
|
718
|
+
xr_dataset['sigma0_ocean'].attrs = xr_dataset['sigma0'].attrs
|
|
719
|
+
# we forced it to 1e-15
|
|
720
|
+
xr_dataset['sigma0_ocean'].attrs['comment'] = "clipped, no values <=0 ; 1e-15 instread"
|
|
721
|
+
|
|
722
|
+
# rajout d'un mask pour les valeurs <=0:
|
|
723
|
+
xr_dataset['sigma0_mask'] = xr.where(
|
|
724
|
+
xr_dataset['sigma0_ocean'] <= 0, 1, 0).transpose(*xr_dataset['sigma0'].dims)
|
|
725
|
+
xr_dataset.sigma0_mask.attrs['valid_range'] = np.array([0, 1])
|
|
726
|
+
xr_dataset.sigma0_mask.attrs['flag_values'] = np.array([0, 1])
|
|
727
|
+
xr_dataset.sigma0_mask.attrs['flag_meanings'] = 'valid no_valid'
|
|
728
|
+
xr_dataset['sigma0_ocean'] = xr.where(
|
|
729
|
+
xr_dataset['sigma0_ocean'] <= 0, 1e-15, xr_dataset['sigma0_ocean'])
|
|
730
|
+
|
|
731
|
+
xr_dataset['sigma0_ocean_raw'] = xr.where(xr_dataset['mask'], np.nan,
|
|
732
|
+
xr_dataset['sigma0_raw'].compute()).transpose(*xr_dataset['sigma0_raw'].dims)
|
|
733
|
+
|
|
734
|
+
xr_dataset['sigma0_ocean_raw'].attrs = xr_dataset['sigma0_raw'].attrs
|
|
735
|
+
|
|
736
|
+
xr_dataset['sigma0_detrend'] = xsarsea.sigma0_detrend(
|
|
737
|
+
xr_dataset.sigma0.sel(pol=copol), xr_dataset.incidence, model=model_vv)
|
|
738
|
+
|
|
739
|
+
# processing
|
|
740
|
+
if dual_pol:
|
|
741
|
+
|
|
742
|
+
xr_dataset['sigma0_detrend_cross'] = xsarsea.sigma0_detrend(
|
|
743
|
+
xr_dataset.sigma0.sel(pol=crosspol), xr_dataset.incidence, model=model_vh)
|
|
744
|
+
if config["apply_flattening"]:
|
|
745
|
+
xr_dataset = xr_dataset.assign(nesz_cross_final=(
|
|
746
|
+
['line', 'sample'], windspeed.nesz_flattening(xr_dataset.nesz.sel(pol=crosspol), xr_dataset.incidence)))
|
|
747
|
+
xr_dataset['nesz_cross_final'].attrs[
|
|
748
|
+
"comment"] = 'nesz has been flattened using windspeed.nesz_flattening'
|
|
749
|
+
|
|
750
|
+
else:
|
|
751
|
+
xr_dataset = xr_dataset.assign(
|
|
752
|
+
nesz_cross_final=(['line', 'sample'], xr_dataset.nesz.sel(pol=crosspol).values))
|
|
753
|
+
xr_dataset['nesz_cross_final'].attrs["comment"] = 'nesz has not been flattened'
|
|
754
|
+
|
|
755
|
+
xr_dataset.nesz_cross_final.attrs['units'] = 'm^2 / m^2'
|
|
756
|
+
xr_dataset.nesz_cross_final.attrs['long_name'] = 'Noise Equivalent SigmaNaught'
|
|
757
|
+
|
|
758
|
+
# dsig
|
|
759
|
+
sigma0_ocean_cross = xr_dataset['sigma0_ocean'].sel(pol=crosspol)
|
|
760
|
+
xr_dataset["dsig_cross"] = windspeed.get_dsig(config["dsig_"+crosspol_gmf+"_NAME"], xr_dataset.incidence,
|
|
761
|
+
sigma0_ocean_cross, xr_dataset.nesz_cross_final)
|
|
762
|
+
|
|
763
|
+
xr_dataset.dsig_cross.attrs['comment'] = 'variable used to ponderate copol and crosspol'
|
|
764
|
+
dsig_cross = xr_dataset.dsig_cross
|
|
765
|
+
else:
|
|
766
|
+
sigma0_ocean_cross = None
|
|
767
|
+
dsig_cross = 0.1 # default value set in xsarsea
|
|
768
|
+
|
|
769
|
+
if ((recalibration) & ("SENTINEL" in sensor_longname)):
|
|
770
|
+
xr_dataset.attrs["path_aux_pp1_new"] = os.path.basename(os.path.dirname(
|
|
771
|
+
os.path.dirname(xsar_dataset.datatree['recalibration'].attrs['path_aux_pp1_new'])))
|
|
772
|
+
xr_dataset.attrs["path_aux_cal_new"] = os.path.basename(os.path.dirname(
|
|
773
|
+
os.path.dirname(xsar_dataset.datatree['recalibration'].attrs['path_aux_cal_new'])))
|
|
774
|
+
|
|
775
|
+
xr_dataset.attrs["path_aux_pp1_old"] = os.path.basename(os.path.dirname(
|
|
776
|
+
os.path.dirname(xsar_dataset.datatree['recalibration'].attrs['path_aux_pp1_old'])))
|
|
777
|
+
xr_dataset.attrs["path_aux_cal_old"] = os.path.basename(os.path.dirname(
|
|
778
|
+
os.path.dirname(xsar_dataset.datatree['recalibration'].attrs['path_aux_cal_old'])))
|
|
779
|
+
|
|
780
|
+
if add_streaks:
|
|
781
|
+
xsar_dataset_100 = fct_dataset(
|
|
782
|
+
meta, resolution='100m')
|
|
783
|
+
xr_dataset_100 = xsar_dataset_100.datatree['measurement'].to_dataset()
|
|
784
|
+
xr_dataset_100 = xr_dataset_100.rename(map_model)
|
|
785
|
+
|
|
786
|
+
# adding sigma0 detrend
|
|
787
|
+
xr_dataset_100['sigma0_detrend'] = xsarsea.sigma0_detrend(
|
|
788
|
+
xr_dataset_100.sigma0.sel(pol=copol), xr_dataset_100.incidence, model=model_vv)
|
|
789
|
+
|
|
790
|
+
xr_dataset_100['sigma0_detrend_cross'] = xsarsea.sigma0_detrend(
|
|
791
|
+
xr_dataset_100.sigma0.sel(pol=crosspol), xr_dataset_100.incidence, model=model_vh)
|
|
792
|
+
|
|
793
|
+
sigma0_detrend_combined = xr.concat(
|
|
794
|
+
[xr_dataset_100['sigma0_detrend'],
|
|
795
|
+
xr_dataset_100['sigma0_detrend_cross']],
|
|
796
|
+
dim='pol'
|
|
797
|
+
)
|
|
798
|
+
sigma0_detrend_combined['pol'] = [copol, crosspol]
|
|
799
|
+
|
|
800
|
+
xr_dataset_100['sigma0_detrend'] = sigma0_detrend_combined
|
|
801
|
+
xr_dataset_100.land_mask.values = binary_dilation(xr_dataset_100['land_mask'].values.astype('uint8'),
|
|
802
|
+
structure=np.ones((3, 3), np.uint8), iterations=3)
|
|
803
|
+
xr_dataset_100['sigma0_detrend'] = xr.where(
|
|
804
|
+
xr_dataset_100['land_mask'], np.nan, xr_dataset_100['sigma0'].compute()).transpose(*xr_dataset_100['sigma0'].dims)
|
|
805
|
+
|
|
806
|
+
xr_dataset['streaks_direction'] = get_streaks(
|
|
807
|
+
xr_dataset, xr_dataset_100)
|
|
808
|
+
|
|
809
|
+
return xr_dataset, dual_pol, copol, crosspol, copol_gmf, crosspol_gmf, model_vv, model_vh, sigma0_ocean_cross, dsig_cross, sensor_longname, out_file, config
|
|
810
|
+
|
|
811
|
+
|
|
812
|
+
def makeL2(filename, outdir, config_path, overwrite=False, generateCSV=True, add_streaks=False, resolution='1000m'):
|
|
813
|
+
"""
|
|
814
|
+
Main function to generate L2 product.
|
|
815
|
+
|
|
816
|
+
Parameters
|
|
817
|
+
----------
|
|
818
|
+
filename : str
|
|
819
|
+
input filename
|
|
820
|
+
outdir : str
|
|
821
|
+
output folder
|
|
822
|
+
config_path : str
|
|
823
|
+
configuration file path
|
|
824
|
+
overwrite : bool, optional
|
|
825
|
+
overwrite existing file
|
|
826
|
+
generateCSV : bool, optional
|
|
827
|
+
generate CSV file
|
|
828
|
+
resolution : str, optional
|
|
829
|
+
working resolution
|
|
830
|
+
|
|
831
|
+
Returns
|
|
832
|
+
-------
|
|
833
|
+
str
|
|
834
|
+
output filename
|
|
835
|
+
xarray.Dataset
|
|
836
|
+
final dataset
|
|
837
|
+
"""
|
|
838
|
+
|
|
839
|
+
xr_dataset, dual_pol, copol, crosspol, copol_gmf, crosspol_gmf, model_vv, model_vh, sigma0_ocean_cross, dsig_cross, sensor_longname, out_file, config = preprocess(
|
|
840
|
+
filename, outdir, config_path, overwrite, add_streaks, resolution)
|
|
841
|
+
|
|
842
|
+
kwargs = {
|
|
843
|
+
"inc_step_lr": config.pop("inc_step_lr", None),
|
|
844
|
+
"wpsd_step_lr": config.pop("wspd_step_lr", None),
|
|
845
|
+
"phi_step_lr": config.pop("phi_step_lr", None),
|
|
846
|
+
"inc_step": config.pop("inc_step", None),
|
|
847
|
+
"wpsd_step": config.pop("wspd_step", None),
|
|
848
|
+
"phi_step": config.pop("phi_step", None),
|
|
849
|
+
"resolution": config.pop("resolution", None),
|
|
850
|
+
}
|
|
851
|
+
|
|
852
|
+
wind_co, wind_dual, windspeed_cr = inverse(dual_pol,
|
|
853
|
+
inc=xr_dataset['incidence'],
|
|
854
|
+
sigma0=xr_dataset['sigma0_ocean'].sel(
|
|
855
|
+
pol=copol),
|
|
856
|
+
sigma0_dual=sigma0_ocean_cross,
|
|
857
|
+
ancillary_wind=xr_dataset['ancillary_wind'],
|
|
858
|
+
dsig_cr=dsig_cross,
|
|
859
|
+
model_vv=model_vv,
|
|
860
|
+
model_vh=model_vh,
|
|
861
|
+
** kwargs)
|
|
862
|
+
|
|
863
|
+
# windspeed_co
|
|
864
|
+
xr_dataset['windspeed_co'] = np.abs(wind_co)
|
|
865
|
+
xr_dataset["windspeed_co"].attrs["units"] = "m.s⁻1"
|
|
866
|
+
xr_dataset["windspeed_co"].attrs["long_name"] = "Wind speed inverted from model %s (%s)" % (
|
|
867
|
+
model_vv, copol)
|
|
868
|
+
xr_dataset["windspeed_co"].attrs["standart_name"] = "wind_speed"
|
|
869
|
+
xr_dataset["windspeed_co"].attrs["model"] = wind_co.attrs["model"]
|
|
870
|
+
del xr_dataset["windspeed_co"].attrs['comment']
|
|
871
|
+
|
|
872
|
+
# winddir_co
|
|
873
|
+
xr_dataset['winddir_co'] = (
|
|
874
|
+
90 - (np.angle(wind_co, deg=True)) + xr_dataset.ground_heading) % 360
|
|
875
|
+
xr_dataset["winddir_co"].attrs["units"] = "degrees_north"
|
|
876
|
+
xr_dataset["winddir_co"].attrs["long_name"] = "Wind direction in meteorological convention, 0=North, 90=East, inverted from model %s (%s)" % (
|
|
877
|
+
model_vv, copol)
|
|
878
|
+
xr_dataset["winddir_co"].attrs["standart_name"] = "wind_direction"
|
|
879
|
+
xr_dataset["winddir_co"].attrs["model"] = wind_co.attrs["model"]
|
|
880
|
+
|
|
881
|
+
# windspeed_dual / windspeed_cr / /winddir_dual / winddir_cr
|
|
882
|
+
if dual_pol:
|
|
883
|
+
xr_dataset['windspeed_dual'] = np.abs(wind_dual)
|
|
884
|
+
xr_dataset["windspeed_dual"].attrs["units"] = "m.s⁻1"
|
|
885
|
+
xr_dataset["windspeed_dual"].attrs["long_name"] = "Wind speed inverted from model %s (%s) & %s (%s)" % (
|
|
886
|
+
model_vv, copol, model_vh, crosspol)
|
|
887
|
+
xr_dataset["windspeed_dual"].attrs["standart_name"] = "wind_speed"
|
|
888
|
+
xr_dataset["windspeed_dual"].attrs["model"] = wind_dual.attrs["model"]
|
|
889
|
+
del xr_dataset["windspeed_dual"].attrs['comment']
|
|
890
|
+
|
|
891
|
+
xr_dataset['winddir_dual'] = (
|
|
892
|
+
90 - (np.angle(wind_dual, deg=True)) + xr_dataset.ground_heading) % 360
|
|
893
|
+
xr_dataset["winddir_dual"].attrs["units"] = "degrees_north"
|
|
894
|
+
xr_dataset["winddir_dual"].attrs["long_name"] = "Wind direction in meteorological convention, 0=North, 90=East inverted from model %s (%s) & %s (%s)" % (
|
|
895
|
+
model_vv, copol, model_vh, crosspol)
|
|
896
|
+
xr_dataset["winddir_dual"].attrs["standart_name"] = "wind_direction"
|
|
897
|
+
xr_dataset["winddir_dual"].attrs["model"] = wind_dual.attrs["model"]
|
|
898
|
+
|
|
899
|
+
xr_dataset = xr_dataset.assign(
|
|
900
|
+
windspeed_cross=(['line', 'sample'], windspeed_cr))
|
|
901
|
+
xr_dataset["windspeed_cross"].attrs["units"] = "m.s⁻1"
|
|
902
|
+
xr_dataset["windspeed_cross"].attrs["long_name"] = "Wind Speed inverted from model %s (%s)" % (
|
|
903
|
+
model_vh, crosspol)
|
|
904
|
+
xr_dataset["windspeed_cross"].attrs["standart_name"] = "wind_speed"
|
|
905
|
+
xr_dataset["windspeed_cross"].attrs["model"] = "%s" % (model_vh)
|
|
906
|
+
|
|
907
|
+
xr_dataset['winddir_cross'] = xr_dataset['winddir_dual'].copy()
|
|
908
|
+
xr_dataset["winddir_cross"].attrs["units"] = "degrees_north"
|
|
909
|
+
xr_dataset["winddir_cross"].attrs["long_name"] = "Wind direction in meteorological convention, 0=North, 90=East, copied from dualpol"
|
|
910
|
+
xr_dataset["winddir_cross"].attrs["standart_name"] = "wind_direction"
|
|
911
|
+
xr_dataset["winddir_cross"].attrs["model"] = "No model used ; content is a copy of dualpol wind direction"
|
|
912
|
+
|
|
913
|
+
xr_dataset, encoding = makeL2asOwi(
|
|
914
|
+
xr_dataset, dual_pol, copol, crosspol, add_streaks=add_streaks)
|
|
915
|
+
|
|
916
|
+
# add attributes
|
|
917
|
+
firstMeasurementTime = None
|
|
918
|
+
lastMeasurementTime = None
|
|
919
|
+
try:
|
|
920
|
+
firstMeasurementTime = datetime.datetime.strptime(xr_dataset.attrs['start_date'],
|
|
921
|
+
"%Y-%m-%d %H:%M:%S.%f").strftime(
|
|
922
|
+
"%Y-%m-%dT%H:%M:%SZ")
|
|
923
|
+
lastMeasurementTime = datetime.datetime.strptime(xr_dataset.attrs['stop_date'],
|
|
924
|
+
"%Y-%m-%d %H:%M:%S.%f").strftime(
|
|
925
|
+
"%Y-%m-%dT%H:%M:%SZ")
|
|
926
|
+
except:
|
|
927
|
+
firstMeasurementTime = datetime.datetime.strptime(xr_dataset.attrs['start_date'],
|
|
928
|
+
"%Y-%m-%d %H:%M:%S").strftime(
|
|
929
|
+
"%Y-%m-%dT%H:%M:%SZ")
|
|
930
|
+
lastMeasurementTime = datetime.datetime.strptime(xr_dataset.attrs['stop_date'],
|
|
931
|
+
"%Y-%m-%d %H:%M:%S").strftime(
|
|
932
|
+
"%Y-%m-%dT%H:%M:%SZ")
|
|
933
|
+
|
|
934
|
+
attrs = {
|
|
935
|
+
"TITLE": "Sentinel-1 OWI Component",
|
|
936
|
+
"productOwner": "IFREMER",
|
|
937
|
+
"sourceProduct": (xr_dataset.attrs["safe"] if "safe" in xr_dataset.attrs else os.path.basename(xr_dataset.attrs["product_path"])),
|
|
938
|
+
"sourceProduct_fullpath": xr_dataset.attrs.pop('name'),
|
|
939
|
+
"missionName": sensor_longname,
|
|
940
|
+
"missionPhase": "Operational",
|
|
941
|
+
"polarisation": xr_dataset.attrs['pols'],
|
|
942
|
+
"acquisitionStation": '',
|
|
943
|
+
"xsar_version": xsar.__version__,
|
|
944
|
+
"xsarsea_version": xsarsea.__version__,
|
|
945
|
+
"pythonVersion": f"{sys.version_info.major}.{sys.version_info.minor}.{sys.version_info.micro}",
|
|
946
|
+
"polarisationRatio": "/",
|
|
947
|
+
"l2ProcessingUtcTime": datetime.datetime.now().strftime("%Y-%m-%dT%H:%M:%SZ"),
|
|
948
|
+
"processingCenter": "IFREMER",
|
|
949
|
+
"firstMeasurementTime": firstMeasurementTime,
|
|
950
|
+
"lastMeasurementTime": lastMeasurementTime,
|
|
951
|
+
"clmSource": "/",
|
|
952
|
+
"bathySource": "/",
|
|
953
|
+
"oswAlgorithmName": "grdwindinversion",
|
|
954
|
+
"owiAlgorithmVersion": grdwindinversion.__version__,
|
|
955
|
+
"gmf": config['GMF_'+copol_gmf+'_NAME'] + ", " + config["GMF_"+crosspol_gmf+"_NAME"],
|
|
956
|
+
"iceSource": "/",
|
|
957
|
+
"owiNoiseCorrection": "True",
|
|
958
|
+
"inversionTabGMF": config['GMF_'+copol_gmf +
|
|
959
|
+
'_NAME'] + ", " + config["GMF_"+crosspol_gmf+"_NAME"],
|
|
960
|
+
"wnf_3km_average": "False",
|
|
961
|
+
"owiWindSpeedSrc": "owiWindSpeed",
|
|
962
|
+
"owiWindDirectionSrc": "/",
|
|
963
|
+
"ancillary_source": xr_dataset.attrs['ancillary_source']
|
|
964
|
+
}
|
|
965
|
+
|
|
966
|
+
for recalib_attrs in ["path_aux_pp1_new", 'path_aux_pp1_old', "path_aux_cal_new", "path_aux_cal_old"]:
|
|
967
|
+
if recalib_attrs in xr_dataset.attrs:
|
|
968
|
+
attrs[recalib_attrs] = xr_dataset.attrs[recalib_attrs]
|
|
969
|
+
|
|
970
|
+
# new one to match convention
|
|
971
|
+
_S1_added_attrs = ["product", "ipf", "multi_dataset", "footprint",
|
|
972
|
+
"coverage", "orbit_pass", "platform_heading"]
|
|
973
|
+
_RS2_added_attrs = ["passDirection", "swath", "footprint", "coverage"]
|
|
974
|
+
_RCM_added_attrs = ["swath", "footprint", "coverage", "productId",]
|
|
975
|
+
|
|
976
|
+
for sup_attr in _S1_added_attrs + _RS2_added_attrs + _RCM_added_attrs:
|
|
977
|
+
if sup_attr in xr_dataset.attrs:
|
|
978
|
+
attrs[sup_attr] = xr_dataset.attrs[sup_attr]
|
|
979
|
+
for var in ['footprint', 'multidataset']:
|
|
980
|
+
if var in attrs:
|
|
981
|
+
attrs[var] = str(attrs[var])
|
|
982
|
+
|
|
983
|
+
# add in kwargs in attrs
|
|
984
|
+
for key in kwargs:
|
|
985
|
+
attrs["lut_params_"+key] = "/" if kwargs[key] is None else kwargs[key]
|
|
986
|
+
|
|
987
|
+
xr_dataset.attrs = attrs
|
|
988
|
+
|
|
989
|
+
os.makedirs(os.path.dirname(out_file), exist_ok=True)
|
|
990
|
+
|
|
991
|
+
xr_dataset.to_netcdf(out_file, mode="w", encoding=encoding)
|
|
992
|
+
if generateCSV:
|
|
993
|
+
df = xr_dataset.to_dataframe()
|
|
994
|
+
df = df[df.owiMask == False]
|
|
995
|
+
|
|
996
|
+
df = df.assign(**xr_dataset.attrs)
|
|
997
|
+
df.reset_index(drop=False, inplace=True)
|
|
998
|
+
df.to_csv(out_file.replace(".nc", ".csv"))
|
|
999
|
+
|
|
1000
|
+
logging.info("OK for %s ", os.path.basename(filename))
|
|
1001
|
+
|
|
1002
|
+
return out_file, xr_dataset
|