cloudnetpy 1.57.0__py3-none-any.whl → 1.58.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/atmos.py +1 -1
- cloudnetpy/categorize/categorize.py +26 -15
- cloudnetpy/categorize/classify.py +5 -6
- cloudnetpy/categorize/containers.py +30 -54
- cloudnetpy/categorize/disdrometer.py +53 -0
- cloudnetpy/categorize/insects.py +14 -1
- cloudnetpy/categorize/lidar.py +3 -3
- cloudnetpy/categorize/radar.py +34 -20
- cloudnetpy/cloudnetarray.py +7 -3
- cloudnetpy/constants.py +4 -0
- cloudnetpy/instruments/disdrometer/parsivel.py +42 -6
- cloudnetpy/output.py +4 -1
- cloudnetpy/plotting/plotting.py +3 -0
- cloudnetpy/products/der.py +1 -1
- cloudnetpy/products/drizzle_tools.py +6 -5
- cloudnetpy/products/product_tools.py +16 -13
- cloudnetpy/utils.py +11 -2
- cloudnetpy/version.py +2 -2
- {cloudnetpy-1.57.0.dist-info → cloudnetpy-1.58.1.dist-info}/METADATA +1 -1
- {cloudnetpy-1.57.0.dist-info → cloudnetpy-1.58.1.dist-info}/RECORD +23 -22
- {cloudnetpy-1.57.0.dist-info → cloudnetpy-1.58.1.dist-info}/LICENSE +0 -0
- {cloudnetpy-1.57.0.dist-info → cloudnetpy-1.58.1.dist-info}/WHEEL +0 -0
- {cloudnetpy-1.57.0.dist-info → cloudnetpy-1.58.1.dist-info}/top_level.txt +0 -0
cloudnetpy/categorize/atmos.py
CHANGED
@@ -219,7 +219,7 @@ class LiquidAttenuation(Attenuation):
|
|
219
219
|
def _find_pixels_hard_to_correct(self) -> np.ndarray:
|
220
220
|
melting_layer = utils.isbit(self.classification.category_bits, 3)
|
221
221
|
hard_to_correct = np.cumsum(melting_layer, axis=1) >= 1
|
222
|
-
hard_to_correct[self.classification.is_rain, :] = True
|
222
|
+
hard_to_correct[self.classification.is_rain == 1, :] = True
|
223
223
|
attenuated = self._find_attenuated_part_of_atmosphere()
|
224
224
|
hard_to_correct[attenuated & self.atten.mask] = True
|
225
225
|
return hard_to_correct
|
@@ -1,10 +1,12 @@
|
|
1
1
|
"""Module that generates Cloudnet categorize file."""
|
2
2
|
from cloudnetpy import output, utils
|
3
3
|
from cloudnetpy.categorize import atmos, classify
|
4
|
+
from cloudnetpy.categorize.disdrometer import Disdrometer
|
4
5
|
from cloudnetpy.categorize.lidar import Lidar
|
5
6
|
from cloudnetpy.categorize.model import Model
|
6
7
|
from cloudnetpy.categorize.mwr import Mwr
|
7
8
|
from cloudnetpy.categorize.radar import Radar
|
9
|
+
from cloudnetpy.datasource import DataSource
|
8
10
|
from cloudnetpy.exceptions import ValidTimeStampError
|
9
11
|
from cloudnetpy.metadata import MetaData
|
10
12
|
|
@@ -59,6 +61,8 @@ def generate_categorize(
|
|
59
61
|
def _interpolate_to_cloudnet_grid() -> list:
|
60
62
|
wl_band = utils.get_wl_band(data["radar"].radar_frequency)
|
61
63
|
data["mwr"].rebin_to_grid(time)
|
64
|
+
if is_disdrometer:
|
65
|
+
data["disdrometer"].interpolate_to_grid(time)
|
62
66
|
data["model"].interpolate_to_common_height(wl_band)
|
63
67
|
model_gap_ind = data["model"].interpolate_to_grid(time, height)
|
64
68
|
radar_gap_ind = data["radar"].rebin_to_grid(time)
|
@@ -69,8 +73,10 @@ def generate_categorize(
|
|
69
73
|
def _screen_bad_time_indices(valid_indices: list) -> None:
|
70
74
|
n_time_full = len(time)
|
71
75
|
data["radar"].time = time[valid_indices]
|
72
|
-
for
|
73
|
-
|
76
|
+
for data_key, obj in data.items():
|
77
|
+
if obj is None or data_key == "lv0_files":
|
78
|
+
continue
|
79
|
+
for key, item in obj.data.items():
|
74
80
|
if utils.isscalar(item.data):
|
75
81
|
continue
|
76
82
|
array = item[:]
|
@@ -81,26 +87,31 @@ def generate_categorize(
|
|
81
87
|
array = array[valid_indices, :]
|
82
88
|
else:
|
83
89
|
continue
|
84
|
-
|
90
|
+
obj.data[key].data = array
|
85
91
|
for key, item in data["model"].data_dense.items():
|
86
92
|
data["model"].data_dense[key] = item[valid_indices, :]
|
87
93
|
|
88
94
|
def _prepare_output() -> dict:
|
89
95
|
data["radar"].add_meta()
|
90
96
|
data["model"].screen_sparse_fields()
|
91
|
-
|
97
|
+
if is_disdrometer:
|
98
|
+
data["radar"].data.pop("rainfall_rate", None)
|
99
|
+
data["disdrometer"].data.pop("n_particles", None)
|
100
|
+
for key in ("category_bits", "insect_prob"):
|
92
101
|
data["radar"].append_data(getattr(classification, key), key)
|
93
102
|
if classification.liquid_prob is not None:
|
94
103
|
data["radar"].append_data(classification.liquid_prob, "liquid_prob")
|
95
104
|
for key in ("radar_liquid_atten", "radar_gas_atten"):
|
96
105
|
data["radar"].append_data(attenuations[key], key)
|
97
106
|
data["radar"].append_data(quality["quality_bits"], "quality_bits")
|
107
|
+
data["radar"].append_data(classification.is_rain, "rain_detected")
|
98
108
|
return {
|
99
109
|
**data["radar"].data,
|
100
110
|
**data["lidar"].data,
|
101
111
|
**data["model"].data,
|
102
112
|
**data["model"].data_sparse,
|
103
113
|
**data["mwr"].data,
|
114
|
+
**(data["disdrometer"].data if is_disdrometer else {}),
|
104
115
|
}
|
105
116
|
|
106
117
|
def _define_dense_grid() -> tuple:
|
@@ -108,19 +119,20 @@ def generate_categorize(
|
|
108
119
|
|
109
120
|
def _close_all() -> None:
|
110
121
|
for obj in data.values():
|
111
|
-
if isinstance(obj,
|
122
|
+
if isinstance(obj, DataSource):
|
112
123
|
obj.close()
|
113
124
|
|
114
125
|
try:
|
126
|
+
is_disdrometer = "disdrometer" in input_files
|
115
127
|
data = {
|
116
128
|
"radar": Radar(input_files["radar"]),
|
117
129
|
"lidar": Lidar(input_files["lidar"]),
|
118
130
|
"mwr": Mwr(input_files["mwr"]),
|
119
131
|
"lv0_files": input_files.get("lv0_files", None),
|
132
|
+
"disdrometer": Disdrometer(input_files["disdrometer"])
|
133
|
+
if is_disdrometer
|
134
|
+
else None,
|
120
135
|
}
|
121
|
-
if data["radar"].altitude is None:
|
122
|
-
msg = "Radar altitude not defined"
|
123
|
-
raise RuntimeError(msg)
|
124
136
|
data["model"] = Model(input_files["model"], data["radar"].altitude)
|
125
137
|
time, height = _define_dense_grid()
|
126
138
|
valid_ind = _interpolate_to_cloudnet_grid()
|
@@ -141,7 +153,7 @@ def generate_categorize(
|
|
141
153
|
classification = classify.classify_measurements(data)
|
142
154
|
attenuations = atmos.get_attenuations(data, classification)
|
143
155
|
data["radar"].correct_atten(attenuations)
|
144
|
-
data["radar"].calc_errors(attenuations, classification)
|
156
|
+
data["radar"].calc_errors(attenuations, classification.is_clutter)
|
145
157
|
quality = classify.fetch_quality(data, classification, attenuations)
|
146
158
|
cloudnet_arrays = _prepare_output()
|
147
159
|
date = data["radar"].get_date()
|
@@ -382,12 +394,6 @@ CATEGORIZE_ATTRIBUTES = {
|
|
382
394
|
definition=DEFINITIONS["quality_bits"],
|
383
395
|
units="1",
|
384
396
|
),
|
385
|
-
"rainfall_rate": MetaData(
|
386
|
-
long_name="Rainfall rate",
|
387
|
-
standard_name="rainfall_rate",
|
388
|
-
units="m s-1",
|
389
|
-
comment="Fill values denote rain with undefined intensity.",
|
390
|
-
),
|
391
397
|
"radar_liquid_atten": MetaData(
|
392
398
|
long_name="Two-way radar attenuation due to liquid water",
|
393
399
|
units="dB",
|
@@ -409,4 +415,9 @@ CATEGORIZE_ATTRIBUTES = {
|
|
409
415
|
units="1",
|
410
416
|
comment=COMMENTS["liquid_prob"],
|
411
417
|
),
|
418
|
+
"rain_detected": MetaData(
|
419
|
+
long_name="Rain detected",
|
420
|
+
units="1",
|
421
|
+
comment="1 = rain detected, 0 = no rain detected",
|
422
|
+
),
|
412
423
|
}
|
@@ -66,12 +66,11 @@ def classify_measurements(data: dict) -> ClassificationResult:
|
|
66
66
|
bits[4] = _find_aerosols(obs, bits[1], bits[0])
|
67
67
|
bits[4][filtered_ice] = False
|
68
68
|
return ClassificationResult(
|
69
|
-
_bits_to_integer(bits),
|
70
|
-
obs.is_rain,
|
71
|
-
obs.is_clutter,
|
72
|
-
|
73
|
-
|
74
|
-
liquid_prob,
|
69
|
+
category_bits=_bits_to_integer(bits),
|
70
|
+
is_rain=obs.is_rain,
|
71
|
+
is_clutter=obs.is_clutter,
|
72
|
+
insect_prob=insect_prob,
|
73
|
+
liquid_prob=liquid_prob,
|
75
74
|
)
|
76
75
|
|
77
76
|
|
@@ -1,11 +1,10 @@
|
|
1
|
-
import logging
|
2
1
|
from dataclasses import dataclass
|
3
2
|
|
4
3
|
import numpy as np
|
5
|
-
import skimage
|
6
4
|
from numpy import ma
|
7
5
|
|
8
6
|
from cloudnetpy import utils
|
7
|
+
from cloudnetpy.constants import MM_H_TO_M_S
|
9
8
|
|
10
9
|
|
11
10
|
@dataclass
|
@@ -15,7 +14,6 @@ class ClassificationResult:
|
|
15
14
|
category_bits: np.ndarray
|
16
15
|
is_rain: np.ndarray
|
17
16
|
is_clutter: np.ndarray
|
18
|
-
rainfall_rate: np.ndarray
|
19
17
|
insect_prob: np.ndarray
|
20
18
|
liquid_prob: np.ndarray | None
|
21
19
|
|
@@ -42,12 +40,12 @@ class ClassData:
|
|
42
40
|
radar_type (str): Radar identifier.
|
43
41
|
is_rain (ndarray): 2D boolean array denoting rain.
|
44
42
|
is_clutter (ndarray): 2D boolean array denoting clutter.
|
45
|
-
rainfall_rate: 1D rain rate.
|
46
43
|
altitude: site altitude.
|
47
44
|
|
48
45
|
"""
|
49
46
|
|
50
47
|
def __init__(self, data: dict):
|
48
|
+
self.data = data
|
51
49
|
self.z = data["radar"].data["Z"][:]
|
52
50
|
self.v = data["radar"].data["v"][:]
|
53
51
|
self.v_sigma = data["radar"].data["v_sigma"][:]
|
@@ -61,61 +59,39 @@ class ClassData:
|
|
61
59
|
self.model_type = data["model"].source_type
|
62
60
|
self.beta = data["lidar"].data["beta"][:]
|
63
61
|
self.lwp = data["mwr"].data["lwp"][:]
|
64
|
-
self.is_rain =
|
65
|
-
self.rainfall_rate = _find_rainfall_rate(self.is_rain, data["radar"])
|
62
|
+
self.is_rain = self._find_profiles_with_rain()
|
66
63
|
self.is_clutter = _find_clutter(self.v, self.is_rain)
|
67
64
|
self.altitude = data["radar"].altitude
|
68
65
|
self.lv0_files = data["lv0_files"]
|
69
66
|
self.date = data["radar"].get_date()
|
70
67
|
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
connectivity=1,
|
99
|
-
) # Filter hot pixels
|
100
|
-
n_profiles = len(time)
|
101
|
-
n_steps = utils.n_elements(time, time_buffer, "time")
|
102
|
-
for ind in np.where(is_rain)[0]:
|
103
|
-
ind1 = max(0, ind - n_steps)
|
104
|
-
ind2 = min(ind + n_steps, n_profiles)
|
105
|
-
is_rain[ind1 : ind2 + 1] = True
|
106
|
-
return is_rain
|
107
|
-
|
108
|
-
|
109
|
-
def _find_rainfall_rate(is_rain: np.ndarray, radar) -> np.ndarray:
|
110
|
-
rainfall_rate = ma.zeros(len(is_rain))
|
111
|
-
rainfall_rate[is_rain] = ma.masked
|
112
|
-
if "rainfall_rate" in radar.data:
|
113
|
-
radar_rainfall_rate = radar.data["rainfall_rate"].data
|
114
|
-
ind = np.where(~radar_rainfall_rate.mask)
|
115
|
-
rainfall_rate[ind] = radar_rainfall_rate[ind]
|
116
|
-
else:
|
117
|
-
logging.info("No measured rain rate available")
|
118
|
-
return rainfall_rate
|
68
|
+
def _find_profiles_with_rain(self) -> np.ndarray:
|
69
|
+
is_rain = self._find_rain_from_radar_echo()
|
70
|
+
rain_from_disdrometer = self._find_rain_from_disdrometer()
|
71
|
+
ind = ~rain_from_disdrometer.mask
|
72
|
+
is_rain[ind] = rain_from_disdrometer[ind]
|
73
|
+
return is_rain
|
74
|
+
|
75
|
+
def _find_rain_from_radar_echo(self) -> np.ndarray:
|
76
|
+
gate_number = 3
|
77
|
+
threshold = 0
|
78
|
+
z = self.z[:, gate_number]
|
79
|
+
return np.where((~ma.getmaskarray(z)) & (z > threshold), 1, 0)
|
80
|
+
|
81
|
+
def _find_rain_from_disdrometer(self) -> ma.MaskedArray:
|
82
|
+
threshold_mm_h = 0.25 # Standard threshold for drizzle -> rain
|
83
|
+
threshold_particles = 10 # This is arbitrary and should be better tested
|
84
|
+
threshold_rate = threshold_mm_h * MM_H_TO_M_S
|
85
|
+
try:
|
86
|
+
rainfall_rate = self.data["disdrometer"].data["rainfall_rate"].data
|
87
|
+
n_particles = self.data["disdrometer"].data["n_particles"].data
|
88
|
+
is_rain = ma.array(
|
89
|
+
(rainfall_rate > threshold_rate) & (n_particles > threshold_particles),
|
90
|
+
dtype=int,
|
91
|
+
)
|
92
|
+
except (AttributeError, KeyError):
|
93
|
+
is_rain = ma.masked_all(self.time.shape, dtype=int)
|
94
|
+
return is_rain
|
119
95
|
|
120
96
|
|
121
97
|
def _find_clutter(
|
@@ -0,0 +1,53 @@
|
|
1
|
+
"""Mwr module, containing the :class:`Mwr` class."""
|
2
|
+
import logging
|
3
|
+
|
4
|
+
import numpy as np
|
5
|
+
from numpy import ma
|
6
|
+
from scipy.interpolate import interp1d
|
7
|
+
|
8
|
+
from cloudnetpy.categorize.lidar import get_gap_ind
|
9
|
+
from cloudnetpy.datasource import DataSource
|
10
|
+
|
11
|
+
|
12
|
+
class Disdrometer(DataSource):
|
13
|
+
"""Disdrometer class, child of DataSource.
|
14
|
+
|
15
|
+
Args:
|
16
|
+
----
|
17
|
+
full_path: Cloudnet Level 1b disdrometer file.
|
18
|
+
|
19
|
+
"""
|
20
|
+
|
21
|
+
def __init__(self, full_path: str):
|
22
|
+
super().__init__(full_path)
|
23
|
+
self._init_rainfall_rate()
|
24
|
+
|
25
|
+
def interpolate_to_grid(self, time_grid: np.ndarray) -> None:
|
26
|
+
for key, array in self.data.items():
|
27
|
+
self.data[key].data = self._interpolate(array.data, time_grid)
|
28
|
+
|
29
|
+
def _init_rainfall_rate(self) -> None:
|
30
|
+
keys = ("rainfall_rate", "n_particles")
|
31
|
+
for key in keys:
|
32
|
+
self.append_data(self.dataset.variables[key][:], key)
|
33
|
+
|
34
|
+
def _interpolate(self, y: ma.MaskedArray, x_new: np.ndarray) -> np.ndarray:
|
35
|
+
if ma.getmask(y) is ma.nomask:
|
36
|
+
non_masked_indices = np.arange(len(y))
|
37
|
+
elif y.mask.all():
|
38
|
+
return ma.masked_all(x_new.shape)
|
39
|
+
else:
|
40
|
+
non_masked_indices = np.where(~y.mask)[0]
|
41
|
+
non_masked_values = y[non_masked_indices]
|
42
|
+
non_masked_time = self.time[non_masked_indices]
|
43
|
+
fun = interp1d(non_masked_time, non_masked_values, fill_value="extrapolate")
|
44
|
+
interpolated_array = ma.array(fun(x_new))
|
45
|
+
max_time = 1 / 60 # min -> fraction hour
|
46
|
+
mask_ind = get_gap_ind(non_masked_time, x_new, max_time)
|
47
|
+
|
48
|
+
if len(mask_ind) > 0:
|
49
|
+
msg = f"Unable to interpolate disdrometer for {len(mask_ind)} time steps"
|
50
|
+
logging.warning(msg)
|
51
|
+
interpolated_array[mask_ind] = ma.masked
|
52
|
+
|
53
|
+
return interpolated_array
|
cloudnetpy/categorize/insects.py
CHANGED
@@ -147,7 +147,8 @@ def _screen_insects(
|
|
147
147
|
prob[(above_liquid == 1) & (insect_prob_no_ldr > 0)] = 0
|
148
148
|
|
149
149
|
def _screen_rainy_profiles() -> None:
|
150
|
-
|
150
|
+
rain_smoothed = _smooth_rain(obs.time, obs.is_rain)
|
151
|
+
prob[rain_smoothed == 1, :] = 0
|
151
152
|
|
152
153
|
prob = np.copy(insect_prob)
|
153
154
|
_screen_liquid_layers()
|
@@ -155,3 +156,15 @@ def _screen_insects(
|
|
155
156
|
_screen_above_liquid()
|
156
157
|
_screen_rainy_profiles()
|
157
158
|
return prob
|
159
|
+
|
160
|
+
|
161
|
+
def _smooth_rain(time: np.ndarray, is_rain: np.ndarray) -> np.ndarray:
|
162
|
+
is_rain_smoothed = np.copy(is_rain)
|
163
|
+
time_buffer = 5 # minutes
|
164
|
+
n_profiles = len(is_rain)
|
165
|
+
n_steps = utils.n_elements(time, time_buffer, "time")
|
166
|
+
for rain_idx in np.where(is_rain)[0]:
|
167
|
+
idx_start = max(0, rain_idx - n_steps)
|
168
|
+
idx_end = min(rain_idx + n_steps, n_profiles)
|
169
|
+
is_rain_smoothed[idx_start : idx_end + 1] = True
|
170
|
+
return is_rain_smoothed
|
cloudnetpy/categorize/lidar.py
CHANGED
@@ -44,8 +44,8 @@ class Lidar(DataSource):
|
|
44
44
|
height_new,
|
45
45
|
)
|
46
46
|
# Mask data points that are too far from the original grid
|
47
|
-
time_gap_ind =
|
48
|
-
height_gap_ind =
|
47
|
+
time_gap_ind = get_gap_ind(self.time[indices], time_new, max_time)
|
48
|
+
height_gap_ind = get_gap_ind(self.height, height_new, max_height)
|
49
49
|
self._mask_profiles(beta_interp, time_gap_ind, "time")
|
50
50
|
self._mask_profiles(beta_interp, height_gap_ind, "height")
|
51
51
|
self.data["beta"].data = beta_interp
|
@@ -69,7 +69,7 @@ class Lidar(DataSource):
|
|
69
69
|
self.append_data(3.0, "beta_bias")
|
70
70
|
|
71
71
|
|
72
|
-
def
|
72
|
+
def get_gap_ind(grid: np.ndarray, new_grid: np.ndarray, threshold: float) -> list[int]:
|
73
73
|
return [
|
74
74
|
ind
|
75
75
|
for ind, value in enumerate(new_grid)
|
cloudnetpy/categorize/radar.py
CHANGED
@@ -7,8 +7,7 @@ from numpy import ma
|
|
7
7
|
from scipy import constants
|
8
8
|
|
9
9
|
from cloudnetpy import utils
|
10
|
-
from cloudnetpy.
|
11
|
-
from cloudnetpy.constants import SEC_IN_HOUR
|
10
|
+
from cloudnetpy.constants import GHZ_TO_HZ, SEC_IN_HOUR, SPEED_OF_LIGHT
|
12
11
|
from cloudnetpy.datasource import DataSource
|
13
12
|
|
14
13
|
|
@@ -69,8 +68,10 @@ class Radar(DataSource):
|
|
69
68
|
)
|
70
69
|
case "v_sigma":
|
71
70
|
array.calc_linear_std(self.time, time_new)
|
72
|
-
case "width"
|
71
|
+
case "width":
|
73
72
|
array.rebin_data(self.time, time_new)
|
73
|
+
case "rainfall_rate":
|
74
|
+
array.rebin_data(self.time, time_new, mask_zeros=False)
|
74
75
|
case _:
|
75
76
|
continue
|
76
77
|
return bad_time_indices
|
@@ -214,7 +215,7 @@ class Radar(DataSource):
|
|
214
215
|
def calc_errors(
|
215
216
|
self,
|
216
217
|
attenuations: dict,
|
217
|
-
|
218
|
+
is_clutter: np.ndarray,
|
218
219
|
) -> None:
|
219
220
|
"""Calculates uncertainties of radar echo.
|
220
221
|
|
@@ -223,7 +224,7 @@ class Radar(DataSource):
|
|
223
224
|
|
224
225
|
Args:
|
225
226
|
attenuations: 2-D attenuations due to atmospheric gases.
|
226
|
-
|
227
|
+
is_clutter: 2-D boolean array denoting pixels contaminated by clutter.
|
227
228
|
|
228
229
|
References:
|
229
230
|
The method is based on Hogan R. and O'Connor E., 2004,
|
@@ -235,17 +236,24 @@ class Radar(DataSource):
|
|
235
236
|
"""Returns sensitivity of radar as function of altitude."""
|
236
237
|
mean_gas_atten = ma.mean(attenuations["radar_gas_atten"], axis=0)
|
237
238
|
z_sensitivity = z_power_min + log_range + mean_gas_atten
|
238
|
-
zc = ma.median(ma.array(z, mask=~
|
239
|
+
zc = ma.median(ma.array(z, mask=~is_clutter), axis=0)
|
239
240
|
valid_values = np.logical_not(zc.mask)
|
240
241
|
z_sensitivity[valid_values] = zc[valid_values]
|
241
242
|
return z_sensitivity
|
242
243
|
|
243
244
|
def _calc_error() -> np.ndarray | float:
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
245
|
+
"""Returns error of radar as function of altitude.
|
246
|
+
|
247
|
+
References:
|
248
|
+
Hogan, R. J., 1998: Dual-wavelength radar studies of clouds.
|
249
|
+
PhD Thesis, University of Reading, UK.
|
250
|
+
|
251
|
+
"""
|
252
|
+
noise_threshold = 3
|
253
|
+
n_pulses = _number_of_independent_pulses()
|
254
|
+
ln_to_log10 = 10 / np.log(10)
|
255
|
+
z_precision = (ln_to_log10 / np.sqrt(n_pulses)) * (
|
256
|
+
1 + (utils.db2lin(z_power_min - z_power) / noise_threshold)
|
249
257
|
)
|
250
258
|
gas_error = attenuations["radar_gas_atten"] * 0.1
|
251
259
|
liq_error = attenuations["liquid_atten_err"].filled(0)
|
@@ -254,16 +262,22 @@ class Radar(DataSource):
|
|
254
262
|
return z_error
|
255
263
|
|
256
264
|
def _number_of_independent_pulses() -> float:
|
265
|
+
"""Returns number of independent pulses.
|
266
|
+
|
267
|
+
References:
|
268
|
+
Atlas, D., 1964: Advances in radar meteorology.
|
269
|
+
Advances in Geophys., 10, 318-478.
|
270
|
+
|
271
|
+
"""
|
272
|
+
if "width" not in self.data:
|
273
|
+
default_width = 0.3
|
274
|
+
width = np.zeros_like(self.data["Z"][:])
|
275
|
+
width[~width.mask] = default_width
|
276
|
+
else:
|
277
|
+
width = self.data["width"][:]
|
257
278
|
dwell_time = utils.mdiff(self.time) * SEC_IN_HOUR
|
258
|
-
|
259
|
-
|
260
|
-
* self.radar_frequency
|
261
|
-
* 1e9
|
262
|
-
* 4
|
263
|
-
* np.sqrt(math.pi)
|
264
|
-
* self.data["width"][:]
|
265
|
-
/ 3e8
|
266
|
-
)
|
279
|
+
wl = SPEED_OF_LIGHT / (self.radar_frequency * GHZ_TO_HZ)
|
280
|
+
return 4 * np.sqrt(math.pi) * dwell_time * width / wl
|
267
281
|
|
268
282
|
def _calc_z_power_min() -> float:
|
269
283
|
if ma.all(z_power.mask):
|
cloudnetpy/cloudnetarray.py
CHANGED
@@ -54,7 +54,9 @@ class CloudnetArray:
|
|
54
54
|
"""Masks data from given indices."""
|
55
55
|
self.data[ind] = ma.masked
|
56
56
|
|
57
|
-
def rebin_data(
|
57
|
+
def rebin_data(
|
58
|
+
self, time: np.ndarray, time_new: np.ndarray, *, mask_zeros: bool = True
|
59
|
+
) -> list:
|
58
60
|
"""Rebins `data` in time.
|
59
61
|
|
60
62
|
Args:
|
@@ -66,12 +68,14 @@ class CloudnetArray:
|
|
66
68
|
|
67
69
|
"""
|
68
70
|
if self.data.ndim == 1:
|
69
|
-
self.data = utils.rebin_1d(time, self.data, time_new)
|
71
|
+
self.data = utils.rebin_1d(time, self.data, time_new, mask_zeros=mask_zeros)
|
70
72
|
bad_indices = list(np.where(self.data == ma.masked)[0])
|
71
73
|
else:
|
72
74
|
if not isinstance(self.data, ma.MaskedArray):
|
73
75
|
self.data = ma.masked_array(self.data)
|
74
|
-
self.data, bad_indices = utils.rebin_2d(
|
76
|
+
self.data, bad_indices = utils.rebin_2d(
|
77
|
+
time, self.data, time_new, mask_zeros=mask_zeros
|
78
|
+
)
|
75
79
|
return bad_indices
|
76
80
|
|
77
81
|
def fetch_attributes(self) -> list:
|
cloudnetpy/constants.py
CHANGED
@@ -20,8 +20,12 @@ RS: Final = 287.058
|
|
20
20
|
RHO_ICE: Final = 917
|
21
21
|
|
22
22
|
# other
|
23
|
+
SPEED_OF_LIGHT: Final = 3.0e8
|
23
24
|
SEC_IN_MINUTE: Final = 60
|
24
25
|
SEC_IN_HOUR: Final = 3600
|
25
26
|
SEC_IN_DAY: Final = 86400
|
26
27
|
MM_TO_M: Final = 1e-3
|
27
28
|
G_TO_KG: Final = 1e-3
|
29
|
+
M_S_TO_MM_H: Final = SEC_IN_HOUR / MM_TO_M
|
30
|
+
MM_H_TO_M_S: Final = 1 / M_S_TO_MM_H
|
31
|
+
GHZ_TO_HZ: Final = 1e9
|
@@ -372,7 +372,10 @@ def _parse_spectrum(tokens: Iterator[str]) -> np.ndarray:
|
|
372
372
|
return np.array(values, dtype="i2").reshape((32, 32))
|
373
373
|
|
374
374
|
|
375
|
-
|
375
|
+
ParserType = Callable[[Iterator[str]], Any]
|
376
|
+
|
377
|
+
|
378
|
+
PARSERS: dict[str, ParserType] = {
|
376
379
|
"I_heating": _parse_float,
|
377
380
|
"T_sensor": _parse_int,
|
378
381
|
"_T_pcb": _parse_int,
|
@@ -401,6 +404,16 @@ PARSERS: dict[str, Callable[[Iterator[str]], Any]] = {
|
|
401
404
|
"visibility": _parse_int,
|
402
405
|
}
|
403
406
|
|
407
|
+
EMPTY_VALUES: dict[ParserType, Any] = {
|
408
|
+
_parse_int: 0,
|
409
|
+
_parse_float: 0.0,
|
410
|
+
_parse_date: datetime.date(2000, 1, 1),
|
411
|
+
_parse_time: datetime.time(12, 0, 0),
|
412
|
+
_parse_datetime: datetime.datetime(2000, 1, 1),
|
413
|
+
_parse_vector: np.zeros(32, dtype=float),
|
414
|
+
_parse_spectrum: np.zeros((32, 32), dtype="i2"),
|
415
|
+
}
|
416
|
+
|
404
417
|
|
405
418
|
def _parse_headers(line: str) -> list[str]:
|
406
419
|
return [CSV_HEADERS[header.strip()] for header in line.split(";")]
|
@@ -508,7 +521,7 @@ def _read_toa5(filename: str | PathLike) -> dict[str, list]:
|
|
508
521
|
return data
|
509
522
|
|
510
523
|
|
511
|
-
def _read_typ_op4a(lines: list[str]) -> dict[str,
|
524
|
+
def _read_typ_op4a(lines: list[str]) -> dict[str, Any]:
|
512
525
|
"""Read output of "CS/PA" command. The output starts with line "TYP OP4A"
|
513
526
|
followed by one line per measured variable in format: <number>:<value>.
|
514
527
|
Output ends with characters: <ETX><CR><LF><NUL>. Lines are separated by
|
@@ -527,7 +540,7 @@ def _read_typ_op4a(lines: list[str]) -> dict[str, list]:
|
|
527
540
|
continue
|
528
541
|
parser = PARSERS.get(varname, next)
|
529
542
|
tokens = value.split(";")
|
530
|
-
data[varname] =
|
543
|
+
data[varname] = parser(iter(tokens))
|
531
544
|
return data
|
532
545
|
|
533
546
|
|
@@ -538,15 +551,26 @@ def _read_fmi(content: str):
|
|
538
551
|
- output of "CS/PA" command without non-printable characters at the end
|
539
552
|
- "]\n"
|
540
553
|
"""
|
541
|
-
output: dict[str,
|
554
|
+
output: dict[str, list] = {"_datetime": []}
|
542
555
|
for m in re.finditer(
|
543
556
|
r"\[(?P<year>\d+)-(?P<month>\d+)-(?P<day>\d+) "
|
544
557
|
r"(?P<hour>\d+):(?P<minute>\d+):(?P<second>\d+)"
|
545
558
|
r"(?P<output>[^\]]*)\]",
|
546
559
|
content,
|
547
560
|
):
|
548
|
-
|
561
|
+
try:
|
562
|
+
record = _read_typ_op4a(m["output"].splitlines())
|
563
|
+
except ValueError:
|
564
|
+
continue
|
565
|
+
|
566
|
+
for key, value in record.items():
|
567
|
+
if key not in output:
|
568
|
+
output[key] = [None] * len(output["_datetime"])
|
549
569
|
output[key].append(value)
|
570
|
+
for key in output:
|
571
|
+
if key not in record and key != "_datetime":
|
572
|
+
output[key].append(None)
|
573
|
+
|
550
574
|
output["_datetime"].append(
|
551
575
|
datetime.datetime(
|
552
576
|
int(m["year"]),
|
@@ -577,6 +601,7 @@ def _read_parsivel(
|
|
577
601
|
data = _read_toa5(filename)
|
578
602
|
elif "TYP OP4A" in lines[0]:
|
579
603
|
data = _read_typ_op4a(lines)
|
604
|
+
data = {key: [value] for key, value in data.items()}
|
580
605
|
elif "Date" in lines[0]:
|
581
606
|
headers = _parse_headers(lines[0])
|
582
607
|
data = _read_rows(headers, lines[1:])
|
@@ -597,6 +622,17 @@ def _read_parsivel(
|
|
597
622
|
combined_data[key].extend(values)
|
598
623
|
if timestamps is not None:
|
599
624
|
combined_data["_datetime"] = list(timestamps)
|
600
|
-
result = {
|
625
|
+
result = {}
|
626
|
+
for key, value in combined_data.items():
|
627
|
+
array = np.array(
|
628
|
+
[
|
629
|
+
x
|
630
|
+
if x is not None
|
631
|
+
else (EMPTY_VALUES[PARSERS[key]] if key in PARSERS else "")
|
632
|
+
for x in value
|
633
|
+
]
|
634
|
+
)
|
635
|
+
mask = [np.full(array.shape[1:], x is None) for x in value]
|
636
|
+
result[key] = ma.array(array, mask=mask)
|
601
637
|
result["time"] = result["_datetime"].astype("datetime64[s]")
|
602
638
|
return result
|
cloudnetpy/output.py
CHANGED
@@ -317,7 +317,7 @@ def add_time_attribute(
|
|
317
317
|
|
318
318
|
|
319
319
|
def add_source_attribute(attributes: dict, data: dict) -> dict:
|
320
|
-
"""Adds source attribute."""
|
320
|
+
"""Adds source attribute to variables."""
|
321
321
|
variables = {
|
322
322
|
"radar": (
|
323
323
|
"v",
|
@@ -334,8 +334,11 @@ def add_source_attribute(attributes: dict, data: dict) -> dict:
|
|
334
334
|
"lidar": ("beta", "lidar_wavelength"),
|
335
335
|
"mwr": ("lwp",),
|
336
336
|
"model": ("uwind", "vwind", "Tw", "q", "pressure", "temperature"),
|
337
|
+
"disdrometer": ("rainfall_rate",),
|
337
338
|
}
|
338
339
|
for instrument, keys in variables.items():
|
340
|
+
if data[instrument] is None:
|
341
|
+
continue
|
339
342
|
source = data[instrument].dataset.source
|
340
343
|
for key in keys:
|
341
344
|
if key in attributes:
|
cloudnetpy/plotting/plotting.py
CHANGED
@@ -514,6 +514,9 @@ class Plot2D(Plot):
|
|
514
514
|
if figure_data.height is None:
|
515
515
|
msg = "No height information in the file."
|
516
516
|
raise ValueError(msg)
|
517
|
+
if self._data.ndim < 2:
|
518
|
+
msg = "Data has to be 2D."
|
519
|
+
raise PlottingError(msg)
|
517
520
|
alt = figure_data.height
|
518
521
|
if figure_data.options.max_y is None:
|
519
522
|
return alt
|
cloudnetpy/products/der.py
CHANGED
@@ -214,7 +214,7 @@ class DerSource(DataSource):
|
|
214
214
|
is_retrieved = ~self.data["der"][:].mask
|
215
215
|
is_mixed = droplet_classification.is_mixed
|
216
216
|
is_ice = droplet_classification.is_ice
|
217
|
-
is_rain = np.tile(self.is_rain, (is_retrieved.shape[1], 1)).T
|
217
|
+
is_rain = np.tile(self.is_rain == 1, (is_retrieved.shape[1], 1)).T
|
218
218
|
|
219
219
|
retrieval_status = np.zeros(is_retrieved.shape, dtype=int)
|
220
220
|
retrieval_status[is_ice] = 4
|
@@ -4,6 +4,7 @@ from bisect import bisect_left
|
|
4
4
|
|
5
5
|
import netCDF4
|
6
6
|
import numpy as np
|
7
|
+
from numpy import ma
|
7
8
|
from scipy.special import gamma
|
8
9
|
|
9
10
|
from cloudnetpy import utils
|
@@ -101,7 +102,7 @@ class DrizzleClassification(ProductClassification):
|
|
101
102
|
|
102
103
|
@staticmethod
|
103
104
|
def _find_v_sigma(cat_file: str) -> np.ndarray:
|
104
|
-
v_sigma = product_tools.
|
105
|
+
v_sigma = product_tools.read_nc_field(cat_file, "v_sigma")
|
105
106
|
return np.isfinite(v_sigma)
|
106
107
|
|
107
108
|
def _find_warm_liquid(self) -> np.ndarray:
|
@@ -160,11 +161,11 @@ class SpectralWidth:
|
|
160
161
|
self.width_ht = self._calculate_spectral_width()
|
161
162
|
|
162
163
|
def _calculate_spectral_width(self) -> np.ndarray:
|
163
|
-
v_sigma = product_tools.
|
164
|
+
v_sigma = product_tools.read_nc_field(self.cat_file, "v_sigma")
|
164
165
|
try:
|
165
|
-
width = product_tools.
|
166
|
+
width = product_tools.read_nc_field(self.cat_file, "width")
|
166
167
|
except KeyError:
|
167
|
-
width = [0]
|
168
|
+
width = ma.array([0])
|
168
169
|
logging.warning("No spectral width, assuming width = %s", width[0])
|
169
170
|
sigma_factor = self._calc_v_sigma_factor()
|
170
171
|
return width - sigma_factor * v_sigma
|
@@ -178,7 +179,7 @@ class SpectralWidth:
|
|
178
179
|
|
179
180
|
def _calc_beam_divergence(self) -> np.ndarray:
|
180
181
|
beam_width = 0.5
|
181
|
-
height = product_tools.
|
182
|
+
height = product_tools.read_nc_field(self.cat_file, "height")
|
182
183
|
return height * np.deg2rad(beam_width)
|
183
184
|
|
184
185
|
def _calc_horizontal_wind(self) -> np.ndarray:
|
@@ -232,32 +232,35 @@ class IceSource(DataSource):
|
|
232
232
|
|
233
233
|
def get_is_rain(filename: str) -> np.ndarray:
|
234
234
|
try:
|
235
|
-
|
235
|
+
is_rain = read_nc_field(filename, "rain_detected")
|
236
236
|
except KeyError:
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
237
|
+
try:
|
238
|
+
rainfall_rate = read_nc_field(filename, "rainfall_rate")
|
239
|
+
except KeyError:
|
240
|
+
rainfall_rate = read_nc_field(filename, "rain_rate")
|
241
|
+
is_rain = rainfall_rate != 0
|
242
|
+
is_rain[is_rain.mask] = True
|
242
243
|
return np.array(is_rain)
|
243
244
|
|
244
245
|
|
245
|
-
def
|
246
|
+
def read_nc_field(nc_file: str, name: str) -> ma.MaskedArray:
|
247
|
+
with netCDF4.Dataset(nc_file) as nc:
|
248
|
+
return nc.variables[name][:]
|
249
|
+
|
250
|
+
|
251
|
+
def read_nc_fields(nc_file: str, names: list[str]) -> list[ma.MaskedArray]:
|
246
252
|
"""Reads selected variables from a netCDF file.
|
247
253
|
|
248
254
|
Args:
|
249
255
|
nc_file: netCDF file name.
|
250
|
-
names: Variables to be read, e.g.
|
256
|
+
names: Variables to be read, e.g. ['ldr', 'lwp'].
|
251
257
|
|
252
258
|
Returns:
|
253
|
-
|
254
|
-
List of arrays otherwise.
|
259
|
+
List of numpy arrays.
|
255
260
|
|
256
261
|
"""
|
257
|
-
names = [names] if isinstance(names, str) else names
|
258
262
|
with netCDF4.Dataset(nc_file) as nc:
|
259
|
-
|
260
|
-
return data[0] if len(data) == 1 else data
|
263
|
+
return [nc.variables[name][:] for name in names]
|
261
264
|
|
262
265
|
|
263
266
|
def interpolate_model(cat_file: str, names: str | list) -> dict[str, np.ndarray]:
|
cloudnetpy/utils.py
CHANGED
@@ -140,6 +140,8 @@ def rebin_2d(
|
|
140
140
|
x_new: np.ndarray,
|
141
141
|
statistic: str = "mean",
|
142
142
|
n_min: int = 1,
|
143
|
+
*,
|
144
|
+
mask_zeros: bool = True,
|
143
145
|
) -> tuple[ma.MaskedArray, list]:
|
144
146
|
"""Rebins 2-D data in one dimension.
|
145
147
|
|
@@ -171,7 +173,10 @@ def rebin_2d(
|
|
171
173
|
bins=edges,
|
172
174
|
)
|
173
175
|
result[~np.isfinite(result)] = 0
|
174
|
-
|
176
|
+
if mask_zeros is True:
|
177
|
+
masked_result = ma.masked_equal(result, 0)
|
178
|
+
else:
|
179
|
+
masked_result = ma.array(result)
|
175
180
|
|
176
181
|
# Fill bins with not enough profiles
|
177
182
|
empty_indices = []
|
@@ -191,6 +196,8 @@ def rebin_1d(
|
|
191
196
|
array: np.ndarray | ma.MaskedArray,
|
192
197
|
x_new: np.ndarray,
|
193
198
|
statistic: str = "mean",
|
199
|
+
*,
|
200
|
+
mask_zeros: bool = True,
|
194
201
|
) -> ma.MaskedArray:
|
195
202
|
"""Rebins 1D array.
|
196
203
|
|
@@ -217,7 +224,9 @@ def rebin_1d(
|
|
217
224
|
bins=edges,
|
218
225
|
)
|
219
226
|
result[~np.isfinite(result)] = 0
|
220
|
-
|
227
|
+
if mask_zeros:
|
228
|
+
return ma.masked_equal(result, 0)
|
229
|
+
return ma.array(result)
|
221
230
|
|
222
231
|
|
223
232
|
def filter_isolated_pixels(array: np.ndarray) -> np.ndarray:
|
cloudnetpy/version.py
CHANGED
@@ -1,29 +1,30 @@
|
|
1
1
|
cloudnetpy/__init__.py,sha256=X_FqY-4yg5GUj5Edo14SToLEos6JIsC3fN-v1FUgQoA,43
|
2
|
-
cloudnetpy/cloudnetarray.py,sha256=
|
2
|
+
cloudnetpy/cloudnetarray.py,sha256=HT6bLtjnimOVbGrdjQBqD0F8GW0KWkn2qhaIGFMKLAY,6987
|
3
3
|
cloudnetpy/concat_lib.py,sha256=YK5ho5msqwNxpPtPT8f2OewIJ8hTrbhCkaxHaBKLTI0,9809
|
4
|
-
cloudnetpy/constants.py,sha256=
|
4
|
+
cloudnetpy/constants.py,sha256=l7_ohQgLEQ6XEG9AMBarTPKp9OM8B1ElJ6fSN0ScdmM,733
|
5
5
|
cloudnetpy/datasource.py,sha256=-6oLC5bsn9sIoaN0glV88owFyTeGRsW1ZVJSV8rM5rE,7813
|
6
6
|
cloudnetpy/exceptions.py,sha256=nA6YieylwKSp5KQOh3DhhcTmUBsd0tBZnedgmlWck-w,1415
|
7
7
|
cloudnetpy/metadata.py,sha256=Bcu1a9UyUq61jomuZ0_6hYIOzf61e5qCXeiwLm46ikw,5040
|
8
|
-
cloudnetpy/output.py,sha256=
|
8
|
+
cloudnetpy/output.py,sha256=WoVTNuxni0DUr163vZ-_mDr1brXhY15XSlGMrq9Aoqw,14700
|
9
9
|
cloudnetpy/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
10
|
-
cloudnetpy/utils.py,sha256=
|
11
|
-
cloudnetpy/version.py,sha256=
|
10
|
+
cloudnetpy/utils.py,sha256=0TlHm71YtSrKXBsRKctitnhQrvZPE-ulEVeAQW-oK58,27398
|
11
|
+
cloudnetpy/version.py,sha256=ACE_5AEhiiFIDukxdKYlkIvBmsdfhExmwRoHxF3tRPs,72
|
12
12
|
cloudnetpy/categorize/__init__.py,sha256=gP5q3Vis1y9u9OWgA_idlbjfWXYN_S0IBSWdwBhL_uU,69
|
13
|
-
cloudnetpy/categorize/atmos.py,sha256=
|
13
|
+
cloudnetpy/categorize/atmos.py,sha256=fWW8ye_8HZASRAiYwURFKWzcGOYIA2RKeVxCq0lVOuM,12389
|
14
14
|
cloudnetpy/categorize/atmos_utils.py,sha256=wndpwJxc2-QnNTkV8tc8I11Vs_WkNz9sVMX1fuGgUC4,3777
|
15
|
-
cloudnetpy/categorize/categorize.py,sha256=
|
16
|
-
cloudnetpy/categorize/classify.py,sha256=
|
17
|
-
cloudnetpy/categorize/containers.py,sha256=
|
15
|
+
cloudnetpy/categorize/categorize.py,sha256=_chLFT0l9ll78y3oaxFwOTBbv2raxxjghCz-_KGdImQ,17476
|
16
|
+
cloudnetpy/categorize/classify.py,sha256=vIR7Ztu41kQbRaA2xIy_mm08KxVgQczYXpNSo0vz2Yg,8905
|
17
|
+
cloudnetpy/categorize/containers.py,sha256=j6oSKPeZcq9vFthYaocAw1m6yReRNNPYUQF5UTDq4YM,4232
|
18
|
+
cloudnetpy/categorize/disdrometer.py,sha256=0Z0nvUtoZKDxiUfBZzoYZxUFOVjq-thmYfaCkskeECs,1799
|
18
19
|
cloudnetpy/categorize/droplet.py,sha256=pUmB-gN0t9sVgsGLof6X9N0nuEb4EBtEUswwpoQapTY,8687
|
19
20
|
cloudnetpy/categorize/falling.py,sha256=xES5ZdYs34tbX1p4a9kzt9r3G5s25Mpvs5WeFs1KNzo,4385
|
20
21
|
cloudnetpy/categorize/freezing.py,sha256=684q83TPQ5hHrbbHX-E36VoTlWLSOlGfOW1FC8b3wfI,3754
|
21
|
-
cloudnetpy/categorize/insects.py,sha256=
|
22
|
-
cloudnetpy/categorize/lidar.py,sha256=
|
22
|
+
cloudnetpy/categorize/insects.py,sha256=7m31aQSO9nekf_d3TgXMnPpgHIP7J_xhHLShfQ9JS9E,5764
|
23
|
+
cloudnetpy/categorize/lidar.py,sha256=LYqXw30sLOYxhKRcO3k5r0uVLGRYmJ5k0KuVOMduY5A,2604
|
23
24
|
cloudnetpy/categorize/melting.py,sha256=AOq36yLntDXYbeMw5QhZ7kMLwt0INyUbhzv-rSILLyo,6261
|
24
25
|
cloudnetpy/categorize/model.py,sha256=xWB6XOSz9p0h4b4m6ImMmzcTImOmz54d093WmsLogdQ,5535
|
25
26
|
cloudnetpy/categorize/mwr.py,sha256=-KMoYlch_C79bqgcEiRDCTRCcQf1ZsYxU90GQ8hzMgs,1435
|
26
|
-
cloudnetpy/categorize/radar.py,sha256=
|
27
|
+
cloudnetpy/categorize/radar.py,sha256=S3561FVK6J4NUjqPi2S6fKnjnOm0oLwbIC54J64AN4Y,13659
|
27
28
|
cloudnetpy/instruments/__init__.py,sha256=_jejVwi_viSZehmAOkEqTNI-0-exGgAJ_bHW1IRRwTI,398
|
28
29
|
cloudnetpy/instruments/basta.py,sha256=OeWBP_5b2xjOzPaFWEFC0irgLhSqX1YdcPkVBRbxd4c,3742
|
29
30
|
cloudnetpy/instruments/campbell_scientific.py,sha256=2WHfBKQjtRSl0AqvtPeX7G8Hdi3Dn0WbvoAppFOMbA8,5270
|
@@ -48,7 +49,7 @@ cloudnetpy/instruments/vaisala.py,sha256=E6PaK26lHprqOJUCEDZPZQu83Qan9n_THudTFQM
|
|
48
49
|
cloudnetpy/instruments/weather_station.py,sha256=IMJHGXfMhb4jJw_i66oGDCkeeRn3_eko8zVehu6Fte0,5970
|
49
50
|
cloudnetpy/instruments/disdrometer/__init__.py,sha256=lyjwttWvFvuwYxEkusoAvgRcbBmglmOp5HJOpXUqLWo,93
|
50
51
|
cloudnetpy/instruments/disdrometer/common.py,sha256=TTsvWzhHg5tansTs47WB-7uuBRCZdjbFQMAyAQtFkSU,15636
|
51
|
-
cloudnetpy/instruments/disdrometer/parsivel.py,sha256=
|
52
|
+
cloudnetpy/instruments/disdrometer/parsivel.py,sha256=TWq8VgG8U75AJQfCX-V2y8qy-nO6dKMOGd2eG8-u7to,22342
|
52
53
|
cloudnetpy/instruments/disdrometer/thies.py,sha256=h7EwZ9tn47UUMiYqDQ68vkXv4q0rEqX1ZeFXd7XJYNg,5050
|
53
54
|
cloudnetpy/model_evaluation/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
54
55
|
cloudnetpy/model_evaluation/file_handler.py,sha256=oUGIblcEWLLv16YKUch-M5KA-dGRAcuHa-9anP3xtX4,6447
|
@@ -92,22 +93,22 @@ cloudnetpy/model_evaluation/tests/unit/test_statistical_methods.py,sha256=Ra3r4V
|
|
92
93
|
cloudnetpy/model_evaluation/tests/unit/test_tools.py,sha256=Ia_VrLdV2NstX5gbx_3AZTOAlrgLAy_xFZ8fHYVX0xI,3817
|
93
94
|
cloudnetpy/plotting/__init__.py,sha256=lg9Smn4BI0dVBgnDLC3JVJ4GmwoSnO-qoSd4ApvwV6Y,107
|
94
95
|
cloudnetpy/plotting/plot_meta.py,sha256=MK_-fByZgihXkTXVJhs942qvq9SLfyzLa911y6x8cV0,14791
|
95
|
-
cloudnetpy/plotting/plotting.py,sha256=
|
96
|
+
cloudnetpy/plotting/plotting.py,sha256=TUC6kZqpBDE-EgehkFFebw4y9tZOIVP3kH6UHh41H3o,30863
|
96
97
|
cloudnetpy/products/__init__.py,sha256=2hRb5HG9hNrxH1if5laJkLeFeaZCd5W1q3hh4ewsX0E,273
|
97
98
|
cloudnetpy/products/classification.py,sha256=0E9OUGR3uLCsS1nORwQu0SqW0_8uX7n6LlRcVhtzKw4,7845
|
98
|
-
cloudnetpy/products/der.py,sha256=
|
99
|
+
cloudnetpy/products/der.py,sha256=mam6jWV7A2h8V5WC3DIeFp6ou7UD1JOw9r7h2B0su-s,12403
|
99
100
|
cloudnetpy/products/drizzle.py,sha256=BY2HvJeWt_ps6KKCGXwUUNRTy78q0cQM8bOCCoj8TWA,10803
|
100
101
|
cloudnetpy/products/drizzle_error.py,sha256=4GwlHRtNbk9ks7bGtXCco-wXbcDOKeAQwKmbhzut6Qk,6132
|
101
|
-
cloudnetpy/products/drizzle_tools.py,sha256=
|
102
|
+
cloudnetpy/products/drizzle_tools.py,sha256=UhcJbPa4tXHbuVlegIRfOl5nZ_E6ddKv20aghfP0hdg,10847
|
102
103
|
cloudnetpy/products/ier.py,sha256=IcGPlQahbwJjp3vOOrxWSYW2FPzbSV0KQL5eYECc4kU,7777
|
103
104
|
cloudnetpy/products/iwc.py,sha256=MUPuVKWgqOuuLRCGk3QY74uBZB_7P1qlinlP8nEvz9o,10124
|
104
105
|
cloudnetpy/products/lwc.py,sha256=TbIR6kMwjbm63ed5orB1pkqx9ZBm8C5TF2JmT8WKdKI,18794
|
105
106
|
cloudnetpy/products/mie_lu_tables.nc,sha256=It4fYpqJXlqOgL8jeZ-PxGzP08PMrELIDVe55y9ob58,16637951
|
106
107
|
cloudnetpy/products/mwr_tools.py,sha256=PRm5aCULccUehU-Byk55wYhhEHseMjoAjGBu5TSyHao,4621
|
107
|
-
cloudnetpy/products/product_tools.py,sha256=
|
108
|
+
cloudnetpy/products/product_tools.py,sha256=rhx_Ru9FLlQqCNM-awoiHx18-Aq1eBwL9LiUaQoJs6k,10412
|
108
109
|
docs/source/conf.py,sha256=IKiFWw6xhUd8NrCg0q7l596Ck1d61XWeVjIFHVSG9Og,1490
|
109
|
-
cloudnetpy-1.
|
110
|
-
cloudnetpy-1.
|
111
|
-
cloudnetpy-1.
|
112
|
-
cloudnetpy-1.
|
113
|
-
cloudnetpy-1.
|
110
|
+
cloudnetpy-1.58.1.dist-info/LICENSE,sha256=wcZF72bdaoG9XugpyE95Juo7lBQOwLuTKBOhhtANZMM,1094
|
111
|
+
cloudnetpy-1.58.1.dist-info/METADATA,sha256=CqdJACjYG1_pszHVdzrrr6RZGJNO-YUvhSmrNEuBhz8,5733
|
112
|
+
cloudnetpy-1.58.1.dist-info/WHEEL,sha256=oiQVh_5PnQM0E3gPdiz09WCNmwiHDMaGer_elqB3coM,92
|
113
|
+
cloudnetpy-1.58.1.dist-info/top_level.txt,sha256=ibSPWRr6ojS1i11rtBFz2_gkIe68mggj7aeswYfaOo0,16
|
114
|
+
cloudnetpy-1.58.1.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|