disdrodb 0.1.0__py3-none-any.whl → 0.1.1__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 (63) hide show
  1. disdrodb/__init__.py +1 -1
  2. disdrodb/_version.py +2 -2
  3. disdrodb/api/io.py +12 -2
  4. disdrodb/l0/check_standards.py +15 -10
  5. disdrodb/l0/configs/LPM/l0a_encodings.yml +4 -4
  6. disdrodb/l0/configs/LPM/l0b_cf_attrs.yml +22 -6
  7. disdrodb/l0/configs/LPM/l0b_encodings.yml +41 -0
  8. disdrodb/l0/configs/LPM/raw_data_format.yml +40 -0
  9. disdrodb/l0/configs/PARSIVEL/l0b_cf_attrs.yml +1 -1
  10. disdrodb/l0/configs/PARSIVEL/raw_data_format.yml +1 -1
  11. disdrodb/l0/configs/PARSIVEL2/l0b_cf_attrs.yml +4 -4
  12. disdrodb/l0/configs/PARSIVEL2/raw_data_format.yml +10 -10
  13. disdrodb/l0/configs/PWS100/bins_diameter.yml +173 -0
  14. disdrodb/l0/configs/PWS100/bins_velocity.yml +173 -0
  15. disdrodb/l0/configs/PWS100/l0a_encodings.yml +19 -0
  16. disdrodb/l0/configs/PWS100/l0b_cf_attrs.yml +76 -0
  17. disdrodb/l0/configs/PWS100/l0b_encodings.yml +176 -0
  18. disdrodb/l0/configs/PWS100/raw_data_format.yml +182 -0
  19. disdrodb/l0/configs/RD80/raw_data_format.yml +2 -6
  20. disdrodb/l0/l0b_nc_processing.py +1 -1
  21. disdrodb/l0/l0b_processing.py +12 -10
  22. disdrodb/l0/readers/LPM/AUSTRALIA/MELBOURNE_2007_LPM.py +23 -13
  23. disdrodb/l0/readers/LPM/BRAZIL/CHUVA_LPM.py +3 -3
  24. disdrodb/l0/readers/LPM/BRAZIL/GOAMAZON_LPM.py +5 -3
  25. disdrodb/l0/readers/LPM/ITALY/GID_LPM.py +36 -20
  26. disdrodb/l0/readers/LPM/ITALY/GID_LPM_W.py +210 -0
  27. disdrodb/l0/readers/LPM/KIT/CHWALA.py +225 -0
  28. disdrodb/l0/readers/LPM/SLOVENIA/ARSO.py +197 -0
  29. disdrodb/l0/readers/LPM/SLOVENIA/CRNI_VRH.py +197 -0
  30. disdrodb/l0/readers/PARSIVEL/KIT/BURKINA_FASO.py +133 -0
  31. disdrodb/l0/readers/PARSIVEL/NCAR/PECAN_MOBILE.py +1 -1
  32. disdrodb/l0/readers/PARSIVEL/NCAR/VORTEX2_2009.py +1 -1
  33. disdrodb/l0/readers/PARSIVEL/SLOVENIA/UL_FGG.py +121 -0
  34. disdrodb/l0/readers/PARSIVEL2/FRANCE/ENPC_PARSIVEL2.py +189 -0
  35. disdrodb/l0/readers/PARSIVEL2/NCAR/PECAN_FP3.py +1 -1
  36. disdrodb/l0/readers/PARSIVEL2/NCAR/VORTEX_SE_2016_P2.py +1 -1
  37. disdrodb/l0/readers/PWS100/FRANCE/ENPC_PWS100.py +150 -0
  38. disdrodb/l0/readers/RD80/NOAA/PSL_RD80.py +274 -0
  39. disdrodb/l0/readers/template_reader_raw_netcdf_data.py +1 -1
  40. disdrodb/l0/standards.py +7 -4
  41. disdrodb/l0/template_tools.py +2 -2
  42. disdrodb/l1/encoding_attrs.py +21 -6
  43. disdrodb/l1/processing.py +6 -4
  44. disdrodb/l1/resampling.py +1 -1
  45. disdrodb/l1/routines.py +2 -1
  46. disdrodb/l2/empirical_dsd.py +100 -2
  47. disdrodb/l2/event.py +3 -3
  48. disdrodb/l2/processing.py +21 -12
  49. disdrodb/l2/processing_options.py +7 -7
  50. disdrodb/l2/routines.py +3 -3
  51. disdrodb/metadata/checks.py +15 -6
  52. disdrodb/metadata/manipulation.py +2 -2
  53. disdrodb/metadata/standards.py +83 -79
  54. disdrodb/metadata/writer.py +2 -2
  55. disdrodb/routines.py +246 -10
  56. disdrodb/scattering/routines.py +1 -1
  57. disdrodb/utils/dataframe.py +342 -0
  58. {disdrodb-0.1.0.dist-info → disdrodb-0.1.1.dist-info}/METADATA +34 -61
  59. {disdrodb-0.1.0.dist-info → disdrodb-0.1.1.dist-info}/RECORD +63 -47
  60. {disdrodb-0.1.0.dist-info → disdrodb-0.1.1.dist-info}/WHEEL +1 -1
  61. {disdrodb-0.1.0.dist-info → disdrodb-0.1.1.dist-info}/entry_points.txt +3 -3
  62. {disdrodb-0.1.0.dist-info → disdrodb-0.1.1.dist-info}/licenses/LICENSE +0 -0
  63. {disdrodb-0.1.0.dist-info → disdrodb-0.1.1.dist-info}/top_level.txt +0 -0
@@ -500,7 +500,7 @@ def _search_possible_columns(string: str, sensor_name: str) -> list:
500
500
  #### Infer column names and checks validity
501
501
 
502
502
 
503
- def infer_column_names(df: pd.DataFrame, sensor_name: str, row_idx: int = 1):
503
+ def infer_column_names(df: pd.DataFrame, sensor_name: str, row_idx: int = 0):
504
504
  """Try to guess the dataframe columns names based on string characteristics.
505
505
 
506
506
  Parameters
@@ -511,7 +511,7 @@ def infer_column_names(df: pd.DataFrame, sensor_name: str, row_idx: int = 1):
511
511
  name of the sensor.
512
512
  row_idx : int, optional
513
513
  The row index of the dataframe to use to infer the column names.
514
- The default row index is 1.
514
+ The default row index is 0.
515
515
 
516
516
  Returns
517
517
  -------
@@ -51,21 +51,36 @@ def get_attrs_dict():
51
51
  "long_name": "Measured average drop fall velocity",
52
52
  "units": "m s-1",
53
53
  },
54
- "n_drops_selected": {
54
+ "N": {
55
55
  "description": "Total number of selected drops",
56
56
  "long_name": "Total number of selected drops",
57
57
  "units": "",
58
58
  },
59
- "n_drops_discarded": {
59
+ "Nremoved": {
60
60
  "description": "Total number of discarded drops",
61
61
  "long_name": "Total number of discarded drops",
62
62
  "units": "",
63
63
  },
64
- "n_bins_with_drops": {
64
+ "Nbins": {
65
65
  "description": "Number of diameter bins with drops",
66
66
  "long_name": "Number of diameter bins with drops",
67
67
  "units": "",
68
68
  },
69
+ "Nbins_missing": {
70
+ "description": "Number of diameter bins with no drops",
71
+ "long_name": "Number of diameter bins with no drops",
72
+ "units": "",
73
+ },
74
+ "Nbins_missing_fraction": {
75
+ "description": "Fraction of diameter bins with no drops",
76
+ "long_name": "Fraction of diameter bins with no drops",
77
+ "units": "",
78
+ },
79
+ "Nbins_missing_consecutive": {
80
+ "description": "Maximum number of consecutive diameter bins with no drops",
81
+ "long_name": "Maximum number of consecutive diameter bins with no drops",
82
+ "units": "",
83
+ },
69
84
  #### L2
70
85
  "drop_number_concentration": {
71
86
  "description": "Number concentration of drops per diameter class per unit volume",
@@ -436,7 +451,7 @@ def get_encoding_dict():
436
451
  "contiguous": False,
437
452
  "_FillValue": 4294967295,
438
453
  },
439
- "n_drops_selected": {
454
+ "N": {
440
455
  "dtype": "uint32",
441
456
  "zlib": True,
442
457
  "complevel": 3,
@@ -445,7 +460,7 @@ def get_encoding_dict():
445
460
  "contiguous": False,
446
461
  "_FillValue": 4294967295,
447
462
  },
448
- "n_drops_discarded": {
463
+ "Nremoved": {
449
464
  "dtype": "uint32",
450
465
  "zlib": True,
451
466
  "complevel": 3,
@@ -454,7 +469,7 @@ def get_encoding_dict():
454
469
  "contiguous": False,
455
470
  "_FillValue": 4294967295,
456
471
  },
457
- "n_bins_with_drops": {
472
+ "Nbins": {
458
473
  "dtype": "uint8",
459
474
  "_FillValue": 255,
460
475
  "zlib": True,
disdrodb/l1/processing.py CHANGED
@@ -26,7 +26,7 @@ from disdrodb.l1.filters import define_spectrum_mask, filter_diameter_bins, filt
26
26
  from disdrodb.l1.resampling import add_sample_interval
27
27
  from disdrodb.l1_env.routines import load_env_dataset
28
28
  from disdrodb.l2.empirical_dsd import ( # TODO: maybe move out of L2
29
- count_bins_with_drops,
29
+ compute_qc_bins_metrics,
30
30
  get_min_max_diameter,
31
31
  )
32
32
  from disdrodb.utils.attrs import set_attrs
@@ -172,9 +172,11 @@ def generate_l1(
172
172
  # Add drop statistics
173
173
  ds_l1["Dmin"] = min_drop_diameter
174
174
  ds_l1["Dmax"] = max_drop_diameter
175
- ds_l1["n_drops_selected"] = drop_counts.sum(dim=DIAMETER_DIMENSION)
176
- ds_l1["n_drops_discarded"] = drop_counts_raw.sum(dim=DIAMETER_DIMENSION) - ds_l1["n_drops_selected"]
177
- ds_l1["n_bins_with_drops"] = count_bins_with_drops(ds_l1)
175
+ ds_l1["N"] = drop_counts.sum(dim=DIAMETER_DIMENSION)
176
+ ds_l1["Nremoved"] = drop_counts_raw.sum(dim=DIAMETER_DIMENSION) - ds_l1["N"]
177
+
178
+ # Add bins statistics
179
+ ds_l1.update(compute_qc_bins_metrics(ds_l1))
178
180
 
179
181
  # -------------------------------------------------------------------------------------------
180
182
  # Add quality flags
disdrodb/l1/resampling.py CHANGED
@@ -141,7 +141,7 @@ def resample_dataset(ds, sample_interval, accumulation_interval, rolling=True):
141
141
 
142
142
  # Retrieve variables to average/sum
143
143
  var_to_average = ["fall_velocity"]
144
- var_to_cumulate = ["raw_drop_number", "drop_number", "drop_counts", "n_drops_selected", "n_drops_discarded"]
144
+ var_to_cumulate = ["raw_drop_number", "drop_number", "drop_counts", "N", "Nremoved"]
145
145
  var_to_min = ["Dmin"]
146
146
  var_to_max = ["Dmax"]
147
147
 
disdrodb/l1/routines.py CHANGED
@@ -61,6 +61,7 @@ def get_l1_options():
61
61
  # - TODO: as function of sensor name
62
62
 
63
63
  # minimum_diameter
64
+ # --> PWS100: 0.05
64
65
  # --> PARSIVEL: 0.2495
65
66
  # --> RD80: 0.313
66
67
  # --> LPM: 0.125 (we currently discard first bin with this setting)
@@ -75,7 +76,7 @@ def get_l1_options():
75
76
  "fall_velocity_method": "Beard1976",
76
77
  # Diameter-Velocity Filtering Options
77
78
  "minimum_diameter": 0.2495, # OTT PARSIVEL first two bin no data !
78
- "maximum_diameter": 8,
79
+ "maximum_diameter": 10,
79
80
  "minimum_velocity": 0,
80
81
  "maximum_velocity": 12,
81
82
  "above_velocity_fraction": 0.5,
@@ -101,6 +101,101 @@ def count_bins_with_drops(ds):
101
101
  return da
102
102
 
103
103
 
104
+ def _compute_qc_bins_metrics(arr):
105
+ # Find indices of non-zero elements
106
+ arr = arr.copy()
107
+ arr[np.isnan(arr)] = 0
108
+ non_zero_indices = np.nonzero(arr)[0]
109
+ if non_zero_indices.size == 0:
110
+ return np.array([0, len(arr), 1, len(arr)])
111
+
112
+ # Define bins interval with drops
113
+ start_idx, end_idx = non_zero_indices[0], non_zero_indices[-1]
114
+ segment = arr[start_idx : end_idx + 1]
115
+
116
+ # Compute number of bins with drops
117
+ total_bins = segment.size
118
+
119
+ # Compute number of missing bins (zeros)
120
+ n_missing_bins = int(np.sum(segment == 0))
121
+
122
+ # Compute fraction of bins with missing drops
123
+ fraction_missing = n_missing_bins / total_bins
124
+
125
+ # Identify longest with with consecutive zeros
126
+ zero_mask = (segment == 0).astype(int)
127
+ # - Pad with zeros at both ends to detect edges
128
+ padded = np.pad(zero_mask, (1, 1), "constant", constant_values=0)
129
+ diffs = np.diff(padded)
130
+ # - Start and end indices of runs
131
+ run_starts = np.where(diffs == 1)[0]
132
+ run_ends = np.where(diffs == -1)[0]
133
+ run_lengths = run_ends - run_starts
134
+ max_consecutive_missing = run_lengths.max() if run_lengths.size > 0 else 0
135
+
136
+ # Define output
137
+ output = np.array([total_bins, n_missing_bins, fraction_missing, max_consecutive_missing])
138
+ return output
139
+
140
+
141
+ def compute_qc_bins_metrics(ds):
142
+ """
143
+ Compute quality-control metrics for drop-count bins along the diameter dimension.
144
+
145
+ This function selects the first available drop-related variable from the dataset,
146
+ optionally collapses over velocity methods and the velocity dimension, then
147
+ computes four metrics per time step:
148
+
149
+ 1. Nbins: total number of diameter bins between the first and last non-zero count
150
+ 2. Nbins_missing: number of bins with zero or NaN counts in that interval
151
+ 3. Nbins_missing_fraction: fraction of missing bins (zeros) in the interval
152
+ 4. Nbins_missing_consecutive: maximum length of consecutive missing bins
153
+
154
+ Parameters
155
+ ----------
156
+ ds : xr.Dataset
157
+ Input dataset containing one of the following variables:
158
+ 'drop_counts', 'drop_number_concentration', or 'drop_number'.
159
+ If a 'velocity_method' dimension exists, only the first method is used.
160
+ If a velocity dimension (specified by VELOCITY_DIMENSION) exists, it is summed over.
161
+
162
+ Returns
163
+ -------
164
+ xr.Dataset
165
+ Dataset with a new 'metric' dimension of size 4 and coordinates:
166
+ ['Nbins', 'Nbins_missing', 'Nbins_missing_fraction', 'Nbins_missing_consecutive'],
167
+ indexed by 'time'.
168
+ """
169
+ # Select useful variable
170
+ candidate_variables = ["drop_counts", "drop_number_concentration", "drop_number"]
171
+ available_variables = [var for var in candidate_variables if var in ds]
172
+ if len(available_variables) == 0:
173
+ raise ValueError(f"One of these variables is required: {candidate_variables}")
174
+ da = ds[available_variables[0]]
175
+ if "velocity_method" in da.dims:
176
+ da = da.isel(velocity_method=0)
177
+ da = da.drop_vars("velocity_method")
178
+ if VELOCITY_DIMENSION in da.dims:
179
+ da = da.sum(dim=VELOCITY_DIMENSION)
180
+
181
+ # Compute QC metrics
182
+ da_qc_bins = xr.apply_ufunc(
183
+ _compute_qc_bins_metrics,
184
+ da,
185
+ input_core_dims=[[DIAMETER_DIMENSION]],
186
+ output_core_dims=[["metric"]],
187
+ vectorize=True,
188
+ dask="parallelized",
189
+ output_dtypes=[float],
190
+ dask_gufunc_kwargs={"output_sizes": {"metric": 4}},
191
+ )
192
+
193
+ # Assign meaningful labels to the qc 'metric' dimension
194
+ variables = ["Nbins", "Nbins_missing", "Nbins_missing_fraction", "Nbins_missing_consecutive"]
195
+ ds_qc_bins = da_qc_bins.assign_coords(metric=variables).to_dataset(dim="metric")
196
+ return ds_qc_bins
197
+
198
+
104
199
  ####-------------------------------------------------------------------------------------------------------------------.
105
200
  #### DSD Spectrum, Concentration, Moments
106
201
 
@@ -117,13 +212,16 @@ def get_effective_sampling_area(sensor_name, diameter):
117
212
  B = 30 / 1000 # Width of the Parsivel beam in m (30mm)
118
213
  sampling_area = L * (B - diameter / 2)
119
214
  return sampling_area
120
- if sensor_name in "LPM":
215
+ if sensor_name == "LPM":
121
216
  # Calculate sampling area for each diameter bin (S_i)
122
217
  L = 228 / 1000 # Length of the Parsivel beam in m (228 mm)
123
218
  B = 20 / 1000 # Width of the Parsivel beam in m (20 mm)
124
219
  sampling_area = L * (B - diameter / 2)
125
220
  return sampling_area
126
- if sensor_name in "RD80":
221
+ if sensor_name == "PWS100":
222
+ sampling_area = 0.004 # m2 # TODO: L * (B - diameter / 2) ?
223
+ return sampling_area
224
+ if sensor_name == "RD80":
127
225
  sampling_area = 0.005 # m2
128
226
  return sampling_area
129
227
  raise NotImplementedError(f"Effective sampling area for {sensor_name} must yet to be specified in the software.")
disdrodb/l2/event.py CHANGED
@@ -43,7 +43,7 @@ def identify_events(
43
43
  ):
44
44
  """Return a list of rainy events.
45
45
 
46
- Rainy timesteps are defined when n_drops_selected > min_n_drops.
46
+ Rainy timesteps are defined when N > min_n_drops.
47
47
  Any rainy isolated timesteps (based on neighborhood criteria) is removed.
48
48
  Then, consecutive rainy timesteps are grouped into the same event if the time gap between them does not
49
49
  exceed `intra_event_max_time_gap`. Finally, events that do not meet minimum size or duration
@@ -90,7 +90,7 @@ def identify_events(
90
90
  else:
91
91
  list_ds = [xr.open_dataset(filepath, chunks={}, cache=False, decode_timedelta=False) for filepath in filepaths]
92
92
  # Filter dataset for requested variables
93
- variables = ["time", "n_drops_selected"]
93
+ variables = ["time", "N"]
94
94
  list_ds = [ds[variables] for ds in list_ds]
95
95
  # Concat datasets
96
96
  ds = xr.concat(list_ds, dim="time", compat="no_conflicts", combine_attrs="override")
@@ -102,7 +102,7 @@ def identify_events(
102
102
  # Sort dataset by time
103
103
  ds = ensure_sorted_by_time(ds)
104
104
  # Define candidate timesteps to group into events
105
- idx_valid = ds["n_drops_selected"].data > min_n_drops
105
+ idx_valid = ds["N"].data > min_n_drops
106
106
  timesteps = ds["time"].data[idx_valid]
107
107
  # Define event list
108
108
  event_list = group_timesteps_into_event(
disdrodb/l2/processing.py CHANGED
@@ -24,8 +24,8 @@ from disdrodb.l1.fall_velocity import get_raindrop_fall_velocity
24
24
  from disdrodb.l1_env.routines import load_env_dataset
25
25
  from disdrodb.l2.empirical_dsd import (
26
26
  compute_integral_parameters,
27
+ compute_qc_bins_metrics,
27
28
  compute_spectrum_parameters,
28
- count_bins_with_drops,
29
29
  get_drop_average_velocity,
30
30
  get_drop_number_concentration,
31
31
  get_effective_sampling_area,
@@ -140,11 +140,12 @@ def generate_l2_empirical(ds, ds_env=None, compute_spectra=False):
140
140
  # Discard all timesteps without measured drops
141
141
  # - This allow to speed up processing
142
142
  # - Regularization can be done at the end
143
- ds = ds.isel(time=ds["n_drops_selected"] > 0)
143
+ ds = ds.isel(time=ds["N"] > 0)
144
144
 
145
145
  # Count number of diameter bins with data
146
- if "n_bins_with_drops" not in ds:
147
- ds["n_bins_with_drops"] = count_bins_with_drops(ds)
146
+ if "Nbins" not in ds:
147
+ # Add bins statistics
148
+ ds.update(compute_qc_bins_metrics(ds))
148
149
 
149
150
  # Retrieve ENV dataset or take defaults
150
151
  # --> Used for fall velocity and water density estimates
@@ -174,8 +175,8 @@ def generate_l2_empirical(ds, ds_env=None, compute_spectra=False):
174
175
  "drop_number", # 2D V x D
175
176
  "drop_counts", # 1D D
176
177
  "sample_interval",
177
- "n_drops_selected",
178
- "n_drops_discarded",
178
+ "N",
179
+ "Nremoved",
179
180
  "Dmin",
180
181
  "Dmax",
181
182
  "fall_velocity",
@@ -291,14 +292,14 @@ def generate_l2_model(
291
292
  fall_velocity_method="Beard1976",
292
293
  # PSD discretization
293
294
  diameter_min=0,
294
- diameter_max=8,
295
+ diameter_max=10,
295
296
  diameter_spacing=0.05,
296
297
  # Fitting options
297
298
  psd_model=None,
298
299
  optimization=None,
299
300
  optimization_kwargs=None,
300
301
  # Filtering options
301
- min_bins_with_drops=4,
302
+ min_nbins=4,
302
303
  remove_timesteps_with_few_bins=False,
303
304
  mask_timesteps_with_few_bins=False,
304
305
  # GOF metrics options
@@ -357,11 +358,12 @@ def generate_l2_model(
357
358
  ####------------------------------------------------------.
358
359
  #### Preprocessing
359
360
  # Count number of diameter bins with data
360
- if "n_bins_with_drops" not in ds:
361
- ds["n_bins_with_drops"] = count_bins_with_drops(ds)
361
+ if "Nbins" not in ds:
362
+ # Add bins statistics
363
+ ds.update(compute_qc_bins_metrics(ds))
362
364
 
363
365
  # Identify timesteps with enough diameter bins with counted trops
364
- valid_timesteps = ds["n_bins_with_drops"] >= min_bins_with_drops
366
+ valid_timesteps = ds["Nbins"] >= min_nbins
365
367
 
366
368
  # Drop such timesteps if asked
367
369
  if remove_timesteps_with_few_bins:
@@ -466,7 +468,14 @@ def generate_l2_model(
466
468
 
467
469
 
468
470
  @check_pytmatrix_availability
469
- def generate_l2_radar(ds, radar_band=None, canting_angle_std=7, diameter_max=8, axis_ratio="Thurai2007", parallel=True):
471
+ def generate_l2_radar(
472
+ ds,
473
+ radar_band=None,
474
+ canting_angle_std=7,
475
+ diameter_max=10,
476
+ axis_ratio="Thurai2007",
477
+ parallel=True,
478
+ ):
470
479
  """Simulate polarimetric radar variables from empirical drop number concentration or the estimated PSD.
471
480
 
472
481
  Parameters
@@ -7,16 +7,16 @@ DEFAULT_CONFIG = {
7
7
  "global_settings": {
8
8
  "time_integration": [
9
9
  "1MIN",
10
+ "5MIN",
10
11
  "10MIN",
11
12
  "ROLL1MIN",
12
- "ROLL10MIN",
13
13
  ], # ["10S", "30S", "1MIN", "5MIN", "10MIN", "15MIN", "30MIN", "1H", "ROLL5MIN", "ROLL10MIN"],
14
14
  # Radar options
15
15
  "radar_simulation_enabled": False,
16
16
  "radar_simulation_options": {
17
17
  "radar_band": ["S", "C", "X", "Ku", "Ka", "W"],
18
18
  "canting_angle_std": 7,
19
- "diameter_max": 8,
19
+ "diameter_max": 10,
20
20
  "axis_ratio": "Thurai2007",
21
21
  },
22
22
  # L2E options
@@ -25,10 +25,10 @@ DEFAULT_CONFIG = {
25
25
  "l2m_options": {
26
26
  "fall_velocity_method": "Beard1976",
27
27
  "diameter_min": 0,
28
- "diameter_max": 8,
28
+ "diameter_max": 10,
29
29
  "diameter_spacing": 0.05,
30
30
  "gof_metrics": True,
31
- "min_bins_with_drops": 4,
31
+ "min_nbins": 4,
32
32
  "remove_timesteps_with_few_bins": False,
33
33
  "mask_timesteps_with_few_bins": False,
34
34
  "models": {
@@ -112,7 +112,7 @@ TEST_CONFIG = {
112
112
  "radar_simulation_options": {
113
113
  "radar_band": ["S", "C", "X", "Ku", "Ka", "W"],
114
114
  "canting_angle_std": 7,
115
- "diameter_max": 8,
115
+ "diameter_max": 10,
116
116
  "axis_ratio": "Thurai2007",
117
117
  },
118
118
  # L2E options
@@ -121,10 +121,10 @@ TEST_CONFIG = {
121
121
  "l2m_options": {
122
122
  "fall_velocity_method": "Beard1976",
123
123
  "diameter_min": 0,
124
- "diameter_max": 8,
124
+ "diameter_max": 10,
125
125
  "diameter_spacing": 0.05,
126
126
  "gof_metrics": True,
127
- "min_bins_with_drops": 4,
127
+ "min_nbins": 4,
128
128
  "remove_timesteps_with_few_bins": False,
129
129
  "mask_timesteps_with_few_bins": False,
130
130
  "models": {
disdrodb/l2/routines.py CHANGED
@@ -156,11 +156,11 @@ def _generate_l2e(
156
156
 
157
157
  ##------------------------------------------------------------------------.
158
158
  # Remove timesteps with no drops or NaN (from L2E computations)
159
- # timestep_zero_drops = ds["time"].data[ds["n_drops_selected"].data == 0]
160
- # timestep_nan = ds["time"].data[np.isnan(ds["n_drops_selected"].data)]
159
+ # timestep_zero_drops = ds["time"].data[ds["N"].data == 0]
160
+ # timestep_nan = ds["time"].data[np.isnan(ds["N"].data)]
161
161
  # TODO: Make it a choice !
162
162
  indices_valid_timesteps = np.where(
163
- ~np.logical_or(ds["n_drops_selected"].data == 0, np.isnan(ds["n_drops_selected"].data)),
163
+ ~np.logical_or(ds["N"].data == 0, np.isnan(ds["N"].data)),
164
164
  )[0]
165
165
  ds = ds.isel(time=indices_valid_timesteps)
166
166
 
@@ -30,7 +30,7 @@ from disdrodb.api.info import (
30
30
  from disdrodb.configs import get_metadata_archive_dir
31
31
  from disdrodb.metadata.reader import read_station_metadata
32
32
  from disdrodb.metadata.search import get_list_metadata
33
- from disdrodb.metadata.standards import get_valid_metadata_keys
33
+ from disdrodb.metadata.standards import METADATA_KEYS, METADATA_VALUES
34
34
  from disdrodb.utils.yaml import read_yaml
35
35
 
36
36
  #### --------------------------------------------------------------------------.
@@ -40,19 +40,17 @@ from disdrodb.utils.yaml import read_yaml
40
40
  def get_metadata_missing_keys(metadata):
41
41
  """Return the DISDRODB metadata keys which are missing."""
42
42
  keys = list(metadata.keys())
43
- valid_keys = get_valid_metadata_keys()
44
43
  # Identify missing keys
45
- idx_missing_keys = np.where(np.isin(valid_keys, keys, invert=True))[0]
46
- missing_keys = np.array(valid_keys)[idx_missing_keys].tolist()
44
+ idx_missing_keys = np.where(np.isin(METADATA_KEYS, keys, invert=True))[0]
45
+ missing_keys = np.array(METADATA_KEYS)[idx_missing_keys].tolist()
47
46
  return missing_keys
48
47
 
49
48
 
50
49
  def get_metadata_invalid_keys(metadata):
51
50
  """Return the DISDRODB metadata keys which are not valid."""
52
51
  keys = list(metadata.keys())
53
- valid_keys = get_valid_metadata_keys()
54
52
  # Identify invalid keys
55
- idx_invalid_keys = np.where(np.isin(keys, valid_keys, invert=True))[0]
53
+ idx_invalid_keys = np.where(np.isin(keys, METADATA_KEYS, invert=True))[0]
56
54
  invalid_keys = np.array(keys)[idx_invalid_keys].tolist()
57
55
  return invalid_keys
58
56
 
@@ -73,11 +71,22 @@ def _check_metadata_values(metadata):
73
71
  """Check validity of metadata values.
74
72
 
75
73
  If null is specified in the YAML files (or None in the dict) raise error.
74
+ For specific keys, check that values match one of the allowed options in METADATA_VALUES.
76
75
  """
77
76
  for key, value in metadata.items():
77
+ # Check for None/null values
78
78
  if isinstance(value, type(None)):
79
79
  raise ValueError(f"The metadata key {key} has None or null value. Use '' instead.")
80
80
 
81
+ # Check that values match allowed options for specific keys
82
+ if key in METADATA_VALUES:
83
+ allowed_values = METADATA_VALUES[key]
84
+ if value not in allowed_values:
85
+ allowed_str = ", ".join([f"'{v}'" for v in allowed_values])
86
+ raise ValueError(
87
+ f"Invalid value '{value}' for metadata key '{key}'. " f"Allowed values are: {allowed_str}.",
88
+ )
89
+
81
90
 
82
91
  def _check_metadata_campaign_name(metadata, expected_name):
83
92
  """Check metadata ``campaign_name``."""
@@ -41,8 +41,8 @@ def add_missing_metadata_keys(metadata):
41
41
 
42
42
  def sort_metadata_dictionary(metadata):
43
43
  """Sort the keys of the metadata dictionary by ``valid_metadata_keys`` list order."""
44
- from disdrodb.metadata.standards import get_valid_metadata_keys
44
+ from disdrodb.metadata.standards import METADATA_KEYS
45
45
 
46
- list_metadata_keys = get_valid_metadata_keys()
46
+ list_metadata_keys = METADATA_KEYS
47
47
  metadata = {k: metadata[k] for k in list_metadata_keys}
48
48
  return metadata