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,65 +1,87 @@
1
1
  """General helper classes and functions for all products."""
2
- from collections import namedtuple
2
+
3
+ from dataclasses import dataclass
4
+ from os import PathLike
5
+ from typing import NamedTuple
3
6
 
4
7
  import netCDF4
5
8
  import numpy as np
9
+ import numpy.typing as npt
6
10
  from numpy import ma
11
+ from numpy.typing import NDArray
7
12
 
8
13
  from cloudnetpy import constants, utils
9
14
  from cloudnetpy.categorize import atmos_utils
10
15
  from cloudnetpy.datasource import DataSource
11
16
 
12
- IceCoefficients = namedtuple("IceCoefficients", "K2liquid0 ZT T Z c")
13
17
 
18
+ class IceCoefficients(NamedTuple):
19
+ """Coefficients for ice effective radius retrieval."""
14
20
 
15
- class CategorizeBits:
16
- """Class holding information about category and quality bits.
21
+ K2liquid0: float
22
+ ZT: float
23
+ T: float
24
+ Z: float
25
+ c: float
17
26
 
18
- Args:
19
- categorize_file (str): Categorize file name.
20
27
 
21
- Attributes:
22
- category_bits (dict): Dictionary containing boolean fields for `droplet`,
23
- `falling`, `cold`, `melting`, `aerosol`, `insect`.
28
+ @dataclass
29
+ class CategoryBits:
30
+ droplet: NDArray[np.bool_]
31
+ falling: NDArray[np.bool_]
32
+ freezing: NDArray[np.bool_]
33
+ melting: NDArray[np.bool_]
34
+ aerosol: NDArray[np.bool_]
35
+ insect: NDArray[np.bool_]
24
36
 
25
- quality_bits (dict): Dictionary containing boolean fields for `radar`,
26
- `lidar`, `clutter`, `molecular`, `attenuated` and `corrected`.
27
37
 
28
- """
38
+ @dataclass
39
+ class QualityBits:
40
+ radar: NDArray[np.bool_]
41
+ lidar: NDArray[np.bool_]
42
+ clutter: NDArray[np.bool_]
43
+ molecular: NDArray[np.bool_]
44
+ attenuated_liquid: NDArray[np.bool_]
45
+ corrected_liquid: NDArray[np.bool_]
46
+ attenuated_rain: NDArray[np.bool_]
47
+ corrected_rain: NDArray[np.bool_]
48
+ attenuated_melting: NDArray[np.bool_]
49
+ corrected_melting: NDArray[np.bool_]
29
50
 
30
- category_keys = (
31
- "droplet",
32
- "falling",
33
- "cold",
34
- "melting",
35
- "aerosol",
36
- "insect",
37
- )
38
-
39
- quality_keys = (
40
- "radar",
41
- "lidar",
42
- "clutter",
43
- "molecular",
44
- "attenuated",
45
- "corrected",
46
- )
47
-
48
- def __init__(self, categorize_file: str):
51
+
52
+ class CategorizeBits:
53
+ def __init__(self, categorize_file: str | PathLike) -> None:
49
54
  self._categorize_file = categorize_file
50
- self.category_bits = self._read_bits("category")
51
- self.quality_bits = self._read_bits("quality")
55
+ self.category_bits = self._read_category_bits()
56
+ self.quality_bits = self._read_quality_bits()
52
57
 
53
- def _read_bits(self, bit_type: str) -> dict:
54
- """Converts bitfield into dictionary."""
58
+ def _read_category_bits(self) -> CategoryBits:
55
59
  with netCDF4.Dataset(self._categorize_file) as nc:
56
- try:
57
- bitfield = nc.variables[f"{bit_type}_bits"][:]
58
- except KeyError as err:
59
- raise KeyError from err
60
- keys = getattr(CategorizeBits, f"{bit_type}_keys")
61
- bits = {key: utils.isbit(bitfield, i) for i, key in enumerate(keys)}
62
- return bits
60
+ bits = nc.variables["category_bits"][:]
61
+ return CategoryBits(
62
+ droplet=utils.isbit(bits, 0),
63
+ falling=utils.isbit(bits, 1),
64
+ freezing=utils.isbit(bits, 2),
65
+ melting=utils.isbit(bits, 3),
66
+ aerosol=utils.isbit(bits, 4),
67
+ insect=utils.isbit(bits, 5),
68
+ )
69
+
70
+ def _read_quality_bits(self) -> QualityBits:
71
+ with netCDF4.Dataset(self._categorize_file) as nc:
72
+ bits = nc.variables["quality_bits"][:]
73
+ return QualityBits(
74
+ radar=utils.isbit(bits, 0),
75
+ lidar=utils.isbit(bits, 1),
76
+ clutter=utils.isbit(bits, 2),
77
+ molecular=utils.isbit(bits, 3),
78
+ attenuated_liquid=utils.isbit(bits, 4),
79
+ corrected_liquid=utils.isbit(bits, 5),
80
+ attenuated_rain=utils.isbit(bits, 6),
81
+ corrected_rain=utils.isbit(bits, 7),
82
+ attenuated_melting=utils.isbit(bits, 8),
83
+ corrected_melting=utils.isbit(bits, 9),
84
+ )
63
85
 
64
86
 
65
87
  class ProductClassification(CategorizeBits):
@@ -74,7 +96,7 @@ class ProductClassification(CategorizeBits):
74
96
 
75
97
  """
76
98
 
77
- def __init__(self, categorize_file: str):
99
+ def __init__(self, categorize_file: str | PathLike) -> None:
78
100
  super().__init__(categorize_file)
79
101
  self.is_rain = get_is_rain(categorize_file)
80
102
 
@@ -84,79 +106,99 @@ class IceClassification(ProductClassification):
84
106
  Child of ProductClassification().
85
107
  """
86
108
 
87
- def __init__(self, categorize_file: str):
109
+ def __init__(self, categorize_file: str | PathLike) -> None:
88
110
  super().__init__(categorize_file)
111
+ self._is_attenuated = self._find_attenuated()
112
+ self._is_corrected = self._find_corrected()
89
113
  self.is_ice = self._find_ice()
90
114
  self.would_be_ice = self._find_would_be_ice()
91
115
  self.corrected_ice = self._find_corrected_ice()
92
116
  self.uncorrected_ice = self._find_uncorrected_ice()
93
117
  self.ice_above_rain = self._find_ice_above_rain()
94
- self.cold_above_rain = self._find_cold_above_rain()
118
+ self.clear_above_rain = self._find_clear_above_rain()
95
119
 
96
- def _find_ice(self) -> np.ndarray:
120
+ def _find_clear_above_rain(self) -> npt.NDArray:
97
121
  return (
98
- self.category_bits["falling"]
99
- & self.category_bits["cold"]
100
- & ~self.category_bits["melting"]
101
- & ~self.category_bits["insect"]
122
+ utils.transpose(self.is_rain) * ~self.is_ice
123
+ & self.category_bits.freezing
124
+ & ~self.category_bits.melting
102
125
  )
103
126
 
104
- def _find_would_be_ice(self) -> np.ndarray:
105
- warm_falling = (
106
- self.category_bits["falling"]
107
- & ~self.category_bits["cold"]
108
- & ~self.category_bits["insect"]
127
+ def _find_attenuated(self) -> npt.NDArray:
128
+ return (
129
+ self.quality_bits.attenuated_liquid
130
+ | self.quality_bits.attenuated_rain
131
+ | self.quality_bits.attenuated_melting
109
132
  )
110
- return warm_falling | self.category_bits["melting"]
111
133
 
112
- def _find_corrected_ice(self) -> np.ndarray:
134
+ def _find_corrected(self) -> npt.NDArray:
113
135
  return (
114
- self.is_ice
115
- & self.quality_bits["attenuated"]
116
- & self.quality_bits["corrected"]
136
+ self.quality_bits.corrected_liquid
137
+ | self.quality_bits.corrected_rain
138
+ | self.quality_bits.corrected_melting
139
+ )
140
+
141
+ def _find_ice(self) -> npt.NDArray:
142
+ return (
143
+ self.category_bits.falling
144
+ & self.category_bits.freezing
145
+ & ~self.category_bits.melting
146
+ & ~self.category_bits.insect
147
+ )
148
+
149
+ def _find_would_be_ice(self) -> npt.NDArray:
150
+ warm_falling = (
151
+ self.category_bits.falling
152
+ & ~self.category_bits.freezing
153
+ & ~self.category_bits.insect
117
154
  )
155
+ return warm_falling | self.category_bits.melting
118
156
 
119
- def _find_uncorrected_ice(self) -> np.ndarray:
157
+ def _find_corrected_ice(self) -> npt.NDArray:
158
+ return self.is_ice & self._is_attenuated & self._is_corrected
159
+
160
+ def _find_uncorrected_ice(self) -> npt.NDArray:
161
+ uncorrected_melting = (
162
+ self.quality_bits.attenuated_melting & ~self.quality_bits.corrected_melting
163
+ )
164
+ uncorrected_rain = (
165
+ self.quality_bits.attenuated_rain & ~self.quality_bits.corrected_rain
166
+ )
167
+ uncorrected_liquid = (
168
+ self.quality_bits.attenuated_liquid & ~self.quality_bits.corrected_liquid
169
+ )
120
170
  return (
121
171
  self.is_ice
122
- & self.quality_bits["attenuated"]
123
- & ~self.quality_bits["corrected"]
172
+ & self._is_attenuated
173
+ & (uncorrected_melting | uncorrected_rain | uncorrected_liquid)
124
174
  )
125
175
 
126
- def _find_ice_above_rain(self) -> np.ndarray:
176
+ def _find_ice_above_rain(self) -> npt.NDArray:
127
177
  is_rain = utils.transpose(self.is_rain)
128
178
  return (self.is_ice * is_rain) == 1
129
179
 
130
- def _find_cold_above_rain(self) -> np.ndarray:
131
- is_cold = self.category_bits["cold"]
132
- is_rain = utils.transpose(self.is_rain)
133
- is_cold_rain = (is_cold * is_rain) == 1
134
- return is_cold_rain & ~self.category_bits["melting"]
135
-
136
180
 
137
181
  class IceSource(DataSource):
138
182
  """Base class for different ice products."""
139
183
 
140
- def __init__(self, categorize_file: str, product: str):
184
+ def __init__(self, categorize_file: str | PathLike, product: str) -> None:
141
185
  super().__init__(categorize_file)
142
- self.wl_band = utils.get_wl_band(float(self.getvar("radar_frequency")))
143
- self.temperature = get_temperature(categorize_file)
186
+ self.radar_frequency = float(self.getvar("radar_frequency"))
187
+ self.wl_band = utils.get_wl_band(self.radar_frequency)
188
+ self.temperature = _get_temperature(categorize_file)
144
189
  self.product = product
145
190
  self.coefficients = self._get_coefficients()
146
191
 
147
- def append_main_variable_including_rain(
148
- self, ice_classification: IceClassification
192
+ def append_icy_data(
193
+ self,
194
+ ice_classification: IceClassification,
149
195
  ) -> None:
150
196
  """Adds the main variable (including ice above rain)."""
151
- data_including_rain = self._convert_z()
152
- data_including_rain[~ice_classification.is_ice] = ma.masked
153
- self.append_data(data_including_rain, f"{self.product}_inc_rain")
154
-
155
- def append_main_variable(self, ice_classification: IceClassification) -> None:
156
- """Adds the main variable (excluding rain)."""
157
- data = ma.copy(self.data[f"{self.product}_inc_rain"][:])
158
- data[ice_classification.ice_above_rain] = ma.masked
159
- self.append_data(data, self.product)
197
+ data = self._convert_z()
198
+ data[~ice_classification.is_ice | ice_classification.uncorrected_ice] = (
199
+ ma.masked
200
+ )
201
+ self.append_data(data, f"{self.product}")
160
202
 
161
203
  def append_status(self, ice_classification: IceClassification) -> None:
162
204
  """Adds the status of retrieval."""
@@ -165,10 +207,9 @@ class IceSource(DataSource):
165
207
  is_data = ~data.mask
166
208
  retrieval_status[is_data] = 1
167
209
  retrieval_status[is_data & ice_classification.corrected_ice] = 3
168
- retrieval_status[is_data & ice_classification.uncorrected_ice] = 2
169
210
  retrieval_status[~is_data & ice_classification.is_ice] = 4
170
- retrieval_status[ice_classification.cold_above_rain] = 6
171
- retrieval_status[ice_classification.ice_above_rain] = 5
211
+ retrieval_status[ice_classification.uncorrected_ice] = 2
212
+ retrieval_status[ice_classification.clear_above_rain] = 6
172
213
  retrieval_status[ice_classification.would_be_ice & (retrieval_status == 0)] = 7
173
214
  self.append_data(retrieval_status, f"{self.product}_retrieval_status")
174
215
 
@@ -178,18 +219,27 @@ class IceSource(DataSource):
178
219
  References:
179
220
  Hogan et.al. 2006, https://doi.org/10.1175/JAM2340.1
180
221
  """
222
+ msg = f"Unsupported band: {self.wl_band}"
181
223
  if self.product == "ier":
182
- if self.wl_band == 0:
224
+ if self.wl_band == "Ka":
183
225
  return IceCoefficients(0.878, -0.000205, -0.0015, 0.0016, -1.52)
184
- return IceCoefficients(0.669, -0.000296, -0.00193, -0.000, -1.502)
185
- if self.wl_band == 0:
226
+ if self.wl_band == "W":
227
+ return IceCoefficients(0.669, -0.000296, -0.00193, -0.000, -1.502)
228
+ raise ValueError(msg)
229
+ if self.wl_band == "Ka":
186
230
  return IceCoefficients(0.878, 0.000242, -0.0186, 0.0699, -1.63)
187
- return IceCoefficients(0.669, 0.000580, -0.00706, 0.0923, -0.992)
231
+ if self.wl_band == "W":
232
+ return IceCoefficients(0.669, 0.000580, -0.00706, 0.0923, -0.992)
233
+ raise ValueError(msg)
188
234
 
189
- def _convert_z(self, z_variable: str = "Z") -> np.ndarray:
235
+ def _convert_z(self, z_variable: str = "Z") -> npt.NDArray:
190
236
  """Calculates temperature weighted z, i.e. ice effective radius [m]."""
191
- assert self.product in ("iwc", "ier")
192
- assert z_variable in ("Z", "Z_sensitivity")
237
+ if self.product not in ("iwc", "ier"):
238
+ msg = f"Invalid product: {self.product}"
239
+ raise ValueError(msg)
240
+ if z_variable not in ("Z", "Z_sensitivity"):
241
+ msg = f"Invalid z_variable: {z_variable}"
242
+ raise ValueError(msg)
193
243
  temperature = (
194
244
  self.temperature if z_variable == "Z" else ma.mean(self.temperature, axis=0)
195
245
  )
@@ -212,39 +262,31 @@ class IceSource(DataSource):
212
262
 
213
263
  def _get_z_factor(self) -> float:
214
264
  """Returns empirical scaling factor for radar echo."""
215
- return float(utils.lin2db(self.coefficients.K2liquid0 / 0.93))
216
-
265
+ k2 = np.array(self.coefficients.K2liquid0) / 0.93
266
+ return float(utils.lin2db(k2))
217
267
 
218
- def get_is_rain(filename: str) -> np.ndarray:
219
- try:
220
- rainfall_rate = read_nc_fields(filename, "rainfall_rate")
221
- except KeyError:
222
- rainfall_rate = read_nc_fields(filename, "rain_rate")
223
- is_rain = rainfall_rate != 0
224
- assert isinstance(is_rain, ma.MaskedArray)
225
- is_rain[is_rain.mask] = True
226
- return np.array(is_rain)
227
268
 
269
+ def get_is_rain(filename: str | PathLike) -> npt.NDArray:
270
+ # TODO: Check that this is correct
271
+ with netCDF4.Dataset(filename) as nc:
272
+ for name in ["rain_detected", "rainfall_rate", "rain_rate"]:
273
+ if name in nc.variables:
274
+ data = nc.variables[name][:]
275
+ data = data != 0
276
+ data[data.mask] = True
277
+ return np.array(data)
278
+ msg = "No rain data found."
279
+ raise ValueError(msg)
228
280
 
229
- def read_nc_fields(nc_file: str, names: str | list) -> ma.MaskedArray | list:
230
- """Reads selected variables from a netCDF file.
231
-
232
- Args:
233
- nc_file: netCDF file name.
234
- names: Variables to be read, e.g. 'temperature' or ['ldr', 'lwp'].
235
281
 
236
- Returns:
237
- ndarray/list: Array in case of one variable passed as a string.
238
- List of arrays otherwise.
239
-
240
- """
241
- names = [names] if isinstance(names, str) else names
282
+ def read_nc_field(nc_file: str | PathLike, name: str) -> ma.MaskedArray:
242
283
  with netCDF4.Dataset(nc_file) as nc:
243
- data = [nc.variables[name][:] for name in names]
244
- return data[0] if len(data) == 1 else data
284
+ return nc.variables[name][:]
245
285
 
246
286
 
247
- def interpolate_model(cat_file: str, names: str | list) -> dict[str, np.ndarray]:
287
+ def interpolate_model(
288
+ cat_file: str | PathLike, names: str | list
289
+ ) -> dict[str, npt.NDArray]:
248
290
  """Interpolates 2D model field into dense Cloudnet grid.
249
291
 
250
292
  Args:
@@ -257,8 +299,8 @@ def interpolate_model(cat_file: str, names: str | list) -> dict[str, np.ndarray]
257
299
 
258
300
  """
259
301
 
260
- def _interp_field(var_name: str) -> np.ndarray:
261
- values = read_nc_fields(
302
+ def _interp_field(var_name: str) -> npt.NDArray:
303
+ values = _read_nc_fields(
262
304
  cat_file,
263
305
  ["model_time", "model_height", var_name, "time", "height"],
264
306
  )
@@ -268,12 +310,12 @@ def interpolate_model(cat_file: str, names: str | list) -> dict[str, np.ndarray]
268
310
  return {name: _interp_field(name) for name in names}
269
311
 
270
312
 
271
- def get_temperature(categorize_file: str) -> np.ndarray:
313
+ def _read_nc_fields(nc_file: str | PathLike, names: list[str]) -> list[ma.MaskedArray]:
314
+ with netCDF4.Dataset(nc_file) as nc:
315
+ return [nc.variables[name][:] for name in names]
316
+
317
+
318
+ def _get_temperature(categorize_file: str | PathLike) -> npt.NDArray:
272
319
  """Returns interpolated temperatures in Celsius."""
273
320
  atmosphere = interpolate_model(categorize_file, "temperature")
274
321
  return atmos_utils.k2c(atmosphere["temperature"])
275
-
276
-
277
- def get_mwrpy_coeffs(nc_file: str) -> str:
278
- with netCDF4.Dataset(nc_file) as nc:
279
- return nc.mwrpy_coefficients