cloudnetpy 1.55.21__py3-none-any.whl → 1.55.22__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_utils.py +1 -1
- cloudnetpy/categorize/categorize.py +2 -2
- cloudnetpy/categorize/lidar.py +35 -40
- cloudnetpy/categorize/mwr.py +2 -2
- cloudnetpy/categorize/radar.py +2 -2
- cloudnetpy/constants.py +7 -0
- cloudnetpy/instruments/disdrometer/common.py +6 -4
- cloudnetpy/instruments/disdrometer/parsivel.py +3 -3
- cloudnetpy/instruments/nc_lidar.py +2 -2
- cloudnetpy/instruments/rpg.py +10 -5
- cloudnetpy/instruments/rpg_reader.py +2 -1
- cloudnetpy/instruments/vaisala.py +2 -3
- cloudnetpy/products/iwc.py +2 -1
- cloudnetpy/utils.py +24 -35
- cloudnetpy/version.py +1 -1
- {cloudnetpy-1.55.21.dist-info → cloudnetpy-1.55.22.dist-info}/METADATA +1 -1
- {cloudnetpy-1.55.21.dist-info → cloudnetpy-1.55.22.dist-info}/RECORD +20 -20
- {cloudnetpy-1.55.21.dist-info → cloudnetpy-1.55.22.dist-info}/LICENSE +0 -0
- {cloudnetpy-1.55.21.dist-info → cloudnetpy-1.55.22.dist-info}/WHEEL +0 -0
- {cloudnetpy-1.55.21.dist-info → cloudnetpy-1.55.22.dist-info}/top_level.txt +0 -0
@@ -68,8 +68,8 @@ def generate_categorize(
|
|
68
68
|
model_gap_ind = data["model"].interpolate_to_grid(time, height)
|
69
69
|
radar_gap_ind = data["radar"].rebin_to_grid(time)
|
70
70
|
lidar_gap_ind = data["lidar"].interpolate_to_grid(time, height)
|
71
|
-
gap_indices = radar_gap_ind + lidar_gap_ind + model_gap_ind
|
72
|
-
return
|
71
|
+
gap_indices = set(radar_gap_ind + lidar_gap_ind + model_gap_ind)
|
72
|
+
return [ind for ind in range(len(time)) if ind not in gap_indices]
|
73
73
|
|
74
74
|
def _screen_bad_time_indices(valid_indices: list) -> None:
|
75
75
|
n_time_full = len(time)
|
cloudnetpy/categorize/lidar.py
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
"""Lidar module, containing the :class:`Lidar` class."""
|
2
2
|
import logging
|
3
|
+
from typing import Literal
|
3
4
|
|
4
5
|
import numpy as np
|
5
6
|
from numpy import ma
|
@@ -22,46 +23,46 @@ class Lidar(DataSource):
|
|
22
23
|
self.append_data(self.getvar("beta"), "beta")
|
23
24
|
self._add_meta()
|
24
25
|
|
25
|
-
def interpolate_to_grid(
|
26
|
+
def interpolate_to_grid(
|
27
|
+
self, time_new: np.ndarray, height_new: np.ndarray
|
28
|
+
) -> list[int]:
|
26
29
|
"""Interpolate beta using nearest neighbor."""
|
27
|
-
max_height = 100
|
28
|
-
max_time = 1
|
30
|
+
max_height = 100 # m
|
31
|
+
max_time = 1 / 60 # min -> fraction hour
|
29
32
|
|
30
|
-
# Remove completely masked profiles from the interpolation
|
31
|
-
beta = self.data["beta"][:]
|
32
|
-
indices = []
|
33
|
-
for ind, b in enumerate(beta):
|
34
|
-
if ma.all(b) is not ma.masked:
|
35
|
-
indices.append(ind)
|
36
33
|
if self.height is None:
|
37
34
|
msg = "Unable to interpolate lidar: no height information"
|
38
35
|
raise RuntimeError(msg)
|
39
|
-
|
36
|
+
|
37
|
+
# Interpolate beta to new grid but ignore profiles that are completely masked
|
38
|
+
beta = self.data["beta"][:]
|
39
|
+
indices = [ind for ind, b in enumerate(beta) if ma.all(b) is not ma.masked]
|
40
|
+
beta_interp = interpolate_2d_nearest(
|
40
41
|
self.time[indices],
|
41
42
|
self.height,
|
42
43
|
beta[indices, :],
|
43
44
|
time_new,
|
44
45
|
height_new,
|
45
46
|
)
|
47
|
+
# Mask data points that are too far from the original grid
|
48
|
+
time_gap_ind = _get_gap_ind(self.time[indices], time_new, max_time)
|
49
|
+
height_gap_ind = _get_gap_ind(self.height, height_new, max_height)
|
50
|
+
self._mask_profiles(beta_interp, time_gap_ind, "time")
|
51
|
+
self._mask_profiles(beta_interp, height_gap_ind, "height")
|
52
|
+
self.data["beta"].data = beta_interp
|
53
|
+
return time_gap_ind
|
46
54
|
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
logging.warning(
|
59
|
-
"Unable to interpolate lidar for %s altitudes",
|
60
|
-
len(bad_height_indices),
|
61
|
-
)
|
62
|
-
beta_interpolated[:, bad_height_indices] = ma.masked
|
63
|
-
self.data["beta"].data = beta_interpolated
|
64
|
-
return bad_time_indices
|
55
|
+
@staticmethod
|
56
|
+
def _mask_profiles(
|
57
|
+
data: ma.MaskedArray, ind: list[int], dim: Literal["time", "height"]
|
58
|
+
) -> None:
|
59
|
+
prefix = f"Unable to interpolate lidar for {len(ind)}"
|
60
|
+
if dim == "time" and ind:
|
61
|
+
logging.warning("%s time steps", prefix)
|
62
|
+
data[ind, :] = ma.masked
|
63
|
+
elif dim == "height" and ind:
|
64
|
+
logging.warning("%s altitudes", prefix)
|
65
|
+
data[:, ind] = ma.masked
|
65
66
|
|
66
67
|
def _add_meta(self) -> None:
|
67
68
|
self.append_data(float(self.getvar("wavelength")), "lidar_wavelength")
|
@@ -69,15 +70,9 @@ class Lidar(DataSource):
|
|
69
70
|
self.append_data(3.0, "beta_bias")
|
70
71
|
|
71
72
|
|
72
|
-
def
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
)
|
77
|
-
|
78
|
-
for ind, value in enumerate(new_grid):
|
79
|
-
diffu = np.abs(original_grid - value)
|
80
|
-
distance = diffu[diffu.argmin()]
|
81
|
-
if distance > threshold:
|
82
|
-
indices.append(ind)
|
83
|
-
return indices
|
73
|
+
def _get_gap_ind(grid: np.ndarray, new_grid: np.ndarray, threshold: float) -> list[int]:
|
74
|
+
return [
|
75
|
+
ind
|
76
|
+
for ind, value in enumerate(new_grid)
|
77
|
+
if np.min(np.abs(grid - value)) > threshold
|
78
|
+
]
|
cloudnetpy/categorize/mwr.py
CHANGED
@@ -2,6 +2,7 @@
|
|
2
2
|
import numpy as np
|
3
3
|
|
4
4
|
from cloudnetpy import utils
|
5
|
+
from cloudnetpy.constants import G_TO_KG
|
5
6
|
from cloudnetpy.datasource import DataSource
|
6
7
|
|
7
8
|
|
@@ -36,8 +37,7 @@ class Mwr(DataSource):
|
|
36
37
|
|
37
38
|
def _init_lwp_error(self) -> None:
|
38
39
|
random_error, bias = 0.25, 20
|
39
|
-
|
40
|
-
lwp_error = utils.l2norm(self.data["lwp"][:] * random_error, bias * g2kg)
|
40
|
+
lwp_error = utils.l2norm(self.data["lwp"][:] * random_error, bias * G_TO_KG)
|
41
41
|
self.append_data(lwp_error, "lwp_error", units="kg m-2")
|
42
42
|
self.data["lwp_error"].comment = (
|
43
43
|
"This variable is a rough estimate of the one-standard-deviation\n"
|
cloudnetpy/categorize/radar.py
CHANGED
@@ -8,6 +8,7 @@ from scipy import constants
|
|
8
8
|
|
9
9
|
from cloudnetpy import utils
|
10
10
|
from cloudnetpy.categorize.classify import ClassificationResult
|
11
|
+
from cloudnetpy.constants import SEC_IN_HOUR
|
11
12
|
from cloudnetpy.datasource import DataSource
|
12
13
|
|
13
14
|
|
@@ -261,8 +262,7 @@ class Radar(DataSource):
|
|
261
262
|
return z_error
|
262
263
|
|
263
264
|
def _number_of_independent_pulses() -> float:
|
264
|
-
|
265
|
-
dwell_time = utils.mdiff(self.time) * seconds_in_hour
|
265
|
+
dwell_time = utils.mdiff(self.time) * SEC_IN_HOUR
|
266
266
|
return (
|
267
267
|
dwell_time
|
268
268
|
* self.radar_frequency
|
cloudnetpy/constants.py
CHANGED
@@ -6,6 +6,7 @@ from numpy import ma
|
|
6
6
|
|
7
7
|
from cloudnetpy import utils
|
8
8
|
from cloudnetpy.cloudnetarray import CloudnetArray
|
9
|
+
from cloudnetpy.constants import MM_TO_M, SEC_IN_HOUR, SEC_IN_MINUTE
|
9
10
|
from cloudnetpy.exceptions import DisdrometerDataError, ValidTimeStampError
|
10
11
|
from cloudnetpy.instruments.cloudnet_instrument import CloudnetInstrument
|
11
12
|
from cloudnetpy.instruments.vaisala import values_to_dict
|
@@ -28,13 +29,12 @@ class Disdrometer(CloudnetInstrument):
|
|
28
29
|
self._file_data = self._read_file()
|
29
30
|
|
30
31
|
def convert_units(self) -> None:
|
31
|
-
|
32
|
-
mmh_to_ms = 3600 * mm_to_m
|
32
|
+
mmh_to_ms = SEC_IN_HOUR / MM_TO_M
|
33
33
|
c_to_k = 273.15
|
34
34
|
self._convert_data(("rainfall_rate_1min_total",), mmh_to_ms)
|
35
35
|
self._convert_data(("rainfall_rate",), mmh_to_ms)
|
36
36
|
self._convert_data(("rainfall_rate_1min_solid",), mmh_to_ms)
|
37
|
-
self._convert_data(("diameter", "diameter_spread", "diameter_bnds"),
|
37
|
+
self._convert_data(("diameter", "diameter_spread", "diameter_bnds"), 1e3)
|
38
38
|
self._convert_data(("V_sensor_supply",), 10)
|
39
39
|
self._convert_data(("I_mean_laser",), 100)
|
40
40
|
self._convert_data(("T_sensor",), c_to_k, method="add")
|
@@ -150,7 +150,9 @@ class Disdrometer(CloudnetInstrument):
|
|
150
150
|
if self.source == PARSIVEL:
|
151
151
|
raise NotImplementedError
|
152
152
|
hour, minute, sec = timestamp.split(":")
|
153
|
-
seconds.append(
|
153
|
+
seconds.append(
|
154
|
+
int(hour) * SEC_IN_HOUR + int(minute) * SEC_IN_MINUTE + int(sec)
|
155
|
+
)
|
154
156
|
return CloudnetArray(utils.seconds2hours(np.array(seconds)), "time")
|
155
157
|
|
156
158
|
def _convert_data(self, keys: tuple, value: float, method: str = "divide") -> None:
|
@@ -12,6 +12,7 @@ import numpy as np
|
|
12
12
|
|
13
13
|
from cloudnetpy import output
|
14
14
|
from cloudnetpy.cloudnetarray import CloudnetArray
|
15
|
+
from cloudnetpy.constants import MM_TO_M, SEC_IN_HOUR
|
15
16
|
from cloudnetpy.exceptions import DisdrometerDataError
|
16
17
|
from cloudnetpy.instruments import instruments
|
17
18
|
from cloudnetpy.instruments.cloudnet_instrument import CloudnetInstrument
|
@@ -148,12 +149,11 @@ class Parsivel(CloudnetInstrument):
|
|
148
149
|
Disdrometer.store_vectors(self.data, n_values, spreads, "diameter")
|
149
150
|
|
150
151
|
def convert_units(self) -> None:
|
151
|
-
|
152
|
-
mmh_to_ms = 3600 * mm_to_m
|
152
|
+
mmh_to_ms = SEC_IN_HOUR / MM_TO_M
|
153
153
|
c_to_k = 273.15
|
154
154
|
self._convert_data(("rainfall_rate",), mmh_to_ms)
|
155
155
|
self._convert_data(("snowfall_rate",), mmh_to_ms)
|
156
|
-
self._convert_data(("diameter", "diameter_spread", "diameter_bnds"),
|
156
|
+
self._convert_data(("diameter", "diameter_spread", "diameter_bnds"), 1e3)
|
157
157
|
self._convert_data(("V_sensor_supply",), 10)
|
158
158
|
self._convert_data(("T_sensor",), c_to_k, method="add")
|
159
159
|
|
@@ -1,6 +1,6 @@
|
|
1
1
|
"""Module with a class for Lufft chm15k ceilometer."""
|
2
2
|
import logging
|
3
|
-
from typing import TYPE_CHECKING
|
3
|
+
from typing import TYPE_CHECKING, Literal
|
4
4
|
|
5
5
|
import numpy as np
|
6
6
|
from numpy import ma
|
@@ -19,7 +19,7 @@ class NcLidar(Ceilometer):
|
|
19
19
|
super().__init__()
|
20
20
|
self.dataset: netCDF4.Dataset | None = None
|
21
21
|
|
22
|
-
def _fetch_range(self, reference:
|
22
|
+
def _fetch_range(self, reference: Literal["upper", "lower"]) -> None:
|
23
23
|
if self.dataset is None:
|
24
24
|
msg = "No dataset found"
|
25
25
|
raise RuntimeError(msg)
|
cloudnetpy/instruments/rpg.py
CHANGED
@@ -11,6 +11,7 @@ from rpgpy import RPGFileError
|
|
11
11
|
from cloudnetpy import output, utils
|
12
12
|
from cloudnetpy.categorize.atmos_utils import mmh2ms
|
13
13
|
from cloudnetpy.cloudnetarray import CloudnetArray
|
14
|
+
from cloudnetpy.constants import G_TO_KG
|
14
15
|
from cloudnetpy.exceptions import InconsistentDataError, ValidTimeStampError
|
15
16
|
from cloudnetpy.instruments import instruments
|
16
17
|
from cloudnetpy.instruments.cloudnet_instrument import CloudnetInstrument
|
@@ -162,6 +163,8 @@ def _mask_invalid_data(data_in: dict) -> dict:
|
|
162
163
|
data = data_in.copy()
|
163
164
|
fill_values = (-999, 1e-10)
|
164
165
|
for name in data:
|
166
|
+
if np.issubdtype(data[name].dtype, np.integer) or name == "rainfall_rate":
|
167
|
+
continue
|
165
168
|
data[name] = ma.masked_equal(data[name], 0)
|
166
169
|
for value in fill_values:
|
167
170
|
data[name][data[name] == value] = ma.masked
|
@@ -234,11 +237,13 @@ class Rpg(CloudnetInstrument):
|
|
234
237
|
|
235
238
|
def convert_time_to_fraction_hour(self, data_type: str | None = None) -> None:
|
236
239
|
"""Converts time to fraction hour."""
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
+
ms2s = 1e-3
|
241
|
+
total_time_sec = self.raw_data["time"] + self.raw_data.get("time_ms", 0) * ms2s
|
242
|
+
fraction_hour = utils.seconds2hours(total_time_sec)
|
243
|
+
|
244
|
+
self.data["time"] = CloudnetArray(
|
240
245
|
np.array(fraction_hour),
|
241
|
-
|
246
|
+
"time",
|
242
247
|
data_type=data_type,
|
243
248
|
)
|
244
249
|
|
@@ -307,7 +312,7 @@ class Fmcw(Rpg):
|
|
307
312
|
def convert_units(self) -> None:
|
308
313
|
"""Converts units."""
|
309
314
|
self.data["rainfall_rate"].data = mmh2ms(self.data["rainfall_rate"].data)
|
310
|
-
self.data["lwp"].data *=
|
315
|
+
self.data["lwp"].data *= G_TO_KG
|
311
316
|
|
312
317
|
@staticmethod
|
313
318
|
def _get_instrument(data: dict):
|
@@ -6,6 +6,7 @@ from numpy import ma
|
|
6
6
|
from numpy.lib import recfunctions as rfn
|
7
7
|
from rpgpy import read_rpg
|
8
8
|
|
9
|
+
from cloudnetpy.constants import G_TO_KG
|
9
10
|
from cloudnetpy.exceptions import ValidTimeStampError
|
10
11
|
|
11
12
|
|
@@ -256,7 +257,7 @@ class HatproBinLwp(HatproBin):
|
|
256
257
|
],
|
257
258
|
self.header["_n_samples"],
|
258
259
|
)
|
259
|
-
self.data["lwp"] *=
|
260
|
+
self.data["lwp"] *= G_TO_KG
|
260
261
|
|
261
262
|
|
262
263
|
class HatproBinIwv(HatproBin):
|
@@ -5,13 +5,12 @@ import logging
|
|
5
5
|
import numpy as np
|
6
6
|
|
7
7
|
from cloudnetpy import utils
|
8
|
+
from cloudnetpy.constants import SEC_IN_HOUR, SEC_IN_MINUTE
|
8
9
|
from cloudnetpy.exceptions import ValidTimeStampError
|
9
10
|
from cloudnetpy.instruments import instruments
|
10
11
|
from cloudnetpy.instruments.ceilometer import Ceilometer, NoiseParam
|
11
12
|
|
12
13
|
M2KM = 0.001
|
13
|
-
SECONDS_IN_MINUTE = 60
|
14
|
-
SECONDS_IN_HOUR = 3600
|
15
14
|
|
16
15
|
|
17
16
|
class VaisalaCeilo(Ceilometer):
|
@@ -400,4 +399,4 @@ def values_to_dict(keys: tuple, values: list) -> dict:
|
|
400
399
|
def time_to_fraction_hour(time: str) -> float:
|
401
400
|
"""Returns time (hh:mm:ss) as fraction hour"""
|
402
401
|
hour, minute, sec = time.split(":")
|
403
|
-
return int(hour) + (int(minute) *
|
402
|
+
return int(hour) + (int(minute) * SEC_IN_MINUTE + int(sec)) / SEC_IN_HOUR
|
cloudnetpy/products/iwc.py
CHANGED
@@ -3,6 +3,7 @@ import numpy as np
|
|
3
3
|
from numpy import ma
|
4
4
|
|
5
5
|
from cloudnetpy import output, utils
|
6
|
+
from cloudnetpy.constants import G_TO_KG
|
6
7
|
from cloudnetpy.metadata import MetaData
|
7
8
|
from cloudnetpy.products.product_tools import IceClassification, IceSource
|
8
9
|
|
@@ -83,7 +84,7 @@ class IwcSource(IceSource):
|
|
83
84
|
def _calc_error_in_uncorrected_ice() -> float:
|
84
85
|
spec_liq_atten = 1.0 if self.wl_band == 0 else 4.5
|
85
86
|
liq_atten_scaled = spec_liq_atten * self.coefficients.Z
|
86
|
-
return lwp_prior *
|
87
|
+
return lwp_prior * G_TO_KG * liq_atten_scaled * 2 * 10
|
87
88
|
|
88
89
|
lwp_prior = 250 # g m-2
|
89
90
|
retrieval_uncertainty = 1.7 # dB
|
cloudnetpy/utils.py
CHANGED
@@ -7,7 +7,7 @@ import uuid
|
|
7
7
|
import warnings
|
8
8
|
from collections.abc import Iterator
|
9
9
|
from datetime import timezone
|
10
|
-
from typing import
|
10
|
+
from typing import Literal
|
11
11
|
|
12
12
|
import netCDF4
|
13
13
|
import numpy as np
|
@@ -15,15 +15,12 @@ from numpy import ma
|
|
15
15
|
from scipy import ndimage, stats
|
16
16
|
from scipy.interpolate import RectBivariateSpline, RegularGridInterpolator, griddata
|
17
17
|
|
18
|
+
from cloudnetpy.constants import SEC_IN_DAY, SEC_IN_HOUR, SEC_IN_MINUTE
|
18
19
|
from cloudnetpy.exceptions import ValidTimeStampError
|
19
20
|
|
20
21
|
Epoch = tuple[int, int, int]
|
21
22
|
Date = tuple[str, str, str]
|
22
23
|
|
23
|
-
SECONDS_PER_MINUTE: Final = 60
|
24
|
-
SECONDS_PER_HOUR: Final = 3600
|
25
|
-
SECONDS_PER_DAY: Final = 86400
|
26
|
-
|
27
24
|
|
28
25
|
def seconds2hours(time_in_seconds: np.ndarray) -> np.ndarray:
|
29
26
|
"""Converts seconds since some epoch to fraction hour.
|
@@ -41,8 +38,8 @@ def seconds2hours(time_in_seconds: np.ndarray) -> np.ndarray:
|
|
41
38
|
Excludes leap seconds.
|
42
39
|
|
43
40
|
"""
|
44
|
-
seconds_since_midnight = np.mod(time_in_seconds,
|
45
|
-
fraction_hour = seconds_since_midnight /
|
41
|
+
seconds_since_midnight = np.mod(time_in_seconds, SEC_IN_DAY)
|
42
|
+
fraction_hour = seconds_since_midnight / SEC_IN_HOUR
|
46
43
|
if fraction_hour[-1] == 0:
|
47
44
|
fraction_hour[-1] = 24
|
48
45
|
return fraction_hour
|
@@ -60,10 +57,10 @@ def seconds2time(time_in_seconds: float) -> list:
|
|
60
57
|
list: [hours, minutes, seconds] formatted as '05' etc.
|
61
58
|
|
62
59
|
"""
|
63
|
-
seconds_since_midnight = np.mod(time_in_seconds,
|
64
|
-
hours = seconds_since_midnight //
|
65
|
-
minutes = seconds_since_midnight %
|
66
|
-
seconds = seconds_since_midnight %
|
60
|
+
seconds_since_midnight = np.mod(time_in_seconds, SEC_IN_DAY)
|
61
|
+
hours = seconds_since_midnight // SEC_IN_HOUR
|
62
|
+
minutes = seconds_since_midnight % SEC_IN_HOUR // SEC_IN_MINUTE
|
63
|
+
seconds = seconds_since_midnight % SEC_IN_MINUTE
|
67
64
|
time = [hours, minutes, seconds]
|
68
65
|
return [str(t).zfill(2) for t in time]
|
69
66
|
|
@@ -97,7 +94,7 @@ def datetime2decimal_hours(data: np.ndarray | list) -> np.ndarray:
|
|
97
94
|
output = []
|
98
95
|
for timestamp in data:
|
99
96
|
t = timestamp.time()
|
100
|
-
decimal_hours = t.hour + t.minute /
|
97
|
+
decimal_hours = t.hour + t.minute / SEC_IN_MINUTE + t.second / SEC_IN_HOUR
|
101
98
|
output.append(decimal_hours)
|
102
99
|
return np.array(output)
|
103
100
|
|
@@ -124,7 +121,7 @@ def time_grid(time_step: int = 30) -> np.ndarray:
|
|
124
121
|
if time_step < 1:
|
125
122
|
msg = "Time resolution should be >= 1 seconds"
|
126
123
|
raise ValueError(msg)
|
127
|
-
half_step = time_step /
|
124
|
+
half_step = time_step / SEC_IN_HOUR / 2
|
128
125
|
return np.arange(half_step, 24 + half_step, half_step * 2)
|
129
126
|
|
130
127
|
|
@@ -223,7 +220,7 @@ def rebin_1d(
|
|
223
220
|
|
224
221
|
Returns:
|
225
222
|
-------
|
226
|
-
|
223
|
+
Re-binned data with shape (N,).
|
227
224
|
|
228
225
|
"""
|
229
226
|
edges = binvec(x_new)
|
@@ -301,7 +298,7 @@ def _filter(array: np.ndarray, structure: np.ndarray) -> np.ndarray:
|
|
301
298
|
|
302
299
|
|
303
300
|
def isbit(array: np.ndarray, nth_bit: int) -> np.ndarray:
|
304
|
-
"""Tests if nth bit (0,1,2
|
301
|
+
"""Tests if nth bit (0,1,2,...) is set.
|
305
302
|
|
306
303
|
Args:
|
307
304
|
----
|
@@ -671,7 +668,7 @@ def n_elements(array: np.ndarray, dist: float, var: str | None = None) -> int:
|
|
671
668
|
array: Input array with arbitrary units or time in fraction hour. *x* should
|
672
669
|
be evenly spaced or at least close to.
|
673
670
|
dist: Distance to be covered. If x is fraction time, *dist* is in minutes.
|
674
|
-
Otherwise *x* and *dist* should have the same units.
|
671
|
+
Otherwise, *x* and *dist* should have the same units.
|
675
672
|
var: If 'time', input is fraction hour and distance in minutes, else inputs
|
676
673
|
have the same units. Default is None (same units).
|
677
674
|
|
@@ -701,11 +698,11 @@ def n_elements(array: np.ndarray, dist: float, var: str | None = None) -> int:
|
|
701
698
|
"""
|
702
699
|
n = dist / mdiff(array)
|
703
700
|
if var == "time":
|
704
|
-
n = n /
|
701
|
+
n = n / SEC_IN_MINUTE
|
705
702
|
return int(np.round(n))
|
706
703
|
|
707
704
|
|
708
|
-
def isscalar(array) -> bool:
|
705
|
+
def isscalar(array: np.ndarray | float | list) -> bool:
|
709
706
|
"""Tests if input is scalar.
|
710
707
|
|
711
708
|
By "scalar" we mean that array has a single value.
|
@@ -723,9 +720,7 @@ def isscalar(array) -> bool:
|
|
723
720
|
|
724
721
|
"""
|
725
722
|
arr = ma.array(array)
|
726
|
-
|
727
|
-
return True
|
728
|
-
return False
|
723
|
+
return not hasattr(arr, "__len__") or arr.shape == () or len(arr) == 1
|
729
724
|
|
730
725
|
|
731
726
|
def get_time() -> str:
|
@@ -1000,7 +995,7 @@ def append_data(data_in: dict, key: str, array: np.ndarray) -> dict:
|
|
1000
995
|
return data
|
1001
996
|
|
1002
997
|
|
1003
|
-
def edges2mid(data: np.ndarray, reference:
|
998
|
+
def edges2mid(data: np.ndarray, reference: Literal["upper", "lower"]) -> np.ndarray:
|
1004
999
|
"""Shifts values half bin towards up or down.
|
1005
1000
|
|
1006
1001
|
Args:
|
@@ -1013,8 +1008,6 @@ def edges2mid(data: np.ndarray, reference: str) -> np.ndarray:
|
|
1013
1008
|
Shifted values.
|
1014
1009
|
|
1015
1010
|
"""
|
1016
|
-
if reference not in ("lower", "upper"):
|
1017
|
-
raise ValueError
|
1018
1011
|
gaps = (data[1:] - data[0:-1]) / 2
|
1019
1012
|
if reference == "lower":
|
1020
1013
|
gaps = np.append(gaps, gaps[-1])
|
@@ -1037,31 +1030,27 @@ def get_file_type(filename: str) -> str:
|
|
1037
1030
|
raise ValueError(msg)
|
1038
1031
|
|
1039
1032
|
|
1040
|
-
def get_files_with_common_range(
|
1033
|
+
def get_files_with_common_range(filenames: list) -> list:
|
1041
1034
|
"""Returns files with the same (most common) number of range gates."""
|
1042
1035
|
n_range = []
|
1043
|
-
for file in
|
1036
|
+
for file in filenames:
|
1044
1037
|
with netCDF4.Dataset(file) as nc:
|
1045
1038
|
n_range.append(len(nc.variables["range"]))
|
1046
1039
|
most_common = np.bincount(n_range).argmax()
|
1047
|
-
n_removed
|
1048
|
-
if n_removed > 0:
|
1040
|
+
if n_removed := len(filenames) - n_range.count(int(most_common)) > 0:
|
1049
1041
|
logging.warning(
|
1050
|
-
"Removing %s files due to inconsistent height vector",
|
1051
|
-
n_removed,
|
1042
|
+
"Removing %s files due to inconsistent height vector", n_removed
|
1052
1043
|
)
|
1053
|
-
|
1054
|
-
return [file for i, file in enumerate(files) if i in ind]
|
1044
|
+
return [file for i, file in enumerate(filenames) if n_range[i] == most_common]
|
1055
1045
|
|
1056
1046
|
|
1057
1047
|
def is_all_masked(array: np.ndarray) -> bool:
|
1058
1048
|
"""Tests if all values are masked."""
|
1059
|
-
|
1060
|
-
return array.mask.all()
|
1061
|
-
return False
|
1049
|
+
return ma.isMaskedArray(array) and hasattr(array, "mask") and array.mask.all()
|
1062
1050
|
|
1063
1051
|
|
1064
1052
|
def find_masked_profiles_indices(array: ma.MaskedArray) -> list:
|
1053
|
+
"""Finds indices of masked profiles in a 2-D array."""
|
1065
1054
|
non_masked_counts = np.ma.count(array, axis=1)
|
1066
1055
|
masked_profiles_indices = np.where(non_masked_counts == 0)[0]
|
1067
1056
|
return list(masked_profiles_indices)
|
cloudnetpy/version.py
CHANGED
@@ -1,29 +1,29 @@
|
|
1
1
|
cloudnetpy/__init__.py,sha256=X_FqY-4yg5GUj5Edo14SToLEos6JIsC3fN-v1FUgQoA,43
|
2
2
|
cloudnetpy/cloudnetarray.py,sha256=HKgWGi5fgW-N1LfESga2Y2TbvjmJxLhe_2eloVQjz-s,6947
|
3
3
|
cloudnetpy/concat_lib.py,sha256=beXqjZRaYYZsEBRblOO9_Juwlj3RHIoW8Z2J8HCuZnk,9859
|
4
|
-
cloudnetpy/constants.py,sha256=
|
4
|
+
cloudnetpy/constants.py,sha256=OMp3pKHCZmdKyRvfO35E7vE3FrS_DHIs_GJuexJLCQk,600
|
5
5
|
cloudnetpy/datasource.py,sha256=SCixsM_TWiAbQeGHHdVhPX1kzyY8dSlei2PhH7WqlVg,7980
|
6
6
|
cloudnetpy/exceptions.py,sha256=cPh7CDgD2zHeyK1-D1jdqOKs0rPFfpyu9fj8eliGis8,1370
|
7
7
|
cloudnetpy/metadata.py,sha256=Bcu1a9UyUq61jomuZ0_6hYIOzf61e5qCXeiwLm46ikw,5040
|
8
8
|
cloudnetpy/output.py,sha256=YlCGSFY-xj1PHv_u1qAFH0Kr3KL7GC3jyGGRwhImjSc,14670
|
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=LVVnsjay8j2DcgvtfC2h0OJ6CN1BC4npzV2jaXtZaGA,28071
|
11
|
+
cloudnetpy/version.py,sha256=uQtd9awNyKlJwCZwFAIzB7JIlsT49O3XYAOfsM0mrYA,73
|
12
12
|
cloudnetpy/categorize/__init__.py,sha256=gP5q3Vis1y9u9OWgA_idlbjfWXYN_S0IBSWdwBhL_uU,69
|
13
13
|
cloudnetpy/categorize/atmos.py,sha256=sti9lZavQ9E59MIaHSkEEkhqvgT4OE1lgcQUznzZQG8,12681
|
14
|
-
cloudnetpy/categorize/atmos_utils.py,sha256=
|
15
|
-
cloudnetpy/categorize/categorize.py,sha256=
|
14
|
+
cloudnetpy/categorize/atmos_utils.py,sha256=lzHIfoyi_6my3Qg-BVXqbq6DkA6AjZi5nfNwyMDi3wU,3886
|
15
|
+
cloudnetpy/categorize/categorize.py,sha256=dVy3-D9JY6jlPyjhWI0DJDyxJbEKgt4n2SolSXlhqBU,17024
|
16
16
|
cloudnetpy/categorize/classify.py,sha256=w4GsO9a4FkR0wmWsARRNyYZ6tu5Sg6PqNkzfckT23TI,8937
|
17
17
|
cloudnetpy/categorize/containers.py,sha256=uXpPXqcysD_iGlC-u1QOL_kyIQN74xtyWhxNMKmOL4c,4674
|
18
18
|
cloudnetpy/categorize/droplet.py,sha256=2b2ibrPCeA8xnsXAfqJuo1z1K6P7uRIggAa5QTz4kow,8883
|
19
19
|
cloudnetpy/categorize/falling.py,sha256=BJYGMMJo_G8lhXo4K3Rq6qLWL819K0O-mTyFYZp5sAs,4421
|
20
20
|
cloudnetpy/categorize/freezing.py,sha256=8ruPFVi9TI7d7RyUkNoXc2WIgRWuOeGpUke9xFaPMiA,3806
|
21
21
|
cloudnetpy/categorize/insects.py,sha256=x_Kl5bw7hC-yO4ie9F1OtK1rREguXoLn3UkJROWTMSk,5273
|
22
|
-
cloudnetpy/categorize/lidar.py,sha256=
|
22
|
+
cloudnetpy/categorize/lidar.py,sha256=9EVqQ85uR57evn1DuUa0w20YekfqHR8hAS2B4TcWl_A,2616
|
23
23
|
cloudnetpy/categorize/melting.py,sha256=wnxuWNsMrFFTuXWpLVatExmGBiNGtonYjEWcQK8JGR4,6292
|
24
24
|
cloudnetpy/categorize/model.py,sha256=vOr3eF2Q9BcyhDH36ru37RrkPBrCWtpi_n9LIzAuEvU,5601
|
25
|
-
cloudnetpy/categorize/mwr.py,sha256=
|
26
|
-
cloudnetpy/categorize/radar.py,sha256=
|
25
|
+
cloudnetpy/categorize/mwr.py,sha256=yNvF_ZNJHjfayriovNqsohYSDvviJqEPLsN882zE71s,1457
|
26
|
+
cloudnetpy/categorize/radar.py,sha256=cNpMDMVkjvuwYPt_y4obN90EyhTH-8W0o-qJ7DrvDZ8,13095
|
27
27
|
cloudnetpy/instruments/__init__.py,sha256=_jejVwi_viSZehmAOkEqTNI-0-exGgAJ_bHW1IRRwTI,398
|
28
28
|
cloudnetpy/instruments/basta.py,sha256=qnvVZMSdxvK_YuL3c33QAJYDPWh7ipQXdeGn4p7ikp0,3796
|
29
29
|
cloudnetpy/instruments/campbell_scientific.py,sha256=2WHfBKQjtRSl0AqvtPeX7G8Hdi3Dn0WbvoAppFOMbA8,5270
|
@@ -38,17 +38,17 @@ cloudnetpy/instruments/instruments.py,sha256=GcUEbQFPHUhRTnp300AZ2PK0O2d2EPIGtHq
|
|
38
38
|
cloudnetpy/instruments/lufft.py,sha256=nozeiMRMz7I6q_FwmlxDGhWeJlqTuNh6ru39-M4K3BI,3629
|
39
39
|
cloudnetpy/instruments/mira.py,sha256=lq4vmu-faU8kcQum-2jB4wDVkRjIM9wUNsbkM5bpoBs,9386
|
40
40
|
cloudnetpy/instruments/mrr.py,sha256=rqb1YZ47a5oiH2a2l5GI0f2BUXtmtKuhYUNMt-8y7oE,5878
|
41
|
-
cloudnetpy/instruments/nc_lidar.py,sha256=
|
41
|
+
cloudnetpy/instruments/nc_lidar.py,sha256=Q4sJJwiEPthDz0Zb-laISX32jNYzlUBMafxLJiOAN5c,1704
|
42
42
|
cloudnetpy/instruments/nc_radar.py,sha256=vO35xrzxLrLGjAxRj_adDTJ3H4qu1j3jIazl7471L1c,6905
|
43
43
|
cloudnetpy/instruments/pollyxt.py,sha256=4X1OHSi1ttg7Y4juLfsejhs_vpomX2NhNMMy89CStvM,8594
|
44
44
|
cloudnetpy/instruments/radiometrics.py,sha256=PXVAfT_abyq82CGQwVXzS83-gtI1XZ4YMyj8iFdqXgs,7669
|
45
|
-
cloudnetpy/instruments/rpg.py,sha256=
|
46
|
-
cloudnetpy/instruments/rpg_reader.py,sha256=
|
47
|
-
cloudnetpy/instruments/vaisala.py,sha256=
|
45
|
+
cloudnetpy/instruments/rpg.py,sha256=D8TDRmypa7ahmClGy4BzHAcn8OcoHreXwd5zEwQjTJA,17185
|
46
|
+
cloudnetpy/instruments/rpg_reader.py,sha256=SvYwW0rVuIdAcnqlHCz_Zbenxk-jnGoSYcAfm2kbDuA,11370
|
47
|
+
cloudnetpy/instruments/vaisala.py,sha256=OPqS-wV4YvKRiyjov0fhLZzoBMZbNjATfgh1AMuNmyE,14499
|
48
48
|
cloudnetpy/instruments/weather_station.py,sha256=OcNOvbZl2m1cEouFk5bndabTNalGVPLqwG3RJXvCiSk,6002
|
49
49
|
cloudnetpy/instruments/disdrometer/__init__.py,sha256=lyjwttWvFvuwYxEkusoAvgRcbBmglmOp5HJOpXUqLWo,93
|
50
|
-
cloudnetpy/instruments/disdrometer/common.py,sha256=
|
51
|
-
cloudnetpy/instruments/disdrometer/parsivel.py,sha256=
|
50
|
+
cloudnetpy/instruments/disdrometer/common.py,sha256=nWlVqwvlxei4wJaubBN6NoNsAOpnEqDHvKCPjrAb6Go,15701
|
51
|
+
cloudnetpy/instruments/disdrometer/parsivel.py,sha256=I8mP4oH1CCcSnkSWJ0rqWtE0VJvlXzpvACifJJdoM5E,19730
|
52
52
|
cloudnetpy/instruments/disdrometer/thies.py,sha256=1hJK7m7utG5QSmsBRWGu0JNsIVnZvCQZ4NZc6-Y8NaI,5095
|
53
53
|
cloudnetpy/model_evaluation/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
54
54
|
cloudnetpy/model_evaluation/file_handler.py,sha256=NiBKBnV8o5DRxafKKShBCmVabDkwBH-VpcaAhA6X8j4,6486
|
@@ -100,15 +100,15 @@ cloudnetpy/products/drizzle.py,sha256=lxEkKcQWoPZN_LkV7TCE5AFNzqa2r1ZwO7V4UKF3MG
|
|
100
100
|
cloudnetpy/products/drizzle_error.py,sha256=o6njlgyNFQ1_O8fG1d9hGCJEPQYU6yiSbMrSlNd5BcM,6153
|
101
101
|
cloudnetpy/products/drizzle_tools.py,sha256=oYEHTUd2nEUkeScpUWw3NGAkkbeAo8DsaF7uThhfyqw,10980
|
102
102
|
cloudnetpy/products/ier.py,sha256=BJRZtY3OfCIKNHii4OuniniVOazhoKx64fSWmV9qGMM,7826
|
103
|
-
cloudnetpy/products/iwc.py,sha256=
|
103
|
+
cloudnetpy/products/iwc.py,sha256=jvUEIU_PePvVeFbfbdQ8hfHLJbRiQS8d6LAnLentfho,10173
|
104
104
|
cloudnetpy/products/lwc.py,sha256=0WOtKiYWwDlyZ6fgkVFCoOTQYNdCPPUkuBrNoE3l-zk,18968
|
105
105
|
cloudnetpy/products/mie_lu_tables.nc,sha256=It4fYpqJXlqOgL8jeZ-PxGzP08PMrELIDVe55y9ob58,16637951
|
106
106
|
cloudnetpy/products/mwr_multi.py,sha256=mRMzdDhTddhPLBcDiG_2kw_BSRL_01hA9-o4-cmaVaQ,3301
|
107
107
|
cloudnetpy/products/mwr_single.py,sha256=4KyxeFg7AphEJg5P7ey8SXacyNAG3PGDOvnksvZj3R8,3168
|
108
108
|
cloudnetpy/products/product_tools.py,sha256=dQoFIvM6flnfYXfmm1A71WH7_BZQrf3K_WrsY18Vrgs,10551
|
109
109
|
docs/source/conf.py,sha256=IKiFWw6xhUd8NrCg0q7l596Ck1d61XWeVjIFHVSG9Og,1490
|
110
|
-
cloudnetpy-1.55.
|
111
|
-
cloudnetpy-1.55.
|
112
|
-
cloudnetpy-1.55.
|
113
|
-
cloudnetpy-1.55.
|
114
|
-
cloudnetpy-1.55.
|
110
|
+
cloudnetpy-1.55.22.dist-info/LICENSE,sha256=wcZF72bdaoG9XugpyE95Juo7lBQOwLuTKBOhhtANZMM,1094
|
111
|
+
cloudnetpy-1.55.22.dist-info/METADATA,sha256=75usdsxUS7PzvXmT4ViGMbI0v4J2IyWsHp1OmsFOLbc,5869
|
112
|
+
cloudnetpy-1.55.22.dist-info/WHEEL,sha256=Xo9-1PvkuimrydujYJAjF7pCkriuXBpUPEjma1nZyJ0,92
|
113
|
+
cloudnetpy-1.55.22.dist-info/top_level.txt,sha256=ibSPWRr6ojS1i11rtBFz2_gkIe68mggj7aeswYfaOo0,16
|
114
|
+
cloudnetpy-1.55.22.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|