cloudnetpy 1.83.1__py3-none-any.whl → 1.84.0__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.
@@ -93,7 +93,7 @@ def generate_categorize(
93
93
  if data.disdrometer is not None:
94
94
  data.disdrometer.interpolate_to_grid(time)
95
95
  if data.mwr is not None:
96
- data.mwr.rebin_to_grid(time)
96
+ data.mwr.interpolate_to_grid(time)
97
97
  data.model.calc_attenuations(data.radar.radar_frequency)
98
98
  data.model.interpolate_to_common_height()
99
99
  model_gap_ind = data.model.interpolate_to_grid(time, height)
@@ -1,16 +1,12 @@
1
1
  """Mwr module, containing the :class:`Mwr` class."""
2
2
 
3
- import logging
4
3
  from os import PathLike
5
4
 
6
- import numpy as np
7
5
  import numpy.typing as npt
8
- from numpy import ma
9
- from scipy.interpolate import interp1d
10
6
 
11
- from cloudnetpy.categorize.lidar import get_gap_ind
12
7
  from cloudnetpy.datasource import DataSource
13
8
  from cloudnetpy.exceptions import DisdrometerDataError
9
+ from cloudnetpy.utils import interpolate_1d
14
10
 
15
11
 
16
12
  class Disdrometer(DataSource):
@@ -28,7 +24,9 @@ class Disdrometer(DataSource):
28
24
 
29
25
  def interpolate_to_grid(self, time_grid: npt.NDArray) -> None:
30
26
  for key, array in self.data.items():
31
- self.data[key].data = self._interpolate(array.data, time_grid)
27
+ self.data[key].data = interpolate_1d(
28
+ self.time, array.data, time_grid, max_time=1
29
+ )
32
30
 
33
31
  def _init_rainfall_rate(self) -> None:
34
32
  keys = ("rainfall_rate", "n_particles")
@@ -37,24 +35,3 @@ class Disdrometer(DataSource):
37
35
  msg = f"variable {key} is missing"
38
36
  raise DisdrometerDataError(msg)
39
37
  self.append_data(self.dataset.variables[key][:], key)
40
-
41
- def _interpolate(self, y: ma.MaskedArray, x_new: npt.NDArray) -> npt.NDArray:
42
- time = self.time
43
- mask = ma.getmask(y)
44
- if mask is not ma.nomask:
45
- if np.all(mask):
46
- return ma.masked_all(x_new.shape)
47
- not_masked = ~mask
48
- y = y[not_masked]
49
- time = time[not_masked]
50
- fun = interp1d(time, y, fill_value="extrapolate")
51
- interpolated_array = ma.array(fun(x_new))
52
- max_time = 1 / 60 # min -> fraction hour
53
- mask_ind = get_gap_ind(time, x_new, max_time)
54
-
55
- if len(mask_ind) > 0:
56
- msg = f"Unable to interpolate disdrometer for {len(mask_ind)} time steps"
57
- logging.warning(msg)
58
- interpolated_array[mask_ind] = ma.masked
59
-
60
- return interpolated_array
@@ -4,12 +4,11 @@ import logging
4
4
  from os import PathLike
5
5
  from typing import Literal
6
6
 
7
- import numpy as np
8
7
  import numpy.typing as npt
9
8
  from numpy import ma
10
9
 
11
10
  from cloudnetpy.datasource import DataSource
12
- from cloudnetpy.utils import interpolate_2d_nearest
11
+ from cloudnetpy.utils import get_gap_ind, interpolate_2d_nearest
13
12
 
14
13
 
15
14
  class Lidar(DataSource):
@@ -70,13 +69,3 @@ class Lidar(DataSource):
70
69
  self.append_data(float(self.getvar("wavelength")), "lidar_wavelength")
71
70
  self.append_data(0.5, "beta_error")
72
71
  self.append_data(3.0, "beta_bias")
73
-
74
-
75
- def get_gap_ind(
76
- grid: npt.NDArray, new_grid: npt.NDArray, threshold: float
77
- ) -> list[int]:
78
- return [
79
- ind
80
- for ind, value in enumerate(new_grid)
81
- if np.min(np.abs(grid - value)) > threshold
82
- ]
@@ -8,6 +8,7 @@ import numpy.typing as npt
8
8
  from cloudnetpy import utils
9
9
  from cloudnetpy.constants import G_TO_KG
10
10
  from cloudnetpy.datasource import DataSource
11
+ from cloudnetpy.utils import interpolate_1d
11
12
 
12
13
 
13
14
  class Mwr(DataSource):
@@ -23,15 +24,11 @@ class Mwr(DataSource):
23
24
  self._init_lwp_data()
24
25
  self._init_lwp_error()
25
26
 
26
- def rebin_to_grid(self, time_grid: npt.NDArray) -> None:
27
- """Approximates lwp and its error in a grid using mean.
28
-
29
- Args:
30
- time_grid: 1D target time grid.
31
-
32
- """
33
- for array in self.data.values():
34
- array.rebin_data(self.time, time_grid)
27
+ def interpolate_to_grid(self, time_grid: npt.NDArray, max_time: float = 1) -> None:
28
+ for key, array in self.data.items():
29
+ self.data[key].data = interpolate_1d(
30
+ self.time, array.data, time_grid, max_time=max_time
31
+ )
35
32
 
36
33
  def _init_lwp_data(self) -> None:
37
34
  lwp = self.dataset.variables["lwp"][:]
cloudnetpy/utils.py CHANGED
@@ -3,6 +3,7 @@
3
3
  import base64
4
4
  import datetime
5
5
  import hashlib
6
+ import logging
6
7
  import os
7
8
  import re
8
9
  import textwrap
@@ -17,7 +18,12 @@ import numpy as np
17
18
  import numpy.typing as npt
18
19
  from numpy import ma
19
20
  from scipy import ndimage, stats
20
- from scipy.interpolate import RectBivariateSpline, RegularGridInterpolator, griddata
21
+ from scipy.interpolate import (
22
+ RectBivariateSpline,
23
+ RegularGridInterpolator,
24
+ griddata,
25
+ interp1d,
26
+ )
21
27
 
22
28
  from cloudnetpy.cloudnetarray import CloudnetArray
23
29
  from cloudnetpy.constants import SEC_IN_DAY, SEC_IN_HOUR, SEC_IN_MINUTE
@@ -413,6 +419,55 @@ def interpolate_2d_nearest(
413
419
  return fun((xx, yy)).T
414
420
 
415
421
 
422
+ def interpolate_1d(
423
+ time: npt.NDArray, y: ma.MaskedArray, time_new: npt.NDArray, max_time: float
424
+ ) -> npt.NDArray:
425
+ """1D linear interpolation preserving the mask.
426
+
427
+ Args:
428
+ time: 1D array in fraction hour.
429
+ y: 1D masked array, data values.
430
+ time_new: 1D array, new time coordinates.
431
+ max_time: Maximum allowed gap in minutes. Values outside this gap will
432
+ be masked.
433
+ """
434
+ if np.max(time) > 24 or np.min(time) < 0:
435
+ msg = "Time vector must be in fraction hours between 0 and 24"
436
+ raise ValueError(msg)
437
+ if ma.is_masked(y):
438
+ if y.mask.all():
439
+ return ma.masked_all(time_new.shape)
440
+ time = time[~y.mask]
441
+ y = y[~y.mask]
442
+ fun = interp1d(time, y, fill_value=(y[0], y[-1]), bounds_error=False)
443
+ interpolated = ma.array(fun(time_new))
444
+ bad_idx = get_gap_ind(time, time_new, max_time / 60)
445
+
446
+ if len(bad_idx) > 0:
447
+ msg = f"Unable to interpolate for {len(bad_idx)} time steps"
448
+ logging.warning(msg)
449
+ interpolated[bad_idx] = ma.masked
450
+
451
+ return interpolated
452
+
453
+
454
+ def get_gap_ind(
455
+ grid: npt.NDArray, new_grid: npt.NDArray, threshold: float
456
+ ) -> list[int]:
457
+ """Finds indices in new_grid that are too far from grid."""
458
+ if grid.size == 0:
459
+ return list(range(len(new_grid)))
460
+ idxs = np.searchsorted(grid, new_grid)
461
+ left_dist = np.where(idxs > 0, np.abs(new_grid - grid[idxs - 1]), np.inf)
462
+ right_dist = np.where(
463
+ idxs < len(grid),
464
+ np.abs(new_grid - grid[np.clip(idxs, 0, len(grid) - 1)]),
465
+ np.inf,
466
+ )
467
+ nearest = np.minimum(left_dist, right_dist)
468
+ return np.where(nearest > threshold)[0].tolist()
469
+
470
+
416
471
  def calc_relative_error(reference: npt.NDArray, array: npt.NDArray) -> npt.NDArray:
417
472
  """Calculates relative error (%)."""
418
473
  return ((array - reference) / reference) * 100
cloudnetpy/version.py CHANGED
@@ -1,4 +1,4 @@
1
1
  MAJOR = 1
2
- MINOR = 83
3
- PATCH = 1
2
+ MINOR = 84
3
+ PATCH = 0
4
4
  __version__ = f"{MAJOR}.{MINOR}.{PATCH}"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cloudnetpy
3
- Version: 1.83.1
3
+ Version: 1.84.0
4
4
  Summary: Python package for Cloudnet processing
5
5
  Author: Simo Tukiainen
6
6
  License: MIT License
@@ -8,24 +8,24 @@ cloudnetpy/exceptions.py,sha256=ZB3aUwjVRznR0CcZ5sZHrB0yz13URDf52Ksv7G7C7EA,1817
8
8
  cloudnetpy/metadata.py,sha256=CFpXmdEkVPzvLPv2xHIR-aMMQ-TR26KfESYw-98j7sk,7213
9
9
  cloudnetpy/output.py,sha256=0bybnILsgKHWIuw2GYkqTz2iMCJDZLUN25IQ9o_v3Cg,14968
10
10
  cloudnetpy/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
11
- cloudnetpy/utils.py,sha256=Qv60_vxknB3f2S3EFtyoD2CBY3N6mgDRObNp2u1oYUc,31806
12
- cloudnetpy/version.py,sha256=d6L38ljX0Yx-3zD1dzNjm0VBGy_-ok1lac4Hom2TSAY,72
11
+ cloudnetpy/utils.py,sha256=O5kEXMt03fFKabPxRiCfuahVfTqa4fWWZ11orQnQeXU,33530
12
+ cloudnetpy/version.py,sha256=muQUQb3xKBF1loNyy50A6CariaWU5ZNM_Ff1hDxBTqA,72
13
13
  cloudnetpy/categorize/__init__.py,sha256=gtvzWr0IDRn2oA6yHBvinEhTGTuub-JkrOv93lBsgrE,61
14
14
  cloudnetpy/categorize/atmos_utils.py,sha256=uWc9TABVYPI0sn4H5Az9Jf6NVRaWyEKIi17f0pAJQxE,10679
15
15
  cloudnetpy/categorize/attenuation.py,sha256=Y_-fzmQTltWTqIZTulJhovC7a6ifpMcaAazDJcnMIOc,990
16
- cloudnetpy/categorize/categorize.py,sha256=NtSnLblVBgUobCJkP5SscsQ0UJNoWjJQKuyl2ZyytV0,23221
16
+ cloudnetpy/categorize/categorize.py,sha256=UZph73hVCU-UIxB2837oyLFKvTk8fdgLC-krgJDg8zk,23227
17
17
  cloudnetpy/categorize/classify.py,sha256=skA9K6Bxh9mFZ_fM4d78zt09BPDzfHLttXle6mFCMbw,8553
18
18
  cloudnetpy/categorize/containers.py,sha256=PIJwgQos3CxF9BG4hBNLTaZq252FTH0kdgagT31mFmc,5517
19
- cloudnetpy/categorize/disdrometer.py,sha256=XNL8kDtBAB12UmgRZ4ayxuVs8e3ghyLO0Hy5XpRgfMU,1966
19
+ cloudnetpy/categorize/disdrometer.py,sha256=_Hc7EhoxIIsAn7HCrgSB6aPMelaIW6c4lLeMf0NOnVI,1117
20
20
  cloudnetpy/categorize/droplet.py,sha256=wnMN9rHNSMZLXNXuYEd-RAS_8eAIIo2vkE7pp3DSTKs,8725
21
21
  cloudnetpy/categorize/falling.py,sha256=ZscFGFPFz_Nlc7PwjVwFDSVOzMGOuCDVAFj7pLvYctQ,4475
22
22
  cloudnetpy/categorize/freezing.py,sha256=gigqpb4qfeQSlKXkrPUwCbMnMsxl74thJWSRW2iHJOg,3796
23
23
  cloudnetpy/categorize/insects.py,sha256=bAqm4kFRtU16RPttsRLedofPd-yfbALNqz26jKlMNUE,5357
24
24
  cloudnetpy/categorize/itu.py,sha256=ffXK27guyRS4d66VWQ2h4UEGjUIhGjPKbFmj7kh698c,10304
25
- cloudnetpy/categorize/lidar.py,sha256=CQsDEeQYiuQCfCmJQWrqQvCfmciN1NPZ6uRdt89CZLY,2685
25
+ cloudnetpy/categorize/lidar.py,sha256=CkIbsMFHROYnhIc1e1ci61-POqeL8AOf6RCZicpNBVc,2452
26
26
  cloudnetpy/categorize/melting.py,sha256=vhc6zq3L4gp7oEPHMnQlT2m6YBE5-CS5gdTNA7gVHRg,6329
27
27
  cloudnetpy/categorize/model.py,sha256=iFakrXy3npbg4qUrpUGEBEdwBnmlWsMgogPCtfWl7sw,6805
28
- cloudnetpy/categorize/mwr.py,sha256=kzSivQuKrsqmFInDLlSM1R2wAG5j-tQebOi_1IwUW_I,1690
28
+ cloudnetpy/categorize/mwr.py,sha256=QKc6f_h0mCVjzSa4BrwTtVAPx0Tn1VJ4jqGmIBke5II,1710
29
29
  cloudnetpy/categorize/radar.py,sha256=2mTDa9BLxQeaORm-YPQ1lJyjAKew6NYzjtUvjpIvBYU,16044
30
30
  cloudnetpy/categorize/attenuations/__init__.py,sha256=kIyQEZ6VVO6jJOAndrt7jNU15pm0Cavh5GnDjFmIG1M,1040
31
31
  cloudnetpy/categorize/attenuations/gas_attenuation.py,sha256=emr-RCxQT0i2N8k6eBNhRsmsCBPHJzQsWJfjC4fVSTo,975
@@ -117,10 +117,10 @@ cloudnetpy/products/lwc.py,sha256=xsNiiG6dGKIkWaFk0xWTabc1bZ4ULf6SqcqHs7itAUk,19
117
117
  cloudnetpy/products/mie_lu_tables.nc,sha256=It4fYpqJXlqOgL8jeZ-PxGzP08PMrELIDVe55y9ob58,16637951
118
118
  cloudnetpy/products/mwr_tools.py,sha256=MMWnp68U7bv157-CPB2VeTQvaR6zl7sexbBT_kJ_pn8,6734
119
119
  cloudnetpy/products/product_tools.py,sha256=eyqIw_0KhlpmmYQE69RpGdRIAOW7JVPlEgkTBp2kdps,11302
120
- cloudnetpy-1.83.1.dist-info/licenses/LICENSE,sha256=wcZF72bdaoG9XugpyE95Juo7lBQOwLuTKBOhhtANZMM,1094
120
+ cloudnetpy-1.84.0.dist-info/licenses/LICENSE,sha256=wcZF72bdaoG9XugpyE95Juo7lBQOwLuTKBOhhtANZMM,1094
121
121
  docs/source/conf.py,sha256=IKiFWw6xhUd8NrCg0q7l596Ck1d61XWeVjIFHVSG9Og,1490
122
- cloudnetpy-1.83.1.dist-info/METADATA,sha256=5h8hN5FEQoYpQjog5r1LvdsqnJ2MZDF9yMvzGXY2Yw4,5836
123
- cloudnetpy-1.83.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
124
- cloudnetpy-1.83.1.dist-info/entry_points.txt,sha256=HhY7LwCFk4qFgDlXx_Fy983ZTd831WlhtdPIzV-Y3dY,51
125
- cloudnetpy-1.83.1.dist-info/top_level.txt,sha256=ibSPWRr6ojS1i11rtBFz2_gkIe68mggj7aeswYfaOo0,16
126
- cloudnetpy-1.83.1.dist-info/RECORD,,
122
+ cloudnetpy-1.84.0.dist-info/METADATA,sha256=b1T8fzF9ooTKaIjpb03WolZZKzdvlBxNP8IyO38aD7U,5836
123
+ cloudnetpy-1.84.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
124
+ cloudnetpy-1.84.0.dist-info/entry_points.txt,sha256=HhY7LwCFk4qFgDlXx_Fy983ZTd831WlhtdPIzV-Y3dY,51
125
+ cloudnetpy-1.84.0.dist-info/top_level.txt,sha256=ibSPWRr6ojS1i11rtBFz2_gkIe68mggj7aeswYfaOo0,16
126
+ cloudnetpy-1.84.0.dist-info/RECORD,,