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,9 +1,12 @@
|
|
|
1
1
|
import importlib
|
|
2
2
|
import logging
|
|
3
|
+
from os import PathLike
|
|
3
4
|
|
|
4
5
|
import numpy as np
|
|
6
|
+
import numpy.typing as npt
|
|
7
|
+
import scipy.special
|
|
8
|
+
import scipy.stats
|
|
5
9
|
from numpy import ma
|
|
6
|
-
from scipy.special import gamma
|
|
7
10
|
|
|
8
11
|
import cloudnetpy.utils as cl_tools
|
|
9
12
|
from cloudnetpy.datasource import DataSource
|
|
@@ -24,9 +27,9 @@ class AdvanceProductMethods(DataSource):
|
|
|
24
27
|
def __init__(
|
|
25
28
|
self,
|
|
26
29
|
model_obj: ModelManager,
|
|
27
|
-
model_file: str,
|
|
30
|
+
model_file: str | PathLike,
|
|
28
31
|
obs_obj: ObservationManager,
|
|
29
|
-
):
|
|
32
|
+
) -> None:
|
|
30
33
|
super().__init__(model_file)
|
|
31
34
|
self._obs_obj = obs_obj
|
|
32
35
|
self.product = obs_obj.obs
|
|
@@ -38,70 +41,88 @@ class AdvanceProductMethods(DataSource):
|
|
|
38
41
|
self._model_height = model_obj.data[model_obj.keys["height"]][:]
|
|
39
42
|
self.generate_products()
|
|
40
43
|
|
|
41
|
-
def generate_products(self):
|
|
42
|
-
cls =
|
|
44
|
+
def generate_products(self) -> None:
|
|
45
|
+
cls = importlib.import_module(__name__).AdvanceProductMethods
|
|
43
46
|
try:
|
|
44
47
|
name = f"get_advance_{self.product}"
|
|
45
48
|
getattr(cls, name)(self)
|
|
46
49
|
except AttributeError as error:
|
|
47
|
-
logging.
|
|
50
|
+
logging.debug("No advance method for %s: %s", self.product, error)
|
|
48
51
|
|
|
49
|
-
def get_advance_cf(self):
|
|
52
|
+
def get_advance_cf(self) -> None:
|
|
50
53
|
self.cf_cirrus_filter()
|
|
51
54
|
|
|
52
|
-
def cf_cirrus_filter(self):
|
|
55
|
+
def cf_cirrus_filter(self) -> None:
|
|
53
56
|
cf = self.getvar_from_object("cf")
|
|
54
57
|
h = self.getvar_from_object("h")
|
|
55
58
|
temperature = self.getvar("temperature")
|
|
56
59
|
t_screened = self.remove_extra_levels(temperature - 273.15)
|
|
57
|
-
iwc, lwc =
|
|
60
|
+
iwc, lwc = (self._model_obj.get_water_content(var) for var in ["iwc", "lwc"])
|
|
58
61
|
tZT, tT, tZ, t = self.set_frequency_parameters()
|
|
59
62
|
z_sen = self.fit_z_sensitivity(h)
|
|
60
63
|
cf_filtered = self.filter_high_iwc_low_cf(cf, iwc, lwc)
|
|
61
64
|
cloud_iwc, ice_ind = self.find_ice_in_clouds(cf_filtered, iwc, lwc)
|
|
62
65
|
variance_iwc = self.iwc_variance(h, ice_ind)
|
|
63
|
-
|
|
64
|
-
for i, ind in enumerate(zip(ice_ind[0], ice_ind[-1])):
|
|
65
|
-
|
|
66
|
+
|
|
67
|
+
for i, ind in enumerate(zip(ice_ind[0], ice_ind[-1], strict=True)):
|
|
68
|
+
try:
|
|
69
|
+
iwc_dist = self.calculate_iwc_distribution(
|
|
70
|
+
cloud_iwc[i], variance_iwc[i]
|
|
71
|
+
)
|
|
72
|
+
except ValueError:
|
|
73
|
+
continue
|
|
74
|
+
|
|
66
75
|
p_iwc = self.gamma_distribution(iwc_dist, variance_iwc[i], cloud_iwc[i])
|
|
76
|
+
|
|
77
|
+
if np.isinf(p_iwc).any():
|
|
78
|
+
cf_filtered[ind] = ma.masked
|
|
79
|
+
continue
|
|
80
|
+
|
|
67
81
|
if np.sum(p_iwc) == 0 or p_iwc[-1] > 0.01 * np.sum(p_iwc):
|
|
68
|
-
cf_filtered[ind] =
|
|
82
|
+
cf_filtered[ind] = ma.masked
|
|
69
83
|
continue
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
tZT
|
|
73
|
-
tT
|
|
74
|
-
tZ
|
|
75
|
-
t
|
|
76
|
-
float(t_screened[ind]),
|
|
77
|
-
float(z_sen[ind]),
|
|
84
|
+
|
|
85
|
+
min_iwc = 10 ** (
|
|
86
|
+
tZT * z_sen[ind] * t_screened[ind]
|
|
87
|
+
+ tT * t_screened[ind]
|
|
88
|
+
+ tZ * z_sen[ind]
|
|
89
|
+
+ t
|
|
78
90
|
)
|
|
91
|
+
obs_index = iwc_dist > min_iwc
|
|
79
92
|
cf_filtered[ind] = self.filter_cirrus(p_iwc, obs_index, cf_filtered[ind])
|
|
93
|
+
|
|
80
94
|
cf_filtered[cf_filtered < 0.05] = ma.masked
|
|
95
|
+
|
|
81
96
|
self._model_obj.append_data(
|
|
82
97
|
cf_filtered,
|
|
83
98
|
f"{self._model_obj.model}{self._model_obj.cycle}_cf_cirrus",
|
|
84
99
|
)
|
|
85
100
|
|
|
86
|
-
def getvar_from_object(self, arg: str) ->
|
|
101
|
+
def getvar_from_object(self, arg: str) -> npt.NDArray:
|
|
87
102
|
v_name = arg if arg == "cf" else self._model_obj.get_model_var_names((arg,))[0]
|
|
88
103
|
key = f"{self._model_obj.model}{self._model_obj.cycle}_{v_name}"
|
|
89
104
|
return self._model_obj.data[key][:]
|
|
90
105
|
|
|
91
|
-
def remove_extra_levels(self, arg:
|
|
106
|
+
def remove_extra_levels(self, arg: npt.NDArray) -> npt.NDArray:
|
|
92
107
|
return self._model_obj.cut_off_extra_levels(arg)
|
|
93
108
|
|
|
94
109
|
def set_frequency_parameters(self) -> tuple:
|
|
95
|
-
|
|
110
|
+
if self._obs_obj.radar_freq is None:
|
|
111
|
+
msg = "No radar frequency in observation file"
|
|
112
|
+
raise ValueError(msg)
|
|
96
113
|
if 30 <= self._obs_obj.radar_freq <= 40:
|
|
97
114
|
return 0.000242, -0.0186, 0.0699, -1.63
|
|
98
115
|
if 90 <= float(self._obs_obj.radar_freq) <= 100:
|
|
99
116
|
return 0.00058, -0.00706, 0.0923, -0.992
|
|
100
117
|
raise ValueError
|
|
101
118
|
|
|
102
|
-
def fit_z_sensitivity(self, h:
|
|
103
|
-
|
|
104
|
-
|
|
119
|
+
def fit_z_sensitivity(self, h: npt.NDArray) -> npt.NDArray:
|
|
120
|
+
if self._obs_obj.z_sensitivity is None:
|
|
121
|
+
msg = "No z_sensitivity in observation file"
|
|
122
|
+
raise ValueError(msg)
|
|
123
|
+
if self._obs_obj.height is None:
|
|
124
|
+
msg = "No height in observation file"
|
|
125
|
+
raise ValueError(msg)
|
|
105
126
|
z_sen = [
|
|
106
127
|
cl_tools.rebin_1d(self._obs_obj.height, self._obs_obj.z_sensitivity, h[i])
|
|
107
128
|
for i in range(len(h))
|
|
@@ -109,46 +130,58 @@ class AdvanceProductMethods(DataSource):
|
|
|
109
130
|
return np.asarray(z_sen)
|
|
110
131
|
|
|
111
132
|
def filter_high_iwc_low_cf(
|
|
112
|
-
self,
|
|
113
|
-
|
|
133
|
+
self,
|
|
134
|
+
cf: npt.NDArray,
|
|
135
|
+
iwc: npt.NDArray,
|
|
136
|
+
lwc: npt.NDArray,
|
|
137
|
+
) -> npt.NDArray:
|
|
114
138
|
cf_filtered = self.mask_weird_indices(cf, iwc, lwc)
|
|
115
139
|
if np.sum((iwc > 0) & (lwc < iwc / 10) & (cf_filtered > 0)) == 0:
|
|
116
|
-
|
|
140
|
+
msg = "No ice clouds in a input data"
|
|
141
|
+
raise ValueError(msg)
|
|
117
142
|
return cf_filtered
|
|
118
143
|
|
|
119
144
|
@staticmethod
|
|
120
145
|
def mask_weird_indices(
|
|
121
|
-
cf:
|
|
122
|
-
|
|
123
|
-
|
|
146
|
+
cf: npt.NDArray,
|
|
147
|
+
iwc: npt.NDArray,
|
|
148
|
+
lwc: npt.NDArray,
|
|
149
|
+
) -> npt.NDArray:
|
|
150
|
+
cf_filtered = ma.copy(cf)
|
|
124
151
|
weird_ind = (iwc / cf > 0.5e-3) & (cf < 0.001)
|
|
125
152
|
weird_ind = weird_ind | (iwc == 0) & (lwc == 0) & (cf == 0)
|
|
126
153
|
cf_filtered[weird_ind] = ma.masked
|
|
127
154
|
return cf_filtered
|
|
128
155
|
|
|
129
156
|
def find_ice_in_clouds(
|
|
130
|
-
self,
|
|
131
|
-
|
|
157
|
+
self,
|
|
158
|
+
cf_filtered: npt.NDArray,
|
|
159
|
+
iwc: npt.NDArray,
|
|
160
|
+
lwc: npt.NDArray,
|
|
161
|
+
) -> tuple[npt.NDArray, tuple]:
|
|
132
162
|
ice_ind = self.get_ice_indices(cf_filtered, iwc, lwc)
|
|
133
163
|
cloud_iwc = iwc[ice_ind] / cf_filtered[ice_ind] * 1e3
|
|
134
164
|
return cloud_iwc, ice_ind
|
|
135
165
|
|
|
136
166
|
@staticmethod
|
|
137
167
|
def get_ice_indices(
|
|
138
|
-
cf_filtered:
|
|
168
|
+
cf_filtered: npt.NDArray,
|
|
169
|
+
iwc: npt.NDArray,
|
|
170
|
+
lwc: npt.NDArray,
|
|
139
171
|
) -> tuple:
|
|
140
172
|
return tuple(np.where((cf_filtered > 0) & (iwc > 0) & (lwc < iwc / 10)))
|
|
141
173
|
|
|
142
|
-
def iwc_variance(self, height:
|
|
174
|
+
def iwc_variance(self, height: npt.NDArray, ice_ind: tuple) -> npt.NDArray:
|
|
143
175
|
u = self.getvar("uwind")
|
|
144
176
|
v = self.getvar("vwind")
|
|
145
177
|
u = self.remove_extra_levels(u)
|
|
146
178
|
v = self.remove_extra_levels(v)
|
|
147
179
|
w_shear = self.calculate_wind_shear(self._model_obj.wind, u, v, height)
|
|
148
|
-
|
|
149
|
-
return variance_iwc
|
|
180
|
+
return self.calculate_variance_iwc(w_shear, ice_ind)
|
|
150
181
|
|
|
151
|
-
def calculate_variance_iwc(
|
|
182
|
+
def calculate_variance_iwc(
|
|
183
|
+
self, w_shear: npt.NDArray, ice_ind: tuple
|
|
184
|
+
) -> npt.NDArray:
|
|
152
185
|
return 10 ** (
|
|
153
186
|
0.3 * np.log10(self._model_obj.resolution_h)
|
|
154
187
|
- 0.04 * w_shear[ice_ind]
|
|
@@ -157,8 +190,11 @@ class AdvanceProductMethods(DataSource):
|
|
|
157
190
|
|
|
158
191
|
@staticmethod
|
|
159
192
|
def calculate_wind_shear(
|
|
160
|
-
wind
|
|
161
|
-
|
|
193
|
+
wind: npt.NDArray,
|
|
194
|
+
u: npt.NDArray,
|
|
195
|
+
v: npt.NDArray,
|
|
196
|
+
height: npt.NDArray,
|
|
197
|
+
) -> npt.NDArray:
|
|
162
198
|
grand_winds = []
|
|
163
199
|
for w in (wind, u, v):
|
|
164
200
|
grad_w = np.zeros(w.shape)
|
|
@@ -179,8 +215,10 @@ class AdvanceProductMethods(DataSource):
|
|
|
179
215
|
f_variance_iwc: float,
|
|
180
216
|
n_std: int = 5,
|
|
181
217
|
n_dist: int = 250,
|
|
182
|
-
) ->
|
|
183
|
-
finish = cloud_iwc + n_std * (
|
|
218
|
+
) -> npt.NDArray:
|
|
219
|
+
finish = cloud_iwc + n_std * (ma.sqrt(f_variance_iwc) * cloud_iwc)
|
|
220
|
+
if isinstance(finish, ma.MaskedArray) and finish.mask.all():
|
|
221
|
+
raise ValueError
|
|
184
222
|
iwc_dist = np.arange(0, finish, finish / (n_dist - 1))
|
|
185
223
|
if cloud_iwc < iwc_dist[2]:
|
|
186
224
|
finish = cloud_iwc * 10
|
|
@@ -189,45 +227,16 @@ class AdvanceProductMethods(DataSource):
|
|
|
189
227
|
|
|
190
228
|
@staticmethod
|
|
191
229
|
def gamma_distribution(
|
|
192
|
-
iwc_dist:
|
|
193
|
-
) ->
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
1
|
|
198
|
-
/ gamma(alpha)
|
|
199
|
-
* (alpha / cloud_iwc) ** alpha
|
|
200
|
-
* iwc_dist[i] ** (alpha - 1)
|
|
201
|
-
* ma.exp(-(alpha * iwc_dist[i] / cloud_iwc))
|
|
202
|
-
)
|
|
203
|
-
|
|
204
|
-
p_iwc = np.zeros(iwc_dist.shape)
|
|
205
|
-
for i in range(len(iwc_dist)):
|
|
206
|
-
p_iwc[i] = calculate_gamma_dist()
|
|
207
|
-
return p_iwc
|
|
208
|
-
|
|
209
|
-
@staticmethod
|
|
210
|
-
def get_observation_index(
|
|
211
|
-
iwc_dist: np.ndarray,
|
|
212
|
-
tZT: float,
|
|
213
|
-
tT: float,
|
|
214
|
-
tZ: float,
|
|
215
|
-
t: np.ndarray,
|
|
216
|
-
temperature: float,
|
|
217
|
-
z_sen: float,
|
|
218
|
-
) -> np.ndarray:
|
|
219
|
-
def calculate_min_iwc():
|
|
220
|
-
min_iwc = 10 ** (
|
|
221
|
-
tZT * z_sen * temperature + tT * temperature + tZ * z_sen + t
|
|
222
|
-
)
|
|
223
|
-
return min_iwc
|
|
224
|
-
|
|
225
|
-
iwc_min = calculate_min_iwc()
|
|
226
|
-
obs_index = iwc_dist > iwc_min
|
|
227
|
-
return obs_index
|
|
230
|
+
iwc_dist: npt.NDArray, f_variance_iwc: float, cloud_iwc: float
|
|
231
|
+
) -> npt.NDArray:
|
|
232
|
+
alpha = 1 / f_variance_iwc
|
|
233
|
+
theta = cloud_iwc / alpha
|
|
234
|
+
return scipy.stats.gamma.pdf(iwc_dist, a=alpha, scale=theta)
|
|
228
235
|
|
|
229
236
|
@staticmethod
|
|
230
237
|
def filter_cirrus(
|
|
231
|
-
p_iwc:
|
|
232
|
-
|
|
233
|
-
|
|
238
|
+
p_iwc: npt.NDArray,
|
|
239
|
+
obs_index: npt.NDArray,
|
|
240
|
+
cf_filtered: npt.NDArray,
|
|
241
|
+
) -> npt.NDArray:
|
|
242
|
+
return (ma.sum(p_iwc * obs_index) / ma.sum(p_iwc)) * cf_filtered
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import numpy as np
|
|
2
|
+
import numpy.typing as npt
|
|
2
3
|
from numpy import ma
|
|
3
4
|
|
|
4
5
|
from cloudnetpy import utils
|
|
@@ -13,12 +14,13 @@ class ProductGrid:
|
|
|
13
14
|
Args:
|
|
14
15
|
model_obj (object): The :class:'ModelManager' object.
|
|
15
16
|
obs_obj (object): The :class:'ObservationManager' object.
|
|
17
|
+
|
|
16
18
|
Notes:
|
|
17
19
|
Downsampled observation products data is added to a ModelManager
|
|
18
20
|
object which is used for nc-file creation and writing
|
|
19
21
|
"""
|
|
20
22
|
|
|
21
|
-
def __init__(self, model_obj: ModelManager, obs_obj: ObservationManager):
|
|
23
|
+
def __init__(self, model_obj: ModelManager, obs_obj: ObservationManager) -> None:
|
|
22
24
|
self._obs_obj = obs_obj
|
|
23
25
|
self._date = obs_obj.date
|
|
24
26
|
self._obs_time = tl.time2datetime(obs_obj.time, self._date)
|
|
@@ -28,13 +30,15 @@ class ProductGrid:
|
|
|
28
30
|
self._model_time = model_obj.time
|
|
29
31
|
self._model_height = model_obj.data[model_obj.keys["height"]][:]
|
|
30
32
|
self._time_adv = tl.calculate_advection_time(
|
|
31
|
-
int(model_obj.resolution_h),
|
|
33
|
+
int(model_obj.resolution_h),
|
|
34
|
+
ma.array(model_obj.wind),
|
|
35
|
+
1,
|
|
32
36
|
)
|
|
33
37
|
time_steps = utils.binvec(self._model_time)
|
|
34
38
|
self._time_steps = tl.time2datetime(time_steps, self._date)
|
|
35
39
|
self._generate_downsample_product()
|
|
36
40
|
|
|
37
|
-
def _generate_downsample_product(self):
|
|
41
|
+
def _generate_downsample_product(self) -> None:
|
|
38
42
|
"""Downsampling products are generated with different averaging methods
|
|
39
43
|
for a selected size of model time-height window.
|
|
40
44
|
"""
|
|
@@ -42,7 +46,8 @@ class ProductGrid:
|
|
|
42
46
|
model_t = tl.time2datetime(self._model_time, self._date)
|
|
43
47
|
for i in range(len(self._time_steps) - 1):
|
|
44
48
|
x_ind = tl.get_1d_indices(
|
|
45
|
-
(self._time_steps[i], self._time_steps[i + 1]),
|
|
49
|
+
(self._time_steps[i], self._time_steps[i + 1]),
|
|
50
|
+
self._obs_time,
|
|
46
51
|
)
|
|
47
52
|
if self._obs_obj.obs == "iwc":
|
|
48
53
|
x_ind_no_rain = tl.get_1d_indices(
|
|
@@ -53,10 +58,13 @@ class ProductGrid:
|
|
|
53
58
|
y_steps = tl.rebin_edges(self._model_height[i])
|
|
54
59
|
for j in range(len(y_steps) - 1):
|
|
55
60
|
x_ind_adv = tl.get_adv_indices(
|
|
56
|
-
model_t[i],
|
|
61
|
+
model_t[i],
|
|
62
|
+
self._time_adv[i, j],
|
|
63
|
+
self._obs_time,
|
|
57
64
|
)
|
|
58
65
|
y_ind = tl.get_1d_indices(
|
|
59
|
-
(y_steps[j], y_steps[j + 1]),
|
|
66
|
+
(y_steps[j], y_steps[j + 1]),
|
|
67
|
+
self._obs_height,
|
|
60
68
|
)
|
|
61
69
|
ind = np.outer(x_ind, y_ind)
|
|
62
70
|
ind_avd = np.outer(x_ind_adv, y_ind)
|
|
@@ -66,7 +74,9 @@ class ProductGrid:
|
|
|
66
74
|
continue
|
|
67
75
|
product_dict = self._regrid_cf(product_dict, i, j, data)
|
|
68
76
|
data_adv = self._reshape_data_to_window(ind_avd, x_ind_adv, y_ind)
|
|
69
|
-
|
|
77
|
+
if data_adv is None:
|
|
78
|
+
msg = "No data for advection"
|
|
79
|
+
raise RuntimeError(msg)
|
|
70
80
|
product_adv_dict = self._regrid_cf(product_adv_dict, i, j, data_adv)
|
|
71
81
|
elif self._obs_obj.obs == "iwc":
|
|
72
82
|
x_ind_no_rain_adv = tl.get_adv_indices(
|
|
@@ -78,19 +88,30 @@ class ProductGrid:
|
|
|
78
88
|
ind_no_rain = np.outer(x_ind_no_rain, y_ind)
|
|
79
89
|
ind_no_rain_adv = np.outer(x_ind_no_rain_adv, y_ind)
|
|
80
90
|
product_dict = self._regrid_iwc(
|
|
81
|
-
product_dict,
|
|
91
|
+
product_dict,
|
|
92
|
+
i,
|
|
93
|
+
j,
|
|
94
|
+
ind,
|
|
95
|
+
ind_no_rain,
|
|
82
96
|
)
|
|
83
97
|
product_adv_dict = self._regrid_iwc(
|
|
84
|
-
product_adv_dict,
|
|
98
|
+
product_adv_dict,
|
|
99
|
+
i,
|
|
100
|
+
j,
|
|
101
|
+
ind_avd,
|
|
102
|
+
ind_no_rain_adv,
|
|
85
103
|
)
|
|
86
104
|
else:
|
|
87
105
|
product_dict = self._regrid_product(product_dict, i, j, ind)
|
|
88
106
|
product_adv_dict = self._regrid_product(
|
|
89
|
-
product_adv_dict,
|
|
107
|
+
product_adv_dict,
|
|
108
|
+
i,
|
|
109
|
+
j,
|
|
110
|
+
ind_avd,
|
|
90
111
|
)
|
|
91
112
|
self._append_data2object([product_dict, product_adv_dict])
|
|
92
113
|
|
|
93
|
-
def _get_method_storage(self):
|
|
114
|
+
def _get_method_storage(self) -> tuple[dict, dict]:
|
|
94
115
|
if self._obs_obj.obs == "cf":
|
|
95
116
|
return self._cf_method_storage()
|
|
96
117
|
if self._obs_obj.obs == "iwc":
|
|
@@ -99,95 +120,99 @@ class ProductGrid:
|
|
|
99
120
|
|
|
100
121
|
def _cf_method_storage(self) -> tuple[dict, dict]:
|
|
101
122
|
cf_dict = {
|
|
102
|
-
"cf_V":
|
|
103
|
-
"cf_A":
|
|
123
|
+
"cf_V": ma.zeros(self._model_height.shape),
|
|
124
|
+
"cf_A": ma.zeros(self._model_height.shape),
|
|
104
125
|
}
|
|
105
126
|
cf_adv_dict = {
|
|
106
|
-
"cf_V_adv":
|
|
107
|
-
"cf_A_adv":
|
|
127
|
+
"cf_V_adv": ma.zeros(self._model_height.shape),
|
|
128
|
+
"cf_A_adv": ma.zeros(self._model_height.shape),
|
|
108
129
|
}
|
|
109
130
|
return cf_dict, cf_adv_dict
|
|
110
131
|
|
|
111
132
|
def _iwc_method_storage(self) -> tuple[dict, dict]:
|
|
112
133
|
iwc_dict = {
|
|
113
|
-
"iwc":
|
|
114
|
-
"iwc_att":
|
|
115
|
-
"iwc_rain":
|
|
134
|
+
"iwc": ma.zeros(self._model_height.shape),
|
|
135
|
+
"iwc_att": ma.zeros(self._model_height.shape),
|
|
136
|
+
"iwc_rain": ma.zeros(self._model_height.shape),
|
|
116
137
|
}
|
|
117
138
|
iwc_adv_dict = {
|
|
118
|
-
"iwc_adv":
|
|
119
|
-
"iwc_att_adv":
|
|
120
|
-
"iwc_rain_adv":
|
|
139
|
+
"iwc_adv": ma.zeros(self._model_height.shape),
|
|
140
|
+
"iwc_att_adv": ma.zeros(self._model_height.shape),
|
|
141
|
+
"iwc_rain_adv": ma.zeros(self._model_height.shape),
|
|
121
142
|
}
|
|
122
143
|
return iwc_dict, iwc_adv_dict
|
|
123
144
|
|
|
124
145
|
def _product_method_storage(self) -> tuple[dict, dict]:
|
|
125
|
-
product_dict = {f"{self._obs_obj.obs}":
|
|
146
|
+
product_dict = {f"{self._obs_obj.obs}": ma.zeros(self._model_height.shape)}
|
|
126
147
|
product_adv_dict = {
|
|
127
|
-
f"{self._obs_obj.obs}_adv":
|
|
148
|
+
f"{self._obs_obj.obs}_adv": ma.zeros(self._model_height.shape),
|
|
128
149
|
}
|
|
129
150
|
return product_dict, product_adv_dict
|
|
130
151
|
|
|
131
|
-
@staticmethod
|
|
132
|
-
def _regrid_cf(storage: dict, i: int, j: int, data: np.ndarray | None) -> dict:
|
|
133
|
-
"""Calculates average cloud fraction value to grid point"""
|
|
134
|
-
for key, downsample in storage.items():
|
|
135
|
-
if data is not None:
|
|
136
|
-
downsample[i, j] = np.nanmean(data)
|
|
137
|
-
if "_A" in key:
|
|
138
|
-
downsample[i, j] = tl.average_column_sum(data)
|
|
139
|
-
else:
|
|
140
|
-
downsample[i, j] = np.nan
|
|
141
|
-
storage[key] = downsample
|
|
142
|
-
return storage
|
|
143
|
-
|
|
144
152
|
def _reshape_data_to_window(
|
|
145
|
-
self,
|
|
146
|
-
|
|
147
|
-
|
|
153
|
+
self,
|
|
154
|
+
ind: npt.NDArray,
|
|
155
|
+
x_ind: npt.NDArray,
|
|
156
|
+
y_ind: npt.NDArray,
|
|
157
|
+
) -> npt.NDArray | None:
|
|
158
|
+
"""Reshapes True observation values to windows shape."""
|
|
148
159
|
window_size = tl.get_obs_window_size(x_ind, y_ind)
|
|
149
160
|
if window_size is not None:
|
|
150
161
|
return self._obs_data[ind].reshape(window_size)
|
|
151
162
|
return None
|
|
152
163
|
|
|
164
|
+
@staticmethod
|
|
165
|
+
def _regrid_cf(storage: dict, i: int, j: int, data: npt.NDArray) -> dict:
|
|
166
|
+
"""Calculates average cloud fraction value to grid point."""
|
|
167
|
+
data_ma = ma.array(data) if not isinstance(data, ma.MaskedArray) else data
|
|
168
|
+
for key, downsample in storage.items():
|
|
169
|
+
downsample[i, j] = ma.mean(data_ma)
|
|
170
|
+
if "_A" in key and not data_ma.mask.all():
|
|
171
|
+
downsample[i, j] = tl.average_column_sum(data_ma)
|
|
172
|
+
storage[key] = downsample
|
|
173
|
+
return storage
|
|
174
|
+
|
|
153
175
|
def _regrid_iwc(
|
|
154
176
|
self,
|
|
155
177
|
storage: dict,
|
|
156
178
|
i: int,
|
|
157
179
|
j: int,
|
|
158
|
-
ind_rain:
|
|
159
|
-
ind_no_rain:
|
|
180
|
+
ind_rain: npt.NDArray,
|
|
181
|
+
ind_no_rain: npt.NDArray,
|
|
160
182
|
) -> dict:
|
|
161
|
-
"""Calculates average iwc value for grid point"""
|
|
162
|
-
for key,
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
183
|
+
"""Calculates average iwc value for each grid point."""
|
|
184
|
+
for key, down_sample in storage.items():
|
|
185
|
+
down_sample[i, j] = ma.masked
|
|
186
|
+
if "rain" not in key:
|
|
187
|
+
no_rain_data = self._obs_data[ind_no_rain]
|
|
188
|
+
if ind_no_rain.any() and not no_rain_data.mask.all():
|
|
189
|
+
down_sample[i, j] = ma.mean(no_rain_data)
|
|
190
|
+
if "rain" in key:
|
|
191
|
+
rain_data = self._obs_data[ind_rain]
|
|
192
|
+
if ind_rain.any() and not rain_data.mask.all():
|
|
193
|
+
down_sample[i, j] = ma.mean(rain_data)
|
|
169
194
|
if "att" in key:
|
|
170
|
-
|
|
171
|
-
if
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
downsample[i, j] = np.nanmean(iwc_att[ind_no_rain])
|
|
175
|
-
storage[key] = downsample
|
|
195
|
+
no_rain_att_data = self._obs_obj.data["iwc_att"][ind_no_rain]
|
|
196
|
+
if ind_no_rain.any() and not no_rain_att_data.mask.all():
|
|
197
|
+
down_sample[i, j] = ma.mean(no_rain_att_data)
|
|
198
|
+
storage[key] = down_sample
|
|
176
199
|
return storage
|
|
177
200
|
|
|
178
|
-
def _regrid_product(self, storage: dict, i: int, j: int, ind:
|
|
179
|
-
"""Calculates average of standard product value
|
|
201
|
+
def _regrid_product(self, storage: dict, i: int, j: int, ind: npt.NDArray) -> dict:
|
|
202
|
+
"""Calculates average of standard product value for each grid point."""
|
|
180
203
|
for key, down_sample in storage.items():
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
204
|
+
obs_data_selected = ma.masked_invalid(self._obs_data[ind])
|
|
205
|
+
down_sample[i, j] = (
|
|
206
|
+
ma.mean(obs_data_selected)
|
|
207
|
+
if not obs_data_selected.mask.all()
|
|
208
|
+
else ma.masked
|
|
209
|
+
)
|
|
185
210
|
storage[key] = down_sample
|
|
186
211
|
return storage
|
|
187
212
|
|
|
188
|
-
def _append_data2object(self, data_storage: list):
|
|
213
|
+
def _append_data2object(self, data_storage: list) -> None:
|
|
189
214
|
for storage in data_storage:
|
|
190
|
-
for key in storage
|
|
215
|
+
for key in storage:
|
|
191
216
|
down_sample = storage[key]
|
|
192
217
|
self.model_obj.append_data(
|
|
193
218
|
down_sample,
|