cloudnetpy 1.92.2__tar.gz → 1.92.4__tar.gz

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 (140) hide show
  1. {cloudnetpy-1.92.2/cloudnetpy.egg-info → cloudnetpy-1.92.4}/PKG-INFO +1 -1
  2. {cloudnetpy-1.92.2 → cloudnetpy-1.92.4}/cloudnetpy/cli.py +58 -29
  3. {cloudnetpy-1.92.2 → cloudnetpy-1.92.4}/cloudnetpy/disdronator/parsivel.py +1 -1
  4. {cloudnetpy-1.92.2 → cloudnetpy-1.92.4}/cloudnetpy/instruments/weather_station.py +31 -0
  5. {cloudnetpy-1.92.2 → cloudnetpy-1.92.4}/cloudnetpy/model_evaluation/file_handler.py +1 -1
  6. {cloudnetpy-1.92.2 → cloudnetpy-1.92.4}/cloudnetpy/output.py +1 -1
  7. {cloudnetpy-1.92.2 → cloudnetpy-1.92.4}/cloudnetpy/plotting/plot_meta.py +0 -1
  8. {cloudnetpy-1.92.2 → cloudnetpy-1.92.4}/cloudnetpy/plotting/plotting.py +2 -0
  9. {cloudnetpy-1.92.2 → cloudnetpy-1.92.4}/cloudnetpy/utils.py +5 -5
  10. {cloudnetpy-1.92.2 → cloudnetpy-1.92.4}/cloudnetpy/version.py +1 -1
  11. {cloudnetpy-1.92.2 → cloudnetpy-1.92.4/cloudnetpy.egg-info}/PKG-INFO +1 -1
  12. {cloudnetpy-1.92.2 → cloudnetpy-1.92.4}/LICENSE +0 -0
  13. {cloudnetpy-1.92.2 → cloudnetpy-1.92.4}/MANIFEST.in +0 -0
  14. {cloudnetpy-1.92.2 → cloudnetpy-1.92.4}/README.md +0 -0
  15. {cloudnetpy-1.92.2 → cloudnetpy-1.92.4}/cloudnetpy/__init__.py +0 -0
  16. {cloudnetpy-1.92.2 → cloudnetpy-1.92.4}/cloudnetpy/categorize/__init__.py +0 -0
  17. {cloudnetpy-1.92.2 → cloudnetpy-1.92.4}/cloudnetpy/categorize/atmos_utils.py +0 -0
  18. {cloudnetpy-1.92.2 → cloudnetpy-1.92.4}/cloudnetpy/categorize/attenuation.py +0 -0
  19. {cloudnetpy-1.92.2 → cloudnetpy-1.92.4}/cloudnetpy/categorize/attenuations/__init__.py +0 -0
  20. {cloudnetpy-1.92.2 → cloudnetpy-1.92.4}/cloudnetpy/categorize/attenuations/gas_attenuation.py +0 -0
  21. {cloudnetpy-1.92.2 → cloudnetpy-1.92.4}/cloudnetpy/categorize/attenuations/liquid_attenuation.py +0 -0
  22. {cloudnetpy-1.92.2 → cloudnetpy-1.92.4}/cloudnetpy/categorize/attenuations/melting_attenuation.py +0 -0
  23. {cloudnetpy-1.92.2 → cloudnetpy-1.92.4}/cloudnetpy/categorize/attenuations/rain_attenuation.py +0 -0
  24. {cloudnetpy-1.92.2 → cloudnetpy-1.92.4}/cloudnetpy/categorize/categorize.py +0 -0
  25. {cloudnetpy-1.92.2 → cloudnetpy-1.92.4}/cloudnetpy/categorize/classify.py +0 -0
  26. {cloudnetpy-1.92.2 → cloudnetpy-1.92.4}/cloudnetpy/categorize/containers.py +0 -0
  27. {cloudnetpy-1.92.2 → cloudnetpy-1.92.4}/cloudnetpy/categorize/disdrometer.py +0 -0
  28. {cloudnetpy-1.92.2 → cloudnetpy-1.92.4}/cloudnetpy/categorize/droplet.py +0 -0
  29. {cloudnetpy-1.92.2 → cloudnetpy-1.92.4}/cloudnetpy/categorize/falling.py +0 -0
  30. {cloudnetpy-1.92.2 → cloudnetpy-1.92.4}/cloudnetpy/categorize/freezing.py +0 -0
  31. {cloudnetpy-1.92.2 → cloudnetpy-1.92.4}/cloudnetpy/categorize/insects.py +0 -0
  32. {cloudnetpy-1.92.2 → cloudnetpy-1.92.4}/cloudnetpy/categorize/lidar.py +0 -0
  33. {cloudnetpy-1.92.2 → cloudnetpy-1.92.4}/cloudnetpy/categorize/melting.py +0 -0
  34. {cloudnetpy-1.92.2 → cloudnetpy-1.92.4}/cloudnetpy/categorize/model.py +0 -0
  35. {cloudnetpy-1.92.2 → cloudnetpy-1.92.4}/cloudnetpy/categorize/mwr.py +0 -0
  36. {cloudnetpy-1.92.2 → cloudnetpy-1.92.4}/cloudnetpy/categorize/radar.py +0 -0
  37. {cloudnetpy-1.92.2 → cloudnetpy-1.92.4}/cloudnetpy/cloudnetarray.py +0 -0
  38. {cloudnetpy-1.92.2 → cloudnetpy-1.92.4}/cloudnetpy/concat_lib.py +0 -0
  39. {cloudnetpy-1.92.2 → cloudnetpy-1.92.4}/cloudnetpy/constants.py +0 -0
  40. {cloudnetpy-1.92.2 → cloudnetpy-1.92.4}/cloudnetpy/datasource.py +0 -0
  41. {cloudnetpy-1.92.2 → cloudnetpy-1.92.4}/cloudnetpy/disdronator/__init__.py +0 -0
  42. {cloudnetpy-1.92.2 → cloudnetpy-1.92.4}/cloudnetpy/disdronator/lpm.py +0 -0
  43. {cloudnetpy-1.92.2 → cloudnetpy-1.92.4}/cloudnetpy/disdronator/rd80.py +0 -0
  44. {cloudnetpy-1.92.2 → cloudnetpy-1.92.4}/cloudnetpy/disdronator/utils.py +0 -0
  45. {cloudnetpy-1.92.2 → cloudnetpy-1.92.4}/cloudnetpy/exceptions.py +0 -0
  46. {cloudnetpy-1.92.2 → cloudnetpy-1.92.4}/cloudnetpy/instruments/__init__.py +0 -0
  47. {cloudnetpy-1.92.2 → cloudnetpy-1.92.4}/cloudnetpy/instruments/basta.py +0 -0
  48. {cloudnetpy-1.92.2 → cloudnetpy-1.92.4}/cloudnetpy/instruments/bowtie.py +0 -0
  49. {cloudnetpy-1.92.2 → cloudnetpy-1.92.4}/cloudnetpy/instruments/ceilo.py +0 -0
  50. {cloudnetpy-1.92.2 → cloudnetpy-1.92.4}/cloudnetpy/instruments/ceilometer.py +0 -0
  51. {cloudnetpy-1.92.2 → cloudnetpy-1.92.4}/cloudnetpy/instruments/cl61d.py +0 -0
  52. {cloudnetpy-1.92.2 → cloudnetpy-1.92.4}/cloudnetpy/instruments/cloudnet_instrument.py +0 -0
  53. {cloudnetpy-1.92.2 → cloudnetpy-1.92.4}/cloudnetpy/instruments/copernicus.py +0 -0
  54. {cloudnetpy-1.92.2 → cloudnetpy-1.92.4}/cloudnetpy/instruments/da10.py +0 -0
  55. {cloudnetpy-1.92.2 → cloudnetpy-1.92.4}/cloudnetpy/instruments/disdrometer/__init__.py +0 -0
  56. {cloudnetpy-1.92.2 → cloudnetpy-1.92.4}/cloudnetpy/instruments/disdrometer/common.py +0 -0
  57. {cloudnetpy-1.92.2 → cloudnetpy-1.92.4}/cloudnetpy/instruments/disdrometer/parsivel.py +0 -0
  58. {cloudnetpy-1.92.2 → cloudnetpy-1.92.4}/cloudnetpy/instruments/disdrometer/rd80.py +0 -0
  59. {cloudnetpy-1.92.2 → cloudnetpy-1.92.4}/cloudnetpy/instruments/disdrometer/thies.py +0 -0
  60. {cloudnetpy-1.92.2 → cloudnetpy-1.92.4}/cloudnetpy/instruments/fd12p.py +0 -0
  61. {cloudnetpy-1.92.2 → cloudnetpy-1.92.4}/cloudnetpy/instruments/galileo.py +0 -0
  62. {cloudnetpy-1.92.2 → cloudnetpy-1.92.4}/cloudnetpy/instruments/hatpro.py +0 -0
  63. {cloudnetpy-1.92.2 → cloudnetpy-1.92.4}/cloudnetpy/instruments/instruments.py +0 -0
  64. {cloudnetpy-1.92.2 → cloudnetpy-1.92.4}/cloudnetpy/instruments/lufft.py +0 -0
  65. {cloudnetpy-1.92.2 → cloudnetpy-1.92.4}/cloudnetpy/instruments/mira.py +0 -0
  66. {cloudnetpy-1.92.2 → cloudnetpy-1.92.4}/cloudnetpy/instruments/mrr.py +0 -0
  67. {cloudnetpy-1.92.2 → cloudnetpy-1.92.4}/cloudnetpy/instruments/nc_lidar.py +0 -0
  68. {cloudnetpy-1.92.2 → cloudnetpy-1.92.4}/cloudnetpy/instruments/nc_radar.py +0 -0
  69. {cloudnetpy-1.92.2 → cloudnetpy-1.92.4}/cloudnetpy/instruments/pollyxt.py +0 -0
  70. {cloudnetpy-1.92.2 → cloudnetpy-1.92.4}/cloudnetpy/instruments/radiometrics.py +0 -0
  71. {cloudnetpy-1.92.2 → cloudnetpy-1.92.4}/cloudnetpy/instruments/rain_e_h3.py +0 -0
  72. {cloudnetpy-1.92.2 → cloudnetpy-1.92.4}/cloudnetpy/instruments/rpg.py +0 -0
  73. {cloudnetpy-1.92.2 → cloudnetpy-1.92.4}/cloudnetpy/instruments/rpg_reader.py +0 -0
  74. {cloudnetpy-1.92.2 → cloudnetpy-1.92.4}/cloudnetpy/instruments/toa5.py +0 -0
  75. {cloudnetpy-1.92.2 → cloudnetpy-1.92.4}/cloudnetpy/instruments/vaisala.py +0 -0
  76. {cloudnetpy-1.92.2 → cloudnetpy-1.92.4}/cloudnetpy/instruments/weather_radar.py +0 -0
  77. {cloudnetpy-1.92.2 → cloudnetpy-1.92.4}/cloudnetpy/metadata.py +0 -0
  78. {cloudnetpy-1.92.2 → cloudnetpy-1.92.4}/cloudnetpy/model_evaluation/__init__.py +0 -0
  79. {cloudnetpy-1.92.2 → cloudnetpy-1.92.4}/cloudnetpy/model_evaluation/metadata.py +0 -0
  80. {cloudnetpy-1.92.2 → cloudnetpy-1.92.4}/cloudnetpy/model_evaluation/model_metadata.py +0 -0
  81. {cloudnetpy-1.92.2 → cloudnetpy-1.92.4}/cloudnetpy/model_evaluation/plotting/__init__.py +0 -0
  82. {cloudnetpy-1.92.2 → cloudnetpy-1.92.4}/cloudnetpy/model_evaluation/plotting/plot_meta.py +0 -0
  83. {cloudnetpy-1.92.2 → cloudnetpy-1.92.4}/cloudnetpy/model_evaluation/plotting/plot_tools.py +0 -0
  84. {cloudnetpy-1.92.2 → cloudnetpy-1.92.4}/cloudnetpy/model_evaluation/plotting/plotting.py +0 -0
  85. {cloudnetpy-1.92.2 → cloudnetpy-1.92.4}/cloudnetpy/model_evaluation/products/__init__.py +0 -0
  86. {cloudnetpy-1.92.2 → cloudnetpy-1.92.4}/cloudnetpy/model_evaluation/products/advance_methods.py +0 -0
  87. {cloudnetpy-1.92.2 → cloudnetpy-1.92.4}/cloudnetpy/model_evaluation/products/grid_methods.py +0 -0
  88. {cloudnetpy-1.92.2 → cloudnetpy-1.92.4}/cloudnetpy/model_evaluation/products/model_products.py +0 -0
  89. {cloudnetpy-1.92.2 → cloudnetpy-1.92.4}/cloudnetpy/model_evaluation/products/observation_products.py +0 -0
  90. {cloudnetpy-1.92.2 → cloudnetpy-1.92.4}/cloudnetpy/model_evaluation/products/product_resampling.py +0 -0
  91. {cloudnetpy-1.92.2 → cloudnetpy-1.92.4}/cloudnetpy/model_evaluation/products/tools.py +0 -0
  92. {cloudnetpy-1.92.2 → cloudnetpy-1.92.4}/cloudnetpy/model_evaluation/statistics/__init__.py +0 -0
  93. {cloudnetpy-1.92.2 → cloudnetpy-1.92.4}/cloudnetpy/model_evaluation/statistics/statistical_methods.py +0 -0
  94. {cloudnetpy-1.92.2 → cloudnetpy-1.92.4}/cloudnetpy/model_evaluation/tests/__init__.py +0 -0
  95. {cloudnetpy-1.92.2 → cloudnetpy-1.92.4}/cloudnetpy/model_evaluation/tests/e2e/__init__.py +0 -0
  96. {cloudnetpy-1.92.2 → cloudnetpy-1.92.4}/cloudnetpy/model_evaluation/tests/e2e/conftest.py +0 -0
  97. {cloudnetpy-1.92.2 → cloudnetpy-1.92.4}/cloudnetpy/model_evaluation/tests/e2e/process_cf/__init__.py +0 -0
  98. {cloudnetpy-1.92.2 → cloudnetpy-1.92.4}/cloudnetpy/model_evaluation/tests/e2e/process_cf/main.py +0 -0
  99. {cloudnetpy-1.92.2 → cloudnetpy-1.92.4}/cloudnetpy/model_evaluation/tests/e2e/process_cf/tests.py +0 -0
  100. {cloudnetpy-1.92.2 → cloudnetpy-1.92.4}/cloudnetpy/model_evaluation/tests/e2e/process_iwc/__init__.py +0 -0
  101. {cloudnetpy-1.92.2 → cloudnetpy-1.92.4}/cloudnetpy/model_evaluation/tests/e2e/process_iwc/main.py +0 -0
  102. {cloudnetpy-1.92.2 → cloudnetpy-1.92.4}/cloudnetpy/model_evaluation/tests/e2e/process_iwc/tests.py +0 -0
  103. {cloudnetpy-1.92.2 → cloudnetpy-1.92.4}/cloudnetpy/model_evaluation/tests/e2e/process_lwc/__init__.py +0 -0
  104. {cloudnetpy-1.92.2 → cloudnetpy-1.92.4}/cloudnetpy/model_evaluation/tests/e2e/process_lwc/main.py +0 -0
  105. {cloudnetpy-1.92.2 → cloudnetpy-1.92.4}/cloudnetpy/model_evaluation/tests/e2e/process_lwc/tests.py +0 -0
  106. {cloudnetpy-1.92.2 → cloudnetpy-1.92.4}/cloudnetpy/model_evaluation/tests/unit/__init__.py +0 -0
  107. {cloudnetpy-1.92.2 → cloudnetpy-1.92.4}/cloudnetpy/model_evaluation/tests/unit/conftest.py +0 -0
  108. {cloudnetpy-1.92.2 → cloudnetpy-1.92.4}/cloudnetpy/model_evaluation/tests/unit/test_advance_methods.py +0 -0
  109. {cloudnetpy-1.92.2 → cloudnetpy-1.92.4}/cloudnetpy/model_evaluation/tests/unit/test_grid_methods.py +0 -0
  110. {cloudnetpy-1.92.2 → cloudnetpy-1.92.4}/cloudnetpy/model_evaluation/tests/unit/test_model_products.py +0 -0
  111. {cloudnetpy-1.92.2 → cloudnetpy-1.92.4}/cloudnetpy/model_evaluation/tests/unit/test_observation_products.py +0 -0
  112. {cloudnetpy-1.92.2 → cloudnetpy-1.92.4}/cloudnetpy/model_evaluation/tests/unit/test_plot_tools.py +0 -0
  113. {cloudnetpy-1.92.2 → cloudnetpy-1.92.4}/cloudnetpy/model_evaluation/tests/unit/test_plotting.py +0 -0
  114. {cloudnetpy-1.92.2 → cloudnetpy-1.92.4}/cloudnetpy/model_evaluation/tests/unit/test_statistical_methods.py +0 -0
  115. {cloudnetpy-1.92.2 → cloudnetpy-1.92.4}/cloudnetpy/model_evaluation/tests/unit/test_tools.py +0 -0
  116. {cloudnetpy-1.92.2 → cloudnetpy-1.92.4}/cloudnetpy/model_evaluation/utils.py +0 -0
  117. {cloudnetpy-1.92.2 → cloudnetpy-1.92.4}/cloudnetpy/plotting/__init__.py +0 -0
  118. {cloudnetpy-1.92.2 → cloudnetpy-1.92.4}/cloudnetpy/products/__init__.py +0 -0
  119. {cloudnetpy-1.92.2 → cloudnetpy-1.92.4}/cloudnetpy/products/classification.py +0 -0
  120. {cloudnetpy-1.92.2 → cloudnetpy-1.92.4}/cloudnetpy/products/der.py +0 -0
  121. {cloudnetpy-1.92.2 → cloudnetpy-1.92.4}/cloudnetpy/products/drizzle.py +0 -0
  122. {cloudnetpy-1.92.2 → cloudnetpy-1.92.4}/cloudnetpy/products/drizzle_error.py +0 -0
  123. {cloudnetpy-1.92.2 → cloudnetpy-1.92.4}/cloudnetpy/products/drizzle_tools.py +0 -0
  124. {cloudnetpy-1.92.2 → cloudnetpy-1.92.4}/cloudnetpy/products/epsilon_lidar.py +0 -0
  125. {cloudnetpy-1.92.2 → cloudnetpy-1.92.4}/cloudnetpy/products/epsilon_radar.py +0 -0
  126. {cloudnetpy-1.92.2 → cloudnetpy-1.92.4}/cloudnetpy/products/ier.py +0 -0
  127. {cloudnetpy-1.92.2 → cloudnetpy-1.92.4}/cloudnetpy/products/iwc.py +0 -0
  128. {cloudnetpy-1.92.2 → cloudnetpy-1.92.4}/cloudnetpy/products/lwc.py +0 -0
  129. {cloudnetpy-1.92.2 → cloudnetpy-1.92.4}/cloudnetpy/products/mie_lu_tables.nc +0 -0
  130. {cloudnetpy-1.92.2 → cloudnetpy-1.92.4}/cloudnetpy/products/mwr_tools.py +0 -0
  131. {cloudnetpy-1.92.2 → cloudnetpy-1.92.4}/cloudnetpy/products/product_tools.py +0 -0
  132. {cloudnetpy-1.92.2 → cloudnetpy-1.92.4}/cloudnetpy/py.typed +0 -0
  133. {cloudnetpy-1.92.2 → cloudnetpy-1.92.4}/cloudnetpy.egg-info/SOURCES.txt +0 -0
  134. {cloudnetpy-1.92.2 → cloudnetpy-1.92.4}/cloudnetpy.egg-info/dependency_links.txt +0 -0
  135. {cloudnetpy-1.92.2 → cloudnetpy-1.92.4}/cloudnetpy.egg-info/entry_points.txt +0 -0
  136. {cloudnetpy-1.92.2 → cloudnetpy-1.92.4}/cloudnetpy.egg-info/requires.txt +0 -0
  137. {cloudnetpy-1.92.2 → cloudnetpy-1.92.4}/cloudnetpy.egg-info/top_level.txt +0 -0
  138. {cloudnetpy-1.92.2 → cloudnetpy-1.92.4}/docs/source/conf.py +0 -0
  139. {cloudnetpy-1.92.2 → cloudnetpy-1.92.4}/pyproject.toml +0 -0
  140. {cloudnetpy-1.92.2 → cloudnetpy-1.92.4}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cloudnetpy
3
- Version: 1.92.2
3
+ Version: 1.92.4
4
4
  Summary: Python package for Cloudnet processing
5
5
  Author: Simo Tukiainen
6
6
  License: MIT License
@@ -120,23 +120,34 @@ def _process_epsilon_radar(
120
120
 
121
121
  def _process_categorize(
122
122
  input_files: dict,
123
- instrument_prefs: dict[str, str],
123
+ instrument_prefs: dict[str, list[str]],
124
124
  args: argparse.Namespace,
125
125
  client: APIClient,
126
126
  ) -> str | None:
127
127
  cat_filepath = _create_categorize_filepath(args)
128
128
 
129
129
  def fetch_instrument(product: str) -> tuple[str, str | None]:
130
- source = instrument_prefs.get(product)
131
- filepath = _fetch_product(args, product, client, source=source)
132
- if filepath is None and source:
130
+ prefs = instrument_prefs.get(product, [])
131
+ for source in prefs:
132
+ filepath = _fetch_product(args, product, client, source=source)
133
+ if filepath is None and product == "lidar":
134
+ # Doppler lidars (e.g. halo) upload as "doppler-lidar" product,
135
+ # but categorize can use those files as lidar input.
136
+ filepath = _fetch_product(args, "doppler-lidar", client, source=source)
137
+ if filepath:
138
+ logging.info(
139
+ "Using doppler-lidar product from '%s' as lidar input",
140
+ source,
141
+ )
142
+ if filepath:
143
+ return product, filepath
144
+ if prefs:
133
145
  logging.info(
134
- "Preferred instrument '%s' not found for %s, using available",
135
- source,
146
+ "Preferred instruments for %s not found (%s), using available",
136
147
  product,
148
+ ", ".join(prefs),
137
149
  )
138
- filepath = _fetch_product(args, product, client)
139
- return product, filepath
150
+ return product, _fetch_product(args, product, client)
140
151
 
141
152
  def fetch_mwr() -> tuple[str, str | None]:
142
153
  try:
@@ -383,17 +394,17 @@ def _filter_by_suffix(meta: list[RawMetadata], product: str) -> list[RawMetadata
383
394
 
384
395
 
385
396
  def _get_source_instruments(
386
- products: list[str], instrument_prefs: dict[str, str], client: APIClient
397
+ products: list[str], instrument_prefs: dict[str, list[str]], client: APIClient
387
398
  ) -> dict[str, list[str]]:
388
399
  source_instruments = {}
389
400
  for product in products:
390
401
  prod, model = _parse_instrument(product)
391
- if model is None:
392
- pref = instrument_prefs.get(prod)
393
- if pref is not None and not _is_pid(pref):
394
- model = pref
402
+ if model is not None:
403
+ models = [model]
404
+ else:
405
+ models = [p for p in instrument_prefs.get(prod, []) if not _is_pid(p)]
395
406
  all_possible = client.product(prod).source_instrument_ids
396
- if all_possible and (match := [i for i in all_possible if i == model]):
407
+ if all_possible and (match := [m for m in models if m in all_possible]):
397
408
  source_instruments[prod] = match
398
409
  else:
399
410
  source_instruments[prod] = list(all_possible)
@@ -422,11 +433,16 @@ def _get_product_sources(
422
433
  return source_products
423
434
 
424
435
 
425
- def _parse_instrument_preferences(args: list[str] | None) -> dict[str, str]:
426
- """Parses instrument preferences like 'radar:mira-35' into a dict."""
436
+ def _parse_instrument_preferences(args: list[str] | None) -> dict[str, list[str]]:
437
+ """Parses instrument preferences into ordered preference lists per product.
438
+
439
+ Each argument has the form 'product:value' where value is an instrument_id,
440
+ a PID, or a comma-separated list of these. Repeated flags for the same
441
+ product accumulate; ordering reflects user preference (first match wins).
442
+ """
427
443
  if not args:
428
444
  return {}
429
- prefs = {}
445
+ prefs: dict[str, list[str]] = {}
430
446
  for arg in args:
431
447
  if ":" not in arg:
432
448
  msg = (
@@ -435,7 +451,15 @@ def _parse_instrument_preferences(args: list[str] | None) -> dict[str, str]:
435
451
  )
436
452
  raise argparse.ArgumentTypeError(msg)
437
453
  product, value = arg.split(":", 1)
438
- prefs[product] = value
454
+ values = [v.strip() for v in value.split(",") if v.strip()]
455
+ for v in values:
456
+ if ":" in v and not _is_pid(v):
457
+ msg = (
458
+ f"Invalid instrument value '{v}'; did you forget to "
459
+ "repeat -i for the next product?"
460
+ )
461
+ raise argparse.ArgumentTypeError(msg)
462
+ prefs.setdefault(product, []).extend(values)
439
463
  return prefs
440
464
 
441
465
 
@@ -450,18 +474,21 @@ def _parse_instrument(s: str) -> tuple[str, str | None]:
450
474
 
451
475
 
452
476
  def _select_instrument(
453
- meta: list[RawMetadata], product: str, pref: str | None = None
477
+ meta: list[RawMetadata], product: str, prefs: list[str] | None = None
454
478
  ) -> Instrument | None:
455
479
  instruments = _get_unique_instruments(meta)
456
480
  if len(instruments) == 0:
457
481
  logging.info("No instruments found")
458
482
  return None
459
- if pref:
460
- matches = [i for i in instruments if _instrument_matches(i, pref)]
461
- if matches:
462
- logging.info("Selected instrument by preference: %s", matches[0].name)
463
- return matches[0]
464
- logging.info("Preferred instrument '%s' not found, falling back", pref)
483
+ if prefs:
484
+ for pref in prefs:
485
+ matches = [i for i in instruments if _instrument_matches(i, pref)]
486
+ if matches:
487
+ logging.info("Selected instrument by preference: %s", matches[0].name)
488
+ return matches[0]
489
+ logging.info(
490
+ "Preferred instruments not found (%s), falling back", ", ".join(prefs)
491
+ )
465
492
  if len(instruments) > 1:
466
493
  logging.info("Multiple instruments found for %s", product)
467
494
  logging.info("Please specify which one to use")
@@ -557,7 +584,7 @@ def _download_product_file(
557
584
 
558
585
 
559
586
  def _shorten_pid(pid: str) -> str:
560
- return pid.split(".")[-1][:8]
587
+ return pid.rsplit(".", maxsplit=1)[-1][:8]
561
588
 
562
589
 
563
590
  def _check_input(files: list) -> None:
@@ -687,9 +714,11 @@ def main() -> None:
687
714
  help=(
688
715
  "Preferred instrument for a product, e.g. 'radar:mira-35', "
689
716
  "'lidar:cl61d', or 'radar:https://hdl.handle.net/<pid>'. The "
690
- "value is either an instrument_id or an instrument PID. Applies "
691
- "both to instrument processing and categorize input. Can be "
692
- "specified multiple times."
717
+ "value is either an instrument_id or an instrument PID. Multiple "
718
+ "preferences can be given as a comma-separated list (first match "
719
+ "wins), e.g. 'lidar:halo-doppler-lidar,cl61d'. Applies both to "
720
+ "instrument processing and categorize input. Can be specified "
721
+ "multiple times."
693
722
  ),
694
723
  default=None,
695
724
  )
@@ -294,7 +294,7 @@ def _read_lines(
294
294
  def _expand_spectrum(m: re.Match) -> bytes:
295
295
  if m[1] == b"ZERO":
296
296
  return b"0;" * 1024
297
- return b";".join([x if x else b"0" for x in m[1].split(b";")])
297
+ return b";".join([x or b"0" for x in m[1].split(b";")])
298
298
 
299
299
 
300
300
  def _read_typ_op4a(content: bytes) -> dict:
@@ -78,6 +78,8 @@ def ws2nc(
78
78
  ws = ClujWS(weather_station_file, site_meta)
79
79
  elif site_meta["name"] == "Falkenberg":
80
80
  ws = FalkenbergWS(weather_station_file, site_meta)
81
+ elif site_meta["name"] == "Potenza":
82
+ ws = PotenzaWS(weather_station_file, site_meta)
81
83
  else:
82
84
  msg = "Unsupported site"
83
85
  raise ValueError(msg)
@@ -291,6 +293,35 @@ class MaidoWS(PalaiseauWS):
291
293
  pass
292
294
 
293
295
 
296
+ class PotenzaWS(PalaiseauWS):
297
+ expected_header_identifiers = (
298
+ "DateTime(yyyy-mm-ddThh:mm:ssZ)",
299
+ "WindspeedMaximumvalue(m/s)",
300
+ "WindspeedAveragevalue(m/s)",
301
+ "Winddirection(degres)",
302
+ "Airtemperature(°C)",
303
+ "Relativehumidity(%)",
304
+ "Pressure(hPa)",
305
+ "Precipitationrate(mm/min)",
306
+ "24-hrcumulatedprecipitation(mm)",
307
+ )
308
+
309
+ keys = (
310
+ "_wind_speed_max",
311
+ "wind_speed",
312
+ "wind_direction",
313
+ "air_temperature",
314
+ "relative_humidity",
315
+ "air_pressure",
316
+ # Skip rainfall as they seem to contain strange values:
317
+ "_rainfall_rate",
318
+ "_rainfall_amount",
319
+ )
320
+
321
+ def convert_rainfall_amount(self) -> None:
322
+ pass
323
+
324
+
294
325
  class BucharestWS(PalaiseauWS):
295
326
  def convert_rainfall_rate(self) -> None:
296
327
  rainfall_rate = self.data["rainfall_rate"][:]
@@ -87,7 +87,7 @@ def save_downsampled_file(
87
87
  dimensions = {"time": len(obj.time), "level": len(obj.data["level"][:])}
88
88
  with output.init_file(file_name, dimensions, obj.data, uuid) as root_group:
89
89
  _augment_global_attributes(root_group)
90
- root_group.cloudnet_file_type = "l3-" + id_mark.split("_")[0]
90
+ root_group.cloudnet_file_type = "l3-" + id_mark.split("_", maxsplit=1)[0]
91
91
  root_group.title = (
92
92
  f"Downsampled {id_mark.capitalize().replace('_', ' of ')} "
93
93
  f"from {obj.dataset.location}"
@@ -263,7 +263,7 @@ def merge_history(
263
263
  def parse_time(line: str) -> datetime.datetime:
264
264
  try:
265
265
  return datetime.datetime.strptime(
266
- line.split(" - ")[0].strip(), "%Y-%m-%d %H:%M:%S %z"
266
+ line.split(" - ", maxsplit=1)[0].strip(), "%Y-%m-%d %H:%M:%S %z"
267
267
  )
268
268
  except ValueError:
269
269
  return datetime.datetime.min.replace(
@@ -414,7 +414,6 @@ ATTRIBUTES = {
414
414
  "cloud_fraction": PlotMeta(
415
415
  cmap="Blues",
416
416
  plot_range=(0, 1),
417
- mask_zeros=True,
418
417
  ),
419
418
  "Tw": PlotMeta(
420
419
  cmap="RdBu_r",
@@ -636,6 +636,8 @@ class Plot2D(Plot):
636
636
  else:
637
637
  min_x, max_x = 0, 24
638
638
  self._mark_gaps(figure_data, min_x=min_x, max_x=max_x)
639
+ if self.sub_plot.variable.name == "cloud_fraction":
640
+ self._data[self._data == 0] = ma.masked
639
641
  if any(
640
642
  key in self.sub_plot.variable.name for key in ("status", "classification")
641
643
  ):
@@ -566,16 +566,16 @@ def l2norm(*args: npt.NDArray | float) -> ma.MaskedArray:
566
566
  The l2 norm.
567
567
 
568
568
  """
569
- arg_cpy: float | npt.NDArray
569
+ arg_copy: float | npt.NDArray
570
570
  ss: float | npt.NDArray = 0
571
571
  for arg in args:
572
572
  if isinstance(arg, ma.MaskedArray):
573
573
  # Raise only non-masked values, not sure if this is needed...
574
- arg_cpy = ma.copy(arg)
575
- arg_cpy[~arg.mask] = arg_cpy[~arg.mask] ** 2
574
+ arg_copy = ma.copy(arg)
575
+ arg_copy[~arg.mask] = arg_copy[~arg.mask] ** 2
576
576
  else:
577
- arg_cpy = arg**2
578
- ss = ss + arg_cpy
577
+ arg_copy = arg**2
578
+ ss = ss + arg_copy
579
579
  return ma.sqrt(ss)
580
580
 
581
581
 
@@ -1,4 +1,4 @@
1
1
  MAJOR = 1
2
2
  MINOR = 92
3
- PATCH = 2
3
+ PATCH = 4
4
4
  __version__ = f"{MAJOR}.{MINOR}.{PATCH}"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cloudnetpy
3
- Version: 1.92.2
3
+ Version: 1.92.4
4
4
  Summary: Python package for Cloudnet processing
5
5
  Author: Simo Tukiainen
6
6
  License: MIT License
File without changes
File without changes
File without changes
File without changes
File without changes