disdrodb 0.1.2__py3-none-any.whl → 0.1.3__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 (123) hide show
  1. disdrodb/__init__.py +64 -34
  2. disdrodb/_config.py +5 -4
  3. disdrodb/_version.py +16 -3
  4. disdrodb/accessor/__init__.py +20 -0
  5. disdrodb/accessor/methods.py +125 -0
  6. disdrodb/api/checks.py +139 -9
  7. disdrodb/api/configs.py +4 -2
  8. disdrodb/api/info.py +10 -10
  9. disdrodb/api/io.py +237 -18
  10. disdrodb/api/path.py +81 -75
  11. disdrodb/api/search.py +6 -6
  12. disdrodb/cli/disdrodb_create_summary_station.py +91 -0
  13. disdrodb/cli/disdrodb_run_l0.py +1 -1
  14. disdrodb/cli/disdrodb_run_l0_station.py +1 -1
  15. disdrodb/cli/disdrodb_run_l0b.py +1 -1
  16. disdrodb/cli/disdrodb_run_l0b_station.py +1 -1
  17. disdrodb/cli/disdrodb_run_l0c.py +1 -1
  18. disdrodb/cli/disdrodb_run_l0c_station.py +1 -1
  19. disdrodb/cli/disdrodb_run_l2e_station.py +1 -1
  20. disdrodb/configs.py +149 -4
  21. disdrodb/constants.py +61 -0
  22. disdrodb/data_transfer/download_data.py +5 -5
  23. disdrodb/etc/configs/attributes.yaml +339 -0
  24. disdrodb/etc/configs/encodings.yaml +473 -0
  25. disdrodb/etc/products/L1/global.yaml +13 -0
  26. disdrodb/etc/products/L2E/10MIN.yaml +12 -0
  27. disdrodb/etc/products/L2E/1MIN.yaml +1 -0
  28. disdrodb/etc/products/L2E/global.yaml +22 -0
  29. disdrodb/etc/products/L2M/10MIN.yaml +12 -0
  30. disdrodb/etc/products/L2M/GAMMA_ML.yaml +8 -0
  31. disdrodb/etc/products/L2M/NGAMMA_GS_LOG_ND_MAE.yaml +6 -0
  32. disdrodb/etc/products/L2M/NGAMMA_GS_ND_MAE.yaml +6 -0
  33. disdrodb/etc/products/L2M/NGAMMA_GS_Z_MAE.yaml +6 -0
  34. disdrodb/etc/products/L2M/global.yaml +26 -0
  35. disdrodb/l0/__init__.py +13 -0
  36. disdrodb/l0/configs/LPM/l0b_cf_attrs.yml +4 -4
  37. disdrodb/l0/configs/PARSIVEL/l0b_cf_attrs.yml +1 -1
  38. disdrodb/l0/configs/PARSIVEL/l0b_encodings.yml +3 -3
  39. disdrodb/l0/configs/PARSIVEL/raw_data_format.yml +1 -1
  40. disdrodb/l0/configs/PARSIVEL2/l0b_cf_attrs.yml +5 -5
  41. disdrodb/l0/configs/PARSIVEL2/l0b_encodings.yml +3 -3
  42. disdrodb/l0/configs/PARSIVEL2/raw_data_format.yml +1 -1
  43. disdrodb/l0/configs/PWS100/l0b_cf_attrs.yml +4 -4
  44. disdrodb/l0/configs/PWS100/raw_data_format.yml +1 -1
  45. disdrodb/l0/l0a_processing.py +30 -30
  46. disdrodb/l0/l0b_nc_processing.py +108 -2
  47. disdrodb/l0/l0b_processing.py +4 -4
  48. disdrodb/l0/l0c_processing.py +5 -13
  49. disdrodb/l0/readers/LPM/NETHERLANDS/DELFT_LPM_NC.py +66 -0
  50. disdrodb/l0/readers/LPM/SLOVENIA/{CRNI_VRH.py → UL.py} +3 -0
  51. disdrodb/l0/readers/LPM/SWITZERLAND/INNERERIZ_LPM.py +195 -0
  52. disdrodb/l0/readers/PARSIVEL/GPM/PIERS.py +0 -2
  53. disdrodb/l0/readers/PARSIVEL/JAPAN/JMA.py +4 -1
  54. disdrodb/l0/readers/PARSIVEL/NCAR/PECAN_MOBILE.py +1 -1
  55. disdrodb/l0/readers/PARSIVEL/NCAR/VORTEX2_2009.py +1 -1
  56. disdrodb/l0/readers/PARSIVEL2/BELGIUM/ILVO.py +168 -0
  57. disdrodb/l0/readers/PARSIVEL2/DENMARK/DTU.py +165 -0
  58. disdrodb/l0/readers/PARSIVEL2/FINLAND/FMI_PARSIVEL2.py +69 -0
  59. disdrodb/l0/readers/PARSIVEL2/FRANCE/ENPC_PARSIVEL2.py +255 -134
  60. disdrodb/l0/readers/PARSIVEL2/FRANCE/OSUG.py +525 -0
  61. disdrodb/l0/readers/PARSIVEL2/FRANCE/SIRTA_PARSIVEL2.py +1 -1
  62. disdrodb/l0/readers/PARSIVEL2/GPM/GCPEX.py +9 -7
  63. disdrodb/l0/readers/PARSIVEL2/KIT/BURKINA_FASO.py +1 -1
  64. disdrodb/l0/readers/PARSIVEL2/KIT/TEAMX.py +123 -0
  65. disdrodb/l0/readers/PARSIVEL2/NASA/APU.py +120 -0
  66. disdrodb/l0/readers/PARSIVEL2/NCAR/FARM_PARSIVEL2.py +1 -0
  67. disdrodb/l0/readers/PARSIVEL2/NCAR/PECAN_FP3.py +1 -1
  68. disdrodb/l0/readers/PARSIVEL2/NCAR/PERILS_MIPS.py +126 -0
  69. disdrodb/l0/readers/PARSIVEL2/NCAR/PERILS_PIPS.py +165 -0
  70. disdrodb/l0/readers/PARSIVEL2/NCAR/VORTEX_SE_2016_P2.py +1 -1
  71. disdrodb/l0/readers/PARSIVEL2/NCAR/VORTEX_SE_2016_PIPS.py +20 -12
  72. disdrodb/l0/readers/PARSIVEL2/NETHERLANDS/DELFT_NC.py +2 -0
  73. disdrodb/l0/readers/PARSIVEL2/SPAIN/CENER.py +144 -0
  74. disdrodb/l0/readers/PARSIVEL2/SPAIN/CR1000DL.py +201 -0
  75. disdrodb/l0/readers/PARSIVEL2/SPAIN/LIAISE.py +137 -0
  76. disdrodb/l0/readers/PARSIVEL2/{NETHERLANDS/DELFT.py → USA/C3WE.py} +65 -85
  77. disdrodb/l0/readers/PWS100/FRANCE/ENPC_PWS100.py +105 -99
  78. disdrodb/l0/readers/PWS100/FRANCE/ENPC_PWS100_SIRTA.py +151 -0
  79. disdrodb/l0/routines.py +105 -14
  80. disdrodb/l1/__init__.py +5 -0
  81. disdrodb/l1/filters.py +34 -20
  82. disdrodb/l1/processing.py +45 -44
  83. disdrodb/l1/resampling.py +77 -66
  84. disdrodb/l1/routines.py +35 -43
  85. disdrodb/l1_env/routines.py +18 -3
  86. disdrodb/l2/__init__.py +7 -0
  87. disdrodb/l2/empirical_dsd.py +58 -10
  88. disdrodb/l2/event.py +27 -120
  89. disdrodb/l2/processing.py +267 -116
  90. disdrodb/l2/routines.py +618 -254
  91. disdrodb/metadata/standards.py +3 -1
  92. disdrodb/psd/fitting.py +463 -144
  93. disdrodb/psd/models.py +8 -5
  94. disdrodb/routines.py +3 -3
  95. disdrodb/scattering/__init__.py +16 -4
  96. disdrodb/scattering/axis_ratio.py +56 -36
  97. disdrodb/scattering/permittivity.py +486 -0
  98. disdrodb/scattering/routines.py +701 -159
  99. disdrodb/summary/__init__.py +17 -0
  100. disdrodb/summary/routines.py +4120 -0
  101. disdrodb/utils/attrs.py +68 -125
  102. disdrodb/utils/compression.py +30 -1
  103. disdrodb/utils/dask.py +59 -8
  104. disdrodb/utils/dataframe.py +61 -7
  105. disdrodb/utils/directories.py +35 -15
  106. disdrodb/utils/encoding.py +33 -19
  107. disdrodb/utils/logger.py +13 -6
  108. disdrodb/utils/manipulations.py +71 -0
  109. disdrodb/utils/subsetting.py +214 -0
  110. disdrodb/utils/time.py +165 -19
  111. disdrodb/utils/writer.py +20 -7
  112. disdrodb/utils/xarray.py +2 -4
  113. disdrodb/viz/__init__.py +13 -0
  114. disdrodb/viz/plots.py +327 -0
  115. {disdrodb-0.1.2.dist-info → disdrodb-0.1.3.dist-info}/METADATA +3 -2
  116. {disdrodb-0.1.2.dist-info → disdrodb-0.1.3.dist-info}/RECORD +121 -88
  117. {disdrodb-0.1.2.dist-info → disdrodb-0.1.3.dist-info}/entry_points.txt +1 -0
  118. disdrodb/l1/encoding_attrs.py +0 -642
  119. disdrodb/l2/processing_options.py +0 -213
  120. /disdrodb/l0/readers/PARSIVEL/SLOVENIA/{UL_FGG.py → UL.py} +0 -0
  121. {disdrodb-0.1.2.dist-info → disdrodb-0.1.3.dist-info}/WHEEL +0 -0
  122. {disdrodb-0.1.2.dist-info → disdrodb-0.1.3.dist-info}/licenses/LICENSE +0 -0
  123. {disdrodb-0.1.2.dist-info → disdrodb-0.1.3.dist-info}/top_level.txt +0 -0
disdrodb/l0/routines.py CHANGED
@@ -21,14 +21,13 @@
21
21
  import datetime
22
22
  import logging
23
23
  import os
24
+ import shutil
24
25
  import time
25
26
  from typing import Optional
26
27
 
27
28
  import dask
28
29
 
29
- from disdrodb.api.checks import check_sensor_name
30
-
31
- # Directory
30
+ from disdrodb.api.checks import check_sensor_name, check_station_inputs
32
31
  from disdrodb.api.create_directories import (
33
32
  create_l0_directory_structure,
34
33
  create_logs_directory,
@@ -41,6 +40,7 @@ from disdrodb.api.path import (
41
40
  define_l0b_filename,
42
41
  define_l0c_filename,
43
42
  define_metadata_filepath,
43
+ define_partitioning_tree,
44
44
  )
45
45
  from disdrodb.api.search import get_required_product
46
46
  from disdrodb.configs import get_data_archive_dir, get_folder_partitioning, get_metadata_archive_dir
@@ -53,7 +53,7 @@ from disdrodb.l0.l0a_processing import (
53
53
  )
54
54
  from disdrodb.l0.l0b_nc_processing import sanitize_ds
55
55
  from disdrodb.l0.l0b_processing import (
56
- create_l0b_from_l0a,
56
+ generate_l0b,
57
57
  set_l0b_encodings,
58
58
  write_l0b,
59
59
  )
@@ -63,6 +63,7 @@ from disdrodb.l0.l0c_processing import (
63
63
  retrieve_possible_measurement_intervals,
64
64
  )
65
65
  from disdrodb.metadata import read_station_metadata
66
+ from disdrodb.utils.attrs import set_disdrodb_attrs
66
67
  from disdrodb.utils.decorators import delayed_if_parallel, single_threaded_if_parallel
67
68
 
68
69
  # Logger
@@ -73,8 +74,6 @@ from disdrodb.utils.logger import (
73
74
  log_error,
74
75
  log_info,
75
76
  )
76
-
77
- # log_warning,
78
77
  from disdrodb.utils.writer import write_product
79
78
  from disdrodb.utils.yaml import read_yaml
80
79
 
@@ -124,7 +123,7 @@ def _generate_l0a(
124
123
  # Log start processing
125
124
  msg = f"{product} processing of {filename} has started."
126
125
  log_info(logger=logger, msg=msg, verbose=verbose)
127
-
126
+ success_flag = False
128
127
  ##------------------------------------------------------------------------.
129
128
  ### - Read raw file into a dataframe and sanitize for L0A format
130
129
  try:
@@ -144,6 +143,12 @@ def _generate_l0a(
144
143
  filepath = os.path.join(folder_path, filename)
145
144
  write_l0a(df=df, filepath=filepath, force=force, logger=logger, verbose=verbose)
146
145
 
146
+ ##--------------------------------------------------------------------.
147
+ #### - Define logger file final directory
148
+ if folder_partitioning != "":
149
+ log_dst_dir = define_file_folder_path(df, data_dir=logs_dir, folder_partitioning=folder_partitioning)
150
+ os.makedirs(log_dst_dir, exist_ok=True)
151
+
147
152
  ##--------------------------------------------------------------------.
148
153
  # Clean environment
149
154
  del df
@@ -151,6 +156,7 @@ def _generate_l0a(
151
156
  # Log end processing
152
157
  msg = f"{product} processing of {filename} has ended."
153
158
  log_info(logger=logger, msg=msg, verbose=verbose)
159
+ success_flag = True
154
160
 
155
161
  # Otherwise log the error
156
162
  except Exception as e:
@@ -161,6 +167,13 @@ def _generate_l0a(
161
167
  # Close the file logger
162
168
  close_logger(logger)
163
169
 
170
+ # Move logger file to correct partitioning directory
171
+ if success_flag and folder_partitioning != "" and logger_filepath is not None:
172
+ # Move logger file to correct partitioning directory
173
+ dst_filepath = os.path.join(log_dst_dir, os.path.basename(logger_filepath))
174
+ shutil.move(logger_filepath, dst_filepath)
175
+ logger_filepath = dst_filepath
176
+
164
177
  # Return the logger file path
165
178
  return logger_filepath
166
179
 
@@ -200,6 +213,7 @@ def _generate_l0b(
200
213
  # Log start processing
201
214
  msg = f"{product} processing of {filename} has started."
202
215
  log_info(logger=logger, msg=msg, verbose=verbose)
216
+ success_flag = False
203
217
 
204
218
  ##------------------------------------------------------------------------.
205
219
  # Retrieve sensor name
@@ -209,11 +223,11 @@ def _generate_l0b(
209
223
  ##------------------------------------------------------------------------.
210
224
  try:
211
225
  # Read L0A Apache Parquet file
212
- df = read_l0a_dataframe(filepath, logger=logger, verbose=verbose, debugging_mode=debugging_mode)
226
+ df = read_l0a_dataframe(filepath, debugging_mode=debugging_mode)
213
227
 
214
228
  # -----------------------------------------------------------------.
215
229
  # Create xarray Dataset
216
- ds = create_l0b_from_l0a(df=df, metadata=metadata, logger=logger, verbose=verbose)
230
+ ds = generate_l0b(df=df, metadata=metadata, logger=logger, verbose=verbose)
217
231
 
218
232
  # -----------------------------------------------------------------.
219
233
  # Write L0B netCDF4 dataset
@@ -222,6 +236,12 @@ def _generate_l0b(
222
236
  filepath = os.path.join(folder_path, filename)
223
237
  write_l0b(ds, filepath=filepath, force=force)
224
238
 
239
+ ##--------------------------------------------------------------------.
240
+ #### - Define logger file final directory
241
+ if folder_partitioning != "":
242
+ log_dst_dir = define_file_folder_path(ds, data_dir=logs_dir, folder_partitioning=folder_partitioning)
243
+ os.makedirs(log_dst_dir, exist_ok=True)
244
+
225
245
  ##--------------------------------------------------------------------.
226
246
  # Clean environment
227
247
  del ds, df
@@ -229,6 +249,7 @@ def _generate_l0b(
229
249
  # Log end processing
230
250
  msg = f"{product} processing of {filename} has ended."
231
251
  log_info(logger=logger, msg=msg, verbose=verbose)
252
+ success_flag = True
232
253
 
233
254
  # Otherwise log the error
234
255
  except Exception as e:
@@ -239,10 +260,19 @@ def _generate_l0b(
239
260
  # Close the file logger
240
261
  close_logger(logger)
241
262
 
263
+ # Move logger file to correct partitioning directory
264
+ if success_flag and folder_partitioning != "" and logger_filepath is not None:
265
+ # Move logger file to correct partitioning directory
266
+ dst_filepath = os.path.join(log_dst_dir, os.path.basename(logger_filepath))
267
+ shutil.move(logger_filepath, dst_filepath)
268
+ logger_filepath = dst_filepath
269
+
242
270
  # Return the logger file path
243
271
  return logger_filepath
244
272
 
245
273
 
274
+ @delayed_if_parallel
275
+ @single_threaded_if_parallel
246
276
  def _generate_l0b_from_nc(
247
277
  filepath,
248
278
  data_dir,
@@ -282,6 +312,7 @@ def _generate_l0b_from_nc(
282
312
  # Log start processing
283
313
  msg = f"{product} processing of {filename} has started."
284
314
  log_info(logger=logger, msg=msg, verbose=verbose)
315
+ success_flag = False
285
316
 
286
317
  ##------------------------------------------------------------------------.
287
318
  ### - Read raw netCDF and sanitize for L0B format
@@ -303,6 +334,12 @@ def _generate_l0b_from_nc(
303
334
  filepath = os.path.join(folder_path, filename)
304
335
  write_l0b(ds, filepath=filepath, force=force)
305
336
 
337
+ ##--------------------------------------------------------------------.
338
+ #### - Define logger file final directory
339
+ if folder_partitioning != "":
340
+ log_dst_dir = define_file_folder_path(ds, data_dir=logs_dir, folder_partitioning=folder_partitioning)
341
+ os.makedirs(log_dst_dir, exist_ok=True)
342
+
306
343
  ##--------------------------------------------------------------------.
307
344
  # Clean environment
308
345
  del ds
@@ -310,6 +347,7 @@ def _generate_l0b_from_nc(
310
347
  # Log end processing
311
348
  msg = f"L0B processing of {filename} has ended."
312
349
  log_info(logger=logger, msg=msg, verbose=verbose)
350
+ success_flag = True
313
351
 
314
352
  # Otherwise log the error
315
353
  except Exception as e:
@@ -320,6 +358,13 @@ def _generate_l0b_from_nc(
320
358
  # Close the file logger
321
359
  close_logger(logger)
322
360
 
361
+ # Move logger file to correct partitioning directory
362
+ if success_flag and folder_partitioning != "" and logger_filepath is not None:
363
+ # Move logger file to correct partitioning directory
364
+ dst_filepath = os.path.join(log_dst_dir, os.path.basename(logger_filepath))
365
+ shutil.move(logger_filepath, dst_filepath)
366
+ logger_filepath = dst_filepath
367
+
323
368
  # Return the logger file path
324
369
  return logger_filepath
325
370
 
@@ -358,6 +403,7 @@ def _generate_l0c(
358
403
  # Log start processing
359
404
  msg = f"{product} processing for {day} has started."
360
405
  log_info(logger=logger, msg=msg, verbose=verbose)
406
+ success_flag = False
361
407
 
362
408
  ##------------------------------------------------------------------------.
363
409
  ### Core computation
@@ -388,21 +434,35 @@ def _generate_l0c(
388
434
 
389
435
  # Set encodings
390
436
  ds = set_l0b_encodings(ds=ds, sensor_name=sensor_name)
437
+ # Update global attributes
438
+ ds = set_disdrodb_attrs(ds, product=product)
391
439
 
392
- # Define filepath
440
+ # Define product filepath
393
441
  filename = define_l0c_filename(ds, campaign_name=campaign_name, station_name=station_name)
394
442
  folder_path = define_file_folder_path(ds, data_dir=data_dir, folder_partitioning=folder_partitioning)
395
443
  filepath = os.path.join(folder_path, filename)
396
444
 
397
445
  # Write to disk
398
- write_product(ds, product=product, filepath=filepath, force=force)
446
+ write_product(ds, filepath=filepath, force=force)
399
447
 
400
448
  # Clean environment
401
449
  del ds
402
450
 
451
+ ##--------------------------------------------------------------------.
452
+ #### - Define logger file final directory
453
+ if folder_partitioning != "":
454
+ print(day)
455
+ dirtree = define_partitioning_tree(
456
+ time=datetime.datetime.strptime("2022-03-22", "%Y-%m-%d"),
457
+ folder_partitioning=folder_partitioning,
458
+ )
459
+ log_dst_dir = os.path.join(logs_dir, dirtree)
460
+ os.makedirs(log_dst_dir, exist_ok=True)
461
+
403
462
  # Log end processing
404
463
  msg = f"{product} processing for {day} has ended."
405
464
  log_info(logger=logger, msg=msg, verbose=verbose)
465
+ success_flag = True
406
466
 
407
467
  ##--------------------------------------------------------------------.
408
468
  # Otherwise log the error
@@ -414,6 +474,13 @@ def _generate_l0c(
414
474
  # Close the file logger
415
475
  close_logger(logger)
416
476
 
477
+ # Move logger file to correct partitioning directory
478
+ if success_flag and folder_partitioning != "" and logger_filepath is not None:
479
+ # Move logger file to correct partitioning directory
480
+ dst_filepath = os.path.join(log_dst_dir, os.path.basename(logger_filepath))
481
+ shutil.move(logger_filepath, dst_filepath)
482
+ logger_filepath = dst_filepath
483
+
417
484
  # Return the logger file path
418
485
  return logger_filepath
419
486
 
@@ -474,6 +541,15 @@ def run_l0a_station(
474
541
  data_archive_dir = get_data_archive_dir(data_archive_dir)
475
542
  metadata_archive_dir = get_metadata_archive_dir(metadata_archive_dir)
476
543
 
544
+ # Check valid data_source, campaign_name, and station_name
545
+ check_station_inputs(
546
+ metadata_archive_dir=metadata_archive_dir,
547
+ data_source=data_source,
548
+ campaign_name=campaign_name,
549
+ station_name=station_name,
550
+ )
551
+
552
+ # ------------------------------------------------------------------------.
477
553
  # Read metadata
478
554
  metadata = read_station_metadata(
479
555
  metadata_archive_dir=metadata_archive_dir,
@@ -652,7 +728,7 @@ def run_l0b_station(
652
728
  and multi-threading will be automatically exploited to speed up I/O tasks.
653
729
  debugging_mode : bool, optional
654
730
  If ``True``, the amount of data processed will be reduced.
655
- Only the first 100 rows of 3 L0A files will be processed. The default value is ``False``.
731
+ Only 100 rows sampled from 3 L0A files will be processed. The default value is ``False``.
656
732
  remove_l0a: bool, optional
657
733
  Whether to remove the processed L0A files. The default value is ``False``.
658
734
  data_archive_dir : str, optional
@@ -669,6 +745,13 @@ def run_l0b_station(
669
745
  # Retrieve DISDRODB Metadata Archive directory
670
746
  metadata_archive_dir = get_metadata_archive_dir(metadata_archive_dir)
671
747
 
748
+ # Check valid data_source, campaign_name, and station_name
749
+ check_station_inputs(
750
+ metadata_archive_dir=metadata_archive_dir,
751
+ data_source=data_source,
752
+ campaign_name=campaign_name,
753
+ station_name=station_name,
754
+ )
672
755
  # -----------------------------------------------------------------.
673
756
  # Retrieve metadata
674
757
  metadata = read_station_metadata(
@@ -731,7 +814,7 @@ def run_l0b_station(
731
814
  # If no data available, print error message and return None
732
815
  if flag_not_available_data:
733
816
  msg = (
734
- f"{product} processing of {data_source} {campaign_name} {station_name}"
817
+ f"{product} processing of {data_source} {campaign_name} {station_name} "
735
818
  + f"has not been launched because of missing {required_product} data."
736
819
  )
737
820
  print(msg)
@@ -899,6 +982,14 @@ def run_l0c_station(
899
982
  # Retrieve DISDRODB Metadata Archive directory
900
983
  metadata_archive_dir = get_metadata_archive_dir(metadata_archive_dir)
901
984
 
985
+ # Check valid data_source, campaign_name, and station_name
986
+ check_station_inputs(
987
+ metadata_archive_dir=metadata_archive_dir,
988
+ data_source=data_source,
989
+ campaign_name=campaign_name,
990
+ station_name=station_name,
991
+ )
992
+
902
993
  # ------------------------------------------------------------------------.
903
994
  # Start processing
904
995
  t_i = time.time()
@@ -957,7 +1048,7 @@ def run_l0c_station(
957
1048
  # If no data available, print error message and return None
958
1049
  if flag_not_available_data:
959
1050
  msg = (
960
- f"{product} processing of {data_source} {campaign_name} {station_name}"
1051
+ f"{product} processing of {data_source} {campaign_name} {station_name} "
961
1052
  + f"has not been launched because of missing {required_product} data."
962
1053
  )
963
1054
  print(msg)
disdrodb/l1/__init__.py CHANGED
@@ -15,3 +15,8 @@
15
15
  # along with this program. If not, see <http://www.gnu.org/licenses/>.
16
16
  # -----------------------------------------------------------------------------.
17
17
  """DISDRODB L1 module."""
18
+ from disdrodb.l1.processing import generate_l1
19
+
20
+ __all__ = [
21
+ "generate_l1",
22
+ ]
disdrodb/l1/filters.py CHANGED
@@ -19,6 +19,8 @@
19
19
  import numpy as np
20
20
  import xarray as xr
21
21
 
22
+ from disdrodb.constants import DIAMETER_DIMENSION, VELOCITY_DIMENSION
23
+
22
24
 
23
25
  def filter_diameter_bins(ds, minimum_diameter=None, maximum_diameter=None):
24
26
  """
@@ -29,10 +31,10 @@ def filter_diameter_bins(ds, minimum_diameter=None, maximum_diameter=None):
29
31
  ds : xarray.Dataset
30
32
  The dataset containing diameter bin data.
31
33
  minimum_diameter : float, optional
32
- The minimum diameter to include in the filter, in millimeters.
34
+ The minimum diameter to be included, in millimeters.
33
35
  Defaults to the minimum value in `ds["diameter_bin_lower"]`.
34
36
  maximum_diameter : float, optional
35
- The maximum diameter to include in the filter, in millimeters.
37
+ The maximum diameter to be included, in millimeters.
36
38
  Defaults to the maximum value in `ds["diameter_bin_upper"]`.
37
39
 
38
40
  Returns
@@ -40,22 +42,28 @@ def filter_diameter_bins(ds, minimum_diameter=None, maximum_diameter=None):
40
42
  xarray.Dataset
41
43
  The filtered dataset containing only the specified diameter bins.
42
44
  """
45
+ # Put data into memory
46
+ ds["diameter_bin_lower"] = ds["diameter_bin_lower"].compute()
47
+ ds["diameter_bin_upper"] = ds["diameter_bin_upper"].compute()
48
+
43
49
  # Initialize default arguments
44
50
  if minimum_diameter is None:
45
51
  minimum_diameter = ds["diameter_bin_lower"].min().item()
46
52
  if maximum_diameter is None:
47
53
  maximum_diameter = ds["diameter_bin_upper"].max().item()
48
- # Select valid bins
54
+
55
+ # Select bins which overlap the specified diameters
49
56
  valid_indices = np.logical_and(
50
- ds["diameter_bin_lower"] >= minimum_diameter,
51
- ds["diameter_bin_upper"] <= maximum_diameter,
52
- )
53
- ds = ds.isel({"diameter_bin_center": valid_indices})
54
- # Update history
55
- history = ds.attrs.get("history", "")
56
- ds.attrs["history"] = (
57
- history + f" Selected drops with diameters between {minimum_diameter} and {maximum_diameter} mm \n"
57
+ ds["diameter_bin_upper"] > minimum_diameter,
58
+ ds["diameter_bin_lower"] < maximum_diameter,
58
59
  )
60
+
61
+ # Select bins with diameter values entirely inside the specified min/max values
62
+ # valid_indices = np.logical_and(
63
+ # ds["diameter_bin_lower"] >= minimum_diameter,
64
+ # ds["diameter_bin_upper"] <= maximum_diameter,
65
+ # )
66
+ ds = ds.isel({DIAMETER_DIMENSION: valid_indices})
59
67
  return ds
60
68
 
61
69
 
@@ -79,22 +87,28 @@ def filter_velocity_bins(ds, minimum_velocity=0, maximum_velocity=12):
79
87
  xarray.Dataset
80
88
  The filtered dataset containing only the specified velocity bins.
81
89
  """
90
+ # Put data into memory
91
+ ds["velocity_bin_lower"] = ds["velocity_bin_lower"].compute()
92
+ ds["velocity_bin_upper"] = ds["velocity_bin_upper"].compute()
93
+
82
94
  # Initialize default arguments
83
95
  if minimum_velocity is None:
84
96
  minimum_velocity = ds["velocity_bin_lower"].min().item()
85
97
  if maximum_velocity is None:
86
98
  maximum_velocity = ds["velocity_bin_upper"].max().item()
87
- # Select valid bins
99
+
100
+ # Select bins which overlap the specified velocities
88
101
  valid_indices = np.logical_and(
89
- ds["velocity_bin_lower"] >= minimum_velocity,
90
- ds["velocity_bin_upper"] <= maximum_velocity,
91
- )
92
- ds = ds.isel({"velocity_bin_center": valid_indices})
93
- # Update history
94
- history = ds.attrs.get("history", "")
95
- ds.attrs["history"] = (
96
- history + f" Selected drops with fall velocity between {minimum_velocity} and {maximum_velocity} m/s \n"
102
+ ds["velocity_bin_upper"] > minimum_velocity,
103
+ ds["velocity_bin_lower"] < maximum_velocity,
97
104
  )
105
+
106
+ # Select bins with velocity values entirely inside the specified min/max values
107
+ # valid_indices = np.logical_and(
108
+ # ds["velocity_bin_lower"] >= minimum_velocity,
109
+ # ds["velocity_bin_upper"] <= maximum_velocity,
110
+ # )
111
+ ds = ds.isel({VELOCITY_DIMENSION: valid_indices})
98
112
  return ds
99
113
 
100
114
 
disdrodb/l1/processing.py CHANGED
@@ -16,22 +16,19 @@
16
16
  # -----------------------------------------------------------------------------.
17
17
  """Core functions for DISDRODB L1 production."""
18
18
 
19
-
20
19
  import xarray as xr
21
20
 
22
- from disdrodb import DIAMETER_DIMENSION, VELOCITY_DIMENSION
23
- from disdrodb.l1.encoding_attrs import get_attrs_dict, get_encoding_dict
21
+ from disdrodb.constants import DIAMETER_DIMENSION, VELOCITY_DIMENSION
24
22
  from disdrodb.l1.fall_velocity import get_raindrop_fall_velocity
25
23
  from disdrodb.l1.filters import define_spectrum_mask, filter_diameter_bins, filter_velocity_bins
26
24
  from disdrodb.l1.resampling import add_sample_interval
27
25
  from disdrodb.l1_env.routines import load_env_dataset
28
26
  from disdrodb.l2.empirical_dsd import ( # TODO: maybe move out of L2
29
- compute_qc_bins_metrics,
27
+ add_bins_metrics,
30
28
  get_min_max_diameter,
31
29
  )
32
- from disdrodb.utils.attrs import set_attrs
33
- from disdrodb.utils.encoding import set_encodings
34
30
  from disdrodb.utils.time import ensure_sample_interval_in_seconds, infer_sample_interval
31
+ from disdrodb.utils.writer import finalize_product
35
32
 
36
33
 
37
34
  def generate_l1(
@@ -51,7 +48,7 @@ def generate_l1(
51
48
  small_velocity_threshold=2.5, # 3
52
49
  maintain_smallest_drops=True,
53
50
  ):
54
- """Generate the DISDRODB L1 dataset from the DISDRODB L0C dataset.
51
+ """Generate DISDRODB L1 Dataset from DISDRODB L0C Dataset.
55
52
 
56
53
  Parameters
57
54
  ----------
@@ -88,17 +85,17 @@ def generate_l1(
88
85
  xarray.Dataset
89
86
  DISRODB L1 dataset.
90
87
  """
91
- # Take as input an L0 !
92
-
93
88
  # Retrieve source attributes
94
89
  attrs = ds.attrs.copy()
95
90
 
96
91
  # Determine if the velocity dimension is available
97
92
  has_velocity_dimension = VELOCITY_DIMENSION in ds.dims
98
93
 
99
- # Initialize L2 dataset
100
- ds_l1 = xr.Dataset()
94
+ # Retrieve sensor_name
95
+ # - If not present, don't drop Parsivels first two bins
96
+ sensor_name = attrs.get("sensor_name", "")
101
97
 
98
+ # ---------------------------------------------------------------------------
102
99
  # Retrieve sample interval
103
100
  # --> sample_interval is a coordinate of L0C products
104
101
  if "sample_interval" in ds:
@@ -107,39 +104,52 @@ def generate_l1(
107
104
  # This line is not called in the DISDRODB processing chain !
108
105
  sample_interval = infer_sample_interval(ds, verbose=False)
109
106
 
110
- # Re-add sample interval as coordinate (in seconds)
111
- ds = add_sample_interval(ds, sample_interval=sample_interval)
112
-
113
107
  # ---------------------------------------------------------------------------
114
108
  # Retrieve ENV dataset or take defaults
115
109
  # --> Used only for Beard fall velocity currently !
116
110
  ds_env = load_env_dataset(ds)
117
111
 
112
+ # ---------------------------------------------------------------------------
113
+ # Initialize L1 dataset
114
+ ds_l1 = xr.Dataset()
115
+
116
+ # Add raw_drop_number variable to L1 dataset
117
+ ds_l1["raw_drop_number"] = ds["raw_drop_number"]
118
+
119
+ # Add sample interval as coordinate (in seconds)
120
+ ds_l1 = add_sample_interval(ds_l1, sample_interval=sample_interval)
121
+
122
+ # Add L0C coordinates that might got lost
123
+ if "time_qc" in ds_l1:
124
+ ds_l1 = ds_l1.assign_coords({"time_qc": ds["time_qc"]})
125
+
118
126
  # -------------------------------------------------------------------------------------------
119
127
  # Filter dataset by diameter and velocity bins
128
+ if sensor_name in ["PARSIVEL", "PARSIVEL2"]:
129
+ # - Remove first two bins because never reports data !
130
+ # - If not removed, can alter e.g. L2M model fitting
131
+ ds_l1 = filter_diameter_bins(ds=ds_l1, minimum_diameter=0.312) # it includes the 0.2495-0.3745 bin
132
+
120
133
  # - Filter diameter bins
121
- ds = filter_diameter_bins(ds=ds, minimum_diameter=minimum_diameter, maximum_diameter=maximum_diameter)
134
+ ds_l1 = filter_diameter_bins(ds=ds_l1, minimum_diameter=minimum_diameter, maximum_diameter=maximum_diameter)
122
135
  # - Filter velocity bins
123
136
  if has_velocity_dimension:
124
- ds = filter_velocity_bins(ds=ds, minimum_velocity=minimum_velocity, maximum_velocity=maximum_velocity)
137
+ ds_l1 = filter_velocity_bins(ds=ds_l1, minimum_velocity=minimum_velocity, maximum_velocity=maximum_velocity)
125
138
 
126
139
  # -------------------------------------------------------------------------------------------
127
140
  # Compute fall velocity
128
- fall_velocity = get_raindrop_fall_velocity(
129
- diameter=ds["diameter_bin_center"],
141
+ ds_l1["fall_velocity"] = get_raindrop_fall_velocity(
142
+ diameter=ds_l1["diameter_bin_center"],
130
143
  method=fall_velocity_method,
131
144
  ds_env=ds_env, # mm
132
145
  )
133
146
 
134
- # Add fall velocity
135
- ds_l1["fall_velocity"] = fall_velocity
136
-
137
147
  # -------------------------------------------------------------------------------------------
138
148
  # Define filtering mask according to fall velocity
139
149
  if has_velocity_dimension:
140
150
  mask = define_spectrum_mask(
141
- drop_number=ds["raw_drop_number"],
142
- fall_velocity=fall_velocity,
151
+ drop_number=ds_l1["raw_drop_number"],
152
+ fall_velocity=ds_l1["fall_velocity"],
143
153
  above_velocity_fraction=above_velocity_fraction,
144
154
  above_velocity_tolerance=above_velocity_tolerance,
145
155
  below_velocity_fraction=below_velocity_fraction,
@@ -152,14 +162,14 @@ def generate_l1(
152
162
  # -------------------------------------------------------------------------------------------
153
163
  # Retrieve drop number and drop_counts arrays
154
164
  if has_velocity_dimension:
155
- drop_number = ds["raw_drop_number"].where(mask) # 2D (diameter, velocity)
165
+ drop_number = ds_l1["raw_drop_number"].where(mask) # 2D (diameter, velocity)
156
166
  drop_counts = drop_number.sum(dim=VELOCITY_DIMENSION) # 1D (diameter)
157
- drop_counts_raw = ds["raw_drop_number"].sum(dim=VELOCITY_DIMENSION) # 1D (diameter)
167
+ drop_counts_raw = ds_l1["raw_drop_number"].sum(dim=VELOCITY_DIMENSION) # 1D (diameter)
158
168
 
159
169
  else:
160
- drop_number = ds["raw_drop_number"] # 1D (diameter)
161
- drop_counts = ds["raw_drop_number"] # 1D (diameter)
162
- drop_counts_raw = ds["raw_drop_number"]
170
+ drop_number = ds_l1["raw_drop_number"] # 1D (diameter)
171
+ drop_counts = ds_l1["raw_drop_number"] # 1D (diameter)
172
+ drop_counts_raw = ds_l1["raw_drop_number"]
163
173
 
164
174
  # Add drop number and drop_counts
165
175
  ds_l1["drop_number"] = drop_number
@@ -173,30 +183,21 @@ def generate_l1(
173
183
  ds_l1["Dmin"] = min_drop_diameter
174
184
  ds_l1["Dmax"] = max_drop_diameter
175
185
  ds_l1["N"] = drop_counts.sum(dim=DIAMETER_DIMENSION)
176
- ds_l1["Nremoved"] = drop_counts_raw.sum(dim=DIAMETER_DIMENSION) - ds_l1["N"]
186
+ ds_l1["Nraw"] = drop_counts_raw.sum(dim=DIAMETER_DIMENSION)
187
+ ds_l1["Nremoved"] = ds_l1["Nraw"] - ds_l1["N"]
177
188
 
178
189
  # Add bins statistics
179
- ds_l1.update(compute_qc_bins_metrics(ds_l1))
190
+ ds_l1 = add_bins_metrics(ds_l1)
180
191
 
181
192
  # -------------------------------------------------------------------------------------------
182
193
  # Add quality flags
183
194
  # TODO: snow_flags, insects_flag, ...
184
195
 
185
- # -------------------------------------------------------------------------------------------
186
- #### Add L0C coordinates that might got lost
187
- if "time_qc" in ds:
188
- ds_l1 = ds_l1.assign_coords({"time_qc": ds["time_qc"]})
189
-
190
196
  #### ----------------------------------------------------------------------------.
191
- #### Add encodings and attributes
192
- # Add variables attributes
193
- attrs_dict = get_attrs_dict()
194
- ds_l1 = set_attrs(ds_l1, attrs_dict=attrs_dict)
195
-
196
- # Add variables encoding
197
- encoding_dict = get_encoding_dict()
198
- ds_l1 = set_encodings(ds_l1, encoding_dict=encoding_dict)
199
-
197
+ #### Finalize dataset
200
198
  # Add global attributes
201
199
  ds_l1.attrs = attrs
200
+
201
+ # Add variables attributes and encodings
202
+ ds_l1 = finalize_product(ds_l1, product="L1")
202
203
  return ds_l1