disdrodb 0.1.4__py3-none-any.whl → 0.2.0__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 (135) hide show
  1. disdrodb/__init__.py +1 -5
  2. disdrodb/_version.py +2 -2
  3. disdrodb/accessor/methods.py +14 -3
  4. disdrodb/api/checks.py +10 -0
  5. disdrodb/api/create_directories.py +0 -2
  6. disdrodb/api/io.py +14 -17
  7. disdrodb/api/path.py +42 -77
  8. disdrodb/api/search.py +89 -23
  9. disdrodb/cli/disdrodb_create_summary.py +11 -1
  10. disdrodb/cli/disdrodb_create_summary_station.py +10 -0
  11. disdrodb/cli/disdrodb_run_l0.py +1 -1
  12. disdrodb/cli/disdrodb_run_l0a.py +1 -1
  13. disdrodb/cli/disdrodb_run_l0b.py +1 -1
  14. disdrodb/cli/disdrodb_run_l0c.py +1 -1
  15. disdrodb/cli/disdrodb_run_l1.py +1 -1
  16. disdrodb/cli/disdrodb_run_l2e.py +1 -1
  17. disdrodb/cli/disdrodb_run_l2m.py +1 -1
  18. disdrodb/configs.py +30 -83
  19. disdrodb/constants.py +4 -3
  20. disdrodb/data_transfer/download_data.py +4 -2
  21. disdrodb/docs.py +2 -2
  22. disdrodb/etc/products/L1/1MIN.yaml +13 -0
  23. disdrodb/etc/products/L1/LPM/1MIN.yaml +13 -0
  24. disdrodb/etc/products/L1/PARSIVEL/1MIN.yaml +13 -0
  25. disdrodb/etc/products/L1/PARSIVEL2/1MIN.yaml +13 -0
  26. disdrodb/etc/products/L1/PWS100/1MIN.yaml +13 -0
  27. disdrodb/etc/products/L1/RD80/1MIN.yaml +13 -0
  28. disdrodb/etc/products/L1/SWS250/1MIN.yaml +13 -0
  29. disdrodb/etc/products/L1/global.yaml +7 -1
  30. disdrodb/etc/products/L2E/10MIN.yaml +1 -12
  31. disdrodb/etc/products/L2E/5MIN.yaml +1 -0
  32. disdrodb/etc/products/L2E/global.yaml +1 -1
  33. disdrodb/etc/products/L2M/MODELS/GAMMA_GS_ND_MAE.yaml +6 -0
  34. disdrodb/etc/products/L2M/{GAMMA_ML.yaml → MODELS/GAMMA_ML.yaml} +1 -1
  35. disdrodb/etc/products/L2M/MODELS/LOGNORMAL_GS_LOG_ND_MAE.yaml +6 -0
  36. disdrodb/etc/products/L2M/MODELS/LOGNORMAL_GS_ND_MAE.yaml +6 -0
  37. disdrodb/etc/products/L2M/MODELS/LOGNORMAL_ML.yaml +8 -0
  38. disdrodb/etc/products/L2M/MODELS/NGAMMA_GS_R_MAE.yaml +6 -0
  39. disdrodb/etc/products/L2M/global.yaml +11 -3
  40. disdrodb/l0/check_configs.py +49 -16
  41. disdrodb/l0/configs/LPM/l0a_encodings.yml +2 -2
  42. disdrodb/l0/configs/LPM/l0b_cf_attrs.yml +2 -2
  43. disdrodb/l0/configs/LPM/l0b_encodings.yml +2 -2
  44. disdrodb/l0/configs/LPM/raw_data_format.yml +2 -2
  45. disdrodb/l0/configs/PARSIVEL/l0b_encodings.yml +1 -1
  46. disdrodb/l0/configs/PWS100/l0b_encodings.yml +1 -0
  47. disdrodb/l0/configs/SWS250/bins_diameter.yml +108 -0
  48. disdrodb/l0/configs/SWS250/bins_velocity.yml +83 -0
  49. disdrodb/l0/configs/SWS250/l0a_encodings.yml +18 -0
  50. disdrodb/l0/configs/SWS250/l0b_cf_attrs.yml +72 -0
  51. disdrodb/l0/configs/SWS250/l0b_encodings.yml +155 -0
  52. disdrodb/l0/configs/SWS250/raw_data_format.yml +148 -0
  53. disdrodb/l0/l0_reader.py +2 -2
  54. disdrodb/l0/l0b_processing.py +70 -15
  55. disdrodb/l0/l0c_processing.py +7 -3
  56. disdrodb/l0/readers/LPM/ARM/ARM_LPM.py +1 -1
  57. disdrodb/l0/readers/LPM/AUSTRALIA/MELBOURNE_2007_LPM.py +2 -2
  58. disdrodb/l0/readers/LPM/BELGIUM/ULIEGE.py +256 -0
  59. disdrodb/l0/readers/LPM/BRAZIL/CHUVA_LPM.py +2 -2
  60. disdrodb/l0/readers/LPM/BRAZIL/GOAMAZON_LPM.py +2 -2
  61. disdrodb/l0/readers/LPM/GERMANY/DWD.py +491 -0
  62. disdrodb/l0/readers/LPM/ITALY/GID_LPM.py +2 -2
  63. disdrodb/l0/readers/LPM/ITALY/GID_LPM_W.py +2 -2
  64. disdrodb/l0/readers/LPM/KIT/CHWALA.py +2 -2
  65. disdrodb/l0/readers/LPM/SLOVENIA/ARSO.py +107 -12
  66. disdrodb/l0/readers/LPM/SLOVENIA/UL.py +3 -3
  67. disdrodb/l0/readers/LPM/SWITZERLAND/INNERERIZ_LPM.py +2 -2
  68. disdrodb/l0/readers/PARSIVEL/BASQUECOUNTRY/EUSKALMET_OTT.py +227 -0
  69. disdrodb/l0/readers/PARSIVEL/{GPM → NASA}/LPVEX.py +1 -1
  70. disdrodb/l0/readers/PARSIVEL/NCAR/VORTEX2_2010.py +5 -14
  71. disdrodb/l0/readers/PARSIVEL/NCAR/VORTEX2_2010_UF.py +8 -17
  72. disdrodb/l0/readers/PARSIVEL/SLOVENIA/UL.py +117 -8
  73. disdrodb/l0/readers/PARSIVEL2/BASQUECOUNTRY/EUSKALMET_OTT2.py +232 -0
  74. disdrodb/l0/readers/PARSIVEL2/BRAZIL/CHUVA_PARSIVEL2.py +10 -14
  75. disdrodb/l0/readers/PARSIVEL2/BRAZIL/GOAMAZON_PARSIVEL2.py +10 -14
  76. disdrodb/l0/readers/PARSIVEL2/DENMARK/DTU.py +8 -14
  77. disdrodb/l0/readers/PARSIVEL2/DENMARK/EROSION_raw.py +382 -0
  78. disdrodb/l0/readers/PARSIVEL2/FINLAND/FMI_PARSIVEL2.py +4 -0
  79. disdrodb/l0/readers/PARSIVEL2/FRANCE/OSUG.py +1 -1
  80. disdrodb/l0/readers/PARSIVEL2/GREECE/NOA.py +127 -0
  81. disdrodb/l0/readers/PARSIVEL2/ITALY/HYDROX.py +239 -0
  82. disdrodb/l0/readers/PARSIVEL2/NCAR/FARM_PARSIVEL2.py +5 -11
  83. disdrodb/l0/readers/PARSIVEL2/NCAR/PERILS_MIPS.py +4 -17
  84. disdrodb/l0/readers/PARSIVEL2/NCAR/RELAMPAGO_PARSIVEL2.py +5 -14
  85. disdrodb/l0/readers/PARSIVEL2/NCAR/SNOWIE_PJ.py +10 -13
  86. disdrodb/l0/readers/PARSIVEL2/NCAR/SNOWIE_SB.py +10 -13
  87. disdrodb/l0/readers/PARSIVEL2/PHILIPPINES/PAGASA.py +232 -0
  88. disdrodb/l0/readers/PARSIVEL2/SPAIN/CENER.py +6 -18
  89. disdrodb/l0/readers/PARSIVEL2/{NASA/LPVEX.py → SPAIN/GRANADA.py} +46 -35
  90. disdrodb/l0/readers/PARSIVEL2/SWEDEN/SMHI.py +189 -0
  91. disdrodb/l0/readers/PARSIVEL2/USA/{C3WE.py → CW3E.py} +10 -28
  92. disdrodb/l0/readers/PWS100/AUSTRIA/HOAL.py +321 -0
  93. disdrodb/l0/readers/SW250/BELGIUM/KMI.py +239 -0
  94. disdrodb/l1/beard_model.py +31 -129
  95. disdrodb/l1/fall_velocity.py +136 -83
  96. disdrodb/l1/filters.py +25 -28
  97. disdrodb/l1/processing.py +16 -17
  98. disdrodb/l1/resampling.py +101 -38
  99. disdrodb/l1_env/routines.py +46 -17
  100. disdrodb/l2/empirical_dsd.py +6 -0
  101. disdrodb/l2/processing.py +6 -5
  102. disdrodb/metadata/geolocation.py +0 -2
  103. disdrodb/metadata/search.py +3 -4
  104. disdrodb/psd/fitting.py +16 -13
  105. disdrodb/routines/l0.py +2 -2
  106. disdrodb/routines/l1.py +173 -60
  107. disdrodb/routines/l2.py +148 -284
  108. disdrodb/routines/options.py +345 -0
  109. disdrodb/routines/wrappers.py +14 -1
  110. disdrodb/scattering/axis_ratio.py +90 -84
  111. disdrodb/scattering/permittivity.py +6 -0
  112. disdrodb/summary/routines.py +735 -670
  113. disdrodb/utils/archiving.py +51 -44
  114. disdrodb/utils/attrs.py +3 -1
  115. disdrodb/utils/dask.py +4 -4
  116. disdrodb/utils/dict.py +33 -0
  117. disdrodb/utils/encoding.py +6 -1
  118. disdrodb/utils/routines.py +9 -8
  119. disdrodb/utils/time.py +11 -3
  120. disdrodb/viz/__init__.py +0 -13
  121. disdrodb/viz/plots.py +231 -1
  122. {disdrodb-0.1.4.dist-info → disdrodb-0.2.0.dist-info}/METADATA +2 -1
  123. {disdrodb-0.1.4.dist-info → disdrodb-0.2.0.dist-info}/RECORD +135 -103
  124. /disdrodb/etc/products/L2M/{NGAMMA_GS_LOG_ND_MAE.yaml → MODELS/NGAMMA_GS_LOG_ND_MAE.yaml} +0 -0
  125. /disdrodb/etc/products/L2M/{NGAMMA_GS_ND_MAE.yaml → MODELS/NGAMMA_GS_ND_MAE.yaml} +0 -0
  126. /disdrodb/etc/products/L2M/{NGAMMA_GS_Z_MAE.yaml → MODELS/NGAMMA_GS_Z_MAE.yaml} +0 -0
  127. /disdrodb/l0/readers/PARSIVEL/{GPM → NASA}/IFLOODS.py +0 -0
  128. /disdrodb/l0/readers/PARSIVEL/{GPM → NASA}/MC3E.py +0 -0
  129. /disdrodb/l0/readers/PARSIVEL/{GPM → NASA}/PIERS.py +0 -0
  130. /disdrodb/l0/readers/PARSIVEL2/{GPM → NASA}/GCPEX.py +0 -0
  131. /disdrodb/l0/readers/PARSIVEL2/{GPM → NASA}/NSSTC.py +0 -0
  132. {disdrodb-0.1.4.dist-info → disdrodb-0.2.0.dist-info}/WHEEL +0 -0
  133. {disdrodb-0.1.4.dist-info → disdrodb-0.2.0.dist-info}/entry_points.txt +0 -0
  134. {disdrodb-0.1.4.dist-info → disdrodb-0.2.0.dist-info}/licenses/LICENSE +0 -0
  135. {disdrodb-0.1.4.dist-info → disdrodb-0.2.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,345 @@
1
+ # -----------------------------------------------------------------------------.
2
+ # Copyright (c) 2021-2023 DISDRODB developers
3
+ #
4
+ # This program is free software: you can redistribute it and/or modify
5
+ # it under the terms of the GNU General Public License as published by
6
+ # the Free Software Foundation, either version 3 of the License, or
7
+ # (at your option) any later version.
8
+ #
9
+ # This program is distributed in the hope that it will be useful,
10
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
11
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
+ # GNU General Public License for more details.
13
+ #
14
+ # You should have received a copy of the GNU General Public License
15
+ # along with this program. If not, see <http://www.gnu.org/licenses/>.
16
+ # -----------------------------------------------------------------------------.
17
+ """Implements ProcessingOption class for DISDRODB routines."""
18
+ import json
19
+ import os
20
+
21
+ import disdrodb
22
+ from disdrodb.api.checks import check_product, check_sensor_name, check_temporal_resolution
23
+ from disdrodb.api.info import group_filepaths
24
+ from disdrodb.configs import get_products_configs_dir
25
+ from disdrodb.utils.archiving import define_temporal_partitions, group_files_by_temporal_partitions
26
+ from disdrodb.utils.list import flatten_list
27
+ from disdrodb.utils.routines import is_possible_product
28
+ from disdrodb.utils.time import ensure_timedelta_seconds, get_sampling_information
29
+ from disdrodb.utils.yaml import read_yaml
30
+
31
+ # TODO: Test ensure recursive update for product_options key, do not replace just "product_options" dict !
32
+ # get_product_options(product="L2E", temporal_resolution="10MIN")
33
+ # get_product_options(product="L2M", temporal_resolution="10MIN")
34
+ # get_product_options(product="L1")
35
+ # get_product_options(product="L1", temporal_resolution="1MIN")
36
+ # get_product_options(product="L1", temporal_resolution="1MIN", sensor_name="PARSIVEL")
37
+
38
+ # TODO: test return list
39
+ # get_product_temporal_resolutions(product="L1")
40
+ # get_product_temporal_resolutions(product="L2E")
41
+ # get_product_temporal_resolutions(product="L2M")
42
+
43
+
44
+ def get_product_options(product, temporal_resolution=None, sensor_name=None):
45
+ """Return DISDRODB product options.
46
+
47
+ If temporal resolution is not provided, it returns the global product option.
48
+ If temporal resolution is provided, it provides the custom product options, if specified.
49
+ If product="L1" and sensor_name is specified, it customize product options also by sensor.
50
+ """
51
+ # Retrieve products configuration directory
52
+ products_configs_dir = get_products_configs_dir()
53
+
54
+ # Validate DISDRODB products configuration
55
+ validate_product_configuration(products_configs_dir)
56
+
57
+ # Check product
58
+ check_product(product)
59
+
60
+ # Retrieve global product options (when no temporal resolution !)
61
+ global_options = read_yaml(os.path.join(products_configs_dir, product, "global.yaml"))
62
+ if temporal_resolution is None:
63
+ global_options = check_availability_radar_simulations(global_options)
64
+ return global_options
65
+
66
+ # Check temporal resolution
67
+ check_temporal_resolution(temporal_resolution)
68
+
69
+ # If temporal resolutions are specified, drop 'temporal_resolutions' key
70
+ global_options.pop("temporal_resolutions", None)
71
+
72
+ # Read custom options for specific temporal resolution
73
+ custom_options_path = os.path.join(products_configs_dir, product, f"{temporal_resolution}.yaml")
74
+ if not os.path.exists(custom_options_path):
75
+ return global_options
76
+ custom_options = read_yaml(custom_options_path)
77
+
78
+ # Define product options
79
+ options = global_options.copy()
80
+ if "product_options" in custom_options:
81
+ options["product_options"].update(custom_options.pop("product_options"))
82
+ options.update(custom_options)
83
+
84
+ # Check availability of radar simulations
85
+ options = check_availability_radar_simulations(options)
86
+
87
+ # Customize product options by sensor if L1 product
88
+ if product == "L1" and sensor_name is not None:
89
+ check_sensor_name(sensor_name)
90
+ custom_options_path = os.path.join(products_configs_dir, product, sensor_name, f"{temporal_resolution}.yaml")
91
+ if not os.path.exists(custom_options_path):
92
+ return options
93
+ custom_options = read_yaml(custom_options_path)
94
+ if "product_options" in custom_options:
95
+ options["product_options"].update(custom_options.pop("product_options"))
96
+ return options
97
+
98
+
99
+ def get_product_temporal_resolutions(product):
100
+ """Return DISDRODB products temporal resolutions."""
101
+ # Check only L2E and L2M
102
+ return get_product_options(product)["temporal_resolutions"]
103
+
104
+
105
+ def get_model_options(product, model_name):
106
+ """Return DISDRODB L2M product model options."""
107
+ # Retrieve products configuration directory
108
+ products_configs_dir = get_products_configs_dir()
109
+ model_options_path = os.path.join(products_configs_dir, product, "MODELS", f"{model_name}.yaml")
110
+ model_options = read_yaml(model_options_path)
111
+ return model_options
112
+
113
+
114
+ def check_availability_radar_simulations(options):
115
+ """Check radar simulations are possible for L2E and L2M products."""
116
+ if "radar_enabled" in options and not disdrodb.is_pytmatrix_available():
117
+ options["radar_enabled"] = False
118
+ return options
119
+
120
+
121
+ def validate_product_configuration(products_configs_dir):
122
+ """Validate the DISDRODB products configuration files."""
123
+ # TODO: Implement validation of DISDRODB products configuration files with pydantic
124
+ # TODO: Raise warning if L1 temporal resolutions does not includes all temporal resolutions of L2 products.
125
+ # TODO: Raise warning if L2E temporal resolutions does not includes all temporal resolutions of L2M products.
126
+ # if stategy_event, check neighbor_time_interval >= sample_interval !
127
+ # if temporal_resolution_to_seconds(neighbor_time_interval) < temporal_resolution_to_seconds(sample_interval):
128
+ # msg = "'neighbor_time_interval' must be at least equal to the dataset sample interval ({sample_interval})"
129
+ # raise ValueError(msg)
130
+
131
+ pass
132
+
133
+
134
+ def _define_blocks_offsets(sample_interval, temporal_resolution):
135
+ """Define blocks offset for resampling logic."""
136
+ # Retrieve accumulation_interval and rolling option
137
+ accumulation_interval, rolling = get_sampling_information(temporal_resolution)
138
+
139
+ # Ensure sample_interval and accumulation_interval is numpy.timedelta64
140
+ accumulation_interval = ensure_timedelta_seconds(accumulation_interval)
141
+ sample_interval = ensure_timedelta_seconds(sample_interval)
142
+
143
+ # Define offset to apply to time partitions blocks
144
+ block_starts_offset = 0
145
+ block_ends_offset = 0
146
+
147
+ # If rolling, need to search also in next time block ...
148
+ if rolling and sample_interval != accumulation_interval:
149
+ block_ends_offset = accumulation_interval - sample_interval
150
+ return block_starts_offset, block_ends_offset
151
+
152
+
153
+ class L1ProcessingOptions:
154
+ """Define L1 product processing options."""
155
+
156
+ def __init__(self, filepaths, parallel, sensor_name, temporal_resolutions=None):
157
+ """Define DISDRODB L1 product processing options."""
158
+ product = "L1"
159
+
160
+ # ---------------------------------------------------------------------.
161
+ # Define temporal resolutions for which to retrieve processing options
162
+ if temporal_resolutions is None:
163
+ temporal_resolutions = get_product_temporal_resolutions(product)
164
+ elif isinstance(temporal_resolutions, str):
165
+ temporal_resolutions = [temporal_resolutions]
166
+ _ = [check_temporal_resolution(temporal_resolution) for temporal_resolution in temporal_resolutions]
167
+
168
+ # ---------------------------------------------------------------------.
169
+ # Get product options at various temporal resolutions
170
+ src_dict_product_options = {
171
+ temporal_resolution: get_product_options(
172
+ product=product,
173
+ temporal_resolution=temporal_resolution,
174
+ sensor_name=sensor_name,
175
+ )
176
+ for temporal_resolution in temporal_resolutions
177
+ }
178
+
179
+ # ---------------------------------------------------------------------.
180
+ # Group filepaths by source sample intervals
181
+ # - Typically the sample interval is fixed and is just one
182
+ # - Some stations might change the sample interval along the years
183
+ # - For each sample interval, separated processing must take place
184
+ dict_filepaths = group_filepaths(filepaths, groups="sample_interval")
185
+
186
+ # ---------------------------------------------------------------------.
187
+ # Retrieve processing information for each temporal resolution
188
+ dict_product_options = {}
189
+ dict_folder_partitioning = {}
190
+ dict_files_partitions = {}
191
+ _cache_dict_temporal_partitions: dict[str, dict] = {}
192
+ # temporal_resolution = temporal_resolutions[0]
193
+ for temporal_resolution in temporal_resolutions:
194
+
195
+ # -------------------------------------------------------------------------.
196
+ # Retrieve product options
197
+ product_options = src_dict_product_options[temporal_resolution].copy()
198
+
199
+ # Extract processing options
200
+ archive_options = product_options.pop("archive_options")
201
+
202
+ dict_product_options[temporal_resolution] = product_options
203
+ # -------------------------------------------------------------------------.
204
+ # Define folder partitioning
205
+ if "folder_partitioning" not in archive_options:
206
+ dict_folder_partitioning[temporal_resolution] = disdrodb.config.get("folder_partitioning")
207
+ else:
208
+ dict_folder_partitioning[temporal_resolution] = archive_options.pop("folder_partitioning")
209
+
210
+ # -------------------------------------------------------------------------.
211
+ # Define list of temporal partitions
212
+ # - [{start_time: np.datetime64, end_time: np.datetime64}, ....]
213
+ # - Either strategy: "event" or "time_block" or save_by_time_block"
214
+ # - "event" requires loading data into memory to identify events
215
+ # --> Does some data filtering on what to process !
216
+ # - "time_block" does not require loading data into memory
217
+ # --> Does not do data filtering on what to process !
218
+ # --> Here we cache dict_temporal_partitions so that we don't need to recompute
219
+ # stuffs if processing options are the same
220
+ # --> Using time_block with e.g. freq="day" we get start/end in the format of 00:00:00-23:59:59
221
+ key = json.dumps(archive_options, sort_keys=True)
222
+ if key not in _cache_dict_temporal_partitions:
223
+ _cache_dict_temporal_partitions[key] = {
224
+ sample_interval: define_temporal_partitions(filepaths, parallel=parallel, **archive_options)
225
+ for sample_interval, filepaths in dict_filepaths.items()
226
+ }
227
+ dict_temporal_partitions = _cache_dict_temporal_partitions[key].copy() # To avoid in-place replacement
228
+
229
+ # ------------------------------------------------------------------.
230
+ # Group filepaths by temporal partitions
231
+ # - This is done separately for each possible source sample interval
232
+ # - It groups filepaths by start_time and end_time provided by temporal_partitions
233
+ # --> Output: [{'start_time': ..., 'end_time': ..., filepaths: [...]}, ...]
234
+ # - In L0C we ensure that the time reported correspond to the start of the measurement interval.
235
+ # - When aggregating/resampling/accumulating data, we need to load also some data after the
236
+ # actual end_time of the time partition to ensure that
237
+ # the resampled dataset contains the timesteps of the partition end time.
238
+ # --> Use of block_starts_offset and block_ends_offset in group_files_by_temporal_partitions
239
+ # - ATTENTION: group_files_by_temporal_partitions returns
240
+ # start_time and end_time as datetime.datetime64objects !
241
+ # - ATTENTION: Files within each files_partitions block have the same sample_interval !
242
+
243
+ # sample_interval = 30
244
+ # temporal_partitions = dict_temporal_partitions[sample_interval]
245
+
246
+ files_partitions = []
247
+ for sample_interval, temporal_partitions in dict_temporal_partitions.items():
248
+ if is_possible_product(
249
+ temporal_resolution=temporal_resolution,
250
+ sample_interval=sample_interval,
251
+ ):
252
+
253
+ block_starts_offset, block_ends_offset = _define_blocks_offsets(
254
+ sample_interval=sample_interval,
255
+ temporal_resolution=temporal_resolution,
256
+ )
257
+
258
+ files_partitions.append(
259
+ group_files_by_temporal_partitions(
260
+ temporal_partitions=temporal_partitions,
261
+ filepaths=dict_filepaths[sample_interval],
262
+ block_starts_offset=block_starts_offset,
263
+ block_ends_offset=block_ends_offset,
264
+ ),
265
+ )
266
+ files_partitions = flatten_list(files_partitions)
267
+ dict_files_partitions[temporal_resolution] = files_partitions
268
+
269
+ # ------------------------------------------------------------------.
270
+ # Keep only temporal_resolutions for which products can be defined
271
+ # - Remove e.g when not compatible accumulation_interval with source sample_interval
272
+ temporal_resolutions = [
273
+ temporal_resolution
274
+ for temporal_resolution in temporal_resolutions
275
+ if len(dict_files_partitions[temporal_resolution]) > 0
276
+ ]
277
+ # ------------------------------------------------------------------.
278
+ # Add attributes
279
+ self.temporal_resolutions = temporal_resolutions
280
+ self.dict_files_partitions = dict_files_partitions
281
+ self.dict_product_options = dict_product_options
282
+ self.dict_folder_partitioning = dict_folder_partitioning
283
+
284
+ def group_files_by_temporal_partitions(self, temporal_resolution):
285
+ """Return files partitions dictionary for a specific L2E product."""
286
+ return self.dict_files_partitions[temporal_resolution]
287
+
288
+ def get_product_options(self, temporal_resolution):
289
+ """Return product options dictionary for a specific L2E product."""
290
+ return self.dict_product_options[temporal_resolution]
291
+
292
+ def get_folder_partitioning(self, temporal_resolution):
293
+ """Return the folder partitioning for a specific L2E product."""
294
+ # to be used for logs and files !
295
+ return self.dict_folder_partitioning[temporal_resolution]
296
+
297
+
298
+ class L2ProcessingOptions:
299
+ """Define L2 products processing options."""
300
+
301
+ def __init__(self, product, filepaths, parallel, temporal_resolution):
302
+ """Define DISDRODB L2 products processing options."""
303
+ import disdrodb
304
+
305
+ # Check temporal resolution
306
+ check_temporal_resolution(temporal_resolution)
307
+
308
+ # Get product options
309
+ product_options = get_product_options(product, temporal_resolution=temporal_resolution)
310
+
311
+ # Extract processing options
312
+ archive_options = product_options.pop("archive_options")
313
+
314
+ # Define folder partitioning
315
+ if "folder_partitioning" not in archive_options:
316
+ folder_partitioning = disdrodb.config.get("folder_partitioning")
317
+ else:
318
+ folder_partitioning = archive_options.pop("folder_partitioning")
319
+
320
+ # Define files temporal partitions
321
+ # - [{start_time: np.datetime64, end_time: np.datetime64}, ....]
322
+ # - Either strategy: "event" or "time_block"
323
+ # - "strategy=event" requires loading data into memory to identify events
324
+ # --> Does some data filtering on what to process !
325
+ # - "strategy=time_block" does not require loading data into memory
326
+ # --> Does not do data filtering on what to process !
327
+ temporal_partitions = define_temporal_partitions(filepaths, parallel=parallel, **archive_options)
328
+
329
+ # ------------------------------------------------------------------.
330
+ # Group filepaths by temporal partitions
331
+ # - It groups filepaths by start_time and end_time provided by temporal_partitions
332
+ # - ATTENTION: group_files_by_temporal_partitions returns
333
+ # start_time and end_time as datetime.datetime64 objects !
334
+ files_partitions = group_files_by_temporal_partitions(
335
+ temporal_partitions=temporal_partitions,
336
+ filepaths=filepaths,
337
+ )
338
+ files_partitions = flatten_list(files_partitions)
339
+
340
+ # ------------------------------------------------------------------.
341
+ # Add attributes
342
+ # self.temporal_partitions = temporal_partitions
343
+ self.folder_partitioning = folder_partitioning
344
+ self.files_partitions = files_partitions
345
+ self.product_options = product_options
@@ -643,6 +643,7 @@ def create_summary_station(
643
643
  campaign_name,
644
644
  station_name,
645
645
  parallel=False,
646
+ temporal_resolution="1MIN",
646
647
  data_archive_dir=None,
647
648
  ):
648
649
  """Create summary figures and tables for a DISDRODB station."""
@@ -658,6 +659,8 @@ def create_summary_station(
658
659
  str(data_archive_dir),
659
660
  "--parallel",
660
661
  str(parallel),
662
+ "--temporal_resolution",
663
+ str(temporal_resolution),
661
664
  ],
662
665
  )
663
666
  # Execute command
@@ -1440,6 +1443,7 @@ def create_summary(
1440
1443
  campaign_names=None,
1441
1444
  station_names=None,
1442
1445
  parallel=False,
1446
+ temporal_resolution="1MIN",
1443
1447
  data_archive_dir=None,
1444
1448
  metadata_archive_dir=None,
1445
1449
  ):
@@ -1464,6 +1468,14 @@ def create_summary(
1464
1468
  The directory path must end with ``<...>/DISDRODB``.
1465
1469
  If ``None``, it uses the ``data_archive_dir`` path specified
1466
1470
  in the DISDRODB active configuration.
1471
+ metadata_archive_dir
1472
+ The directory path where the DISDRODB Metadata Archive is located.
1473
+ The directory path must end with ``<...>/DISDRODB-METADATA/DISDRODB``.
1474
+ If ``None``, it uses the ``metadata_archive_dir`` path specified
1475
+ in the DISDRODB active configuration.
1476
+ temporal_resolution : str
1477
+ Temporal resolution of the summary.
1478
+ The default value is ``1MIN``.
1467
1479
  """
1468
1480
  # Get list of available stations
1469
1481
  list_info = available_stations(
@@ -1476,7 +1488,7 @@ def create_summary(
1476
1488
  station_names=station_names,
1477
1489
  # Search options
1478
1490
  product="L2E",
1479
- product_kwargs={"rolling": False, "sample_interval": 60},
1491
+ temporal_resolution=temporal_resolution,
1480
1492
  raise_error_if_empty=True,
1481
1493
  )
1482
1494
 
@@ -1493,6 +1505,7 @@ def create_summary(
1493
1505
  station_name=station_name,
1494
1506
  # Processing option
1495
1507
  parallel=parallel,
1508
+ temporal_resolution=temporal_resolution,
1496
1509
  )
1497
1510
  print("Creation of station summaries has terminated.")
1498
1511
 
@@ -20,90 +20,6 @@ import numpy as np
20
20
  import xarray as xr
21
21
 
22
22
 
23
- def available_axis_ratio_models():
24
- """Return a list of the available drop axis ratio models."""
25
- return list(AXIS_RATIO_MODELS)
26
-
27
-
28
- def get_axis_ratio_model(model):
29
- """Return the specified drop axis ratio model.
30
-
31
- Parameters
32
- ----------
33
- model : str
34
- The model to use for calculating the axis ratio. Available models are:
35
- 'Thurai2005', 'Thurai2007', 'Battaglia2010', 'Brandes2002',
36
- 'Pruppacher1970', 'Beard1987', 'Andsager1999'.
37
-
38
- Returns
39
- -------
40
- callable
41
- A function which compute the vertical-to-horizontal axis ratio given a
42
- particle diameter in mm.
43
-
44
- Notes
45
- -----
46
- This function serves as a wrapper to various axis ratio models for raindrops.
47
- It returns the appropriate model based on the `model` parameter.
48
-
49
- Please note that the axis ratio function to be provided to pyTmatrix expects to
50
- return a horizontal-to-vertical axis ratio !
51
-
52
- """
53
- model = check_axis_ratio_model(model)
54
- return AXIS_RATIO_MODELS[model]
55
-
56
-
57
- def check_axis_ratio_model(model):
58
- """Check validity of the specified drop axis ratio model."""
59
- available_models = available_axis_ratio_models()
60
- if model not in available_models:
61
- raise ValueError(f"{model} is an invalid axis-ratio model. Valid models: {available_models}.")
62
- return model
63
-
64
-
65
- def get_axis_ratio(diameter, model):
66
- """
67
- Compute the axis ratio of raindrops using the specified model.
68
-
69
- Parameters
70
- ----------
71
- diameter : array-like
72
- Raindrops diameter in mm.
73
- model : str
74
- The axis ratio model to use for calculating the axis ratio. Available models are:
75
- 'Thurai2005', 'Thurai2007', 'Battaglia2010', 'Brandes2002',
76
- 'Pruppacher1970', 'Beard1987', 'Andsager1999'.
77
-
78
- Returns
79
- -------
80
- axis_ratio : array-like
81
- The vertical-to-horizontal drop axis ratio corresponding to the input diameters.
82
- Values of 1 indicate spherical particles, while values <1 indicate oblate particles.
83
- Values >1 means prolate particles.
84
-
85
- Notes
86
- -----
87
- This function serves as a wrapper to various axis ratio models for raindrops.
88
- It selects and applies the appropriate model based on the `model` parameter.
89
-
90
- Examples
91
- --------
92
- >>> diameter = np.array([0.5, 1.0, 2.0, 3.0])
93
- >>> axis_ratio = get_axis_ratio(diameter, model="Brandes2002")
94
-
95
- """
96
- # Retrieve axis ratio function
97
- axis_ratio_func = get_axis_ratio_model(model)
98
-
99
- # Retrieve axis ratio
100
- axis_ratio = axis_ratio_func(diameter)
101
-
102
- # Clip values between 0 and 1
103
- axis_ratio = np.clip(axis_ratio, 0, 1)
104
- return axis_ratio
105
-
106
-
107
23
  def get_axis_ratio_andsager_1999(diameter):
108
24
  """
109
25
  Compute the axis ratio of raindrops using the Andsager et al. (1999) model.
@@ -366,3 +282,93 @@ AXIS_RATIO_MODELS = {
366
282
  "Beard1987": get_axis_ratio_beard_1987,
367
283
  "Andsager1999": get_axis_ratio_andsager_1999,
368
284
  }
285
+
286
+
287
+ def available_axis_ratio_models():
288
+ """Return a list of the available drop axis ratio models."""
289
+ return list(AXIS_RATIO_MODELS)
290
+
291
+
292
+ def check_axis_ratio_model(model):
293
+ """Check validity of the specified drop axis ratio model."""
294
+ available_models = available_axis_ratio_models()
295
+ if model not in available_models:
296
+ raise ValueError(f"{model} is an invalid axis-ratio model. Valid models: {available_models}.")
297
+ return model
298
+
299
+
300
+ def get_axis_ratio_model(model):
301
+ """Return the specified drop axis ratio model.
302
+
303
+ Parameters
304
+ ----------
305
+ model : str
306
+ The model to use for calculating the axis ratio. Available models are:
307
+ 'Thurai2005', 'Thurai2007', 'Battaglia2010', 'Brandes2002',
308
+ 'Pruppacher1970', 'Beard1987', 'Andsager1999'.
309
+
310
+ Returns
311
+ -------
312
+ callable
313
+ A function which compute the vertical-to-horizontal axis ratio given a
314
+ particle diameter in mm.
315
+
316
+ Notes
317
+ -----
318
+ This function serves as a wrapper to various axis ratio models for raindrops.
319
+ It returns the appropriate model based on the `model` parameter.
320
+
321
+ Please note that the axis ratio function to be provided to pyTmatrix expects to
322
+ return a horizontal-to-vertical axis ratio !
323
+
324
+ """
325
+ model = check_axis_ratio_model(model)
326
+ return AXIS_RATIO_MODELS[model]
327
+
328
+
329
+ def get_axis_ratio(diameter, model):
330
+ """
331
+ Compute the axis ratio of raindrops using the specified model.
332
+
333
+ Parameters
334
+ ----------
335
+ diameter : array-like
336
+ Raindrops diameter in mm.
337
+ model : str
338
+ The axis ratio model to use for calculating the axis ratio. Available models are:
339
+ 'Thurai2005', 'Thurai2007', 'Battaglia2010', 'Brandes2002',
340
+ 'Pruppacher1970', 'Beard1987', 'Andsager1999'.
341
+
342
+ Returns
343
+ -------
344
+ axis_ratio : array-like
345
+ The vertical-to-horizontal drop axis ratio corresponding to the input diameters.
346
+ Values of 1 indicate spherical particles, while values <1 indicate oblate particles.
347
+ Values >1 means prolate particles.
348
+
349
+ Notes
350
+ -----
351
+ This function serves as a wrapper to various axis ratio models for raindrops.
352
+ It selects and applies the appropriate model based on the `model` parameter.
353
+
354
+ Examples
355
+ --------
356
+ >>> diameter = np.array([0.5, 1.0, 2.0, 3.0])
357
+ >>> axis_ratio = get_axis_ratio(diameter, model="Brandes2002")
358
+
359
+ """
360
+ # Retrieve axis ratio function
361
+ axis_ratio_func = get_axis_ratio_model(model)
362
+
363
+ # Retrieve axis ratio
364
+ axis_ratio = axis_ratio_func(diameter)
365
+
366
+ # Clip values between 0 and 1
367
+ axis_ratio = np.clip(axis_ratio, 0, 1)
368
+
369
+ # Add attributes
370
+ if isinstance(axis_ratio, xr.DataArray):
371
+ axis_ratio.name = "axis_ratio"
372
+ axis_ratio.attrs["units"] = ""
373
+ axis_ratio.attrs["model"] = model
374
+ return axis_ratio
@@ -147,6 +147,12 @@ def get_refractive_index(temperature, frequency, permittivity_model):
147
147
 
148
148
  # Retrieve refractive_index
149
149
  refractive_index = func(temperature=temperature, frequency=frequency)
150
+
151
+ # Add attributes
152
+ if isinstance(refractive_index, xr.DataArray):
153
+ refractive_index.name = "refractive_index"
154
+ refractive_index.attrs["units"] = ""
155
+ refractive_index.attrs["model"] = permittivity_model
150
156
  return refractive_index
151
157
 
152
158