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.
- disdrodb/__init__.py +1 -1
- disdrodb/_version.py +2 -2
- disdrodb/api/io.py +12 -2
- disdrodb/l0/check_standards.py +15 -10
- disdrodb/l0/configs/LPM/l0a_encodings.yml +4 -4
- disdrodb/l0/configs/LPM/l0b_cf_attrs.yml +22 -6
- disdrodb/l0/configs/LPM/l0b_encodings.yml +41 -0
- disdrodb/l0/configs/LPM/raw_data_format.yml +40 -0
- disdrodb/l0/configs/PARSIVEL/l0b_cf_attrs.yml +1 -1
- disdrodb/l0/configs/PARSIVEL/raw_data_format.yml +1 -1
- disdrodb/l0/configs/PARSIVEL2/l0b_cf_attrs.yml +4 -4
- disdrodb/l0/configs/PARSIVEL2/raw_data_format.yml +10 -10
- disdrodb/l0/configs/PWS100/bins_diameter.yml +173 -0
- disdrodb/l0/configs/PWS100/bins_velocity.yml +173 -0
- disdrodb/l0/configs/PWS100/l0a_encodings.yml +19 -0
- disdrodb/l0/configs/PWS100/l0b_cf_attrs.yml +76 -0
- disdrodb/l0/configs/PWS100/l0b_encodings.yml +176 -0
- disdrodb/l0/configs/PWS100/raw_data_format.yml +182 -0
- disdrodb/l0/configs/RD80/raw_data_format.yml +2 -6
- disdrodb/l0/l0b_nc_processing.py +1 -1
- disdrodb/l0/l0b_processing.py +12 -10
- disdrodb/l0/readers/LPM/AUSTRALIA/MELBOURNE_2007_LPM.py +23 -13
- disdrodb/l0/readers/LPM/BRAZIL/CHUVA_LPM.py +3 -3
- disdrodb/l0/readers/LPM/BRAZIL/GOAMAZON_LPM.py +5 -3
- disdrodb/l0/readers/LPM/ITALY/GID_LPM.py +36 -20
- disdrodb/l0/readers/LPM/ITALY/GID_LPM_W.py +210 -0
- disdrodb/l0/readers/LPM/KIT/CHWALA.py +225 -0
- disdrodb/l0/readers/LPM/SLOVENIA/ARSO.py +197 -0
- disdrodb/l0/readers/LPM/SLOVENIA/CRNI_VRH.py +197 -0
- disdrodb/l0/readers/PARSIVEL/KIT/BURKINA_FASO.py +133 -0
- disdrodb/l0/readers/PARSIVEL/NCAR/PECAN_MOBILE.py +1 -1
- disdrodb/l0/readers/PARSIVEL/NCAR/VORTEX2_2009.py +1 -1
- disdrodb/l0/readers/PARSIVEL/SLOVENIA/UL_FGG.py +121 -0
- disdrodb/l0/readers/PARSIVEL2/FRANCE/ENPC_PARSIVEL2.py +189 -0
- disdrodb/l0/readers/PARSIVEL2/NCAR/PECAN_FP3.py +1 -1
- disdrodb/l0/readers/PARSIVEL2/NCAR/VORTEX_SE_2016_P2.py +1 -1
- disdrodb/l0/readers/PWS100/FRANCE/ENPC_PWS100.py +150 -0
- disdrodb/l0/readers/RD80/NOAA/PSL_RD80.py +274 -0
- disdrodb/l0/readers/template_reader_raw_netcdf_data.py +1 -1
- disdrodb/l0/standards.py +7 -4
- disdrodb/l0/template_tools.py +2 -2
- disdrodb/l1/encoding_attrs.py +21 -6
- disdrodb/l1/processing.py +6 -4
- disdrodb/l1/resampling.py +1 -1
- disdrodb/l1/routines.py +2 -1
- disdrodb/l2/empirical_dsd.py +100 -2
- disdrodb/l2/event.py +3 -3
- disdrodb/l2/processing.py +21 -12
- disdrodb/l2/processing_options.py +7 -7
- disdrodb/l2/routines.py +3 -3
- disdrodb/metadata/checks.py +15 -6
- disdrodb/metadata/manipulation.py +2 -2
- disdrodb/metadata/standards.py +83 -79
- disdrodb/metadata/writer.py +2 -2
- disdrodb/routines.py +246 -10
- disdrodb/scattering/routines.py +1 -1
- disdrodb/utils/dataframe.py +342 -0
- {disdrodb-0.1.0.dist-info → disdrodb-0.1.1.dist-info}/METADATA +34 -61
- {disdrodb-0.1.0.dist-info → disdrodb-0.1.1.dist-info}/RECORD +63 -47
- {disdrodb-0.1.0.dist-info → disdrodb-0.1.1.dist-info}/WHEEL +1 -1
- {disdrodb-0.1.0.dist-info → disdrodb-0.1.1.dist-info}/entry_points.txt +3 -3
- {disdrodb-0.1.0.dist-info → disdrodb-0.1.1.dist-info}/licenses/LICENSE +0 -0
- {disdrodb-0.1.0.dist-info → disdrodb-0.1.1.dist-info}/top_level.txt +0 -0
disdrodb/l0/template_tools.py
CHANGED
|
@@ -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 =
|
|
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
|
|
514
|
+
The default row index is 0.
|
|
515
515
|
|
|
516
516
|
Returns
|
|
517
517
|
-------
|
disdrodb/l1/encoding_attrs.py
CHANGED
|
@@ -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
|
-
"
|
|
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
|
-
"
|
|
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
|
-
"
|
|
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
|
-
"
|
|
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
|
-
"
|
|
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
|
-
"
|
|
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
|
-
|
|
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["
|
|
176
|
-
ds_l1["
|
|
177
|
-
|
|
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", "
|
|
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":
|
|
79
|
+
"maximum_diameter": 10,
|
|
79
80
|
"minimum_velocity": 0,
|
|
80
81
|
"maximum_velocity": 12,
|
|
81
82
|
"above_velocity_fraction": 0.5,
|
disdrodb/l2/empirical_dsd.py
CHANGED
|
@@ -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
|
|
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
|
|
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
|
|
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", "
|
|
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["
|
|
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["
|
|
143
|
+
ds = ds.isel(time=ds["N"] > 0)
|
|
144
144
|
|
|
145
145
|
# Count number of diameter bins with data
|
|
146
|
-
if "
|
|
147
|
-
|
|
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
|
-
"
|
|
178
|
-
"
|
|
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=
|
|
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
|
-
|
|
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 "
|
|
361
|
-
|
|
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["
|
|
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(
|
|
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":
|
|
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":
|
|
28
|
+
"diameter_max": 10,
|
|
29
29
|
"diameter_spacing": 0.05,
|
|
30
30
|
"gof_metrics": True,
|
|
31
|
-
"
|
|
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":
|
|
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":
|
|
124
|
+
"diameter_max": 10,
|
|
125
125
|
"diameter_spacing": 0.05,
|
|
126
126
|
"gof_metrics": True,
|
|
127
|
-
"
|
|
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["
|
|
160
|
-
# timestep_nan = ds["time"].data[np.isnan(ds["
|
|
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["
|
|
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
|
|
disdrodb/metadata/checks.py
CHANGED
|
@@ -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
|
|
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(
|
|
46
|
-
missing_keys = np.array(
|
|
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,
|
|
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
|
|
44
|
+
from disdrodb.metadata.standards import METADATA_KEYS
|
|
45
45
|
|
|
46
|
-
list_metadata_keys =
|
|
46
|
+
list_metadata_keys = METADATA_KEYS
|
|
47
47
|
metadata = {k: metadata[k] for k in list_metadata_keys}
|
|
48
48
|
return metadata
|