grdwindinversion 1.0.0__py3-none-any.whl → 1.0.2__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,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(
grdwindinversion/main.py CHANGED
@@ -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
 
grdwindinversion/utils.py CHANGED
@@ -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")
@@ -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
@@ -0,0 +1,20 @@
1
+ grdwindinversion/.gitignore,sha256=vmDRHGeESYckUdUztsPr7u6ZNfBYMCVR3GE3AJCBnsY,1204
2
+ grdwindinversion/__init__.py,sha256=WElmPGXfKJ2BbiiBiFLyJObZHEmglAgKZcso7Pl7NhU,331
3
+ grdwindinversion/config_prod_recal.yaml,sha256=Dg8VBZRIEVF0ASC6XLPbv06kwLgNzF13cUwwkjVLW-A,4533
4
+ grdwindinversion/config_prod_recal_streaks_nrcsmod.yaml,sha256=bMdnQnqLvwXVxymCAT0a19mYAgAVjeZgJP8OUHMDIMg,4531
5
+ grdwindinversion/config_prod_streaks.yaml,sha256=II4r-PIgtzAmjw4s307UQGkrfTwmgpympxC9Pmzhulw,4539
6
+ grdwindinversion/config_prod_streaks_nrcsmod.yaml,sha256=k18T5rXS7LRV3n69AOPrhBYWl1io9z4juJ-Z3cjo8ps,4537
7
+ grdwindinversion/config_prod_v3.yaml,sha256=BNXi8r5cF3GkdD126g7y1tAnSpyZwwWoThh87xIP6bQ,4540
8
+ grdwindinversion/gradientFeatures.py,sha256=NIeAJzb1zrlJPBq1th7wjEZoNJMxwqDpNHLMWP6FQN0,18198
9
+ grdwindinversion/inversion.py,sha256=R2PtzY8b94ouGRFLluwNP6_igGAg4YerqRhD1LGI-F8,73167
10
+ grdwindinversion/main.py,sha256=4i5ecjt_hkSYUavS7CxsqR0OfLaWUoxrxambzMWyPbA,3191
11
+ grdwindinversion/utils.py,sha256=VfUePsHc3Zkbf3mqIMSKDAdm9GUca_Qrp2jgiuAAEG0,8519
12
+ grdwindinversion/utils_memory.py,sha256=NA0bvkpCTkEiqCcJuldG1XsrP40-3AQUUt3HLeoRpbY,1432
13
+ grdwindinversion/.github/ISSUE_TEMPLATE.md,sha256=qiM_a7CCUz3fSrz3Q20Se1nwPNFS8QCc8tkwK_0DSCo,327
14
+ grdwindinversion-1.0.2.dist-info/licenses/AUTHORS.rst,sha256=C9EFY6YdOu5qAe6pNIvCvviXH91vCej-WxOobYftoMk,155
15
+ grdwindinversion-1.0.2.dist-info/licenses/LICENSE,sha256=-B8mBiTeY3J7OLuayiV1myqmc7yeijBc7s34kc8RTmg,1075
16
+ grdwindinversion-1.0.2.dist-info/METADATA,sha256=PrgdI1st0CygkG9bqjFoZptcR3xH-rLuSHyW8e3p62k,2529
17
+ grdwindinversion-1.0.2.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
18
+ grdwindinversion-1.0.2.dist-info/entry_points.txt,sha256=2rjvlVCy0iasRXjOz3kOIGuy2OCGQ-VTNuwuViQ6cMM,95
19
+ grdwindinversion-1.0.2.dist-info/top_level.txt,sha256=z6lPix3QPEYOo37qq8plA2hY7S3C8MQZY81agRlksMI,17
20
+ grdwindinversion-1.0.2.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (80.9.0)
2
+ Generator: setuptools (80.10.2)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
@@ -1,25 +0,0 @@
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/vincelhx/Téléchargements/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"
@@ -1,30 +0,0 @@
1
- from yaml import load
2
- import logging
3
- import os
4
- import grdwindinversion
5
- from yaml import CLoader as Loader
6
-
7
- local_config_potential_path1 = os.path.expanduser(
8
- "~/.grdwindinversion/data_config.yaml"
9
- )
10
- local_config_potential_path2 = os.path.join(
11
- os.path.dirname(grdwindinversion.__file__), "local_data_config.yaml"
12
- )
13
- if os.path.exists(local_config_potential_path1):
14
- config_path = local_config_potential_path1
15
- elif os.path.exists(local_config_potential_path2):
16
- config_path = local_config_potential_path2
17
- else:
18
- config_path = os.path.join(
19
- os.path.dirname(grdwindinversion.__file__), "data_config.yaml"
20
- )
21
- stream = open(config_path, "r")
22
- conf = load(stream, Loader=Loader)
23
-
24
-
25
- def getConf():
26
- """
27
- if local_config_potential_path exists it will superseed config_path
28
- :return:
29
- """
30
- return conf
@@ -1,22 +0,0 @@
1
- grdwindinversion/.gitignore,sha256=vmDRHGeESYckUdUztsPr7u6ZNfBYMCVR3GE3AJCBnsY,1204
2
- grdwindinversion/__init__.py,sha256=pQ3rvsuY5HqPMMi-ucT0oGfltpL3M903X2zZRV3QIi0,395
3
- grdwindinversion/config_prod_recal.yaml,sha256=WBvaYjPEzTWkEjwYlP0-xj_D6xgzaavCe6dVFXPJbnw,3129
4
- grdwindinversion/config_prod_recal_streaks_nrcsmod.yaml,sha256=8PwGHnVmaJa-j5jX59BXtUvRIPt0IFCoylMmWvMGjsQ,3127
5
- grdwindinversion/config_prod_streaks.yaml,sha256=imd4XFSLwZ2TH9Rw9bBKOZZHRJ42wW9ckMe5xYgMQKI,3134
6
- grdwindinversion/config_prod_streaks_nrcsmod.yaml,sha256=mme6YulYxc_HD8uX9XCpn1tfJpN-D5GSa_2mZ3X7220,3133
7
- grdwindinversion/config_prod_v3.yaml,sha256=YdIYJcQhX8bM2vhU4pDmT4BI8O0Mn4DVSS_LvB0CbgI,3136
8
- grdwindinversion/data_config.yaml,sha256=WUkJPt5BGi9TF05xRs_uuzCAR4i4mEmoFL9dcGESD_4,1375
9
- grdwindinversion/gradientFeatures.py,sha256=NIeAJzb1zrlJPBq1th7wjEZoNJMxwqDpNHLMWP6FQN0,18198
10
- grdwindinversion/inversion.py,sha256=k7JqFU1M69MJ4zQWiOrzmCGf5Ty3Mkp29i5tPZ4gSkY,72724
11
- grdwindinversion/load_config.py,sha256=ZPozOWt0rf2Pmyc6P2D75cE_9wKUfKfr7RUzlE3WoiY,833
12
- grdwindinversion/main.py,sha256=OtfohM-CWgXD6Zyw2vOFZMDyBXII5qPRZIA8u_dKB4I,3298
13
- grdwindinversion/utils.py,sha256=zcAqpGyyqUleM8YUqyofaEvm2Hua1n-JjY-j83R2az8,4393
14
- grdwindinversion/utils_memory.py,sha256=NA0bvkpCTkEiqCcJuldG1XsrP40-3AQUUt3HLeoRpbY,1432
15
- grdwindinversion/.github/ISSUE_TEMPLATE.md,sha256=qiM_a7CCUz3fSrz3Q20Se1nwPNFS8QCc8tkwK_0DSCo,327
16
- grdwindinversion-1.0.0.dist-info/licenses/AUTHORS.rst,sha256=C9EFY6YdOu5qAe6pNIvCvviXH91vCej-WxOobYftoMk,155
17
- grdwindinversion-1.0.0.dist-info/licenses/LICENSE,sha256=-B8mBiTeY3J7OLuayiV1myqmc7yeijBc7s34kc8RTmg,1075
18
- grdwindinversion-1.0.0.dist-info/METADATA,sha256=Gf4VC2pR5gOqb8Xh8LEPVHGzCPHCObjVTWTMjWdBowY,2529
19
- grdwindinversion-1.0.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
20
- grdwindinversion-1.0.0.dist-info/entry_points.txt,sha256=2rjvlVCy0iasRXjOz3kOIGuy2OCGQ-VTNuwuViQ6cMM,95
21
- grdwindinversion-1.0.0.dist-info/top_level.txt,sha256=z6lPix3QPEYOo37qq8plA2hY7S3C8MQZY81agRlksMI,17
22
- grdwindinversion-1.0.0.dist-info/RECORD,,