cloudnetpy 1.97.0__tar.gz → 1.97.2__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 (148) hide show
  1. {cloudnetpy-1.97.0/cloudnetpy.egg-info → cloudnetpy-1.97.2}/PKG-INFO +1 -1
  2. {cloudnetpy-1.97.0 → cloudnetpy-1.97.2}/cloudnetpy/disdronator/parsivel.py +39 -0
  3. {cloudnetpy-1.97.0 → cloudnetpy-1.97.2}/cloudnetpy/instruments/kazr.py +26 -3
  4. {cloudnetpy-1.97.0 → cloudnetpy-1.97.2}/cloudnetpy/model_evaluation/metadata.py +3 -1
  5. {cloudnetpy-1.97.0 → cloudnetpy-1.97.2}/cloudnetpy/model_evaluation/products/advance_methods.py +35 -23
  6. {cloudnetpy-1.97.0 → cloudnetpy-1.97.2}/cloudnetpy/model_evaluation/products/grid_methods.py +23 -15
  7. {cloudnetpy-1.97.0 → cloudnetpy-1.97.2}/cloudnetpy/model_evaluation/products/observation_products.py +8 -4
  8. {cloudnetpy-1.97.0 → cloudnetpy-1.97.2}/cloudnetpy/model_evaluation/products/tools.py +11 -0
  9. {cloudnetpy-1.97.0 → cloudnetpy-1.97.2}/cloudnetpy/model_evaluation/tests/unit/test_advance_methods.py +26 -6
  10. {cloudnetpy-1.97.0 → cloudnetpy-1.97.2}/cloudnetpy/model_evaluation/tests/unit/test_grid_methods.py +9 -0
  11. {cloudnetpy-1.97.0 → cloudnetpy-1.97.2}/cloudnetpy/model_evaluation/tests/unit/test_observation_products.py +2 -1
  12. {cloudnetpy-1.97.0 → cloudnetpy-1.97.2}/cloudnetpy/model_evaluation/tests/unit/test_tools.py +14 -0
  13. {cloudnetpy-1.97.0 → cloudnetpy-1.97.2}/cloudnetpy/plotting/plot_meta.py +1 -1
  14. {cloudnetpy-1.97.0 → cloudnetpy-1.97.2}/cloudnetpy/plotting/plotting.py +1 -0
  15. {cloudnetpy-1.97.0 → cloudnetpy-1.97.2}/cloudnetpy/products/lwc.py +15 -8
  16. {cloudnetpy-1.97.0 → cloudnetpy-1.97.2}/cloudnetpy/version.py +1 -1
  17. {cloudnetpy-1.97.0 → cloudnetpy-1.97.2/cloudnetpy.egg-info}/PKG-INFO +1 -1
  18. {cloudnetpy-1.97.0 → cloudnetpy-1.97.2}/LICENSE +0 -0
  19. {cloudnetpy-1.97.0 → cloudnetpy-1.97.2}/MANIFEST.in +0 -0
  20. {cloudnetpy-1.97.0 → cloudnetpy-1.97.2}/README.md +0 -0
  21. {cloudnetpy-1.97.0 → cloudnetpy-1.97.2}/cloudnetpy/__init__.py +0 -0
  22. {cloudnetpy-1.97.0 → cloudnetpy-1.97.2}/cloudnetpy/arm.py +0 -0
  23. {cloudnetpy-1.97.0 → cloudnetpy-1.97.2}/cloudnetpy/categorize/__init__.py +0 -0
  24. {cloudnetpy-1.97.0 → cloudnetpy-1.97.2}/cloudnetpy/categorize/atmos_utils.py +0 -0
  25. {cloudnetpy-1.97.0 → cloudnetpy-1.97.2}/cloudnetpy/categorize/attenuation.py +0 -0
  26. {cloudnetpy-1.97.0 → cloudnetpy-1.97.2}/cloudnetpy/categorize/attenuations/__init__.py +0 -0
  27. {cloudnetpy-1.97.0 → cloudnetpy-1.97.2}/cloudnetpy/categorize/attenuations/gas_attenuation.py +0 -0
  28. {cloudnetpy-1.97.0 → cloudnetpy-1.97.2}/cloudnetpy/categorize/attenuations/liquid_attenuation.py +0 -0
  29. {cloudnetpy-1.97.0 → cloudnetpy-1.97.2}/cloudnetpy/categorize/attenuations/melting_attenuation.py +0 -0
  30. {cloudnetpy-1.97.0 → cloudnetpy-1.97.2}/cloudnetpy/categorize/attenuations/rain_attenuation.py +0 -0
  31. {cloudnetpy-1.97.0 → cloudnetpy-1.97.2}/cloudnetpy/categorize/categorize.py +0 -0
  32. {cloudnetpy-1.97.0 → cloudnetpy-1.97.2}/cloudnetpy/categorize/classify.py +0 -0
  33. {cloudnetpy-1.97.0 → cloudnetpy-1.97.2}/cloudnetpy/categorize/containers.py +0 -0
  34. {cloudnetpy-1.97.0 → cloudnetpy-1.97.2}/cloudnetpy/categorize/disdrometer.py +0 -0
  35. {cloudnetpy-1.97.0 → cloudnetpy-1.97.2}/cloudnetpy/categorize/droplet.py +0 -0
  36. {cloudnetpy-1.97.0 → cloudnetpy-1.97.2}/cloudnetpy/categorize/falling.py +0 -0
  37. {cloudnetpy-1.97.0 → cloudnetpy-1.97.2}/cloudnetpy/categorize/freezing.py +0 -0
  38. {cloudnetpy-1.97.0 → cloudnetpy-1.97.2}/cloudnetpy/categorize/insects.py +0 -0
  39. {cloudnetpy-1.97.0 → cloudnetpy-1.97.2}/cloudnetpy/categorize/lidar.py +0 -0
  40. {cloudnetpy-1.97.0 → cloudnetpy-1.97.2}/cloudnetpy/categorize/melting.py +0 -0
  41. {cloudnetpy-1.97.0 → cloudnetpy-1.97.2}/cloudnetpy/categorize/model.py +0 -0
  42. {cloudnetpy-1.97.0 → cloudnetpy-1.97.2}/cloudnetpy/categorize/mwr.py +0 -0
  43. {cloudnetpy-1.97.0 → cloudnetpy-1.97.2}/cloudnetpy/categorize/radar.py +0 -0
  44. {cloudnetpy-1.97.0 → cloudnetpy-1.97.2}/cloudnetpy/cli.py +0 -0
  45. {cloudnetpy-1.97.0 → cloudnetpy-1.97.2}/cloudnetpy/cloudnetarray.py +0 -0
  46. {cloudnetpy-1.97.0 → cloudnetpy-1.97.2}/cloudnetpy/concat_lib.py +0 -0
  47. {cloudnetpy-1.97.0 → cloudnetpy-1.97.2}/cloudnetpy/constants.py +0 -0
  48. {cloudnetpy-1.97.0 → cloudnetpy-1.97.2}/cloudnetpy/datasource.py +0 -0
  49. {cloudnetpy-1.97.0 → cloudnetpy-1.97.2}/cloudnetpy/disdronator/__init__.py +0 -0
  50. {cloudnetpy-1.97.0 → cloudnetpy-1.97.2}/cloudnetpy/disdronator/lpm.py +0 -0
  51. {cloudnetpy-1.97.0 → cloudnetpy-1.97.2}/cloudnetpy/disdronator/process.py +0 -0
  52. {cloudnetpy-1.97.0 → cloudnetpy-1.97.2}/cloudnetpy/disdronator/rd80.py +0 -0
  53. {cloudnetpy-1.97.0 → cloudnetpy-1.97.2}/cloudnetpy/disdronator/utils.py +0 -0
  54. {cloudnetpy-1.97.0 → cloudnetpy-1.97.2}/cloudnetpy/exceptions.py +0 -0
  55. {cloudnetpy-1.97.0 → cloudnetpy-1.97.2}/cloudnetpy/instruments/__init__.py +0 -0
  56. {cloudnetpy-1.97.0 → cloudnetpy-1.97.2}/cloudnetpy/instruments/arm_ceilo.py +0 -0
  57. {cloudnetpy-1.97.0 → cloudnetpy-1.97.2}/cloudnetpy/instruments/arm_ld.py +0 -0
  58. {cloudnetpy-1.97.0 → cloudnetpy-1.97.2}/cloudnetpy/instruments/arm_utils.py +0 -0
  59. {cloudnetpy-1.97.0 → cloudnetpy-1.97.2}/cloudnetpy/instruments/basta.py +0 -0
  60. {cloudnetpy-1.97.0 → cloudnetpy-1.97.2}/cloudnetpy/instruments/bowtie.py +0 -0
  61. {cloudnetpy-1.97.0 → cloudnetpy-1.97.2}/cloudnetpy/instruments/ceilo.py +0 -0
  62. {cloudnetpy-1.97.0 → cloudnetpy-1.97.2}/cloudnetpy/instruments/ceilometer.py +0 -0
  63. {cloudnetpy-1.97.0 → cloudnetpy-1.97.2}/cloudnetpy/instruments/cl61d.py +0 -0
  64. {cloudnetpy-1.97.0 → cloudnetpy-1.97.2}/cloudnetpy/instruments/cloudnet_instrument.py +0 -0
  65. {cloudnetpy-1.97.0 → cloudnetpy-1.97.2}/cloudnetpy/instruments/copernicus.py +0 -0
  66. {cloudnetpy-1.97.0 → cloudnetpy-1.97.2}/cloudnetpy/instruments/da10.py +0 -0
  67. {cloudnetpy-1.97.0 → cloudnetpy-1.97.2}/cloudnetpy/instruments/dealias.py +0 -0
  68. {cloudnetpy-1.97.0 → cloudnetpy-1.97.2}/cloudnetpy/instruments/disdrometer.py +0 -0
  69. {cloudnetpy-1.97.0 → cloudnetpy-1.97.2}/cloudnetpy/instruments/fd12p.py +0 -0
  70. {cloudnetpy-1.97.0 → cloudnetpy-1.97.2}/cloudnetpy/instruments/galileo.py +0 -0
  71. {cloudnetpy-1.97.0 → cloudnetpy-1.97.2}/cloudnetpy/instruments/hatpro.py +0 -0
  72. {cloudnetpy-1.97.0 → cloudnetpy-1.97.2}/cloudnetpy/instruments/instruments.py +0 -0
  73. {cloudnetpy-1.97.0 → cloudnetpy-1.97.2}/cloudnetpy/instruments/lufft.py +0 -0
  74. {cloudnetpy-1.97.0 → cloudnetpy-1.97.2}/cloudnetpy/instruments/mira.py +0 -0
  75. {cloudnetpy-1.97.0 → cloudnetpy-1.97.2}/cloudnetpy/instruments/mmcr.py +0 -0
  76. {cloudnetpy-1.97.0 → cloudnetpy-1.97.2}/cloudnetpy/instruments/mrr.py +0 -0
  77. {cloudnetpy-1.97.0 → cloudnetpy-1.97.2}/cloudnetpy/instruments/mwr3c.py +0 -0
  78. {cloudnetpy-1.97.0 → cloudnetpy-1.97.2}/cloudnetpy/instruments/mwrlos.py +0 -0
  79. {cloudnetpy-1.97.0 → cloudnetpy-1.97.2}/cloudnetpy/instruments/nc_lidar.py +0 -0
  80. {cloudnetpy-1.97.0 → cloudnetpy-1.97.2}/cloudnetpy/instruments/nc_radar.py +0 -0
  81. {cloudnetpy-1.97.0 → cloudnetpy-1.97.2}/cloudnetpy/instruments/pollyxt.py +0 -0
  82. {cloudnetpy-1.97.0 → cloudnetpy-1.97.2}/cloudnetpy/instruments/radiometrics.py +0 -0
  83. {cloudnetpy-1.97.0 → cloudnetpy-1.97.2}/cloudnetpy/instruments/rain_e_h3.py +0 -0
  84. {cloudnetpy-1.97.0 → cloudnetpy-1.97.2}/cloudnetpy/instruments/rpg.py +0 -0
  85. {cloudnetpy-1.97.0 → cloudnetpy-1.97.2}/cloudnetpy/instruments/rpg_reader.py +0 -0
  86. {cloudnetpy-1.97.0 → cloudnetpy-1.97.2}/cloudnetpy/instruments/toa5.py +0 -0
  87. {cloudnetpy-1.97.0 → cloudnetpy-1.97.2}/cloudnetpy/instruments/vaisala.py +0 -0
  88. {cloudnetpy-1.97.0 → cloudnetpy-1.97.2}/cloudnetpy/instruments/weather_radar.py +0 -0
  89. {cloudnetpy-1.97.0 → cloudnetpy-1.97.2}/cloudnetpy/instruments/weather_station.py +0 -0
  90. {cloudnetpy-1.97.0 → cloudnetpy-1.97.2}/cloudnetpy/metadata.py +0 -0
  91. {cloudnetpy-1.97.0 → cloudnetpy-1.97.2}/cloudnetpy/model_evaluation/__init__.py +0 -0
  92. {cloudnetpy-1.97.0 → cloudnetpy-1.97.2}/cloudnetpy/model_evaluation/file_handler.py +0 -0
  93. {cloudnetpy-1.97.0 → cloudnetpy-1.97.2}/cloudnetpy/model_evaluation/model_metadata.py +0 -0
  94. {cloudnetpy-1.97.0 → cloudnetpy-1.97.2}/cloudnetpy/model_evaluation/plotting/__init__.py +0 -0
  95. {cloudnetpy-1.97.0 → cloudnetpy-1.97.2}/cloudnetpy/model_evaluation/plotting/plot_meta.py +0 -0
  96. {cloudnetpy-1.97.0 → cloudnetpy-1.97.2}/cloudnetpy/model_evaluation/plotting/plot_tools.py +0 -0
  97. {cloudnetpy-1.97.0 → cloudnetpy-1.97.2}/cloudnetpy/model_evaluation/plotting/plotting.py +0 -0
  98. {cloudnetpy-1.97.0 → cloudnetpy-1.97.2}/cloudnetpy/model_evaluation/products/__init__.py +0 -0
  99. {cloudnetpy-1.97.0 → cloudnetpy-1.97.2}/cloudnetpy/model_evaluation/products/model_products.py +0 -0
  100. {cloudnetpy-1.97.0 → cloudnetpy-1.97.2}/cloudnetpy/model_evaluation/products/product_resampling.py +0 -0
  101. {cloudnetpy-1.97.0 → cloudnetpy-1.97.2}/cloudnetpy/model_evaluation/statistics/__init__.py +0 -0
  102. {cloudnetpy-1.97.0 → cloudnetpy-1.97.2}/cloudnetpy/model_evaluation/statistics/statistical_methods.py +0 -0
  103. {cloudnetpy-1.97.0 → cloudnetpy-1.97.2}/cloudnetpy/model_evaluation/tests/__init__.py +0 -0
  104. {cloudnetpy-1.97.0 → cloudnetpy-1.97.2}/cloudnetpy/model_evaluation/tests/e2e/__init__.py +0 -0
  105. {cloudnetpy-1.97.0 → cloudnetpy-1.97.2}/cloudnetpy/model_evaluation/tests/e2e/conftest.py +0 -0
  106. {cloudnetpy-1.97.0 → cloudnetpy-1.97.2}/cloudnetpy/model_evaluation/tests/e2e/process_cf/__init__.py +0 -0
  107. {cloudnetpy-1.97.0 → cloudnetpy-1.97.2}/cloudnetpy/model_evaluation/tests/e2e/process_cf/main.py +0 -0
  108. {cloudnetpy-1.97.0 → cloudnetpy-1.97.2}/cloudnetpy/model_evaluation/tests/e2e/process_cf/tests.py +0 -0
  109. {cloudnetpy-1.97.0 → cloudnetpy-1.97.2}/cloudnetpy/model_evaluation/tests/e2e/process_iwc/__init__.py +0 -0
  110. {cloudnetpy-1.97.0 → cloudnetpy-1.97.2}/cloudnetpy/model_evaluation/tests/e2e/process_iwc/main.py +0 -0
  111. {cloudnetpy-1.97.0 → cloudnetpy-1.97.2}/cloudnetpy/model_evaluation/tests/e2e/process_iwc/tests.py +0 -0
  112. {cloudnetpy-1.97.0 → cloudnetpy-1.97.2}/cloudnetpy/model_evaluation/tests/e2e/process_lwc/__init__.py +0 -0
  113. {cloudnetpy-1.97.0 → cloudnetpy-1.97.2}/cloudnetpy/model_evaluation/tests/e2e/process_lwc/main.py +0 -0
  114. {cloudnetpy-1.97.0 → cloudnetpy-1.97.2}/cloudnetpy/model_evaluation/tests/e2e/process_lwc/tests.py +0 -0
  115. {cloudnetpy-1.97.0 → cloudnetpy-1.97.2}/cloudnetpy/model_evaluation/tests/unit/__init__.py +0 -0
  116. {cloudnetpy-1.97.0 → cloudnetpy-1.97.2}/cloudnetpy/model_evaluation/tests/unit/conftest.py +0 -0
  117. {cloudnetpy-1.97.0 → cloudnetpy-1.97.2}/cloudnetpy/model_evaluation/tests/unit/test_model_products.py +0 -0
  118. {cloudnetpy-1.97.0 → cloudnetpy-1.97.2}/cloudnetpy/model_evaluation/tests/unit/test_plot_tools.py +0 -0
  119. {cloudnetpy-1.97.0 → cloudnetpy-1.97.2}/cloudnetpy/model_evaluation/tests/unit/test_plotting.py +0 -0
  120. {cloudnetpy-1.97.0 → cloudnetpy-1.97.2}/cloudnetpy/model_evaluation/tests/unit/test_statistical_methods.py +0 -0
  121. {cloudnetpy-1.97.0 → cloudnetpy-1.97.2}/cloudnetpy/model_evaluation/utils.py +0 -0
  122. {cloudnetpy-1.97.0 → cloudnetpy-1.97.2}/cloudnetpy/output.py +0 -0
  123. {cloudnetpy-1.97.0 → cloudnetpy-1.97.2}/cloudnetpy/plotting/__init__.py +0 -0
  124. {cloudnetpy-1.97.0 → cloudnetpy-1.97.2}/cloudnetpy/products/__init__.py +0 -0
  125. {cloudnetpy-1.97.0 → cloudnetpy-1.97.2}/cloudnetpy/products/classification.py +0 -0
  126. {cloudnetpy-1.97.0 → cloudnetpy-1.97.2}/cloudnetpy/products/cod.py +0 -0
  127. {cloudnetpy-1.97.0 → cloudnetpy-1.97.2}/cloudnetpy/products/der.py +0 -0
  128. {cloudnetpy-1.97.0 → cloudnetpy-1.97.2}/cloudnetpy/products/drizzle.py +0 -0
  129. {cloudnetpy-1.97.0 → cloudnetpy-1.97.2}/cloudnetpy/products/drizzle_error.py +0 -0
  130. {cloudnetpy-1.97.0 → cloudnetpy-1.97.2}/cloudnetpy/products/drizzle_tools.py +0 -0
  131. {cloudnetpy-1.97.0 → cloudnetpy-1.97.2}/cloudnetpy/products/epsilon_lidar.py +0 -0
  132. {cloudnetpy-1.97.0 → cloudnetpy-1.97.2}/cloudnetpy/products/epsilon_radar.py +0 -0
  133. {cloudnetpy-1.97.0 → cloudnetpy-1.97.2}/cloudnetpy/products/ier.py +0 -0
  134. {cloudnetpy-1.97.0 → cloudnetpy-1.97.2}/cloudnetpy/products/iwc.py +0 -0
  135. {cloudnetpy-1.97.0 → cloudnetpy-1.97.2}/cloudnetpy/products/iwc_weather_radar.py +0 -0
  136. {cloudnetpy-1.97.0 → cloudnetpy-1.97.2}/cloudnetpy/products/mie_lu_tables.nc +0 -0
  137. {cloudnetpy-1.97.0 → cloudnetpy-1.97.2}/cloudnetpy/products/mwr_tools.py +0 -0
  138. {cloudnetpy-1.97.0 → cloudnetpy-1.97.2}/cloudnetpy/products/product_tools.py +0 -0
  139. {cloudnetpy-1.97.0 → cloudnetpy-1.97.2}/cloudnetpy/py.typed +0 -0
  140. {cloudnetpy-1.97.0 → cloudnetpy-1.97.2}/cloudnetpy/utils.py +0 -0
  141. {cloudnetpy-1.97.0 → cloudnetpy-1.97.2}/cloudnetpy.egg-info/SOURCES.txt +0 -0
  142. {cloudnetpy-1.97.0 → cloudnetpy-1.97.2}/cloudnetpy.egg-info/dependency_links.txt +0 -0
  143. {cloudnetpy-1.97.0 → cloudnetpy-1.97.2}/cloudnetpy.egg-info/entry_points.txt +0 -0
  144. {cloudnetpy-1.97.0 → cloudnetpy-1.97.2}/cloudnetpy.egg-info/requires.txt +0 -0
  145. {cloudnetpy-1.97.0 → cloudnetpy-1.97.2}/cloudnetpy.egg-info/top_level.txt +0 -0
  146. {cloudnetpy-1.97.0 → cloudnetpy-1.97.2}/docs/source/conf.py +0 -0
  147. {cloudnetpy-1.97.0 → cloudnetpy-1.97.2}/pyproject.toml +0 -0
  148. {cloudnetpy-1.97.0 → cloudnetpy-1.97.2}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cloudnetpy
3
- Version: 1.97.0
3
+ Version: 1.97.2
4
4
  Summary: Python package for Cloudnet processing
5
5
  Author: Simo Tukiainen
6
6
  License: MIT License
@@ -237,6 +237,32 @@ LEIPZIG_KEYS = {
237
237
  FLOAT_KEYS = {1, 2, 7, 16, 17, 24, 30, 31, 33, 34, 35}
238
238
  INT_KEYS = {3, 4, 8, 9, 10, 11, 12, 18, 25, 26, 27, 28, 60}
239
239
 
240
+ KOLSASS_TELEGRAM: list[str | int | None] = [
241
+ "%Y-%m-%d %H:%M:%S",
242
+ 18,
243
+ 20,
244
+ 25,
245
+ 1,
246
+ 2,
247
+ 7,
248
+ 9,
249
+ 35,
250
+ 11,
251
+ 90,
252
+ 91,
253
+ 93,
254
+ 13,
255
+ 14,
256
+ 15,
257
+ 16,
258
+ 17,
259
+ 10,
260
+ 12,
261
+ 26,
262
+ 27,
263
+ 28,
264
+ ]
265
+
240
266
 
241
267
  def _read_lines(
242
268
  telegram: Sequence[str | int | None],
@@ -387,6 +413,17 @@ def _read_granada(filename: str | PathLike) -> ParsivelOutput:
387
413
  return _read_lines(telegram, content, b",", b".")
388
414
 
389
415
 
416
+ def _read_kolsass(filename: str | PathLike) -> ParsivelOutput:
417
+ with open(filename, "rb") as f:
418
+ _header = f.readline()
419
+ content = f.read()
420
+ # Fix "0-9" to "-9" (e.g. 2025-01-01)
421
+ content = re.sub(rb",0-(\d),", rb",-\1,", content)
422
+ # Remove decimal part from integers (e.g. 2025-01-02)
423
+ content = re.sub(rb"(\d+)\.0,", rb"\1,", content, flags=re.MULTILINE)
424
+ return _read_lines(KOLSASS_TELEGRAM, content, b",", b".")
425
+
426
+
390
427
  def _read_headerless(
391
428
  filename: str | PathLike,
392
429
  telegram: Sequence[int | str | None],
@@ -516,6 +553,8 @@ def _read_parsivel(
516
553
  head = f.read(50)
517
554
  if head.startswith(b"datetime_utc;"):
518
555
  return _read_pyatmoslogger(filename)
556
+ if head.startswith(b"datetime_utc,sensor status,sensor time,"):
557
+ return _read_kolsass(filename)
519
558
  if head.startswith(b"Date;Time;"):
520
559
  return _read_asdo(filename)
521
560
  if head.startswith(b'"TOA5"'):
@@ -164,10 +164,10 @@ class Kazr(CloudnetInstrument):
164
164
 
165
165
  def read_files(self) -> None:
166
166
  """Reads and concatenates all input files."""
167
+ with netCDF4.Dataset(self._find_reference_file()) as nc:
168
+ self._init_metadata(nc)
167
169
  for file in self.files:
168
170
  with netCDF4.Dataset(file) as nc:
169
- if not self.keymap:
170
- self._init_metadata(nc)
171
171
  try:
172
172
  self._read_file(nc)
173
173
  except (KeyError, ValueError) as err:
@@ -350,6 +350,24 @@ class Kazr(CloudnetInstrument):
350
350
  msg = "All radar data are masked"
351
351
  raise RadarDataError(msg)
352
352
 
353
+ def _find_reference_file(self) -> Path:
354
+ """Returns the first file using the range grid with the most profiles."""
355
+ n_profiles: dict[int, int] = {}
356
+ first_file: dict[int, Path] = {}
357
+ for file in self.files:
358
+ with netCDF4.Dataset(file) as nc:
359
+ if "range" not in nc.dimensions or "time" not in nc.dimensions:
360
+ continue
361
+ n_range = nc.dimensions["range"].size
362
+ n_profiles[n_range] = (
363
+ n_profiles.get(n_range, 0) + nc.dimensions["time"].size
364
+ )
365
+ first_file.setdefault(n_range, file)
366
+ if not first_file:
367
+ msg = "No valid KAZR files found"
368
+ raise ValidTimeStampError(msg)
369
+ return first_file[max(n_profiles, key=lambda k: n_profiles[k])]
370
+
353
371
  def _init_metadata(self, nc: netCDF4.Dataset) -> None:
354
372
  if "reflectivity" in nc.variables:
355
373
  self.keymap = KEYMAP_CFR
@@ -366,6 +384,7 @@ class Kazr(CloudnetInstrument):
366
384
  if bias is not None:
367
385
  self.offset_applied = _parse_bias(bias)
368
386
  self.serial_number = getattr(nc, "serial_number", None) or None
387
+ self.date = _parse_time_units(nc["time"].units).date()
369
388
  self.append_data(np.array(nc["range"][:], dtype=float), "range")
370
389
  self.append_data(0.0, "zenith_angle")
371
390
  self.append_data(self._read_frequency(nc), "radar_frequency")
@@ -421,7 +440,11 @@ class Kazr(CloudnetInstrument):
421
440
  @staticmethod
422
441
  def _read_frequency(nc: netCDF4.Dataset) -> float:
423
442
  if "frequency" in nc.variables:
424
- return float(np.mean(nc["frequency"][:]) / 1e9) # Hz -> GHz
443
+ frequency = ma.mean(nc["frequency"][:])
444
+ if not ma.is_masked(frequency) and np.isfinite(frequency):
445
+ return float(frequency / 1e9) # Hz -> GHz
446
+ # E.g. bnfkazr2cfrgeqcM1.b1 files have only a fill value
447
+ logging.warning("Radar frequency missing from file, using default")
425
448
  value = _parse_global_attribute(nc, "radar_operating_frequency")
426
449
  if value is not None:
427
450
  return value
@@ -171,7 +171,9 @@ REGRID_PRODUCT_ATTRIBUTES = {
171
171
  "This variable is the observed mean ice water content derived from radar\n"
172
172
  "reflectivity factor averaged onto the model grid with height and time.\n"
173
173
  "The formula has been applied where the categorization data has\n"
174
- "diagnosed that the radar echo is due to ice."
174
+ "diagnosed that the radar echo is due to ice; pixels without ice count\n"
175
+ "as zero and unreliable retrievals (e.g. rain) are excluded, so the\n"
176
+ "value is a grid-box mean comparable to the model ice water content."
175
177
  ),
176
178
  dimensions=("time", "level"),
177
179
  ),
@@ -6,6 +6,7 @@ from numpy import ma
6
6
 
7
7
  import cloudnetpy.utils as cl_tools
8
8
  from cloudnetpy.constants import T_FREEZING
9
+ from cloudnetpy.exceptions import InvalidSourceFileError
9
10
  from cloudnetpy.model_evaluation.model_metadata import MODEL_PREFIX
10
11
  from cloudnetpy.model_evaluation.products.model_products import ModelManager
11
12
  from cloudnetpy.model_evaluation.products.observation_products import ObservationManager
@@ -15,6 +16,8 @@ from cloudnetpy.products.product_tools import (
15
16
  z_to_iwc,
16
17
  )
17
18
 
19
+ MAX_WIND_SHEAR = 20.0 # m s-1 km-1
20
+
18
21
 
19
22
  class AdvanceProductMethods:
20
23
  """Class that adds advance methods of product to nc-file.
@@ -53,7 +56,8 @@ class AdvanceProductMethods:
53
56
  t_screened = self.remove_extra_levels(temperature - T_FREEZING)
54
57
  iwc, lwc = (self._model_obj.get_water_content(var) for var in ["iwc", "lwc"])
55
58
  coeffs = self.set_frequency_parameters()
56
- z_sen = self.fit_z_sensitivity(h)
59
+ # Same dielectric factor scaling of Z as in the IWC product.
60
+ z_sen = self.fit_z_sensitivity(h) + 10 * np.log10(coeffs.K2liquid0 / 0.93)
57
61
  cf_filtered = self.filter_high_iwc_low_cf(cf, iwc, lwc)
58
62
  cloud_iwc, ice_ind = self.find_ice_in_clouds(cf_filtered, iwc, lwc)
59
63
  variance_iwc = self.iwc_variance(h, ice_ind)
@@ -102,15 +106,14 @@ class AdvanceProductMethods:
102
106
  if self._obs_obj.z_sensitivity is None:
103
107
  msg = "No z_sensitivity in observation file"
104
108
  raise ValueError(msg)
105
- if self._obs_obj.height is None:
106
- msg = "No height in observation file"
107
- raise ValueError(msg)
109
+ if self._obs_obj.height_agl is None:
110
+ msg = "Observation file is missing height or altitude"
111
+ raise InvalidSourceFileError(msg)
108
112
  # Interpolate at the model level heights: rebinning assumes evenly
109
113
  # spaced levels, which maps stretched model levels to wrong heights.
110
- obs_height = self._obs_obj.height_agl
111
- if obs_height is None:
112
- obs_height = self._obs_obj.height
113
- obs_height = np.asarray(ma.filled(obs_height, np.nan), dtype=float)
114
+ obs_height = np.asarray(
115
+ ma.filled(self._obs_obj.height_agl, np.nan), dtype=float
116
+ )
114
117
  z_obs = np.asarray(ma.filled(self._obs_obj.z_sensitivity, np.nan), dtype=float)
115
118
  is_valid = np.isfinite(obs_height) & np.isfinite(z_obs)
116
119
  model_height = np.asarray(ma.filled(h, np.nan), dtype=float)
@@ -167,36 +170,45 @@ class AdvanceProductMethods:
167
170
  v = self._model_obj.getvar("vwind")
168
171
  u = self.remove_extra_levels(u)
169
172
  v = self.remove_extra_levels(v)
170
- w_shear = self.calculate_wind_shear(self._model_obj.wind, u, v, height)
173
+ w_shear = self.calculate_wind_shear(u, v, height)
171
174
  return self.calculate_variance_iwc(w_shear, ice_ind)
172
175
 
173
176
  def calculate_variance_iwc(
174
177
  self, w_shear: npt.NDArray, ice_ind: tuple
175
178
  ) -> npt.NDArray:
179
+ """Fractional variance of IWC within a grid box (Hogan and Illingworth
180
+ 2003, eq. 11), with the horizontal resolution in km and the wind shear
181
+ in m s-1 km-1. The shear is capped and the constant is -1.03 as in the
182
+ original Cloudnet code (the published equation has -0.93).
183
+ """
184
+ shear = np.minimum(w_shear[ice_ind], MAX_WIND_SHEAR)
176
185
  return 10 ** (
177
- 0.3 * np.log10(self._model_obj.resolution_h)
178
- - 0.04 * w_shear[ice_ind]
179
- - 1.03
186
+ 0.3 * np.log10(self._model_obj.resolution_h) - 0.04 * shear - 1.03
180
187
  )
181
188
 
182
189
  @staticmethod
183
190
  def calculate_wind_shear(
184
- wind: npt.NDArray,
185
191
  u: npt.NDArray,
186
192
  v: npt.NDArray,
187
193
  height: npt.NDArray,
188
194
  ) -> npt.NDArray:
189
- grand_winds = []
190
- for w in (wind, u, v):
195
+ """Magnitude of the vertical shear of the horizontal wind (m s-1 km-1)
196
+ from (time, level) arrays.
197
+ """
198
+ height_km = height / 1000
199
+ gradients = []
200
+ for w in (u, v):
191
201
  grad_w = np.zeros(w.shape)
192
- grad_w[0, :] = (w[1, :] - w[0, :]) / (height[1, :] - height[0, :])
193
- grad_w[1:-1, :] = (w[2:, :] - w[:-2, :]) / (height[2:, :] - height[:-2, :])
194
- grad_w[-1, :] = (w[-1, :] - w[-2, :]) / (height[-1, :] - height[-2, :])
195
- grand_winds.append(grad_w)
196
-
197
- w_shear = np.sqrt(np.power(grand_winds[1], 2) + np.power(grand_winds[-1], 2))
198
- w_shear[grand_winds[0] < 0] = 0 - w_shear[grand_winds[0] < 0]
199
- return w_shear
202
+ grad_w[:, 0] = (w[:, 1] - w[:, 0]) / (height_km[:, 1] - height_km[:, 0])
203
+ grad_w[:, 1:-1] = (w[:, 2:] - w[:, :-2]) / (
204
+ height_km[:, 2:] - height_km[:, :-2]
205
+ )
206
+ grad_w[:, -1] = (w[:, -1] - w[:, -2]) / (
207
+ height_km[:, -1] - height_km[:, -2]
208
+ )
209
+ gradients.append(grad_w)
210
+
211
+ return np.sqrt(np.power(gradients[0], 2) + np.power(gradients[1], 2))
200
212
 
201
213
  @staticmethod
202
214
  def calculate_iwc_distribution(
@@ -2,7 +2,7 @@ import numpy as np
2
2
  import numpy.typing as npt
3
3
  from numpy import ma
4
4
 
5
- from cloudnetpy import utils
5
+ from cloudnetpy.exceptions import InvalidSourceFileError
6
6
  from cloudnetpy.model_evaluation.products import tools as tl
7
7
  from cloudnetpy.model_evaluation.products.model_products import ModelManager
8
8
  from cloudnetpy.model_evaluation.products.observation_products import ObservationManager
@@ -24,7 +24,12 @@ class ProductGrid:
24
24
  self._obs_obj = obs_obj
25
25
  self._date = obs_obj.date
26
26
  self._obs_time = tl.time2datetime(obs_obj.time, self._date)
27
- self._obs_height = obs_obj.data["height"][:]
27
+ # Model heights are above ground; the observation file's height is
28
+ # above mean sea level.
29
+ if obs_obj.height_agl is None:
30
+ msg = "Observation file is missing height or altitude"
31
+ raise InvalidSourceFileError(msg)
32
+ self._obs_height = np.asarray(obs_obj.height_agl)
28
33
  self._obs_data = obs_obj.data[obs_obj.product][:]
29
34
  self.model_obj = model_obj
30
35
  self._model_time = model_obj.time
@@ -34,21 +39,22 @@ class ProductGrid:
34
39
  ma.array(model_obj.wind),
35
40
  1,
36
41
  )
37
- time_steps = utils.binvec(self._model_time)
38
- self._time_steps = tl.time2datetime(time_steps, self._date)
42
+ self._time_windows = [
43
+ tuple(tl.time2datetime(np.array(edges), self._date))
44
+ for edges in tl.time_windows(self._model_time)
45
+ ]
39
46
  self._generate_downsample_product()
40
47
 
41
48
  def _generate_downsample_product(self) -> None:
42
49
  """Downsampling products are generated with different averaging methods
43
50
  for a selected size of model time-height window.
51
+
52
+ Grid cells without any observation pixels stay masked.
44
53
  """
45
54
  product_dict, product_adv_dict = self._get_method_storage()
46
55
  model_t = tl.time2datetime(self._model_time, self._date)
47
- for i in range(len(self._time_steps) - 1):
48
- x_ind = tl.get_1d_indices(
49
- (self._time_steps[i], self._time_steps[i + 1]),
50
- self._obs_time,
51
- )
56
+ for i, window in enumerate(self._time_windows):
57
+ x_ind = tl.get_1d_indices(window, self._obs_time)
52
58
  y_steps = tl.rebin_edges(self._model_height[i])
53
59
  for j in range(len(y_steps) - 1):
54
60
  x_ind_adv = tl.get_adv_indices(
@@ -91,19 +97,21 @@ class ProductGrid:
91
97
 
92
98
  def _cf_method_storage(self) -> tuple[dict, dict]:
93
99
  cf_dict = {
94
- "cf_V": ma.zeros(self._model_height.shape),
95
- "cf_A": ma.zeros(self._model_height.shape),
100
+ "cf_V": ma.masked_all(self._model_height.shape),
101
+ "cf_A": ma.masked_all(self._model_height.shape),
96
102
  }
97
103
  cf_adv_dict = {
98
- "cf_V_adv": ma.zeros(self._model_height.shape),
99
- "cf_A_adv": ma.zeros(self._model_height.shape),
104
+ "cf_V_adv": ma.masked_all(self._model_height.shape),
105
+ "cf_A_adv": ma.masked_all(self._model_height.shape),
100
106
  }
101
107
  return cf_dict, cf_adv_dict
102
108
 
103
109
  def _product_method_storage(self) -> tuple[dict, dict]:
104
- product_dict = {f"{self._obs_obj.product}": ma.zeros(self._model_height.shape)}
110
+ product_dict = {
111
+ f"{self._obs_obj.product}": ma.masked_all(self._model_height.shape)
112
+ }
105
113
  product_adv_dict = {
106
- f"{self._obs_obj.product}_adv": ma.zeros(self._model_height.shape),
114
+ f"{self._obs_obj.product}_adv": ma.masked_all(self._model_height.shape),
107
115
  }
108
116
  return product_dict, product_adv_dict
109
117
 
@@ -103,10 +103,14 @@ class ObservationManager(DataSource):
103
103
  """Keeps only reliable ice water content retrievals.
104
104
 
105
105
  Status 1 is a reliable retrieval and status 3 is a retrieval with the
106
- radar corrected for liquid, rain and melting attenuation; everything
107
- else (uncorrected attenuation, lidar-only, rain) is masked out.
106
+ radar corrected for liquid, rain and melting attenuation. Status 0 (no
107
+ ice) is set to zero so that the regridded value is a grid-box mean,
108
+ comparable to the model. Everything else (uncorrected attenuation,
109
+ lidar-only, rain) is masked out.
108
110
  """
109
111
  iwc = self.getvar("iwc")
110
- iwc_status = self.getvar("iwc_retrieval_status")
111
- iwc[~np.isin(iwc_status, (1, 3))] = ma.masked
112
+ status = ma.filled(self.getvar("iwc_retrieval_status"), -1)
113
+ no_ice = status == 0
114
+ invalid = (ma.getmaskarray(iwc) & ~no_ice) | ~np.isin(status, (0, 1, 3))
115
+ iwc = ma.array(ma.filled(iwc, 0), mask=invalid)
112
116
  self.append_data(iwc, "iwc")
@@ -13,6 +13,17 @@ def time2datetime(time: npt.NDArray, date: datetime.datetime) -> npt.NDArray:
13
13
  return np.asarray([date + timedelta(hours=float(t)) for t in time])
14
14
 
15
15
 
16
+ def time_windows(time: npt.NDArray) -> list[tuple[float, float]]:
17
+ """Averaging window around each model time step.
18
+
19
+ Each window is +/- half the smallest time step, so a gap in the time
20
+ vector (e.g. a missing forecast run) does not widen the windows.
21
+ """
22
+ time = np.asarray(time, dtype=float)
23
+ half = np.min(np.diff(time)) / 2 if time.size > 1 else 0.5
24
+ return [(t - half, t + half) for t in time]
25
+
26
+
16
27
  def rebin_edges(arr: npt.NDArray) -> npt.NDArray:
17
28
  """Rebins array bins by half and adds boundaries."""
18
29
  new_arr = [(arr[i] + arr[i + 1]) / 2 for i in range(len(arr) - 1)]
@@ -229,19 +229,39 @@ def test_calculate_variance_iwc(obs_file, model_file) -> None:
229
229
  testing.assert_array_almost_equal(result, expected)
230
230
 
231
231
 
232
+ def test_calculate_variance_iwc_shear_cap(obs_file, model_file) -> None:
233
+ obs = ObservationManager(PRODUCT, str(obs_file))
234
+ model = ModelManager(str(model_file), MODEL, PRODUCT)
235
+ advance = AdvanceProductMethods(model, obs)
236
+ shear = np.array([[20.0, 50.0]])
237
+ result = advance.calculate_variance_iwc(shear, (np.array([0, 0]), np.array([0, 1])))
238
+ assert result[0] == result[1]
239
+
240
+
232
241
  def test_calculate_wind_shear(obs_file, model_file) -> None:
233
242
  obs = ObservationManager(PRODUCT, str(obs_file))
234
243
  model = ModelManager(str(model_file), MODEL, PRODUCT)
235
244
  advance = AdvanceProductMethods(model, obs)
236
- u = np.array([[1, 2, 0, 1], [-1, 0, 1, -1], [1, 0, 1, -1]])
237
- v = np.array([[1, 0, 1, -1], [1, 2, -1, 0], [1, 2, 0, 1]])
238
- wind = np.sqrt(np.power(u, 2) + np.power(v, 2))
239
- height = np.array([[1, 1, 1, 1], [2, 2, 2, 2], [3, 3, 3, 3]])
240
- expected = np.array([[2, 2.83, 2.24, -2.24], [0, 1.41, 0.71, 1.41], [2, 0, -1, 1]])
241
- result = advance.calculate_wind_shear(wind, u, v, height)
245
+ # (time, level) arrays; shear is the vertical gradient in m s-1 km-1
246
+ u = np.array([[1, 2, 0, 1], [-1, 0, 1, -1], [1, 0, 1, -1]]).T
247
+ v = np.array([[1, 0, 1, -1], [1, 2, -1, 0], [1, 2, 0, 1]]).T
248
+ height = np.array([[1, 1, 1, 1], [2, 2, 2, 2], [3, 3, 3, 3]]).T * 1000
249
+ expected = np.array([[2, 2.83, 2.24, 2.24], [0, 1.41, 0.71, 1.41], [2, 0, 1, 1]]).T
250
+ result = advance.calculate_wind_shear(u, v, height)
242
251
  testing.assert_array_almost_equal(np.round(result, 2), expected)
243
252
 
244
253
 
254
+ def test_calculate_wind_shear_constant_height_in_time(obs_file, model_file) -> None:
255
+ obs = ObservationManager(PRODUCT, str(obs_file))
256
+ model = ModelManager(str(model_file), MODEL, PRODUCT)
257
+ advance = AdvanceProductMethods(model, obs)
258
+ u = np.array([[1.0, 3.0, 4.0], [1.0, 3.0, 4.0]])
259
+ v = np.zeros_like(u)
260
+ height = np.array([[0.0, 1000.0, 2000.0], [0.0, 1000.0, 2000.0]])
261
+ result = advance.calculate_wind_shear(u, v, height)
262
+ testing.assert_array_almost_equal(result, [[2, 1.5, 1], [2, 1.5, 1]])
263
+
264
+
245
265
  def test_calculate_iwc_distribution(obs_file, model_file) -> None:
246
266
  obs = ObservationManager(PRODUCT, str(obs_file))
247
267
  model = ModelManager(str(model_file), MODEL, PRODUCT)
@@ -2,6 +2,7 @@ import numpy as np
2
2
  import pytest
3
3
  from numpy import ma, testing
4
4
 
5
+ from cloudnetpy.exceptions import InvalidSourceFileError
5
6
  from cloudnetpy.model_evaluation.products.grid_methods import ProductGrid
6
7
  from cloudnetpy.model_evaluation.products.model_products import ModelManager
7
8
  from cloudnetpy.model_evaluation.products.observation_products import ObservationManager
@@ -42,6 +43,14 @@ def test_generate_regrid_product(model_file, obs_file, product, variables) -> No
42
43
  assert var in model.data
43
44
 
44
45
 
46
+ def test_missing_height_agl_raises(model_file, obs_file) -> None:
47
+ obs = ObservationManager(PRODUCT, str(obs_file))
48
+ model = ModelManager(str(model_file), MODEL, PRODUCT)
49
+ obs.height_agl = None
50
+ with pytest.raises(InvalidSourceFileError):
51
+ ProductGrid(model, obs)
52
+
53
+
45
54
  @pytest.mark.parametrize("key, value", [("iwc", 1), ("lwc", 1), ("cf", 2)])
46
55
  def test_get_method_storage(key, value, model_file, obs_file) -> None:
47
56
  obs = ObservationManager(key, str(obs_file))
@@ -113,6 +113,7 @@ def test_mask_iwc(obs_file) -> None:
113
113
  obs = ObservationManager("iwc", str(obs_file))
114
114
  iwc_status = obs.getvar("iwc_retrieval_status")
115
115
  expected = ma.copy(obs.getvar("iwc"))
116
- expected[~np.isin(iwc_status, (1, 3))] = ma.masked
116
+ expected[iwc_status == 0] = 0
117
+ expected[~np.isin(iwc_status, (0, 1, 3))] = ma.masked
117
118
  obs._mask_iwc()
118
119
  testing.assert_array_almost_equal(expected, obs.data["iwc"][:])
@@ -128,3 +128,17 @@ def test_obs_windows_size_first_index() -> None:
128
128
  result = tools.get_obs_window_size(ind_x, ind_y)
129
129
  assert result is not None
130
130
  testing.assert_almost_equal(result, (1, 1))
131
+
132
+
133
+ def test_time_windows_even_spacing() -> None:
134
+ result = tools.time_windows(np.array([0.0, 1.0, 2.0]))
135
+ testing.assert_array_almost_equal(result, [(-0.5, 0.5), (0.5, 1.5), (1.5, 2.5)])
136
+
137
+
138
+ def test_time_windows_gap() -> None:
139
+ result = tools.time_windows(np.array([0.0, 1.0, 24.0]))
140
+ testing.assert_array_almost_equal(result, [(-0.5, 0.5), (0.5, 1.5), (23.5, 24.5)])
141
+
142
+
143
+ def test_time_windows_single() -> None:
144
+ testing.assert_array_almost_equal(tools.time_windows(np.array([3.0])), [(2.5, 3.5)])
@@ -668,7 +668,7 @@ ATTRIBUTES = {
668
668
  ),
669
669
  "lwc_error": PlotMeta(
670
670
  cmap="RdYlGn_r",
671
- plot_range=(0, 2),
671
+ plot_range=(0, 100),
672
672
  ),
673
673
  "lwc_retrieval_status": PlotMeta(
674
674
  clabel=_CLABEL["lwc_retrieval_status"],
@@ -464,6 +464,7 @@ class Plot:
464
464
  "air_pressure": (multiply, 0.01, "hPa"),
465
465
  "relative_humidity": (multiply, 100, "%"),
466
466
  "rh": (multiply, 100, "%"),
467
+ "lwc_error": (multiply, 100, "%"),
467
468
  "rainfall_amount": (multiply, con.M_TO_MM, "mm"),
468
469
  "snowfall_amount": (multiply, con.M_TO_MM, "mm"),
469
470
  "precipitation_amount": (multiply, con.M_TO_MM, "mm"),
@@ -117,7 +117,7 @@ class LwcSource(DataSource):
117
117
  ) -> None:
118
118
  self.append_data(lwc, "lwc", units="kg m-3")
119
119
  self.append_data(status, "lwc_retrieval_status")
120
- self.append_data(error, "lwc_error", units="dB")
120
+ self.append_data(error, "lwc_error", units="1")
121
121
 
122
122
  @staticmethod
123
123
  def _get_atmosphere(
@@ -348,15 +348,16 @@ class LwcError:
348
348
  return self._fill_error_array(combined_error)
349
349
 
350
350
  def _calc_lwc_relative_error(self) -> npt.NDArray:
351
+ # Half a range gate uncertainty in cloud base and top height
351
352
  lwc_gradient = self._calc_lwc_gradient()
352
353
  error = lwc_gradient / self.lwc / 2
353
354
  return self._limit_error(error, 5)
354
355
 
355
356
  def _calc_lwc_gradient(self) -> npt.NDArray:
357
+ """Returns absolute vertical gradient of LWC per range gate."""
356
358
  if not isinstance(self.lwc, ma.MaskedArray):
357
359
  self.lwc = ma.array(self.lwc)
358
- gradient_elements = np.gradient(self.lwc.filled(0))
359
- return utils.l2norm(*gradient_elements)
360
+ return np.abs(np.gradient(self.lwc.filled(0), axis=1))
360
361
 
361
362
  def _calc_lwp_relative_error(self) -> npt.NDArray:
362
363
  err = self.lwc_source.lwp_error
@@ -412,9 +413,15 @@ COMMENTS = {
412
413
  " liquid layers is difficult to ascertain."
413
414
  ),
414
415
  "lwc_error": (
415
- "This variable is an estimate of the random error in liquid water content\n"
416
- "due to the uncertainty in the microwave radiometer liquid water path\n"
417
- "retrieval and the uncertainty in cloud base and/or cloud top height."
416
+ "This variable is an estimate of the one-standard-deviation random error\n"
417
+ "in liquid water content, expressed as a fraction of the liquid water\n"
418
+ "content. It combines the relative uncertainty in the microwave radiometer\n"
419
+ "liquid water path retrieval (limited to 10) and the uncertainty in cloud\n"
420
+ "base and cloud top height, taken as half a range gate and estimated from\n"
421
+ "the vertical gradient of liquid water content (limited to 5). If the liquid\n"
422
+ "layer is detected by the lidar only, there is the potential for cloud top\n"
423
+ "height to be underestimated. The error contribution from using the model\n"
424
+ "temperature and pressure at cloud base is assumed to be negligible."
418
425
  ),
419
426
  "lwc_retrieval_status": (
420
427
  "This variable describes whether a retrieval was performed for each pixel,\n"
@@ -469,9 +476,9 @@ LWC_ATTRIBUTES = {
469
476
  dimensions=("time", "height"),
470
477
  ),
471
478
  "lwc_error": MetaData(
472
- long_name="Random error in liquid water content, one standard deviation",
479
+ long_name="Relative random error in liquid water content",
473
480
  comment=COMMENTS["lwc_error"],
474
- units="dB",
481
+ units="1",
475
482
  dimensions=("time", "height"),
476
483
  ),
477
484
  "lwc_retrieval_status": MetaData(
@@ -1,4 +1,4 @@
1
1
  MAJOR = 1
2
2
  MINOR = 97
3
- PATCH = 0
3
+ PATCH = 2
4
4
  __version__ = f"{MAJOR}.{MINOR}.{PATCH}"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cloudnetpy
3
- Version: 1.97.0
3
+ Version: 1.97.2
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