cloudnetpy 1.55.20__py3-none-any.whl → 1.55.22__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 (95) hide show
  1. cloudnetpy/categorize/atmos.py +46 -14
  2. cloudnetpy/categorize/atmos_utils.py +11 -1
  3. cloudnetpy/categorize/categorize.py +38 -21
  4. cloudnetpy/categorize/classify.py +31 -9
  5. cloudnetpy/categorize/containers.py +19 -7
  6. cloudnetpy/categorize/droplet.py +24 -8
  7. cloudnetpy/categorize/falling.py +17 -7
  8. cloudnetpy/categorize/freezing.py +19 -5
  9. cloudnetpy/categorize/insects.py +27 -14
  10. cloudnetpy/categorize/lidar.py +38 -36
  11. cloudnetpy/categorize/melting.py +19 -9
  12. cloudnetpy/categorize/model.py +28 -9
  13. cloudnetpy/categorize/mwr.py +4 -2
  14. cloudnetpy/categorize/radar.py +58 -22
  15. cloudnetpy/cloudnetarray.py +15 -6
  16. cloudnetpy/concat_lib.py +39 -16
  17. cloudnetpy/constants.py +7 -0
  18. cloudnetpy/datasource.py +39 -19
  19. cloudnetpy/instruments/basta.py +6 -2
  20. cloudnetpy/instruments/campbell_scientific.py +33 -16
  21. cloudnetpy/instruments/ceilo.py +30 -13
  22. cloudnetpy/instruments/ceilometer.py +76 -37
  23. cloudnetpy/instruments/cl61d.py +8 -3
  24. cloudnetpy/instruments/cloudnet_instrument.py +2 -1
  25. cloudnetpy/instruments/copernicus.py +27 -14
  26. cloudnetpy/instruments/disdrometer/common.py +51 -32
  27. cloudnetpy/instruments/disdrometer/parsivel.py +79 -48
  28. cloudnetpy/instruments/disdrometer/thies.py +10 -6
  29. cloudnetpy/instruments/galileo.py +23 -12
  30. cloudnetpy/instruments/hatpro.py +27 -11
  31. cloudnetpy/instruments/instruments.py +4 -1
  32. cloudnetpy/instruments/lufft.py +20 -11
  33. cloudnetpy/instruments/mira.py +60 -49
  34. cloudnetpy/instruments/mrr.py +31 -20
  35. cloudnetpy/instruments/nc_lidar.py +15 -6
  36. cloudnetpy/instruments/nc_radar.py +31 -22
  37. cloudnetpy/instruments/pollyxt.py +36 -21
  38. cloudnetpy/instruments/radiometrics.py +32 -18
  39. cloudnetpy/instruments/rpg.py +48 -22
  40. cloudnetpy/instruments/rpg_reader.py +39 -30
  41. cloudnetpy/instruments/vaisala.py +39 -27
  42. cloudnetpy/instruments/weather_station.py +15 -11
  43. cloudnetpy/metadata.py +3 -1
  44. cloudnetpy/model_evaluation/file_handler.py +31 -21
  45. cloudnetpy/model_evaluation/metadata.py +3 -1
  46. cloudnetpy/model_evaluation/model_metadata.py +1 -1
  47. cloudnetpy/model_evaluation/plotting/plot_tools.py +20 -15
  48. cloudnetpy/model_evaluation/plotting/plotting.py +114 -64
  49. cloudnetpy/model_evaluation/products/advance_methods.py +48 -28
  50. cloudnetpy/model_evaluation/products/grid_methods.py +44 -19
  51. cloudnetpy/model_evaluation/products/model_products.py +22 -18
  52. cloudnetpy/model_evaluation/products/observation_products.py +15 -9
  53. cloudnetpy/model_evaluation/products/product_resampling.py +14 -4
  54. cloudnetpy/model_evaluation/products/tools.py +16 -7
  55. cloudnetpy/model_evaluation/statistics/statistical_methods.py +28 -15
  56. cloudnetpy/model_evaluation/tests/e2e/conftest.py +3 -3
  57. cloudnetpy/model_evaluation/tests/e2e/process_cf/main.py +9 -5
  58. cloudnetpy/model_evaluation/tests/e2e/process_cf/tests.py +14 -13
  59. cloudnetpy/model_evaluation/tests/e2e/process_iwc/main.py +9 -5
  60. cloudnetpy/model_evaluation/tests/e2e/process_iwc/tests.py +14 -13
  61. cloudnetpy/model_evaluation/tests/e2e/process_lwc/main.py +9 -5
  62. cloudnetpy/model_evaluation/tests/e2e/process_lwc/tests.py +14 -13
  63. cloudnetpy/model_evaluation/tests/unit/conftest.py +11 -11
  64. cloudnetpy/model_evaluation/tests/unit/test_advance_methods.py +33 -27
  65. cloudnetpy/model_evaluation/tests/unit/test_grid_methods.py +83 -83
  66. cloudnetpy/model_evaluation/tests/unit/test_model_products.py +23 -21
  67. cloudnetpy/model_evaluation/tests/unit/test_observation_products.py +24 -25
  68. cloudnetpy/model_evaluation/tests/unit/test_plot_tools.py +40 -39
  69. cloudnetpy/model_evaluation/tests/unit/test_plotting.py +12 -11
  70. cloudnetpy/model_evaluation/tests/unit/test_statistical_methods.py +30 -30
  71. cloudnetpy/model_evaluation/tests/unit/test_tools.py +18 -17
  72. cloudnetpy/model_evaluation/utils.py +3 -2
  73. cloudnetpy/output.py +45 -19
  74. cloudnetpy/plotting/plot_meta.py +35 -11
  75. cloudnetpy/plotting/plotting.py +172 -104
  76. cloudnetpy/products/classification.py +20 -8
  77. cloudnetpy/products/der.py +25 -10
  78. cloudnetpy/products/drizzle.py +41 -26
  79. cloudnetpy/products/drizzle_error.py +10 -5
  80. cloudnetpy/products/drizzle_tools.py +43 -24
  81. cloudnetpy/products/ier.py +10 -5
  82. cloudnetpy/products/iwc.py +16 -9
  83. cloudnetpy/products/lwc.py +34 -12
  84. cloudnetpy/products/mwr_multi.py +4 -1
  85. cloudnetpy/products/mwr_single.py +4 -1
  86. cloudnetpy/products/product_tools.py +33 -10
  87. cloudnetpy/utils.py +175 -74
  88. cloudnetpy/version.py +1 -1
  89. {cloudnetpy-1.55.20.dist-info → cloudnetpy-1.55.22.dist-info}/METADATA +11 -10
  90. cloudnetpy-1.55.22.dist-info/RECORD +114 -0
  91. docs/source/conf.py +2 -2
  92. cloudnetpy-1.55.20.dist-info/RECORD +0 -114
  93. {cloudnetpy-1.55.20.dist-info → cloudnetpy-1.55.22.dist-info}/LICENSE +0 -0
  94. {cloudnetpy-1.55.20.dist-info → cloudnetpy-1.55.22.dist-info}/WHEEL +0 -0
  95. {cloudnetpy-1.55.20.dist-info → cloudnetpy-1.55.22.dist-info}/top_level.txt +0 -0
@@ -16,9 +16,11 @@ class DrizzleSource(DataSource):
16
16
  """Class holding the input data for drizzle calculations.
17
17
 
18
18
  Args:
19
+ ----
19
20
  categorize_file: Categorize file name.
20
21
 
21
22
  Attributes:
23
+ ----------
22
24
  mie (dict): Mie look-up table data.
23
25
  dheight (float): Median difference of height array.
24
26
  z (ndarray): 2D radar echo (linear units).
@@ -35,13 +37,13 @@ class DrizzleSource(DataSource):
35
37
  self.beta = self.getvar("beta")
36
38
  self.v = self.getvar("v")
37
39
 
38
- def _convert_z_units(self):
40
+ def _convert_z_units(self) -> np.ndarray:
39
41
  """Converts reflectivity factor to SI units."""
40
42
  z = self.getvar("Z") - 180
41
43
  z[z > 0.0] = 0.0
42
44
  return utils.db2lin(z)
43
45
 
44
- def _read_mie_lut(self):
46
+ def _read_mie_lut(self) -> dict:
45
47
  """Reads mie scattering look-up table."""
46
48
  mie_file = self._get_mie_file()
47
49
  with netCDF4.Dataset(mie_file) as nc:
@@ -59,16 +61,16 @@ class DrizzleSource(DataSource):
59
61
  "width": mie[f"lu_width_{band}"][:],
60
62
  "ray": mie[f"lu_mie_ray_{band}"][:],
61
63
  "v": mie[f"lu_v_{band}"][:],
62
- }
64
+ },
63
65
  )
64
66
  return lut
65
67
 
66
68
  @staticmethod
67
- def _get_mie_file():
69
+ def _get_mie_file() -> str:
68
70
  module_path = os.path.dirname(os.path.abspath(__file__))
69
- return "/".join((module_path, "mie_lu_tables.nc"))
71
+ return f"{module_path}/mie_lu_tables.nc"
70
72
 
71
- def _get_wl_band(self):
73
+ def _get_wl_band(self) -> str:
72
74
  """Returns string corresponding the radar frequency."""
73
75
  radar_frequency = float(self.getvar("radar_frequency"))
74
76
  wl_band = utils.get_wl_band(radar_frequency)
@@ -80,9 +82,11 @@ class DrizzleClassification(ProductClassification):
80
82
  child of :class:`ProductClassification`.
81
83
 
82
84
  Args:
85
+ ----
83
86
  categorize_file: Categorize file name.
84
87
 
85
88
  Attributes:
89
+ ----------
86
90
  is_v_sigma (ndarray): 2D array denoting finite v_sigma.
87
91
  warm_liquid (ndarray): 2D array denoting warm liquid.
88
92
  drizzle (ndarray): 2D array denoting drizzle presence.
@@ -100,14 +104,14 @@ class DrizzleClassification(ProductClassification):
100
104
  self.cold_rain = self._find_cold_rain()
101
105
 
102
106
  @staticmethod
103
- def _find_v_sigma(cat_file: str):
107
+ def _find_v_sigma(cat_file: str) -> np.ndarray:
104
108
  v_sigma = product_tools.read_nc_fields(cat_file, "v_sigma")
105
109
  return np.isfinite(v_sigma)
106
110
 
107
- def _find_warm_liquid(self):
111
+ def _find_warm_liquid(self) -> np.ndarray:
108
112
  return self.category_bits["droplet"] & ~self.category_bits["cold"]
109
113
 
110
- def _find_drizzle(self):
114
+ def _find_drizzle(self) -> np.ndarray:
111
115
  return (
112
116
  ~utils.transpose(self.is_rain)
113
117
  & self.category_bits["falling"]
@@ -123,7 +127,7 @@ class DrizzleClassification(ProductClassification):
123
127
  & self.is_v_sigma
124
128
  )
125
129
 
126
- def _find_would_be_drizzle(self):
130
+ def _find_would_be_drizzle(self) -> np.ndarray:
127
131
  return (
128
132
  ~utils.transpose(self.is_rain)
129
133
  & self.warm_liquid
@@ -135,7 +139,7 @@ class DrizzleClassification(ProductClassification):
135
139
  & ~self.quality_bits["molecular"]
136
140
  )
137
141
 
138
- def _find_cold_rain(self):
142
+ def _find_cold_rain(self) -> np.ndarray:
139
143
  return np.any(self.category_bits["melting"], axis=1)
140
144
 
141
145
 
@@ -146,9 +150,11 @@ class SpectralWidth:
146
150
  spectral broadening of the Doppler velocity.
147
151
 
148
152
  Args:
153
+ ----
149
154
  categorize_file: Categorize file name.
150
155
 
151
156
  Attributes:
157
+ ----------
152
158
  categorize_file (str): Categorize file name.
153
159
  width_ht (ndarray): Spectral width containing the correction for turbulence
154
160
  broadening.
@@ -159,32 +165,33 @@ class SpectralWidth:
159
165
  self.cat_file = categorize_file
160
166
  self.width_ht = self._calculate_spectral_width()
161
167
 
162
- def _calculate_spectral_width(self):
168
+ def _calculate_spectral_width(self) -> np.ndarray:
163
169
  v_sigma = product_tools.read_nc_fields(self.cat_file, "v_sigma")
164
170
  try:
165
171
  width = product_tools.read_nc_fields(self.cat_file, "width")
166
172
  except KeyError:
167
173
  width = [0]
168
- logging.warning(f"No spectral width, assuming width = {width[0]}")
174
+ logging.warning("No spectral width, assuming width = %s", width[0])
169
175
  sigma_factor = self._calc_v_sigma_factor()
170
176
  return width - sigma_factor * v_sigma
171
177
 
172
- def _calc_v_sigma_factor(self):
178
+ def _calc_v_sigma_factor(self) -> np.ndarray:
173
179
  beam_divergence = self._calc_beam_divergence()
174
180
  wind = self._calc_horizontal_wind()
175
181
  actual_wind = (wind + beam_divergence) ** (2 / 3)
176
182
  scaled_wind = (30 * wind + beam_divergence) ** (2 / 3)
177
183
  return actual_wind / (scaled_wind - actual_wind)
178
184
 
179
- def _calc_beam_divergence(self):
185
+ def _calc_beam_divergence(self) -> np.ndarray:
180
186
  beam_width = 0.5
181
187
  height = product_tools.read_nc_fields(self.cat_file, "height")
182
188
  return height * np.deg2rad(beam_width)
183
189
 
184
- def _calc_horizontal_wind(self):
190
+ def _calc_horizontal_wind(self) -> np.ndarray:
185
191
  """Calculates magnitude of horizontal wind.
186
192
 
187
- Returns:
193
+ Returns
194
+ -------
188
195
  ndarray: Horizontal wind (m s-1).
189
196
 
190
197
  """
@@ -198,11 +205,13 @@ class DrizzleSolver:
198
205
  """Estimates drizzle parameters.
199
206
 
200
207
  Args:
208
+ ----
201
209
  drizzle_source: The :class:`DrizzleSource` instance.
202
210
  drizzle_class: The :class:`DrizzleClassification` instance.
203
211
  spectral_width: The :class:`SpectralWidth` instance.
204
212
 
205
213
  Attributes:
214
+ ----------
206
215
  params (dict): Dictionary of retrieved drizzle parameters 'Do', 'mu', 'S',
207
216
  'beta_corr'.
208
217
 
@@ -238,16 +247,18 @@ class DrizzleSolver:
238
247
  def _find_lut_indices(self, ind, dia_init, n_dia, n_widths) -> tuple[int, int]:
239
248
  ind_dia = bisect_left(self._data.mie["Do"], dia_init[ind], hi=n_dia - 1)
240
249
  ind_width = bisect_left(
241
- self._width_lut[:, ind_dia], -self._width_ht[ind], hi=n_widths - 1
250
+ self._width_lut[:, ind_dia],
251
+ -self._width_ht[ind],
252
+ hi=n_widths - 1,
242
253
  )
243
254
  return ind_width, ind_dia
244
255
 
245
- def _solve_drizzle(self, dia_init: np.ndarray):
256
+ def _solve_drizzle(self, dia_init: np.ndarray) -> None:
246
257
  drizzle_ind = np.where(self._drizzle_class.drizzle == 1)
247
258
  dia_init[drizzle_ind] = self._calc_dia(self._beta_z_ratio[drizzle_ind], k=18.8)
248
259
  n_widths, n_dia = self._width_lut.shape[0], len(self._data.mie["Do"])
249
260
  max_ite = 10
250
- for ind in zip(*drizzle_ind):
261
+ for ind in zip(*drizzle_ind, strict=True):
251
262
  for _ in range(max_ite):
252
263
  lut_ind = self._find_lut_indices(ind, dia_init, n_dia, n_widths)
253
264
  dia = self._calc_dia(
@@ -261,13 +272,16 @@ class DrizzleSolver:
261
272
  break
262
273
  self._dia_init[ind] = dia
263
274
  beta_factor = np.exp(
264
- 2 * self.params["S"][ind] * self._data.beta[ind] * self._data.dheight
275
+ 2 * self.params["S"][ind] * self._data.beta[ind] * self._data.dheight,
265
276
  )
266
277
  self.params["beta_corr"][ind[0], (ind[-1] + 1) :] *= beta_factor
267
278
 
268
279
  def _update_result_tables(
269
- self, ind: tuple, dia: np.ndarray | float, lut_ind: tuple
270
- ):
280
+ self,
281
+ ind: tuple,
282
+ dia: np.ndarray | float,
283
+ lut_ind: tuple,
284
+ ) -> None:
271
285
  self.params["Do"][ind] = dia
272
286
  self.params["mu"][ind] = self._data.mie["mu"][lut_ind[0]]
273
287
  self.params["S"][ind] = self._data.mie["S"][lut_ind]
@@ -282,15 +296,18 @@ class DrizzleSolver:
282
296
  """Drizzle diameter calculation.
283
297
 
284
298
  Args:
299
+ ----
285
300
  beta_z_ratio: Beta to z ratio, multiplied by (2 / pi).
286
301
  mu: Shape parameter for gamma calculations. Default is 0.
287
302
  ray: Mie to Rayleigh ratio for z. Default is 1.
288
303
  k: Alpha to beta ratio . Default is 1.
289
304
 
290
305
  Returns:
306
+ -------
291
307
  ndarray: Drizzle diameter.
292
308
 
293
309
  References:
310
+ ----------
294
311
  https://journals.ametsoc.org/doi/pdf/10.1175/JAM-2181.1
295
312
 
296
313
  """
@@ -299,7 +316,9 @@ class DrizzleSolver:
299
316
 
300
317
  @staticmethod
301
318
  def _is_converged(
302
- ind: tuple, dia: np.ndarray | float, dia_init: np.ndarray
319
+ ind: tuple,
320
+ dia: np.ndarray | float,
321
+ dia_init: np.ndarray,
303
322
  ) -> bool:
304
323
  threshold = 1e-3
305
324
  return abs((dia - dia_init[ind]) / dia_init[ind]) < threshold
@@ -8,7 +8,9 @@ from cloudnetpy.products.product_tools import IceClassification, IceSource
8
8
 
9
9
 
10
10
  def generate_ier(
11
- categorize_file: str, output_file: str, uuid: str | None = None
11
+ categorize_file: str,
12
+ output_file: str,
13
+ uuid: str | None = None,
12
14
  ) -> str:
13
15
  """Generates Cloudnet ice effective radius product.
14
16
 
@@ -19,18 +21,22 @@ def generate_ier(
19
21
  and model temperature. The results are written in a netCDF file.
20
22
 
21
23
  Args:
24
+ ----
22
25
  categorize_file: Categorize file name.
23
26
  output_file: Output file name.
24
27
  uuid: Set specific UUID for the file.
25
28
 
26
29
  Returns:
30
+ -------
27
31
  UUID of the generated file.
28
32
 
29
33
  Examples:
34
+ --------
30
35
  >>> from cloudnetpy.products import generate_ier
31
36
  >>> generate_ier('categorize.nc', 'ier.nc')
32
37
 
33
38
  References:
39
+ ----------
34
40
  Hogan, R. J., Mittermaier, M. P., & Illingworth, A. J. (2006). The Retrieval
35
41
  of Ice Water Content from Radar Reflectivity Factor and Temperature and Its
36
42
  Use in Evaluating a Mesoscale Model, Journal of Applied Meteorology and
@@ -63,14 +69,13 @@ def generate_ier(
63
69
  attributes = output.add_time_attribute(IER_ATTRIBUTES, date)
64
70
  attributes = _add_ier_comment(attributes, ier_source)
65
71
  output.update_attributes(ier_source.data, attributes)
66
- uuid = output.save_product_file(product, ier_source, output_file, uuid)
67
- return uuid
72
+ return output.save_product_file(product, ier_source, output_file, uuid)
68
73
 
69
74
 
70
75
  class IerSource(IceSource):
71
76
  """Data container for ice effective radius calculations."""
72
77
 
73
- def convert_units(self):
78
+ def convert_units(self) -> None:
74
79
  """Convert um to m."""
75
80
  for prod in ("ier", "ier_inc_rain"):
76
81
  self.data[prod].data[:] /= 1e6
@@ -105,7 +110,7 @@ def _add_ier_comment(attributes: dict, ier: IerSource) -> dict:
105
110
  "as ice. Missing data indicates either that ice cloud was present but it was\n"
106
111
  "only detected by the lidar so its ice water content could not be estimated,\n"
107
112
  "or than there was rain below the ice associated with uncertain attenuation\n"
108
- "of the reflectivities in the ice.\n"
113
+ "of the reflectivities in the ice.\n",
109
114
  )
110
115
  return attributes
111
116
 
@@ -3,12 +3,15 @@ import numpy as np
3
3
  from numpy import ma
4
4
 
5
5
  from cloudnetpy import output, utils
6
+ from cloudnetpy.constants import G_TO_KG
6
7
  from cloudnetpy.metadata import MetaData
7
8
  from cloudnetpy.products.product_tools import IceClassification, IceSource
8
9
 
9
10
 
10
11
  def generate_iwc(
11
- categorize_file: str, output_file: str, uuid: str | None = None
12
+ categorize_file: str,
13
+ output_file: str,
14
+ uuid: str | None = None,
12
15
  ) -> str:
13
16
  """Generates Cloudnet ice water content product.
14
17
 
@@ -18,18 +21,22 @@ def generate_iwc(
18
21
  netCDF file.
19
22
 
20
23
  Args:
24
+ ----
21
25
  categorize_file: Categorize file name.
22
26
  output_file: Output file name.
23
27
  uuid: Set specific UUID for the file.
24
28
 
25
29
  Returns:
30
+ -------
26
31
  UUID of the generated file.
27
32
 
28
33
  Examples:
34
+ --------
29
35
  >>> from cloudnetpy.products import generate_iwc
30
36
  >>> generate_iwc('categorize.nc', 'iwc.nc')
31
37
 
32
38
  References:
39
+ ----------
33
40
  Hogan, R.J., M.P. Mittermaier, and A.J. Illingworth, 2006:
34
41
  The Retrieval of Ice Water Content from Radar Reflectivity Factor and
35
42
  Temperature and Its Use in Evaluating a Mesoscale Model.
@@ -50,8 +57,7 @@ def generate_iwc(
50
57
  attributes = _add_iwc_comment(attributes, iwc_source)
51
58
  attributes = _add_iwc_error_comment(attributes, lwp_prior, bias)
52
59
  output.update_attributes(iwc_source.data, attributes)
53
- uuid = output.save_product_file(product, iwc_source, output_file, uuid)
54
- return uuid
60
+ return output.save_product_file(product, iwc_source, output_file, uuid)
55
61
 
56
62
 
57
63
  class IwcSource(IceSource):
@@ -75,10 +81,10 @@ class IwcSource(IceSource):
75
81
  scaled_temperature += self.coefficients.Z
76
82
  return self.getvar("Z_error") * scaled_temperature * 10
77
83
 
78
- def _calc_error_in_uncorrected_ice() -> np.ndarray:
84
+ def _calc_error_in_uncorrected_ice() -> float:
79
85
  spec_liq_atten = 1.0 if self.wl_band == 0 else 4.5
80
86
  liq_atten_scaled = spec_liq_atten * self.coefficients.Z
81
- return lwp_prior * liq_atten_scaled * 2 * 1e-3 * 10
87
+ return lwp_prior * G_TO_KG * liq_atten_scaled * 2 * 10
82
88
 
83
89
  lwp_prior = 250 # g m-2
84
90
  retrieval_uncertainty = 1.7 # dB
@@ -86,7 +92,8 @@ class IwcSource(IceSource):
86
92
  error_uncorrected = _calc_error_in_uncorrected_ice()
87
93
  iwc_error = utils.l2norm(retrieval_uncertainty, random_error)
88
94
  iwc_error[ice_classification.uncorrected_ice] = utils.l2norm(
89
- retrieval_uncertainty, error_uncorrected
95
+ retrieval_uncertainty,
96
+ error_uncorrected,
90
97
  )
91
98
  iwc_error[
92
99
  (~ice_classification.is_ice | ice_classification.ice_above_rain)
@@ -104,7 +111,7 @@ def _add_iwc_error_comment(attributes: dict, lwp_prior, uncertainty: float) -> d
104
111
  "present beneath the ice but no microwave radiometer data were available to\n"
105
112
  "correct for the associated attenuation, the error also includes a\n"
106
113
  f"contribution equivalent to approximately {lwp_prior} g m-2 of liquid water\n"
107
- "path being uncorrected for."
114
+ "path being uncorrected for.",
108
115
  )
109
116
  return attributes
110
117
 
@@ -139,7 +146,7 @@ def _add_iwc_comment(attributes: dict, iwc: IwcSource) -> dict:
139
146
  "and liquid cloud occurred below the ice, the retrieval was still performed\n"
140
147
  "but its reliability is questionable due to the uncorrected liquid water\n"
141
148
  "attenuation. This is indicated by a value of 2 in the iwc_retrieval_status\n"
142
- "variable, and an increase in the value of the iwc_error variable."
149
+ "variable, and an increase in the value of the iwc_error variable.",
143
150
  )
144
151
  return attributes
145
152
 
@@ -183,7 +190,7 @@ DEFINITIONS = {
183
190
  "Value 7: Drizzle or rain that would have been classified as ice if the\n"
184
191
  " wet-bulb temperature were less than 0degC: may be ice if\n"
185
192
  " temperature is in error."
186
- )
193
+ ),
187
194
  }
188
195
 
189
196
  IWC_ATTRIBUTES = {
@@ -13,7 +13,9 @@ from cloudnetpy.products.product_tools import CategorizeBits, get_is_rain
13
13
 
14
14
 
15
15
  def generate_lwc(
16
- categorize_file: str, output_file: str, uuid: str | None = None
16
+ categorize_file: str,
17
+ output_file: str,
18
+ uuid: str | None = None,
17
19
  ) -> str:
18
20
  """Generates Cloudnet liquid water content product.
19
21
 
@@ -23,18 +25,22 @@ def generate_lwc(
23
25
  content of observed liquid clouds. The results are written in a netCDF file.
24
26
 
25
27
  Args:
28
+ ----
26
29
  categorize_file: Categorize file name.
27
30
  output_file: Output file name.
28
31
  uuid: Set specific UUID for the file.
29
32
 
30
33
  Returns:
34
+ -------
31
35
  str: UUID of the generated file.
32
36
 
33
37
  Examples:
38
+ --------
34
39
  >>> from cloudnetpy.products import generate_lwc
35
40
  >>> generate_lwc('categorize.nc', 'lwc.nc')
36
41
 
37
42
  References:
43
+ ----------
38
44
  Illingworth, A.J., R.J. Hogan, E. O'Connor, D. Bouniol, M.E. Brooks,
39
45
  J. Delanoé, D.P. Donovan, J.D. Eastment, N. Gaussiat, J.W. Goddard,
40
46
  M. Haeffelin, H.K. Baltink, O.A. Krasnov, J. Pelon, J. Piriou, A. Protat,
@@ -51,7 +57,7 @@ def generate_lwc(
51
57
  date = lwc_source.get_date()
52
58
  attributes = output.add_time_attribute(LWC_ATTRIBUTES, date)
53
59
  output.update_attributes(lwc_source.data, attributes)
54
- uuid = output.save_product_file(
60
+ return output.save_product_file(
55
61
  "lwc",
56
62
  lwc_source,
57
63
  output_file,
@@ -61,7 +67,6 @@ def generate_lwc(
61
67
  "lwp_error",
62
68
  ),
63
69
  )
64
- return uuid
65
70
 
66
71
 
67
72
  class LwcSource(DataSource):
@@ -71,9 +76,11 @@ class LwcSource(DataSource):
71
76
  structures and methods for holding the results.
72
77
 
73
78
  Args:
79
+ ----
74
80
  categorize_file: Categorize file name.
75
81
 
76
82
  Attributes:
83
+ ----------
77
84
  lwp (ndarray): 1D liquid water path.
78
85
  lwp_error (ndarray): 1D error of liquid water path.
79
86
  is_rain (ndarray): 1D array denoting presence of rain.
@@ -95,7 +102,10 @@ class LwcSource(DataSource):
95
102
  self.categorize_bits = CategorizeBits(categorize_file)
96
103
 
97
104
  def append_results(
98
- self, lwc: np.ndarray, status: np.ndarray, error: np.ndarray
105
+ self,
106
+ lwc: np.ndarray,
107
+ status: np.ndarray,
108
+ error: np.ndarray,
99
109
  ) -> None:
100
110
  self.append_data(lwc, "lwc", units="kg m-3")
101
111
  self.append_data(status, "lwc_retrieval_status")
@@ -112,9 +122,11 @@ class Lwc:
112
122
  """Class handling the actual LWC calculations.
113
123
 
114
124
  Args:
125
+ ----
115
126
  lwc_source: The :class:`LwcSource` instance.
116
127
 
117
128
  Attributes:
129
+ ----------
118
130
  lwc_source (LwcSource): The :class:`LwcSource` instance.
119
131
  dheight (float): Median difference in height vector.
120
132
  is_liquid (ndarray): 2D array denoting liquid.
@@ -138,7 +150,8 @@ class Lwc:
138
150
  def _init_lwc_adiabatic(self) -> np.ndarray:
139
151
  """Returns theoretical adiabatic lwc in liquid clouds (kg/m3)."""
140
152
  lwc_dz = atmos.fill_clouds_with_lwc_dz(
141
- self.lwc_source.atmosphere, self.is_liquid
153
+ self.lwc_source.atmosphere,
154
+ self.is_liquid,
142
155
  )
143
156
  return atmos.calc_adiabatic_lwc(lwc_dz, self.dheight)
144
157
 
@@ -148,7 +161,8 @@ class Lwc:
148
161
  Calculates LWC for ALL profiles (rain, lwp > theoretical, etc.),
149
162
  """
150
163
  lwc_scaled = atmos.distribute_lwp_to_liquid_clouds(
151
- self.lwc_adiabatic, self.lwc_source.lwp
164
+ self.lwc_adiabatic,
165
+ self.lwc_source.lwp,
152
166
  )
153
167
  return lwc_scaled / self.dheight
154
168
 
@@ -161,10 +175,12 @@ class CloudAdjustor:
161
175
  """Adjusts clouds (where possible) so that theoretical and measured LWP agree.
162
176
 
163
177
  Args:
178
+ ----
164
179
  lwc_source: The :class:`LwcSource` instance.
165
180
  lwc: The :class:`Lwc` instance.
166
181
 
167
182
  Attributes:
183
+ ----------
168
184
  lwc_source (LwcSource): The :class:`LwcSource` instance.
169
185
  lwc (ndarray): Liquid water content data.
170
186
  is_liquid (ndarray): 2D array denoting liquid.
@@ -196,7 +212,8 @@ class CloudAdjustor:
196
212
 
197
213
  def _adjust_cloud_tops(self, adjustable_clouds: np.ndarray) -> None:
198
214
  """Adjusts cloud top index so that measured lwc corresponds to theoretical
199
- value."""
215
+ value.
216
+ """
200
217
  for time_index in np.unique(np.where(adjustable_clouds)[0]):
201
218
  base_index = np.where(adjustable_clouds[time_index, :])[0][0]
202
219
  self._update_status(time_index)
@@ -234,8 +251,7 @@ class CloudAdjustor:
234
251
  detection_type[~top_clouds] = 0
235
252
  lidar_only_clouds = self._find_lidar_only_clouds(detection_type)
236
253
  top_clouds[~lidar_only_clouds, :] = 0
237
- top_clouds = self._remove_good_profiles(top_clouds)
238
- return top_clouds
254
+ return self._remove_good_profiles(top_clouds)
239
255
 
240
256
  def _find_topmost_clouds(self) -> np.ndarray:
241
257
  top_clouds = np.copy(self.is_liquid)
@@ -256,9 +272,11 @@ class CloudAdjustor:
256
272
  """Finds top clouds that contain only lidar-detected pixels.
257
273
 
258
274
  Args:
275
+ ----
259
276
  detection: Array of integers where 1=lidar, 2=radar, 3=both.
260
277
 
261
278
  Returns:
279
+ -------
262
280
  Boolean array containing top-clouds that are detected only by lidar.
263
281
 
264
282
  """
@@ -295,10 +313,12 @@ class LwcError:
295
313
  """Calculates liquid water content error.
296
314
 
297
315
  Args:
316
+ ----
298
317
  lwc_source: The :class:`LwcSource` instance.
299
318
  lwc: The :class:`Lwc` instance.
300
319
 
301
320
  Attributes:
321
+ ----------
302
322
  lwc_source (LwcSource): The :class:`LwcSource` instance.
303
323
  lwc (ndarray): Liquid water content data.
304
324
  error (ndarray): 2D array storing lwc_error.
@@ -315,7 +335,8 @@ class LwcError:
315
335
  lwc_relative_error = self._calc_lwc_relative_error()
316
336
  lwp_relative_error = self._calc_lwp_relative_error()
317
337
  combined_error = self._calc_combined_error(
318
- lwc_relative_error, lwp_relative_error
338
+ lwc_relative_error,
339
+ lwp_relative_error,
319
340
  )
320
341
  return self._fill_error_array(combined_error)
321
342
 
@@ -325,7 +346,8 @@ class LwcError:
325
346
  return self._limit_error(error, 5)
326
347
 
327
348
  def _calc_lwc_gradient(self) -> np.ndarray:
328
- assert isinstance(self.lwc, ma.MaskedArray)
349
+ if not isinstance(self.lwc, ma.MaskedArray):
350
+ self.lwc = ma.masked_array(self.lwc)
329
351
  gradient_elements = np.gradient(self.lwc.filled(0))
330
352
  return utils.l2norm(*gradient_elements)
331
353
 
@@ -424,7 +446,7 @@ DEFINITIONS = {
424
446
  " attenuated."
425
447
  "Value 6: Rain present: cloud extent is difficult to ascertain and liquid\n"
426
448
  " water path also uncertain."
427
- )
449
+ ),
428
450
  }
429
451
 
430
452
 
@@ -11,7 +11,9 @@ from cloudnetpy.products import product_tools
11
11
 
12
12
 
13
13
  def generate_mwr_multi(
14
- mwr_l1c_file: str, output_file: str, uuid: str | None = None
14
+ mwr_l1c_file: str,
15
+ output_file: str,
16
+ uuid: str | None = None,
15
17
  ) -> str:
16
18
  file_uuid = uuid if uuid is not None else utils.get_uuid()
17
19
 
@@ -28,6 +30,7 @@ def generate_mwr_multi(
28
30
  for prod, file in zip(
29
31
  ("2P02", "2P03", "2P04", "2P07", "2P08"),
30
32
  (temp_file, abs_hum_file, rel_hum_file, t_pot_file, eq_temp_file),
33
+ strict=True,
31
34
  ):
32
35
  try:
33
36
  lev2_to_nc(
@@ -10,7 +10,9 @@ from cloudnetpy.products import product_tools
10
10
 
11
11
 
12
12
  def generate_mwr_single(
13
- mwr_l1c_file: str, output_file: str, uuid: str | None = None
13
+ mwr_l1c_file: str,
14
+ output_file: str,
15
+ uuid: str | None = None,
14
16
  ) -> str:
15
17
  file_uuid = uuid if uuid is not None else utils.get_uuid()
16
18
 
@@ -26,6 +28,7 @@ def generate_mwr_single(
26
28
  for prod, file in zip(
27
29
  ("2I01", "2I02", "2P01", "2P03"),
28
30
  (lwp_file, iwv_file, t_prof_file, abs_hum_file),
31
+ strict=True,
29
32
  ):
30
33
  lev2_to_nc(prod, mwr_l1c_file, file.name, coeff_files=coeffs)
31
34