cloudnetpy 1.49.9__py3-none-any.whl → 1.87.3__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.
Files changed (116) hide show
  1. cloudnetpy/categorize/__init__.py +1 -2
  2. cloudnetpy/categorize/atmos_utils.py +297 -67
  3. cloudnetpy/categorize/attenuation.py +31 -0
  4. cloudnetpy/categorize/attenuations/__init__.py +37 -0
  5. cloudnetpy/categorize/attenuations/gas_attenuation.py +30 -0
  6. cloudnetpy/categorize/attenuations/liquid_attenuation.py +84 -0
  7. cloudnetpy/categorize/attenuations/melting_attenuation.py +78 -0
  8. cloudnetpy/categorize/attenuations/rain_attenuation.py +84 -0
  9. cloudnetpy/categorize/categorize.py +332 -156
  10. cloudnetpy/categorize/classify.py +127 -125
  11. cloudnetpy/categorize/containers.py +107 -76
  12. cloudnetpy/categorize/disdrometer.py +40 -0
  13. cloudnetpy/categorize/droplet.py +23 -21
  14. cloudnetpy/categorize/falling.py +53 -24
  15. cloudnetpy/categorize/freezing.py +25 -12
  16. cloudnetpy/categorize/insects.py +35 -23
  17. cloudnetpy/categorize/itu.py +243 -0
  18. cloudnetpy/categorize/lidar.py +36 -41
  19. cloudnetpy/categorize/melting.py +34 -26
  20. cloudnetpy/categorize/model.py +84 -37
  21. cloudnetpy/categorize/mwr.py +18 -14
  22. cloudnetpy/categorize/radar.py +215 -102
  23. cloudnetpy/cli.py +578 -0
  24. cloudnetpy/cloudnetarray.py +43 -89
  25. cloudnetpy/concat_lib.py +218 -78
  26. cloudnetpy/constants.py +28 -10
  27. cloudnetpy/datasource.py +61 -86
  28. cloudnetpy/exceptions.py +49 -20
  29. cloudnetpy/instruments/__init__.py +5 -0
  30. cloudnetpy/instruments/basta.py +29 -12
  31. cloudnetpy/instruments/bowtie.py +135 -0
  32. cloudnetpy/instruments/ceilo.py +138 -115
  33. cloudnetpy/instruments/ceilometer.py +164 -80
  34. cloudnetpy/instruments/cl61d.py +21 -5
  35. cloudnetpy/instruments/cloudnet_instrument.py +74 -36
  36. cloudnetpy/instruments/copernicus.py +108 -30
  37. cloudnetpy/instruments/da10.py +54 -0
  38. cloudnetpy/instruments/disdrometer/common.py +126 -223
  39. cloudnetpy/instruments/disdrometer/parsivel.py +453 -94
  40. cloudnetpy/instruments/disdrometer/thies.py +254 -87
  41. cloudnetpy/instruments/fd12p.py +201 -0
  42. cloudnetpy/instruments/galileo.py +65 -23
  43. cloudnetpy/instruments/hatpro.py +123 -49
  44. cloudnetpy/instruments/instruments.py +113 -1
  45. cloudnetpy/instruments/lufft.py +39 -17
  46. cloudnetpy/instruments/mira.py +268 -61
  47. cloudnetpy/instruments/mrr.py +187 -0
  48. cloudnetpy/instruments/nc_lidar.py +19 -8
  49. cloudnetpy/instruments/nc_radar.py +109 -55
  50. cloudnetpy/instruments/pollyxt.py +135 -51
  51. cloudnetpy/instruments/radiometrics.py +313 -59
  52. cloudnetpy/instruments/rain_e_h3.py +171 -0
  53. cloudnetpy/instruments/rpg.py +321 -189
  54. cloudnetpy/instruments/rpg_reader.py +74 -40
  55. cloudnetpy/instruments/toa5.py +49 -0
  56. cloudnetpy/instruments/vaisala.py +95 -343
  57. cloudnetpy/instruments/weather_station.py +774 -105
  58. cloudnetpy/metadata.py +90 -19
  59. cloudnetpy/model_evaluation/file_handler.py +55 -52
  60. cloudnetpy/model_evaluation/metadata.py +46 -20
  61. cloudnetpy/model_evaluation/model_metadata.py +1 -1
  62. cloudnetpy/model_evaluation/plotting/plot_tools.py +32 -37
  63. cloudnetpy/model_evaluation/plotting/plotting.py +327 -117
  64. cloudnetpy/model_evaluation/products/advance_methods.py +92 -83
  65. cloudnetpy/model_evaluation/products/grid_methods.py +88 -63
  66. cloudnetpy/model_evaluation/products/model_products.py +43 -35
  67. cloudnetpy/model_evaluation/products/observation_products.py +41 -35
  68. cloudnetpy/model_evaluation/products/product_resampling.py +17 -7
  69. cloudnetpy/model_evaluation/products/tools.py +29 -20
  70. cloudnetpy/model_evaluation/statistics/statistical_methods.py +30 -20
  71. cloudnetpy/model_evaluation/tests/e2e/conftest.py +3 -3
  72. cloudnetpy/model_evaluation/tests/e2e/process_cf/main.py +9 -5
  73. cloudnetpy/model_evaluation/tests/e2e/process_cf/tests.py +15 -14
  74. cloudnetpy/model_evaluation/tests/e2e/process_iwc/main.py +9 -5
  75. cloudnetpy/model_evaluation/tests/e2e/process_iwc/tests.py +15 -14
  76. cloudnetpy/model_evaluation/tests/e2e/process_lwc/main.py +9 -5
  77. cloudnetpy/model_evaluation/tests/e2e/process_lwc/tests.py +15 -14
  78. cloudnetpy/model_evaluation/tests/unit/conftest.py +42 -41
  79. cloudnetpy/model_evaluation/tests/unit/test_advance_methods.py +41 -48
  80. cloudnetpy/model_evaluation/tests/unit/test_grid_methods.py +216 -194
  81. cloudnetpy/model_evaluation/tests/unit/test_model_products.py +23 -21
  82. cloudnetpy/model_evaluation/tests/unit/test_observation_products.py +37 -38
  83. cloudnetpy/model_evaluation/tests/unit/test_plot_tools.py +43 -40
  84. cloudnetpy/model_evaluation/tests/unit/test_plotting.py +30 -36
  85. cloudnetpy/model_evaluation/tests/unit/test_statistical_methods.py +68 -31
  86. cloudnetpy/model_evaluation/tests/unit/test_tools.py +33 -26
  87. cloudnetpy/model_evaluation/utils.py +2 -1
  88. cloudnetpy/output.py +170 -111
  89. cloudnetpy/plotting/__init__.py +2 -1
  90. cloudnetpy/plotting/plot_meta.py +562 -822
  91. cloudnetpy/plotting/plotting.py +1142 -704
  92. cloudnetpy/products/__init__.py +1 -0
  93. cloudnetpy/products/classification.py +370 -88
  94. cloudnetpy/products/der.py +85 -55
  95. cloudnetpy/products/drizzle.py +77 -34
  96. cloudnetpy/products/drizzle_error.py +15 -11
  97. cloudnetpy/products/drizzle_tools.py +79 -59
  98. cloudnetpy/products/epsilon.py +211 -0
  99. cloudnetpy/products/ier.py +27 -50
  100. cloudnetpy/products/iwc.py +55 -48
  101. cloudnetpy/products/lwc.py +96 -70
  102. cloudnetpy/products/mwr_tools.py +186 -0
  103. cloudnetpy/products/product_tools.py +170 -128
  104. cloudnetpy/utils.py +455 -240
  105. cloudnetpy/version.py +2 -2
  106. {cloudnetpy-1.49.9.dist-info → cloudnetpy-1.87.3.dist-info}/METADATA +44 -40
  107. cloudnetpy-1.87.3.dist-info/RECORD +127 -0
  108. {cloudnetpy-1.49.9.dist-info → cloudnetpy-1.87.3.dist-info}/WHEEL +1 -1
  109. cloudnetpy-1.87.3.dist-info/entry_points.txt +2 -0
  110. docs/source/conf.py +2 -2
  111. cloudnetpy/categorize/atmos.py +0 -361
  112. cloudnetpy/products/mwr_multi.py +0 -68
  113. cloudnetpy/products/mwr_single.py +0 -75
  114. cloudnetpy-1.49.9.dist-info/RECORD +0 -112
  115. {cloudnetpy-1.49.9.dist-info → cloudnetpy-1.87.3.dist-info/licenses}/LICENSE +0 -0
  116. {cloudnetpy-1.49.9.dist-info → cloudnetpy-1.87.3.dist-info}/top_level.txt +0 -0
@@ -1,10 +1,20 @@
1
1
  """Model module, containing the :class:`Model` class."""
2
+
3
+ import os.path
4
+ from os import PathLike
5
+
2
6
  import numpy as np
7
+ import numpy.typing as npt
3
8
  from numpy import ma
4
9
  from scipy.interpolate import interp1d
5
10
 
6
11
  from cloudnetpy import utils
7
12
  from cloudnetpy.categorize import atmos_utils
13
+ from cloudnetpy.categorize.itu import (
14
+ calc_gas_specific_attenuation,
15
+ calc_liquid_specific_attenuation,
16
+ calc_saturation_vapor_pressure,
17
+ )
8
18
  from cloudnetpy.cloudnetarray import CloudnetArray
9
19
  from cloudnetpy.datasource import DataSource
10
20
  from cloudnetpy.exceptions import ModelDataError
@@ -16,9 +26,10 @@ class Model(DataSource):
16
26
  Args:
17
27
  model_file: File name of the NWP model file.
18
28
  alt_site: Altitude of the site above mean sea level (m).
29
+ options: Dictionary containing optional parameters.
19
30
 
20
31
  Attributes:
21
- type (str): Model type, e.g. 'gdas1' or 'ecwmf'.
32
+ source_type (str): Model type, e.g. 'gdas1' or 'ecwmf'.
22
33
  model_heights (ndarray): 2-D array of model heights (one for each time
23
34
  step).
24
35
  mean_height (ndarray): Mean of *model_heights*.
@@ -33,35 +44,37 @@ class Model(DataSource):
33
44
  "temperature",
34
45
  "pressure",
35
46
  "rh",
36
- "gas_atten",
47
+ "q",
48
+ )
49
+ fields_sparse = (*fields_dense, "uwind", "vwind")
50
+ fields_atten = (
37
51
  "specific_gas_atten",
38
52
  "specific_saturated_gas_atten",
39
53
  "specific_liquid_atten",
40
54
  )
41
- fields_sparse = fields_dense + ("q", "uwind", "vwind")
42
55
 
43
- def __init__(self, model_file: str, alt_site: float):
56
+ def __init__(
57
+ self, model_file: str | PathLike, alt_site: float, options: dict | None = None
58
+ ) -> None:
44
59
  super().__init__(model_file)
45
- self.type = _find_model_type(model_file)
60
+ self.options = options
61
+ self.source_type = _find_model_type(model_file)
46
62
  self.model_heights = self._get_model_heights(alt_site)
47
63
  self.mean_height = _calc_mean_height(self.model_heights)
48
- self.height: np.ndarray
64
+ self.height: npt.NDArray
49
65
  self.data_sparse: dict = {}
50
66
  self.data_dense: dict = {}
67
+ self.alt_site = alt_site
51
68
  self._append_grid()
52
69
 
53
- def interpolate_to_common_height(self, wl_band: int) -> None:
54
- """Interpolates model variables to common height grid.
55
-
56
- Args:
57
- wl_band: Integer denoting the approximate wavelength band of the
58
- cloud radar (0 = ~35.5 GHz, 1 = ~94 GHz).
59
-
60
- """
70
+ def interpolate_to_common_height(self) -> None:
71
+ """Interpolates model variables to common height grid."""
61
72
 
62
73
  def _interpolate_variable(data_in: ma.MaskedArray) -> CloudnetArray:
63
74
  datai = ma.zeros((len(self.time), len(self.mean_height)))
64
- for ind, (alt, prof) in enumerate(zip(self.model_heights, data_in)):
75
+ for ind, (alt, prof) in enumerate(
76
+ zip(self.model_heights, data_in, strict=True),
77
+ ):
65
78
  if prof.mask.all():
66
79
  datai[ind, :] = ma.masked
67
80
  else:
@@ -73,34 +86,47 @@ class Model(DataSource):
73
86
  variable = self.dataset.variables[key]
74
87
  data = variable[:]
75
88
  units = variable.units
76
- if "atten" in key:
77
- data = data[wl_band, :, :]
78
89
  self.data_sparse[key] = _interpolate_variable(data)
79
90
 
80
91
  def interpolate_to_grid(
81
- self, time_grid: np.ndarray, height_grid: np.ndarray
82
- ) -> None:
92
+ self,
93
+ time_grid: npt.NDArray,
94
+ height_grid: npt.NDArray,
95
+ ) -> list:
83
96
  """Interpolates model variables to Cloudnet's dense time / height grid.
84
97
 
85
98
  Args:
86
99
  time_grid: The target time array (fraction hour).
87
100
  height_grid: The target height array (m).
88
101
 
102
+ Returns:
103
+ Indices fully masked profiles.
104
+
89
105
  """
90
- for key in self.fields_dense:
106
+ half_height = height_grid - np.diff(height_grid, prepend=self.alt_site) / 2
107
+ for key in self.fields_dense + self.fields_atten:
91
108
  array = self.data_sparse[key][:]
92
109
  valid_profiles = _find_number_of_valid_profiles(array)
93
110
  if valid_profiles < 2:
94
111
  raise ModelDataError
95
112
  self.data_dense[key] = utils.interpolate_2d_mask(
96
- self.time, self.mean_height, array, time_grid, height_grid
113
+ self.time,
114
+ self.mean_height,
115
+ array,
116
+ time_grid,
117
+ half_height if "atten" in key else height_grid,
97
118
  )
98
119
  self.height = height_grid
120
+ return utils.find_masked_profiles_indices(self.data_dense["temperature"])
99
121
 
100
122
  def calc_wet_bulb(self) -> None:
101
123
  """Calculates wet-bulb temperature in dense grid."""
102
124
  wet_bulb_temp = atmos_utils.calc_wet_bulb_temperature(self.data_dense)
125
+ offset = (self.options or {}).get("temperature_offset", 0)
126
+ wet_bulb_temp += offset
103
127
  self.append_data(wet_bulb_temp, "Tw", units="K")
128
+ if offset:
129
+ self.data["Tw"].temperature_correction_applied = offset
104
130
 
105
131
  def screen_sparse_fields(self) -> None:
106
132
  """Removes model fields that we don't want to write in the output."""
@@ -111,29 +137,50 @@ class Model(DataSource):
111
137
  self.append_data(np.array(self.time), "model_time")
112
138
  self.append_data(self.mean_height, "model_height")
113
139
 
114
- def _get_model_heights(self, alt_site: float) -> np.ndarray:
140
+ def _get_model_heights(self, alt_site: float) -> npt.NDArray:
115
141
  """Returns model heights for each time step."""
116
- model_heights = self.dataset.variables["height"]
117
- return self.km2m(model_heights) + alt_site
118
-
119
-
120
- def _calc_mean_height(model_heights: np.ndarray) -> np.ndarray:
142
+ try:
143
+ model_heights = self.dataset.variables["height"]
144
+ except KeyError as err:
145
+ msg = "No 'height' variable in the model file."
146
+ raise ModelDataError(msg) from err
147
+ return self.to_m(model_heights) + alt_site
148
+
149
+ def calc_attenuations(self, frequency: float) -> None:
150
+ temperature = self.getvar("temperature")
151
+ pressure = self.getvar("pressure")
152
+ specific_humidity = self.getvar("q")
153
+
154
+ self.data_sparse["specific_liquid_atten"] = calc_liquid_specific_attenuation(
155
+ temperature, frequency
156
+ )
157
+ vp = atmos_utils.calc_vapor_pressure(pressure, specific_humidity)
158
+ svp = calc_saturation_vapor_pressure(temperature)
159
+ self.data_sparse["specific_gas_atten"] = calc_gas_specific_attenuation(
160
+ pressure, vp, temperature, frequency
161
+ )
162
+ self.data_sparse["specific_saturated_gas_atten"] = (
163
+ calc_gas_specific_attenuation(pressure, svp, temperature, frequency)
164
+ )
165
+
166
+
167
+ def _calc_mean_height(model_heights: npt.NDArray) -> npt.NDArray:
121
168
  mean_height = ma.mean(model_heights, axis=0)
122
169
  return np.array(mean_height)
123
170
 
124
171
 
125
- def _find_model_type(file_name: str) -> str:
172
+ def _find_model_type(file_name: str | PathLike) -> str:
126
173
  """Finds model type from the model filename."""
127
- possible_keys = utils.fetch_cloudnet_model_types()
174
+ possible_keys = ("gdas1", "icon", "ecmwf", "harmonie", "era5", "arpege")
175
+ basename = os.path.basename(file_name)
128
176
  for key in possible_keys:
129
- if key in file_name:
177
+ if key in basename:
130
178
  return key
131
- raise ValueError("Unknown model type")
179
+ msg = f"Unknown model type: {file_name}"
180
+ raise ValueError(msg)
132
181
 
133
182
 
134
- def _find_number_of_valid_profiles(array: np.ndarray) -> int:
135
- n_good = 0
136
- for row in array:
137
- if not hasattr(row, "mask") or np.sum(row.mask.astype(int)) == 0:
138
- n_good += 1
139
- return n_good
183
+ def _find_number_of_valid_profiles(array: npt.NDArray) -> int:
184
+ mask = ma.getmaskarray(array)
185
+ all_masked_profiles = np.all(mask, axis=1)
186
+ return int(np.count_nonzero(~all_masked_profiles))
@@ -1,8 +1,14 @@
1
1
  """Mwr module, containing the :class:`Mwr` class."""
2
- import numpy as np
2
+
3
+ from os import PathLike
4
+
5
+ import numpy.ma as ma
6
+ import numpy.typing as npt
3
7
 
4
8
  from cloudnetpy import utils
9
+ from cloudnetpy.constants import G_TO_KG
5
10
  from cloudnetpy.datasource import DataSource
11
+ from cloudnetpy.utils import interpolate_1d
6
12
 
7
13
 
8
14
  class Mwr(DataSource):
@@ -13,33 +19,31 @@ class Mwr(DataSource):
13
19
 
14
20
  """
15
21
 
16
- def __init__(self, full_path: str):
22
+ def __init__(self, full_path: str | PathLike) -> None:
17
23
  super().__init__(full_path)
18
24
  self._init_lwp_data()
19
25
  self._init_lwp_error()
20
26
 
21
- def rebin_to_grid(self, time_grid: np.ndarray) -> None:
22
- """Approximates lwp and its error in a grid using mean.
23
-
24
- Args:
25
- time_grid: 1D target time grid.
26
-
27
- """
28
- for array in self.data.values():
29
- 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
+ )
30
32
 
31
33
  def _init_lwp_data(self) -> None:
32
34
  lwp = self.dataset.variables["lwp"][:]
35
+ if "lwp_quality_flag" in self.dataset.variables:
36
+ quality_flag = self.dataset.variables["lwp_quality_flag"][:]
37
+ lwp[quality_flag != 0] = ma.masked
33
38
  self.append_data(lwp, "lwp")
34
39
 
35
40
  def _init_lwp_error(self) -> None:
36
41
  random_error, bias = 0.25, 20
37
- g2kg = 1e-3
38
- lwp_error = utils.l2norm(self.data["lwp"][:] * random_error, bias * g2kg)
42
+ lwp_error = utils.l2norm(self.data["lwp"][:] * random_error, bias * G_TO_KG)
39
43
  self.append_data(lwp_error, "lwp_error", units="kg m-2")
40
44
  self.data["lwp_error"].comment = (
41
45
  "This variable is a rough estimate of the one-standard-deviation\n"
42
46
  f"error in liquid water path, calculated as a combination of\n"
43
- f"a {bias} g m-2 linear error and a {round(random_error*100)} %\n"
47
+ f"a {bias} g m-2 linear error and a {round(random_error * 100)} %\n"
44
48
  "fractional error."
45
49
  )