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.
@@ -140,4 +140,4 @@ def k2c(temp: np.ndarray) -> np.ndarray:
140
140
 
141
141
  def mmh2ms(data: np.ndarray) -> np.ndarray:
142
142
  """Converts mm h-1 to m s-1"""
143
- return data / 3600_000
143
+ return data / con.SEC_IN_HOUR * con.MM_TO_M
@@ -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 list(set(range(len(time))) - set(gap_indices))
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)
@@ -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(self, time_new: np.ndarray, height_new: np.ndarray) -> list:
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.0 # m
28
- max_time = 1.0 # min
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
- beta_interpolated = interpolate_2d_nearest(
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
- # Filter profiles and range gates having data gap
48
- max_time /= 60 # to fraction hour
49
- bad_time_indices = _get_bad_indices(self.time[indices], time_new, max_time)
50
- bad_height_indices = _get_bad_indices(self.height, height_new, max_height)
51
- if bad_time_indices:
52
- logging.warning(
53
- "Unable to interpolate lidar for %s time steps",
54
- len(bad_time_indices),
55
- )
56
- beta_interpolated[bad_time_indices, :] = ma.masked
57
- if bad_height_indices:
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 _get_bad_indices(
73
- original_grid: np.ndarray,
74
- new_grid: np.ndarray,
75
- threshold: float,
76
- ) -> list:
77
- indices = []
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
+ ]
@@ -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
- g2kg = 1e-3
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"
@@ -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
- seconds_in_hour = 3600
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
@@ -18,3 +18,10 @@ RS: Final = 287.058
18
18
 
19
19
  # ice density kg m-3
20
20
  RHO_ICE: Final = 917
21
+
22
+ # other
23
+ SEC_IN_MINUTE: Final = 60
24
+ SEC_IN_HOUR: Final = 3600
25
+ SEC_IN_DAY: Final = 86400
26
+ MM_TO_M: Final = 1e-3
27
+ G_TO_KG: Final = 1e-3
@@ -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
- mm_to_m = 1e3
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"), mm_to_m)
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(int(hour) * 3600 + int(minute) * 60 + int(sec))
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
- mm_to_m = 1e3
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"), mm_to_m)
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: str) -> None:
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)
@@ -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
- key = "time"
238
- fraction_hour = utils.seconds2hours(self.raw_data[key])
239
- self.data[key] = CloudnetArray(
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
- key,
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 *= 1e-3 # g -> kg
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"] *= 1e-3 # Convert from g/m^2 to kg/m^2
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) * SECONDS_IN_MINUTE + int(sec)) / SECONDS_IN_HOUR
402
+ return int(hour) + (int(minute) * SEC_IN_MINUTE + int(sec)) / SEC_IN_HOUR
@@ -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 * liq_atten_scaled * 2 * 1e-3 * 10
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 Final
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, SECONDS_PER_DAY)
45
- fraction_hour = seconds_since_midnight / SECONDS_PER_HOUR
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, SECONDS_PER_DAY)
64
- hours = seconds_since_midnight // SECONDS_PER_HOUR
65
- minutes = seconds_since_midnight % SECONDS_PER_HOUR // SECONDS_PER_MINUTE
66
- seconds = seconds_since_midnight % SECONDS_PER_MINUTE
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 / 60 + t.second / 3600
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 / SECONDS_PER_HOUR / 2
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
- Rebinned data with shape (N,).
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..) is set.
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 / 60
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
- if not hasattr(arr, "__len__") or arr.shape == () or len(arr) == 1:
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: str) -> np.ndarray:
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(files: list) -> list:
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 files:
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 = len([n for n in n_range if n != most_common])
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
- ind = np.where(n_range == most_common)[0]
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
- if ma.isMaskedArray(array) and hasattr(array, "mask"):
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,4 +1,4 @@
1
1
  MAJOR = 1
2
2
  MINOR = 55
3
- PATCH = 21
3
+ PATCH = 22
4
4
  __version__ = f"{MAJOR}.{MINOR}.{PATCH}"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: cloudnetpy
3
- Version: 1.55.21
3
+ Version: 1.55.22
4
4
  Summary: Python package for Cloudnet processing
5
5
  Author: Simo Tukiainen
6
6
  License: MIT License
@@ -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=8cZpPTAuJi8gvacK5e36jaNOK-1irlGBmyXEz62tzhQ,469
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=L-aSQsBQG73kTWvMeneBl99LlxTw4qGI_7aqm5e69jI,28158
11
- cloudnetpy/version.py,sha256=dBQBvia28s9CqNGuF4W6zJTlNGx7LmXKCYKJuWw6ero,73
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=Q9MeDvo0Z4P1zLL9fh5RPQeLqhY6cR5QtboceRFtrNY,3865
15
- cloudnetpy/categorize/categorize.py,sha256=NbfKky19n1JhZCsy6Fu4QEQTby-rE--gk3Gx3wpCZYw,17006
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=KCa7Zo8uWshWfiEiC4JtvhHCanoqOdRXBOlt-MvK3rU,2675
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=_RM5n6SJLlHESmLcbjpcBpW2qaTnumbtNWAh__Ejwiw,1433
26
- cloudnetpy/categorize/radar.py,sha256=7fBKVZvFjqlREi254O4_KJIGDvK6zDwRsEyUfL2gU0Q,13089
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=L7H86wRcTToJS4AAUPabg9nnyVMeSGvSCBwl5rO6kv8,1673
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=6DdmPu9oKHT_RcVKVAvfReTwcvGf4C0D2QqiXq6HhMM,16958
46
- cloudnetpy/instruments/rpg_reader.py,sha256=iWgk-RlA1hTyREzdzJW9DBaQa44iZEjWwdYqiexb8K8,11358
47
- cloudnetpy/instruments/vaisala.py,sha256=aB43XVPpDZUFsmkbzcBsNNokAANIHCHtJIVvE_wHKv8,14493
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=raTlRowixhmXq76e56U0Byh_8o_odr-v7vmNUl0gaEs,15603
51
- cloudnetpy/instruments/disdrometer/parsivel.py,sha256=iCQK5FauTniT8f3uJtm8bpa9PCjOmwm4jVUvwpKRUiM,19695
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=bJz99EmkwPUxBAsM3PtMOUr_1QztfZKnimJw5KQTrwQ,10129
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.21.dist-info/LICENSE,sha256=wcZF72bdaoG9XugpyE95Juo7lBQOwLuTKBOhhtANZMM,1094
111
- cloudnetpy-1.55.21.dist-info/METADATA,sha256=Z4CTwNSIDWRrpKdvtY_zOMGTkrnFbgBAurB2p8pURMg,5869
112
- cloudnetpy-1.55.21.dist-info/WHEEL,sha256=Xo9-1PvkuimrydujYJAjF7pCkriuXBpUPEjma1nZyJ0,92
113
- cloudnetpy-1.55.21.dist-info/top_level.txt,sha256=ibSPWRr6ojS1i11rtBFz2_gkIe68mggj7aeswYfaOo0,16
114
- cloudnetpy-1.55.21.dist-info/RECORD,,
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,,