cloudnetpy 1.80.8__py3-none-any.whl → 1.81.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (83) hide show
  1. cloudnetpy/categorize/__init__.py +1 -1
  2. cloudnetpy/categorize/atmos_utils.py +31 -27
  3. cloudnetpy/categorize/attenuations/__init__.py +4 -4
  4. cloudnetpy/categorize/attenuations/liquid_attenuation.py +7 -5
  5. cloudnetpy/categorize/attenuations/melting_attenuation.py +3 -3
  6. cloudnetpy/categorize/attenuations/rain_attenuation.py +4 -4
  7. cloudnetpy/categorize/categorize.py +25 -11
  8. cloudnetpy/categorize/classify.py +9 -8
  9. cloudnetpy/categorize/containers.py +13 -10
  10. cloudnetpy/categorize/disdrometer.py +5 -3
  11. cloudnetpy/categorize/droplet.py +12 -9
  12. cloudnetpy/categorize/falling.py +9 -8
  13. cloudnetpy/categorize/freezing.py +10 -7
  14. cloudnetpy/categorize/insects.py +18 -17
  15. cloudnetpy/categorize/lidar.py +7 -3
  16. cloudnetpy/categorize/melting.py +16 -15
  17. cloudnetpy/categorize/model.py +17 -10
  18. cloudnetpy/categorize/mwr.py +5 -3
  19. cloudnetpy/categorize/radar.py +15 -13
  20. cloudnetpy/cli.py +10 -8
  21. cloudnetpy/cloudnetarray.py +8 -7
  22. cloudnetpy/concat_lib.py +29 -20
  23. cloudnetpy/datasource.py +26 -21
  24. cloudnetpy/exceptions.py +12 -10
  25. cloudnetpy/instruments/basta.py +19 -9
  26. cloudnetpy/instruments/bowtie.py +18 -11
  27. cloudnetpy/instruments/ceilo.py +22 -10
  28. cloudnetpy/instruments/ceilometer.py +33 -34
  29. cloudnetpy/instruments/cl61d.py +5 -3
  30. cloudnetpy/instruments/cloudnet_instrument.py +7 -7
  31. cloudnetpy/instruments/copernicus.py +16 -7
  32. cloudnetpy/instruments/disdrometer/common.py +5 -4
  33. cloudnetpy/instruments/disdrometer/parsivel.py +14 -9
  34. cloudnetpy/instruments/disdrometer/thies.py +11 -7
  35. cloudnetpy/instruments/fd12p.py +7 -6
  36. cloudnetpy/instruments/galileo.py +16 -7
  37. cloudnetpy/instruments/hatpro.py +33 -24
  38. cloudnetpy/instruments/lufft.py +6 -4
  39. cloudnetpy/instruments/mira.py +33 -19
  40. cloudnetpy/instruments/mrr.py +12 -12
  41. cloudnetpy/instruments/nc_lidar.py +1 -1
  42. cloudnetpy/instruments/nc_radar.py +8 -8
  43. cloudnetpy/instruments/pollyxt.py +19 -12
  44. cloudnetpy/instruments/radiometrics.py +17 -10
  45. cloudnetpy/instruments/rain_e_h3.py +9 -5
  46. cloudnetpy/instruments/rpg.py +32 -21
  47. cloudnetpy/instruments/rpg_reader.py +15 -12
  48. cloudnetpy/instruments/vaisala.py +32 -24
  49. cloudnetpy/instruments/weather_station.py +28 -21
  50. cloudnetpy/model_evaluation/file_handler.py +27 -29
  51. cloudnetpy/model_evaluation/plotting/plot_tools.py +7 -5
  52. cloudnetpy/model_evaluation/plotting/plotting.py +41 -32
  53. cloudnetpy/model_evaluation/products/advance_methods.py +38 -34
  54. cloudnetpy/model_evaluation/products/grid_methods.py +10 -9
  55. cloudnetpy/model_evaluation/products/model_products.py +15 -9
  56. cloudnetpy/model_evaluation/products/observation_products.py +12 -10
  57. cloudnetpy/model_evaluation/products/product_resampling.py +11 -7
  58. cloudnetpy/model_evaluation/products/tools.py +18 -14
  59. cloudnetpy/model_evaluation/statistics/statistical_methods.py +6 -5
  60. cloudnetpy/model_evaluation/tests/unit/test_plotting.py +18 -25
  61. cloudnetpy/model_evaluation/utils.py +3 -3
  62. cloudnetpy/output.py +15 -32
  63. cloudnetpy/plotting/plotting.py +22 -12
  64. cloudnetpy/products/classification.py +15 -9
  65. cloudnetpy/products/der.py +24 -19
  66. cloudnetpy/products/drizzle.py +21 -13
  67. cloudnetpy/products/drizzle_error.py +8 -7
  68. cloudnetpy/products/drizzle_tools.py +27 -23
  69. cloudnetpy/products/epsilon.py +6 -5
  70. cloudnetpy/products/ier.py +11 -5
  71. cloudnetpy/products/iwc.py +18 -9
  72. cloudnetpy/products/lwc.py +41 -31
  73. cloudnetpy/products/mwr_tools.py +30 -19
  74. cloudnetpy/products/product_tools.py +23 -19
  75. cloudnetpy/utils.py +84 -98
  76. cloudnetpy/version.py +2 -2
  77. {cloudnetpy-1.80.8.dist-info → cloudnetpy-1.81.1.dist-info}/METADATA +3 -2
  78. cloudnetpy-1.81.1.dist-info/RECORD +126 -0
  79. cloudnetpy-1.80.8.dist-info/RECORD +0 -126
  80. {cloudnetpy-1.80.8.dist-info → cloudnetpy-1.81.1.dist-info}/WHEEL +0 -0
  81. {cloudnetpy-1.80.8.dist-info → cloudnetpy-1.81.1.dist-info}/entry_points.txt +0 -0
  82. {cloudnetpy-1.80.8.dist-info → cloudnetpy-1.81.1.dist-info}/licenses/LICENSE +0 -0
  83. {cloudnetpy-1.80.8.dist-info → cloudnetpy-1.81.1.dist-info}/top_level.txt +0 -0
@@ -1 +1 @@
1
- from .categorize import generate_categorize
1
+ from .categorize import CategorizeInput, generate_categorize
@@ -9,7 +9,7 @@ import cloudnetpy.constants as con
9
9
  from cloudnetpy import utils
10
10
 
11
11
 
12
- def calc_wet_bulb_temperature(model_data: dict) -> np.ndarray:
12
+ def calc_wet_bulb_temperature(model_data: dict) -> npt.NDArray:
13
13
  """Calculate wet-bulb temperature iteratively.
14
14
 
15
15
  Args:
@@ -30,7 +30,7 @@ def calc_wet_bulb_temperature(model_data: dict) -> np.ndarray:
30
30
  W = calc_mixing_ratio(vp, pressure)
31
31
  L_v_0 = 2501e3 # Latent heat of vaporization at 0degC (J kg-1)
32
32
 
33
- def f(tw):
33
+ def f(tw: npt.NDArray) -> npt.NDArray:
34
34
  svp = calc_saturation_vapor_pressure(c2k(tw))
35
35
  W_s = calc_mixing_ratio(svp, pressure)
36
36
  C_p_w = 0.0265 * tw**2 - 1.7688 * tw + 4205.6 # Eq. 6 (J kg-1 C-1)
@@ -86,17 +86,17 @@ def calc_vapor_pressure(
86
86
  )
87
87
 
88
88
 
89
- def c2k(temp: np.ndarray) -> np.ndarray:
89
+ def c2k(temp: npt.NDArray) -> npt.NDArray:
90
90
  """Converts Celsius to Kelvins."""
91
91
  return ma.array(temp) + 273.15
92
92
 
93
93
 
94
- def k2c(temp: np.ndarray) -> np.ndarray:
94
+ def k2c(temp: npt.NDArray) -> npt.NDArray:
95
95
  """Converts Kelvins to Celsius."""
96
96
  return ma.array(temp) - 273.15
97
97
 
98
98
 
99
- def find_cloud_bases(array: np.ndarray) -> np.ndarray:
99
+ def find_cloud_bases(array: npt.NDArray) -> npt.NDArray:
100
100
  """Finds bases of clouds.
101
101
 
102
102
  Args:
@@ -111,7 +111,7 @@ def find_cloud_bases(array: np.ndarray) -> np.ndarray:
111
111
  return np.diff(array_padded, axis=1) == 1
112
112
 
113
113
 
114
- def find_cloud_tops(array: np.ndarray) -> np.ndarray:
114
+ def find_cloud_tops(array: npt.NDArray) -> npt.NDArray:
115
115
  """Finds tops of clouds.
116
116
 
117
117
  Args:
@@ -127,8 +127,8 @@ def find_cloud_tops(array: np.ndarray) -> np.ndarray:
127
127
 
128
128
 
129
129
  def find_lowest_cloud_bases(
130
- cloud_mask: np.ndarray,
131
- height: np.ndarray,
130
+ cloud_mask: npt.NDArray,
131
+ height: npt.NDArray,
132
132
  ) -> ma.MaskedArray:
133
133
  """Finds altitudes of cloud bases."""
134
134
  cloud_heights = cloud_mask * height
@@ -136,8 +136,8 @@ def find_lowest_cloud_bases(
136
136
 
137
137
 
138
138
  def find_highest_cloud_tops(
139
- cloud_mask: np.ndarray,
140
- height: np.ndarray,
139
+ cloud_mask: npt.NDArray,
140
+ height: npt.NDArray,
141
141
  ) -> ma.MaskedArray:
142
142
  """Finds altitudes of cloud tops."""
143
143
  cloud_heights = cloud_mask * height
@@ -145,15 +145,15 @@ def find_highest_cloud_tops(
145
145
  return _find_lowest_heights(cloud_heights_flipped)
146
146
 
147
147
 
148
- def _find_lowest_heights(cloud_heights: np.ndarray) -> ma.MaskedArray:
148
+ def _find_lowest_heights(cloud_heights: npt.NDArray) -> ma.MaskedArray:
149
149
  inds = (cloud_heights != 0).argmax(axis=1)
150
150
  heights = np.array([cloud_heights[i, ind] for i, ind in enumerate(inds)])
151
151
  return ma.masked_equal(heights, 0.0)
152
152
 
153
153
 
154
154
  def fill_clouds_with_lwc_dz(
155
- temperature: np.ndarray, pressure: np.ndarray, is_liquid: np.ndarray
156
- ) -> np.ndarray:
155
+ temperature: npt.NDArray, pressure: npt.NDArray, is_liquid: npt.NDArray
156
+ ) -> npt.NDArray:
157
157
  """Fills liquid clouds with lwc change rate at the cloud bases.
158
158
 
159
159
  Args:
@@ -173,10 +173,10 @@ def fill_clouds_with_lwc_dz(
173
173
 
174
174
 
175
175
  def get_lwc_change_rate_at_bases(
176
- temperature: np.ndarray,
177
- pressure: np.ndarray,
178
- is_liquid: np.ndarray,
179
- ) -> np.ndarray:
176
+ temperature: npt.NDArray,
177
+ pressure: npt.NDArray,
178
+ is_liquid: npt.NDArray,
179
+ ) -> npt.NDArray:
180
180
  """Finds LWC change rate in liquid cloud bases.
181
181
 
182
182
  Args:
@@ -198,7 +198,9 @@ def get_lwc_change_rate_at_bases(
198
198
  return lwc_dz
199
199
 
200
200
 
201
- def calc_lwc_change_rate(temperature: np.ndarray, pressure: np.ndarray) -> np.ndarray:
201
+ def calc_lwc_change_rate(
202
+ temperature: npt.NDArray, pressure: npt.NDArray
203
+ ) -> npt.NDArray:
202
204
  """Returns rate of change of condensable water (LWC).
203
205
 
204
206
  Calculates the theoretical adiabatic rate of increase of LWC
@@ -242,7 +244,7 @@ def calc_lwc_change_rate(temperature: np.ndarray, pressure: np.ndarray) -> np.nd
242
244
  return dqs_dz * air_density
243
245
 
244
246
 
245
- def calc_saturation_vapor_pressure(temperature: np.ndarray) -> np.ndarray:
247
+ def calc_saturation_vapor_pressure(temperature: npt.NDArray) -> npt.NDArray:
246
248
  """Goff-Gratch formula for saturation vapor pressure over water adopted by WMO.
247
249
 
248
250
  Args:
@@ -266,7 +268,9 @@ def calc_saturation_vapor_pressure(temperature: np.ndarray) -> np.ndarray:
266
268
  ) * con.HPA_TO_PA
267
269
 
268
270
 
269
- def calc_mixing_ratio(vapor_pressure: np.ndarray, pressure: np.ndarray) -> np.ndarray:
271
+ def calc_mixing_ratio(
272
+ vapor_pressure: npt.NDArray, pressure: npt.NDArray
273
+ ) -> npt.NDArray:
270
274
  """Calculates mixing ratio from partial vapor pressure and pressure.
271
275
 
272
276
  Args:
@@ -281,10 +285,10 @@ def calc_mixing_ratio(vapor_pressure: np.ndarray, pressure: np.ndarray) -> np.nd
281
285
 
282
286
 
283
287
  def calc_air_density(
284
- pressure: np.ndarray,
285
- temperature: np.ndarray,
286
- svp_mixing_ratio: np.ndarray,
287
- ) -> np.ndarray:
288
+ pressure: npt.NDArray,
289
+ temperature: npt.NDArray,
290
+ svp_mixing_ratio: npt.NDArray,
291
+ ) -> npt.NDArray:
288
292
  """Calculates air density (kg m-3).
289
293
 
290
294
  Args:
@@ -299,7 +303,7 @@ def calc_air_density(
299
303
  return pressure / (con.RS * temperature * (0.6 * svp_mixing_ratio + 1))
300
304
 
301
305
 
302
- def calc_adiabatic_lwc(lwc_dz: np.ndarray, height: np.ndarray) -> np.ndarray:
306
+ def calc_adiabatic_lwc(lwc_dz: npt.NDArray, height: npt.NDArray) -> npt.NDArray:
303
307
  """Calculates adiabatic liquid water content (kg m-3).
304
308
 
305
309
  Args:
@@ -319,8 +323,8 @@ def calc_adiabatic_lwc(lwc_dz: np.ndarray, height: np.ndarray) -> np.ndarray:
319
323
 
320
324
 
321
325
  def normalize_lwc_by_lwp(
322
- lwc_adiabatic: np.ndarray, lwp: np.ndarray, height: np.ndarray
323
- ) -> np.ndarray:
326
+ lwc_adiabatic: npt.NDArray, lwp: npt.NDArray, height: npt.NDArray
327
+ ) -> npt.NDArray:
324
328
  """Finds LWC that would produce measured LWP.
325
329
 
326
330
  Calculates LWP-weighted, normalized LWC. This is the measured
@@ -2,8 +2,8 @@ from dataclasses import dataclass
2
2
  from typing import Annotated
3
3
 
4
4
  import numpy as np
5
+ import numpy.typing as npt
5
6
  from numpy import ma
6
- from numpy.typing import NDArray
7
7
 
8
8
  from cloudnetpy import constants as con
9
9
  from cloudnetpy.utils import path_lengths_from_ground
@@ -13,8 +13,8 @@ from cloudnetpy.utils import path_lengths_from_ground
13
13
  class Attenuation:
14
14
  amount: Annotated[ma.MaskedArray, "float32"]
15
15
  error: Annotated[ma.MaskedArray, "float32"]
16
- attenuated: NDArray[np.bool_]
17
- uncorrected: NDArray[np.bool_]
16
+ attenuated: npt.NDArray[np.bool_]
17
+ uncorrected: npt.NDArray[np.bool_]
18
18
 
19
19
 
20
20
  @dataclass
@@ -26,7 +26,7 @@ class RadarAttenuation:
26
26
 
27
27
 
28
28
  def calc_two_way_attenuation(
29
- height: np.ndarray, specific_attenuation: ma.MaskedArray
29
+ height: npt.NDArray, specific_attenuation: ma.MaskedArray
30
30
  ) -> ma.MaskedArray:
31
31
  """Calculates two-way attenuation (dB) for given specific attenuation
32
32
  (dB km-1) and height (m).
@@ -1,4 +1,4 @@
1
- import numpy as np
1
+ import numpy.typing as npt
2
2
  from numpy import ma
3
3
 
4
4
  import cloudnetpy.constants as con
@@ -16,7 +16,9 @@ class LiquidAttenuation:
16
16
  algorithms: the Cloudnet Instrument Synergy/Target Categorization product.
17
17
  """
18
18
 
19
- def __init__(self, data: Observations, classification: ClassificationResult):
19
+ def __init__(
20
+ self, data: Observations, classification: ClassificationResult
21
+ ) -> None:
20
22
  self._model = data.model.data_dense
21
23
  self._liquid_in_pixel = classification.category_bits.droplet
22
24
  self._height = data.radar.height
@@ -50,7 +52,7 @@ class LiquidAttenuation:
50
52
  )
51
53
 
52
54
  def _calc_liquid_atten(
53
- self, lwp: ma.MaskedArray, lwc_dz: np.ndarray
55
+ self, lwp: ma.MaskedArray, lwc_dz: npt.NDArray
54
56
  ) -> ma.MaskedArray:
55
57
  """Finds radar liquid attenuation."""
56
58
  lwp = lwp.copy()
@@ -60,7 +62,7 @@ class LiquidAttenuation:
60
62
  return self._calc_two_way_attenuation(lwc_scaled)
61
63
 
62
64
  def _calc_liquid_atten_err(
63
- self, lwp_error: ma.MaskedArray, lwc_dz: np.ndarray
65
+ self, lwp_error: ma.MaskedArray, lwc_dz: npt.NDArray
64
66
  ) -> ma.MaskedArray:
65
67
  """Finds radar liquid attenuation error."""
66
68
  lwc_err_scaled = atmos_utils.normalize_lwc_by_lwp(
@@ -68,7 +70,7 @@ class LiquidAttenuation:
68
70
  )
69
71
  return self._calc_two_way_attenuation(lwc_err_scaled)
70
72
 
71
- def _calc_two_way_attenuation(self, lwc_scaled: np.ndarray) -> ma.MaskedArray:
73
+ def _calc_two_way_attenuation(self, lwc_scaled: npt.NDArray) -> ma.MaskedArray:
72
74
  """Calculates liquid attenuation (dB).
73
75
 
74
76
  Args:
@@ -1,4 +1,4 @@
1
- import numpy as np
1
+ import numpy.typing as npt
2
2
  from numpy import ma
3
3
 
4
4
  import cloudnetpy.constants as con
@@ -55,8 +55,8 @@ def calc_melting_attenuation(
55
55
 
56
56
 
57
57
  def _calc_melting_attenuation(
58
- rainfall_rate: np.ndarray, frequency: float
59
- ) -> np.ndarray:
58
+ rainfall_rate: npt.NDArray, frequency: float
59
+ ) -> npt.NDArray:
60
60
  """Calculates total attenuation due to melting layer (dB).
61
61
 
62
62
  References:
@@ -1,6 +1,6 @@
1
1
  import numpy as np
2
+ import numpy.typing as npt
2
3
  from numpy import ma
3
- from numpy.typing import NDArray
4
4
 
5
5
  import cloudnetpy.constants as con
6
6
  from cloudnetpy import utils
@@ -53,7 +53,7 @@ def calc_rain_attenuation(
53
53
 
54
54
  def _find_regions(
55
55
  classification: ClassificationResult,
56
- ) -> tuple[NDArray[np.bool_], NDArray[np.bool_]]:
56
+ ) -> tuple[npt.NDArray[np.bool_], npt.NDArray[np.bool_]]:
57
57
  """Finds regions where rain attenuation is present and can be corrected or not."""
58
58
  warm_region = ~classification.category_bits.freezing
59
59
  is_rain = utils.transpose(classification.is_rain).astype(bool)
@@ -63,8 +63,8 @@ def _find_regions(
63
63
 
64
64
 
65
65
  def _calc_rain_specific_attenuation(
66
- rainfall_rate: np.ndarray, frequency: float
67
- ) -> np.ndarray:
66
+ rainfall_rate: npt.NDArray, frequency: float
67
+ ) -> npt.NDArray:
68
68
  """Calculates specific attenuation due to rain (dB km-1).
69
69
 
70
70
  References:
@@ -2,10 +2,15 @@
2
2
 
3
3
  import dataclasses
4
4
  import logging
5
+ from collections.abc import Sequence
5
6
  from dataclasses import fields
7
+ from os import PathLike
8
+ from typing import TypedDict
9
+ from uuid import UUID
6
10
 
7
11
  import numpy as np
8
12
  from numpy.typing import NDArray
13
+ from typing_extensions import NotRequired
9
14
 
10
15
  from cloudnetpy import output, utils
11
16
  from cloudnetpy.categorize import attenuation, classify
@@ -22,12 +27,21 @@ from cloudnetpy.metadata import COMMON_ATTRIBUTES, MetaData
22
27
  from cloudnetpy.products.product_tools import CategoryBits, QualityBits
23
28
 
24
29
 
30
+ class CategorizeInput(TypedDict):
31
+ radar: str | PathLike
32
+ lidar: str | PathLike
33
+ model: str | PathLike
34
+ disdrometer: NotRequired[str | PathLike]
35
+ mwr: NotRequired[str | PathLike]
36
+ lv0_files: NotRequired[Sequence[str | PathLike]]
37
+
38
+
25
39
  def generate_categorize(
26
- input_files: dict,
27
- output_file: str,
28
- uuid: str | None = None,
40
+ input_files: CategorizeInput,
41
+ output_file: str | PathLike,
42
+ uuid: str | UUID | None = None,
29
43
  options: dict | None = None,
30
- ) -> str:
44
+ ) -> UUID:
31
45
  """Generates a Cloudnet Level 1c categorize file.
32
46
 
33
47
  This function rebins measurements into a common height/time grid
@@ -73,6 +87,7 @@ def generate_categorize(
73
87
  >>> input_files['lv0_files'] = ['file1.LV0', 'file2.LV0'] # Add RPG LV0 files
74
88
  >>> generate_categorize(input_files, 'output.nc') # Use the Voodoo method
75
89
  """
90
+ uuid = utils.get_uuid(uuid)
76
91
 
77
92
  def _interpolate_to_cloudnet_grid() -> list[int]:
78
93
  if data.disdrometer is not None:
@@ -199,17 +214,18 @@ def generate_categorize(
199
214
  attributes = output.add_time_attribute(attributes, date, "model_time")
200
215
  attributes = output.add_source_attribute(attributes, data)
201
216
  output.update_attributes(cloudnet_arrays, attributes)
202
- return _save_cat(output_file, data, cloudnet_arrays, uuid)
217
+ _save_cat(output_file, data, cloudnet_arrays, uuid)
218
+ return uuid
203
219
  finally:
204
220
  _close_all()
205
221
 
206
222
 
207
223
  def _save_cat(
208
- full_path: str,
224
+ full_path: str | PathLike,
209
225
  data_obs: Observations,
210
226
  cloudnet_arrays: dict,
211
- uuid: str | None,
212
- ) -> str:
227
+ uuid: UUID,
228
+ ) -> None:
213
229
  """Creates a categorize netCDF4 file and saves all data into it."""
214
230
  dims = {
215
231
  "time": len(data_obs.radar.time),
@@ -223,7 +239,6 @@ def _save_cat(
223
239
  file_type += "-voodoo"
224
240
 
225
241
  with output.init_file(full_path, dims, cloudnet_arrays, uuid) as nc:
226
- uuid_out = nc.file_uuid
227
242
  nc.cloudnet_file_type = file_type
228
243
  output.copy_global(
229
244
  data_obs.radar.dataset,
@@ -246,7 +261,6 @@ def _save_cat(
246
261
  nc.voodoonet_version = voodoonet.version.__version__
247
262
  output.add_source_instruments(nc, data_obs)
248
263
  output.merge_history(nc, file_type, data_obs)
249
- return uuid_out
250
264
 
251
265
 
252
266
  def _classes_to_bits(data: QualityBits | CategoryBits) -> NDArray[np.int_]:
@@ -411,7 +425,7 @@ CATEGORIZE_ATTRIBUTES = {
411
425
  long_name="Minimum detectable radar reflectivity",
412
426
  units="dBZ",
413
427
  comment=COMMENTS["Z_sensitivity"],
414
- dimensions=("time", "height"),
428
+ dimensions=("height",),
415
429
  ),
416
430
  "v": COMMON_ATTRIBUTES["v"]._replace(dimensions=("time", "height")),
417
431
  "width": COMMON_ATTRIBUTES["width"]._replace(dimensions=("time", "height")),
@@ -1,7 +1,7 @@
1
1
  import numpy as np
2
+ import numpy.typing as npt
2
3
  import skimage
3
4
  from numpy import ma
4
- from numpy.typing import NDArray
5
5
 
6
6
  from cloudnetpy import utils
7
7
  from cloudnetpy.categorize import (
@@ -67,9 +67,10 @@ def classify_measurements(data: Observations) -> ClassificationResult:
67
67
  import voodoonet # noqa: PLC0415
68
68
 
69
69
  options = voodoonet.VoodooOptions(progress_bar=False)
70
- target_time = voodoonet.utils.decimal_hour2unix(obs.date, obs.time)
70
+ dumb_date = obs.date.isoformat().split("-")
71
+ target_time = voodoonet.utils.decimal_hour2unix(dumb_date, obs.time)
71
72
  liquid_prob = voodoonet.infer(
72
- obs.lv0_files, target_time=target_time, options=options
73
+ list(obs.lv0_files), target_time=target_time, options=options
73
74
  )
74
75
  liquid_from_radar = liquid_prob > 0.55
75
76
  liquid_from_radar = _remove_false_radar_liquid(
@@ -131,9 +132,9 @@ def _fix_super_cold_liquid(obs: ClassData, bits: CategoryBits) -> None:
131
132
 
132
133
 
133
134
  def _remove_false_radar_liquid(
134
- liquid_from_radar: np.ndarray,
135
- liquid_from_lidar: np.ndarray,
136
- ) -> NDArray[np.bool_]:
135
+ liquid_from_radar: npt.NDArray,
136
+ liquid_from_lidar: npt.NDArray,
137
+ ) -> npt.NDArray[np.bool_]:
137
138
  """Removes radar-liquid below lidar-detected liquid bases."""
138
139
  lidar_liquid_bases = atmos_utils.find_cloud_bases(liquid_from_lidar)
139
140
  for prof, base in zip(*np.where(lidar_liquid_bases), strict=True):
@@ -144,7 +145,7 @@ def _remove_false_radar_liquid(
144
145
  def _find_aerosols(
145
146
  obs: ClassData,
146
147
  bits: CategoryBits,
147
- ) -> NDArray[np.bool_]:
148
+ ) -> npt.NDArray[np.bool_]:
148
149
  """Estimates aerosols from lidar backscattering.
149
150
 
150
151
  Aerosols are lidar signals that are: a) not falling, b) not liquid droplets.
@@ -167,7 +168,7 @@ def _fix_undetected_melting_layer(bits: CategoryBits) -> None:
167
168
  bits.melting[:, 1:][transition] = True
168
169
 
169
170
 
170
- def _find_drizzle_and_falling(bits: CategoryBits) -> np.ndarray:
171
+ def _find_drizzle_and_falling(bits: CategoryBits) -> npt.NDArray:
171
172
  """Classifies pixels as falling, drizzle and others.
172
173
 
173
174
  Args:
@@ -1,6 +1,9 @@
1
+ from collections.abc import Sequence
1
2
  from dataclasses import dataclass
3
+ from os import PathLike
2
4
 
3
5
  import numpy as np
6
+ import numpy.typing as npt
4
7
  from numpy import ma
5
8
 
6
9
  from cloudnetpy import utils
@@ -21,16 +24,16 @@ class Observations:
21
24
  model: Model
22
25
  mwr: Mwr | None = None
23
26
  disdrometer: Disdrometer | None = None
24
- lv0_files: list[str] | None = None
27
+ lv0_files: Sequence[str | PathLike] | None = None
25
28
 
26
29
 
27
30
  @dataclass
28
31
  class ClassificationResult:
29
32
  category_bits: CategoryBits
30
- is_rain: np.ndarray
31
- is_clutter: np.ndarray
32
- insect_prob: np.ndarray
33
- liquid_prob: np.ndarray | None
33
+ is_rain: npt.NDArray
34
+ is_clutter: npt.NDArray
35
+ insect_prob: npt.NDArray
36
+ liquid_prob: npt.NDArray | None
34
37
 
35
38
 
36
39
  class ClassData:
@@ -59,7 +62,7 @@ class ClassData:
59
62
 
60
63
  """
61
64
 
62
- def __init__(self, data: Observations):
65
+ def __init__(self, data: Observations) -> None:
63
66
  self.data = data
64
67
  self.z = data.radar.data["Z"][:]
65
68
  self.v = data.radar.data["v"][:]
@@ -84,7 +87,7 @@ class ClassData:
84
87
  self.lv0_files = data.lv0_files
85
88
  self.date = data.radar.get_date()
86
89
 
87
- def _find_profiles_with_rain(self) -> np.ndarray:
90
+ def _find_profiles_with_rain(self) -> npt.NDArray:
88
91
  is_rain = self._find_rain_from_radar_echo()
89
92
  rain_from_disdrometer = self._find_rain_from_disdrometer()
90
93
  ind = ~rain_from_disdrometer.mask
@@ -94,7 +97,7 @@ class ClassData:
94
97
  is_positive_temp = self.tw[:, first_valid_ind] > T0 + 5 # Filter snowfall
95
98
  return is_rain & is_positive_temp
96
99
 
97
- def _find_rain_from_radar_echo(self) -> np.ndarray:
100
+ def _find_rain_from_radar_echo(self) -> npt.NDArray:
98
101
  first_gate_with_data = np.argmin(self.z.mask.all(axis=0))
99
102
  gate_number = first_gate_with_data + 3
100
103
  threshold = {"z": 3, "v": 0, "ldr": -15}
@@ -132,10 +135,10 @@ class ClassData:
132
135
 
133
136
  def _find_clutter(
134
137
  v: np.ma.MaskedArray,
135
- is_rain: np.ndarray,
138
+ is_rain: npt.NDArray,
136
139
  n_gates: int = 10,
137
140
  v_lim: float = 0.05,
138
- ) -> np.ndarray:
141
+ ) -> npt.NDArray:
139
142
  """Estimates clutter from doppler velocity.
140
143
 
141
144
  Args:
@@ -1,8 +1,10 @@
1
1
  """Mwr module, containing the :class:`Mwr` class."""
2
2
 
3
3
  import logging
4
+ from os import PathLike
4
5
 
5
6
  import numpy as np
7
+ import numpy.typing as npt
6
8
  from numpy import ma
7
9
  from scipy.interpolate import interp1d
8
10
 
@@ -20,11 +22,11 @@ class Disdrometer(DataSource):
20
22
 
21
23
  """
22
24
 
23
- def __init__(self, full_path: str):
25
+ def __init__(self, full_path: str | PathLike) -> None:
24
26
  super().__init__(full_path)
25
27
  self._init_rainfall_rate()
26
28
 
27
- def interpolate_to_grid(self, time_grid: np.ndarray) -> None:
29
+ def interpolate_to_grid(self, time_grid: npt.NDArray) -> None:
28
30
  for key, array in self.data.items():
29
31
  self.data[key].data = self._interpolate(array.data, time_grid)
30
32
 
@@ -36,7 +38,7 @@ class Disdrometer(DataSource):
36
38
  raise DisdrometerDataError(msg)
37
39
  self.append_data(self.dataset.variables[key][:], key)
38
40
 
39
- def _interpolate(self, y: ma.MaskedArray, x_new: np.ndarray) -> np.ndarray:
41
+ def _interpolate(self, y: ma.MaskedArray, x_new: npt.NDArray) -> npt.NDArray:
40
42
  time = self.time
41
43
  mask = ma.getmask(y)
42
44
  if mask is not ma.nomask:
@@ -1,6 +1,7 @@
1
1
  """This module has functions for liquid layer detection."""
2
2
 
3
3
  import numpy as np
4
+ import numpy.typing as npt
4
5
  import scipy.signal
5
6
  from numpy import ma
6
7
 
@@ -11,10 +12,10 @@ from cloudnetpy.categorize.containers import ClassData
11
12
 
12
13
  def correct_liquid_top(
13
14
  obs: ClassData,
14
- is_liquid: np.ndarray,
15
- is_freezing: np.ndarray,
15
+ is_liquid: npt.NDArray,
16
+ is_freezing: npt.NDArray,
16
17
  limit: float = 200,
17
- ) -> np.ndarray:
18
+ ) -> npt.NDArray:
18
19
  """Corrects lidar detected liquid cloud top using radar data.
19
20
 
20
21
  Args:
@@ -43,7 +44,7 @@ def correct_liquid_top(
43
44
  return is_liquid_corrected
44
45
 
45
46
 
46
- def _find_ind_above_top(is_freezing_from_peak: np.ndarray, top_above: int) -> int:
47
+ def _find_ind_above_top(is_freezing_from_peak: npt.NDArray, top_above: int) -> int:
47
48
  first_point_below_zero = np.where(is_freezing_from_peak)[0][0]
48
49
  ind = first_point_below_zero + top_above
49
50
  return min(len(is_freezing_from_peak) - 1, ind)
@@ -57,7 +58,7 @@ def find_liquid(
57
58
  min_top_der: float = 1e-7,
58
59
  min_lwp: float = 0,
59
60
  min_alt: float = 100,
60
- ) -> np.ndarray:
61
+ ) -> npt.NDArray:
61
62
  """Estimate liquid layers from SNR-screened attenuated backscatter.
62
63
 
63
64
  Args:
@@ -121,7 +122,7 @@ def find_liquid(
121
122
  return is_liquid
122
123
 
123
124
 
124
- def ind_base(dprof: np.ndarray, ind_peak: int, dist: int, lim: float) -> int:
125
+ def ind_base(dprof: npt.NDArray, ind_peak: int, dist: int, lim: float) -> int:
125
126
  """Finds base index of a peak in profile.
126
127
 
127
128
  Return the lowermost index of profile where 1st order differences
@@ -186,7 +187,9 @@ def ind_base(dprof: np.ndarray, ind_peak: int, dist: int, lim: float) -> int:
186
187
  return start + np.where(diffs > diffs[mind] / lim)[0][0]
187
188
 
188
189
 
189
- def ind_top(dprof: np.ndarray, ind_peak: int, nprof: int, dist: int, lim: float) -> int:
190
+ def ind_top(
191
+ dprof: npt.NDArray, ind_peak: int, nprof: int, dist: int, lim: float
192
+ ) -> int:
190
193
  """Finds top index of a peak in profile.
191
194
 
192
195
  Return the uppermost index of profile where 1st order differences
@@ -222,7 +225,7 @@ def ind_top(dprof: np.ndarray, ind_peak: int, nprof: int, dist: int, lim: float)
222
225
  return ind_peak + np.where(diffs < diffs[mind] / lim)[0][-1] + 1
223
226
 
224
227
 
225
- def interpolate_lwp(obs: ClassData) -> np.ndarray:
228
+ def interpolate_lwp(obs: ClassData) -> npt.NDArray:
226
229
  """Linear interpolation of liquid water path to fill masked values.
227
230
 
228
231
  Args:
@@ -238,7 +241,7 @@ def interpolate_lwp(obs: ClassData) -> np.ndarray:
238
241
  return np.interp(obs.time, obs.time[ind], obs.lwp[ind])
239
242
 
240
243
 
241
- def _find_strong_peaks(data: np.ndarray, threshold: float) -> tuple:
244
+ def _find_strong_peaks(data: npt.NDArray, threshold: float) -> tuple:
242
245
  """Finds local maximums from data (greater than *threshold*)."""
243
246
  peaks = scipy.signal.argrelextrema(data, np.greater, order=4, axis=1)
244
247
  strong_peaks = np.where(data[peaks] > threshold)
@@ -1,6 +1,7 @@
1
1
  """Module to find falling hydrometeors from data."""
2
2
 
3
3
  import numpy as np
4
+ import numpy.typing as npt
4
5
  from numpy import ma
5
6
 
6
7
  from cloudnetpy.categorize import atmos_utils
@@ -10,9 +11,9 @@ from cloudnetpy.constants import T0
10
11
 
11
12
  def find_falling_hydrometeors(
12
13
  obs: ClassData,
13
- is_liquid: np.ndarray,
14
- is_insects: np.ndarray,
15
- ) -> np.ndarray:
14
+ is_liquid: npt.NDArray,
15
+ is_insects: npt.NDArray,
16
+ ) -> npt.NDArray:
16
17
  """Finds falling hydrometeors.
17
18
 
18
19
  Falling hydrometeors are radar signals that are
@@ -43,14 +44,14 @@ def find_falling_hydrometeors(
43
44
  return falling_from_radar_fixed | cold_aerosols
44
45
 
45
46
 
46
- def _find_falling_from_radar(obs: ClassData, is_insects: np.ndarray) -> np.ndarray:
47
+ def _find_falling_from_radar(obs: ClassData, is_insects: npt.NDArray) -> npt.NDArray:
47
48
  is_z = ~obs.z.mask
48
49
  no_clutter = ~obs.is_clutter
49
50
  no_insects = ~is_insects
50
51
  return is_z & no_clutter & no_insects
51
52
 
52
53
 
53
- def _find_cold_aerosols(obs: ClassData, is_liquid: np.ndarray) -> np.ndarray:
54
+ def _find_cold_aerosols(obs: ClassData, is_liquid: npt.NDArray) -> npt.NDArray:
54
55
  """Lidar signals which are in colder than the threshold temperature
55
56
  and threshold altitude from the ground are assumed ice.
56
57
 
@@ -90,9 +91,9 @@ def _find_cold_aerosols(obs: ClassData, is_liquid: np.ndarray) -> np.ndarray:
90
91
 
91
92
  def _fix_liquid_dominated_radar(
92
93
  obs: ClassData,
93
- falling_from_radar: np.ndarray,
94
- is_liquid: np.ndarray,
95
- ) -> np.ndarray:
94
+ falling_from_radar: npt.NDArray,
95
+ is_liquid: npt.NDArray,
96
+ ) -> npt.NDArray:
96
97
  """Radar signals inside liquid clouds are NOT ice if Z is
97
98
  increasing in height inside the cloud.
98
99
  """