grdwindinversion 0.2.3.post12__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 (56) hide show
  1. {grdwindinversion-0.2.3.post12 → grdwindinversion-0.2.3.post15}/.github/workflows/publish.yml +6 -6
  2. {grdwindinversion-0.2.3.post12/grdwindinversion.egg-info → grdwindinversion-0.2.3.post15}/PKG-INFO +1 -1
  3. {grdwindinversion-0.2.3.post12 → grdwindinversion-0.2.3.post15}/grdwindinversion/config_prod.yaml +17 -0
  4. {grdwindinversion-0.2.3.post12 → grdwindinversion-0.2.3.post15}/grdwindinversion/config_prod_recal.yaml +17 -0
  5. {grdwindinversion-0.2.3.post12 → grdwindinversion-0.2.3.post15}/grdwindinversion/inversion.py +116 -76
  6. {grdwindinversion-0.2.3.post12 → grdwindinversion-0.2.3.post15}/grdwindinversion/main.py +2 -5
  7. grdwindinversion-0.2.3.post15/grdwindinversion/streaks.py +79 -0
  8. {grdwindinversion-0.2.3.post12 → grdwindinversion-0.2.3.post15/grdwindinversion.egg-info}/PKG-INFO +1 -1
  9. {grdwindinversion-0.2.3.post12 → grdwindinversion-0.2.3.post15}/grdwindinversion.egg-info/SOURCES.txt +2 -1
  10. grdwindinversion-0.2.3.post15/recipe/meta.yaml +34 -0
  11. grdwindinversion-0.2.3.post12/grdwindinversion/config_luts.yaml +0 -4
  12. {grdwindinversion-0.2.3.post12 → grdwindinversion-0.2.3.post15}/.editorconfig +0 -0
  13. {grdwindinversion-0.2.3.post12 → grdwindinversion-0.2.3.post15}/.github/dependabot.yml +0 -0
  14. {grdwindinversion-0.2.3.post12 → grdwindinversion-0.2.3.post15}/.gitignore +0 -0
  15. {grdwindinversion-0.2.3.post12 → grdwindinversion-0.2.3.post15}/.pre-commit-config.yaml +0 -0
  16. {grdwindinversion-0.2.3.post12 → grdwindinversion-0.2.3.post15}/AUTHORS.rst +0 -0
  17. {grdwindinversion-0.2.3.post12 → grdwindinversion-0.2.3.post15}/CONTRIBUTING.rst +0 -0
  18. {grdwindinversion-0.2.3.post12 → grdwindinversion-0.2.3.post15}/HISTORY.rst +0 -0
  19. {grdwindinversion-0.2.3.post12 → grdwindinversion-0.2.3.post15}/LICENSE +0 -0
  20. {grdwindinversion-0.2.3.post12 → grdwindinversion-0.2.3.post15}/MANIFEST.in +0 -0
  21. {grdwindinversion-0.2.3.post12 → grdwindinversion-0.2.3.post15}/Makefile +0 -0
  22. {grdwindinversion-0.2.3.post12 → grdwindinversion-0.2.3.post15}/README.md +0 -0
  23. {grdwindinversion-0.2.3.post12 → grdwindinversion-0.2.3.post15}/ci/requirements/docs.yaml +0 -0
  24. {grdwindinversion-0.2.3.post12 → grdwindinversion-0.2.3.post15}/ci/requirements/environment.yaml +0 -0
  25. {grdwindinversion-0.2.3.post12 → grdwindinversion-0.2.3.post15}/docs/Makefile +0 -0
  26. {grdwindinversion-0.2.3.post12 → grdwindinversion-0.2.3.post15}/docs/_static/css/grdwindinversion.css +0 -0
  27. {grdwindinversion-0.2.3.post12 → grdwindinversion-0.2.3.post15}/docs/algorithm.rst +0 -0
  28. {grdwindinversion-0.2.3.post12 → grdwindinversion-0.2.3.post15}/docs/authors.rst +0 -0
  29. {grdwindinversion-0.2.3.post12 → grdwindinversion-0.2.3.post15}/docs/conf.py +0 -0
  30. {grdwindinversion-0.2.3.post12 → grdwindinversion-0.2.3.post15}/docs/contributing.rst +0 -0
  31. {grdwindinversion-0.2.3.post12 → grdwindinversion-0.2.3.post15}/docs/examples/wind-inversion-from-grd.ipynb +0 -0
  32. {grdwindinversion-0.2.3.post12 → grdwindinversion-0.2.3.post15}/docs/history.rst +0 -0
  33. {grdwindinversion-0.2.3.post12 → grdwindinversion-0.2.3.post15}/docs/index.rst +0 -0
  34. {grdwindinversion-0.2.3.post12 → grdwindinversion-0.2.3.post15}/docs/installation.rst +0 -0
  35. {grdwindinversion-0.2.3.post12 → grdwindinversion-0.2.3.post15}/docs/make.bat +0 -0
  36. {grdwindinversion-0.2.3.post12 → grdwindinversion-0.2.3.post15}/docs/modules.rst +0 -0
  37. {grdwindinversion-0.2.3.post12 → grdwindinversion-0.2.3.post15}/docs/readme.rst +0 -0
  38. {grdwindinversion-0.2.3.post12 → grdwindinversion-0.2.3.post15}/docs/usage.rst +0 -0
  39. {grdwindinversion-0.2.3.post12 → grdwindinversion-0.2.3.post15}/grdwindinversion/.github/ISSUE_TEMPLATE.md +0 -0
  40. {grdwindinversion-0.2.3.post12 → grdwindinversion-0.2.3.post15}/grdwindinversion/.gitignore +0 -0
  41. {grdwindinversion-0.2.3.post12 → grdwindinversion-0.2.3.post15}/grdwindinversion/.travis.yml +0 -0
  42. {grdwindinversion-0.2.3.post12 → grdwindinversion-0.2.3.post15}/grdwindinversion/__init__.py +0 -0
  43. {grdwindinversion-0.2.3.post12 → grdwindinversion-0.2.3.post15}/grdwindinversion/data_config.yaml +0 -0
  44. {grdwindinversion-0.2.3.post12 → grdwindinversion-0.2.3.post15}/grdwindinversion/load_config.py +0 -0
  45. {grdwindinversion-0.2.3.post12 → grdwindinversion-0.2.3.post15}/grdwindinversion/utils.py +0 -0
  46. {grdwindinversion-0.2.3.post12 → grdwindinversion-0.2.3.post15}/grdwindinversion.egg-info/dependency_links.txt +0 -0
  47. {grdwindinversion-0.2.3.post12 → grdwindinversion-0.2.3.post15}/grdwindinversion.egg-info/entry_points.txt +0 -0
  48. {grdwindinversion-0.2.3.post12 → grdwindinversion-0.2.3.post15}/grdwindinversion.egg-info/requires.txt +0 -0
  49. {grdwindinversion-0.2.3.post12 → grdwindinversion-0.2.3.post15}/grdwindinversion.egg-info/top_level.txt +0 -0
  50. {grdwindinversion-0.2.3.post12 → grdwindinversion-0.2.3.post15}/pyproject.toml +0 -0
  51. {grdwindinversion-0.2.3.post12 → grdwindinversion-0.2.3.post15}/requirements_dev.txt +0 -0
  52. {grdwindinversion-0.2.3.post12 → grdwindinversion-0.2.3.post15}/requirements_doc.txt +0 -0
  53. {grdwindinversion-0.2.3.post12 → grdwindinversion-0.2.3.post15}/setup.cfg +0 -0
  54. {grdwindinversion-0.2.3.post12 → grdwindinversion-0.2.3.post15}/tests/__init__.py +0 -0
  55. {grdwindinversion-0.2.3.post12 → grdwindinversion-0.2.3.post15}/tests/test_grdwindinversion.py +0 -0
  56. {grdwindinversion-0.2.3.post12 → 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.post12
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,3 +39,7 @@ RCM:
26
39
  apply_flattening: True
27
40
  recalibration: False
28
41
  ancillary: "ecmwf"
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"
@@ -5,6 +6,10 @@ S1A:
5
6
  apply_flattening: True
6
7
  recalibration: True
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: True
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: True
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,3 +39,7 @@ RCM:
26
39
  apply_flattening: True
27
40
  recalibration: True
28
41
  ancillary: "ecmwf"
42
+ inc_step: 0.1
43
+ wspd_step: 0.1
44
+ phi_step: 1.0
45
+ resolution: "high"
@@ -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, config_luts_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
 
@@ -499,8 +511,6 @@ def preprocess(filename, outdir, config_path, config_luts_path, overwrite=False,
499
511
  output folder
500
512
  config_path : str
501
513
  configuration file path
502
- config_luts_path : str
503
- configuration LUTs file path
504
514
  overwrite : bool, optional
505
515
  overwrite existing file
506
516
  resolution : str, optional
@@ -509,22 +519,12 @@ def preprocess(filename, outdir, config_path, config_luts_path, overwrite=False,
509
519
  Returns
510
520
  -------
511
521
  xarray.Dataset
512
- final dataset
522
+ final dataset
513
523
  """
514
524
 
515
525
  sensor, sensor_longname, fct_meta, fct_dataset = getSensorMetaDataset(
516
526
  filename)
517
527
 
518
- if os.path.exists(config_luts_path):
519
- with open(config_luts_path, 'r') as file:
520
- config_luts = yaml.load(
521
- file,
522
- Loader=yaml.FullLoader
523
- )
524
- else:
525
- raise FileNotFoundError(
526
- 'config_luts_path do not exists, got %s ' % config_luts_path)
527
-
528
528
  if os.path.exists(config_path):
529
529
  with open(config_path, 'r') as file:
530
530
  config_base = yaml.load(
@@ -541,16 +541,19 @@ def preprocess(filename, outdir, config_path, config_luts_path, overwrite=False,
541
541
 
542
542
  recalibration = config["recalibration"]
543
543
  meta = fct_meta(filename)
544
- 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)
545
548
 
546
549
  if os.path.exists(out_file) and overwrite is False:
547
- raise FileExistsError("out_file %s exists already")
550
+ raise FileExistsError("outfile %s already exists" % out_file)
548
551
 
549
552
  ancillary_name = config["ancillary"]
550
553
  map_model = getAncillary(meta, ancillary_name)
551
554
  if map_model is None:
552
555
  raise Exception(
553
- 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")
554
557
 
555
558
  try:
556
559
  if ((recalibration) & ("SENTINEL" in sensor_longname)):
@@ -604,6 +607,25 @@ def preprocess(filename, outdir, config_path, config_luts_path, overwrite=False,
604
607
  copol_gmf = 'HH'
605
608
  crosspol_gmf = 'VH'
606
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"])
607
629
  #  Step 2 - clean and prepare dataset
608
630
 
609
631
  # variables to not keep in the L2
@@ -693,21 +715,32 @@ def preprocess(filename, outdir, config_path, config_luts_path, overwrite=False,
693
715
  # nrcs processing
694
716
  xr_dataset['sigma0_ocean'] = xr.where(xr_dataset['mask'], np.nan,
695
717
  xr_dataset['sigma0'].compute()).transpose(*xr_dataset['sigma0'].dims)
696
- xr_dataset['sigma0_ocean'] = xr.where(
697
- xr_dataset['sigma0_ocean'] <= 0, np.nan, xr_dataset['sigma0_ocean'])
698
-
699
718
  xr_dataset['sigma0_ocean'].attrs = xr_dataset['sigma0'].attrs
700
- #  we forced it to nan
701
- 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'])
702
730
 
703
731
  xr_dataset['sigma0_ocean_raw'] = xr.where(xr_dataset['mask'], np.nan,
704
732
  xr_dataset['sigma0_raw'].compute()).transpose(*xr_dataset['sigma0_raw'].dims)
705
- xr_dataset['sigma0_ocean_raw'] = xr.where(
706
- xr_dataset['sigma0_ocean_raw'] <= 0, np.nan, xr_dataset['sigma0_ocean_raw'])
733
+
707
734
  xr_dataset['sigma0_ocean_raw'].attrs = xr_dataset['sigma0_raw'].attrs
708
735
 
736
+ xr_dataset['sigma0_detrend'] = xsarsea.sigma0_detrend(
737
+ xr_dataset.sigma0.sel(pol=copol), xr_dataset.incidence, model=model_vv)
738
+
709
739
  # processing
710
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)
711
744
  if config["apply_flattening"]:
712
745
  xr_dataset = xr_dataset.assign(nesz_cross_final=(
713
746
  ['line', 'sample'], windspeed.nesz_flattening(xr_dataset.nesz.sel(pol=crosspol), xr_dataset.incidence)))
@@ -733,24 +766,50 @@ def preprocess(filename, outdir, config_path, config_luts_path, overwrite=False,
733
766
  sigma0_ocean_cross = None
734
767
  dsig_cross = 0.1 # default value set in xsarsea
735
768
 
736
- model_vv = config["GMF_"+copol_gmf+"_NAME"]
737
- model_vh = config["GMF_"+crosspol_gmf+"_NAME"]
738
-
739
769
  if ((recalibration) & ("SENTINEL" in sensor_longname)):
740
- 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(
741
771
  os.path.dirname(xsar_dataset.datatree['recalibration'].attrs['path_aux_pp1_new'])))
742
- 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(
743
773
  os.path.dirname(xsar_dataset.datatree['recalibration'].attrs['path_aux_cal_new'])))
744
774
 
745
- 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(
746
776
  os.path.dirname(xsar_dataset.datatree['recalibration'].attrs['path_aux_pp1_old'])))
747
- 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(
748
778
  os.path.dirname(xsar_dataset.datatree['recalibration'].attrs['path_aux_cal_old'])))
749
779
 
750
- 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, config_luts
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)
751
805
 
806
+ xr_dataset['streaks_direction'] = get_streaks(
807
+ xr_dataset, xr_dataset_100)
752
808
 
753
- def makeL2(filename, outdir, config_path, config_luts_path, overwrite=False, generateCSV=True, resolution='1000m'):
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'):
754
813
  """
755
814
  Main function to generate L2 product.
756
815
 
@@ -762,8 +821,6 @@ def makeL2(filename, outdir, config_path, config_luts_path, overwrite=False, gen
762
821
  output folder
763
822
  config_path : str
764
823
  configuration file path
765
- config_luts_path : str
766
- configuration LUTs file path
767
824
  overwrite : bool, optional
768
825
  overwrite existing file
769
826
  generateCSV : bool, optional
@@ -779,36 +836,19 @@ def makeL2(filename, outdir, config_path, config_luts_path, overwrite=False, gen
779
836
  final dataset
780
837
  """
781
838
 
782
- xr_dataset, dual_pol, copol, crosspol, copol_gmf, crosspol_gmf, model_vv, model_vh, sigma0_ocean_cross, dsig_cross, sensor_longname, out_file, config, config_luts = preprocess(
783
- filename, outdir, config_path, config_luts_path, overwrite, resolution)
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)
784
841
 
785
842
  kwargs = {
786
- "inc_step_lr": config_luts.pop("inc_step_lr", None),
787
- "wpsd_step_lr": config_luts.pop("wspd_step_lr", None),
788
- "phi_step_lr": config_luts.pop("phi_step_lr", None),
789
- "inc_step": config_luts.pop("inc_step", None),
790
- "wpsd_step": config_luts.pop("wspd_step", None),
791
- "phi_step": config_luts.pop("phi_step", None),
792
- "resolution": config_luts.pop("resolution", None),
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),
793
850
  }
794
851
 
795
- # need to load gmfs before
796
-
797
- gmfs_impl = [x for x in [model_vv, model_vh] if "gmf_" in x]
798
- windspeed.gmfs.GmfModel.activate_gmfs_impl(gmfs_impl)
799
- sarwings_luts = [x for x in [model_vv, model_vh]
800
- if x.startswith("sarwing_lut_")]
801
- if len(sarwings_luts) > 0:
802
- windspeed.register_sarwing_luts(getConf()["sarwing_luts_path"])
803
-
804
- nc_luts = [x for x in [model_vv, model_vh] if x.startswith("nc_lut")]
805
-
806
- if len(nc_luts) > 0:
807
- windspeed.register_nc_luts(getConf()["nc_luts_path"])
808
-
809
- if (model_vv == "gmf_cmod7"):
810
- windspeed.register_cmod7(getConf()["lut_cmod7_path"])
811
-
812
852
  wind_co, wind_dual, windspeed_cr = inverse(dual_pol,
813
853
  inc=xr_dataset['incidence'],
814
854
  sigma0=xr_dataset['sigma0_ocean'].sel(
@@ -871,7 +911,7 @@ def makeL2(filename, outdir, config_path, config_luts_path, overwrite=False, gen
871
911
  xr_dataset["winddir_cross"].attrs["model"] = "No model used ; content is a copy of dualpol wind direction"
872
912
 
873
913
  xr_dataset, encoding = makeL2asOwi(
874
- xr_dataset, dual_pol, copol, crosspol)
914
+ xr_dataset, dual_pol, copol, crosspol, add_streaks=add_streaks)
875
915
 
876
916
  #  add attributes
877
917
  firstMeasurementTime = None
@@ -924,7 +964,7 @@ def makeL2(filename, outdir, config_path, config_luts_path, overwrite=False, gen
924
964
  }
925
965
 
926
966
  for recalib_attrs in ["path_aux_pp1_new", 'path_aux_pp1_old', "path_aux_cal_new", "path_aux_cal_old"]:
927
- if recalib_attrs in xr_dataset:
967
+ if recalib_attrs in xr_dataset.attrs:
928
968
  attrs[recalib_attrs] = xr_dataset.attrs[recalib_attrs]
929
969
 
930
970
  # new one to match convention
@@ -13,9 +13,6 @@ def processor_starting_point():
13
13
  parser.add_argument('--input_file', help='input file path', required=True)
14
14
  parser.add_argument('--config_file',
15
15
  help='config file path [if not provided will take config file based on input file]', required=True)
16
- parser.add_argument('--config_file_luts',
17
- help='config file (luts) path', required=True)
18
-
19
16
  parser.add_argument('--resolution', required=False, default='1000m',
20
17
  help='set resolution ["full" | "1000m" | "xXxm"]')
21
18
 
@@ -45,14 +42,14 @@ def processor_starting_point():
45
42
  # raise Exception('this processor only handle acquisitions with VV or VV+VH polarization for now.')
46
43
 
47
44
  config_file = args.config_file
48
- config_file_luts = args.config_file_luts
49
45
  out_folder = args.outputdir
50
46
  resolution = args.resolution
51
47
  if resolution == "full":
52
48
  resolution = None
53
49
 
54
- out_file, outputds = makeL2(input_file, out_folder, config_file, config_file_luts,
50
+ out_file, outputds = makeL2(input_file, out_folder, config_file,
55
51
  overwrite=args.overwrite, resolution=resolution, generateCSV=args.no_generate_csv)
52
+
56
53
  logging.info('out_file: %s', out_file)
57
54
  logging.info('current memory usage: %s ', get_memory_usage(var='current'))
58
55
  logging.info('done in %1.3f min', (time.time() - t0) / 60.)
@@ -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.post12
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
@@ -33,13 +33,13 @@ docs/examples/wind-inversion-from-grd.ipynb
33
33
  grdwindinversion/.gitignore
34
34
  grdwindinversion/.travis.yml
35
35
  grdwindinversion/__init__.py
36
- grdwindinversion/config_luts.yaml
37
36
  grdwindinversion/config_prod.yaml
38
37
  grdwindinversion/config_prod_recal.yaml
39
38
  grdwindinversion/data_config.yaml
40
39
  grdwindinversion/inversion.py
41
40
  grdwindinversion/load_config.py
42
41
  grdwindinversion/main.py
42
+ grdwindinversion/streaks.py
43
43
  grdwindinversion/utils.py
44
44
  grdwindinversion.egg-info/PKG-INFO
45
45
  grdwindinversion.egg-info/SOURCES.txt
@@ -48,5 +48,6 @@ grdwindinversion.egg-info/entry_points.txt
48
48
  grdwindinversion.egg-info/requires.txt
49
49
  grdwindinversion.egg-info/top_level.txt
50
50
  grdwindinversion/.github/ISSUE_TEMPLATE.md
51
+ recipe/meta.yaml
51
52
  tests/__init__.py
52
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'
@@ -1,4 +0,0 @@
1
- inc_step: 0.1
2
- wspd_step: 0.1
3
- phi_step: 1.0
4
- resolution: "high"