cloudnetpy 1.49.9__py3-none-any.whl → 1.87.3__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/__init__.py +1 -2
- cloudnetpy/categorize/atmos_utils.py +297 -67
- cloudnetpy/categorize/attenuation.py +31 -0
- cloudnetpy/categorize/attenuations/__init__.py +37 -0
- cloudnetpy/categorize/attenuations/gas_attenuation.py +30 -0
- cloudnetpy/categorize/attenuations/liquid_attenuation.py +84 -0
- cloudnetpy/categorize/attenuations/melting_attenuation.py +78 -0
- cloudnetpy/categorize/attenuations/rain_attenuation.py +84 -0
- cloudnetpy/categorize/categorize.py +332 -156
- cloudnetpy/categorize/classify.py +127 -125
- cloudnetpy/categorize/containers.py +107 -76
- cloudnetpy/categorize/disdrometer.py +40 -0
- cloudnetpy/categorize/droplet.py +23 -21
- cloudnetpy/categorize/falling.py +53 -24
- cloudnetpy/categorize/freezing.py +25 -12
- cloudnetpy/categorize/insects.py +35 -23
- cloudnetpy/categorize/itu.py +243 -0
- cloudnetpy/categorize/lidar.py +36 -41
- cloudnetpy/categorize/melting.py +34 -26
- cloudnetpy/categorize/model.py +84 -37
- cloudnetpy/categorize/mwr.py +18 -14
- cloudnetpy/categorize/radar.py +215 -102
- cloudnetpy/cli.py +578 -0
- cloudnetpy/cloudnetarray.py +43 -89
- cloudnetpy/concat_lib.py +218 -78
- cloudnetpy/constants.py +28 -10
- cloudnetpy/datasource.py +61 -86
- cloudnetpy/exceptions.py +49 -20
- cloudnetpy/instruments/__init__.py +5 -0
- cloudnetpy/instruments/basta.py +29 -12
- cloudnetpy/instruments/bowtie.py +135 -0
- cloudnetpy/instruments/ceilo.py +138 -115
- cloudnetpy/instruments/ceilometer.py +164 -80
- cloudnetpy/instruments/cl61d.py +21 -5
- cloudnetpy/instruments/cloudnet_instrument.py +74 -36
- cloudnetpy/instruments/copernicus.py +108 -30
- cloudnetpy/instruments/da10.py +54 -0
- cloudnetpy/instruments/disdrometer/common.py +126 -223
- cloudnetpy/instruments/disdrometer/parsivel.py +453 -94
- cloudnetpy/instruments/disdrometer/thies.py +254 -87
- cloudnetpy/instruments/fd12p.py +201 -0
- cloudnetpy/instruments/galileo.py +65 -23
- cloudnetpy/instruments/hatpro.py +123 -49
- cloudnetpy/instruments/instruments.py +113 -1
- cloudnetpy/instruments/lufft.py +39 -17
- cloudnetpy/instruments/mira.py +268 -61
- cloudnetpy/instruments/mrr.py +187 -0
- cloudnetpy/instruments/nc_lidar.py +19 -8
- cloudnetpy/instruments/nc_radar.py +109 -55
- cloudnetpy/instruments/pollyxt.py +135 -51
- cloudnetpy/instruments/radiometrics.py +313 -59
- cloudnetpy/instruments/rain_e_h3.py +171 -0
- cloudnetpy/instruments/rpg.py +321 -189
- cloudnetpy/instruments/rpg_reader.py +74 -40
- cloudnetpy/instruments/toa5.py +49 -0
- cloudnetpy/instruments/vaisala.py +95 -343
- cloudnetpy/instruments/weather_station.py +774 -105
- cloudnetpy/metadata.py +90 -19
- cloudnetpy/model_evaluation/file_handler.py +55 -52
- cloudnetpy/model_evaluation/metadata.py +46 -20
- cloudnetpy/model_evaluation/model_metadata.py +1 -1
- cloudnetpy/model_evaluation/plotting/plot_tools.py +32 -37
- cloudnetpy/model_evaluation/plotting/plotting.py +327 -117
- cloudnetpy/model_evaluation/products/advance_methods.py +92 -83
- cloudnetpy/model_evaluation/products/grid_methods.py +88 -63
- cloudnetpy/model_evaluation/products/model_products.py +43 -35
- cloudnetpy/model_evaluation/products/observation_products.py +41 -35
- cloudnetpy/model_evaluation/products/product_resampling.py +17 -7
- cloudnetpy/model_evaluation/products/tools.py +29 -20
- cloudnetpy/model_evaluation/statistics/statistical_methods.py +30 -20
- 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 +15 -14
- cloudnetpy/model_evaluation/tests/e2e/process_iwc/main.py +9 -5
- cloudnetpy/model_evaluation/tests/e2e/process_iwc/tests.py +15 -14
- cloudnetpy/model_evaluation/tests/e2e/process_lwc/main.py +9 -5
- cloudnetpy/model_evaluation/tests/e2e/process_lwc/tests.py +15 -14
- cloudnetpy/model_evaluation/tests/unit/conftest.py +42 -41
- cloudnetpy/model_evaluation/tests/unit/test_advance_methods.py +41 -48
- cloudnetpy/model_evaluation/tests/unit/test_grid_methods.py +216 -194
- cloudnetpy/model_evaluation/tests/unit/test_model_products.py +23 -21
- cloudnetpy/model_evaluation/tests/unit/test_observation_products.py +37 -38
- cloudnetpy/model_evaluation/tests/unit/test_plot_tools.py +43 -40
- cloudnetpy/model_evaluation/tests/unit/test_plotting.py +30 -36
- cloudnetpy/model_evaluation/tests/unit/test_statistical_methods.py +68 -31
- cloudnetpy/model_evaluation/tests/unit/test_tools.py +33 -26
- cloudnetpy/model_evaluation/utils.py +2 -1
- cloudnetpy/output.py +170 -111
- cloudnetpy/plotting/__init__.py +2 -1
- cloudnetpy/plotting/plot_meta.py +562 -822
- cloudnetpy/plotting/plotting.py +1142 -704
- cloudnetpy/products/__init__.py +1 -0
- cloudnetpy/products/classification.py +370 -88
- cloudnetpy/products/der.py +85 -55
- cloudnetpy/products/drizzle.py +77 -34
- cloudnetpy/products/drizzle_error.py +15 -11
- cloudnetpy/products/drizzle_tools.py +79 -59
- cloudnetpy/products/epsilon.py +211 -0
- cloudnetpy/products/ier.py +27 -50
- cloudnetpy/products/iwc.py +55 -48
- cloudnetpy/products/lwc.py +96 -70
- cloudnetpy/products/mwr_tools.py +186 -0
- cloudnetpy/products/product_tools.py +170 -128
- cloudnetpy/utils.py +455 -240
- cloudnetpy/version.py +2 -2
- {cloudnetpy-1.49.9.dist-info → cloudnetpy-1.87.3.dist-info}/METADATA +44 -40
- cloudnetpy-1.87.3.dist-info/RECORD +127 -0
- {cloudnetpy-1.49.9.dist-info → cloudnetpy-1.87.3.dist-info}/WHEEL +1 -1
- cloudnetpy-1.87.3.dist-info/entry_points.txt +2 -0
- docs/source/conf.py +2 -2
- cloudnetpy/categorize/atmos.py +0 -361
- cloudnetpy/products/mwr_multi.py +0 -68
- cloudnetpy/products/mwr_single.py +0 -75
- cloudnetpy-1.49.9.dist-info/RECORD +0 -112
- {cloudnetpy-1.49.9.dist-info → cloudnetpy-1.87.3.dist-info/licenses}/LICENSE +0 -0
- {cloudnetpy-1.49.9.dist-info → cloudnetpy-1.87.3.dist-info}/top_level.txt +0 -0
|
@@ -1,65 +1,87 @@
|
|
|
1
1
|
"""General helper classes and functions for all products."""
|
|
2
|
-
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
from os import PathLike
|
|
5
|
+
from typing import NamedTuple
|
|
3
6
|
|
|
4
7
|
import netCDF4
|
|
5
8
|
import numpy as np
|
|
9
|
+
import numpy.typing as npt
|
|
6
10
|
from numpy import ma
|
|
11
|
+
from numpy.typing import NDArray
|
|
7
12
|
|
|
8
13
|
from cloudnetpy import constants, utils
|
|
9
14
|
from cloudnetpy.categorize import atmos_utils
|
|
10
15
|
from cloudnetpy.datasource import DataSource
|
|
11
16
|
|
|
12
|
-
IceCoefficients = namedtuple("IceCoefficients", "K2liquid0 ZT T Z c")
|
|
13
17
|
|
|
18
|
+
class IceCoefficients(NamedTuple):
|
|
19
|
+
"""Coefficients for ice effective radius retrieval."""
|
|
14
20
|
|
|
15
|
-
|
|
16
|
-
|
|
21
|
+
K2liquid0: float
|
|
22
|
+
ZT: float
|
|
23
|
+
T: float
|
|
24
|
+
Z: float
|
|
25
|
+
c: float
|
|
17
26
|
|
|
18
|
-
Args:
|
|
19
|
-
categorize_file (str): Categorize file name.
|
|
20
27
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
28
|
+
@dataclass
|
|
29
|
+
class CategoryBits:
|
|
30
|
+
droplet: NDArray[np.bool_]
|
|
31
|
+
falling: NDArray[np.bool_]
|
|
32
|
+
freezing: NDArray[np.bool_]
|
|
33
|
+
melting: NDArray[np.bool_]
|
|
34
|
+
aerosol: NDArray[np.bool_]
|
|
35
|
+
insect: NDArray[np.bool_]
|
|
24
36
|
|
|
25
|
-
quality_bits (dict): Dictionary containing boolean fields for `radar`,
|
|
26
|
-
`lidar`, `clutter`, `molecular`, `attenuated` and `corrected`.
|
|
27
37
|
|
|
28
|
-
|
|
38
|
+
@dataclass
|
|
39
|
+
class QualityBits:
|
|
40
|
+
radar: NDArray[np.bool_]
|
|
41
|
+
lidar: NDArray[np.bool_]
|
|
42
|
+
clutter: NDArray[np.bool_]
|
|
43
|
+
molecular: NDArray[np.bool_]
|
|
44
|
+
attenuated_liquid: NDArray[np.bool_]
|
|
45
|
+
corrected_liquid: NDArray[np.bool_]
|
|
46
|
+
attenuated_rain: NDArray[np.bool_]
|
|
47
|
+
corrected_rain: NDArray[np.bool_]
|
|
48
|
+
attenuated_melting: NDArray[np.bool_]
|
|
49
|
+
corrected_melting: NDArray[np.bool_]
|
|
29
50
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
"cold",
|
|
34
|
-
"melting",
|
|
35
|
-
"aerosol",
|
|
36
|
-
"insect",
|
|
37
|
-
)
|
|
38
|
-
|
|
39
|
-
quality_keys = (
|
|
40
|
-
"radar",
|
|
41
|
-
"lidar",
|
|
42
|
-
"clutter",
|
|
43
|
-
"molecular",
|
|
44
|
-
"attenuated",
|
|
45
|
-
"corrected",
|
|
46
|
-
)
|
|
47
|
-
|
|
48
|
-
def __init__(self, categorize_file: str):
|
|
51
|
+
|
|
52
|
+
class CategorizeBits:
|
|
53
|
+
def __init__(self, categorize_file: str | PathLike) -> None:
|
|
49
54
|
self._categorize_file = categorize_file
|
|
50
|
-
self.category_bits = self.
|
|
51
|
-
self.quality_bits = self.
|
|
55
|
+
self.category_bits = self._read_category_bits()
|
|
56
|
+
self.quality_bits = self._read_quality_bits()
|
|
52
57
|
|
|
53
|
-
def
|
|
54
|
-
"""Converts bitfield into dictionary."""
|
|
58
|
+
def _read_category_bits(self) -> CategoryBits:
|
|
55
59
|
with netCDF4.Dataset(self._categorize_file) as nc:
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
60
|
+
bits = nc.variables["category_bits"][:]
|
|
61
|
+
return CategoryBits(
|
|
62
|
+
droplet=utils.isbit(bits, 0),
|
|
63
|
+
falling=utils.isbit(bits, 1),
|
|
64
|
+
freezing=utils.isbit(bits, 2),
|
|
65
|
+
melting=utils.isbit(bits, 3),
|
|
66
|
+
aerosol=utils.isbit(bits, 4),
|
|
67
|
+
insect=utils.isbit(bits, 5),
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
def _read_quality_bits(self) -> QualityBits:
|
|
71
|
+
with netCDF4.Dataset(self._categorize_file) as nc:
|
|
72
|
+
bits = nc.variables["quality_bits"][:]
|
|
73
|
+
return QualityBits(
|
|
74
|
+
radar=utils.isbit(bits, 0),
|
|
75
|
+
lidar=utils.isbit(bits, 1),
|
|
76
|
+
clutter=utils.isbit(bits, 2),
|
|
77
|
+
molecular=utils.isbit(bits, 3),
|
|
78
|
+
attenuated_liquid=utils.isbit(bits, 4),
|
|
79
|
+
corrected_liquid=utils.isbit(bits, 5),
|
|
80
|
+
attenuated_rain=utils.isbit(bits, 6),
|
|
81
|
+
corrected_rain=utils.isbit(bits, 7),
|
|
82
|
+
attenuated_melting=utils.isbit(bits, 8),
|
|
83
|
+
corrected_melting=utils.isbit(bits, 9),
|
|
84
|
+
)
|
|
63
85
|
|
|
64
86
|
|
|
65
87
|
class ProductClassification(CategorizeBits):
|
|
@@ -74,7 +96,7 @@ class ProductClassification(CategorizeBits):
|
|
|
74
96
|
|
|
75
97
|
"""
|
|
76
98
|
|
|
77
|
-
def __init__(self, categorize_file: str):
|
|
99
|
+
def __init__(self, categorize_file: str | PathLike) -> None:
|
|
78
100
|
super().__init__(categorize_file)
|
|
79
101
|
self.is_rain = get_is_rain(categorize_file)
|
|
80
102
|
|
|
@@ -84,79 +106,99 @@ class IceClassification(ProductClassification):
|
|
|
84
106
|
Child of ProductClassification().
|
|
85
107
|
"""
|
|
86
108
|
|
|
87
|
-
def __init__(self, categorize_file: str):
|
|
109
|
+
def __init__(self, categorize_file: str | PathLike) -> None:
|
|
88
110
|
super().__init__(categorize_file)
|
|
111
|
+
self._is_attenuated = self._find_attenuated()
|
|
112
|
+
self._is_corrected = self._find_corrected()
|
|
89
113
|
self.is_ice = self._find_ice()
|
|
90
114
|
self.would_be_ice = self._find_would_be_ice()
|
|
91
115
|
self.corrected_ice = self._find_corrected_ice()
|
|
92
116
|
self.uncorrected_ice = self._find_uncorrected_ice()
|
|
93
117
|
self.ice_above_rain = self._find_ice_above_rain()
|
|
94
|
-
self.
|
|
118
|
+
self.clear_above_rain = self._find_clear_above_rain()
|
|
95
119
|
|
|
96
|
-
def
|
|
120
|
+
def _find_clear_above_rain(self) -> npt.NDArray:
|
|
97
121
|
return (
|
|
98
|
-
self.
|
|
99
|
-
& self.category_bits
|
|
100
|
-
& ~self.category_bits
|
|
101
|
-
& ~self.category_bits["insect"]
|
|
122
|
+
utils.transpose(self.is_rain) * ~self.is_ice
|
|
123
|
+
& self.category_bits.freezing
|
|
124
|
+
& ~self.category_bits.melting
|
|
102
125
|
)
|
|
103
126
|
|
|
104
|
-
def
|
|
105
|
-
|
|
106
|
-
self.
|
|
107
|
-
|
|
108
|
-
|
|
127
|
+
def _find_attenuated(self) -> npt.NDArray:
|
|
128
|
+
return (
|
|
129
|
+
self.quality_bits.attenuated_liquid
|
|
130
|
+
| self.quality_bits.attenuated_rain
|
|
131
|
+
| self.quality_bits.attenuated_melting
|
|
109
132
|
)
|
|
110
|
-
return warm_falling | self.category_bits["melting"]
|
|
111
133
|
|
|
112
|
-
def
|
|
134
|
+
def _find_corrected(self) -> npt.NDArray:
|
|
113
135
|
return (
|
|
114
|
-
self.
|
|
115
|
-
|
|
116
|
-
|
|
136
|
+
self.quality_bits.corrected_liquid
|
|
137
|
+
| self.quality_bits.corrected_rain
|
|
138
|
+
| self.quality_bits.corrected_melting
|
|
139
|
+
)
|
|
140
|
+
|
|
141
|
+
def _find_ice(self) -> npt.NDArray:
|
|
142
|
+
return (
|
|
143
|
+
self.category_bits.falling
|
|
144
|
+
& self.category_bits.freezing
|
|
145
|
+
& ~self.category_bits.melting
|
|
146
|
+
& ~self.category_bits.insect
|
|
147
|
+
)
|
|
148
|
+
|
|
149
|
+
def _find_would_be_ice(self) -> npt.NDArray:
|
|
150
|
+
warm_falling = (
|
|
151
|
+
self.category_bits.falling
|
|
152
|
+
& ~self.category_bits.freezing
|
|
153
|
+
& ~self.category_bits.insect
|
|
117
154
|
)
|
|
155
|
+
return warm_falling | self.category_bits.melting
|
|
118
156
|
|
|
119
|
-
def
|
|
157
|
+
def _find_corrected_ice(self) -> npt.NDArray:
|
|
158
|
+
return self.is_ice & self._is_attenuated & self._is_corrected
|
|
159
|
+
|
|
160
|
+
def _find_uncorrected_ice(self) -> npt.NDArray:
|
|
161
|
+
uncorrected_melting = (
|
|
162
|
+
self.quality_bits.attenuated_melting & ~self.quality_bits.corrected_melting
|
|
163
|
+
)
|
|
164
|
+
uncorrected_rain = (
|
|
165
|
+
self.quality_bits.attenuated_rain & ~self.quality_bits.corrected_rain
|
|
166
|
+
)
|
|
167
|
+
uncorrected_liquid = (
|
|
168
|
+
self.quality_bits.attenuated_liquid & ~self.quality_bits.corrected_liquid
|
|
169
|
+
)
|
|
120
170
|
return (
|
|
121
171
|
self.is_ice
|
|
122
|
-
& self.
|
|
123
|
-
&
|
|
172
|
+
& self._is_attenuated
|
|
173
|
+
& (uncorrected_melting | uncorrected_rain | uncorrected_liquid)
|
|
124
174
|
)
|
|
125
175
|
|
|
126
|
-
def _find_ice_above_rain(self) ->
|
|
176
|
+
def _find_ice_above_rain(self) -> npt.NDArray:
|
|
127
177
|
is_rain = utils.transpose(self.is_rain)
|
|
128
178
|
return (self.is_ice * is_rain) == 1
|
|
129
179
|
|
|
130
|
-
def _find_cold_above_rain(self) -> np.ndarray:
|
|
131
|
-
is_cold = self.category_bits["cold"]
|
|
132
|
-
is_rain = utils.transpose(self.is_rain)
|
|
133
|
-
is_cold_rain = (is_cold * is_rain) == 1
|
|
134
|
-
return is_cold_rain & ~self.category_bits["melting"]
|
|
135
|
-
|
|
136
180
|
|
|
137
181
|
class IceSource(DataSource):
|
|
138
182
|
"""Base class for different ice products."""
|
|
139
183
|
|
|
140
|
-
def __init__(self, categorize_file: str, product: str):
|
|
184
|
+
def __init__(self, categorize_file: str | PathLike, product: str) -> None:
|
|
141
185
|
super().__init__(categorize_file)
|
|
142
|
-
self.
|
|
143
|
-
self.
|
|
186
|
+
self.radar_frequency = float(self.getvar("radar_frequency"))
|
|
187
|
+
self.wl_band = utils.get_wl_band(self.radar_frequency)
|
|
188
|
+
self.temperature = _get_temperature(categorize_file)
|
|
144
189
|
self.product = product
|
|
145
190
|
self.coefficients = self._get_coefficients()
|
|
146
191
|
|
|
147
|
-
def
|
|
148
|
-
self,
|
|
192
|
+
def append_icy_data(
|
|
193
|
+
self,
|
|
194
|
+
ice_classification: IceClassification,
|
|
149
195
|
) -> None:
|
|
150
196
|
"""Adds the main variable (including ice above rain)."""
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
"""Adds the main variable (excluding rain)."""
|
|
157
|
-
data = ma.copy(self.data[f"{self.product}_inc_rain"][:])
|
|
158
|
-
data[ice_classification.ice_above_rain] = ma.masked
|
|
159
|
-
self.append_data(data, self.product)
|
|
197
|
+
data = self._convert_z()
|
|
198
|
+
data[~ice_classification.is_ice | ice_classification.uncorrected_ice] = (
|
|
199
|
+
ma.masked
|
|
200
|
+
)
|
|
201
|
+
self.append_data(data, f"{self.product}")
|
|
160
202
|
|
|
161
203
|
def append_status(self, ice_classification: IceClassification) -> None:
|
|
162
204
|
"""Adds the status of retrieval."""
|
|
@@ -165,10 +207,9 @@ class IceSource(DataSource):
|
|
|
165
207
|
is_data = ~data.mask
|
|
166
208
|
retrieval_status[is_data] = 1
|
|
167
209
|
retrieval_status[is_data & ice_classification.corrected_ice] = 3
|
|
168
|
-
retrieval_status[is_data & ice_classification.uncorrected_ice] = 2
|
|
169
210
|
retrieval_status[~is_data & ice_classification.is_ice] = 4
|
|
170
|
-
retrieval_status[ice_classification.
|
|
171
|
-
retrieval_status[ice_classification.
|
|
211
|
+
retrieval_status[ice_classification.uncorrected_ice] = 2
|
|
212
|
+
retrieval_status[ice_classification.clear_above_rain] = 6
|
|
172
213
|
retrieval_status[ice_classification.would_be_ice & (retrieval_status == 0)] = 7
|
|
173
214
|
self.append_data(retrieval_status, f"{self.product}_retrieval_status")
|
|
174
215
|
|
|
@@ -178,18 +219,27 @@ class IceSource(DataSource):
|
|
|
178
219
|
References:
|
|
179
220
|
Hogan et.al. 2006, https://doi.org/10.1175/JAM2340.1
|
|
180
221
|
"""
|
|
222
|
+
msg = f"Unsupported band: {self.wl_band}"
|
|
181
223
|
if self.product == "ier":
|
|
182
|
-
if self.wl_band ==
|
|
224
|
+
if self.wl_band == "Ka":
|
|
183
225
|
return IceCoefficients(0.878, -0.000205, -0.0015, 0.0016, -1.52)
|
|
184
|
-
|
|
185
|
-
|
|
226
|
+
if self.wl_band == "W":
|
|
227
|
+
return IceCoefficients(0.669, -0.000296, -0.00193, -0.000, -1.502)
|
|
228
|
+
raise ValueError(msg)
|
|
229
|
+
if self.wl_band == "Ka":
|
|
186
230
|
return IceCoefficients(0.878, 0.000242, -0.0186, 0.0699, -1.63)
|
|
187
|
-
|
|
231
|
+
if self.wl_band == "W":
|
|
232
|
+
return IceCoefficients(0.669, 0.000580, -0.00706, 0.0923, -0.992)
|
|
233
|
+
raise ValueError(msg)
|
|
188
234
|
|
|
189
|
-
def _convert_z(self, z_variable: str = "Z") ->
|
|
235
|
+
def _convert_z(self, z_variable: str = "Z") -> npt.NDArray:
|
|
190
236
|
"""Calculates temperature weighted z, i.e. ice effective radius [m]."""
|
|
191
|
-
|
|
192
|
-
|
|
237
|
+
if self.product not in ("iwc", "ier"):
|
|
238
|
+
msg = f"Invalid product: {self.product}"
|
|
239
|
+
raise ValueError(msg)
|
|
240
|
+
if z_variable not in ("Z", "Z_sensitivity"):
|
|
241
|
+
msg = f"Invalid z_variable: {z_variable}"
|
|
242
|
+
raise ValueError(msg)
|
|
193
243
|
temperature = (
|
|
194
244
|
self.temperature if z_variable == "Z" else ma.mean(self.temperature, axis=0)
|
|
195
245
|
)
|
|
@@ -212,39 +262,31 @@ class IceSource(DataSource):
|
|
|
212
262
|
|
|
213
263
|
def _get_z_factor(self) -> float:
|
|
214
264
|
"""Returns empirical scaling factor for radar echo."""
|
|
215
|
-
|
|
216
|
-
|
|
265
|
+
k2 = np.array(self.coefficients.K2liquid0) / 0.93
|
|
266
|
+
return float(utils.lin2db(k2))
|
|
217
267
|
|
|
218
|
-
def get_is_rain(filename: str) -> np.ndarray:
|
|
219
|
-
try:
|
|
220
|
-
rainfall_rate = read_nc_fields(filename, "rainfall_rate")
|
|
221
|
-
except KeyError:
|
|
222
|
-
rainfall_rate = read_nc_fields(filename, "rain_rate")
|
|
223
|
-
is_rain = rainfall_rate != 0
|
|
224
|
-
assert isinstance(is_rain, ma.MaskedArray)
|
|
225
|
-
is_rain[is_rain.mask] = True
|
|
226
|
-
return np.array(is_rain)
|
|
227
268
|
|
|
269
|
+
def get_is_rain(filename: str | PathLike) -> npt.NDArray:
|
|
270
|
+
# TODO: Check that this is correct
|
|
271
|
+
with netCDF4.Dataset(filename) as nc:
|
|
272
|
+
for name in ["rain_detected", "rainfall_rate", "rain_rate"]:
|
|
273
|
+
if name in nc.variables:
|
|
274
|
+
data = nc.variables[name][:]
|
|
275
|
+
data = data != 0
|
|
276
|
+
data[data.mask] = True
|
|
277
|
+
return np.array(data)
|
|
278
|
+
msg = "No rain data found."
|
|
279
|
+
raise ValueError(msg)
|
|
228
280
|
|
|
229
|
-
def read_nc_fields(nc_file: str, names: str | list) -> ma.MaskedArray | list:
|
|
230
|
-
"""Reads selected variables from a netCDF file.
|
|
231
|
-
|
|
232
|
-
Args:
|
|
233
|
-
nc_file: netCDF file name.
|
|
234
|
-
names: Variables to be read, e.g. 'temperature' or ['ldr', 'lwp'].
|
|
235
281
|
|
|
236
|
-
|
|
237
|
-
ndarray/list: Array in case of one variable passed as a string.
|
|
238
|
-
List of arrays otherwise.
|
|
239
|
-
|
|
240
|
-
"""
|
|
241
|
-
names = [names] if isinstance(names, str) else names
|
|
282
|
+
def read_nc_field(nc_file: str | PathLike, name: str) -> ma.MaskedArray:
|
|
242
283
|
with netCDF4.Dataset(nc_file) as nc:
|
|
243
|
-
|
|
244
|
-
return data[0] if len(data) == 1 else data
|
|
284
|
+
return nc.variables[name][:]
|
|
245
285
|
|
|
246
286
|
|
|
247
|
-
def interpolate_model(
|
|
287
|
+
def interpolate_model(
|
|
288
|
+
cat_file: str | PathLike, names: str | list
|
|
289
|
+
) -> dict[str, npt.NDArray]:
|
|
248
290
|
"""Interpolates 2D model field into dense Cloudnet grid.
|
|
249
291
|
|
|
250
292
|
Args:
|
|
@@ -257,8 +299,8 @@ def interpolate_model(cat_file: str, names: str | list) -> dict[str, np.ndarray]
|
|
|
257
299
|
|
|
258
300
|
"""
|
|
259
301
|
|
|
260
|
-
def _interp_field(var_name: str) ->
|
|
261
|
-
values =
|
|
302
|
+
def _interp_field(var_name: str) -> npt.NDArray:
|
|
303
|
+
values = _read_nc_fields(
|
|
262
304
|
cat_file,
|
|
263
305
|
["model_time", "model_height", var_name, "time", "height"],
|
|
264
306
|
)
|
|
@@ -268,12 +310,12 @@ def interpolate_model(cat_file: str, names: str | list) -> dict[str, np.ndarray]
|
|
|
268
310
|
return {name: _interp_field(name) for name in names}
|
|
269
311
|
|
|
270
312
|
|
|
271
|
-
def
|
|
313
|
+
def _read_nc_fields(nc_file: str | PathLike, names: list[str]) -> list[ma.MaskedArray]:
|
|
314
|
+
with netCDF4.Dataset(nc_file) as nc:
|
|
315
|
+
return [nc.variables[name][:] for name in names]
|
|
316
|
+
|
|
317
|
+
|
|
318
|
+
def _get_temperature(categorize_file: str | PathLike) -> npt.NDArray:
|
|
272
319
|
"""Returns interpolated temperatures in Celsius."""
|
|
273
320
|
atmosphere = interpolate_model(categorize_file, "temperature")
|
|
274
321
|
return atmos_utils.k2c(atmosphere["temperature"])
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
def get_mwrpy_coeffs(nc_file: str) -> str:
|
|
278
|
-
with netCDF4.Dataset(nc_file) as nc:
|
|
279
|
-
return nc.mwrpy_coefficients
|