cloudnetpy 1.96.2__tar.gz → 1.96.4__tar.gz
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-1.96.2/cloudnetpy.egg-info → cloudnetpy-1.96.4}/PKG-INFO +1 -1
- {cloudnetpy-1.96.2 → cloudnetpy-1.96.4}/cloudnetpy/categorize/insects.py +24 -2
- {cloudnetpy-1.96.2 → cloudnetpy-1.96.4}/cloudnetpy/categorize/melting.py +41 -5
- {cloudnetpy-1.96.2 → cloudnetpy-1.96.4}/cloudnetpy/cli.py +8 -2
- {cloudnetpy-1.96.2 → cloudnetpy-1.96.4}/cloudnetpy/disdronator/parsivel.py +17 -0
- {cloudnetpy-1.96.2 → cloudnetpy-1.96.4}/cloudnetpy/model_evaluation/products/advance_methods.py +14 -5
- {cloudnetpy-1.96.2 → cloudnetpy-1.96.4}/cloudnetpy/model_evaluation/tests/unit/test_advance_methods.py +17 -1
- {cloudnetpy-1.96.2 → cloudnetpy-1.96.4}/cloudnetpy/version.py +1 -1
- {cloudnetpy-1.96.2 → cloudnetpy-1.96.4/cloudnetpy.egg-info}/PKG-INFO +1 -1
- {cloudnetpy-1.96.2 → cloudnetpy-1.96.4}/LICENSE +0 -0
- {cloudnetpy-1.96.2 → cloudnetpy-1.96.4}/MANIFEST.in +0 -0
- {cloudnetpy-1.96.2 → cloudnetpy-1.96.4}/README.md +0 -0
- {cloudnetpy-1.96.2 → cloudnetpy-1.96.4}/cloudnetpy/__init__.py +0 -0
- {cloudnetpy-1.96.2 → cloudnetpy-1.96.4}/cloudnetpy/arm.py +0 -0
- {cloudnetpy-1.96.2 → cloudnetpy-1.96.4}/cloudnetpy/categorize/__init__.py +0 -0
- {cloudnetpy-1.96.2 → cloudnetpy-1.96.4}/cloudnetpy/categorize/atmos_utils.py +0 -0
- {cloudnetpy-1.96.2 → cloudnetpy-1.96.4}/cloudnetpy/categorize/attenuation.py +0 -0
- {cloudnetpy-1.96.2 → cloudnetpy-1.96.4}/cloudnetpy/categorize/attenuations/__init__.py +0 -0
- {cloudnetpy-1.96.2 → cloudnetpy-1.96.4}/cloudnetpy/categorize/attenuations/gas_attenuation.py +0 -0
- {cloudnetpy-1.96.2 → cloudnetpy-1.96.4}/cloudnetpy/categorize/attenuations/liquid_attenuation.py +0 -0
- {cloudnetpy-1.96.2 → cloudnetpy-1.96.4}/cloudnetpy/categorize/attenuations/melting_attenuation.py +0 -0
- {cloudnetpy-1.96.2 → cloudnetpy-1.96.4}/cloudnetpy/categorize/attenuations/rain_attenuation.py +0 -0
- {cloudnetpy-1.96.2 → cloudnetpy-1.96.4}/cloudnetpy/categorize/categorize.py +0 -0
- {cloudnetpy-1.96.2 → cloudnetpy-1.96.4}/cloudnetpy/categorize/classify.py +0 -0
- {cloudnetpy-1.96.2 → cloudnetpy-1.96.4}/cloudnetpy/categorize/containers.py +0 -0
- {cloudnetpy-1.96.2 → cloudnetpy-1.96.4}/cloudnetpy/categorize/disdrometer.py +0 -0
- {cloudnetpy-1.96.2 → cloudnetpy-1.96.4}/cloudnetpy/categorize/droplet.py +0 -0
- {cloudnetpy-1.96.2 → cloudnetpy-1.96.4}/cloudnetpy/categorize/falling.py +0 -0
- {cloudnetpy-1.96.2 → cloudnetpy-1.96.4}/cloudnetpy/categorize/freezing.py +0 -0
- {cloudnetpy-1.96.2 → cloudnetpy-1.96.4}/cloudnetpy/categorize/lidar.py +0 -0
- {cloudnetpy-1.96.2 → cloudnetpy-1.96.4}/cloudnetpy/categorize/model.py +0 -0
- {cloudnetpy-1.96.2 → cloudnetpy-1.96.4}/cloudnetpy/categorize/mwr.py +0 -0
- {cloudnetpy-1.96.2 → cloudnetpy-1.96.4}/cloudnetpy/categorize/radar.py +0 -0
- {cloudnetpy-1.96.2 → cloudnetpy-1.96.4}/cloudnetpy/cloudnetarray.py +0 -0
- {cloudnetpy-1.96.2 → cloudnetpy-1.96.4}/cloudnetpy/concat_lib.py +0 -0
- {cloudnetpy-1.96.2 → cloudnetpy-1.96.4}/cloudnetpy/constants.py +0 -0
- {cloudnetpy-1.96.2 → cloudnetpy-1.96.4}/cloudnetpy/datasource.py +0 -0
- {cloudnetpy-1.96.2 → cloudnetpy-1.96.4}/cloudnetpy/disdronator/__init__.py +0 -0
- {cloudnetpy-1.96.2 → cloudnetpy-1.96.4}/cloudnetpy/disdronator/lpm.py +0 -0
- {cloudnetpy-1.96.2 → cloudnetpy-1.96.4}/cloudnetpy/disdronator/process.py +0 -0
- {cloudnetpy-1.96.2 → cloudnetpy-1.96.4}/cloudnetpy/disdronator/rd80.py +0 -0
- {cloudnetpy-1.96.2 → cloudnetpy-1.96.4}/cloudnetpy/disdronator/utils.py +0 -0
- {cloudnetpy-1.96.2 → cloudnetpy-1.96.4}/cloudnetpy/exceptions.py +0 -0
- {cloudnetpy-1.96.2 → cloudnetpy-1.96.4}/cloudnetpy/instruments/__init__.py +0 -0
- {cloudnetpy-1.96.2 → cloudnetpy-1.96.4}/cloudnetpy/instruments/arm_ceilo.py +0 -0
- {cloudnetpy-1.96.2 → cloudnetpy-1.96.4}/cloudnetpy/instruments/arm_ld.py +0 -0
- {cloudnetpy-1.96.2 → cloudnetpy-1.96.4}/cloudnetpy/instruments/arm_utils.py +0 -0
- {cloudnetpy-1.96.2 → cloudnetpy-1.96.4}/cloudnetpy/instruments/basta.py +0 -0
- {cloudnetpy-1.96.2 → cloudnetpy-1.96.4}/cloudnetpy/instruments/bowtie.py +0 -0
- {cloudnetpy-1.96.2 → cloudnetpy-1.96.4}/cloudnetpy/instruments/ceilo.py +0 -0
- {cloudnetpy-1.96.2 → cloudnetpy-1.96.4}/cloudnetpy/instruments/ceilometer.py +0 -0
- {cloudnetpy-1.96.2 → cloudnetpy-1.96.4}/cloudnetpy/instruments/cl61d.py +0 -0
- {cloudnetpy-1.96.2 → cloudnetpy-1.96.4}/cloudnetpy/instruments/cloudnet_instrument.py +0 -0
- {cloudnetpy-1.96.2 → cloudnetpy-1.96.4}/cloudnetpy/instruments/copernicus.py +0 -0
- {cloudnetpy-1.96.2 → cloudnetpy-1.96.4}/cloudnetpy/instruments/da10.py +0 -0
- {cloudnetpy-1.96.2 → cloudnetpy-1.96.4}/cloudnetpy/instruments/dealias.py +0 -0
- {cloudnetpy-1.96.2 → cloudnetpy-1.96.4}/cloudnetpy/instruments/disdrometer.py +0 -0
- {cloudnetpy-1.96.2 → cloudnetpy-1.96.4}/cloudnetpy/instruments/fd12p.py +0 -0
- {cloudnetpy-1.96.2 → cloudnetpy-1.96.4}/cloudnetpy/instruments/galileo.py +0 -0
- {cloudnetpy-1.96.2 → cloudnetpy-1.96.4}/cloudnetpy/instruments/hatpro.py +0 -0
- {cloudnetpy-1.96.2 → cloudnetpy-1.96.4}/cloudnetpy/instruments/instruments.py +0 -0
- {cloudnetpy-1.96.2 → cloudnetpy-1.96.4}/cloudnetpy/instruments/kazr.py +0 -0
- {cloudnetpy-1.96.2 → cloudnetpy-1.96.4}/cloudnetpy/instruments/lufft.py +0 -0
- {cloudnetpy-1.96.2 → cloudnetpy-1.96.4}/cloudnetpy/instruments/mira.py +0 -0
- {cloudnetpy-1.96.2 → cloudnetpy-1.96.4}/cloudnetpy/instruments/mmcr.py +0 -0
- {cloudnetpy-1.96.2 → cloudnetpy-1.96.4}/cloudnetpy/instruments/mrr.py +0 -0
- {cloudnetpy-1.96.2 → cloudnetpy-1.96.4}/cloudnetpy/instruments/mwr3c.py +0 -0
- {cloudnetpy-1.96.2 → cloudnetpy-1.96.4}/cloudnetpy/instruments/mwrlos.py +0 -0
- {cloudnetpy-1.96.2 → cloudnetpy-1.96.4}/cloudnetpy/instruments/nc_lidar.py +0 -0
- {cloudnetpy-1.96.2 → cloudnetpy-1.96.4}/cloudnetpy/instruments/nc_radar.py +0 -0
- {cloudnetpy-1.96.2 → cloudnetpy-1.96.4}/cloudnetpy/instruments/pollyxt.py +0 -0
- {cloudnetpy-1.96.2 → cloudnetpy-1.96.4}/cloudnetpy/instruments/radiometrics.py +0 -0
- {cloudnetpy-1.96.2 → cloudnetpy-1.96.4}/cloudnetpy/instruments/rain_e_h3.py +0 -0
- {cloudnetpy-1.96.2 → cloudnetpy-1.96.4}/cloudnetpy/instruments/rpg.py +0 -0
- {cloudnetpy-1.96.2 → cloudnetpy-1.96.4}/cloudnetpy/instruments/rpg_reader.py +0 -0
- {cloudnetpy-1.96.2 → cloudnetpy-1.96.4}/cloudnetpy/instruments/toa5.py +0 -0
- {cloudnetpy-1.96.2 → cloudnetpy-1.96.4}/cloudnetpy/instruments/vaisala.py +0 -0
- {cloudnetpy-1.96.2 → cloudnetpy-1.96.4}/cloudnetpy/instruments/weather_radar.py +0 -0
- {cloudnetpy-1.96.2 → cloudnetpy-1.96.4}/cloudnetpy/instruments/weather_station.py +0 -0
- {cloudnetpy-1.96.2 → cloudnetpy-1.96.4}/cloudnetpy/metadata.py +0 -0
- {cloudnetpy-1.96.2 → cloudnetpy-1.96.4}/cloudnetpy/model_evaluation/__init__.py +0 -0
- {cloudnetpy-1.96.2 → cloudnetpy-1.96.4}/cloudnetpy/model_evaluation/file_handler.py +0 -0
- {cloudnetpy-1.96.2 → cloudnetpy-1.96.4}/cloudnetpy/model_evaluation/metadata.py +0 -0
- {cloudnetpy-1.96.2 → cloudnetpy-1.96.4}/cloudnetpy/model_evaluation/model_metadata.py +0 -0
- {cloudnetpy-1.96.2 → cloudnetpy-1.96.4}/cloudnetpy/model_evaluation/plotting/__init__.py +0 -0
- {cloudnetpy-1.96.2 → cloudnetpy-1.96.4}/cloudnetpy/model_evaluation/plotting/plot_meta.py +0 -0
- {cloudnetpy-1.96.2 → cloudnetpy-1.96.4}/cloudnetpy/model_evaluation/plotting/plot_tools.py +0 -0
- {cloudnetpy-1.96.2 → cloudnetpy-1.96.4}/cloudnetpy/model_evaluation/plotting/plotting.py +0 -0
- {cloudnetpy-1.96.2 → cloudnetpy-1.96.4}/cloudnetpy/model_evaluation/products/__init__.py +0 -0
- {cloudnetpy-1.96.2 → cloudnetpy-1.96.4}/cloudnetpy/model_evaluation/products/grid_methods.py +0 -0
- {cloudnetpy-1.96.2 → cloudnetpy-1.96.4}/cloudnetpy/model_evaluation/products/model_products.py +0 -0
- {cloudnetpy-1.96.2 → cloudnetpy-1.96.4}/cloudnetpy/model_evaluation/products/observation_products.py +0 -0
- {cloudnetpy-1.96.2 → cloudnetpy-1.96.4}/cloudnetpy/model_evaluation/products/product_resampling.py +0 -0
- {cloudnetpy-1.96.2 → cloudnetpy-1.96.4}/cloudnetpy/model_evaluation/products/tools.py +0 -0
- {cloudnetpy-1.96.2 → cloudnetpy-1.96.4}/cloudnetpy/model_evaluation/statistics/__init__.py +0 -0
- {cloudnetpy-1.96.2 → cloudnetpy-1.96.4}/cloudnetpy/model_evaluation/statistics/statistical_methods.py +0 -0
- {cloudnetpy-1.96.2 → cloudnetpy-1.96.4}/cloudnetpy/model_evaluation/tests/__init__.py +0 -0
- {cloudnetpy-1.96.2 → cloudnetpy-1.96.4}/cloudnetpy/model_evaluation/tests/e2e/__init__.py +0 -0
- {cloudnetpy-1.96.2 → cloudnetpy-1.96.4}/cloudnetpy/model_evaluation/tests/e2e/conftest.py +0 -0
- {cloudnetpy-1.96.2 → cloudnetpy-1.96.4}/cloudnetpy/model_evaluation/tests/e2e/process_cf/__init__.py +0 -0
- {cloudnetpy-1.96.2 → cloudnetpy-1.96.4}/cloudnetpy/model_evaluation/tests/e2e/process_cf/main.py +0 -0
- {cloudnetpy-1.96.2 → cloudnetpy-1.96.4}/cloudnetpy/model_evaluation/tests/e2e/process_cf/tests.py +0 -0
- {cloudnetpy-1.96.2 → cloudnetpy-1.96.4}/cloudnetpy/model_evaluation/tests/e2e/process_iwc/__init__.py +0 -0
- {cloudnetpy-1.96.2 → cloudnetpy-1.96.4}/cloudnetpy/model_evaluation/tests/e2e/process_iwc/main.py +0 -0
- {cloudnetpy-1.96.2 → cloudnetpy-1.96.4}/cloudnetpy/model_evaluation/tests/e2e/process_iwc/tests.py +0 -0
- {cloudnetpy-1.96.2 → cloudnetpy-1.96.4}/cloudnetpy/model_evaluation/tests/e2e/process_lwc/__init__.py +0 -0
- {cloudnetpy-1.96.2 → cloudnetpy-1.96.4}/cloudnetpy/model_evaluation/tests/e2e/process_lwc/main.py +0 -0
- {cloudnetpy-1.96.2 → cloudnetpy-1.96.4}/cloudnetpy/model_evaluation/tests/e2e/process_lwc/tests.py +0 -0
- {cloudnetpy-1.96.2 → cloudnetpy-1.96.4}/cloudnetpy/model_evaluation/tests/unit/__init__.py +0 -0
- {cloudnetpy-1.96.2 → cloudnetpy-1.96.4}/cloudnetpy/model_evaluation/tests/unit/conftest.py +0 -0
- {cloudnetpy-1.96.2 → cloudnetpy-1.96.4}/cloudnetpy/model_evaluation/tests/unit/test_grid_methods.py +0 -0
- {cloudnetpy-1.96.2 → cloudnetpy-1.96.4}/cloudnetpy/model_evaluation/tests/unit/test_model_products.py +0 -0
- {cloudnetpy-1.96.2 → cloudnetpy-1.96.4}/cloudnetpy/model_evaluation/tests/unit/test_observation_products.py +0 -0
- {cloudnetpy-1.96.2 → cloudnetpy-1.96.4}/cloudnetpy/model_evaluation/tests/unit/test_plot_tools.py +0 -0
- {cloudnetpy-1.96.2 → cloudnetpy-1.96.4}/cloudnetpy/model_evaluation/tests/unit/test_plotting.py +0 -0
- {cloudnetpy-1.96.2 → cloudnetpy-1.96.4}/cloudnetpy/model_evaluation/tests/unit/test_statistical_methods.py +0 -0
- {cloudnetpy-1.96.2 → cloudnetpy-1.96.4}/cloudnetpy/model_evaluation/tests/unit/test_tools.py +0 -0
- {cloudnetpy-1.96.2 → cloudnetpy-1.96.4}/cloudnetpy/model_evaluation/utils.py +0 -0
- {cloudnetpy-1.96.2 → cloudnetpy-1.96.4}/cloudnetpy/output.py +0 -0
- {cloudnetpy-1.96.2 → cloudnetpy-1.96.4}/cloudnetpy/plotting/__init__.py +0 -0
- {cloudnetpy-1.96.2 → cloudnetpy-1.96.4}/cloudnetpy/plotting/plot_meta.py +0 -0
- {cloudnetpy-1.96.2 → cloudnetpy-1.96.4}/cloudnetpy/plotting/plotting.py +0 -0
- {cloudnetpy-1.96.2 → cloudnetpy-1.96.4}/cloudnetpy/products/__init__.py +0 -0
- {cloudnetpy-1.96.2 → cloudnetpy-1.96.4}/cloudnetpy/products/classification.py +0 -0
- {cloudnetpy-1.96.2 → cloudnetpy-1.96.4}/cloudnetpy/products/cod.py +0 -0
- {cloudnetpy-1.96.2 → cloudnetpy-1.96.4}/cloudnetpy/products/der.py +0 -0
- {cloudnetpy-1.96.2 → cloudnetpy-1.96.4}/cloudnetpy/products/drizzle.py +0 -0
- {cloudnetpy-1.96.2 → cloudnetpy-1.96.4}/cloudnetpy/products/drizzle_error.py +0 -0
- {cloudnetpy-1.96.2 → cloudnetpy-1.96.4}/cloudnetpy/products/drizzle_tools.py +0 -0
- {cloudnetpy-1.96.2 → cloudnetpy-1.96.4}/cloudnetpy/products/epsilon_lidar.py +0 -0
- {cloudnetpy-1.96.2 → cloudnetpy-1.96.4}/cloudnetpy/products/epsilon_radar.py +0 -0
- {cloudnetpy-1.96.2 → cloudnetpy-1.96.4}/cloudnetpy/products/ier.py +0 -0
- {cloudnetpy-1.96.2 → cloudnetpy-1.96.4}/cloudnetpy/products/iwc.py +0 -0
- {cloudnetpy-1.96.2 → cloudnetpy-1.96.4}/cloudnetpy/products/iwc_weather_radar.py +0 -0
- {cloudnetpy-1.96.2 → cloudnetpy-1.96.4}/cloudnetpy/products/lwc.py +0 -0
- {cloudnetpy-1.96.2 → cloudnetpy-1.96.4}/cloudnetpy/products/mie_lu_tables.nc +0 -0
- {cloudnetpy-1.96.2 → cloudnetpy-1.96.4}/cloudnetpy/products/mwr_tools.py +0 -0
- {cloudnetpy-1.96.2 → cloudnetpy-1.96.4}/cloudnetpy/products/product_tools.py +0 -0
- {cloudnetpy-1.96.2 → cloudnetpy-1.96.4}/cloudnetpy/py.typed +0 -0
- {cloudnetpy-1.96.2 → cloudnetpy-1.96.4}/cloudnetpy/utils.py +0 -0
- {cloudnetpy-1.96.2 → cloudnetpy-1.96.4}/cloudnetpy.egg-info/SOURCES.txt +0 -0
- {cloudnetpy-1.96.2 → cloudnetpy-1.96.4}/cloudnetpy.egg-info/dependency_links.txt +0 -0
- {cloudnetpy-1.96.2 → cloudnetpy-1.96.4}/cloudnetpy.egg-info/entry_points.txt +0 -0
- {cloudnetpy-1.96.2 → cloudnetpy-1.96.4}/cloudnetpy.egg-info/requires.txt +0 -0
- {cloudnetpy-1.96.2 → cloudnetpy-1.96.4}/cloudnetpy.egg-info/top_level.txt +0 -0
- {cloudnetpy-1.96.2 → cloudnetpy-1.96.4}/docs/source/conf.py +0 -0
- {cloudnetpy-1.96.2 → cloudnetpy-1.96.4}/pyproject.toml +0 -0
- {cloudnetpy-1.96.2 → cloudnetpy-1.96.4}/setup.cfg +0 -0
|
@@ -58,7 +58,9 @@ def _insect_probability(obs: ClassData) -> tuple[npt.NDArray, npt.NDArray]:
|
|
|
58
58
|
prob_from_ldr = _calc_prob_from_ldr(prob)
|
|
59
59
|
prob_from_others = _calc_prob_from_all(prob)
|
|
60
60
|
prob_from_others = _adjust_for_radar(obs, prob, prob_from_others)
|
|
61
|
-
|
|
61
|
+
prob_from_others = _adjust_for_lidar(obs, prob_from_others)
|
|
62
|
+
is_ldr = ~ma.getmaskarray(obs.ldr) if hasattr(obs, "ldr") else None
|
|
63
|
+
prob_combined = _fill_missing_pixels(prob_from_ldr, prob_from_others, is_ldr)
|
|
62
64
|
return prob_combined, prob_from_others
|
|
63
65
|
|
|
64
66
|
|
|
@@ -147,11 +149,31 @@ def _adjust_for_radar(
|
|
|
147
149
|
return prob_from_others
|
|
148
150
|
|
|
149
151
|
|
|
152
|
+
def _adjust_for_lidar(
|
|
153
|
+
obs: ClassData,
|
|
154
|
+
prob_from_others: npt.NDArray,
|
|
155
|
+
weight: float = 0.8,
|
|
156
|
+
) -> npt.NDArray:
|
|
157
|
+
"""Reduces probability above the lidar signal, where liquid may be hidden."""
|
|
158
|
+
is_beta = ~ma.getmaskarray(obs.beta)
|
|
159
|
+
n_gates = is_beta.shape[1]
|
|
160
|
+
top = n_gates - np.argmax(is_beta[:, ::-1], axis=1)
|
|
161
|
+
top[~is_beta.any(axis=1)] = 0
|
|
162
|
+
above_lidar = np.arange(n_gates)[np.newaxis, :] >= top[:, np.newaxis]
|
|
163
|
+
prob_from_others[above_lidar] *= weight
|
|
164
|
+
return prob_from_others
|
|
165
|
+
|
|
166
|
+
|
|
150
167
|
def _fill_missing_pixels(
|
|
151
168
|
prob_from_ldr: npt.NDArray,
|
|
152
169
|
prob_from_others: npt.NDArray,
|
|
170
|
+
is_ldr: npt.NDArray | None = None,
|
|
153
171
|
) -> npt.NDArray:
|
|
154
|
-
|
|
172
|
+
"""Combines probabilities trusting measured ldr over other parameters."""
|
|
173
|
+
prob = np.maximum(prob_from_ldr, prob_from_others)
|
|
174
|
+
if is_ldr is not None:
|
|
175
|
+
prob[is_ldr] = prob_from_ldr[is_ldr]
|
|
176
|
+
return prob
|
|
155
177
|
|
|
156
178
|
|
|
157
179
|
def _screen_insects(
|
|
@@ -10,6 +10,10 @@ from cloudnetpy import utils
|
|
|
10
10
|
from cloudnetpy.categorize import droplet
|
|
11
11
|
from cloudnetpy.categorize.containers import ClassData
|
|
12
12
|
|
|
13
|
+
MIN_LAPSE_RATE = 0.004 # K m-1
|
|
14
|
+
MIN_LDR_COVERAGE = 0.5
|
|
15
|
+
T0_TOLERANCE = 1 # K
|
|
16
|
+
|
|
13
17
|
|
|
14
18
|
def find_melting_layer(obs: ClassData, *, smooth: bool = True) -> npt.NDArray:
|
|
15
19
|
"""Finds melting layer from model temperature, ldr, and velocity.
|
|
@@ -27,11 +31,15 @@ def find_melting_layer(obs: ClassData, *, smooth: bool = True) -> npt.NDArray:
|
|
|
27
31
|
The peak in *ldr* is the primary parameter we analyze. If
|
|
28
32
|
*ldr* has a proper peak, and *v* < -1 m/s in the base, melting layer
|
|
29
33
|
has been found. If *ldr* is missing we only analyze the behaviour
|
|
30
|
-
of *v*, which is always present, to detect the melting layer.
|
|
34
|
+
of *v*, which is always present, to detect the melting layer. If *ldr*
|
|
35
|
+
covers the profile but shows no peak, there is no melting layer.
|
|
31
36
|
|
|
32
37
|
Model temperature is used to limit the melting layer search to a certain
|
|
33
38
|
temperature range around 0 C. For ECMWF the range is -4..+3, and for
|
|
34
|
-
the rest -8..+6.
|
|
39
|
+
the rest -8..+6. Model temperature must also reach -1 C in the profile,
|
|
40
|
+
and the search is limited in altitude above the highest 0 C level,
|
|
41
|
+
because in temperature inversions the temperature range alone may
|
|
42
|
+
span several kilometers.
|
|
35
43
|
|
|
36
44
|
Notes:
|
|
37
45
|
This melting layer detection method is novel and needs to be validated.
|
|
@@ -63,7 +71,7 @@ def find_melting_layer(obs: ClassData, *, smooth: bool = True) -> npt.NDArray:
|
|
|
63
71
|
t_range = _find_model_temperature_range(obs.model_type)
|
|
64
72
|
|
|
65
73
|
for ind, t_prof in enumerate(obs.tw):
|
|
66
|
-
temp_indices = _get_temp_indices(t_prof, t_range)
|
|
74
|
+
temp_indices = _get_temp_indices(t_prof, t_range, obs.height)
|
|
67
75
|
if len(temp_indices) <= 1:
|
|
68
76
|
continue
|
|
69
77
|
z_prof = obs.z[ind, temp_indices]
|
|
@@ -90,6 +98,8 @@ def find_melting_layer(obs: ClassData, *, smooth: bool = True) -> npt.NDArray:
|
|
|
90
98
|
z_prof,
|
|
91
99
|
)
|
|
92
100
|
except (ValueError, IndexError, AssertionError):
|
|
101
|
+
if _has_ldr_coverage(ldr_prof, v_prof):
|
|
102
|
+
continue
|
|
93
103
|
height = obs.height[temp_indices]
|
|
94
104
|
if hasattr(obs, "width"):
|
|
95
105
|
width_prof = obs.width[ind, temp_indices]
|
|
@@ -104,6 +114,16 @@ def find_melting_layer(obs: ClassData, *, smooth: bool = True) -> npt.NDArray:
|
|
|
104
114
|
return melting_layer
|
|
105
115
|
|
|
106
116
|
|
|
117
|
+
def _has_ldr_coverage(ldr_prof: npt.NDArray | None, v_prof: npt.NDArray) -> bool:
|
|
118
|
+
"""Checks if ldr covers enough of the profile to rule out melting layer."""
|
|
119
|
+
if ldr_prof is None:
|
|
120
|
+
return False
|
|
121
|
+
is_v = ~ma.getmaskarray(v_prof)
|
|
122
|
+
is_ldr = ~ma.getmaskarray(ldr_prof)
|
|
123
|
+
n_ldr = np.count_nonzero(is_ldr & is_v)
|
|
124
|
+
return bool(n_ldr >= MIN_LDR_COVERAGE * np.count_nonzero(is_v))
|
|
125
|
+
|
|
126
|
+
|
|
107
127
|
def _find_melting_layer_from_ldr(
|
|
108
128
|
ldr_prof: npt.NDArray,
|
|
109
129
|
ldr_dprof: npt.NDArray,
|
|
@@ -166,12 +186,28 @@ def _basetop(dprof: npt.NDArray, pind: int) -> tuple[int, int]:
|
|
|
166
186
|
return base, top
|
|
167
187
|
|
|
168
188
|
|
|
169
|
-
def _get_temp_indices(
|
|
189
|
+
def _get_temp_indices(
|
|
190
|
+
t_prof: npt.NDArray, t_range: tuple, height: npt.NDArray | None = None
|
|
191
|
+
) -> npt.NDArray:
|
|
170
192
|
"""Finds indices of temperature profile covering the given range."""
|
|
171
|
-
|
|
193
|
+
in_range = (t_prof > min(t_range) + T0) & (t_prof < max(t_range) + T0)
|
|
194
|
+
if height is not None:
|
|
195
|
+
in_range &= height <= _find_max_height(t_prof, t_range, height)
|
|
196
|
+
ind = np.where(in_range)[0]
|
|
172
197
|
return np.array([]) if len(ind) == 0 else np.arange(np.min(ind), np.max(ind) + 1)
|
|
173
198
|
|
|
174
199
|
|
|
200
|
+
def _find_max_height(t_prof: npt.NDArray, t_range: tuple, height: npt.NDArray) -> float:
|
|
201
|
+
"""Finds maximum melting layer height assuming realistic lapse rate."""
|
|
202
|
+
warm = np.where(t_prof >= T0)[0]
|
|
203
|
+
if len(warm) == 0:
|
|
204
|
+
# Model may be slightly too cold when melting layer is near the ground
|
|
205
|
+
warm = np.where(t_prof >= T0 - T0_TOLERANCE)[0]
|
|
206
|
+
if len(warm) == 0:
|
|
207
|
+
return -np.inf
|
|
208
|
+
return height[warm[-1]] - min(t_range) / MIN_LAPSE_RATE
|
|
209
|
+
|
|
210
|
+
|
|
175
211
|
def _find_model_temperature_range(model_type: str) -> tuple[float, float]:
|
|
176
212
|
"""Returns temperature range around 0C for given model type."""
|
|
177
213
|
if model_type == "gdas1":
|
|
@@ -928,9 +928,11 @@ def main() -> None:
|
|
|
928
928
|
)
|
|
929
929
|
parser.add_argument(
|
|
930
930
|
"--show",
|
|
931
|
-
|
|
931
|
+
nargs="?",
|
|
932
|
+
const=True,
|
|
932
933
|
default=False,
|
|
933
|
-
|
|
934
|
+
metavar="VARIABLES",
|
|
935
|
+
help="Show plotted image, optionally only given variables (comma-separated)",
|
|
934
936
|
)
|
|
935
937
|
parser.add_argument(
|
|
936
938
|
"--dl",
|
|
@@ -980,6 +982,10 @@ def main() -> None:
|
|
|
980
982
|
default=None,
|
|
981
983
|
)
|
|
982
984
|
args = parser.parse_args()
|
|
985
|
+
if isinstance(args.show, str):
|
|
986
|
+
if args.variables is None:
|
|
987
|
+
args.variables = args.show
|
|
988
|
+
args.show = True
|
|
983
989
|
|
|
984
990
|
logger = logging.getLogger()
|
|
985
991
|
logger.setLevel(logging.INFO)
|
|
@@ -475,6 +475,21 @@ def _read_leipzig(filename: str | PathLike) -> ParsivelOutput:
|
|
|
475
475
|
return time, data
|
|
476
476
|
|
|
477
477
|
|
|
478
|
+
def _read_cloudlab(filename: str | PathLike) -> ParsivelOutput:
|
|
479
|
+
# Data acquired with ASDO and converted to daily netCDF files. The netCDF
|
|
480
|
+
# files are similar to parsivel2nc files but without "number_concentration"
|
|
481
|
+
# and "fall_velocity" variables.
|
|
482
|
+
with netCDF4.Dataset(filename) as nc:
|
|
483
|
+
time = cftime.num2pydate(nc["time"][:], units=nc["time"].units)
|
|
484
|
+
data = {
|
|
485
|
+
num: nc[key][:]
|
|
486
|
+
for key, num in PARSIVEL2NC_KEYS.items()
|
|
487
|
+
if key in nc.variables
|
|
488
|
+
}
|
|
489
|
+
data[13] = np.repeat(int(nc.Sensor_ID), len(time))
|
|
490
|
+
return time, data
|
|
491
|
+
|
|
492
|
+
|
|
478
493
|
def _read_parsivel(
|
|
479
494
|
filename: str | PathLike,
|
|
480
495
|
telegram: Sequence[int | str | None] | None = None,
|
|
@@ -483,6 +498,8 @@ def _read_parsivel(
|
|
|
483
498
|
) -> ParsivelOutput:
|
|
484
499
|
try:
|
|
485
500
|
with netCDF4.Dataset(filename) as nc:
|
|
501
|
+
if hasattr(nc, "Title") and "CLOUDLAB" in nc.Title:
|
|
502
|
+
return _read_cloudlab(filename)
|
|
486
503
|
if "number_concentration" in nc.variables:
|
|
487
504
|
return _read_parsivel2nc(filename)
|
|
488
505
|
if "fieldN" in nc.variables:
|
{cloudnetpy-1.96.2 → cloudnetpy-1.96.4}/cloudnetpy/model_evaluation/products/advance_methods.py
RENAMED
|
@@ -105,11 +105,20 @@ class AdvanceProductMethods:
|
|
|
105
105
|
if self._obs_obj.height is None:
|
|
106
106
|
msg = "No height in observation file"
|
|
107
107
|
raise ValueError(msg)
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
108
|
+
# Interpolate at the model level heights: rebinning assumes evenly
|
|
109
|
+
# spaced levels, which maps stretched model levels to wrong heights.
|
|
110
|
+
obs_height = self._obs_obj.height_agl
|
|
111
|
+
if obs_height is None:
|
|
112
|
+
obs_height = self._obs_obj.height
|
|
113
|
+
obs_height = np.asarray(ma.filled(obs_height, np.nan), dtype=float)
|
|
114
|
+
z_obs = np.asarray(ma.filled(self._obs_obj.z_sensitivity, np.nan), dtype=float)
|
|
115
|
+
is_valid = np.isfinite(obs_height) & np.isfinite(z_obs)
|
|
116
|
+
model_height = np.asarray(ma.filled(h, np.nan), dtype=float)
|
|
117
|
+
z_sen = np.interp(
|
|
118
|
+
model_height, obs_height[is_valid], z_obs[is_valid], right=np.nan
|
|
119
|
+
)
|
|
120
|
+
z_sen[~np.isfinite(model_height)] = np.nan
|
|
121
|
+
return z_sen
|
|
113
122
|
|
|
114
123
|
def filter_high_iwc_low_cf(
|
|
115
124
|
self,
|
|
@@ -69,14 +69,30 @@ def test_fit_z_sensitivity(obs_file, model_file) -> None:
|
|
|
69
69
|
obs = ObservationManager(PRODUCT, str(obs_file))
|
|
70
70
|
model = ModelManager(str(model_file), MODEL, PRODUCT)
|
|
71
71
|
advance = AdvanceProductMethods(model, obs)
|
|
72
|
+
# obs heights above ground 7, 8, 11 and 14 km with sensitivities
|
|
73
|
+
# 0.1, 0.2, 0.0, 1.0; levels above the radar range are NaN
|
|
72
74
|
height = np.array(
|
|
73
75
|
[[5000, 9000, 13000], [10000, 15000, 20000], [8000, 12000, 16000]]
|
|
74
76
|
)
|
|
75
|
-
expected =
|
|
77
|
+
expected = np.array(
|
|
78
|
+
[[0.1, 0.4 / 3, 2 / 3], [0.2 / 3, np.nan, np.nan], [0.2, 1 / 3, np.nan]]
|
|
79
|
+
)
|
|
76
80
|
result = advance.fit_z_sensitivity(height)
|
|
77
81
|
testing.assert_array_almost_equal(result, expected)
|
|
78
82
|
|
|
79
83
|
|
|
84
|
+
def test_fit_z_sensitivity_stretched_grid(obs_file, model_file) -> None:
|
|
85
|
+
obs = ObservationManager(PRODUCT, str(obs_file))
|
|
86
|
+
model = ModelManager(str(model_file), MODEL, PRODUCT)
|
|
87
|
+
advance = AdvanceProductMethods(model, obs)
|
|
88
|
+
# unevenly spaced model levels within the radar range must all get a value
|
|
89
|
+
height = np.array([[7000, 7100, 7300, 7700, 8500, 10000, 13000, 14000]])
|
|
90
|
+
result = advance.fit_z_sensitivity(height)
|
|
91
|
+
assert np.isfinite(result).all()
|
|
92
|
+
assert result[0, 0] == pytest.approx(0.1)
|
|
93
|
+
assert result[0, -1] == pytest.approx(1.0)
|
|
94
|
+
|
|
95
|
+
|
|
80
96
|
def test_filter_high_iwc_low_cf(obs_file, model_file) -> None:
|
|
81
97
|
obs = ObservationManager(PRODUCT, str(obs_file))
|
|
82
98
|
model = ModelManager(str(model_file), MODEL, PRODUCT)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{cloudnetpy-1.96.2 → cloudnetpy-1.96.4}/cloudnetpy/categorize/attenuations/gas_attenuation.py
RENAMED
|
File without changes
|
{cloudnetpy-1.96.2 → cloudnetpy-1.96.4}/cloudnetpy/categorize/attenuations/liquid_attenuation.py
RENAMED
|
File without changes
|
{cloudnetpy-1.96.2 → cloudnetpy-1.96.4}/cloudnetpy/categorize/attenuations/melting_attenuation.py
RENAMED
|
File without changes
|
{cloudnetpy-1.96.2 → cloudnetpy-1.96.4}/cloudnetpy/categorize/attenuations/rain_attenuation.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{cloudnetpy-1.96.2 → cloudnetpy-1.96.4}/cloudnetpy/model_evaluation/products/grid_methods.py
RENAMED
|
File without changes
|
{cloudnetpy-1.96.2 → cloudnetpy-1.96.4}/cloudnetpy/model_evaluation/products/model_products.py
RENAMED
|
File without changes
|
{cloudnetpy-1.96.2 → cloudnetpy-1.96.4}/cloudnetpy/model_evaluation/products/observation_products.py
RENAMED
|
File without changes
|
{cloudnetpy-1.96.2 → cloudnetpy-1.96.4}/cloudnetpy/model_evaluation/products/product_resampling.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{cloudnetpy-1.96.2 → cloudnetpy-1.96.4}/cloudnetpy/model_evaluation/tests/e2e/process_cf/__init__.py
RENAMED
|
File without changes
|
{cloudnetpy-1.96.2 → cloudnetpy-1.96.4}/cloudnetpy/model_evaluation/tests/e2e/process_cf/main.py
RENAMED
|
File without changes
|
{cloudnetpy-1.96.2 → cloudnetpy-1.96.4}/cloudnetpy/model_evaluation/tests/e2e/process_cf/tests.py
RENAMED
|
File without changes
|
|
File without changes
|
{cloudnetpy-1.96.2 → cloudnetpy-1.96.4}/cloudnetpy/model_evaluation/tests/e2e/process_iwc/main.py
RENAMED
|
File without changes
|
{cloudnetpy-1.96.2 → cloudnetpy-1.96.4}/cloudnetpy/model_evaluation/tests/e2e/process_iwc/tests.py
RENAMED
|
File without changes
|
|
File without changes
|
{cloudnetpy-1.96.2 → cloudnetpy-1.96.4}/cloudnetpy/model_evaluation/tests/e2e/process_lwc/main.py
RENAMED
|
File without changes
|
{cloudnetpy-1.96.2 → cloudnetpy-1.96.4}/cloudnetpy/model_evaluation/tests/e2e/process_lwc/tests.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{cloudnetpy-1.96.2 → cloudnetpy-1.96.4}/cloudnetpy/model_evaluation/tests/unit/test_grid_methods.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{cloudnetpy-1.96.2 → cloudnetpy-1.96.4}/cloudnetpy/model_evaluation/tests/unit/test_plot_tools.py
RENAMED
|
File without changes
|
{cloudnetpy-1.96.2 → cloudnetpy-1.96.4}/cloudnetpy/model_evaluation/tests/unit/test_plotting.py
RENAMED
|
File without changes
|
|
File without changes
|
{cloudnetpy-1.96.2 → cloudnetpy-1.96.4}/cloudnetpy/model_evaluation/tests/unit/test_tools.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|