grdwindinversion 0.2.7__py3-none-any.whl → 0.3.2__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.
@@ -1,3 +1,4 @@
1
+ import tempfile
1
2
  import traceback
2
3
 
3
4
  import xsar
@@ -15,14 +16,12 @@ from scipy.ndimage import binary_dilation
15
16
  import re
16
17
  import string
17
18
  import os
18
- from grdwindinversion.streaks import get_streaks
19
- from grdwindinversion.utils import check_incidence_range, get_pol_ratio_name
19
+ from grdwindinversion.utils import check_incidence_range, get_pol_ratio_name, timing
20
20
  from grdwindinversion.load_config import getConf
21
21
  # optional debug messages
22
22
  import logging
23
- logging.basicConfig()
24
- logging.getLogger('xsarsea.windspeed').setLevel(
25
- logging.INFO) # or .setLevel(logging.INFO)
23
+ logger = logging.getLogger('grdwindinversion.inversion')
24
+ logger.addHandler(logging.NullHandler())
26
25
 
27
26
 
28
27
  def getSensorMetaDataset(filename):
@@ -45,10 +44,16 @@ def getSensorMetaDataset(filename):
45
44
  return "S1B", "SENTINEL-1 B", xsar.Sentinel1Meta, xsar.Sentinel1Dataset
46
45
  elif ("RS2" in filename):
47
46
  return "RS2", "RADARSAT-2", xsar.RadarSat2Meta, xsar.RadarSat2Dataset
48
- elif ("RCM" in filename):
49
- return "RCM", "RADARSAT Constellation", xsar.RcmMeta, xsar.RcmDataset
47
+ elif ("RCM1" in filename):
48
+ return "RCM", "RADARSAT Constellation 1", xsar.RcmMeta, xsar.RcmDataset
49
+ elif ("RCM2" in filename):
50
+ return "RCM", "RADARSAT Constellation 2", xsar.RcmMeta, xsar.RcmDataset
51
+ elif ("RCM3" in filename):
52
+ return "RCM", "RADARSAT Constellation 3", xsar.RcmMeta, xsar.RcmDataset
53
+
50
54
  else:
51
- raise ValueError("must be S1A|S1B|RS2|RCM, got filename %s" % filename)
55
+ raise ValueError(
56
+ "must be S1A|S1B|RS2|RCM1|RCM2|RCM3, got filename %s" % filename)
52
57
 
53
58
 
54
59
  def getOutputName2(input_file, outdir, sensor, meta, subdir=True):
@@ -96,12 +101,10 @@ def getOutputName2(input_file, outdir, sensor, meta, subdir=True):
96
101
  new_format = f"{MISSIONID.lower()}--owi-xx-{meta_start_date.lower()}-{meta_stop_date.lower()}-_____-_____.nc"
97
102
  elif sensor == 'RCM':
98
103
  regex = re.compile(
99
- "([A-Z0-9]+)_OK([0-9]+)_PK([0-9]+)_(.*?)_(.*?)_(.*?)_(.*?)_(.*?)_(.*?)_(.*?)")
100
- template = string.Template(
101
- "${MISSIONID}_OK${DATA1}_PK${DATA2}_${DATA3}_${BEAM}_${DATE}_${TIME}_${POLARIZATION1}_${POLARIZATION2}_${PRODUCT}")
104
+ r"(RCM[0-9])_OK([0-9]+)_PK([0-9]+)_([0-9]+)_([A-Z]+)_(\d{8})_(\d{6})_([A-Z]{2}(?:_[A-Z]{2})?)_([A-Z]+)$")
102
105
  match = regex.match(basename_match)
103
- MISSIONID, DATA1, DATA2, DATA3, BEAM_MODE, DATE, TIME, POLARIZATION1, POLARIZATION2, LAST = match.groups()
104
- new_format = f"{MISSIONID.lower()}-{BEAM_MODE.lower()}-owi-xx-{meta_start_date.lower()}-{meta_stop_date.lower()}-_____-_____.nc"
106
+ MISSIONID, DATA1, DATA2, DATA3, BEAM, DATE, TIME, POLARIZATION, PRODUCT = match.groups()
107
+ new_format = f"{MISSIONID.lower()}-{BEAM.lower()}-owi-xx-{meta_start_date.lower()}-{meta_stop_date.lower()}-_____-_____.nc"
105
108
  else:
106
109
  raise ValueError(
107
110
  "sensor must be S1A|S1B|RS2|RCM, got sensor %s" % sensor)
@@ -209,6 +212,7 @@ def getAncillary(meta, ancillary_name='ecmwf'):
209
212
  ancillary_name)
210
213
 
211
214
 
215
+ @timing(logger=logger.debug)
212
216
  def inverse(dual_pol, inc, sigma0, sigma0_dual, ancillary_wind, dsig_cr, model_co, model_cross, **kwargs):
213
217
  """
214
218
  Invert sigma0 to retrieve wind using model (lut or gmf).
@@ -282,7 +286,8 @@ def inverse(dual_pol, inc, sigma0, sigma0_dual, ancillary_wind, dsig_cr, model_c
282
286
  return wind_co, None, None
283
287
 
284
288
 
285
- def makeL2asOwi(xr_dataset, dual_pol, copol, crosspol, add_streaks, apply_flattening):
289
+ @timing(logger=logger.debug)
290
+ def makeL2asOwi(xr_dataset, config):
286
291
  """
287
292
  Rename xr_dataset variables and attributes to match naming convention.
288
293
 
@@ -290,12 +295,8 @@ def makeL2asOwi(xr_dataset, dual_pol, copol, crosspol, add_streaks, apply_flatte
290
295
  ----------
291
296
  xr_dataset: xarray.Dataset
292
297
  dataset to rename
293
- dual_pol: bool
294
- True if dualpol, False if singlepol
295
- copol: str
296
- copolarization name
297
- crosspol: str
298
- crosspolarization name
298
+ config: dict
299
+ configuration dict
299
300
 
300
301
  Returns
301
302
  -------
@@ -320,13 +321,27 @@ def makeL2asOwi(xr_dataset, dual_pol, copol, crosspol, add_streaks, apply_flatte
320
321
  'winddir_co': 'owiWindDirection_co',
321
322
  'ancillary_wind_speed': 'owiAncillaryWindSpeed',
322
323
  'ancillary_wind_direction': 'owiAncillaryWindDirection',
323
- 'sigma0_detrend': 'owiNrcs_detrend'
324
+ 'sigma0_detrend': 'owiNrcs_detrend',
324
325
  })
325
326
 
326
327
  if "offboresight" in xr_dataset:
327
328
  xr_dataset = xr_dataset.rename(
328
329
  {"offboresight": "owiOffboresightAngle"})
329
330
 
331
+ if config["add_nrcs_model"]:
332
+ xr_dataset = xr_dataset.rename(
333
+ {"ancillary_nrcs": "owiAncillaryNrcs"})
334
+ xr_dataset.owiAncillaryNrcs.attrs["units"] = "m^2 / m^2"
335
+ xr_dataset.owiAncillaryNrcs.attrs[
336
+ "long_name"] = f"Ancillary Normalized Radar Cross Section - simulated from {config['l2_params']['copol_gmf']} & ancillary wind"
337
+
338
+ if config["l2_params"]["dual_pol"]:
339
+ xr_dataset = xr_dataset.rename(
340
+ {"ancillary_nrcs_cross": "owiAncillaryNrcs_cross"})
341
+ xr_dataset.owiAncillaryNrcs_cross.attrs["units"] = "m^2 / m^2"
342
+ xr_dataset.owiAncillaryNrcs_cross.attrs[
343
+ "long_name"] = f"Ancillary Normalized Radar Cross Section - simulated from {config['l2_params']['crosspol_gmf']} & ancillary wind"
344
+
330
345
  xr_dataset.owiLon.attrs["units"] = "degrees_east"
331
346
  xr_dataset.owiLon.attrs["long_name"] = "Longitude at wind cell center"
332
347
  xr_dataset.owiLon.attrs["standard_name"] = "longitude"
@@ -343,23 +358,25 @@ def makeL2asOwi(xr_dataset, dual_pol, copol, crosspol, add_streaks, apply_flatte
343
358
  xr_dataset.owiElevationAngle.attrs["long_name"] = "Elevation angle at wind cell center"
344
359
  xr_dataset.owiElevationAngle.attrs["standard_name"] = "elevation"
345
360
 
346
- xr_dataset['owiNrcs'] = xr_dataset['sigma0_ocean'].sel(pol=copol)
361
+ xr_dataset['owiNrcs'] = xr_dataset['sigma0_ocean'].sel(
362
+ pol=config["l2_params"]["copol"])
347
363
  xr_dataset.owiNrcs.attrs = xr_dataset.sigma0_ocean.attrs
348
364
  xr_dataset.owiNrcs.attrs['units'] = 'm^2 / m^2'
349
365
  xr_dataset.owiNrcs.attrs['long_name'] = 'Normalized Radar Cross Section'
350
366
  xr_dataset.owiNrcs.attrs['definition'] = 'owiNrcs_no_noise_correction - owiNesz'
351
367
 
352
- xr_dataset['owiMask_Nrcs'] = xr_dataset['sigma0_mask'].sel(pol=copol)
368
+ xr_dataset['owiMask_Nrcs'] = xr_dataset['sigma0_mask'].sel(
369
+ pol=config["l2_params"]["copol"])
353
370
  xr_dataset.owiMask_Nrcs.attrs = xr_dataset.sigma0_mask.attrs
354
371
 
355
372
  # NESZ & DSIG
356
373
  xr_dataset = xr_dataset.assign(
357
- owiNesz=(['line', 'sample'], xr_dataset.nesz.sel(pol=copol).values))
374
+ owiNesz=(['line', 'sample'], xr_dataset.nesz.sel(pol=config["l2_params"]["copol"]).values))
358
375
  xr_dataset.owiNesz.attrs['units'] = 'm^2 / m^2'
359
376
  xr_dataset.owiNesz.attrs['long_name'] = 'Noise Equivalent SigmaNaught'
360
377
 
361
378
  xr_dataset['owiNrcs_no_noise_correction'] = xr_dataset['sigma0_ocean_raw'].sel(
362
- pol=copol)
379
+ pol=config["l2_params"]["copol"])
363
380
  xr_dataset.owiNrcs_no_noise_correction.attrs = xr_dataset.sigma0_ocean_raw.attrs
364
381
  xr_dataset.owiNrcs_no_noise_correction.attrs['units'] = 'm^2 / m^2'
365
382
  xr_dataset.owiNrcs_no_noise_correction.attrs[
@@ -378,7 +395,7 @@ def makeL2asOwi(xr_dataset, dual_pol, copol, crosspol, add_streaks, apply_flatte
378
395
  # sigma0_raw__corrected cross
379
396
  if "sigma0_raw__corrected" in xr_dataset:
380
397
  xr_dataset['owiNrcs_no_noise_correction_recalibrated'] = xr_dataset['sigma0_raw__corrected'].sel(
381
- pol=copol)
398
+ pol=config["l2_params"]["copol"])
382
399
  xr_dataset.owiNrcs_no_noise_correction_recalibrated.attrs = xr_dataset.sigma0_raw__corrected.attrs
383
400
  xr_dataset.owiNrcs_no_noise_correction_recalibrated.attrs['units'] = 'm^2 / m^2'
384
401
  xr_dataset.owiNrcs_no_noise_correction_recalibrated.attrs[
@@ -388,7 +405,7 @@ def makeL2asOwi(xr_dataset, dual_pol, copol, crosspol, add_streaks, apply_flatte
388
405
 
389
406
  xr_dataset.owiNrcs.attrs['definition'] = 'owiNrcs_no_noise_correction_recalibrated - owiNesz'
390
407
 
391
- if dual_pol:
408
+ if config["l2_params"]["dual_pol"]:
392
409
 
393
410
  xr_dataset = xr_dataset.rename({
394
411
  'dsig_cross': 'owiDsig_cross',
@@ -399,31 +416,31 @@ def makeL2asOwi(xr_dataset, dual_pol, copol, crosspol, add_streaks, apply_flatte
399
416
  'sigma0_detrend_cross': 'owiNrcs_detrend_cross'
400
417
  })
401
418
 
402
- if apply_flattening:
419
+ if config["apply_flattening"]:
403
420
  xr_dataset = xr_dataset.rename({
404
421
  'nesz_cross_flattened': 'owiNesz_cross_flattened',
405
422
  })
406
423
 
407
424
  # nrcs cross
408
425
  xr_dataset['owiNrcs_cross'] = xr_dataset['sigma0_ocean'].sel(
409
- pol=crosspol)
426
+ pol=config["l2_params"]["crosspol"])
410
427
 
411
428
  xr_dataset.owiNrcs_cross.attrs['units'] = 'm^2 / m^2'
412
429
  xr_dataset.owiNrcs_cross.attrs['long_name'] = 'Normalized Radar Cross Section'
413
430
  xr_dataset.owiNrcs_cross.attrs['definition'] = 'owiNrcs_cross_no_noise_correction - owiNesz_cross'
414
431
 
415
432
  xr_dataset['owiMask_Nrcs_cross'] = xr_dataset['sigma0_mask'].sel(
416
- pol=crosspol)
433
+ pol=config["l2_params"]["crosspol"])
417
434
  xr_dataset.owiMask_Nrcs_cross.attrs = xr_dataset.sigma0_mask.attrs
418
435
 
419
436
  # nesz cross
420
437
  xr_dataset = xr_dataset.assign(owiNesz_cross=(
421
- ['line', 'sample'], xr_dataset.nesz.sel(pol=crosspol).values)) # no flattening
438
+ ['line', 'sample'], xr_dataset.nesz.sel(pol=config["l2_params"]["crosspol"]).values)) # no flattening
422
439
  xr_dataset.owiNesz_cross.attrs['units'] = 'm^2 / m^2'
423
440
  xr_dataset.owiNesz_cross.attrs['long_name'] = 'Noise Equivalent SigmaNaught'
424
441
 
425
442
  xr_dataset['owiNrcs_cross_no_noise_correction'] = xr_dataset['sigma0_ocean_raw'].sel(
426
- pol=crosspol)
443
+ pol=config["l2_params"]["crosspol"])
427
444
 
428
445
  xr_dataset.owiNrcs_cross_no_noise_correction.attrs['units'] = 'm^2 / m^2'
429
446
  xr_dataset.owiNrcs_cross_no_noise_correction.attrs[
@@ -432,7 +449,7 @@ def makeL2asOwi(xr_dataset, dual_pol, copol, crosspol, add_streaks, apply_flatte
432
449
  #  sigma0_raw__corrected cross
433
450
  if "sigma0_raw__corrected" in xr_dataset:
434
451
  xr_dataset['owiNrcs_cross_no_noise_correction_recalibrated'] = xr_dataset['sigma0_raw__corrected'].sel(
435
- pol=crosspol)
452
+ pol=config["l2_params"]["crosspol"])
436
453
  xr_dataset.owiNrcs_cross_no_noise_correction_recalibrated.attrs = xr_dataset.sigma0_raw__corrected.attrs
437
454
  xr_dataset.owiNrcs_cross_no_noise_correction_recalibrated.attrs['units'] = 'm^2 / m^2'
438
455
  xr_dataset.owiNrcs_cross_no_noise_correction_recalibrated.attrs[
@@ -442,10 +459,18 @@ def makeL2asOwi(xr_dataset, dual_pol, copol, crosspol, add_streaks, apply_flatte
442
459
 
443
460
  xr_dataset.owiNrcs_cross.attrs['definition'] = 'owiNrcs_cross_no_noise_correction_recalibrated - owiNesz_cross'
444
461
 
445
- if add_streaks:
462
+ if config["add_gradientsfeatures"]:
446
463
  xr_dataset = xr_dataset.rename({
447
- 'streaks_direction': 'owiStreaksDirection',
464
+ 'heterogeneity_mask': 'owiWindFilter'
448
465
  })
466
+ else:
467
+ xr_dataset['owiWindFilter'] = xr.full_like(xr_dataset.owiNrcs, 0)
468
+ xr_dataset['owiWindFilter'].attrs['long_name'] = "Quality flag taking into account the local heterogeneity"
469
+ xr_dataset['owiWindFilter'].attrs['valid_range'] = np.array([0, 3])
470
+ xr_dataset['owiWindFilter'].attrs['flag_values'] = np.array([
471
+ 0, 1, 2, 3])
472
+ xr_dataset['owiWindFilter'].attrs[
473
+ 'flag_meanings'] = "homogeneous_NRCS, heterogeneous_from_co-polarization_NRCS, heterogeneous_from_cross-polarization_NRCS, heterogeneous_from_dual-polarization_NRCS"
449
474
 
450
475
  #  other variables
451
476
 
@@ -458,15 +483,6 @@ def makeL2asOwi(xr_dataset, dual_pol, copol, crosspol, add_streaks, apply_flatte
458
483
  xr_dataset['owiWindQuality'].attrs['flag_meanings'] = "good medium low poor"
459
484
  xr_dataset['owiWindQuality'].attrs['comment'] = 'NOT COMPUTED YET'
460
485
 
461
- xr_dataset['owiWindFilter'] = xr.full_like(xr_dataset.owiNrcs, 0)
462
- xr_dataset['owiWindFilter'].attrs['long_name'] = "Quality flag taking into account the local heterogeneity"
463
- xr_dataset['owiWindFilter'].attrs['valid_range'] = np.array([0, 3])
464
- xr_dataset['owiWindFilter'].attrs['flag_values'] = np.array([
465
- 0, 1, 2, 3])
466
- xr_dataset['owiWindFilter'].attrs[
467
- 'flag_meanings'] = "homogeneous_NRCS, heterogeneous_from_co-polarization_NRCS, heterogeneous_from_cross-polarization_NRCS, heterogeneous_from_dual-polarization_NRCS"
468
- xr_dataset['owiWindFilter'].attrs['comment'] = 'NOT COMPUTED YET'
469
-
470
486
  xr_dataset = xr_dataset.rename(
471
487
  {"line": "owiAzSize", "sample": "owiRaSize"})
472
488
 
@@ -476,8 +492,6 @@ def makeL2asOwi(xr_dataset, dual_pol, copol, crosspol, add_streaks, apply_flatte
476
492
  xr_dataset = xr_dataset.drop_vars(["sigma0_raw__corrected"])
477
493
  xr_dataset = xr_dataset.drop_dims(['pol'])
478
494
 
479
- xr_dataset.compute()
480
-
481
495
  table_fillValue = {
482
496
  "owiWindQuality": -1,
483
497
  "owiHeading": 9999.99,
@@ -505,7 +519,7 @@ def makeL2asOwi(xr_dataset, dual_pol, copol, crosspol, add_streaks, apply_flatte
505
519
  return xr_dataset, encoding
506
520
 
507
521
 
508
- def preprocess(filename, outdir, config_path, overwrite=False, add_streaks=False, resolution='1000m'):
522
+ def preprocess(filename, outdir, config_path, overwrite=False, add_gradientsfeatures=False, resolution='1000m'):
509
523
  """
510
524
  Main function to generate L2 product.
511
525
 
@@ -549,6 +563,10 @@ def preprocess(filename, outdir, config_path, overwrite=False, add_streaks=False
549
563
  recalibration = config["recalibration"]
550
564
  meta = fct_meta(filename)
551
565
 
566
+ # si une des deux n'est pas VV VH HH HV on ne fait rien
567
+ if not all([pol in ["VV", "VH", "HH", "HV"] for pol in meta.pols.split(' ')]):
568
+ raise ValueError(f"Polarisation non gérée : meta.pols = {meta.pols}")
569
+
552
570
  no_subdir_cfg = config_base.get("no_subdir", False)
553
571
  config["no_subdir"] = no_subdir_cfg
554
572
 
@@ -560,6 +578,26 @@ def preprocess(filename, outdir, config_path, overwrite=False, add_streaks=False
560
578
  f'Using meteorological convention because "winddir_convention" was not found in config.')
561
579
  config["winddir_convention"] = winddir_convention
562
580
 
581
+ if "add_gradientsfeatures" in config_base:
582
+ add_gradientsfeatures = config_base["add_gradientsfeatures"]
583
+ else:
584
+ add_gradientsfeatures = False
585
+ logging.warning(
586
+ f'Not computing gradients by default')
587
+ config["add_gradientsfeatures"] = add_gradientsfeatures
588
+
589
+ if "add_nrcs_model" in config_base:
590
+ add_nrcs_model = config_base["add_nrcs_model"]
591
+ add_nrcs_model = False
592
+ logging.warning(
593
+ f'Force this variable to be false, before fixing the issue'
594
+ )
595
+ else:
596
+ add_nrcs_model = False
597
+ logging.warning(
598
+ f'Not computing nrcs from model by default')
599
+ config["add_nrcs_model"] = add_nrcs_model
600
+
563
601
  # creating a dictionnary of parameters
564
602
  config["l2_params"] = {}
565
603
 
@@ -607,6 +645,11 @@ def preprocess(filename, outdir, config_path, overwrite=False, add_streaks=False
607
645
  logging.error(e)
608
646
  sys.exit(-1)
609
647
 
648
+ #  add parameters in config
649
+ config["meta"] = meta
650
+ config["fct_dataset"] = fct_dataset
651
+ config["map_model"] = map_model
652
+
610
653
  # load
611
654
  xr_dataset = xr_dataset.load()
612
655
 
@@ -642,6 +685,7 @@ def preprocess(filename, outdir, config_path, overwrite=False, add_streaks=False
642
685
  config["l2_params"]["model_co"] = model_co
643
686
  config["l2_params"]["model_cross"] = model_cross
644
687
  config["sensor_longname"] = sensor_longname
688
+ config["sensor"] = sensor
645
689
 
646
690
  # need to load LUTs before inversion
647
691
  nc_luts = [x for x in [model_co, model_cross] if x.startswith("nc_lut")]
@@ -796,41 +840,155 @@ def preprocess(filename, outdir, config_path, overwrite=False, add_streaks=False
796
840
  xr_dataset.attrs["path_aux_cal_old"] = os.path.basename(os.path.dirname(
797
841
  os.path.dirname(xsar_dataset.datatree['recalibration'].attrs['path_aux_cal_old'])))
798
842
 
799
- if add_streaks:
800
- xsar_dataset_100 = fct_dataset(
801
- meta, resolution='100m')
802
- xr_dataset_100 = xsar_dataset_100.datatree['measurement'].to_dataset()
803
- xr_dataset_100 = xr_dataset_100.rename(map_model)
804
-
805
- # adding sigma0 detrend
806
- xr_dataset_100['sigma0_detrend'] = xsarsea.sigma0_detrend(
807
- xr_dataset_100.sigma0.sel(pol=copol), xr_dataset_100.incidence, model=model_co)
843
+ if add_nrcs_model:
844
+ # add timing
845
+ phi = np.abs(
846
+ np.rad2deg(xsarsea.dir_meteo_to_sample(
847
+ xr_dataset["ancillary_wind_direction"], xr_dataset["ground_heading"]))
848
+ )
808
849
 
850
+ varnames = ["ancillary_nrcs"]
851
+ gmf_names = [model_co]
809
852
  if dual_pol:
810
- xr_dataset_100['sigma0_detrend_cross'] = xsarsea.sigma0_detrend(
811
- xr_dataset_100.sigma0.sel(pol=crosspol), xr_dataset_100.incidence, model=model_cross)
853
+ varnames.append("ancillary_nrcs_cross")
854
+ gmf_names.append(model_cross)
812
855
 
813
- sigma0_detrend_combined = xr.concat(
814
- [xr_dataset_100['sigma0_detrend'],
815
- xr_dataset_100['sigma0_detrend_cross']],
816
- dim='pol'
817
- )
818
- sigma0_detrend_combined['pol'] = [copol, crosspol]
819
-
820
- xr_dataset_100['sigma0_detrend'] = sigma0_detrend_combined
856
+ for idx, gmf_name in enumerate(gmf_names):
821
857
 
822
- xr_dataset_100.land_mask.values = binary_dilation(xr_dataset_100['land_mask'].values.astype('uint8'),
823
- structure=np.ones((3, 3), np.uint8), iterations=3)
824
- xr_dataset_100['sigma0_detrend'] = xr.where(
825
- xr_dataset_100['land_mask'], np.nan, xr_dataset_100['sigma0']).transpose(*xr_dataset_100['sigma0'].dims)
858
+ @timing(logger=logger.info)
859
+ def apply_lut_to_dataset():
860
+ lut = xsarsea.windspeed.get_model(
861
+ gmf_name).to_lut(unit="linear")
826
862
 
827
- xr_dataset['streaks_direction'] = get_streaks(
828
- xr_dataset, xr_dataset_100)
863
+ def lut_selection(incidence, wspd, phi):
864
+ if "phi" in lut.coords:
865
+ return lut.sel(
866
+ incidence=incidence, wspd=wspd, phi=phi, method="nearest"
867
+ )
868
+ else:
869
+ return lut.sel(
870
+ incidence=incidence, wspd=wspd, method="nearest"
871
+ )
872
+
873
+ xr_dataset[varnames[idx]] = xr.apply_ufunc(
874
+ lut_selection,
875
+ xr_dataset.incidence,
876
+ xr_dataset.ancillary_wind_speed,
877
+ phi,
878
+ vectorize=True,
879
+ dask="parallelized",
880
+ output_dtypes=[float],
881
+ )
882
+
883
+ apply_lut_to_dataset()
829
884
 
830
885
  return xr_dataset, out_file, config
831
886
 
832
887
 
833
- def makeL2(filename, outdir, config_path, overwrite=False, generateCSV=True, add_streaks=False, resolution='1000m'):
888
+ def process_gradients(xr_dataset, config):
889
+ """
890
+ Function to process gradients features.
891
+
892
+ Parameters
893
+ ----------
894
+ xr_dataset : xarray.Dataset
895
+ Main dataset to process.
896
+ meta : object
897
+ Metadata from the original dataset.
898
+ fct_dataset : callable
899
+ Function to load the dataset.
900
+ map_model : dict
901
+ Mapping model for renaming variables.
902
+ config : dict
903
+ Configuration dictionary.
904
+
905
+ Returns
906
+ -------
907
+ tuple
908
+ Updated xr_dataset and xr_dataset_streaks dataset.
909
+ """
910
+ from grdwindinversion.gradientFeatures import GradientFeatures
911
+
912
+ meta = config["meta"]
913
+ fct_dataset = config["fct_dataset"]
914
+ map_model = config["map_model"]
915
+
916
+ model_co = config["l2_params"]["model_co"]
917
+ model_cross = config["l2_params"]["model_cross"]
918
+ copol = config["l2_params"]["copol"]
919
+ crosspol = config["l2_params"]["crosspol"]
920
+ dual_pol = config["l2_params"]["dual_pol"]
921
+
922
+ # Load the 100m dataset
923
+ xsar_dataset_100 = fct_dataset(
924
+ meta, resolution='100m')
925
+
926
+ xr_dataset_100 = xsar_dataset_100.datatree['measurement'].to_dataset()
927
+ xr_dataset_100 = xr_dataset_100.rename(map_model)
928
+ # load dataset
929
+ xr_dataset_100 = xr_dataset_100.load()
930
+
931
+ # adding sigma0 detrend
932
+ xr_dataset_100['sigma0_detrend'] = xsarsea.sigma0_detrend(
933
+ xr_dataset_100.sigma0.sel(pol=copol), xr_dataset_100.incidence, model=model_co)
934
+
935
+ if dual_pol:
936
+ xr_dataset_100['sigma0_detrend_cross'] = xsarsea.sigma0_detrend(
937
+ xr_dataset_100.sigma0.sel(pol=crosspol), xr_dataset_100.incidence, model=model_cross)
938
+
939
+ sigma0_detrend_combined = xr.concat(
940
+ [xr_dataset_100['sigma0_detrend'],
941
+ xr_dataset_100['sigma0_detrend_cross']],
942
+ dim='pol'
943
+ )
944
+ sigma0_detrend_combined['pol'] = [copol, crosspol]
945
+
946
+ xr_dataset_100['sigma0_detrend'] = sigma0_detrend_combined
947
+
948
+ xr_dataset_100.land_mask.values = binary_dilation(xr_dataset_100['land_mask'].values.astype('uint8'),
949
+ structure=np.ones((3, 3), np.uint8), iterations=3)
950
+ xr_dataset_100['sigma0_detrend'] = xr.where(
951
+ xr_dataset_100['land_mask'], np.nan, xr_dataset_100['sigma0']).transpose(*xr_dataset_100['sigma0'].dims)
952
+
953
+ xr_dataset_100['ancillary_wind'] = (
954
+ xr_dataset_100.model_U10 + 1j * xr_dataset_100.model_V10) * np.exp(1j * np.deg2rad(xr_dataset_100.ground_heading))
955
+
956
+ downscales_factors = [1, 2, 4, 8]
957
+ # 4 and 8 must be in downscales_factors
958
+ assert all([x in downscales_factors for x in [4, 8]])
959
+
960
+ gradientFeatures = GradientFeatures(
961
+ xr_dataset=xr_dataset,
962
+ xr_dataset_100=xr_dataset_100,
963
+ windows_sizes=[1600, 3200],
964
+ downscales_factors=downscales_factors,
965
+ window_step=1
966
+ )
967
+
968
+ # Compute heterogeneity mask and variables
969
+ dataArraysHeterogeneity = gradientFeatures.get_heterogeneity_mask(config)
970
+ xr_dataset = xr_dataset.merge(dataArraysHeterogeneity)
971
+
972
+ # Add streaks dataset
973
+ streaks_indiv = gradientFeatures.streaks_individual()
974
+ if 'longitude' in streaks_indiv:
975
+ xr_dataset_streaks = xr.Dataset({
976
+ 'longitude': streaks_indiv.longitude,
977
+ 'latitude': streaks_indiv.latitude,
978
+ 'dir_smooth': streaks_indiv.angle,
979
+ 'dir_mean_smooth': gradientFeatures.streaks_mean_smooth().angle,
980
+ 'dir_smooth_mean': gradientFeatures.streaks_smooth_mean().angle,
981
+ })
982
+ else:
983
+ logger.warn(
984
+ "'longitude' not found in streaks_indiv : there is probably an error")
985
+ xr_dataset_streaks = None
986
+
987
+ return xr_dataset, xr_dataset_streaks
988
+
989
+
990
+ @timing(logger=logger.info)
991
+ def makeL2(filename, outdir, config_path, overwrite=False, generateCSV=True, resolution='1000m'):
834
992
  """
835
993
  Main function to generate L2 product.
836
994
 
@@ -858,7 +1016,13 @@ def makeL2(filename, outdir, config_path, overwrite=False, generateCSV=True, add
858
1016
  """
859
1017
 
860
1018
  xr_dataset, out_file, config = preprocess(
861
- filename, outdir, config_path, overwrite, add_streaks, resolution)
1019
+ filename, outdir, config_path, overwrite, resolution)
1020
+
1021
+ if config["add_gradientsfeatures"]:
1022
+ xr_dataset, xr_dataset_streaks = process_gradients(
1023
+ xr_dataset, config)
1024
+ else:
1025
+ xr_dataset_streaks = None
862
1026
 
863
1027
  model_co = config["l2_params"]["model_co"]
864
1028
  model_cross = config["l2_params"]["model_cross"]
@@ -951,8 +1115,9 @@ def makeL2(filename, outdir, config_path, overwrite=False, generateCSV=True, add
951
1115
  "long_name"] = f"{ancillary_name} wind direction in oceanographic convention (clockwise, to), ex: 0°=to north, 90°=to east"
952
1116
 
953
1117
  xr_dataset, encoding = makeL2asOwi(
954
- xr_dataset, dual_pol, copol, crosspol, add_streaks=add_streaks, apply_flattening=config["apply_flattening"])
1118
+ xr_dataset, config)
955
1119
 
1120
+ xr_dataset = xr_dataset.compute()
956
1121
  #  add attributes
957
1122
  firstMeasurementTime = None
958
1123
  lastMeasurementTime = None
@@ -1035,7 +1200,26 @@ def makeL2(filename, outdir, config_path, overwrite=False, generateCSV=True, add
1035
1200
 
1036
1201
  os.makedirs(os.path.dirname(out_file), exist_ok=True)
1037
1202
 
1203
+ # Sauvegarde de xr_dataset dans le fichier de sortie final
1038
1204
  xr_dataset.to_netcdf(out_file, mode="w", encoding=encoding)
1205
+
1206
+ # Vérifier si le dataset de streaks est présent
1207
+ if xr_dataset_streaks is not None:
1208
+ # Créer un fichier temporaire pour le dataset streaks
1209
+ with tempfile.NamedTemporaryFile(delete=False, suffix=".nc") as tmp_file:
1210
+ temp_out_file = tmp_file.name
1211
+
1212
+ # Écrire xr_dataset_streaks dans le fichier temporaire
1213
+ xr_dataset_streaks.to_netcdf(
1214
+ temp_out_file, mode="w", group="owiWindStreaks")
1215
+
1216
+ # Charger le fichier temporaire et l'ajouter au fichier final en tant que groupe
1217
+ with xr.open_dataset(temp_out_file, group="owiWindStreaks") as ds_streaks:
1218
+ ds_streaks.to_netcdf(out_file, mode="a", group="owiWindStreaks")
1219
+
1220
+ # Supprimer le fichier temporaire après l'opération
1221
+ os.remove(temp_out_file)
1222
+
1039
1223
  if generateCSV:
1040
1224
  df = xr_dataset.to_dataframe()
1041
1225
  df = df[df.owiMask == False]
@@ -3,11 +3,14 @@ import logging
3
3
  import os
4
4
  import grdwindinversion
5
5
  from yaml import CLoader as Loader
6
- local_config_potential_path = os.path.expanduser(
6
+ local_config_potential_path1 = os.path.expanduser(
7
7
  '~/.grdwindinversion/data_config.yaml')
8
-
9
- if os.path.exists(local_config_potential_path):
10
- config_path = local_config_potential_path
8
+ local_config_potential_path2 = os.path.join(os.path.dirname(
9
+ grdwindinversion.__file__), 'local_data_config.yaml')
10
+ if os.path.exists(local_config_potential_path1):
11
+ config_path = local_config_potential_path1
12
+ elif os.path.exists(local_config_potential_path2):
13
+ config_path = local_config_potential_path2
11
14
  else:
12
15
  config_path = os.path.join(os.path.dirname(
13
16
  grdwindinversion.__file__), 'data_config.yaml')
grdwindinversion/utils.py CHANGED
@@ -1,7 +1,22 @@
1
+ import os
2
+ import time
1
3
  import logging
2
4
  import xsarsea
3
5
 
4
6
 
7
+ logging.basicConfig(level=logging.INFO,
8
+ format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')
9
+ logger = logging.getLogger('grdwindinversion')
10
+
11
+
12
+ mem_monitor = True
13
+ try:
14
+ from psutil import Process
15
+ except ImportError:
16
+ logger.warning("psutil module not found. Disabling memory monitor")
17
+ mem_monitor = False
18
+
19
+
5
20
  def check_incidence_range(incidence, models, **kwargs):
6
21
  """
7
22
  Check if the incidence range of the dataset is within the range of the LUT of the model.
@@ -81,3 +96,28 @@ def get_pol_ratio_name(model_co):
81
96
  return "not_written_in_lut"
82
97
  else:
83
98
  return '/'
99
+
100
+
101
+ def timing(logger=logger.debug):
102
+ """provide a @timing decorator() for functions, that log time spent in it"""
103
+
104
+ def decorator(f):
105
+ # @wraps(f)
106
+ def wrapper(*args, **kwargs):
107
+ mem_str = ''
108
+ process = None
109
+ if mem_monitor:
110
+ process = Process(os.getpid())
111
+ startrss = process.memory_info().rss
112
+ starttime = time.time()
113
+ result = f(*args, **kwargs)
114
+ endtime = time.time()
115
+ if mem_monitor:
116
+ endrss = process.memory_info().rss
117
+ mem_str = 'mem: %+.1fMb' % ((endrss - startrss) / (1024 ** 2))
118
+ logger(
119
+ 'timing %s : %.2fs. %s' % (f.__name__, endtime - starttime, mem_str))
120
+ return result
121
+ wrapper.__doc__ = f.__doc__
122
+ return wrapper
123
+ return decorator