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.
@@ -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