cloudnetpy 1.85.5__py3-none-any.whl → 1.86.1__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/containers.py +12 -4
- cloudnetpy/categorize/disdrometer.py +5 -2
- cloudnetpy/categorize/radar.py +0 -1
- cloudnetpy/instruments/bowtie.py +5 -0
- cloudnetpy/instruments/fd12p.py +0 -3
- cloudnetpy/instruments/rpg.py +125 -88
- cloudnetpy/metadata.py +3 -0
- cloudnetpy/output.py +5 -2
- cloudnetpy/plotting/plot_meta.py +1 -1
- cloudnetpy/products/classification.py +18 -5
- cloudnetpy/utils.py +45 -5
- cloudnetpy/version.py +2 -2
- {cloudnetpy-1.85.5.dist-info → cloudnetpy-1.86.1.dist-info}/METADATA +1 -1
- {cloudnetpy-1.85.5.dist-info → cloudnetpy-1.86.1.dist-info}/RECORD +18 -18
- {cloudnetpy-1.85.5.dist-info → cloudnetpy-1.86.1.dist-info}/WHEEL +0 -0
- {cloudnetpy-1.85.5.dist-info → cloudnetpy-1.86.1.dist-info}/entry_points.txt +0 -0
- {cloudnetpy-1.85.5.dist-info → cloudnetpy-1.86.1.dist-info}/licenses/LICENSE +0 -0
- {cloudnetpy-1.85.5.dist-info → cloudnetpy-1.86.1.dist-info}/top_level.txt +0 -0
@@ -92,10 +92,18 @@ class ClassData:
|
|
92
92
|
rain_from_disdrometer = self._find_rain_from_disdrometer()
|
93
93
|
ind = ~rain_from_disdrometer.mask
|
94
94
|
is_rain[ind] = rain_from_disdrometer[ind]
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
95
|
+
# Filter out snowfall:
|
96
|
+
if (
|
97
|
+
self.data.disdrometer is not None
|
98
|
+
and "synop_WaWa" in self.data.disdrometer.data
|
99
|
+
):
|
100
|
+
wawa = self.data.disdrometer.data["synop_WaWa"].data
|
101
|
+
liquid_rain_only = (wawa >= 57) & (wawa <= 68)
|
102
|
+
else:
|
103
|
+
mask = ma.getmaskarray(self.tw)
|
104
|
+
first_valid_ind = np.nonzero(np.all(~mask, axis=0))[0][0]
|
105
|
+
liquid_rain_only = self.tw[:, first_valid_ind] > T0 + 5
|
106
|
+
return is_rain & liquid_rain_only
|
99
107
|
|
100
108
|
def _find_rain_from_radar_echo(self) -> npt.NDArray:
|
101
109
|
first_gate_with_data = np.argmin(self.z.mask.all(axis=0))
|
@@ -24,14 +24,17 @@ class Disdrometer(DataSource):
|
|
24
24
|
|
25
25
|
def interpolate_to_grid(self, time_grid: npt.NDArray) -> None:
|
26
26
|
for key, array in self.data.items():
|
27
|
+
method = "nearest" if key == "synop_WaWa" else "linear"
|
27
28
|
self.data[key].data = interpolate_1d(
|
28
|
-
self.time, array.data, time_grid, max_time=1
|
29
|
+
self.time, array.data, time_grid, max_time=1, method=method
|
29
30
|
)
|
30
31
|
|
31
32
|
def _init_rainfall_rate(self) -> None:
|
32
|
-
keys = ("rainfall_rate", "n_particles")
|
33
|
+
keys = ("rainfall_rate", "n_particles", "synop_WaWa")
|
33
34
|
for key in keys:
|
34
35
|
if key not in self.dataset.variables:
|
36
|
+
if key == "synop_WaWa":
|
37
|
+
continue
|
35
38
|
msg = f"variable {key} is missing"
|
36
39
|
raise DisdrometerDataError(msg)
|
37
40
|
self.append_data(self.dataset.variables[key][:], key)
|
cloudnetpy/categorize/radar.py
CHANGED
cloudnetpy/instruments/bowtie.py
CHANGED
cloudnetpy/instruments/fd12p.py
CHANGED
cloudnetpy/instruments/rpg.py
CHANGED
@@ -1,9 +1,7 @@
|
|
1
|
-
"""This module contains RPG Cloud Radar related functions."""
|
2
|
-
|
3
1
|
import datetime
|
4
2
|
import logging
|
5
3
|
import math
|
6
|
-
from collections.abc import
|
4
|
+
from collections.abc import Sequence
|
7
5
|
from os import PathLike
|
8
6
|
from uuid import UUID
|
9
7
|
|
@@ -15,7 +13,7 @@ from rpgpy import RPGFileError
|
|
15
13
|
from cloudnetpy import output, utils
|
16
14
|
from cloudnetpy.cloudnetarray import CloudnetArray
|
17
15
|
from cloudnetpy.constants import G_TO_KG, HPA_TO_PA, KM_H_TO_M_S, MM_H_TO_M_S
|
18
|
-
from cloudnetpy.exceptions import
|
16
|
+
from cloudnetpy.exceptions import ValidTimeStampError
|
19
17
|
from cloudnetpy.instruments import instruments
|
20
18
|
from cloudnetpy.instruments.cloudnet_instrument import CloudnetInstrument
|
21
19
|
from cloudnetpy.instruments.instruments import Instrument
|
@@ -68,9 +66,8 @@ def rpg2nc(
|
|
68
66
|
l1_files = utils.get_sorted_filenames(path_to_l1_files, ".LV1")
|
69
67
|
fmcw94_objects, valid_files = _get_fmcw94_objects(l1_files, date)
|
70
68
|
one_day_of_data = create_one_day_data_record(fmcw94_objects)
|
71
|
-
|
72
|
-
|
73
|
-
print_info(one_day_of_data)
|
69
|
+
one_day_of_data["nyquist_velocity"] = _expand_nyquist(one_day_of_data)
|
70
|
+
_print_info(one_day_of_data)
|
74
71
|
fmcw = Fmcw(one_day_of_data, site_meta)
|
75
72
|
fmcw.convert_time_to_fraction_hour()
|
76
73
|
fmcw.mask_invalid_ldr()
|
@@ -93,7 +90,7 @@ def rpg2nc(
|
|
93
90
|
return uuid, valid_files
|
94
91
|
|
95
92
|
|
96
|
-
def
|
93
|
+
def _print_info(data: dict) -> None:
|
97
94
|
dual_pol = data["dual_polarization"]
|
98
95
|
if dual_pol == 0:
|
99
96
|
mode = "single polarisation"
|
@@ -108,57 +105,66 @@ RpgObjects = Sequence[Fmcw94Bin] | Sequence[HatproBinCombined]
|
|
108
105
|
|
109
106
|
|
110
107
|
def create_one_day_data_record(rpg_objects: RpgObjects) -> dict:
|
111
|
-
"""Concatenates all RPG data from one day."""
|
108
|
+
"""Concatenates all RPG FMCW / HATPRO data from one day."""
|
112
109
|
rpg_raw_data, rpg_header = _stack_rpg_data(rpg_objects)
|
113
|
-
if
|
114
|
-
rpg_header =
|
110
|
+
if "range" in rpg_header:
|
111
|
+
rpg_header["range"] = rpg_objects[0].header["range"]
|
112
|
+
should_be_constant = [
|
113
|
+
"model_number",
|
114
|
+
"dual_polarization",
|
115
|
+
"antenna_separation",
|
116
|
+
"antenna_diameter",
|
117
|
+
"antenna_gain",
|
118
|
+
"half_power_beam_width",
|
119
|
+
"radar_frequency",
|
120
|
+
]
|
121
|
+
for key in should_be_constant:
|
122
|
+
if key not in rpg_header:
|
123
|
+
continue
|
124
|
+
unique_values = np.unique(rpg_header[key])
|
125
|
+
if len(unique_values) > 1:
|
126
|
+
msg = f"More than one value for {key} found: {unique_values}"
|
127
|
+
raise ValueError(msg)
|
128
|
+
rpg_header[key] = unique_values[0]
|
129
|
+
|
115
130
|
rpg_raw_data = _mask_invalid_data(rpg_raw_data)
|
116
131
|
return {**rpg_header, **rpg_raw_data}
|
117
132
|
|
118
133
|
|
119
|
-
def
|
120
|
-
"""
|
121
|
-
|
122
|
-
|
123
|
-
|
134
|
+
def _expand_nyquist(data: dict) -> npt.NDArray:
|
135
|
+
"""Expands Nyquist velocity from time X chirp => time X range."""
|
136
|
+
nyquist_velocity = ma.array(data["nyquist_velocity"])
|
137
|
+
chirp_start_indices = ma.array(data["chirp_start_indices"])
|
138
|
+
n_time = chirp_start_indices.shape[0]
|
139
|
+
n_range = len(data["range"])
|
140
|
+
expanded_nyquist = np.empty((n_time, n_range))
|
141
|
+
for t in range(n_time):
|
142
|
+
starts = chirp_start_indices[t].compressed()
|
143
|
+
v_nyq = nyquist_velocity[t].compressed()
|
144
|
+
ends = np.r_[starts[1:], n_range]
|
145
|
+
seg_lengths = ends - starts
|
146
|
+
expanded_nyquist[t, :] = np.repeat(v_nyq, seg_lengths)
|
147
|
+
return expanded_nyquist
|
124
148
|
|
125
|
-
"""
|
126
|
-
|
127
|
-
def _stack(source: dict, target: dict, fun: Callable) -> None:
|
128
|
-
for name, value in source.items():
|
129
|
-
if not name.startswith("_"):
|
130
|
-
target[name] = fun((target[name], value)) if name in target else value
|
131
149
|
|
150
|
+
def _stack_rpg_data(rpg_objects: RpgObjects) -> tuple[dict, dict]:
|
132
151
|
data: dict = {}
|
133
152
|
header: dict = {}
|
134
153
|
for rpg in rpg_objects:
|
135
|
-
|
136
|
-
|
154
|
+
for src, dst in ((rpg.data, data), (rpg.header, header)):
|
155
|
+
for name, value in src.items():
|
156
|
+
if name.startswith("_"):
|
157
|
+
continue
|
158
|
+
arr = dst.get(name)
|
159
|
+
fun = (
|
160
|
+
ma.concatenate
|
161
|
+
if any(isinstance(x, ma.MaskedArray) for x in (value, arr))
|
162
|
+
else np.concatenate
|
163
|
+
)
|
164
|
+
dst[name] = fun((arr, value)) if arr is not None else value
|
137
165
|
return data, header
|
138
166
|
|
139
167
|
|
140
|
-
def _reduce_header(header: dict) -> dict:
|
141
|
-
"""Removes duplicate header data. Otherwise, we would need n_files dimension."""
|
142
|
-
reduced_header = {}
|
143
|
-
for key, data in header.items():
|
144
|
-
# Handle outliers in latitude and longitude (e.g. Galati 2024-02-11):
|
145
|
-
if key in ("latitude", "longitude"):
|
146
|
-
reduced_header[key] = ma.median(data)
|
147
|
-
continue
|
148
|
-
first_profile_value = data[0]
|
149
|
-
is_identical_value = bool(
|
150
|
-
np.isclose(data, first_profile_value, rtol=1e-2).all(),
|
151
|
-
)
|
152
|
-
if is_identical_value is False:
|
153
|
-
msg = f"Inconsistent header: {key}: {data}"
|
154
|
-
if key in ("sample_duration", "calibration_interval", "noise_threshold"):
|
155
|
-
logging.warning(msg)
|
156
|
-
else:
|
157
|
-
raise InconsistentDataError(msg)
|
158
|
-
reduced_header[key] = first_profile_value
|
159
|
-
return reduced_header
|
160
|
-
|
161
|
-
|
162
168
|
def _mask_invalid_data(data_in: dict) -> dict:
|
163
169
|
"""Masks zeros and other fill values from data."""
|
164
170
|
data = data_in.copy()
|
@@ -193,33 +199,68 @@ def _get_fmcw94_objects(
|
|
193
199
|
continue
|
194
200
|
objects.append(obj)
|
195
201
|
valid_files.append(file)
|
196
|
-
if objects:
|
197
|
-
|
198
|
-
|
199
|
-
|
202
|
+
if not objects:
|
203
|
+
msg = "No valid files found"
|
204
|
+
raise ValidTimeStampError(msg)
|
205
|
+
objects = _interpolate_to_common_height(objects)
|
206
|
+
objects = _pad_chirp_related_fields(objects)
|
207
|
+
objects = _expand_time_related_fields(objects)
|
200
208
|
return objects, valid_files
|
201
209
|
|
202
210
|
|
203
|
-
def
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
for
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
211
|
+
def _interpolate_to_common_height(objects: list[Fmcw94Bin]) -> list[Fmcw94Bin]:
|
212
|
+
range_arrays = [obj.header["range"] for obj in objects]
|
213
|
+
if all(np.array_equal(range_arrays[0], r) for r in range_arrays[1:]):
|
214
|
+
return objects
|
215
|
+
# Use range with the highest range gate for interpolation
|
216
|
+
target_height = max(range_arrays, key=lambda r: r[-1])
|
217
|
+
for obj in objects:
|
218
|
+
src_range = obj.header["range"]
|
219
|
+
if np.array_equal(src_range, target_height):
|
220
|
+
continue
|
221
|
+
for key, arr in obj.data.items():
|
222
|
+
if arr.ndim == 2 and arr.shape[1] == src_range.size:
|
223
|
+
obj.data[key] = utils.interpolate_2D_along_y(
|
224
|
+
src_range, arr, target_height
|
225
|
+
)
|
226
|
+
obj.header["range"] = target_height
|
227
|
+
return objects
|
228
|
+
|
229
|
+
|
230
|
+
def _pad_chirp_related_fields(objects: list[Fmcw94Bin]) -> list[Fmcw94Bin]:
|
231
|
+
"""Pads chirp-related header fields with masked values to have the same length."""
|
232
|
+
chirp_lens = [len(obj.header["chirp_start_indices"]) for obj in objects]
|
233
|
+
if all(chirp_lens[0] == length for length in chirp_lens[1:]):
|
234
|
+
return objects
|
235
|
+
max_chirp_len = max(chirp_lens)
|
236
|
+
for obj in objects:
|
237
|
+
n_chirps = len(obj.header["chirp_start_indices"])
|
238
|
+
if n_chirps == max_chirp_len:
|
239
|
+
continue
|
240
|
+
for key, arr in obj.header.items():
|
241
|
+
if not isinstance(arr, str) and arr.ndim == 1 and arr.size == n_chirps:
|
242
|
+
pad_len = max_chirp_len - n_chirps
|
243
|
+
masked_arr = ma.array(arr, dtype=arr.dtype)
|
244
|
+
pad = ma.masked_all(pad_len, dtype=arr.dtype)
|
245
|
+
obj.header[key] = ma.concatenate([masked_arr, pad])
|
246
|
+
return objects
|
247
|
+
|
248
|
+
|
249
|
+
def _expand_time_related_fields(objects: list[Fmcw94Bin]) -> list[Fmcw94Bin]:
|
250
|
+
for obj in objects:
|
251
|
+
n_time = obj.data["time"].size
|
252
|
+
for key in obj.header:
|
253
|
+
if key in ("range", "time") or key.startswith("_"):
|
254
|
+
continue
|
255
|
+
arr = obj.header[key]
|
256
|
+
# Handle outliers in latitude and longitude (e.g. Galati 2024-02-11):
|
257
|
+
if key in ("latitude", "longitude"):
|
258
|
+
arr = ma.median(arr)
|
259
|
+
if utils.isscalar(arr):
|
260
|
+
obj.header[key] = np.repeat(arr, n_time)
|
261
|
+
else:
|
262
|
+
obj.header[key] = np.tile(arr, (n_time, 1))
|
263
|
+
return objects
|
223
264
|
|
224
265
|
|
225
266
|
def _validate_date(obj: Fmcw94Bin, expected_date: datetime.date) -> None:
|
@@ -443,9 +484,11 @@ RPG_ATTRIBUTES = {
|
|
443
484
|
long_name="File code",
|
444
485
|
units="1",
|
445
486
|
comment="Indicates the RPG software version.",
|
446
|
-
dimensions=
|
487
|
+
dimensions=("time",),
|
488
|
+
),
|
489
|
+
"program_number": MetaData(
|
490
|
+
long_name="Program number", units="1", dimensions=("time",)
|
447
491
|
),
|
448
|
-
"program_number": MetaData(long_name="Program number", units="1", dimensions=None),
|
449
492
|
"model_number": MetaData(
|
450
493
|
long_name="Model number",
|
451
494
|
units="1",
|
@@ -469,54 +512,51 @@ RPG_ATTRIBUTES = {
|
|
469
512
|
dimensions=None,
|
470
513
|
),
|
471
514
|
"sample_duration": MetaData(
|
472
|
-
long_name="Sample duration", units="s", dimensions=
|
515
|
+
long_name="Sample duration", units="s", dimensions=("time",)
|
473
516
|
),
|
474
517
|
"calibration_interval": MetaData(
|
475
|
-
long_name="Calibration interval in samples", units="1", dimensions=
|
518
|
+
long_name="Calibration interval in samples", units="1", dimensions=("time",)
|
476
519
|
),
|
477
520
|
"number_of_spectral_samples": MetaData(
|
478
521
|
long_name="Number of spectral samples in each chirp sequence",
|
479
522
|
units="1",
|
480
|
-
dimensions=("chirp_sequence"
|
481
|
-
),
|
482
|
-
"nyquist_velocity": MetaData(
|
483
|
-
long_name="Nyquist velocity", units="m s-1", dimensions=("chirp_sequence",)
|
523
|
+
dimensions=("time", "chirp_sequence"),
|
484
524
|
),
|
485
525
|
"number_of_averaged_chirps": MetaData(
|
486
526
|
long_name="Number of averaged chirps in sequence",
|
487
527
|
units="1",
|
488
|
-
dimensions=("chirp_sequence"
|
528
|
+
dimensions=("time", "chirp_sequence"),
|
489
529
|
),
|
490
530
|
"chirp_start_indices": MetaData(
|
491
531
|
long_name="Chirp sequences start indices",
|
492
532
|
units="1",
|
493
|
-
dimensions=("chirp_sequence"
|
533
|
+
dimensions=("time", "chirp_sequence"),
|
494
534
|
),
|
495
535
|
"integration_time": MetaData(
|
496
536
|
long_name="Integration time",
|
497
537
|
units="s",
|
498
538
|
comment="Effective integration time of chirp sequence",
|
499
|
-
dimensions=("chirp_sequence"
|
539
|
+
dimensions=("time", "chirp_sequence"),
|
500
540
|
),
|
501
541
|
"range_resolution": MetaData(
|
502
542
|
long_name="Vertical resolution of range",
|
503
543
|
units="m",
|
504
|
-
dimensions=("chirp_sequence"
|
544
|
+
dimensions=("time", "chirp_sequence"),
|
505
545
|
),
|
506
546
|
"FFT_window": MetaData(
|
507
547
|
long_name="FFT window type",
|
508
548
|
units="1",
|
509
549
|
definition=DEFINITIONS["FFT_window"],
|
510
|
-
dimensions=
|
550
|
+
dimensions=("time",),
|
511
551
|
),
|
512
552
|
"input_voltage_range": MetaData(
|
513
|
-
long_name="ADC input voltage range (+/-)", units="mV", dimensions=
|
553
|
+
long_name="ADC input voltage range (+/-)", units="mV", dimensions=("time",)
|
514
554
|
),
|
515
555
|
"noise_threshold": MetaData(
|
516
556
|
long_name="Noise filter threshold factor",
|
517
557
|
units="1",
|
518
558
|
comment="Multiple of the standard deviation of Doppler spectra.",
|
519
|
-
dimensions=
|
559
|
+
dimensions=("time",),
|
520
560
|
),
|
521
561
|
"time_ms": MetaData(long_name="Time ms", units="ms", dimensions=("time",)),
|
522
562
|
"quality_flag": MetaData(
|
@@ -545,7 +585,4 @@ RPG_ATTRIBUTES = {
|
|
545
585
|
"pc_temperature": MetaData(
|
546
586
|
long_name="PC temperature", units="K", dimensions=("time",)
|
547
587
|
),
|
548
|
-
"correlation_coefficient": MetaData(
|
549
|
-
long_name="Correlation coefficient", units="1", dimensions=None
|
550
|
-
),
|
551
588
|
}
|
cloudnetpy/metadata.py
CHANGED
cloudnetpy/output.py
CHANGED
@@ -391,7 +391,6 @@ def _write_vars2nc(nc: netCDF4.Dataset, cloudnet_variables: dict) -> None:
|
|
391
391
|
else:
|
392
392
|
fill_value = False
|
393
393
|
size = obj.dimensions if obj.dimensions is not None else ()
|
394
|
-
|
395
394
|
nc_variable = nc.createVariable(
|
396
395
|
obj.name,
|
397
396
|
obj.data_type,
|
@@ -399,7 +398,11 @@ def _write_vars2nc(nc: netCDF4.Dataset, cloudnet_variables: dict) -> None:
|
|
399
398
|
zlib=True,
|
400
399
|
fill_value=fill_value,
|
401
400
|
)
|
402
|
-
|
401
|
+
try:
|
402
|
+
nc_variable[:] = obj.data
|
403
|
+
except IndexError as err:
|
404
|
+
msg = f"Unable to write variable {obj.name} to file: {err}"
|
405
|
+
raise IndexError(msg) from err
|
403
406
|
for attr in obj.fetch_attributes():
|
404
407
|
setattr(nc_variable, attr, getattr(obj, attr))
|
405
408
|
|
cloudnetpy/plotting/plot_meta.py
CHANGED
@@ -11,6 +11,7 @@ from numpy import ma
|
|
11
11
|
|
12
12
|
from cloudnetpy import output, utils
|
13
13
|
from cloudnetpy.categorize import atmos_utils
|
14
|
+
from cloudnetpy.constants import M_S_TO_MM_H
|
14
15
|
from cloudnetpy.datasource import DataSource
|
15
16
|
from cloudnetpy.metadata import MetaData
|
16
17
|
from cloudnetpy.products.product_tools import CategorizeBits, QualityBits
|
@@ -189,24 +190,36 @@ def _get_attenuation_classes(data_source: DataSource) -> AttenuationClass:
|
|
189
190
|
rain_atten = _read_atten("radar_rain_atten")
|
190
191
|
melting_atten = _read_atten("radar_melting_atten")
|
191
192
|
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
):
|
193
|
+
not_w_band = data_source.getvar("radar_frequency") < 90
|
194
|
+
|
195
|
+
if "lwp" not in data_source.dataset.variables or not_w_band:
|
196
196
|
lwp = np.zeros(data_source.time.shape)
|
197
197
|
else:
|
198
198
|
lwp_data = data_source.getvar("lwp")
|
199
199
|
lwp = lwp_data.filled(0) if isinstance(lwp_data, ma.MaskedArray) else lwp_data
|
200
200
|
|
201
|
+
if "rainfall_rate" not in data_source.dataset.variables or not_w_band:
|
202
|
+
rain_rate = np.zeros(data_source.time.shape)
|
203
|
+
else:
|
204
|
+
rain_data = data_source.getvar("rainfall_rate") * M_S_TO_MM_H
|
205
|
+
rain_rate = (
|
206
|
+
rain_data.filled(0) if isinstance(rain_data, ma.MaskedArray) else rain_data
|
207
|
+
)
|
208
|
+
|
201
209
|
total_atten = liquid_atten + rain_atten + melting_atten
|
202
210
|
|
203
211
|
threshold_moderate = 10 # dB
|
204
212
|
threshold_severe = 15 # dB
|
205
213
|
threshold_lwp = 1 # kg/m2
|
214
|
+
threshold_rain = 3 # mm/h
|
206
215
|
|
207
216
|
small = total_atten > 0
|
208
217
|
moderate = total_atten >= threshold_moderate
|
209
|
-
severe = (
|
218
|
+
severe = (
|
219
|
+
(total_atten > threshold_severe)
|
220
|
+
| (lwp[:, np.newaxis] > threshold_lwp)
|
221
|
+
| (rain_rate[:, np.newaxis] > threshold_rain)
|
222
|
+
)
|
210
223
|
|
211
224
|
return AttenuationClass(small=small, moderate=moderate, severe=severe)
|
212
225
|
|
cloudnetpy/utils.py
CHANGED
@@ -387,7 +387,7 @@ def interpolate_2d_mask(
|
|
387
387
|
def interpolate_2d_nearest(
|
388
388
|
x: npt.NDArray,
|
389
389
|
y: npt.NDArray,
|
390
|
-
z:
|
390
|
+
z: ma.MaskedArray,
|
391
391
|
x_new: npt.NDArray,
|
392
392
|
y_new: npt.NDArray,
|
393
393
|
) -> ma.MaskedArray:
|
@@ -419,17 +419,57 @@ def interpolate_2d_nearest(
|
|
419
419
|
return fun((xx, yy)).T
|
420
420
|
|
421
421
|
|
422
|
+
def interpolate_2D_along_y(
|
423
|
+
y: npt.NDArray,
|
424
|
+
z: npt.NDArray | ma.MaskedArray,
|
425
|
+
y_new: npt.NDArray,
|
426
|
+
) -> ma.MaskedArray:
|
427
|
+
"""Fast 1D nearest-neighbor interpolation along y for each x.
|
428
|
+
|
429
|
+
Args:
|
430
|
+
y: 1D numpy array of y-coordinates (length M).
|
431
|
+
z: 2D array of shape (N, M).
|
432
|
+
y_new: 1D numpy array of new y-coordinates.
|
433
|
+
|
434
|
+
Returns:
|
435
|
+
Masked 2D masked array interpolated along y.
|
436
|
+
|
437
|
+
Notes:
|
438
|
+
Only interpolates along y. Points outside range are masked.
|
439
|
+
"""
|
440
|
+
idx = np.searchsorted(y, y_new, side="left")
|
441
|
+
idx = np.clip(idx, 0, len(y) - 1)
|
442
|
+
left = np.maximum(idx - 1, 0)
|
443
|
+
choose_right = (idx == 0) | (
|
444
|
+
(idx < len(y)) & (np.abs(y[idx] - y_new) < np.abs(y_new - y[left]))
|
445
|
+
)
|
446
|
+
idx[~choose_right] = left[~choose_right]
|
447
|
+
z_interp = ma.array(z[:, idx])
|
448
|
+
mask = (y_new < y.min()) | (y_new > y.max())
|
449
|
+
if z_interp.mask is ma.nomask:
|
450
|
+
z_mask = np.zeros(z_interp.shape, dtype=bool)
|
451
|
+
else:
|
452
|
+
z_mask = z_interp.mask.copy()
|
453
|
+
z_mask[:, mask] = True
|
454
|
+
return ma.MaskedArray(z_interp, mask=z_mask)
|
455
|
+
|
456
|
+
|
422
457
|
def interpolate_1d(
|
423
|
-
time: npt.NDArray,
|
424
|
-
|
458
|
+
time: npt.NDArray,
|
459
|
+
y: ma.MaskedArray,
|
460
|
+
time_new: npt.NDArray,
|
461
|
+
max_time: float,
|
462
|
+
method: str = "linear",
|
463
|
+
) -> ma.MaskedArray:
|
425
464
|
"""1D linear interpolation preserving the mask.
|
426
465
|
|
427
466
|
Args:
|
428
467
|
time: 1D array in fraction hour.
|
429
|
-
y: 1D
|
468
|
+
y: 1D array, data values.
|
430
469
|
time_new: 1D array, new time coordinates.
|
431
470
|
max_time: Maximum allowed gap in minutes. Values outside this gap will
|
432
471
|
be masked.
|
472
|
+
method: Interpolation method, 'linear' (default) or 'nearest'.
|
433
473
|
"""
|
434
474
|
if np.max(time) > 24 or np.min(time) < 0:
|
435
475
|
msg = "Time vector must be in fraction hours between 0 and 24"
|
@@ -439,7 +479,7 @@ def interpolate_1d(
|
|
439
479
|
return ma.masked_all(time_new.shape)
|
440
480
|
time = time[~y.mask]
|
441
481
|
y = y[~y.mask]
|
442
|
-
fun = interp1d(time, y, fill_value=(y[0], y[-1]), bounds_error=False)
|
482
|
+
fun = interp1d(time, y, kind=method, fill_value=(y[0], y[-1]), bounds_error=False)
|
443
483
|
interpolated = ma.array(fun(time_new))
|
444
484
|
bad_idx = get_gap_ind(time, time_new, max_time / 60)
|
445
485
|
|
cloudnetpy/version.py
CHANGED
@@ -5,18 +5,18 @@ cloudnetpy/concat_lib.py,sha256=u4UOjYzLnThaq-89iwA837OcOJpfmj_3RmRwBCFUh74,1321
|
|
5
5
|
cloudnetpy/constants.py,sha256=YnoSzZm35NDooJfhlulSJBc7g0eSchT3yGytRaTaJEI,845
|
6
6
|
cloudnetpy/datasource.py,sha256=EMJ4UHD8Z-JJ9Q82S7RgU1I2q4Z0RcBzBMKUAIwnZBI,6356
|
7
7
|
cloudnetpy/exceptions.py,sha256=ZB3aUwjVRznR0CcZ5sZHrB0yz13URDf52Ksv7G7C7EA,1817
|
8
|
-
cloudnetpy/metadata.py,sha256=
|
9
|
-
cloudnetpy/output.py,sha256=
|
8
|
+
cloudnetpy/metadata.py,sha256=qqLXSUVhIi6H_Z1zlfXeEkc9qBqA1XO-UwsCVxjIcfk,7317
|
9
|
+
cloudnetpy/output.py,sha256=NpuxmtLk2JLTdubCupdL4rFbx30YqgeU6vwAe_v8thc,15133
|
10
10
|
cloudnetpy/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
11
|
-
cloudnetpy/utils.py,sha256=
|
12
|
-
cloudnetpy/version.py,sha256=
|
11
|
+
cloudnetpy/utils.py,sha256=9MMYok1A7oJS2XBkIFhaiDWt_MYLo8tLzF3_880masM,34321
|
12
|
+
cloudnetpy/version.py,sha256=QtEAE1savq9vVLdajaUQbntR7f8B0XJ7TQqre-v5kTA,72
|
13
13
|
cloudnetpy/categorize/__init__.py,sha256=gtvzWr0IDRn2oA6yHBvinEhTGTuub-JkrOv93lBsgrE,61
|
14
14
|
cloudnetpy/categorize/atmos_utils.py,sha256=uWc9TABVYPI0sn4H5Az9Jf6NVRaWyEKIi17f0pAJQxE,10679
|
15
15
|
cloudnetpy/categorize/attenuation.py,sha256=Y_-fzmQTltWTqIZTulJhovC7a6ifpMcaAazDJcnMIOc,990
|
16
16
|
cloudnetpy/categorize/categorize.py,sha256=UZph73hVCU-UIxB2837oyLFKvTk8fdgLC-krgJDg8zk,23227
|
17
17
|
cloudnetpy/categorize/classify.py,sha256=skA9K6Bxh9mFZ_fM4d78zt09BPDzfHLttXle6mFCMbw,8553
|
18
|
-
cloudnetpy/categorize/containers.py,sha256=
|
19
|
-
cloudnetpy/categorize/disdrometer.py,sha256=
|
18
|
+
cloudnetpy/categorize/containers.py,sha256=oXvDazVtd9u3PwNP8ssKMfjW8s6O1bKNcTYHif6AHko,5808
|
19
|
+
cloudnetpy/categorize/disdrometer.py,sha256=gJW6qD2Cn3w0JbH69wqtlxDFUhKb2ioR-e9f7VZwxvc,1283
|
20
20
|
cloudnetpy/categorize/droplet.py,sha256=wnMN9rHNSMZLXNXuYEd-RAS_8eAIIo2vkE7pp3DSTKs,8725
|
21
21
|
cloudnetpy/categorize/falling.py,sha256=ZscFGFPFz_Nlc7PwjVwFDSVOzMGOuCDVAFj7pLvYctQ,4475
|
22
22
|
cloudnetpy/categorize/freezing.py,sha256=gigqpb4qfeQSlKXkrPUwCbMnMsxl74thJWSRW2iHJOg,3796
|
@@ -26,7 +26,7 @@ cloudnetpy/categorize/lidar.py,sha256=CkIbsMFHROYnhIc1e1ci61-POqeL8AOf6RCZicpNBV
|
|
26
26
|
cloudnetpy/categorize/melting.py,sha256=vhc6zq3L4gp7oEPHMnQlT2m6YBE5-CS5gdTNA7gVHRg,6329
|
27
27
|
cloudnetpy/categorize/model.py,sha256=iFakrXy3npbg4qUrpUGEBEdwBnmlWsMgogPCtfWl7sw,6805
|
28
28
|
cloudnetpy/categorize/mwr.py,sha256=QKc6f_h0mCVjzSa4BrwTtVAPx0Tn1VJ4jqGmIBke5II,1710
|
29
|
-
cloudnetpy/categorize/radar.py,sha256=
|
29
|
+
cloudnetpy/categorize/radar.py,sha256=CHcCWm2Nl-ycBqmbNz8LPSTmuNzgmmNxaJO2uQ-o09c,16005
|
30
30
|
cloudnetpy/categorize/attenuations/__init__.py,sha256=kIyQEZ6VVO6jJOAndrt7jNU15pm0Cavh5GnDjFmIG1M,1040
|
31
31
|
cloudnetpy/categorize/attenuations/gas_attenuation.py,sha256=emr-RCxQT0i2N8k6eBNhRsmsCBPHJzQsWJfjC4fVSTo,975
|
32
32
|
cloudnetpy/categorize/attenuations/liquid_attenuation.py,sha256=bmqmPk_93J4njE16-VQ1bPI7oNSS8m9ACuUH7IErBs8,3069
|
@@ -34,14 +34,14 @@ cloudnetpy/categorize/attenuations/melting_attenuation.py,sha256=zmpF7Gek4W9cF-5
|
|
34
34
|
cloudnetpy/categorize/attenuations/rain_attenuation.py,sha256=wJPyCiKWzsQDzMhqbA7mYwj9YRVcJIpXWhBnEYFy3uU,2843
|
35
35
|
cloudnetpy/instruments/__init__.py,sha256=PEgrrQNoiOuN_ctYilmt4LV2QCLg1likPjJdWtuGlLs,528
|
36
36
|
cloudnetpy/instruments/basta.py,sha256=N-kRgl5Vm52pXzr9umo4YsA0hn4zZCOa-0_zZTzhheY,4284
|
37
|
-
cloudnetpy/instruments/bowtie.py,sha256=
|
37
|
+
cloudnetpy/instruments/bowtie.py,sha256=1wavsYkVMC81CflUD-nTjeyjmATGKYfEJ6aNcwOOapM,4450
|
38
38
|
cloudnetpy/instruments/ceilo.py,sha256=scb3n0gLOZ1gPs_WdWw9KS5Ut_mtCvSdWFMKKCvXhBI,8979
|
39
39
|
cloudnetpy/instruments/ceilometer.py,sha256=c37uteeuGnlE-o-Smu49H2qQJw6qZ0tc3Bzhyr1FoSo,13063
|
40
40
|
cloudnetpy/instruments/cl61d.py,sha256=JsCHqVzFGhZi-5xcnsB507FDpyuw83uSWK3IFO3DhdI,2238
|
41
41
|
cloudnetpy/instruments/cloudnet_instrument.py,sha256=B1UkiB0ytnT3MWYalEegql5QIPaMLg5bJy5xI50JEco,4503
|
42
42
|
cloudnetpy/instruments/copernicus.py,sha256=ygEViERBSJdMeP9OxfLelZRDEbSRzY8n17ruYie2wm4,6970
|
43
43
|
cloudnetpy/instruments/da10.py,sha256=FcXEu6dfAVvYzMvauiJBSyEkYhGa8hBuwrrdo-3LwHE,1960
|
44
|
-
cloudnetpy/instruments/fd12p.py,sha256=
|
44
|
+
cloudnetpy/instruments/fd12p.py,sha256=M5yJDsPi0tHWMDUYOsoqciyKdRQ-zHEq2t19KnpZ6Io,6915
|
45
45
|
cloudnetpy/instruments/galileo.py,sha256=f_-GkRxhNaQPbI8HpOwSmoKfGqyjmD16A0ZFgwLOIig,5137
|
46
46
|
cloudnetpy/instruments/hatpro.py,sha256=TGOqwW0TfoPEYk13MFvFzwgJGzm6MVE5AsPavcIoj3I,10248
|
47
47
|
cloudnetpy/instruments/instruments.py,sha256=WZgH7HjzM9Ane1CSnYCSLidbST8hunUeSt2lPntq9As,4999
|
@@ -53,7 +53,7 @@ cloudnetpy/instruments/nc_radar.py,sha256=XFKxPLKivnhHTgjE5HFrxjWZ0oCifhDUAog051
|
|
53
53
|
cloudnetpy/instruments/pollyxt.py,sha256=IFq_RJrhgJ79OVyuo48PwYQK_zZ6VZFB_S5bEirRyzs,10566
|
54
54
|
cloudnetpy/instruments/radiometrics.py,sha256=QKfnrZlQ0sFcFjmv1ShnCMTJQv64w4akjK-JAIY4gCg,16116
|
55
55
|
cloudnetpy/instruments/rain_e_h3.py,sha256=fjv3SgeUNx9GisYqLrBnX9AjnO17VtouyoPh12VE9uo,5465
|
56
|
-
cloudnetpy/instruments/rpg.py,sha256=
|
56
|
+
cloudnetpy/instruments/rpg.py,sha256=LmvViLSevzjR2HmkP6MCHHDzafpTWoCvw7GaaEC37s0,21029
|
57
57
|
cloudnetpy/instruments/rpg_reader.py,sha256=EcsUUyKNiZ-kEEz48zibs-uXH7CMNpedqlrfWOY4Dks,11899
|
58
58
|
cloudnetpy/instruments/toa5.py,sha256=CfmmBMv5iMGaWHIGBK01Rw24cuXC1R1RMNTXkmsm340,1760
|
59
59
|
cloudnetpy/instruments/vaisala.py,sha256=tu7aljkMKep0uCWz-Sd-GuBXF_Yy421a4nHy0ffpMoc,4725
|
@@ -103,10 +103,10 @@ cloudnetpy/model_evaluation/tests/unit/test_plotting.py,sha256=5hkhtqX-JQ8-Yy6DA
|
|
103
103
|
cloudnetpy/model_evaluation/tests/unit/test_statistical_methods.py,sha256=Ra3r4V0qbqkpDuaTYvEIbaasl0nZ5gmTLR4eGC0glBQ,9724
|
104
104
|
cloudnetpy/model_evaluation/tests/unit/test_tools.py,sha256=Ia_VrLdV2NstX5gbx_3AZTOAlrgLAy_xFZ8fHYVX0xI,3817
|
105
105
|
cloudnetpy/plotting/__init__.py,sha256=lg9Smn4BI0dVBgnDLC3JVJ4GmwoSnO-qoSd4ApvwV6Y,107
|
106
|
-
cloudnetpy/plotting/plot_meta.py,sha256=
|
106
|
+
cloudnetpy/plotting/plot_meta.py,sha256=4AyKaTgp4ngxcUd3d0d7usJK-VpVrXGOCaXmpkGueNo,18455
|
107
107
|
cloudnetpy/plotting/plotting.py,sha256=v1dD6kmMJ-t88p4rqrnFzS94qUjpI_v9fCAp49fA2V0,42015
|
108
108
|
cloudnetpy/products/__init__.py,sha256=cBJdJBYltz5ZTKDqnRo-0StytAZK8gE3RYxxriFA4ak,295
|
109
|
-
cloudnetpy/products/classification.py,sha256=
|
109
|
+
cloudnetpy/products/classification.py,sha256=Vbx2A43XP8F6q82wOPA_7pyUhuoyDty199bLU_7aNd0,17382
|
110
110
|
cloudnetpy/products/der.py,sha256=UXdAxmmwChVVWSI4QSGAXphfMnbymGRTtGdKWEvh-J4,13162
|
111
111
|
cloudnetpy/products/drizzle.py,sha256=0h1N_WVjC2GgIkAN-4ydOwl7WJn3psxeqmPHfX8WHhQ,11935
|
112
112
|
cloudnetpy/products/drizzle_error.py,sha256=QN98Io9UsBoEYxKBqfwoS88OGBiK5U5RYnVQjyTWHCI,6220
|
@@ -118,10 +118,10 @@ cloudnetpy/products/lwc.py,sha256=xsNiiG6dGKIkWaFk0xWTabc1bZ4ULf6SqcqHs7itAUk,19
|
|
118
118
|
cloudnetpy/products/mie_lu_tables.nc,sha256=It4fYpqJXlqOgL8jeZ-PxGzP08PMrELIDVe55y9ob58,16637951
|
119
119
|
cloudnetpy/products/mwr_tools.py,sha256=MMWnp68U7bv157-CPB2VeTQvaR6zl7sexbBT_kJ_pn8,6734
|
120
120
|
cloudnetpy/products/product_tools.py,sha256=eyqIw_0KhlpmmYQE69RpGdRIAOW7JVPlEgkTBp2kdps,11302
|
121
|
-
cloudnetpy-1.
|
121
|
+
cloudnetpy-1.86.1.dist-info/licenses/LICENSE,sha256=wcZF72bdaoG9XugpyE95Juo7lBQOwLuTKBOhhtANZMM,1094
|
122
122
|
docs/source/conf.py,sha256=IKiFWw6xhUd8NrCg0q7l596Ck1d61XWeVjIFHVSG9Og,1490
|
123
|
-
cloudnetpy-1.
|
124
|
-
cloudnetpy-1.
|
125
|
-
cloudnetpy-1.
|
126
|
-
cloudnetpy-1.
|
127
|
-
cloudnetpy-1.
|
123
|
+
cloudnetpy-1.86.1.dist-info/METADATA,sha256=jSsYS8WOmfToTJzLKqwqAUh3PCJv6BPRv6lUAryjRtM,5836
|
124
|
+
cloudnetpy-1.86.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
125
|
+
cloudnetpy-1.86.1.dist-info/entry_points.txt,sha256=HhY7LwCFk4qFgDlXx_Fy983ZTd831WlhtdPIzV-Y3dY,51
|
126
|
+
cloudnetpy-1.86.1.dist-info/top_level.txt,sha256=ibSPWRr6ojS1i11rtBFz2_gkIe68mggj7aeswYfaOo0,16
|
127
|
+
cloudnetpy-1.86.1.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|