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.
- cloudnetpy/categorize/atmos.py +46 -14
- cloudnetpy/categorize/atmos_utils.py +11 -1
- cloudnetpy/categorize/categorize.py +38 -21
- cloudnetpy/categorize/classify.py +31 -9
- cloudnetpy/categorize/containers.py +19 -7
- cloudnetpy/categorize/droplet.py +24 -8
- cloudnetpy/categorize/falling.py +17 -7
- cloudnetpy/categorize/freezing.py +19 -5
- cloudnetpy/categorize/insects.py +27 -14
- cloudnetpy/categorize/lidar.py +38 -36
- cloudnetpy/categorize/melting.py +19 -9
- cloudnetpy/categorize/model.py +28 -9
- cloudnetpy/categorize/mwr.py +4 -2
- cloudnetpy/categorize/radar.py +58 -22
- cloudnetpy/cloudnetarray.py +15 -6
- cloudnetpy/concat_lib.py +39 -16
- cloudnetpy/constants.py +7 -0
- cloudnetpy/datasource.py +39 -19
- cloudnetpy/instruments/basta.py +6 -2
- cloudnetpy/instruments/campbell_scientific.py +33 -16
- cloudnetpy/instruments/ceilo.py +30 -13
- cloudnetpy/instruments/ceilometer.py +76 -37
- cloudnetpy/instruments/cl61d.py +8 -3
- cloudnetpy/instruments/cloudnet_instrument.py +2 -1
- cloudnetpy/instruments/copernicus.py +27 -14
- cloudnetpy/instruments/disdrometer/common.py +51 -32
- cloudnetpy/instruments/disdrometer/parsivel.py +79 -48
- cloudnetpy/instruments/disdrometer/thies.py +10 -6
- cloudnetpy/instruments/galileo.py +23 -12
- cloudnetpy/instruments/hatpro.py +27 -11
- cloudnetpy/instruments/instruments.py +4 -1
- cloudnetpy/instruments/lufft.py +20 -11
- cloudnetpy/instruments/mira.py +60 -49
- cloudnetpy/instruments/mrr.py +31 -20
- cloudnetpy/instruments/nc_lidar.py +15 -6
- cloudnetpy/instruments/nc_radar.py +31 -22
- cloudnetpy/instruments/pollyxt.py +36 -21
- cloudnetpy/instruments/radiometrics.py +32 -18
- cloudnetpy/instruments/rpg.py +48 -22
- cloudnetpy/instruments/rpg_reader.py +39 -30
- cloudnetpy/instruments/vaisala.py +39 -27
- cloudnetpy/instruments/weather_station.py +15 -11
- cloudnetpy/metadata.py +3 -1
- cloudnetpy/model_evaluation/file_handler.py +31 -21
- cloudnetpy/model_evaluation/metadata.py +3 -1
- cloudnetpy/model_evaluation/model_metadata.py +1 -1
- cloudnetpy/model_evaluation/plotting/plot_tools.py +20 -15
- cloudnetpy/model_evaluation/plotting/plotting.py +114 -64
- cloudnetpy/model_evaluation/products/advance_methods.py +48 -28
- cloudnetpy/model_evaluation/products/grid_methods.py +44 -19
- cloudnetpy/model_evaluation/products/model_products.py +22 -18
- cloudnetpy/model_evaluation/products/observation_products.py +15 -9
- cloudnetpy/model_evaluation/products/product_resampling.py +14 -4
- cloudnetpy/model_evaluation/products/tools.py +16 -7
- cloudnetpy/model_evaluation/statistics/statistical_methods.py +28 -15
- cloudnetpy/model_evaluation/tests/e2e/conftest.py +3 -3
- cloudnetpy/model_evaluation/tests/e2e/process_cf/main.py +9 -5
- cloudnetpy/model_evaluation/tests/e2e/process_cf/tests.py +14 -13
- cloudnetpy/model_evaluation/tests/e2e/process_iwc/main.py +9 -5
- cloudnetpy/model_evaluation/tests/e2e/process_iwc/tests.py +14 -13
- cloudnetpy/model_evaluation/tests/e2e/process_lwc/main.py +9 -5
- cloudnetpy/model_evaluation/tests/e2e/process_lwc/tests.py +14 -13
- cloudnetpy/model_evaluation/tests/unit/conftest.py +11 -11
- cloudnetpy/model_evaluation/tests/unit/test_advance_methods.py +33 -27
- cloudnetpy/model_evaluation/tests/unit/test_grid_methods.py +83 -83
- cloudnetpy/model_evaluation/tests/unit/test_model_products.py +23 -21
- cloudnetpy/model_evaluation/tests/unit/test_observation_products.py +24 -25
- cloudnetpy/model_evaluation/tests/unit/test_plot_tools.py +40 -39
- cloudnetpy/model_evaluation/tests/unit/test_plotting.py +12 -11
- cloudnetpy/model_evaluation/tests/unit/test_statistical_methods.py +30 -30
- cloudnetpy/model_evaluation/tests/unit/test_tools.py +18 -17
- cloudnetpy/model_evaluation/utils.py +3 -2
- cloudnetpy/output.py +45 -19
- cloudnetpy/plotting/plot_meta.py +35 -11
- cloudnetpy/plotting/plotting.py +172 -104
- cloudnetpy/products/classification.py +20 -8
- cloudnetpy/products/der.py +25 -10
- cloudnetpy/products/drizzle.py +41 -26
- cloudnetpy/products/drizzle_error.py +10 -5
- cloudnetpy/products/drizzle_tools.py +43 -24
- cloudnetpy/products/ier.py +10 -5
- cloudnetpy/products/iwc.py +16 -9
- cloudnetpy/products/lwc.py +34 -12
- cloudnetpy/products/mwr_multi.py +4 -1
- cloudnetpy/products/mwr_single.py +4 -1
- cloudnetpy/products/product_tools.py +33 -10
- cloudnetpy/utils.py +175 -74
- cloudnetpy/version.py +1 -1
- {cloudnetpy-1.55.20.dist-info → cloudnetpy-1.55.22.dist-info}/METADATA +11 -10
- cloudnetpy-1.55.22.dist-info/RECORD +114 -0
- docs/source/conf.py +2 -2
- cloudnetpy-1.55.20.dist-info/RECORD +0 -114
- {cloudnetpy-1.55.20.dist-info → cloudnetpy-1.55.22.dist-info}/LICENSE +0 -0
- {cloudnetpy-1.55.20.dist-info → cloudnetpy-1.55.22.dist-info}/WHEEL +0 -0
- {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 "/
|
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(
|
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],
|
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,
|
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,
|
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
|
cloudnetpy/products/ier.py
CHANGED
@@ -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,
|
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
|
-
|
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
|
|
cloudnetpy/products/iwc.py
CHANGED
@@ -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,
|
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
|
-
|
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() ->
|
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 *
|
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,
|
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 = {
|
cloudnetpy/products/lwc.py
CHANGED
@@ -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,
|
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
|
-
|
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,
|
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,
|
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,
|
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
|
-
|
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,
|
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
|
-
|
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
|
|
cloudnetpy/products/mwr_multi.py
CHANGED
@@ -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,
|
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,
|
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
|
|