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.
- cloudnetpy/categorize/__init__.py +1 -2
- cloudnetpy/categorize/atmos_utils.py +297 -67
- cloudnetpy/categorize/attenuation.py +31 -0
- cloudnetpy/categorize/attenuations/__init__.py +37 -0
- cloudnetpy/categorize/attenuations/gas_attenuation.py +30 -0
- cloudnetpy/categorize/attenuations/liquid_attenuation.py +84 -0
- cloudnetpy/categorize/attenuations/melting_attenuation.py +78 -0
- cloudnetpy/categorize/attenuations/rain_attenuation.py +84 -0
- cloudnetpy/categorize/categorize.py +332 -156
- cloudnetpy/categorize/classify.py +127 -125
- cloudnetpy/categorize/containers.py +107 -76
- cloudnetpy/categorize/disdrometer.py +40 -0
- cloudnetpy/categorize/droplet.py +23 -21
- cloudnetpy/categorize/falling.py +53 -24
- cloudnetpy/categorize/freezing.py +25 -12
- cloudnetpy/categorize/insects.py +35 -23
- cloudnetpy/categorize/itu.py +243 -0
- cloudnetpy/categorize/lidar.py +36 -41
- cloudnetpy/categorize/melting.py +34 -26
- cloudnetpy/categorize/model.py +84 -37
- cloudnetpy/categorize/mwr.py +18 -14
- cloudnetpy/categorize/radar.py +215 -102
- cloudnetpy/cli.py +578 -0
- cloudnetpy/cloudnetarray.py +43 -89
- cloudnetpy/concat_lib.py +218 -78
- cloudnetpy/constants.py +28 -10
- cloudnetpy/datasource.py +61 -86
- cloudnetpy/exceptions.py +49 -20
- cloudnetpy/instruments/__init__.py +5 -0
- cloudnetpy/instruments/basta.py +29 -12
- cloudnetpy/instruments/bowtie.py +135 -0
- cloudnetpy/instruments/ceilo.py +138 -115
- cloudnetpy/instruments/ceilometer.py +164 -80
- cloudnetpy/instruments/cl61d.py +21 -5
- cloudnetpy/instruments/cloudnet_instrument.py +74 -36
- cloudnetpy/instruments/copernicus.py +108 -30
- cloudnetpy/instruments/da10.py +54 -0
- cloudnetpy/instruments/disdrometer/common.py +126 -223
- cloudnetpy/instruments/disdrometer/parsivel.py +453 -94
- cloudnetpy/instruments/disdrometer/thies.py +254 -87
- cloudnetpy/instruments/fd12p.py +201 -0
- cloudnetpy/instruments/galileo.py +65 -23
- cloudnetpy/instruments/hatpro.py +123 -49
- cloudnetpy/instruments/instruments.py +113 -1
- cloudnetpy/instruments/lufft.py +39 -17
- cloudnetpy/instruments/mira.py +268 -61
- cloudnetpy/instruments/mrr.py +187 -0
- cloudnetpy/instruments/nc_lidar.py +19 -8
- cloudnetpy/instruments/nc_radar.py +109 -55
- cloudnetpy/instruments/pollyxt.py +135 -51
- cloudnetpy/instruments/radiometrics.py +313 -59
- cloudnetpy/instruments/rain_e_h3.py +171 -0
- cloudnetpy/instruments/rpg.py +321 -189
- cloudnetpy/instruments/rpg_reader.py +74 -40
- cloudnetpy/instruments/toa5.py +49 -0
- cloudnetpy/instruments/vaisala.py +95 -343
- cloudnetpy/instruments/weather_station.py +774 -105
- cloudnetpy/metadata.py +90 -19
- cloudnetpy/model_evaluation/file_handler.py +55 -52
- cloudnetpy/model_evaluation/metadata.py +46 -20
- cloudnetpy/model_evaluation/model_metadata.py +1 -1
- cloudnetpy/model_evaluation/plotting/plot_tools.py +32 -37
- cloudnetpy/model_evaluation/plotting/plotting.py +327 -117
- cloudnetpy/model_evaluation/products/advance_methods.py +92 -83
- cloudnetpy/model_evaluation/products/grid_methods.py +88 -63
- cloudnetpy/model_evaluation/products/model_products.py +43 -35
- cloudnetpy/model_evaluation/products/observation_products.py +41 -35
- cloudnetpy/model_evaluation/products/product_resampling.py +17 -7
- cloudnetpy/model_evaluation/products/tools.py +29 -20
- cloudnetpy/model_evaluation/statistics/statistical_methods.py +30 -20
- cloudnetpy/model_evaluation/tests/e2e/conftest.py +3 -3
- cloudnetpy/model_evaluation/tests/e2e/process_cf/main.py +9 -5
- cloudnetpy/model_evaluation/tests/e2e/process_cf/tests.py +15 -14
- cloudnetpy/model_evaluation/tests/e2e/process_iwc/main.py +9 -5
- cloudnetpy/model_evaluation/tests/e2e/process_iwc/tests.py +15 -14
- cloudnetpy/model_evaluation/tests/e2e/process_lwc/main.py +9 -5
- cloudnetpy/model_evaluation/tests/e2e/process_lwc/tests.py +15 -14
- cloudnetpy/model_evaluation/tests/unit/conftest.py +42 -41
- cloudnetpy/model_evaluation/tests/unit/test_advance_methods.py +41 -48
- cloudnetpy/model_evaluation/tests/unit/test_grid_methods.py +216 -194
- cloudnetpy/model_evaluation/tests/unit/test_model_products.py +23 -21
- cloudnetpy/model_evaluation/tests/unit/test_observation_products.py +37 -38
- cloudnetpy/model_evaluation/tests/unit/test_plot_tools.py +43 -40
- cloudnetpy/model_evaluation/tests/unit/test_plotting.py +30 -36
- cloudnetpy/model_evaluation/tests/unit/test_statistical_methods.py +68 -31
- cloudnetpy/model_evaluation/tests/unit/test_tools.py +33 -26
- cloudnetpy/model_evaluation/utils.py +2 -1
- cloudnetpy/output.py +170 -111
- cloudnetpy/plotting/__init__.py +2 -1
- cloudnetpy/plotting/plot_meta.py +562 -822
- cloudnetpy/plotting/plotting.py +1142 -704
- cloudnetpy/products/__init__.py +1 -0
- cloudnetpy/products/classification.py +370 -88
- cloudnetpy/products/der.py +85 -55
- cloudnetpy/products/drizzle.py +77 -34
- cloudnetpy/products/drizzle_error.py +15 -11
- cloudnetpy/products/drizzle_tools.py +79 -59
- cloudnetpy/products/epsilon.py +211 -0
- cloudnetpy/products/ier.py +27 -50
- cloudnetpy/products/iwc.py +55 -48
- cloudnetpy/products/lwc.py +96 -70
- cloudnetpy/products/mwr_tools.py +186 -0
- cloudnetpy/products/product_tools.py +170 -128
- cloudnetpy/utils.py +455 -240
- cloudnetpy/version.py +2 -2
- {cloudnetpy-1.49.9.dist-info → cloudnetpy-1.87.3.dist-info}/METADATA +44 -40
- cloudnetpy-1.87.3.dist-info/RECORD +127 -0
- {cloudnetpy-1.49.9.dist-info → cloudnetpy-1.87.3.dist-info}/WHEEL +1 -1
- cloudnetpy-1.87.3.dist-info/entry_points.txt +2 -0
- docs/source/conf.py +2 -2
- cloudnetpy/categorize/atmos.py +0 -361
- cloudnetpy/products/mwr_multi.py +0 -68
- cloudnetpy/products/mwr_single.py +0 -75
- cloudnetpy-1.49.9.dist-info/RECORD +0 -112
- {cloudnetpy-1.49.9.dist-info → cloudnetpy-1.87.3.dist-info/licenses}/LICENSE +0 -0
- {cloudnetpy-1.49.9.dist-info → cloudnetpy-1.87.3.dist-info}/top_level.txt +0 -0
cloudnetpy/categorize/model.py
CHANGED
|
@@ -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
|
-
|
|
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
|
-
"
|
|
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__(
|
|
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.
|
|
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:
|
|
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
|
|
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(
|
|
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,
|
|
82
|
-
|
|
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
|
-
|
|
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,
|
|
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) ->
|
|
140
|
+
def _get_model_heights(self, alt_site: float) -> npt.NDArray:
|
|
115
141
|
"""Returns model heights for each time step."""
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
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 =
|
|
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
|
|
177
|
+
if key in basename:
|
|
130
178
|
return key
|
|
131
|
-
|
|
179
|
+
msg = f"Unknown model type: {file_name}"
|
|
180
|
+
raise ValueError(msg)
|
|
132
181
|
|
|
133
182
|
|
|
134
|
-
def _find_number_of_valid_profiles(array:
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
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))
|
cloudnetpy/categorize/mwr.py
CHANGED
|
@@ -1,8 +1,14 @@
|
|
|
1
1
|
"""Mwr module, containing the :class:`Mwr` class."""
|
|
2
|
-
|
|
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
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
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
|
-
|
|
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
|
)
|