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
cloudnetpy/categorize/radar.py
CHANGED
|
@@ -1,13 +1,17 @@
|
|
|
1
1
|
"""Radar module, containing the :class:`Radar` class."""
|
|
2
|
+
|
|
2
3
|
import logging
|
|
3
4
|
import math
|
|
5
|
+
from os import PathLike
|
|
4
6
|
|
|
5
7
|
import numpy as np
|
|
8
|
+
import numpy.typing as npt
|
|
6
9
|
from numpy import ma
|
|
7
10
|
from scipy import constants
|
|
8
11
|
|
|
9
12
|
from cloudnetpy import utils
|
|
10
|
-
from cloudnetpy.categorize.
|
|
13
|
+
from cloudnetpy.categorize.attenuations import RadarAttenuation
|
|
14
|
+
from cloudnetpy.constants import GHZ_TO_HZ, SEC_IN_HOUR, SPEED_OF_LIGHT
|
|
11
15
|
from cloudnetpy.datasource import DataSource
|
|
12
16
|
|
|
13
17
|
|
|
@@ -22,9 +26,7 @@ class Radar(DataSource):
|
|
|
22
26
|
folding_velocity (float): Radar's folding velocity (m/s).
|
|
23
27
|
location (str): Location of the radar, copied from the global attribute
|
|
24
28
|
`location` of the input file.
|
|
25
|
-
|
|
26
|
-
regimes of the radar.
|
|
27
|
-
type (str): Type of the radar, copied from the global attribute
|
|
29
|
+
source_type (str): Type of the radar, copied from the global attribute
|
|
28
30
|
`source` of the *radar_file*. Can be free form string but must
|
|
29
31
|
include either 'rpg' or 'mira' denoting one of the two supported
|
|
30
32
|
radars.
|
|
@@ -34,22 +36,21 @@ class Radar(DataSource):
|
|
|
34
36
|
|
|
35
37
|
"""
|
|
36
38
|
|
|
37
|
-
def __init__(self, full_path: str):
|
|
39
|
+
def __init__(self, full_path: str | PathLike) -> None:
|
|
38
40
|
super().__init__(full_path, radar=True)
|
|
39
41
|
self.radar_frequency = float(self.getvar("radar_frequency"))
|
|
40
|
-
self.folding_velocity = self._get_folding_velocity()
|
|
41
|
-
self.sequence_indices = self._get_sequence_indices()
|
|
42
42
|
self.location = getattr(self.dataset, "location", "")
|
|
43
|
-
self.
|
|
43
|
+
self.source_type = getattr(self.dataset, "source", "")
|
|
44
|
+
self.height: npt.NDArray
|
|
45
|
+
self.height_agl: npt.NDArray
|
|
46
|
+
self.altitude: float
|
|
44
47
|
self._init_data()
|
|
45
|
-
self._init_sigma_v()
|
|
46
|
-
self._get_folding_velocity_full()
|
|
47
48
|
|
|
48
|
-
def rebin_to_grid(self, time_new:
|
|
49
|
-
"""Rebins radar data in time
|
|
49
|
+
def rebin_to_grid(self, time_new: npt.NDArray) -> list:
|
|
50
|
+
"""Rebins radar data in time.
|
|
50
51
|
|
|
51
52
|
Args:
|
|
52
|
-
time_new: Target time array as fraction hour.
|
|
53
|
+
time_new: Target time array as fraction hour.
|
|
53
54
|
|
|
54
55
|
"""
|
|
55
56
|
bad_time_indices = []
|
|
@@ -60,19 +61,25 @@ class Radar(DataSource):
|
|
|
60
61
|
bad_time_indices = array.rebin_data(self.time, time_new)
|
|
61
62
|
array.lin2db()
|
|
62
63
|
case "v":
|
|
63
|
-
array.
|
|
64
|
-
|
|
64
|
+
array.data = self._rebin_velocity(
|
|
65
|
+
array.data,
|
|
65
66
|
time_new,
|
|
66
|
-
self.folding_velocity,
|
|
67
|
-
self.sequence_indices,
|
|
68
67
|
)
|
|
69
68
|
case "v_sigma":
|
|
70
|
-
array.
|
|
71
|
-
|
|
69
|
+
array.data, _ = utils.rebin_2d(
|
|
70
|
+
self.time,
|
|
71
|
+
array.data,
|
|
72
|
+
time_new,
|
|
73
|
+
"std",
|
|
74
|
+
mask_zeros=True,
|
|
75
|
+
)
|
|
76
|
+
case "width":
|
|
77
|
+
array.rebin_data(self.time, time_new)
|
|
78
|
+
case "rainfall_rate":
|
|
72
79
|
array.rebin_data(self.time, time_new)
|
|
73
80
|
case _:
|
|
74
81
|
continue
|
|
75
|
-
return bad_time_indices
|
|
82
|
+
return list(bad_time_indices)
|
|
76
83
|
|
|
77
84
|
def remove_incomplete_pixels(self) -> None:
|
|
78
85
|
"""Mask radar pixels where one or more required quantities are missing.
|
|
@@ -83,7 +90,7 @@ class Radar(DataSource):
|
|
|
83
90
|
|
|
84
91
|
"""
|
|
85
92
|
good_ind = ~ma.getmaskarray(self.data["Z"][:]) & ~ma.getmaskarray(
|
|
86
|
-
self.data["v"][:]
|
|
93
|
+
self.data["v"][:],
|
|
87
94
|
)
|
|
88
95
|
|
|
89
96
|
if "width" in self.data:
|
|
@@ -121,20 +128,33 @@ class Radar(DataSource):
|
|
|
121
128
|
if n_profiles_with_data < 300:
|
|
122
129
|
return
|
|
123
130
|
n_vertical = self._filter(
|
|
124
|
-
data,
|
|
131
|
+
data,
|
|
132
|
+
axis=1,
|
|
133
|
+
min_coverage=0.5,
|
|
134
|
+
z_limit=10,
|
|
135
|
+
distance=4,
|
|
136
|
+
n_blocks=100,
|
|
125
137
|
)
|
|
126
138
|
n_horizontal = self._filter(
|
|
127
|
-
data,
|
|
139
|
+
data,
|
|
140
|
+
axis=0,
|
|
141
|
+
min_coverage=0.3,
|
|
142
|
+
z_limit=-30,
|
|
143
|
+
distance=3,
|
|
144
|
+
n_blocks=20,
|
|
128
145
|
)
|
|
129
146
|
if n_vertical > 0 or n_horizontal > 0:
|
|
130
147
|
logging.debug(
|
|
131
|
-
|
|
132
|
-
|
|
148
|
+
"Filtered %s vertical and %s horizontal stripes "
|
|
149
|
+
"from radar data using %s",
|
|
150
|
+
n_vertical,
|
|
151
|
+
n_horizontal,
|
|
152
|
+
variable,
|
|
133
153
|
)
|
|
134
154
|
|
|
135
155
|
def _filter(
|
|
136
156
|
self,
|
|
137
|
-
data:
|
|
157
|
+
data: npt.NDArray,
|
|
138
158
|
axis: int,
|
|
139
159
|
min_coverage: float,
|
|
140
160
|
z_limit: float,
|
|
@@ -159,10 +179,14 @@ class Radar(DataSource):
|
|
|
159
179
|
q3 = np.quantile(n_values, 0.75)
|
|
160
180
|
except IndexError:
|
|
161
181
|
continue
|
|
182
|
+
|
|
183
|
+
if q1 == q3:
|
|
184
|
+
continue
|
|
185
|
+
|
|
162
186
|
threshold = distance * (q3 - q1) + q3
|
|
163
187
|
|
|
164
188
|
indices = np.where(
|
|
165
|
-
(n_values > threshold) & (n_values > (min_coverage * data.shape[1]))
|
|
189
|
+
(n_values > threshold) & (n_values > (min_coverage * data.shape[1])),
|
|
166
190
|
)[0]
|
|
167
191
|
true_ind = [int(x) for x in (block_number * len_block + indices)]
|
|
168
192
|
n_removed = len(indices)
|
|
@@ -180,25 +204,30 @@ class Radar(DataSource):
|
|
|
180
204
|
|
|
181
205
|
return n_removed_total
|
|
182
206
|
|
|
183
|
-
def correct_atten(self, attenuations:
|
|
207
|
+
def correct_atten(self, attenuations: RadarAttenuation) -> None:
|
|
184
208
|
"""Corrects radar echo for liquid and gas attenuation.
|
|
185
209
|
|
|
186
210
|
Args:
|
|
187
|
-
attenuations:
|
|
188
|
-
`radar_gas_atten`, `radar_liquid_atten`.
|
|
211
|
+
attenuations: Radar attenuation object.
|
|
189
212
|
|
|
190
213
|
References:
|
|
191
214
|
The method is based on Hogan R. and O'Connor E., 2004,
|
|
192
215
|
https://bit.ly/2Yjz9DZ and the original Cloudnet Matlab implementation.
|
|
193
216
|
|
|
194
217
|
"""
|
|
195
|
-
z_corrected = self.data["Z"][:] + attenuations
|
|
196
|
-
ind = ma.where(attenuations
|
|
197
|
-
z_corrected[ind] += attenuations[
|
|
218
|
+
z_corrected = self.data["Z"][:] + attenuations.gas.amount
|
|
219
|
+
ind = ma.where(attenuations.liquid.amount)
|
|
220
|
+
z_corrected[ind] += attenuations.liquid.amount[ind]
|
|
221
|
+
ind = ma.where(attenuations.rain.amount)
|
|
222
|
+
z_corrected[ind] += attenuations.rain.amount[ind]
|
|
223
|
+
ind = ma.where(attenuations.melting.amount)
|
|
224
|
+
z_corrected[ind] += attenuations.melting.amount[ind]
|
|
198
225
|
self.append_data(z_corrected, "Z")
|
|
199
226
|
|
|
200
227
|
def calc_errors(
|
|
201
|
-
self,
|
|
228
|
+
self,
|
|
229
|
+
attenuations: RadarAttenuation,
|
|
230
|
+
is_clutter: npt.NDArray,
|
|
202
231
|
) -> None:
|
|
203
232
|
"""Calculates uncertainties of radar echo.
|
|
204
233
|
|
|
@@ -207,7 +236,7 @@ class Radar(DataSource):
|
|
|
207
236
|
|
|
208
237
|
Args:
|
|
209
238
|
attenuations: 2-D attenuations due to atmospheric gases.
|
|
210
|
-
|
|
239
|
+
is_clutter: 2-D boolean array denoting pixels contaminated by clutter.
|
|
211
240
|
|
|
212
241
|
References:
|
|
213
242
|
The method is based on Hogan R. and O'Connor E., 2004,
|
|
@@ -215,40 +244,62 @@ class Radar(DataSource):
|
|
|
215
244
|
|
|
216
245
|
"""
|
|
217
246
|
|
|
218
|
-
def _calc_sensitivity() ->
|
|
247
|
+
def _calc_sensitivity() -> npt.NDArray:
|
|
219
248
|
"""Returns sensitivity of radar as function of altitude."""
|
|
220
|
-
mean_gas_atten = ma.mean(attenuations
|
|
249
|
+
mean_gas_atten = ma.mean(attenuations.gas.amount, axis=0)
|
|
221
250
|
z_sensitivity = z_power_min + log_range + mean_gas_atten
|
|
222
|
-
zc = ma.median(ma.array(z, mask=~
|
|
251
|
+
zc = ma.median(ma.array(z, mask=~is_clutter), axis=0)
|
|
223
252
|
valid_values = np.logical_not(zc.mask)
|
|
224
253
|
z_sensitivity[valid_values] = zc[valid_values]
|
|
225
254
|
return z_sensitivity
|
|
226
255
|
|
|
227
|
-
def _calc_error() ->
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
256
|
+
def _calc_error() -> npt.NDArray | float:
|
|
257
|
+
"""Returns error of radar as function of altitude.
|
|
258
|
+
|
|
259
|
+
References:
|
|
260
|
+
Hogan, R. J., 1998: Dual-wavelength radar studies of clouds.
|
|
261
|
+
PhD Thesis, University of Reading, UK.
|
|
262
|
+
|
|
263
|
+
"""
|
|
264
|
+
noise_threshold = 3
|
|
265
|
+
n_pulses = _number_of_independent_pulses()
|
|
266
|
+
ln_to_log10 = 10 / np.log(10)
|
|
267
|
+
z_precision = ma.divide(ln_to_log10, np.sqrt(n_pulses)) * (
|
|
268
|
+
1 + (utils.db2lin(z_power_min - z_power) / noise_threshold)
|
|
269
|
+
)
|
|
270
|
+
|
|
271
|
+
z_error = utils.l2norm(
|
|
272
|
+
z_precision,
|
|
273
|
+
attenuations.liquid.error.filled(0),
|
|
274
|
+
attenuations.rain.error.filled(0),
|
|
275
|
+
attenuations.melting.error.filled(0),
|
|
233
276
|
)
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
277
|
+
|
|
278
|
+
z_error[
|
|
279
|
+
attenuations.liquid.uncorrected
|
|
280
|
+
| attenuations.rain.uncorrected
|
|
281
|
+
| attenuations.melting.uncorrected
|
|
282
|
+
] = ma.masked
|
|
283
|
+
|
|
238
284
|
return z_error
|
|
239
285
|
|
|
240
286
|
def _number_of_independent_pulses() -> float:
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
287
|
+
"""Returns number of independent pulses.
|
|
288
|
+
|
|
289
|
+
References:
|
|
290
|
+
Atlas, D., 1964: Advances in radar meteorology.
|
|
291
|
+
Advances in Geophys., 10, 318-478.
|
|
292
|
+
|
|
293
|
+
"""
|
|
294
|
+
if "width" not in self.data:
|
|
295
|
+
default_width = 0.3
|
|
296
|
+
width = np.zeros_like(self.data["Z"][:])
|
|
297
|
+
width[~width.mask] = default_width
|
|
298
|
+
else:
|
|
299
|
+
width = self.data["width"][:]
|
|
300
|
+
dwell_time = utils.mdiff(self.time) * SEC_IN_HOUR
|
|
301
|
+
wl = SPEED_OF_LIGHT / (self.radar_frequency * GHZ_TO_HZ)
|
|
302
|
+
return 4 * np.sqrt(math.pi) * dwell_time * width / wl
|
|
252
303
|
|
|
253
304
|
def _calc_z_power_min() -> float:
|
|
254
305
|
if ma.all(z_power.mask):
|
|
@@ -256,7 +307,7 @@ class Radar(DataSource):
|
|
|
256
307
|
return np.percentile(z_power.compressed(), 0.1)
|
|
257
308
|
|
|
258
309
|
z = self.data["Z"][:]
|
|
259
|
-
radar_range = self.
|
|
310
|
+
radar_range = self.to_m(self.dataset.variables["range"])
|
|
260
311
|
log_range = utils.lin2db(radar_range, scale=20)
|
|
261
312
|
z_power = z - log_range
|
|
262
313
|
z_power_min = _calc_z_power_min()
|
|
@@ -266,57 +317,119 @@ class Radar(DataSource):
|
|
|
266
317
|
|
|
267
318
|
def add_meta(self) -> None:
|
|
268
319
|
"""Copies misc. metadata from the input file."""
|
|
269
|
-
for key in ("latitude", "longitude", "altitude"):
|
|
270
|
-
self.append_data(np.array(self.getvar(key)), key)
|
|
271
320
|
for key in ("time", "height", "radar_frequency"):
|
|
272
321
|
self.append_data(np.array(getattr(self, key)), key)
|
|
273
322
|
|
|
274
|
-
def
|
|
323
|
+
def add_location(self, time_new: npt.NDArray) -> None:
|
|
324
|
+
"""Add latitude, longitude and altitude from nearest timestamp."""
|
|
325
|
+
idx = np.searchsorted(self.time, time_new)
|
|
326
|
+
idx_left = np.clip(idx - 1, 0, len(self.time) - 1)
|
|
327
|
+
idx_right = np.clip(idx, 0, len(self.time) - 1)
|
|
328
|
+
diff_left = np.abs(time_new - self.time[idx_left])
|
|
329
|
+
diff_right = np.abs(time_new - self.time[idx_right])
|
|
330
|
+
idx_closest = np.where(diff_left < diff_right, idx_left, idx_right)
|
|
331
|
+
for key in ("latitude", "longitude", "altitude"):
|
|
332
|
+
data = self.getvar(key)
|
|
333
|
+
if not utils.isscalar(data):
|
|
334
|
+
data = data[idx_closest]
|
|
335
|
+
if not np.any(ma.getmaskarray(data)):
|
|
336
|
+
data = np.array(data)
|
|
337
|
+
self.append_data(data, key)
|
|
338
|
+
|
|
339
|
+
def _init_data(self) -> None:
|
|
275
340
|
self.append_data(self.getvar("Zh"), "Z", units="dBZ")
|
|
276
|
-
for key in ("v", "ldr", "width", "sldr", "rainfall_rate"):
|
|
277
|
-
try:
|
|
278
|
-
self._variables_to_cloudnet_arrays((key,))
|
|
279
|
-
except KeyError:
|
|
280
|
-
continue
|
|
281
|
-
|
|
282
|
-
def _init_sigma_v(self) -> None:
|
|
283
|
-
"""Initializes std of the velocity field. The std will be calculated
|
|
284
|
-
later when re-binning the data."""
|
|
285
341
|
self.append_data(self.getvar("v"), "v_sigma")
|
|
342
|
+
for key in ("v", "ldr", "width", "sldr", "rainfall_rate", "nyquist_velocity"):
|
|
343
|
+
if key in self.dataset.variables:
|
|
344
|
+
data = self.dataset.variables[key]
|
|
345
|
+
self.append_data(data, key)
|
|
286
346
|
|
|
287
|
-
def
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
347
|
+
def _rebin_velocity(
|
|
348
|
+
self,
|
|
349
|
+
data: npt.NDArray,
|
|
350
|
+
time_new: npt.NDArray,
|
|
351
|
+
) -> npt.NDArray:
|
|
352
|
+
"""Rebins Doppler velocity in polar coordinates."""
|
|
353
|
+
folding_velocity = self._get_expanded_folding_velocity()
|
|
354
|
+
# with the new shape (maximum value in every bin)
|
|
355
|
+
max_folding_binned, _ = utils.rebin_2d(
|
|
356
|
+
self.time,
|
|
357
|
+
folding_velocity,
|
|
358
|
+
time_new,
|
|
359
|
+
"max",
|
|
360
|
+
)
|
|
361
|
+
# store this in the file
|
|
362
|
+
self.append_data(max_folding_binned, "nyquist_velocity")
|
|
363
|
+
|
|
364
|
+
if "correction_bits" in self.dataset.variables:
|
|
365
|
+
bits = self.dataset.variables["correction_bits"][:]
|
|
366
|
+
dealiasing_bit = utils.isbit(bits, 0)
|
|
367
|
+
if ma.all(dealiasing_bit):
|
|
368
|
+
return utils.rebin_2d(self.time, data, time_new, "mean")[0]
|
|
369
|
+
if ma.any(dealiasing_bit):
|
|
370
|
+
msg = "Data are only party dealiased. Deal with this later."
|
|
371
|
+
raise NotImplementedError(msg)
|
|
372
|
+
|
|
373
|
+
# with original shape (repeat maximum value for each point in every bin)
|
|
374
|
+
max_folding_full, _ = utils.rebin_2d(
|
|
375
|
+
self.time,
|
|
376
|
+
folding_velocity,
|
|
377
|
+
time_new,
|
|
378
|
+
"max",
|
|
379
|
+
keepdim=True,
|
|
380
|
+
)
|
|
381
|
+
|
|
382
|
+
data_scaled = data * (np.pi / max_folding_full)
|
|
383
|
+
vel_x = ma.cos(data_scaled)
|
|
384
|
+
vel_y = ma.sin(data_scaled)
|
|
385
|
+
vel_x_mean, _ = utils.rebin_2d(self.time, vel_x, time_new)
|
|
386
|
+
vel_y_mean, _ = utils.rebin_2d(self.time, vel_y, time_new)
|
|
387
|
+
vel_scaled = ma.arctan2(vel_y_mean, vel_x_mean)
|
|
388
|
+
return vel_scaled / (np.pi / max_folding_binned)
|
|
389
|
+
|
|
390
|
+
def _get_expanded_folding_velocity(self) -> npt.NDArray:
|
|
298
391
|
if "nyquist_velocity" in self.dataset.variables:
|
|
299
|
-
|
|
300
|
-
|
|
392
|
+
fvel = self.getvar("nyquist_velocity")
|
|
393
|
+
elif "prf" in self.dataset.variables:
|
|
301
394
|
prf = self.getvar("prf")
|
|
302
|
-
|
|
303
|
-
raise RuntimeError("Unable to determine folding velocity")
|
|
304
|
-
|
|
305
|
-
def _get_folding_velocity_full(self):
|
|
306
|
-
folding_velocity: list | np.ndarray = []
|
|
307
|
-
if utils.isscalar(self.folding_velocity):
|
|
308
|
-
folding_velocity = np.repeat(
|
|
309
|
-
self.folding_velocity, len(self.sequence_indices[0])
|
|
310
|
-
)
|
|
395
|
+
fvel = _prf_to_folding_velocity(prf, self.radar_frequency)
|
|
311
396
|
else:
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
397
|
+
msg = "Unable to determine folding velocity"
|
|
398
|
+
raise RuntimeError(msg)
|
|
399
|
+
|
|
400
|
+
n_time = self.getvar("time").size
|
|
401
|
+
n_height = self.height.size
|
|
402
|
+
|
|
403
|
+
if fvel.shape == (n_time, n_height):
|
|
404
|
+
# Folding velocity is already expanded in radar file
|
|
405
|
+
return fvel
|
|
406
|
+
if utils.isscalar(fvel):
|
|
407
|
+
# e.g. MIRA
|
|
408
|
+
return np.broadcast_to(fvel, (n_time, n_height))
|
|
409
|
+
|
|
410
|
+
# RPG radars have chirp segments
|
|
411
|
+
starts = self.getvar("chirp_start_indices")
|
|
412
|
+
n_seg = starts.size if starts.ndim == 1 else starts.shape[1]
|
|
413
|
+
|
|
414
|
+
starts = np.broadcast_to(starts, (n_time, n_seg))
|
|
415
|
+
fvel = np.broadcast_to(fvel, (n_time, n_seg))
|
|
416
|
+
|
|
417
|
+
# Indices should start from zero (first range gate)
|
|
418
|
+
# In pre-processed RV Meteor files the first index is 1, so normalize:
|
|
419
|
+
# Normalize starts so indices begin from zero
|
|
420
|
+
first_values = starts[:, [0]]
|
|
421
|
+
if not np.all(np.isin(first_values, [0, 1])):
|
|
422
|
+
msg = "First value of chirp_start_indices must be 0 or 1"
|
|
423
|
+
raise ValueError(msg)
|
|
424
|
+
starts = starts - first_values
|
|
425
|
+
|
|
426
|
+
chirp_size = np.diff(starts, append=n_height)
|
|
427
|
+
return np.repeat(fvel.ravel(), chirp_size.ravel()).reshape((n_time, n_height))
|
|
318
428
|
|
|
319
429
|
|
|
320
|
-
def _prf_to_folding_velocity(prf:
|
|
430
|
+
def _prf_to_folding_velocity(prf: npt.NDArray, radar_frequency: float) -> npt.NDArray:
|
|
321
431
|
ghz_to_hz = 1e9
|
|
322
|
-
|
|
432
|
+
if len(prf) != 1:
|
|
433
|
+
msg = "Unable to determine folding velocity"
|
|
434
|
+
raise RuntimeError(msg)
|
|
435
|
+
return prf[0] * constants.c / (4 * radar_frequency * ghz_to_hz)
|