disdrodb 0.1.3__py3-none-any.whl → 0.1.5__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.
Files changed (124) hide show
  1. disdrodb/__init__.py +4 -0
  2. disdrodb/_version.py +2 -2
  3. disdrodb/api/checks.py +70 -47
  4. disdrodb/api/configs.py +0 -2
  5. disdrodb/api/create_directories.py +0 -2
  6. disdrodb/api/info.py +3 -3
  7. disdrodb/api/io.py +48 -8
  8. disdrodb/api/path.py +116 -133
  9. disdrodb/api/search.py +12 -3
  10. disdrodb/cli/disdrodb_create_summary.py +113 -0
  11. disdrodb/cli/disdrodb_create_summary_station.py +11 -1
  12. disdrodb/cli/disdrodb_run_l0a_station.py +1 -1
  13. disdrodb/cli/disdrodb_run_l0b_station.py +2 -2
  14. disdrodb/cli/disdrodb_run_l0c_station.py +2 -2
  15. disdrodb/cli/disdrodb_run_l1_station.py +2 -2
  16. disdrodb/cli/disdrodb_run_l2e_station.py +2 -2
  17. disdrodb/cli/disdrodb_run_l2m_station.py +2 -2
  18. disdrodb/constants.py +1 -1
  19. disdrodb/data_transfer/download_data.py +123 -7
  20. disdrodb/etc/products/L1/global.yaml +1 -1
  21. disdrodb/etc/products/L2E/5MIN.yaml +1 -0
  22. disdrodb/etc/products/L2E/global.yaml +1 -1
  23. disdrodb/etc/products/L2M/GAMMA_GS_ND_MAE.yaml +6 -0
  24. disdrodb/etc/products/L2M/GAMMA_ML.yaml +1 -1
  25. disdrodb/etc/products/L2M/LOGNORMAL_GS_LOG_ND_MAE.yaml +6 -0
  26. disdrodb/etc/products/L2M/LOGNORMAL_GS_ND_MAE.yaml +6 -0
  27. disdrodb/etc/products/L2M/LOGNORMAL_ML.yaml +8 -0
  28. disdrodb/etc/products/L2M/global.yaml +11 -3
  29. disdrodb/issue/writer.py +2 -0
  30. disdrodb/l0/check_configs.py +49 -16
  31. disdrodb/l0/configs/LPM/l0a_encodings.yml +2 -2
  32. disdrodb/l0/configs/LPM/l0b_cf_attrs.yml +2 -2
  33. disdrodb/l0/configs/LPM/l0b_encodings.yml +2 -2
  34. disdrodb/l0/configs/LPM/raw_data_format.yml +2 -2
  35. disdrodb/l0/configs/PWS100/l0b_encodings.yml +1 -0
  36. disdrodb/l0/configs/SWS250/bins_diameter.yml +108 -0
  37. disdrodb/l0/configs/SWS250/bins_velocity.yml +83 -0
  38. disdrodb/l0/configs/SWS250/l0a_encodings.yml +18 -0
  39. disdrodb/l0/configs/SWS250/l0b_cf_attrs.yml +72 -0
  40. disdrodb/l0/configs/SWS250/l0b_encodings.yml +155 -0
  41. disdrodb/l0/configs/SWS250/raw_data_format.yml +148 -0
  42. disdrodb/l0/l0a_processing.py +10 -5
  43. disdrodb/l0/l0b_nc_processing.py +10 -6
  44. disdrodb/l0/l0b_processing.py +92 -72
  45. disdrodb/l0/l0c_processing.py +369 -251
  46. disdrodb/l0/readers/LPM/ARM/ARM_LPM.py +8 -1
  47. disdrodb/l0/readers/LPM/AUSTRALIA/MELBOURNE_2007_LPM.py +2 -2
  48. disdrodb/l0/readers/LPM/BELGIUM/ULIEGE.py +256 -0
  49. disdrodb/l0/readers/LPM/BRAZIL/CHUVA_LPM.py +2 -2
  50. disdrodb/l0/readers/LPM/BRAZIL/GOAMAZON_LPM.py +2 -2
  51. disdrodb/l0/readers/LPM/GERMANY/DWD.py +491 -0
  52. disdrodb/l0/readers/LPM/ITALY/GID_LPM.py +2 -2
  53. disdrodb/l0/readers/LPM/ITALY/GID_LPM_W.py +2 -2
  54. disdrodb/l0/readers/LPM/KIT/CHWALA.py +2 -2
  55. disdrodb/l0/readers/LPM/SLOVENIA/ARSO.py +107 -12
  56. disdrodb/l0/readers/LPM/SLOVENIA/UL.py +3 -3
  57. disdrodb/l0/readers/LPM/SWITZERLAND/INNERERIZ_LPM.py +2 -2
  58. disdrodb/l0/readers/PARSIVEL/NCAR/VORTEX2_2010.py +5 -14
  59. disdrodb/l0/readers/PARSIVEL/NCAR/VORTEX2_2010_UF.py +5 -14
  60. disdrodb/l0/readers/PARSIVEL/SLOVENIA/UL.py +117 -8
  61. disdrodb/l0/readers/PARSIVEL2/ARM/ARM_PARSIVEL2.py +4 -0
  62. disdrodb/l0/readers/PARSIVEL2/BRAZIL/CHUVA_PARSIVEL2.py +10 -14
  63. disdrodb/l0/readers/PARSIVEL2/BRAZIL/GOAMAZON_PARSIVEL2.py +10 -14
  64. disdrodb/l0/readers/PARSIVEL2/CANADA/UQAM_NC.py +69 -0
  65. disdrodb/l0/readers/PARSIVEL2/DENMARK/DTU.py +8 -14
  66. disdrodb/l0/readers/PARSIVEL2/DENMARK/EROSION_raw.py +382 -0
  67. disdrodb/l0/readers/PARSIVEL2/FINLAND/FMI_PARSIVEL2.py +4 -0
  68. disdrodb/l0/readers/PARSIVEL2/FRANCE/OSUG.py +1 -1
  69. disdrodb/l0/readers/PARSIVEL2/GREECE/NOA.py +127 -0
  70. disdrodb/l0/readers/PARSIVEL2/ITALY/HYDROX.py +239 -0
  71. disdrodb/l0/readers/PARSIVEL2/MPI/BCO_PARSIVEL2.py +136 -0
  72. disdrodb/l0/readers/PARSIVEL2/MPI/BOWTIE.py +220 -0
  73. disdrodb/l0/readers/PARSIVEL2/NASA/LPVEX.py +109 -0
  74. disdrodb/l0/readers/PARSIVEL2/NCAR/FARM_PARSIVEL2.py +5 -11
  75. disdrodb/l0/readers/PARSIVEL2/NCAR/PERILS_MIPS.py +4 -17
  76. disdrodb/l0/readers/PARSIVEL2/NCAR/RELAMPAGO_PARSIVEL2.py +5 -14
  77. disdrodb/l0/readers/PARSIVEL2/NCAR/SNOWIE_PJ.py +10 -13
  78. disdrodb/l0/readers/PARSIVEL2/NCAR/SNOWIE_SB.py +10 -13
  79. disdrodb/l0/readers/PARSIVEL2/NETHERLANDS/DELFT_NC.py +3 -0
  80. disdrodb/l0/readers/PARSIVEL2/PHILIPPINES/PANGASA.py +232 -0
  81. disdrodb/l0/readers/PARSIVEL2/SPAIN/CENER.py +6 -18
  82. disdrodb/l0/readers/PARSIVEL2/SPAIN/GRANADA.py +120 -0
  83. disdrodb/l0/readers/PARSIVEL2/USA/C3WE.py +7 -25
  84. disdrodb/l0/readers/PWS100/AUSTRIA/HOAL.py +321 -0
  85. disdrodb/l0/readers/SW250/BELGIUM/KMI.py +239 -0
  86. disdrodb/l1/beard_model.py +31 -129
  87. disdrodb/l1/fall_velocity.py +156 -57
  88. disdrodb/l1/filters.py +25 -28
  89. disdrodb/l1/processing.py +12 -14
  90. disdrodb/l1_env/routines.py +46 -17
  91. disdrodb/l2/empirical_dsd.py +6 -0
  92. disdrodb/l2/processing.py +3 -3
  93. disdrodb/metadata/checks.py +132 -125
  94. disdrodb/metadata/geolocation.py +0 -2
  95. disdrodb/psd/fitting.py +180 -210
  96. disdrodb/psd/models.py +1 -1
  97. disdrodb/routines/__init__.py +54 -0
  98. disdrodb/{l0/routines.py → routines/l0.py} +288 -418
  99. disdrodb/{l1/routines.py → routines/l1.py} +60 -92
  100. disdrodb/{l2/routines.py → routines/l2.py} +284 -485
  101. disdrodb/{routines.py → routines/wrappers.py} +100 -7
  102. disdrodb/scattering/axis_ratio.py +95 -85
  103. disdrodb/scattering/permittivity.py +24 -0
  104. disdrodb/scattering/routines.py +56 -36
  105. disdrodb/summary/routines.py +147 -45
  106. disdrodb/utils/archiving.py +434 -0
  107. disdrodb/utils/attrs.py +2 -0
  108. disdrodb/utils/cli.py +5 -5
  109. disdrodb/utils/dask.py +62 -1
  110. disdrodb/utils/decorators.py +31 -0
  111. disdrodb/utils/encoding.py +10 -1
  112. disdrodb/{l2 → utils}/event.py +1 -66
  113. disdrodb/utils/logger.py +1 -1
  114. disdrodb/utils/manipulations.py +22 -12
  115. disdrodb/utils/routines.py +166 -0
  116. disdrodb/utils/time.py +5 -293
  117. disdrodb/utils/xarray.py +3 -0
  118. disdrodb/viz/plots.py +109 -15
  119. {disdrodb-0.1.3.dist-info → disdrodb-0.1.5.dist-info}/METADATA +3 -2
  120. {disdrodb-0.1.3.dist-info → disdrodb-0.1.5.dist-info}/RECORD +124 -96
  121. {disdrodb-0.1.3.dist-info → disdrodb-0.1.5.dist-info}/entry_points.txt +1 -0
  122. {disdrodb-0.1.3.dist-info → disdrodb-0.1.5.dist-info}/WHEEL +0 -0
  123. {disdrodb-0.1.3.dist-info → disdrodb-0.1.5.dist-info}/licenses/LICENSE +0 -0
  124. {disdrodb-0.1.3.dist-info → disdrodb-0.1.5.dist-info}/top_level.txt +0 -0
@@ -89,7 +89,7 @@ def disdrodb_run_l2e_station(
89
89
  Format: <...>/DISDRODB
90
90
  If not specified, uses path specified in the DISDRODB active configuration.
91
91
  """
92
- from disdrodb.l2.routines import run_l2e_station
92
+ from disdrodb.routines.l2 import run_l2e_station
93
93
  from disdrodb.utils.dask import close_dask_cluster, initialize_dask_cluster
94
94
 
95
95
  data_archive_dir = parse_archive_dir(data_archive_dir)
@@ -98,7 +98,7 @@ def disdrodb_run_l2e_station(
98
98
  # -------------------------------------------------------------------------.
99
99
  # If parallel=True, set the dask environment
100
100
  if parallel:
101
- cluster, client = initialize_dask_cluster(minimum_memory="8GB")
101
+ cluster, client = initialize_dask_cluster(minimum_memory="4GB")
102
102
 
103
103
  # -------------------------------------------------------------------------.
104
104
  run_l2e_station(
@@ -89,7 +89,7 @@ def disdrodb_run_l2m_station(
89
89
  Format: <...>/DISDRODB
90
90
  If not specified, uses path specified in the DISDRODB active configuration.
91
91
  """
92
- from disdrodb.l2.routines import run_l2m_station
92
+ from disdrodb.routines.l2 import run_l2m_station
93
93
  from disdrodb.utils.dask import close_dask_cluster, initialize_dask_cluster
94
94
 
95
95
  data_archive_dir = parse_archive_dir(data_archive_dir)
@@ -98,7 +98,7 @@ def disdrodb_run_l2m_station(
98
98
  # -------------------------------------------------------------------------.
99
99
  # If parallel=True, set the dask environment
100
100
  if parallel:
101
- cluster, client = initialize_dask_cluster()
101
+ cluster, client = initialize_dask_cluster(minimum_memory="4GB")
102
102
 
103
103
  # -------------------------------------------------------------------------.
104
104
  run_l2m_station(
disdrodb/constants.py CHANGED
@@ -41,7 +41,7 @@ COORDINATES = [
41
41
  "time",
42
42
  "sample_interval",
43
43
  ]
44
- OPTICAL_SENSORS = ["PARSIVEL", "PARSIVEL2", "LPM", "PWS100"]
44
+ OPTICAL_SENSORS = ["PARSIVEL", "PARSIVEL2", "LPM", "PWS100", "SWS250"]
45
45
  IMPACT_SENSORS = ["RD80"]
46
46
 
47
47
  PRODUCTS = ["RAW", "L0A", "L0B", "L0C", "L1", "L2E", "L2M"]
@@ -239,7 +239,7 @@ def check_consistent_station_name(metadata_filepath, station_name):
239
239
  return station_name
240
240
 
241
241
 
242
- def download_station_data(metadata_filepath: str, data_archive_dir: str, force: bool = False) -> None:
242
+ def download_station_data(metadata_filepath: str, data_archive_dir: str, force: bool = False, verbose=True) -> None:
243
243
  """Download and unzip the station data .
244
244
 
245
245
  Parameters
@@ -275,17 +275,27 @@ def download_station_data(metadata_filepath: str, data_archive_dir: str, force:
275
275
  raise ValueError(f"Invalid disdrodb_data_url '{disdrodb_data_url}' for station {station_name}")
276
276
 
277
277
  # Download files
278
- # - Option 1: Download Zip file containing all station raw data
278
+ # - Option 1: Download ZIP file containing all station raw data
279
279
  if disdrodb_data_url.startswith("https://zenodo.org/") or disdrodb_data_url.startswith("https://cloudnet.fmi.fi/"):
280
280
  download_zip_file(url=disdrodb_data_url, dst_dir=station_dir, force=force)
281
+
281
282
  # - Option 2: Recursive download from a web server via HTTP or HTTPS.
282
283
  elif disdrodb_data_url.startswith("http"):
283
- download_web_server_data(url=disdrodb_data_url, dst_dir=station_dir, force=force, verbose=True)
284
+ download_web_server_data(url=disdrodb_data_url, dst_dir=station_dir, force=force, verbose=verbose)
285
+ # - Retry to be more sure that all data have been downloaded
286
+ download_web_server_data(url=disdrodb_data_url, dst_dir=station_dir, force=True, verbose=verbose)
287
+
288
+ # - Option 3: Recursive download from a ftp server
289
+ elif disdrodb_data_url.startswith("ftp"):
290
+ download_ftp_server_data(url=disdrodb_data_url, dst_dir=station_dir, force=force, verbose=verbose)
291
+ # - Retry to be more sure that all data have been downloaded
292
+ download_ftp_server_data(url=disdrodb_data_url, dst_dir=station_dir, force=True, verbose=verbose)
293
+
284
294
  else:
285
295
  raise NotImplementedError(f"Open a GitHub Issue to enable the download of data from {disdrodb_data_url}.")
286
296
 
287
297
 
288
- ####-----------------------------------------------------------------------------------------.
298
+ ####--------------------------------------------------------------------.
289
299
  #### Download from Web Server via HTTP or HTTPS
290
300
 
291
301
 
@@ -301,9 +311,17 @@ def download_web_server_data(url: str, dst_dir: str, force=True, verbose=True) -
301
311
  3. Compute cut-dirs so that only the last segment of the path remains locally.
302
312
  4. Build and run the wget command.
303
313
 
304
- Example:
305
- download_with_wget("https://ruisdael.citg.tudelft.nl/parsivel/PAR001_Cabauw/2021/202101/")
306
- # Creates a local folder "202101/" with all files and subfolders.
314
+ Parameters
315
+ ----------
316
+ url : str
317
+ HTTPS URL pointing to webserver folder. Example: "https://ruisdael.citg.tudelft.nl/parsivel/PAR001_Cabauw/"
318
+ dst_dir : str
319
+ Local directory where to download the file (DISDRODB station data directory).
320
+ force : bool, optional
321
+ If ``True``, re-download new/updated files (skip unchanged ones).
322
+ If ``False``, keep existing files untouched.
323
+ verbose : bool, optional
324
+ Print wget output (default is True).
307
325
  """
308
326
  # 1. Ensure wget exists
309
327
  ensure_wget_available()
@@ -393,6 +411,104 @@ def build_webserver_wget_command(url: str, cut_dirs: int, dst_dir: str, force: b
393
411
  return cmd
394
412
 
395
413
 
414
+ ####--------------------------------------------------------------------.
415
+ #### Download from FTP Server
416
+
417
+
418
+ def build_ftp_server_wget_command(
419
+ url: str,
420
+ cut_dirs: int,
421
+ dst_dir: str,
422
+ force: bool,
423
+ verbose: bool,
424
+ ) -> list[str]:
425
+ """Construct the wget command list for FTP recursive download.
426
+
427
+ Parameters
428
+ ----------
429
+ url : str
430
+ FTP URL to download from.
431
+ cut_dirs : int
432
+ Number of leading path components to strip.
433
+ dst_dir : str
434
+ Local destination directory.
435
+ force : bool
436
+ If True, re-download newer files (--timestamping).
437
+ If False, keep existing files untouched (--no-clobber).
438
+ verbose : bool
439
+ If False, suppress wget output (-q).
440
+ """
441
+ cmd = ["wget"] # base command
442
+
443
+ if not verbose:
444
+ cmd.append("-q") # quiet mode --> no output except errors
445
+
446
+ cmd += [
447
+ "-r", # recursive --> traverse into subdirectories
448
+ "-np", # no parent --> don't ascend to higher-level dirs
449
+ "-nH", # no host dirs --> avoid creating ftp.example.com/ locally
450
+ f"--cut-dirs={cut_dirs}", # strip N leading path components
451
+ ]
452
+
453
+ if force:
454
+ cmd.append("--timestamping") # download if remote file is newer
455
+ else:
456
+ cmd.append("--no-clobber") # skip files that already exist
457
+
458
+ cmd += [
459
+ "-P", # specify local destination directory
460
+ dst_dir,
461
+ f"ftp://anonymous:disdrodb@{url}", # target FTP URL
462
+ ]
463
+ return cmd
464
+
465
+
466
+ def download_ftp_server_data(url: str, dst_dir: str, force: bool = False, verbose: bool = True) -> None:
467
+ """Download data from an FTP server with anonymous login.
468
+
469
+ Parameters
470
+ ----------
471
+ url : str
472
+ FTP server URL pointing to a folder. Example: "ftp://ftp.example.com/path/to/data/"
473
+ dst_dir : str
474
+ Local directory where to download the file (DISDRODB station data directory).
475
+ force : bool, optional
476
+ If ``True``, re-download new/updated files (skip unchanged ones).
477
+ If ``False``, keep existing files untouched.
478
+ verbose : bool, optional
479
+ Print wget output (default is True).
480
+ """
481
+ ensure_wget_available()
482
+
483
+ # Ensure trailing slash
484
+ url = ensure_trailing_slash(url)
485
+
486
+ # Compute cut-dirs so files land directly in dst_dir
487
+ cut_dirs = compute_cut_dirs(url)
488
+
489
+ # Make destination directory
490
+ os.makedirs(dst_dir, exist_ok=True)
491
+
492
+ # Build wget command
493
+ cmd = build_ftp_server_wget_command(
494
+ url,
495
+ cut_dirs=cut_dirs,
496
+ dst_dir=dst_dir,
497
+ force=force,
498
+ verbose=verbose,
499
+ )
500
+ # Run wget
501
+ try:
502
+ subprocess.run(cmd, check=True)
503
+ except subprocess.CalledProcessError as e:
504
+ raise subprocess.CalledProcessError(
505
+ returncode=e.returncode,
506
+ cmd=e.cmd,
507
+ output=e.output,
508
+ stderr=e.stderr,
509
+ )
510
+
511
+
396
512
  ####--------------------------------------------------------------------.
397
513
  #### Download from Zenodo
398
514
 
@@ -1,5 +1,5 @@
1
1
  product_options:
2
- fall_velocity_method: "Beard1976"
2
+ fall_velocity_model: "Beard1976"
3
3
  minimum_diameter: 0
4
4
  maximum_diameter: 10
5
5
  minimum_velocity: 0
@@ -0,0 +1 @@
1
+ radar_enabled: True
@@ -1,4 +1,4 @@
1
- temporal_resolutions: ["1MIN", "5MIN", "10MIN", "ROLL1MIN"]
1
+ temporal_resolutions: [1MIN"] # "5MIN", "10MIN", "ROLL1MIN"]
2
2
  archive_options:
3
3
  strategy: time_block
4
4
  strategy_options:
@@ -0,0 +1,6 @@
1
+ psd_model: "GammaPSD"
2
+ optimization: "GS"
3
+ optimization_kwargs:
4
+ target: "ND"
5
+ transformation: "identity"
6
+ error_order: 1
@@ -1,7 +1,7 @@
1
1
  psd_model: "GammaPSD"
2
2
  optimization: "ML"
3
3
  optimization_kwargs:
4
- init_method: "M346"
4
+ init_method: "None"
5
5
  probability_method: "cdf"
6
6
  likelihood: "multinomial"
7
7
  truncated_likelihood: True
@@ -0,0 +1,6 @@
1
+ psd_model: "LognormalPSD"
2
+ optimization: "GS"
3
+ optimization_kwargs:
4
+ target: "ND"
5
+ transformation: "log"
6
+ error_order: 1
@@ -0,0 +1,6 @@
1
+ psd_model: "LognormalPSD"
2
+ optimization: "GS"
3
+ optimization_kwargs:
4
+ target: "ND"
5
+ transformation: "identity"
6
+ error_order: 1
@@ -0,0 +1,8 @@
1
+ psd_model: "LognormalPSD"
2
+ optimization: "ML"
3
+ optimization_kwargs:
4
+ init_method: "None"
5
+ probability_method: "cdf"
6
+ likelihood: "multinomial"
7
+ truncated_likelihood: True
8
+ optimizer: "Nelder-Mead"
@@ -1,12 +1,20 @@
1
- temporal_resolutions: ["1MIN", "5MIN", "10MIN"]
2
- models: ["GAMMA_ML", "NGAMMA_GS_LOG_ND_MAE"]
1
+ temporal_resolutions: ["1MIN"] #, "5MIN", "10MIN"]
2
+ models:
3
+ [
4
+ "GAMMA_ML",
5
+ "GAMMA_GS_ND_MAE",
6
+ "NGAMMA_GS_LOG_ND_MAE",
7
+ "NGAMMA_GS_ND_MAE",
8
+ "LOGNORMAL_ML",
9
+ "LOGNORMAL_GS_ND_MAE",
10
+ ]
3
11
  archive_options:
4
12
  strategy: time_block
5
13
  strategy_options:
6
14
  freq: month
7
15
  folder_partitioning: ""
8
16
  product_options:
9
- fall_velocity_method: "Beard1976"
17
+ fall_velocity_model: "Beard1976"
10
18
  diameter_min: 0
11
19
  diameter_max: 10
12
20
  diameter_spacing: 0.05
disdrodb/issue/writer.py CHANGED
@@ -120,9 +120,11 @@ def create_station_issue(data_source, campaign_name, station_name, metadata_arch
120
120
  )
121
121
  if os.path.exists(issue_filepath):
122
122
  raise ValueError("A issue YAML file already exists at {issue_filepath}.")
123
+
123
124
  # Create issue dir if not existing
124
125
  issue_dir = os.path.dirname(issue_filepath)
125
126
  os.makedirs(issue_dir, exist_ok=True)
127
+
126
128
  # Write issue file
127
129
  write_issue(filepath=issue_filepath)
128
130
  print(f"An empty issue YAML file for station {station_name} has been created .")
@@ -22,7 +22,7 @@ import os
22
22
  from typing import Optional, Union
23
23
 
24
24
  import numpy as np
25
- from pydantic import BaseModel, ValidationError, field_validator, model_validator
25
+ from pydantic import BaseModel, Field, ValidationError, field_validator, model_validator
26
26
 
27
27
  from disdrodb.api.configs import available_sensor_names, get_sensor_configs_dir, read_config_file
28
28
  from disdrodb.l0.standards import (
@@ -47,7 +47,7 @@ CONFIG_FILES_LIST = [
47
47
  ]
48
48
 
49
49
 
50
- def _check_yaml_files_exists(sensor_name: str) -> None:
50
+ def check_yaml_files_exists(sensor_name: str) -> None:
51
51
  """Check if all L0 config YAML files exist.
52
52
 
53
53
  Parameters
@@ -64,7 +64,7 @@ def _check_yaml_files_exists(sensor_name: str) -> None:
64
64
  raise FileNotFoundError(f"Missing YAML files {missing_keys_text} in {config_dir} for sensor {sensor_name}.")
65
65
 
66
66
 
67
- def _check_variable_consistency(sensor_name: str) -> None:
67
+ def check_variable_consistency(sensor_name: str) -> None:
68
68
  """
69
69
  Check variable consistency across config files.
70
70
 
@@ -126,7 +126,7 @@ def _schema_error(object_to_validate: Union[str, list], schema: BaseModel, messa
126
126
 
127
127
 
128
128
  class L0BEncodingSchema(BaseModel):
129
- """Pydantic model for DISDRODB L0B encodings."""
129
+ """Pydantic model for DISDRODB netCDF encodings."""
130
130
 
131
131
  contiguous: bool
132
132
  dtype: str
@@ -134,7 +134,7 @@ class L0BEncodingSchema(BaseModel):
134
134
  complevel: int
135
135
  shuffle: bool
136
136
  fletcher32: bool
137
- _FillValue: Optional[Union[int, float]]
137
+ FillValue: Optional[Union[int, float]] = Field(default=None, alias="_FillValue")
138
138
  chunksizes: Optional[Union[int, list[int]]]
139
139
 
140
140
  # if contiguous=False, chunksizes specified, otherwise should be not !
@@ -167,6 +167,39 @@ class L0BEncodingSchema(BaseModel):
167
167
  raise ValueError("'fletcher32' must be set to False if 'contiguous' is True")
168
168
  return values
169
169
 
170
+ # if dtype is integer/unsigned integer, _FillValue must be specified and valid
171
+ @model_validator(mode="before")
172
+ def check_integer_fillvalue(cls, values):
173
+ """Check that integer dtypes have valid _FillValue."""
174
+ dtype = values.get("dtype")
175
+ fill_value = values.get("_FillValue", None)
176
+ integer_types = ["int8", "int16", "int32", "int64", "uint8", "uint16", "uint32", "uint64"]
177
+ # Check if dtype is an integer type
178
+ if dtype in integer_types:
179
+ # _FillValue must be specified for integer types
180
+ if fill_value is None:
181
+ raise ValueError(f"'_FillValue' must be specified for integer dtype '{dtype}'")
182
+
183
+ # Check that _FillValue is within valid range for the dtype
184
+ dtype_info = np.iinfo(dtype)
185
+ max_value = dtype_info.max
186
+ # min_value = dtype_info.min
187
+
188
+ # if not (min_value <= fill_value <= max_value):
189
+ # raise ValueError(
190
+ # f"'_FillValue' ({fill_value}) is out of range for dtype '{dtype}'. "
191
+ # f"Valid range is [{min_value}, {max_value}]",
192
+ # )
193
+
194
+ # Check that _FillValue corresponds to the maximum allowed value
195
+ if fill_value != max_value:
196
+ raise ValueError(
197
+ f"'_FillValue' ({fill_value}) should be set to the maximum allowed value "
198
+ f"({max_value}) for integer dtype '{dtype}'",
199
+ )
200
+
201
+ return values
202
+
170
203
 
171
204
  def check_l0b_encoding(sensor_name: str) -> None:
172
205
  """Check ``l0b_encodings.yml`` file based on the schema defined in the class ``L0BEncodingSchema``.
@@ -176,7 +209,7 @@ def check_l0b_encoding(sensor_name: str) -> None:
176
209
  sensor_name : str
177
210
  Name of the sensor.
178
211
  """
179
- data = read_config_file(sensor_name, product="L0A", filename="l0b_encodings.yml")
212
+ data = read_config_file(sensor_name, product="L0B", filename="l0b_encodings.yml")
180
213
 
181
214
  # check that the second level of the dictionary match the schema
182
215
  for key, value in data.items():
@@ -234,7 +267,7 @@ class RawDataFormatSchema(BaseModel):
234
267
  return None
235
268
 
236
269
 
237
- def _check_raw_data_format(sensor_name: str) -> None:
270
+ def check_raw_data_format(sensor_name: str) -> None:
238
271
  """Check ``raw_data_format.yml`` file based on the schema defined in the class ``RawDataFormatSchema``.
239
272
 
240
273
  Parameters
@@ -253,7 +286,7 @@ def _check_raw_data_format(sensor_name: str) -> None:
253
286
  )
254
287
 
255
288
 
256
- def _check_cf_attributes(sensor_name: str) -> None:
289
+ def check_cf_attributes(sensor_name: str) -> None:
257
290
  """Check that the ``l0b_cf_attrs.yml`` description, long_name and units values are strings.
258
291
 
259
292
  Parameters
@@ -268,7 +301,7 @@ def _check_cf_attributes(sensor_name: str) -> None:
268
301
  raise ValueError(f"Wrong value for {key} in {var} for sensor {sensor_name}.")
269
302
 
270
303
 
271
- def _check_bin_consistency(sensor_name: str) -> None:
304
+ def check_bin_consistency(sensor_name: str) -> None:
272
305
  """Check bin consistency from config file.
273
306
 
274
307
  Do not check the first and last bin !
@@ -313,7 +346,7 @@ def _check_bin_consistency(sensor_name: str) -> None:
313
346
  )
314
347
 
315
348
 
316
- def _check_raw_array(sensor_name: str) -> None:
349
+ def check_raw_array(sensor_name: str) -> None:
317
350
  """Check raw array consistency from config file.
318
351
 
319
352
  Parameters
@@ -363,14 +396,14 @@ def check_sensor_configs(sensor_name: str) -> None:
363
396
  sensor_name : str
364
397
  Name of the sensor.
365
398
  """
366
- _check_yaml_files_exists(sensor_name)
367
- _check_variable_consistency(sensor_name)
399
+ check_yaml_files_exists(sensor_name)
400
+ check_variable_consistency(sensor_name)
368
401
  check_l0b_encoding(sensor_name=sensor_name)
369
402
  check_l0a_encoding(sensor_name=sensor_name)
370
- _check_raw_data_format(sensor_name=sensor_name)
371
- _check_cf_attributes(sensor_name=sensor_name)
372
- _check_bin_consistency(sensor_name=sensor_name)
373
- _check_raw_array(sensor_name=sensor_name)
403
+ check_raw_data_format(sensor_name=sensor_name)
404
+ check_cf_attributes(sensor_name=sensor_name)
405
+ check_bin_consistency(sensor_name=sensor_name)
406
+ check_raw_array(sensor_name=sensor_name)
374
407
 
375
408
 
376
409
  def check_all_sensors_configs() -> None:
@@ -15,7 +15,7 @@ reflectivity: "float32"
15
15
  quality_index: "float32" # 'uint8'
16
16
  max_hail_diameter: "float32"
17
17
  laser_status: "float32" # 'uint8'
18
- static_signal: "float32" # 'uint8'
18
+ static_signal_status: "float32" # 'uint8'
19
19
  laser_temperature_analog_status: "float32" # 'uint8'
20
20
  laser_temperature_digital_status: "float32" # 'uint8'
21
21
  laser_current_analog_status: "float32" # 'uint8'
@@ -29,7 +29,7 @@ current_heating_house_status: "float32" # 'uint8'
29
29
  current_heating_heads_status: "float32" # 'uint8'
30
30
  current_heating_carriers_status: "float32" # 'uint8'
31
31
  control_output_laser_power_status: "float32" # 'uint8'
32
- reserve_status: "float32" # 'uint8'
32
+ reserved_status: "float32" # 'uint8'
33
33
  temperature_interior: "float32" # 'uint16'
34
34
  laser_temperature: "float32" # 'uint16'
35
35
  laser_current_average: "float32" # 'uint16'
@@ -66,7 +66,7 @@ laser_status:
66
66
  description: Status Laser (OK/on:0, off:1)
67
67
  long_name: Status laser
68
68
  units: ""
69
- static_signal:
69
+ static_signal_status:
70
70
  description: Static signal (OK:0, Error:1)
71
71
  long_name: Static signal
72
72
  units: ""
@@ -122,7 +122,7 @@ control_output_laser_power_status:
122
122
  description: Status Control output laser power (OK:0, warning:1)
123
123
  long_name: Status control output laser
124
124
  units: ""
125
- reserve_status:
125
+ reserved_status:
126
126
  description: Reserve Status (0)
127
127
  long_name: Reserve status
128
128
  units: ""
@@ -184,7 +184,7 @@ laser_status:
184
184
  contiguous: false
185
185
  chunksizes: 5000
186
186
  _FillValue: 255
187
- static_signal:
187
+ static_signal_status:
188
188
  dtype: uint8
189
189
  zlib: true
190
190
  complevel: 3
@@ -310,7 +310,7 @@ control_output_laser_power_status:
310
310
  contiguous: false
311
311
  chunksizes: 5000
312
312
  _FillValue: 255
313
- reserve_status:
313
+ reserved_status:
314
314
  dtype: uint8
315
315
  zlib: true
316
316
  complevel: 3
@@ -213,7 +213,7 @@ laser_status:
213
213
  - 0
214
214
  - 1
215
215
  field_number: "22"
216
- static_signal:
216
+ static_signal_status:
217
217
  n_digits: 1
218
218
  n_characters: 1
219
219
  n_decimals: 1
@@ -389,7 +389,7 @@ control_output_laser_power_status:
389
389
  - 0
390
390
  - 1
391
391
  field_number: "36"
392
- reserve_status:
392
+ reserved_status:
393
393
  n_digits: 1
394
394
  n_characters: 1
395
395
  n_decimals: 1
@@ -68,6 +68,7 @@ relative_humidity:
68
68
  shuffle: true
69
69
  fletcher32: false
70
70
  contiguous: false
71
+ _FillValue: 65535
71
72
  chunksizes: 5000
72
73
  wetbulb_temperature:
73
74
  dtype: uint16
@@ -0,0 +1,108 @@
1
+ center:
2
+ 0: 0.2
3
+ 1: 0.45
4
+ 2: 0.55
5
+ 3: 0.65
6
+ 4: 0.75
7
+ 5: 0.85
8
+ 6: 0.95
9
+ 7: 1.05
10
+ 8: 1.15
11
+ 9: 1.25
12
+ 10: 1.35
13
+ 11: 1.5
14
+ 12: 1.75
15
+ 13: 2.08
16
+ 14: 2.475
17
+ 15: 2.945
18
+ 16: 3.5
19
+ 17: 4.16
20
+ 18: 4.95
21
+ 19: 5.89
22
+ 20: 6.7
23
+ bounds:
24
+ 0:
25
+ - 0.0
26
+ - 0.4
27
+ 1:
28
+ - 0.4
29
+ - 0.5
30
+ 2:
31
+ - 0.5
32
+ - 0.6
33
+ 3:
34
+ - 0.6
35
+ - 0.7
36
+ 4:
37
+ - 0.7
38
+ - 0.8
39
+ 5:
40
+ - 0.8
41
+ - 0.9
42
+ 6:
43
+ - 0.9
44
+ - 1.0
45
+ 7:
46
+ - 1.0
47
+ - 1.1
48
+ 8:
49
+ - 1.1
50
+ - 1.2
51
+ 9:
52
+ - 1.2
53
+ - 1.3
54
+ 10:
55
+ - 1.3
56
+ - 1.4
57
+ 11:
58
+ - 1.4
59
+ - 1.6
60
+ 12:
61
+ - 1.6
62
+ - 1.9
63
+ 13:
64
+ - 1.9
65
+ - 2.26
66
+ 14:
67
+ - 2.26
68
+ - 2.69
69
+ 15:
70
+ - 2.69
71
+ - 3.2
72
+ 16:
73
+ - 3.2
74
+ - 3.8
75
+ 17:
76
+ - 3.8
77
+ - 4.52
78
+ 18:
79
+ - 4.52
80
+ - 5.38
81
+ 19:
82
+ - 5.38
83
+ - 6.4
84
+ 20:
85
+ - 6.4
86
+ - 7.0
87
+ width:
88
+ 0: 0.4
89
+ 1: 0.1
90
+ 2: 0.1
91
+ 3: 0.1
92
+ 4: 0.1
93
+ 5: 0.1
94
+ 6: 0.1
95
+ 7: 0.1
96
+ 8: 0.1
97
+ 9: 0.1
98
+ 10: 0.1
99
+ 11: 0.2
100
+ 12: 0.3
101
+ 13: 0.36
102
+ 14: 0.43
103
+ 15: 0.51
104
+ 16: 0.6
105
+ 17: 0.72
106
+ 18: 0.86
107
+ 19: 1.02
108
+ 20: 0.6