grdwindinversion 1.0.0__tar.gz → 1.0.2__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 (72) hide show
  1. {grdwindinversion-1.0.0 → grdwindinversion-1.0.2}/.github/workflows/ci.yml +0 -6
  2. {grdwindinversion-1.0.0 → grdwindinversion-1.0.2}/PKG-INFO +1 -1
  3. {grdwindinversion-1.0.0 → grdwindinversion-1.0.2}/docs/configuration.rst +3 -34
  4. {grdwindinversion-1.0.0 → grdwindinversion-1.0.2}/docs/examples/streaks-display.ipynb +5 -7
  5. {grdwindinversion-1.0.0 → grdwindinversion-1.0.2}/docs/examples/wind-inversion-from-grd.ipynb +2 -2
  6. {grdwindinversion-1.0.0 → grdwindinversion-1.0.2}/docs/modules.rst +0 -8
  7. {grdwindinversion-1.0.0 → grdwindinversion-1.0.2}/docs/usage.rst +4 -4
  8. {grdwindinversion-1.0.0 → grdwindinversion-1.0.2}/grdwindinversion/__init__.py +0 -2
  9. {grdwindinversion-1.0.0 → grdwindinversion-1.0.2}/grdwindinversion/config_prod_recal.yaml +26 -0
  10. {grdwindinversion-1.0.0 → grdwindinversion-1.0.2}/grdwindinversion/config_prod_recal_streaks_nrcsmod.yaml +26 -0
  11. {grdwindinversion-1.0.0 → grdwindinversion-1.0.2}/grdwindinversion/config_prod_streaks.yaml +27 -1
  12. {grdwindinversion-1.0.0 → grdwindinversion-1.0.2}/grdwindinversion/config_prod_streaks_nrcsmod.yaml +26 -0
  13. {grdwindinversion-1.0.0 → grdwindinversion-1.0.2}/grdwindinversion/config_prod_v3.yaml +26 -0
  14. {grdwindinversion-1.0.0 → grdwindinversion-1.0.2}/grdwindinversion/inversion.py +39 -27
  15. {grdwindinversion-1.0.0 → grdwindinversion-1.0.2}/grdwindinversion/main.py +0 -3
  16. {grdwindinversion-1.0.0 → grdwindinversion-1.0.2}/grdwindinversion/utils.py +106 -1
  17. {grdwindinversion-1.0.0 → grdwindinversion-1.0.2}/grdwindinversion.egg-info/PKG-INFO +1 -1
  18. {grdwindinversion-1.0.0 → grdwindinversion-1.0.2}/grdwindinversion.egg-info/SOURCES.txt +11 -11
  19. grdwindinversion-1.0.2/tests/ci/__init__.py +1 -0
  20. {grdwindinversion-1.0.0/tests → grdwindinversion-1.0.2/tests/ci}/config_test.yaml +23 -4
  21. {grdwindinversion-1.0.0/tests → grdwindinversion-1.0.2/tests/ci}/test_grdwindinversion_ci.py +20 -11
  22. grdwindinversion-1.0.2/tests/unit/__init__.py +1 -0
  23. {grdwindinversion-1.0.0/tests → grdwindinversion-1.0.2/tests/unit}/test_ancillary.py +13 -17
  24. {grdwindinversion-1.0.0/tests → grdwindinversion-1.0.2/tests/unit}/test_config.py +16 -12
  25. {grdwindinversion-1.0.0/tests → grdwindinversion-1.0.2/tests/unit}/test_mask_functions.py +8 -9
  26. grdwindinversion-1.0.0/grdwindinversion/data_config.yaml +0 -25
  27. grdwindinversion-1.0.0/grdwindinversion/load_config.py +0 -30
  28. {grdwindinversion-1.0.0 → grdwindinversion-1.0.2}/.editorconfig +0 -0
  29. {grdwindinversion-1.0.0 → grdwindinversion-1.0.2}/.gitattributes +0 -0
  30. {grdwindinversion-1.0.0 → grdwindinversion-1.0.2}/.github/dependabot.yml +0 -0
  31. {grdwindinversion-1.0.0 → grdwindinversion-1.0.2}/.github/workflows/build.yml +0 -0
  32. {grdwindinversion-1.0.0 → grdwindinversion-1.0.2}/.github/workflows/publish.yml +0 -0
  33. {grdwindinversion-1.0.0 → grdwindinversion-1.0.2}/.gitignore +0 -0
  34. {grdwindinversion-1.0.0 → grdwindinversion-1.0.2}/.pre-commit-config.yaml +0 -0
  35. {grdwindinversion-1.0.0 → grdwindinversion-1.0.2}/AUTHORS.rst +0 -0
  36. {grdwindinversion-1.0.0 → grdwindinversion-1.0.2}/CONTRIBUTING.rst +0 -0
  37. {grdwindinversion-1.0.0 → grdwindinversion-1.0.2}/HISTORY.rst +0 -0
  38. {grdwindinversion-1.0.0 → grdwindinversion-1.0.2}/LICENSE +0 -0
  39. {grdwindinversion-1.0.0 → grdwindinversion-1.0.2}/MANIFEST.in +0 -0
  40. {grdwindinversion-1.0.0 → grdwindinversion-1.0.2}/Makefile +0 -0
  41. {grdwindinversion-1.0.0 → grdwindinversion-1.0.2}/README.md +0 -0
  42. {grdwindinversion-1.0.0 → grdwindinversion-1.0.2}/ci/requirements/docs.yaml +0 -0
  43. {grdwindinversion-1.0.0 → grdwindinversion-1.0.2}/ci/requirements/environment.yaml +0 -0
  44. {grdwindinversion-1.0.0 → grdwindinversion-1.0.2}/docs/Makefile +0 -0
  45. {grdwindinversion-1.0.0 → grdwindinversion-1.0.2}/docs/_static/css/grdwindinversion.css +0 -0
  46. {grdwindinversion-1.0.0 → grdwindinversion-1.0.2}/docs/algorithm.rst +0 -0
  47. {grdwindinversion-1.0.0 → grdwindinversion-1.0.2}/docs/authors.rst +0 -0
  48. {grdwindinversion-1.0.0 → grdwindinversion-1.0.2}/docs/conf.py +0 -0
  49. {grdwindinversion-1.0.0 → grdwindinversion-1.0.2}/docs/contributing.rst +0 -0
  50. {grdwindinversion-1.0.0 → grdwindinversion-1.0.2}/docs/history.rst +0 -0
  51. {grdwindinversion-1.0.0 → grdwindinversion-1.0.2}/docs/index.rst +0 -0
  52. {grdwindinversion-1.0.0 → grdwindinversion-1.0.2}/docs/installation.rst +0 -0
  53. {grdwindinversion-1.0.0 → grdwindinversion-1.0.2}/docs/make.bat +0 -0
  54. {grdwindinversion-1.0.0 → grdwindinversion-1.0.2}/grdwindinversion/.github/ISSUE_TEMPLATE.md +0 -0
  55. {grdwindinversion-1.0.0 → grdwindinversion-1.0.2}/grdwindinversion/.gitignore +0 -0
  56. {grdwindinversion-1.0.0 → grdwindinversion-1.0.2}/grdwindinversion/gradientFeatures.py +0 -0
  57. {grdwindinversion-1.0.0 → grdwindinversion-1.0.2}/grdwindinversion/utils_memory.py +0 -0
  58. {grdwindinversion-1.0.0 → grdwindinversion-1.0.2}/grdwindinversion.egg-info/dependency_links.txt +0 -0
  59. {grdwindinversion-1.0.0 → grdwindinversion-1.0.2}/grdwindinversion.egg-info/entry_points.txt +0 -0
  60. {grdwindinversion-1.0.0 → grdwindinversion-1.0.2}/grdwindinversion.egg-info/requires.txt +0 -0
  61. {grdwindinversion-1.0.0 → grdwindinversion-1.0.2}/grdwindinversion.egg-info/top_level.txt +0 -0
  62. {grdwindinversion-1.0.0 → grdwindinversion-1.0.2}/pyproject.toml +0 -0
  63. {grdwindinversion-1.0.0 → grdwindinversion-1.0.2}/recipe/meta.yaml +0 -0
  64. {grdwindinversion-1.0.0 → grdwindinversion-1.0.2}/requirements_dev.txt +0 -0
  65. {grdwindinversion-1.0.0 → grdwindinversion-1.0.2}/requirements_doc.txt +0 -0
  66. {grdwindinversion-1.0.0 → grdwindinversion-1.0.2}/setup.cfg +0 -0
  67. {grdwindinversion-1.0.0 → grdwindinversion-1.0.2}/tests/__init__.py +0 -0
  68. {grdwindinversion-1.0.0/tests → grdwindinversion-1.0.2/tests/ci}/data_config_ci.yaml +0 -0
  69. {grdwindinversion-1.0.0/tests → grdwindinversion-1.0.2/tests/unit}/listing_rcm_safe.txt +0 -0
  70. {grdwindinversion-1.0.0/tests → grdwindinversion-1.0.2/tests/unit}/test_getOutputName.py +0 -0
  71. {grdwindinversion-1.0.0/tests → grdwindinversion-1.0.2/tests/unit}/test_simple_functions.py +0 -0
  72. {grdwindinversion-1.0.0 → grdwindinversion-1.0.2}/tox.ini +0 -0
@@ -79,12 +79,6 @@ jobs:
79
79
  echo "auxiliary_dir: ./test_data/auxiliary" >> ~/.xsar/config.yml
80
80
  echo "path_dataframe_aux: ./test_data/auxiliary/active_aux.csv" >> ~/.xsar/config.yml
81
81
 
82
- # Set up grdwindinversion configuration
83
- - name: Setup grdwindinversion configuration
84
- run: |
85
- mkdir -p ~/.grdwindinversion
86
- cp tests/data_config_ci.yaml ~/.grdwindinversion/data_config.yaml
87
-
88
82
  # Run the tests
89
83
  - name: Run tests
90
84
  run: |
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: grdwindinversion
3
- Version: 1.0.0
3
+ Version: 1.0.2
4
4
  Summary: Package to perform Wind inversion from GRD Level-1 SAR images
5
5
  Author-email: Antoine Grouazel <antoine.grouazel@ifremer.fr>
6
6
  License: MIT
@@ -8,41 +8,10 @@ Configuration Files Implementation Guide
8
8
  Overview
9
9
  --------
10
10
 
11
- The ``grdwindinversion`` configuration system uses two types of YAML files:
11
+ The ``grdwindinversion`` configuration system uses a **single YAML configuration file** that contains both data source paths and processing parameters.
12
12
 
13
- 1. **data_config.yaml**: Paths to data sources (ancillary, , LUTs, masks)
14
- 2. **config_*.yaml**: Processing parameters specific to each satellite
15
-
16
- Configuration System Architecture
17
- ----------------------------------
18
-
19
- Loading Hierarchy
20
- ~~~~~~~~~~~~~~~~~
21
-
22
- The ``data_config.yaml`` file is loaded with the following priority:
23
-
24
- .. code-block:: text
25
-
26
- 1. ~/.grdwindinversion/data_config.yaml (user local configuration)
27
- 2. ./local_data_config.yaml (project local configuration)
28
- 3. <package>/data_config.yaml (package default configuration)
29
-
30
- Accessing Configuration
31
- ~~~~~~~~~~~~~~~~~~~~~~~~
32
-
33
- .. code-block:: python
34
-
35
- from grdwindinversion.load_config import getConf
36
-
37
- # Get the global configuration dictionary
38
- config = getConf()
39
-
40
- # Access data paths
41
- ecmwf_path = config["ecmwf_0100_1h"]
42
- nc_luts_path = config["nc_luts_path"]
43
-
44
- data_config.yaml Structure
45
- ---------------------------
13
+ Configuration File Structure
14
+ -----------------------------
46
15
 
47
16
  1. Meteorological Data Paths (ancillary)
48
17
  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -213,14 +213,14 @@
213
213
  " fig = plt.figure(figsize=(10, 9))\n",
214
214
  " ax = plt.axes(projection=ccrs.PlateCarree())\n",
215
215
  "\n",
216
- " # Calculer les composantes u et v \u00e0 partir des angles streaks_dir\n",
216
+ " # Compute u,v from streaks \n",
217
217
  " u, v, u_norm, v_norm = get_uv_from_dir(streaks_dir)\n",
218
218
  "\n",
219
219
  " # Display streaks direction\n",
220
220
  " #ax.quiver(longitude, latitude, u_norm, v_norm, edgecolors='k', norm=norm, pivot= 'mid', scale_units='xy', scale=4., zorder=10, width=0.1/25,transform = ccrs.PlateCarree(), color = 'red', label = 'owiWindStreaks')\n",
221
221
  " ax.quiver(longitude, latitude, u_norm, v_norm, edgecolors='k', norm=norm, pivot= 'mid', scale_units='xy', scale=8., zorder=10, width=0.1/25,transform = ccrs.PlateCarree(), color = 'red', label = 'owiWindStreaks')\n",
222
222
  "\n",
223
- " #\u00a0Display ancillary wind direction\n",
223
+ " # Display ancillary wind direction\n",
224
224
  " #ax.quiver(lons, lats, u_norm_ancillary, v_norm_ancillary, edgecolors='k', norm=norm, pivot= 'mid', scale_units='xy', scale=4., zorder=10, width=0.1/25,transform = ccrs.PlateCarree(), color='blue', label = \"owiAncillaryWindDirection\")\n",
225
225
  " ax.quiver(lons, lats, u_norm_ancillary, v_norm_ancillary, edgecolors='k', norm=norm, pivot= 'mid', scale_units='xy', scale=8., zorder=10, width=0.1/25,transform = ccrs.PlateCarree(), color='blue', label = \"owiAncillaryWindDirection\")\n",
226
226
  "\n",
@@ -234,8 +234,6 @@
234
234
  " lat_min, lat_max = np.nanmin(lats) - .2, np.nanmax(lats) + .7\n",
235
235
  " ax.set_extent([lon_min, lon_max, lat_min, lat_max], crs=ccrs.PlateCarree())\n",
236
236
  "\n",
237
- "\n",
238
- " # Configurer les axes\n",
239
237
  " ax.set_xlabel('Longitude')\n",
240
238
  " ax.set_ylabel('Latitude')\n",
241
239
  " ax.set_aspect('equal', adjustable='box')\n",
@@ -300,9 +298,9 @@
300
298
  "source": []
301
299
  }
302
300
  ],
303
- "metadata": {
301
+ "metadata": {
304
302
  "kernelspec": {
305
- "display_name": "Python 3 (ipykernel)",
303
+ "display_name": "Python 3",
306
304
  "language": "python",
307
305
  "name": "python3"
308
306
  },
@@ -321,4 +319,4 @@
321
319
  },
322
320
  "nbformat": 4,
323
321
  "nbformat_minor": 4
324
- }
322
+ }
@@ -109,9 +109,9 @@
109
109
  ]
110
110
  }
111
111
  ],
112
- "metadata": {
112
+ "metadata": {
113
113
  "kernelspec": {
114
- "display_name": "env_xsar",
114
+ "display_name": "Python 3",
115
115
  "language": "python",
116
116
  "name": "python3"
117
117
  },
@@ -15,14 +15,6 @@ Wind Inversion
15
15
  :members: inverse, makeL2, makeL2asOwi, getSensorMetaDataset, getOutputName, inverse_dsig_wspd, addMasks_toMeta, mergeLandMasks, processLandMask, getAncillary, preprocess, process_gradients, transform_winddir
16
16
  :show-inheritance:
17
17
 
18
- Configuration Management
19
- ------------------------
20
-
21
- .. automodule:: grdwindinversion.load_config
22
- :members:
23
- :undoc-members:
24
- :show-inheritance:
25
-
26
18
  Gradient Features
27
19
  -----------------
28
20
 
@@ -13,11 +13,11 @@ To use grdwindinversion in a project::
13
13
  Configuration Setup
14
14
  -------------------
15
15
 
16
- To define the path where the ECMWF files used for wind inversion (or any supported ancillary wind)::
16
+ To define paths for ECMWF files and other data sources, create or copy a configuration file::
17
17
 
18
- cp ./grdwindinversion/data_config.yaml ./grdwindinversion/local_data_config.yaml
19
- # Then edit the file
20
- vi ./grdwindinversion/local_data_config.yaml
18
+ cp ./grdwindinversion/config_prod_v3.yaml ./my_config.yaml
19
+ # Then edit the file to match your local paths
20
+ vi ./my_config.yaml
21
21
 
22
22
  See :doc:`configuration` for detailed configuration options.
23
23
 
@@ -1,13 +1,11 @@
1
1
  from importlib.metadata import version
2
2
  from grdwindinversion.inversion import inverse, makeL2, makeL2asOwi, getSensorMetaDataset
3
- from grdwindinversion.load_config import getConf
4
3
 
5
4
  __all__ = [
6
5
  "inverse",
7
6
  "makeL2",
8
7
  "makeL2asOwi",
9
8
  "getSensorMetaDataset",
10
- "getConf",
11
9
  "inversion",
12
10
  ]
13
11
 
@@ -1,3 +1,29 @@
1
+ unit_test_s1_product: "./sentinel-1a/L1/IW/S1A_IW_GRDH_1S/2021/252/S1A_IW_GRDH_1SDV_20210909T130650_20210909T130715_039605_04AE83_C34F.SAFE"
2
+ unit_test_rcm_product: "./l1/rcm/rcm-1/sclnd/2023/273/RCM1_OK2767220_PK2769320_1_SCLND_20230930_214014_VV_VH_GRD"
3
+ unit_test_rs2_product: "./L1/VV_VH/2022/247/RS2_OK141302_PK1242223_DK1208537_SCWA_20220904_093402_VV_VH_SGF"
4
+
5
+ masks:
6
+ land:
7
+ - name: "gshhsH"
8
+ path: "/home/loc-datawork-cersat-public/cache/project/sarwing/masks/gshhg-shp-2.3.7/GSHHS_shp/h/GSHHS_h_L1.shp"
9
+
10
+ ancillary_sources:
11
+ ecmwf:
12
+ - name: "ecmwf_0100_1h"
13
+ path: "/home/loc-datawork-cersat-public/provider/ecmwf/forecast/hourly/0100deg/netcdf_light/%Y/%j/ECMWF_FORECAST_0100_%Y%m%d%H%M_10U_10V.nc"
14
+
15
+ - name: "ecmwf_0125_1h"
16
+ path: "/home/loc-datawork-cersat-intranet/project/ecmwf/0.125deg/1h/forecasts/%Y/%j/ecmwf_%Y%m%d%H%M.nc"
17
+
18
+ era5:
19
+ - name: "era5_0250_1h"
20
+ path: "/home/ref-ecmwf/ERA5/%Y/%m/era_5-copernicus__%Y%m%d.nc"
21
+
22
+ sarwing_luts_path: "/home/loc-datawork-cersat-public/cache/project/sarwing/GMFS/v1.6/"
23
+ nc_luts_path: "/home/loc-datawork-cersat-public/cache/project/sarwing/GMFS/nc_luts"
24
+ lut_cmod7_path: "/home/loc-datawork-cersat-public/cache/project/sarwing/GMFS/v1.6/GMF_cmod7_official/cmod7_and_python_script"
25
+ lut_ms1ahw_path: "/home/loc-datawork-cersat-public/cache/project/sarwing/GMFS/v1.6/GMF_cmodms1ahw"
26
+
1
27
  no_subdir: True
2
28
  winddir_convention: "meteorological"
3
29
  add_gradientsfeatures: False
@@ -1,3 +1,29 @@
1
+ unit_test_s1_product: "./sentinel-1a/L1/IW/S1A_IW_GRDH_1S/2021/252/S1A_IW_GRDH_1SDV_20210909T130650_20210909T130715_039605_04AE83_C34F.SAFE"
2
+ unit_test_rcm_product: "./l1/rcm/rcm-1/sclnd/2023/273/RCM1_OK2767220_PK2769320_1_SCLND_20230930_214014_VV_VH_GRD"
3
+ unit_test_rs2_product: "./L1/VV_VH/2022/247/RS2_OK141302_PK1242223_DK1208537_SCWA_20220904_093402_VV_VH_SGF"
4
+
5
+ masks:
6
+ land:
7
+ - name: "gshhsH"
8
+ path: "/home/loc-datawork-cersat-public/cache/project/sarwing/masks/gshhg-shp-2.3.7/GSHHS_shp/h/GSHHS_h_L1.shp"
9
+
10
+ ancillary_sources:
11
+ ecmwf:
12
+ - name: "ecmwf_0100_1h"
13
+ path: "/home/loc-datawork-cersat-public/provider/ecmwf/forecast/hourly/0100deg/netcdf_light/%Y/%j/ECMWF_FORECAST_0100_%Y%m%d%H%M_10U_10V.nc"
14
+
15
+ - name: "ecmwf_0125_1h"
16
+ path: "/home/loc-datawork-cersat-intranet/project/ecmwf/0.125deg/1h/forecasts/%Y/%j/ecmwf_%Y%m%d%H%M.nc"
17
+
18
+ era5:
19
+ - name: "era5_0250_1h"
20
+ path: "/home/ref-ecmwf/ERA5/%Y/%m/era_5-copernicus__%Y%m%d.nc"
21
+
22
+ sarwing_luts_path: "/home/loc-datawork-cersat-public/cache/project/sarwing/GMFS/v1.6/"
23
+ nc_luts_path: "/home/loc-datawork-cersat-public/cache/project/sarwing/GMFS/nc_luts"
24
+ lut_cmod7_path: "/home/loc-datawork-cersat-public/cache/project/sarwing/GMFS/v1.6/GMF_cmod7_official/cmod7_and_python_script"
25
+ lut_ms1ahw_path: "/home/loc-datawork-cersat-public/cache/project/sarwing/GMFS/v1.6/GMF_cmodms1ahw"
26
+
1
27
  no_subdir: True
2
28
  winddir_convention: "meteorological"
3
29
  add_gradientsfeatures: True
@@ -1,3 +1,29 @@
1
+ unit_test_s1_product: "./sentinel-1a/L1/IW/S1A_IW_GRDH_1S/2021/252/S1A_IW_GRDH_1SDV_20210909T130650_20210909T130715_039605_04AE83_C34F.SAFE"
2
+ unit_test_rcm_product: "./l1/rcm/rcm-1/sclnd/2023/273/RCM1_OK2767220_PK2769320_1_SCLND_20230930_214014_VV_VH_GRD"
3
+ unit_test_rs2_product: "./L1/VV_VH/2022/247/RS2_OK141302_PK1242223_DK1208537_SCWA_20220904_093402_VV_VH_SGF"
4
+
5
+ masks:
6
+ land:
7
+ - name: "gshhsH"
8
+ path: "/home/loc-datawork-cersat-public/cache/project/sarwing/masks/gshhg-shp-2.3.7/GSHHS_shp/h/GSHHS_h_L1.shp"
9
+
10
+ ancillary_sources:
11
+ ecmwf:
12
+ - name: "ecmwf_0100_1h"
13
+ path: "/home/loc-datawork-cersat-public/provider/ecmwf/forecast/hourly/0100deg/netcdf_light/%Y/%j/ECMWF_FORECAST_0100_%Y%m%d%H%M_10U_10V.nc"
14
+
15
+ - name: "ecmwf_0125_1h"
16
+ path: "/home/loc-datawork-cersat-intranet/project/ecmwf/0.125deg/1h/forecasts/%Y/%j/ecmwf_%Y%m%d%H%M.nc"
17
+
18
+ era5:
19
+ - name: "era5_0250_1h"
20
+ path: "/home/ref-ecmwf/ERA5/%Y/%m/era_5-copernicus__%Y%m%d.nc"
21
+
22
+ sarwing_luts_path: "/home/loc-datawork-cersat-public/cache/project/sarwing/GMFS/v1.6/"
23
+ nc_luts_path: "/home/loc-datawork-cersat-public/cache/project/sarwing/GMFS/nc_luts"
24
+ lut_cmod7_path: "/home/loc-datawork-cersat-public/cache/project/sarwing/GMFS/v1.6/GMF_cmod7_official/cmod7_and_python_script"
25
+ lut_ms1ahw_path: "/home/loc-datawork-cersat-public/cache/project/sarwing/GMFS/v1.6/GMF_cmodms1ahw"
26
+
1
27
  no_subdir: True
2
28
  winddir_convention: "meteorological"
3
29
  add_gradientsfeatures: True
@@ -110,4 +136,4 @@ RCM:
110
136
  inc_step: 0.1
111
137
  wspd_step: 0.1
112
138
  phi_step: 1.0
113
- resolution: "high"
139
+ resolution: "high"
@@ -1,3 +1,29 @@
1
+ unit_test_s1_product: "./sentinel-1a/L1/IW/S1A_IW_GRDH_1S/2021/252/S1A_IW_GRDH_1SDV_20210909T130650_20210909T130715_039605_04AE83_C34F.SAFE"
2
+ unit_test_rcm_product: "./l1/rcm/rcm-1/sclnd/2023/273/RCM1_OK2767220_PK2769320_1_SCLND_20230930_214014_VV_VH_GRD"
3
+ unit_test_rs2_product: "./L1/VV_VH/2022/247/RS2_OK141302_PK1242223_DK1208537_SCWA_20220904_093402_VV_VH_SGF"
4
+
5
+ masks:
6
+ land:
7
+ - name: "gshhsH"
8
+ path: "/home/loc-datawork-cersat-public/cache/project/sarwing/masks/gshhg-shp-2.3.7/GSHHS_shp/h/GSHHS_h_L1.shp"
9
+
10
+ ancillary_sources:
11
+ ecmwf:
12
+ - name: "ecmwf_0100_1h"
13
+ path: "/home/loc-datawork-cersat-public/provider/ecmwf/forecast/hourly/0100deg/netcdf_light/%Y/%j/ECMWF_FORECAST_0100_%Y%m%d%H%M_10U_10V.nc"
14
+
15
+ - name: "ecmwf_0125_1h"
16
+ path: "/home/loc-datawork-cersat-intranet/project/ecmwf/0.125deg/1h/forecasts/%Y/%j/ecmwf_%Y%m%d%H%M.nc"
17
+
18
+ era5:
19
+ - name: "era5_0250_1h"
20
+ path: "/home/ref-ecmwf/ERA5/%Y/%m/era_5-copernicus__%Y%m%d.nc"
21
+
22
+ sarwing_luts_path: "/home/loc-datawork-cersat-public/cache/project/sarwing/GMFS/v1.6/"
23
+ nc_luts_path: "/home/loc-datawork-cersat-public/cache/project/sarwing/GMFS/nc_luts"
24
+ lut_cmod7_path: "/home/loc-datawork-cersat-public/cache/project/sarwing/GMFS/v1.6/GMF_cmod7_official/cmod7_and_python_script"
25
+ lut_ms1ahw_path: "/home/loc-datawork-cersat-public/cache/project/sarwing/GMFS/v1.6/GMF_cmodms1ahw"
26
+
1
27
  no_subdir: True
2
28
  winddir_convention: "meteorological"
3
29
  add_gradientsfeatures: True
@@ -1,3 +1,29 @@
1
+ unit_test_s1_product: "./sentinel-1a/L1/IW/S1A_IW_GRDH_1S/2021/252/S1A_IW_GRDH_1SDV_20210909T130650_20210909T130715_039605_04AE83_C34F.SAFE"
2
+ unit_test_rcm_product: "./l1/rcm/rcm-1/sclnd/2023/273/RCM1_OK2767220_PK2769320_1_SCLND_20230930_214014_VV_VH_GRD"
3
+ unit_test_rs2_product: "./L1/VV_VH/2022/247/RS2_OK141302_PK1242223_DK1208537_SCWA_20220904_093402_VV_VH_SGF"
4
+
5
+ masks:
6
+ land:
7
+ - name: "gshhsH"
8
+ path: "/home/loc-datawork-cersat-public/cache/project/sarwing/masks/gshhg-shp-2.3.7/GSHHS_shp/h/GSHHS_h_L1.shp"
9
+
10
+ ancillary_sources:
11
+ ecmwf:
12
+ - name: "ecmwf_0100_1h"
13
+ path: "/home/loc-datawork-cersat-public/provider/ecmwf/forecast/hourly/0100deg/netcdf_light/%Y/%j/ECMWF_FORECAST_0100_%Y%m%d%H%M_10U_10V.nc"
14
+
15
+ - name: "ecmwf_0125_1h"
16
+ path: "/home/loc-datawork-cersat-intranet/project/ecmwf/0.125deg/1h/forecasts/%Y/%j/ecmwf_%Y%m%d%H%M.nc"
17
+
18
+ era5:
19
+ - name: "era5_0250_1h"
20
+ path: "/home/ref-ecmwf/ERA5/%Y/%m/era_5-copernicus__%Y%m%d.nc"
21
+
22
+ sarwing_luts_path: "/home/loc-datawork-cersat-public/cache/project/sarwing/GMFS/v1.6/"
23
+ nc_luts_path: "/home/loc-datawork-cersat-public/cache/project/sarwing/GMFS/nc_luts"
24
+ lut_cmod7_path: "/home/loc-datawork-cersat-public/cache/project/sarwing/GMFS/v1.6/GMF_cmod7_official/cmod7_and_python_script"
25
+ lut_ms1ahw_path: "/home/loc-datawork-cersat-public/cache/project/sarwing/GMFS/v1.6/GMF_cmodms1ahw"
26
+
1
27
  no_subdir: True
2
28
  winddir_convention: "meteorological"
3
29
  add_gradientsfeatures: False
@@ -15,10 +15,9 @@ from scipy.ndimage import binary_dilation
15
15
  import re
16
16
  import os
17
17
  import logging
18
- import string
18
+
19
19
 
20
20
  from grdwindinversion.utils import check_incidence_range, get_pol_ratio_name, timing, convert_polarization_name
21
- from grdwindinversion.load_config import getConf
22
21
  os.environ["OMP_NUM_THREADS"] = "1"
23
22
  os.environ["OPENBLAS_NUM_THREADS"] = "1"
24
23
  os.environ["MKL_NUM_THREADS"] = "1"
@@ -170,7 +169,7 @@ def getOutputName(
170
169
  return out_file
171
170
 
172
171
 
173
- def addMasks_toMeta(meta: xsar.BaseMeta) -> dict:
172
+ def addMasks_toMeta(meta: xsar.BaseMeta, conf: dict) -> dict:
174
173
  """
175
174
  Add high-resolution masks (land, ice, lakes, etc.) from shapefiles to meta object.
176
175
 
@@ -192,6 +191,8 @@ def addMasks_toMeta(meta: xsar.BaseMeta) -> dict:
192
191
  ----------
193
192
  meta : xsar.BaseMeta
194
193
  Metadata object to add mask features to. Must have a set_mask_feature method.
194
+ conf : dict
195
+ Configuration dictionary containing masks definition
195
196
 
196
197
  Returns
197
198
  -------
@@ -210,7 +211,6 @@ def addMasks_toMeta(meta: xsar.BaseMeta) -> dict:
210
211
  raise AttributeError(
211
212
  f"Meta object of type {type(meta).__name__} must have a 'set_mask_feature' method")
212
213
 
213
- conf = getConf()
214
214
  masks_by_category = {}
215
215
 
216
216
  # Check for 'masks' key
@@ -395,7 +395,7 @@ def processLandMask(xr_dataset, dilation_iterations=3, merged_masks=None):
395
395
  xr_dataset.land_mask.attrs["history"] = new_history
396
396
 
397
397
 
398
- def getAncillary(meta, ancillary_name="ecmwf"):
398
+ def getAncillary(meta, ancillary_name, conf):
399
399
  """
400
400
  Map ancillary wind from "ecmwf" or "era5" or other sources.
401
401
  This function is used to check if the model files are available and to map the model to the SAR data.
@@ -406,6 +406,8 @@ def getAncillary(meta, ancillary_name="ecmwf"):
406
406
  meta: obj `xsar.BaseMeta` (one of the supported SAR mission)
407
407
  ancillary_name: str
408
408
  Name of the ancillary source (ecmwf or era5)
409
+ conf: dict
410
+ Configuration dictionary containing ancillary_sources
409
411
 
410
412
  Returns
411
413
  -------
@@ -414,8 +416,7 @@ def getAncillary(meta, ancillary_name="ecmwf"):
414
416
  - map_model (dict): mapping of model variables to SAR data
415
417
  - metadata (dict): ancillary metadata with 'source' and 'source_path' keys
416
418
  """
417
- logging.debug("conf: %s", getConf())
418
- conf = getConf()
419
+ logging.debug("conf: %s", conf)
419
420
  if 'ancillary_sources' not in conf:
420
421
  raise ValueError("Configuration must contain 'ancillary_sources'")
421
422
 
@@ -465,7 +466,7 @@ def getAncillary(meta, ancillary_name="ecmwf"):
465
466
  # Log selection
466
467
  if len(ancillary_sources) > 1:
467
468
  logging.info(
468
- f"Multiple {ancillary_name} models configured. Using {selected_name} (priority order)")
469
+ f"Multiple {ancillary_name} models configured. Using {selected_name} (with respect to priority order)")
469
470
  else:
470
471
  logging.info(
471
472
  f"Only one {ancillary_name} model configured: using {selected_name}")
@@ -1002,6 +1003,11 @@ def preprocess(
1002
1003
  if os.path.exists(config_path):
1003
1004
  with open(config_path, "r") as file:
1004
1005
  config_base = yaml.load(file, Loader=yaml.FullLoader)
1006
+
1007
+ # Validate configuration structure
1008
+ from grdwindinversion.utils import test_config
1009
+ test_config(config_base)
1010
+
1005
1011
  try:
1006
1012
  # check if sensor is in the config
1007
1013
  config = config_base[sensor]
@@ -1015,7 +1021,7 @@ def preprocess(
1015
1021
  meta = fct_meta(filename)
1016
1022
 
1017
1023
  # Add masks to meta if configured (land, ice, lakes, etc.)
1018
- masks_by_category = addMasks_toMeta(meta)
1024
+ masks_by_category = addMasks_toMeta(meta, config_base)
1019
1025
 
1020
1026
  # si une des deux n'est pas VV VH HH HV on ne fait rien
1021
1027
  if not all([pol in ["VV", "VH", "HH", "HV"] for pol in meta.pols.split(" ")]):
@@ -1086,7 +1092,8 @@ def preprocess(
1086
1092
  raise FileExistsError("outfile %s already exists" % out_file)
1087
1093
 
1088
1094
  ancillary_name = config["ancillary"]
1089
- map_model, ancillary_metadata = getAncillary(meta, ancillary_name)
1095
+ map_model, ancillary_metadata = getAncillary(
1096
+ meta, ancillary_name, config_base)
1090
1097
  if map_model is None:
1091
1098
  raise Exception(
1092
1099
  f"the weather model is not set `map_model` is None -> you probably don't have access to {ancillary_name} archive"
@@ -1212,10 +1219,10 @@ def preprocess(
1212
1219
  nc_luts = [x for x in [model_co, model_cross] if x.startswith("nc_lut")]
1213
1220
 
1214
1221
  if len(nc_luts) > 0:
1215
- windspeed.register_nc_luts(getConf()["nc_luts_path"])
1222
+ windspeed.register_nc_luts(config_base["nc_luts_path"])
1216
1223
 
1217
1224
  if model_co == "gmf_cmod7":
1218
- windspeed.register_cmod7(getConf()["lut_cmod7_path"])
1225
+ windspeed.register_cmod7(config_base["lut_cmod7_path"])
1219
1226
  #  Step 2 - clean and prepare dataset
1220
1227
 
1221
1228
  # variables to not keep in the L2
@@ -1275,8 +1282,7 @@ def preprocess(
1275
1282
  xr_dataset.offboresight.attrs["standard_name"] = "offboresight"
1276
1283
 
1277
1284
  # merge land masks
1278
- conf = getConf()
1279
- land_mask_strategy = conf.get("LAND_MASK_STRATEGY", "merge")
1285
+ land_mask_strategy = config_base.get("LAND_MASK_STRATEGY", "merge")
1280
1286
  logging.info(f"land_mask_strategy = {land_mask_strategy}")
1281
1287
 
1282
1288
  # Store masks_by_category in config for later cleanup
@@ -1292,11 +1298,10 @@ def preprocess(
1292
1298
  processLandMask(xr_dataset, dilation_iterations=3,
1293
1299
  merged_masks=merged_land_masks)
1294
1300
 
1295
- logging.debug("mask is a copy of land_mask")
1296
-
1297
1301
  # Create main mask from land_mask
1298
1302
  # For now, mask uses the same values as land_mask
1299
1303
  # Can be extended later to include ice (value 3) and other categories
1304
+ logging.debug("mask is a copy of land_mask")
1300
1305
  xr_dataset["mask"] = xr.DataArray(xr_dataset.land_mask)
1301
1306
  xr_dataset.mask.attrs = {}
1302
1307
  xr_dataset.mask.attrs["long_name"] = "Mask of data"
@@ -1709,8 +1714,9 @@ def makeL2(
1709
1714
  config["return_status"] = 99
1710
1715
 
1711
1716
  if dsig_cr_step == "nrcs":
1712
- logging.info(
1713
- "dsig_cr_step is nrcs : polarization are mixed at cost function step")
1717
+ if dual_pol:
1718
+ logging.info(
1719
+ "dsig_cr_step is nrcs : polarization are mixed at cost function step")
1714
1720
  wind_co, wind_dual, windspeed_cr = inverse(
1715
1721
  dual_pol,
1716
1722
  inc=xr_dataset["incidence"],
@@ -1723,13 +1729,17 @@ def makeL2(
1723
1729
  **kwargs,
1724
1730
  )
1725
1731
  elif dsig_cr_step == "wspd":
1726
- logging.info(
1727
- "dsig_cr_step is wspd : polarization are mixed at winds speed step")
1732
+ if dual_pol:
1733
+ logging.info(
1734
+ "dsig_cr_step is wspd : polarization are mixed at winds speed step")
1728
1735
 
1729
- if apply_flattening:
1730
- nesz_cross = xr_dataset["nesz_cross_flattened"]
1736
+ if dual_pol:
1737
+ if apply_flattening:
1738
+ nesz_cross = xr_dataset["nesz_cross_flattened"]
1739
+ else:
1740
+ nesz_cross = xr_dataset.nesz.sel(pol=crosspol)
1731
1741
  else:
1732
- nesz_cross = xr_dataset.nesz.sel(pol=crosspol)
1742
+ nesz_cross = None
1733
1743
 
1734
1744
  wind_co, wind_dual, windspeed_cr, alpha = inverse_dsig_wspd(
1735
1745
  dual_pol,
@@ -1743,10 +1753,12 @@ def makeL2(
1743
1753
  model_cross=model_cross,
1744
1754
  **kwargs
1745
1755
  )
1746
- xr_dataset["alpha"] = xr.DataArray(
1747
- data=alpha, dims=xr_dataset["incidence"].dims, coords=xr_dataset["incidence"].coords)
1748
- xr_dataset["alpha"].attrs["apply_flattening"] = str(apply_flattening)
1749
- xr_dataset["alpha"].attrs["comments"] = "alpha used to ponderate copol and crosspol. this ponderation is done will combining wind speeds."
1756
+ if dual_pol and alpha is not None:
1757
+ xr_dataset["alpha"] = xr.DataArray(
1758
+ data=alpha, dims=xr_dataset["incidence"].dims, coords=xr_dataset["incidence"].coords)
1759
+ xr_dataset["alpha"].attrs["apply_flattening"] = str(
1760
+ apply_flattening)
1761
+ xr_dataset["alpha"].attrs["comments"] = "alpha used to ponderate copol and crosspol. this ponderation is done will combining wind speeds."
1750
1762
 
1751
1763
  else:
1752
1764
  raise ValueError(
@@ -20,7 +20,6 @@ def processor_starting_point():
20
20
 
21
21
  from grdwindinversion.inversion import makeL2
22
22
  from grdwindinversion.utils_memory import get_memory_usage
23
- from grdwindinversion.load_config import config_path
24
23
  import grdwindinversion
25
24
 
26
25
  parser = argparse.ArgumentParser(
@@ -68,8 +67,6 @@ def processor_starting_point():
68
67
  )
69
68
  t0 = time.time()
70
69
 
71
- logging.info("config path: %s", config_path)
72
-
73
70
  input_file = args.input_file.rstrip("/")
74
71
  logging.info("input file: %s", input_file)
75
72
 
@@ -110,7 +110,7 @@ def get_pol_ratio_name(model_co):
110
110
  import re
111
111
 
112
112
  def check_format(s):
113
- pattern = r"^([a-zA-Z0-9]+)_R(high|low)_hh_([a-zA-Z0-9_]+)$"
113
+ pattern = r"^([a-zA-Z0-9_]+)_R(high|low)_hh_([a-zA-Z0-9_]+)$"
114
114
  match = re.match(pattern, s)
115
115
  if match:
116
116
  vvgmf, res, polrationame = match.groups()
@@ -154,3 +154,108 @@ def timing(logger=logger.debug):
154
154
  return wrapper
155
155
 
156
156
  return decorator
157
+
158
+
159
+ def test_config(config):
160
+ """
161
+ Validate configuration structure.
162
+
163
+ Checks that the configuration contains all required fields:
164
+ - ancillary_sources (with ecmwf or era5)
165
+ - nc_luts_path (required)
166
+ - lut_cmod7_path (optional - required only if using gmf_cmod7)
167
+ - lut_ms1ahw_path (optional - required only if using gmf_cmodms1ahw)
168
+ - masks (optional)
169
+
170
+ Note: If you use predefined LUTs (gmf_cmod7, gmf_cmodms1ahw, etc.),
171
+ they must be loaded with the corresponding path keywords:
172
+ - gmf_cmod7 requires lut_cmod7_path
173
+ - gmf_cmodms1ahw requires lut_ms1ahw_path
174
+
175
+ Parameters
176
+ ----------
177
+ config : dict
178
+ Configuration dictionary to validate
179
+
180
+ Raises
181
+ ------
182
+ ValueError
183
+ If configuration is missing required fields or has invalid structure
184
+ """
185
+ # Check ancillary_sources
186
+ if 'ancillary_sources' not in config:
187
+ raise ValueError("Configuration must contain 'ancillary_sources'")
188
+
189
+ if not isinstance(config['ancillary_sources'], dict):
190
+ raise ValueError("'ancillary_sources' must be a dictionary")
191
+
192
+ if not config['ancillary_sources']:
193
+ raise ValueError("'ancillary_sources' must not be empty")
194
+
195
+ # Check that at least ecmwf or era5 is configured
196
+ has_ecmwf = 'ecmwf' in config['ancillary_sources']
197
+ has_era5 = 'era5' in config['ancillary_sources']
198
+ if not (has_ecmwf or has_era5):
199
+ raise ValueError(
200
+ "'ancillary_sources' must contain at least 'ecmwf' or 'era5'")
201
+
202
+ # Validate ancillary sources structure
203
+ for ancillary_type, sources in config['ancillary_sources'].items():
204
+ if not isinstance(sources, list):
205
+ raise ValueError(
206
+ f"'ancillary_sources.{ancillary_type}' must be a list")
207
+ if not sources:
208
+ raise ValueError(
209
+ f"'ancillary_sources.{ancillary_type}' must not be empty")
210
+
211
+ for source in sources:
212
+ if 'name' not in source:
213
+ raise ValueError(
214
+ f"Each source in 'ancillary_sources.{ancillary_type}' must have a 'name' field")
215
+ if 'path' not in source:
216
+ raise ValueError(
217
+ f"Each source in 'ancillary_sources.{ancillary_type}' must have a 'path' field")
218
+ # Check LUT paths
219
+ if 'nc_luts_path' not in config:
220
+ raise ValueError("Configuration must contain 'nc_luts_path'")
221
+ else:
222
+ logger.debug(f"nc_luts_path found: {config['nc_luts_path']}")
223
+
224
+ # Optional LUT paths (only needed if using specific GMFs)
225
+ if 'lut_cmod7_path' in config:
226
+ logger.debug(f"lut_cmod7_path found: {config['lut_cmod7_path']}")
227
+ if 'lut_ms1ahw_path' in config:
228
+ logger.debug(f"lut_ms1ahw_path found: {config['lut_ms1ahw_path']}")
229
+
230
+ # Validate masks structure if present (optional)
231
+ if 'masks' in config:
232
+ if not isinstance(config['masks'], dict):
233
+ raise ValueError("'masks' must be a dictionary")
234
+
235
+ for category, mask_list in config['masks'].items():
236
+ if not isinstance(mask_list, list):
237
+ raise ValueError(f"'masks.{category}' must be a list")
238
+
239
+ for mask in mask_list:
240
+ if 'name' not in mask:
241
+ raise ValueError(
242
+ f"Each mask in 'masks.{category}' must have a 'name' field")
243
+ if 'path' not in mask:
244
+ raise ValueError(
245
+ f"Each mask in 'masks.{category}' must have a 'path' field")
246
+
247
+ logger.debug(f"Masks configured: {list(config['masks'].keys())}")
248
+ else:
249
+ logger.info("No masks configured (optional)")
250
+
251
+ # Check which sensors are configured
252
+ supported_sensors = ['S1A', 'S1B', 'S1C', 'S1D', 'RS2', 'RCM']
253
+ configured_sensors = [
254
+ sensor for sensor in supported_sensors if sensor in config]
255
+ if configured_sensors:
256
+ logger.info(f"Sensors configured: {', '.join(configured_sensors)}")
257
+ else:
258
+ logger.warning(
259
+ "No sensors configured - at least one sensor (S1A, S1B, S1C, S1D, RS2, RCM) should be present")
260
+
261
+ logger.info("Configuration validation passed")