grdwindinversion 0.2.3.post13__tar.gz → 0.2.3.post15__tar.gz

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.
Files changed (55) hide show
  1. {grdwindinversion-0.2.3.post13 → grdwindinversion-0.2.3.post15}/.github/workflows/publish.yml +6 -6
  2. {grdwindinversion-0.2.3.post13/grdwindinversion.egg-info → grdwindinversion-0.2.3.post15}/PKG-INFO +1 -1
  3. {grdwindinversion-0.2.3.post13 → grdwindinversion-0.2.3.post15}/grdwindinversion/config_prod.yaml +17 -5
  4. {grdwindinversion-0.2.3.post13 → grdwindinversion-0.2.3.post15}/grdwindinversion/config_prod_recal.yaml +1 -0
  5. {grdwindinversion-0.2.3.post13 → grdwindinversion-0.2.3.post15}/grdwindinversion/inversion.py +107 -53
  6. grdwindinversion-0.2.3.post15/grdwindinversion/streaks.py +79 -0
  7. {grdwindinversion-0.2.3.post13 → grdwindinversion-0.2.3.post15/grdwindinversion.egg-info}/PKG-INFO +1 -1
  8. {grdwindinversion-0.2.3.post13 → grdwindinversion-0.2.3.post15}/grdwindinversion.egg-info/SOURCES.txt +2 -0
  9. grdwindinversion-0.2.3.post15/recipe/meta.yaml +34 -0
  10. {grdwindinversion-0.2.3.post13 → grdwindinversion-0.2.3.post15}/.editorconfig +0 -0
  11. {grdwindinversion-0.2.3.post13 → grdwindinversion-0.2.3.post15}/.github/dependabot.yml +0 -0
  12. {grdwindinversion-0.2.3.post13 → grdwindinversion-0.2.3.post15}/.gitignore +0 -0
  13. {grdwindinversion-0.2.3.post13 → grdwindinversion-0.2.3.post15}/.pre-commit-config.yaml +0 -0
  14. {grdwindinversion-0.2.3.post13 → grdwindinversion-0.2.3.post15}/AUTHORS.rst +0 -0
  15. {grdwindinversion-0.2.3.post13 → grdwindinversion-0.2.3.post15}/CONTRIBUTING.rst +0 -0
  16. {grdwindinversion-0.2.3.post13 → grdwindinversion-0.2.3.post15}/HISTORY.rst +0 -0
  17. {grdwindinversion-0.2.3.post13 → grdwindinversion-0.2.3.post15}/LICENSE +0 -0
  18. {grdwindinversion-0.2.3.post13 → grdwindinversion-0.2.3.post15}/MANIFEST.in +0 -0
  19. {grdwindinversion-0.2.3.post13 → grdwindinversion-0.2.3.post15}/Makefile +0 -0
  20. {grdwindinversion-0.2.3.post13 → grdwindinversion-0.2.3.post15}/README.md +0 -0
  21. {grdwindinversion-0.2.3.post13 → grdwindinversion-0.2.3.post15}/ci/requirements/docs.yaml +0 -0
  22. {grdwindinversion-0.2.3.post13 → grdwindinversion-0.2.3.post15}/ci/requirements/environment.yaml +0 -0
  23. {grdwindinversion-0.2.3.post13 → grdwindinversion-0.2.3.post15}/docs/Makefile +0 -0
  24. {grdwindinversion-0.2.3.post13 → grdwindinversion-0.2.3.post15}/docs/_static/css/grdwindinversion.css +0 -0
  25. {grdwindinversion-0.2.3.post13 → grdwindinversion-0.2.3.post15}/docs/algorithm.rst +0 -0
  26. {grdwindinversion-0.2.3.post13 → grdwindinversion-0.2.3.post15}/docs/authors.rst +0 -0
  27. {grdwindinversion-0.2.3.post13 → grdwindinversion-0.2.3.post15}/docs/conf.py +0 -0
  28. {grdwindinversion-0.2.3.post13 → grdwindinversion-0.2.3.post15}/docs/contributing.rst +0 -0
  29. {grdwindinversion-0.2.3.post13 → grdwindinversion-0.2.3.post15}/docs/examples/wind-inversion-from-grd.ipynb +0 -0
  30. {grdwindinversion-0.2.3.post13 → grdwindinversion-0.2.3.post15}/docs/history.rst +0 -0
  31. {grdwindinversion-0.2.3.post13 → grdwindinversion-0.2.3.post15}/docs/index.rst +0 -0
  32. {grdwindinversion-0.2.3.post13 → grdwindinversion-0.2.3.post15}/docs/installation.rst +0 -0
  33. {grdwindinversion-0.2.3.post13 → grdwindinversion-0.2.3.post15}/docs/make.bat +0 -0
  34. {grdwindinversion-0.2.3.post13 → grdwindinversion-0.2.3.post15}/docs/modules.rst +0 -0
  35. {grdwindinversion-0.2.3.post13 → grdwindinversion-0.2.3.post15}/docs/readme.rst +0 -0
  36. {grdwindinversion-0.2.3.post13 → grdwindinversion-0.2.3.post15}/docs/usage.rst +0 -0
  37. {grdwindinversion-0.2.3.post13 → grdwindinversion-0.2.3.post15}/grdwindinversion/.github/ISSUE_TEMPLATE.md +0 -0
  38. {grdwindinversion-0.2.3.post13 → grdwindinversion-0.2.3.post15}/grdwindinversion/.gitignore +0 -0
  39. {grdwindinversion-0.2.3.post13 → grdwindinversion-0.2.3.post15}/grdwindinversion/.travis.yml +0 -0
  40. {grdwindinversion-0.2.3.post13 → grdwindinversion-0.2.3.post15}/grdwindinversion/__init__.py +0 -0
  41. {grdwindinversion-0.2.3.post13 → grdwindinversion-0.2.3.post15}/grdwindinversion/data_config.yaml +0 -0
  42. {grdwindinversion-0.2.3.post13 → grdwindinversion-0.2.3.post15}/grdwindinversion/load_config.py +0 -0
  43. {grdwindinversion-0.2.3.post13 → grdwindinversion-0.2.3.post15}/grdwindinversion/main.py +0 -0
  44. {grdwindinversion-0.2.3.post13 → grdwindinversion-0.2.3.post15}/grdwindinversion/utils.py +0 -0
  45. {grdwindinversion-0.2.3.post13 → grdwindinversion-0.2.3.post15}/grdwindinversion.egg-info/dependency_links.txt +0 -0
  46. {grdwindinversion-0.2.3.post13 → grdwindinversion-0.2.3.post15}/grdwindinversion.egg-info/entry_points.txt +0 -0
  47. {grdwindinversion-0.2.3.post13 → grdwindinversion-0.2.3.post15}/grdwindinversion.egg-info/requires.txt +0 -0
  48. {grdwindinversion-0.2.3.post13 → grdwindinversion-0.2.3.post15}/grdwindinversion.egg-info/top_level.txt +0 -0
  49. {grdwindinversion-0.2.3.post13 → grdwindinversion-0.2.3.post15}/pyproject.toml +0 -0
  50. {grdwindinversion-0.2.3.post13 → grdwindinversion-0.2.3.post15}/requirements_dev.txt +0 -0
  51. {grdwindinversion-0.2.3.post13 → grdwindinversion-0.2.3.post15}/requirements_doc.txt +0 -0
  52. {grdwindinversion-0.2.3.post13 → grdwindinversion-0.2.3.post15}/setup.cfg +0 -0
  53. {grdwindinversion-0.2.3.post13 → grdwindinversion-0.2.3.post15}/tests/__init__.py +0 -0
  54. {grdwindinversion-0.2.3.post13 → grdwindinversion-0.2.3.post15}/tests/test_grdwindinversion.py +0 -0
  55. {grdwindinversion-0.2.3.post13 → grdwindinversion-0.2.3.post15}/tox.ini +0 -0
@@ -11,9 +11,9 @@ jobs:
11
11
  if: github.repository == 'umr-lops/grdwindinversion'
12
12
  steps:
13
13
  - name: Checkout
14
- uses: actions/checkout@v3
14
+ uses: actions/checkout@v4
15
15
  - name: Set up Python
16
- uses: actions/setup-python@v4
16
+ uses: actions/setup-python@v5
17
17
  with:
18
18
  python-version: "3.x"
19
19
  - name: Install dependencies
@@ -22,12 +22,12 @@ jobs:
22
22
  python -m pip install build twine
23
23
  - name: Build
24
24
  run: |
25
- python -m build --sdist --outdir dist/ .
25
+ python -m build --sdist --wheel --outdir dist/ .
26
26
  - name: Check the built archives
27
27
  run: |
28
28
  twine check dist/*
29
29
  - name: Upload build artifacts
30
- uses: actions/upload-artifact@v3
30
+ uses: actions/upload-artifact@v4
31
31
  with:
32
32
  name: packages
33
33
  path: dist/*
@@ -45,10 +45,10 @@ jobs:
45
45
 
46
46
  steps:
47
47
  - name: Download build artifacts
48
- uses: actions/download-artifact@v3
48
+ uses: actions/download-artifact@v4
49
49
  with:
50
50
  name: packages
51
51
  path: dist/
52
52
 
53
53
  - name: Publish to PyPI
54
- uses: pypa/gh-action-pypi-publish@b7f401de30cb6434a1e19f805ff006643653240e
54
+ uses: pypa/gh-action-pypi-publish@81e9d935c883d0b210363ab89cf05f3894778450
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: grdwindinversion
3
- Version: 0.2.3.post13
3
+ Version: 0.2.3.post15
4
4
  Summary: Package to perform Wind inversion from GRD Level-1 SAR images
5
5
  License: MIT
6
6
  Classifier: Development Status :: 2 - Pre-Alpha
@@ -1,3 +1,4 @@
1
+ no_subdir: True
1
2
  S1A:
2
3
  GMF_VV_NAME: "gmf_cmod5n"
3
4
  GMF_VH_NAME: "gmf_s1_v2"
@@ -5,6 +6,10 @@ S1A:
5
6
  apply_flattening: True
6
7
  recalibration: False
7
8
  ancillary: "ecmwf"
9
+ inc_step: 0.1
10
+ wspd_step: 0.1
11
+ phi_step: 1.0
12
+ resolution: "high"
8
13
  S1B:
9
14
  GMF_VV_NAME: "gmf_cmod5n"
10
15
  GMF_VH_NAME: "gmf_s1_v2"
@@ -12,6 +17,10 @@ S1B:
12
17
  apply_flattening: True
13
18
  recalibration: False
14
19
  ancillary: "ecmwf"
20
+ inc_step: 0.1
21
+ wspd_step: 0.1
22
+ phi_step: 1.0
23
+ resolution: "high"
15
24
  RS2:
16
25
  GMF_VV_NAME: "gmf_cmod5n"
17
26
  GMF_VH_NAME: "gmf_rs2_v2"
@@ -19,6 +28,10 @@ RS2:
19
28
  apply_flattening: False
20
29
  recalibration: False
21
30
  ancillary: "ecmwf"
31
+ inc_step: 0.1
32
+ wspd_step: 0.1
33
+ phi_step: 1.0
34
+ resolution: "high"
22
35
  RCM:
23
36
  GMF_VV_NAME: "gmf_cmod5n"
24
37
  GMF_VH_NAME: "gmf_rcm_noaa"
@@ -26,8 +39,7 @@ RCM:
26
39
  apply_flattening: True
27
40
  recalibration: False
28
41
  ancillary: "ecmwf"
29
-
30
- inc_step: 0.1
31
- wspd_step: 0.1
32
- phi_step: 1.0
33
- resolution: "high"
42
+ inc_step: 0.1
43
+ wspd_step: 0.1
44
+ phi_step: 1.0
45
+ resolution: "high"
@@ -1,3 +1,4 @@
1
+ no_subdir: True
1
2
  S1A:
2
3
  GMF_VV_NAME: "gmf_cmod5n"
3
4
  GMF_VH_NAME: "gmf_s1_v2"
@@ -15,6 +15,7 @@ from scipy.ndimage import binary_dilation
15
15
  import re
16
16
  import string
17
17
  import os
18
+ from grdwindinversion.streaks import get_streaks
18
19
  from grdwindinversion.load_config import getConf
19
20
  # optional debug messages
20
21
  import logging
@@ -49,7 +50,7 @@ def getSensorMetaDataset(filename):
49
50
  raise ValueError("must be S1A|S1B|RS2|RCM, got filename %s" % filename)
50
51
 
51
52
 
52
- def getOutputName2(input_file, outdir, sensor, meta):
53
+ def getOutputName2(input_file, outdir, sensor, meta, subdir=True):
53
54
  """
54
55
  Create output filename for L2-GRD product
55
56
 
@@ -84,21 +85,14 @@ def getOutputName2(input_file, outdir, sensor, meta):
84
85
  match = regex.match(basename_match)
85
86
  MISSIONID, BEAM, PRODUCT, RESOLUTION, LEVEL, CLASS, POL, STARTDATE, STOPDATE, ORBIT, TAKEID, PRODID = match.groups()
86
87
  new_format = f"{MISSIONID.lower()}-{BEAM.lower()}-owi-xx-{STARTDATE.lower()}-{STOPDATE.lower()}-{ORBIT}-{TAKEID}.nc"
87
- out_file = os.path.join(outdir, basename, new_format)
88
- return out_file
89
-
90
88
  elif sensor == 'RS2':
91
89
  regex = re.compile(
92
90
  "(RS2)_OK([0-9]+)_PK([0-9]+)_DK([0-9]+)_(....)_(........)_(......)_(.._?.?.?)_(S.F)")
93
91
  template = string.Template(
94
92
  "${MISSIONID}_OK${DATA1}_PK${DATA2}_DK${DATA3}_${DATA4}_${DATE}_${TIME}_${POLARIZATION}_${LAST}")
95
93
  match = regex.match(basename_match)
96
-
97
94
  MISSIONID, DATA1, DATA2, DATA3, DATA4, DATE, TIME, POLARIZATION, LAST = match.groups()
98
95
  new_format = f"{MISSIONID.lower()}--owi-xx-{meta_start_date.lower()}-{meta_stop_date.lower()}-_____-_____.nc"
99
- out_file = os.path.join(outdir, basename, new_format)
100
- return out_file
101
-
102
96
  elif sensor == 'RCM':
103
97
  regex = re.compile(
104
98
  "([A-Z0-9]+)_OK([0-9]+)_PK([0-9]+)_(.*?)_(.*?)_(.*?)_(.*?)_(.*?)_(.*?)_(.*?)")
@@ -107,13 +101,16 @@ def getOutputName2(input_file, outdir, sensor, meta):
107
101
  match = regex.match(basename_match)
108
102
  MISSIONID, DATA1, DATA2, DATA3, DATA4, DATE, TIME, POLARIZATION1, POLARIZATION2, LAST = match.groups()
109
103
  new_format = f"{MISSIONID.lower()}--owi-xx-{meta_start_date.lower()}-{meta_stop_date.lower()}-_____-_____.nc"
110
- out_file = os.path.join(outdir, basename, new_format)
111
- return out_file
112
-
113
104
  else:
114
105
  raise ValueError(
115
106
  "sensor must be S1A|S1B|RS2|RCM, got sensor %s" % sensor)
116
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
+
117
114
 
118
115
  def getAncillary(meta, ancillary_name='ecmwf'):
119
116
  """
@@ -227,7 +224,7 @@ def inverse(dual_pol, inc, sigma0, sigma0_dual, ancillary_wind, dsig_cr, model_v
227
224
  sigma0 to be inverted for dualpol
228
225
  ancillary_wind=: xarray.DataArray (numpy.complex28)
229
226
  ancillary wind
230
- | (for example ecmwf winds), in **GMF convention** (-np.conj included),
227
+ | (for example ecmwf winds), in **GMF convention** (-np.conj included),
231
228
  dsig_cr=: float or xarray.DataArray
232
229
  parameters used for
233
230
 
@@ -284,7 +281,7 @@ def inverse(dual_pol, inc, sigma0, sigma0_dual, ancillary_wind, dsig_cr, model_v
284
281
  return wind_co, None, None
285
282
 
286
283
 
287
- def makeL2asOwi(xr_dataset, dual_pol, copol, crosspol):
284
+ def makeL2asOwi(xr_dataset, dual_pol, copol, crosspol, add_streaks):
288
285
  """
289
286
  Rename xr_dataset variables and attributes to match naming convention.
290
287
 
@@ -322,6 +319,7 @@ def makeL2asOwi(xr_dataset, dual_pol, copol, crosspol):
322
319
  'winddir_co': 'owiWindDirection_co',
323
320
  'ancillary_wind_speed': 'owiAncillaryWindSpeed',
324
321
  'ancillary_wind_direction': 'owiAncillaryWindDirection',
322
+ 'sigma0_detrend': 'owiNrcs_detrend'
325
323
  })
326
324
 
327
325
  if "offboresight" in xr_dataset:
@@ -350,6 +348,9 @@ def makeL2asOwi(xr_dataset, dual_pol, copol, crosspol):
350
348
  xr_dataset.owiNrcs.attrs['long_name'] = 'Normalized Radar Cross Section'
351
349
  xr_dataset.owiNrcs.attrs['definition'] = 'owiNrcs_no_noise_correction - owiNesz'
352
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
+
353
354
  # NESZ & DSIG
354
355
  xr_dataset = xr_dataset.assign(
355
356
  owiNesz=(['line', 'sample'], xr_dataset.nesz.sel(pol=copol).values))
@@ -395,14 +396,20 @@ def makeL2asOwi(xr_dataset, dual_pol, copol, crosspol):
395
396
  'winddir_dual': 'owiWindDirection',
396
397
  'windspeed_cross': 'owiWindSpeed_cross',
397
398
  'windspeed_dual': 'owiWindSpeed',
399
+ 'sigma0_detrend_cross': 'owiNrcs_detrend_cross'
398
400
  })
399
401
  # nrcs cross
400
402
  xr_dataset['owiNrcs_cross'] = xr_dataset['sigma0_ocean'].sel(
401
403
  pol=crosspol)
404
+
402
405
  xr_dataset.owiNrcs_cross.attrs['units'] = 'm^2 / m^2'
403
406
  xr_dataset.owiNrcs_cross.attrs['long_name'] = 'Normalized Radar Cross Section'
404
407
  xr_dataset.owiNrcs_cross.attrs['definition'] = 'owiNrcs_cross_no_noise_correction - owiNesz_cross'
405
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
+
406
413
  # nesz cross
407
414
  xr_dataset = xr_dataset.assign(owiNesz_cross=(
408
415
  ['line', 'sample'], xr_dataset.nesz.sel(pol=crosspol).values)) # no flattening
@@ -429,6 +436,11 @@ def makeL2asOwi(xr_dataset, dual_pol, copol, crosspol):
429
436
 
430
437
  xr_dataset.owiNrcs_cross.attrs['definition'] = 'owiNrcs_cross_no_noise_correction_recalibrated - owiNesz_cross'
431
438
 
439
+ if add_streaks:
440
+ xr_dataset = xr_dataset.rename({
441
+ 'streaks_direction': 'owiStreaksDirection',
442
+ })
443
+
432
444
  #  other variables
433
445
 
434
446
  xr_dataset['owiWindQuality'] = xr.full_like(xr_dataset.owiNrcs, 0)
@@ -487,7 +499,7 @@ def makeL2asOwi(xr_dataset, dual_pol, copol, crosspol):
487
499
  return xr_dataset, encoding
488
500
 
489
501
 
490
- def preprocess(filename, outdir, config_path, overwrite=False, resolution='1000m'):
502
+ def preprocess(filename, outdir, config_path, overwrite=False, add_streaks=False, resolution='1000m'):
491
503
  """
492
504
  Main function to generate L2 product.
493
505
 
@@ -507,7 +519,7 @@ def preprocess(filename, outdir, config_path, overwrite=False, resolution='1000m
507
519
  Returns
508
520
  -------
509
521
  xarray.Dataset
510
- final dataset
522
+ final dataset
511
523
  """
512
524
 
513
525
  sensor, sensor_longname, fct_meta, fct_dataset = getSensorMetaDataset(
@@ -529,16 +541,19 @@ def preprocess(filename, outdir, config_path, overwrite=False, resolution='1000m
529
541
 
530
542
  recalibration = config["recalibration"]
531
543
  meta = fct_meta(filename)
532
- out_file = getOutputName2(filename, outdir, sensor, meta)
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)
533
548
 
534
549
  if os.path.exists(out_file) and overwrite is False:
535
- raise FileExistsError("out_file %s exists already")
550
+ raise FileExistsError("outfile %s already exists" % out_file)
536
551
 
537
552
  ancillary_name = config["ancillary"]
538
553
  map_model = getAncillary(meta, ancillary_name)
539
554
  if map_model is None:
540
555
  raise Exception(
541
- f'the weather model is not set `map_model` is None -> you probably don"t have access to f{ancillary_name} archive')
556
+ f"the weather model is not set `map_model` is None -> you probably don't have access to {ancillary_name} archive")
542
557
 
543
558
  try:
544
559
  if ((recalibration) & ("SENTINEL" in sensor_longname)):
@@ -592,6 +607,25 @@ def preprocess(filename, outdir, config_path, overwrite=False, resolution='1000m
592
607
  copol_gmf = 'HH'
593
608
  crosspol_gmf = 'VH'
594
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"])
595
629
  #  Step 2 - clean and prepare dataset
596
630
 
597
631
  # variables to not keep in the L2
@@ -681,21 +715,32 @@ def preprocess(filename, outdir, config_path, overwrite=False, resolution='1000m
681
715
  # nrcs processing
682
716
  xr_dataset['sigma0_ocean'] = xr.where(xr_dataset['mask'], np.nan,
683
717
  xr_dataset['sigma0'].compute()).transpose(*xr_dataset['sigma0'].dims)
684
- xr_dataset['sigma0_ocean'] = xr.where(
685
- xr_dataset['sigma0_ocean'] <= 0, np.nan, xr_dataset['sigma0_ocean'])
686
-
687
718
  xr_dataset['sigma0_ocean'].attrs = xr_dataset['sigma0'].attrs
688
- #  we forced it to nan
689
- xr_dataset['sigma0_ocean'].attrs['comment'] = "clipped, no values <=0"
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'])
690
730
 
691
731
  xr_dataset['sigma0_ocean_raw'] = xr.where(xr_dataset['mask'], np.nan,
692
732
  xr_dataset['sigma0_raw'].compute()).transpose(*xr_dataset['sigma0_raw'].dims)
693
- xr_dataset['sigma0_ocean_raw'] = xr.where(
694
- xr_dataset['sigma0_ocean_raw'] <= 0, np.nan, xr_dataset['sigma0_ocean_raw'])
733
+
695
734
  xr_dataset['sigma0_ocean_raw'].attrs = xr_dataset['sigma0_raw'].attrs
696
735
 
736
+ xr_dataset['sigma0_detrend'] = xsarsea.sigma0_detrend(
737
+ xr_dataset.sigma0.sel(pol=copol), xr_dataset.incidence, model=model_vv)
738
+
697
739
  # processing
698
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)
699
744
  if config["apply_flattening"]:
700
745
  xr_dataset = xr_dataset.assign(nesz_cross_final=(
701
746
  ['line', 'sample'], windspeed.nesz_flattening(xr_dataset.nesz.sel(pol=crosspol), xr_dataset.incidence)))
@@ -721,24 +766,50 @@ def preprocess(filename, outdir, config_path, overwrite=False, resolution='1000m
721
766
  sigma0_ocean_cross = None
722
767
  dsig_cross = 0.1 # default value set in xsarsea
723
768
 
724
- model_vv = config["GMF_"+copol_gmf+"_NAME"]
725
- model_vh = config["GMF_"+crosspol_gmf+"_NAME"]
726
-
727
769
  if ((recalibration) & ("SENTINEL" in sensor_longname)):
728
- xr_dataset["path_aux_pp1_new"] = os.path.basename(os.path.dirname(
770
+ xr_dataset.attrs["path_aux_pp1_new"] = os.path.basename(os.path.dirname(
729
771
  os.path.dirname(xsar_dataset.datatree['recalibration'].attrs['path_aux_pp1_new'])))
730
- xr_dataset["path_aux_cal_new"] = os.path.basename(os.path.dirname(
772
+ xr_dataset.attrs["path_aux_cal_new"] = os.path.basename(os.path.dirname(
731
773
  os.path.dirname(xsar_dataset.datatree['recalibration'].attrs['path_aux_cal_new'])))
732
774
 
733
- xr_dataset["path_aux_pp1_old"] = os.path.basename(os.path.dirname(
775
+ xr_dataset.attrs["path_aux_pp1_old"] = os.path.basename(os.path.dirname(
734
776
  os.path.dirname(xsar_dataset.datatree['recalibration'].attrs['path_aux_pp1_old'])))
735
- xr_dataset["path_aux_cal_old"] = os.path.basename(os.path.dirname(
777
+ xr_dataset.attrs["path_aux_cal_old"] = os.path.basename(os.path.dirname(
736
778
  os.path.dirname(xsar_dataset.datatree['recalibration'].attrs['path_aux_cal_old'])))
737
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
+
738
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
739
810
 
740
811
 
741
- def makeL2(filename, outdir, config_path, overwrite=False, generateCSV=True, resolution='1000m'):
812
+ def makeL2(filename, outdir, config_path, overwrite=False, generateCSV=True, add_streaks=False, resolution='1000m'):
742
813
  """
743
814
  Main function to generate L2 product.
744
815
 
@@ -766,7 +837,7 @@ def makeL2(filename, outdir, config_path, overwrite=False, generateCSV=True, res
766
837
  """
767
838
 
768
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(
769
- filename, outdir, config_path, overwrite, resolution)
840
+ filename, outdir, config_path, overwrite, add_streaks, resolution)
770
841
 
771
842
  kwargs = {
772
843
  "inc_step_lr": config.pop("inc_step_lr", None),
@@ -778,23 +849,6 @@ def makeL2(filename, outdir, config_path, overwrite=False, generateCSV=True, res
778
849
  "resolution": config.pop("resolution", None),
779
850
  }
780
851
 
781
- # need to load gmfs before
782
-
783
- gmfs_impl = [x for x in [model_vv, model_vh] if "gmf_" in x]
784
- windspeed.gmfs.GmfModel.activate_gmfs_impl(gmfs_impl)
785
- sarwings_luts = [x for x in [model_vv, model_vh]
786
- if x.startswith("sarwing_lut_")]
787
- if len(sarwings_luts) > 0:
788
- windspeed.register_sarwing_luts(getConf()["sarwing_luts_path"])
789
-
790
- nc_luts = [x for x in [model_vv, model_vh] if x.startswith("nc_lut")]
791
-
792
- if len(nc_luts) > 0:
793
- windspeed.register_nc_luts(getConf()["nc_luts_path"])
794
-
795
- if (model_vv == "gmf_cmod7"):
796
- windspeed.register_cmod7(getConf()["lut_cmod7_path"])
797
-
798
852
  wind_co, wind_dual, windspeed_cr = inverse(dual_pol,
799
853
  inc=xr_dataset['incidence'],
800
854
  sigma0=xr_dataset['sigma0_ocean'].sel(
@@ -857,7 +911,7 @@ def makeL2(filename, outdir, config_path, overwrite=False, generateCSV=True, res
857
911
  xr_dataset["winddir_cross"].attrs["model"] = "No model used ; content is a copy of dualpol wind direction"
858
912
 
859
913
  xr_dataset, encoding = makeL2asOwi(
860
- xr_dataset, dual_pol, copol, crosspol)
914
+ xr_dataset, dual_pol, copol, crosspol, add_streaks=add_streaks)
861
915
 
862
916
  #  add attributes
863
917
  firstMeasurementTime = None
@@ -910,7 +964,7 @@ def makeL2(filename, outdir, config_path, overwrite=False, generateCSV=True, res
910
964
  }
911
965
 
912
966
  for recalib_attrs in ["path_aux_pp1_new", 'path_aux_pp1_old', "path_aux_cal_new", "path_aux_cal_old"]:
913
- if recalib_attrs in xr_dataset:
967
+ if recalib_attrs in xr_dataset.attrs:
914
968
  attrs[recalib_attrs] = xr_dataset.attrs[recalib_attrs]
915
969
 
916
970
  # new one to match convention
@@ -0,0 +1,79 @@
1
+ import xarray as xr
2
+ import xsarsea.gradients
3
+ import xarray as xr
4
+ from scipy.ndimage import binary_dilation
5
+ import numpy as np
6
+
7
+
8
+ def get_streaks(xr_dataset, xr_dataset_100):
9
+ """
10
+ Get the streaks from the wind field.
11
+
12
+ Parameters
13
+ ----------
14
+ xr_dataset : xarray.Dataset
15
+ dataset at user resolution.
16
+ xr_dataset_100 : xarray.Dataset
17
+ dataset at 100m resolution.
18
+
19
+ Returns
20
+ -------
21
+ xarray.Dataset
22
+ Extract wind direction from Koch Method using xsarsea tools.
23
+ """
24
+
25
+ # return empy dataArray, waiting for solution
26
+ return xr.DataArray(data=np.nan * np.ones([len(xr_dataset.coords[dim]) for dim in ['line','sample']]),
27
+ dims=['line','sample'],
28
+ coords=[xr_dataset.coords[dim] for dim in ['line','sample']])
29
+ #
30
+
31
+ """
32
+ gradients = xsarsea.gradients.Gradients(xr_dataset_100['sigma0_detrend'], windows_sizes=[
33
+ 1600, 3200], downscales_factors=[1, 2], window_step=1)
34
+
35
+ # get gradients histograms as an xarray dataset
36
+ hist = gradients.histogram
37
+
38
+ # get orthogonals gradients
39
+ hist['angles'] = hist['angles'] + np.pi/2
40
+
41
+ # mean
42
+ hist_mean = hist.mean(['downscale_factor', 'window_size', 'pol'])
43
+
44
+ # smooth
45
+ hist_mean_smooth = hist_mean.copy()
46
+ hist_mean_smooth['weight'] = xsarsea.gradients.circ_smooth(
47
+ hist_mean['weight'])
48
+
49
+ # smooth only
50
+ # hist_smooth = hist.copy()
51
+ # hist_smooth['weight'] = xsarsea.gradients.circ_smooth(hist_smooth['weight'])
52
+
53
+ # select histogram peak
54
+ iangle = hist_mean_smooth['weight'].fillna(0).argmax(dim='angles')
55
+ streaks_dir = hist_mean_smooth.angles.isel(angles=iangle)
56
+ streaks_weight = hist_mean_smooth['weight'].isel(angles=iangle)
57
+ streaks = xr.merge(
58
+ [dict(angle=streaks_dir, weight=streaks_weight)]).drop('angles')
59
+
60
+ # streaks are [0, pi]. Remove ambiguity with anciallary wind
61
+ ancillary_wind = xr_dataset_100['ancillary_wind'].sel(line=streaks.line,
62
+ sample=streaks.sample,
63
+ method='nearest').compute()
64
+ streaks_c = streaks['weight'] * np.exp(1j * streaks['angle'])
65
+ diff_angle = xr.apply_ufunc(np.angle, ancillary_wind / streaks_c)
66
+ streaks_c = xr.where(np.abs(diff_angle) > np.pi/2, -streaks_c, streaks_c)
67
+ streaks['weight'] = np.abs(streaks_c)
68
+ streaks['angle'] = xr.apply_ufunc(np.angle, streaks_c)
69
+
70
+ streaks_dir = xr.apply_ufunc(
71
+ np.angle, streaks_c.interp(line=xr_dataset.line, sample=xr_dataset.sample))
72
+ streaks_dir = xr.where(
73
+ xr_dataset['land_mask'], np.nan, streaks_dir)
74
+ streaks_dir.attrs['comment'] = 'angle in radians, anticlockwise, 0=line'
75
+ streaks_dir.attrs['description'] = 'wind direction estimated from local gradient, and direction ambiguity removed with ancillary wind'
76
+
77
+ return streaks_dir
78
+
79
+ """
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: grdwindinversion
3
- Version: 0.2.3.post13
3
+ Version: 0.2.3.post15
4
4
  Summary: Package to perform Wind inversion from GRD Level-1 SAR images
5
5
  License: MIT
6
6
  Classifier: Development Status :: 2 - Pre-Alpha
@@ -39,6 +39,7 @@ grdwindinversion/data_config.yaml
39
39
  grdwindinversion/inversion.py
40
40
  grdwindinversion/load_config.py
41
41
  grdwindinversion/main.py
42
+ grdwindinversion/streaks.py
42
43
  grdwindinversion/utils.py
43
44
  grdwindinversion.egg-info/PKG-INFO
44
45
  grdwindinversion.egg-info/SOURCES.txt
@@ -47,5 +48,6 @@ grdwindinversion.egg-info/entry_points.txt
47
48
  grdwindinversion.egg-info/requires.txt
48
49
  grdwindinversion.egg-info/top_level.txt
49
50
  grdwindinversion/.github/ISSUE_TEMPLATE.md
51
+ recipe/meta.yaml
50
52
  tests/__init__.py
51
53
  tests/test_grdwindinversion.py
@@ -0,0 +1,34 @@
1
+ package:
2
+ name: "grdwindinversion"
3
+ version: {{ environ.get('GIT_DESCRIBE_TAG', 0)}}
4
+
5
+ source:
6
+ path: ../.
7
+
8
+ build:
9
+ noarch: python
10
+ number: 0
11
+ script: {{ PYTHON }} -m pip install . --no-deps -vv
12
+
13
+ requirements:
14
+ build:
15
+ - python >=3.9,<3.11
16
+ - setuptools_scm
17
+ - setuptools
18
+
19
+ run:
20
+ - python >=3.9,<3.11
21
+ - xsar
22
+ - xsarsea
23
+ - "xarray==2024.2.0"
24
+ - xarray-datatree
25
+ - "rioxarray<=0.15.5"
26
+ - "numpy<=1.26"
27
+ - pyyaml
28
+ - scipy
29
+ - fsspec
30
+ - aiohttp
31
+
32
+ about:
33
+ home: https://github.com/umr-lops/grdwindinversion
34
+ summary: 'python library to compute wind speed from GRD SAR images'