cloudnetpy 1.89.0__tar.gz → 1.89.2__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.89.0/cloudnetpy.egg-info → cloudnetpy-1.89.2}/PKG-INFO +1 -1
- {cloudnetpy-1.89.0 → cloudnetpy-1.89.2}/cloudnetpy/categorize/classify.py +80 -17
- {cloudnetpy-1.89.0 → cloudnetpy-1.89.2}/cloudnetpy/categorize/insects.py +34 -7
- {cloudnetpy-1.89.0 → cloudnetpy-1.89.2}/cloudnetpy/cli.py +67 -12
- {cloudnetpy-1.89.0 → cloudnetpy-1.89.2}/cloudnetpy/version.py +1 -1
- {cloudnetpy-1.89.0 → cloudnetpy-1.89.2/cloudnetpy.egg-info}/PKG-INFO +1 -1
- {cloudnetpy-1.89.0 → cloudnetpy-1.89.2}/LICENSE +0 -0
- {cloudnetpy-1.89.0 → cloudnetpy-1.89.2}/MANIFEST.in +0 -0
- {cloudnetpy-1.89.0 → cloudnetpy-1.89.2}/README.md +0 -0
- {cloudnetpy-1.89.0 → cloudnetpy-1.89.2}/cloudnetpy/__init__.py +0 -0
- {cloudnetpy-1.89.0 → cloudnetpy-1.89.2}/cloudnetpy/categorize/__init__.py +0 -0
- {cloudnetpy-1.89.0 → cloudnetpy-1.89.2}/cloudnetpy/categorize/atmos_utils.py +0 -0
- {cloudnetpy-1.89.0 → cloudnetpy-1.89.2}/cloudnetpy/categorize/attenuation.py +0 -0
- {cloudnetpy-1.89.0 → cloudnetpy-1.89.2}/cloudnetpy/categorize/attenuations/__init__.py +0 -0
- {cloudnetpy-1.89.0 → cloudnetpy-1.89.2}/cloudnetpy/categorize/attenuations/gas_attenuation.py +0 -0
- {cloudnetpy-1.89.0 → cloudnetpy-1.89.2}/cloudnetpy/categorize/attenuations/liquid_attenuation.py +0 -0
- {cloudnetpy-1.89.0 → cloudnetpy-1.89.2}/cloudnetpy/categorize/attenuations/melting_attenuation.py +0 -0
- {cloudnetpy-1.89.0 → cloudnetpy-1.89.2}/cloudnetpy/categorize/attenuations/rain_attenuation.py +0 -0
- {cloudnetpy-1.89.0 → cloudnetpy-1.89.2}/cloudnetpy/categorize/categorize.py +0 -0
- {cloudnetpy-1.89.0 → cloudnetpy-1.89.2}/cloudnetpy/categorize/containers.py +0 -0
- {cloudnetpy-1.89.0 → cloudnetpy-1.89.2}/cloudnetpy/categorize/disdrometer.py +0 -0
- {cloudnetpy-1.89.0 → cloudnetpy-1.89.2}/cloudnetpy/categorize/droplet.py +0 -0
- {cloudnetpy-1.89.0 → cloudnetpy-1.89.2}/cloudnetpy/categorize/falling.py +0 -0
- {cloudnetpy-1.89.0 → cloudnetpy-1.89.2}/cloudnetpy/categorize/freezing.py +0 -0
- {cloudnetpy-1.89.0 → cloudnetpy-1.89.2}/cloudnetpy/categorize/itu.py +0 -0
- {cloudnetpy-1.89.0 → cloudnetpy-1.89.2}/cloudnetpy/categorize/lidar.py +0 -0
- {cloudnetpy-1.89.0 → cloudnetpy-1.89.2}/cloudnetpy/categorize/melting.py +0 -0
- {cloudnetpy-1.89.0 → cloudnetpy-1.89.2}/cloudnetpy/categorize/model.py +0 -0
- {cloudnetpy-1.89.0 → cloudnetpy-1.89.2}/cloudnetpy/categorize/mwr.py +0 -0
- {cloudnetpy-1.89.0 → cloudnetpy-1.89.2}/cloudnetpy/categorize/radar.py +0 -0
- {cloudnetpy-1.89.0 → cloudnetpy-1.89.2}/cloudnetpy/cloudnetarray.py +0 -0
- {cloudnetpy-1.89.0 → cloudnetpy-1.89.2}/cloudnetpy/concat_lib.py +0 -0
- {cloudnetpy-1.89.0 → cloudnetpy-1.89.2}/cloudnetpy/constants.py +0 -0
- {cloudnetpy-1.89.0 → cloudnetpy-1.89.2}/cloudnetpy/datasource.py +0 -0
- {cloudnetpy-1.89.0 → cloudnetpy-1.89.2}/cloudnetpy/disdronator/__init__.py +0 -0
- {cloudnetpy-1.89.0 → cloudnetpy-1.89.2}/cloudnetpy/disdronator/lpm.py +0 -0
- {cloudnetpy-1.89.0 → cloudnetpy-1.89.2}/cloudnetpy/disdronator/parsivel.py +0 -0
- {cloudnetpy-1.89.0 → cloudnetpy-1.89.2}/cloudnetpy/disdronator/rd80.py +0 -0
- {cloudnetpy-1.89.0 → cloudnetpy-1.89.2}/cloudnetpy/disdronator/utils.py +0 -0
- {cloudnetpy-1.89.0 → cloudnetpy-1.89.2}/cloudnetpy/exceptions.py +0 -0
- {cloudnetpy-1.89.0 → cloudnetpy-1.89.2}/cloudnetpy/instruments/__init__.py +0 -0
- {cloudnetpy-1.89.0 → cloudnetpy-1.89.2}/cloudnetpy/instruments/basta.py +0 -0
- {cloudnetpy-1.89.0 → cloudnetpy-1.89.2}/cloudnetpy/instruments/bowtie.py +0 -0
- {cloudnetpy-1.89.0 → cloudnetpy-1.89.2}/cloudnetpy/instruments/ceilo.py +0 -0
- {cloudnetpy-1.89.0 → cloudnetpy-1.89.2}/cloudnetpy/instruments/ceilometer.py +0 -0
- {cloudnetpy-1.89.0 → cloudnetpy-1.89.2}/cloudnetpy/instruments/cl61d.py +0 -0
- {cloudnetpy-1.89.0 → cloudnetpy-1.89.2}/cloudnetpy/instruments/cloudnet_instrument.py +0 -0
- {cloudnetpy-1.89.0 → cloudnetpy-1.89.2}/cloudnetpy/instruments/copernicus.py +0 -0
- {cloudnetpy-1.89.0 → cloudnetpy-1.89.2}/cloudnetpy/instruments/da10.py +0 -0
- {cloudnetpy-1.89.0 → cloudnetpy-1.89.2}/cloudnetpy/instruments/disdrometer/__init__.py +0 -0
- {cloudnetpy-1.89.0 → cloudnetpy-1.89.2}/cloudnetpy/instruments/disdrometer/common.py +0 -0
- {cloudnetpy-1.89.0 → cloudnetpy-1.89.2}/cloudnetpy/instruments/disdrometer/parsivel.py +0 -0
- {cloudnetpy-1.89.0 → cloudnetpy-1.89.2}/cloudnetpy/instruments/disdrometer/rd80.py +0 -0
- {cloudnetpy-1.89.0 → cloudnetpy-1.89.2}/cloudnetpy/instruments/disdrometer/thies.py +0 -0
- {cloudnetpy-1.89.0 → cloudnetpy-1.89.2}/cloudnetpy/instruments/fd12p.py +0 -0
- {cloudnetpy-1.89.0 → cloudnetpy-1.89.2}/cloudnetpy/instruments/galileo.py +0 -0
- {cloudnetpy-1.89.0 → cloudnetpy-1.89.2}/cloudnetpy/instruments/hatpro.py +0 -0
- {cloudnetpy-1.89.0 → cloudnetpy-1.89.2}/cloudnetpy/instruments/instruments.py +0 -0
- {cloudnetpy-1.89.0 → cloudnetpy-1.89.2}/cloudnetpy/instruments/lufft.py +0 -0
- {cloudnetpy-1.89.0 → cloudnetpy-1.89.2}/cloudnetpy/instruments/mira.py +0 -0
- {cloudnetpy-1.89.0 → cloudnetpy-1.89.2}/cloudnetpy/instruments/mrr.py +0 -0
- {cloudnetpy-1.89.0 → cloudnetpy-1.89.2}/cloudnetpy/instruments/nc_lidar.py +0 -0
- {cloudnetpy-1.89.0 → cloudnetpy-1.89.2}/cloudnetpy/instruments/nc_radar.py +0 -0
- {cloudnetpy-1.89.0 → cloudnetpy-1.89.2}/cloudnetpy/instruments/pollyxt.py +0 -0
- {cloudnetpy-1.89.0 → cloudnetpy-1.89.2}/cloudnetpy/instruments/radiometrics.py +0 -0
- {cloudnetpy-1.89.0 → cloudnetpy-1.89.2}/cloudnetpy/instruments/rain_e_h3.py +0 -0
- {cloudnetpy-1.89.0 → cloudnetpy-1.89.2}/cloudnetpy/instruments/rpg.py +0 -0
- {cloudnetpy-1.89.0 → cloudnetpy-1.89.2}/cloudnetpy/instruments/rpg_reader.py +0 -0
- {cloudnetpy-1.89.0 → cloudnetpy-1.89.2}/cloudnetpy/instruments/toa5.py +0 -0
- {cloudnetpy-1.89.0 → cloudnetpy-1.89.2}/cloudnetpy/instruments/vaisala.py +0 -0
- {cloudnetpy-1.89.0 → cloudnetpy-1.89.2}/cloudnetpy/instruments/weather_station.py +0 -0
- {cloudnetpy-1.89.0 → cloudnetpy-1.89.2}/cloudnetpy/metadata.py +0 -0
- {cloudnetpy-1.89.0 → cloudnetpy-1.89.2}/cloudnetpy/model_evaluation/__init__.py +0 -0
- {cloudnetpy-1.89.0 → cloudnetpy-1.89.2}/cloudnetpy/model_evaluation/file_handler.py +0 -0
- {cloudnetpy-1.89.0 → cloudnetpy-1.89.2}/cloudnetpy/model_evaluation/metadata.py +0 -0
- {cloudnetpy-1.89.0 → cloudnetpy-1.89.2}/cloudnetpy/model_evaluation/model_metadata.py +0 -0
- {cloudnetpy-1.89.0 → cloudnetpy-1.89.2}/cloudnetpy/model_evaluation/plotting/__init__.py +0 -0
- {cloudnetpy-1.89.0 → cloudnetpy-1.89.2}/cloudnetpy/model_evaluation/plotting/plot_meta.py +0 -0
- {cloudnetpy-1.89.0 → cloudnetpy-1.89.2}/cloudnetpy/model_evaluation/plotting/plot_tools.py +0 -0
- {cloudnetpy-1.89.0 → cloudnetpy-1.89.2}/cloudnetpy/model_evaluation/plotting/plotting.py +0 -0
- {cloudnetpy-1.89.0 → cloudnetpy-1.89.2}/cloudnetpy/model_evaluation/products/__init__.py +0 -0
- {cloudnetpy-1.89.0 → cloudnetpy-1.89.2}/cloudnetpy/model_evaluation/products/advance_methods.py +0 -0
- {cloudnetpy-1.89.0 → cloudnetpy-1.89.2}/cloudnetpy/model_evaluation/products/grid_methods.py +0 -0
- {cloudnetpy-1.89.0 → cloudnetpy-1.89.2}/cloudnetpy/model_evaluation/products/model_products.py +0 -0
- {cloudnetpy-1.89.0 → cloudnetpy-1.89.2}/cloudnetpy/model_evaluation/products/observation_products.py +0 -0
- {cloudnetpy-1.89.0 → cloudnetpy-1.89.2}/cloudnetpy/model_evaluation/products/product_resampling.py +0 -0
- {cloudnetpy-1.89.0 → cloudnetpy-1.89.2}/cloudnetpy/model_evaluation/products/tools.py +0 -0
- {cloudnetpy-1.89.0 → cloudnetpy-1.89.2}/cloudnetpy/model_evaluation/statistics/__init__.py +0 -0
- {cloudnetpy-1.89.0 → cloudnetpy-1.89.2}/cloudnetpy/model_evaluation/statistics/statistical_methods.py +0 -0
- {cloudnetpy-1.89.0 → cloudnetpy-1.89.2}/cloudnetpy/model_evaluation/tests/__init__.py +0 -0
- {cloudnetpy-1.89.0 → cloudnetpy-1.89.2}/cloudnetpy/model_evaluation/tests/e2e/__init__.py +0 -0
- {cloudnetpy-1.89.0 → cloudnetpy-1.89.2}/cloudnetpy/model_evaluation/tests/e2e/conftest.py +0 -0
- {cloudnetpy-1.89.0 → cloudnetpy-1.89.2}/cloudnetpy/model_evaluation/tests/e2e/process_cf/__init__.py +0 -0
- {cloudnetpy-1.89.0 → cloudnetpy-1.89.2}/cloudnetpy/model_evaluation/tests/e2e/process_cf/main.py +0 -0
- {cloudnetpy-1.89.0 → cloudnetpy-1.89.2}/cloudnetpy/model_evaluation/tests/e2e/process_cf/tests.py +0 -0
- {cloudnetpy-1.89.0 → cloudnetpy-1.89.2}/cloudnetpy/model_evaluation/tests/e2e/process_iwc/__init__.py +0 -0
- {cloudnetpy-1.89.0 → cloudnetpy-1.89.2}/cloudnetpy/model_evaluation/tests/e2e/process_iwc/main.py +0 -0
- {cloudnetpy-1.89.0 → cloudnetpy-1.89.2}/cloudnetpy/model_evaluation/tests/e2e/process_iwc/tests.py +0 -0
- {cloudnetpy-1.89.0 → cloudnetpy-1.89.2}/cloudnetpy/model_evaluation/tests/e2e/process_lwc/__init__.py +0 -0
- {cloudnetpy-1.89.0 → cloudnetpy-1.89.2}/cloudnetpy/model_evaluation/tests/e2e/process_lwc/main.py +0 -0
- {cloudnetpy-1.89.0 → cloudnetpy-1.89.2}/cloudnetpy/model_evaluation/tests/e2e/process_lwc/tests.py +0 -0
- {cloudnetpy-1.89.0 → cloudnetpy-1.89.2}/cloudnetpy/model_evaluation/tests/unit/__init__.py +0 -0
- {cloudnetpy-1.89.0 → cloudnetpy-1.89.2}/cloudnetpy/model_evaluation/tests/unit/conftest.py +0 -0
- {cloudnetpy-1.89.0 → cloudnetpy-1.89.2}/cloudnetpy/model_evaluation/tests/unit/test_advance_methods.py +0 -0
- {cloudnetpy-1.89.0 → cloudnetpy-1.89.2}/cloudnetpy/model_evaluation/tests/unit/test_grid_methods.py +0 -0
- {cloudnetpy-1.89.0 → cloudnetpy-1.89.2}/cloudnetpy/model_evaluation/tests/unit/test_model_products.py +0 -0
- {cloudnetpy-1.89.0 → cloudnetpy-1.89.2}/cloudnetpy/model_evaluation/tests/unit/test_observation_products.py +0 -0
- {cloudnetpy-1.89.0 → cloudnetpy-1.89.2}/cloudnetpy/model_evaluation/tests/unit/test_plot_tools.py +0 -0
- {cloudnetpy-1.89.0 → cloudnetpy-1.89.2}/cloudnetpy/model_evaluation/tests/unit/test_plotting.py +0 -0
- {cloudnetpy-1.89.0 → cloudnetpy-1.89.2}/cloudnetpy/model_evaluation/tests/unit/test_statistical_methods.py +0 -0
- {cloudnetpy-1.89.0 → cloudnetpy-1.89.2}/cloudnetpy/model_evaluation/tests/unit/test_tools.py +0 -0
- {cloudnetpy-1.89.0 → cloudnetpy-1.89.2}/cloudnetpy/model_evaluation/utils.py +0 -0
- {cloudnetpy-1.89.0 → cloudnetpy-1.89.2}/cloudnetpy/output.py +0 -0
- {cloudnetpy-1.89.0 → cloudnetpy-1.89.2}/cloudnetpy/plotting/__init__.py +0 -0
- {cloudnetpy-1.89.0 → cloudnetpy-1.89.2}/cloudnetpy/plotting/plot_meta.py +0 -0
- {cloudnetpy-1.89.0 → cloudnetpy-1.89.2}/cloudnetpy/plotting/plotting.py +0 -0
- {cloudnetpy-1.89.0 → cloudnetpy-1.89.2}/cloudnetpy/products/__init__.py +0 -0
- {cloudnetpy-1.89.0 → cloudnetpy-1.89.2}/cloudnetpy/products/classification.py +0 -0
- {cloudnetpy-1.89.0 → cloudnetpy-1.89.2}/cloudnetpy/products/der.py +0 -0
- {cloudnetpy-1.89.0 → cloudnetpy-1.89.2}/cloudnetpy/products/drizzle.py +0 -0
- {cloudnetpy-1.89.0 → cloudnetpy-1.89.2}/cloudnetpy/products/drizzle_error.py +0 -0
- {cloudnetpy-1.89.0 → cloudnetpy-1.89.2}/cloudnetpy/products/drizzle_tools.py +0 -0
- {cloudnetpy-1.89.0 → cloudnetpy-1.89.2}/cloudnetpy/products/epsilon.py +0 -0
- {cloudnetpy-1.89.0 → cloudnetpy-1.89.2}/cloudnetpy/products/ier.py +0 -0
- {cloudnetpy-1.89.0 → cloudnetpy-1.89.2}/cloudnetpy/products/iwc.py +0 -0
- {cloudnetpy-1.89.0 → cloudnetpy-1.89.2}/cloudnetpy/products/lwc.py +0 -0
- {cloudnetpy-1.89.0 → cloudnetpy-1.89.2}/cloudnetpy/products/mie_lu_tables.nc +0 -0
- {cloudnetpy-1.89.0 → cloudnetpy-1.89.2}/cloudnetpy/products/mwr_tools.py +0 -0
- {cloudnetpy-1.89.0 → cloudnetpy-1.89.2}/cloudnetpy/products/product_tools.py +0 -0
- {cloudnetpy-1.89.0 → cloudnetpy-1.89.2}/cloudnetpy/py.typed +0 -0
- {cloudnetpy-1.89.0 → cloudnetpy-1.89.2}/cloudnetpy/utils.py +0 -0
- {cloudnetpy-1.89.0 → cloudnetpy-1.89.2}/cloudnetpy.egg-info/SOURCES.txt +0 -0
- {cloudnetpy-1.89.0 → cloudnetpy-1.89.2}/cloudnetpy.egg-info/dependency_links.txt +0 -0
- {cloudnetpy-1.89.0 → cloudnetpy-1.89.2}/cloudnetpy.egg-info/entry_points.txt +0 -0
- {cloudnetpy-1.89.0 → cloudnetpy-1.89.2}/cloudnetpy.egg-info/requires.txt +0 -0
- {cloudnetpy-1.89.0 → cloudnetpy-1.89.2}/cloudnetpy.egg-info/top_level.txt +0 -0
- {cloudnetpy-1.89.0 → cloudnetpy-1.89.2}/docs/source/conf.py +0 -0
- {cloudnetpy-1.89.0 → cloudnetpy-1.89.2}/pyproject.toml +0 -0
- {cloudnetpy-1.89.0 → cloudnetpy-1.89.2}/setup.cfg +0 -0
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import numpy as np
|
|
2
2
|
import numpy.typing as npt
|
|
3
3
|
from numpy import ma
|
|
4
|
+
from scipy import ndimage
|
|
4
5
|
|
|
5
6
|
from cloudnetpy import utils
|
|
6
7
|
from cloudnetpy.categorize import (
|
|
@@ -84,19 +85,21 @@ def classify_measurements(data: Observations) -> ClassificationResult:
|
|
|
84
85
|
bits.droplet = droplet.correct_liquid_top(obs, is_liquid, bits.freezing, limit=500)
|
|
85
86
|
bits.insect, insect_prob = insects.find_insects(obs, bits.melting, bits.droplet)
|
|
86
87
|
bits.falling = falling.find_falling_hydrometeors(obs, bits.droplet, bits.insect)
|
|
87
|
-
filtered_ice = _filter_falling(bits)
|
|
88
88
|
for _ in range(5):
|
|
89
89
|
_fix_undetected_melting_layer(bits)
|
|
90
90
|
_filter_insects(bits)
|
|
91
91
|
bits.aerosol = _find_aerosols(obs, bits)
|
|
92
|
-
bits.aerosol[filtered_ice] = False
|
|
93
92
|
_fix_super_cold_liquid(obs, bits)
|
|
94
93
|
_reclassify_ice_with_high_ldr(obs, bits)
|
|
94
|
+
_filter_insects_in_falling(bits)
|
|
95
|
+
_filter_falling(bits, obs)
|
|
96
|
+
|
|
97
|
+
is_clutter = _remove_clutter_in_liquid(obs.is_clutter, bits)
|
|
95
98
|
|
|
96
99
|
return ClassificationResult(
|
|
97
100
|
category_bits=bits,
|
|
98
101
|
is_rain=obs.is_rain,
|
|
99
|
-
is_clutter=
|
|
102
|
+
is_clutter=is_clutter,
|
|
100
103
|
insect_prob=insect_prob,
|
|
101
104
|
liquid_prob=liquid_prob,
|
|
102
105
|
)
|
|
@@ -137,13 +140,50 @@ def _reclassify_ice_with_high_ldr(obs: ClassData, bits: CategoryBits) -> None:
|
|
|
137
140
|
is_ice = bits.falling & bits.freezing & ~bits.droplet & ~bits.melting
|
|
138
141
|
high_ldr = ~ma.getmaskarray(obs.ldr) & (obs.ldr > ldr_limit)
|
|
139
142
|
warm_enough = obs.tw > temp_limit
|
|
140
|
-
|
|
143
|
+
above_melting = utils.ffill(bits.melting)
|
|
144
|
+
reclassify = is_ice & high_ldr & warm_enough & ~above_melting
|
|
141
145
|
bits.insect[reclassify] = True
|
|
142
146
|
bits.falling[reclassify] = False
|
|
143
147
|
has_lidar = ~obs.beta.mask
|
|
144
148
|
bits.aerosol[reclassify & has_lidar] = True
|
|
145
149
|
|
|
146
150
|
|
|
151
|
+
def _filter_insects_in_falling(
|
|
152
|
+
bits: CategoryBits,
|
|
153
|
+
min_falling_size: int = 100,
|
|
154
|
+
dilation: int = 2,
|
|
155
|
+
) -> None:
|
|
156
|
+
"""Reclassifies insect pixels in freezing regions near large falling ice.
|
|
157
|
+
|
|
158
|
+
Insect pixels in freezing temperatures near sufficiently large falling
|
|
159
|
+
hydrometeor regions are almost certainly false positives. Real insects
|
|
160
|
+
cannot survive in freezing conditions, so the freezing constraint
|
|
161
|
+
protects legitimate warm-region insects from being reclassified.
|
|
162
|
+
|
|
163
|
+
Args:
|
|
164
|
+
bits: A :class:`CategoryBits` instance.
|
|
165
|
+
min_falling_size: Minimum size of nearby falling regions required
|
|
166
|
+
to trigger reclassification.
|
|
167
|
+
dilation: Number of pixels to dilate around falling regions.
|
|
168
|
+
"""
|
|
169
|
+
structure = ndimage.generate_binary_structure(2, 1)
|
|
170
|
+
falling_labels, n_falling = ndimage.label(bits.falling, structure=structure)
|
|
171
|
+
if n_falling == 0:
|
|
172
|
+
return
|
|
173
|
+
falling_ids = np.arange(1, n_falling + 1)
|
|
174
|
+
falling_sizes = ndimage.sum(bits.falling, falling_labels, falling_ids)
|
|
175
|
+
large_falling = np.isin(
|
|
176
|
+
falling_labels, falling_ids[falling_sizes >= min_falling_size]
|
|
177
|
+
)
|
|
178
|
+
near_large_falling = ndimage.binary_dilation(
|
|
179
|
+
large_falling, structure, iterations=dilation
|
|
180
|
+
)
|
|
181
|
+
reclassify = bits.insect & bits.freezing & near_large_falling
|
|
182
|
+
bits.falling[reclassify] = True
|
|
183
|
+
bits.insect[reclassify] = False
|
|
184
|
+
bits.aerosol[reclassify] = False
|
|
185
|
+
|
|
186
|
+
|
|
147
187
|
def _fix_super_cold_liquid(obs: ClassData, bits: CategoryBits) -> None:
|
|
148
188
|
"""Supercooled liquid droplets do not exist in atmosphere below around -38 C."""
|
|
149
189
|
t_limit = T0 - 38
|
|
@@ -246,21 +286,44 @@ def _filter_insects(bits: CategoryBits) -> None:
|
|
|
246
286
|
bits.insect = is_insects
|
|
247
287
|
|
|
248
288
|
|
|
249
|
-
def
|
|
289
|
+
def _remove_clutter_in_liquid(
|
|
290
|
+
is_clutter: npt.NDArray,
|
|
291
|
+
bits: CategoryBits,
|
|
292
|
+
) -> npt.NDArray:
|
|
293
|
+
"""Removes clutter that is inside liquid or falling hydrometeor layers.
|
|
294
|
+
|
|
295
|
+
Clutter detection based on near-zero velocity can produce false positives
|
|
296
|
+
inside cloud and drizzle layers. This function removes clutter flags from
|
|
297
|
+
pixels that are adjacent to droplet or falling hydrometeor pixels, and
|
|
298
|
+
reclassifies them as falling hydrometeors.
|
|
299
|
+
"""
|
|
300
|
+
is_clutter = is_clutter.copy()
|
|
301
|
+
liquid_or_falling = bits.droplet | bits.falling
|
|
302
|
+
structure = ndimage.generate_binary_structure(2, 1)
|
|
303
|
+
near_liquid = ndimage.binary_dilation(liquid_or_falling, structure)
|
|
304
|
+
false_clutter = is_clutter & near_liquid
|
|
305
|
+
is_clutter[false_clutter] = False
|
|
306
|
+
bits.falling[false_clutter] = True
|
|
307
|
+
return is_clutter
|
|
308
|
+
|
|
309
|
+
|
|
310
|
+
def _filter_falling(bits: CategoryBits, obs: ClassData) -> None:
|
|
250
311
|
# filter falling ice speckle noise
|
|
312
|
+
is_beta = ~obs.beta.mask
|
|
251
313
|
is_freezing = bits.freezing
|
|
252
314
|
is_falling = bits.falling
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
315
|
+
filtered_out = is_falling & ~np.asarray(
|
|
316
|
+
utils.remove_small_objects(
|
|
317
|
+
is_falling,
|
|
318
|
+
max_size=10,
|
|
319
|
+
connectivity=1,
|
|
320
|
+
),
|
|
321
|
+
dtype=bool,
|
|
257
322
|
)
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
323
|
+
is_falling[filtered_out] = False
|
|
324
|
+
# In warm conditions, these are likely insects
|
|
325
|
+
bits.insect[filtered_out & ~is_freezing] = True
|
|
326
|
+
bits.aerosol[filtered_out & ~is_freezing & is_beta] = True
|
|
327
|
+
# In cold conditions, classify as aerosol if lidar signal is present
|
|
328
|
+
bits.aerosol[filtered_out & is_freezing & is_beta] = True
|
|
264
329
|
bits.falling = is_falling
|
|
265
|
-
bits.insect[insect_ind] = True
|
|
266
|
-
return ice_ind
|
|
@@ -66,7 +66,7 @@ def _get_probabilities(obs: ClassData) -> dict:
|
|
|
66
66
|
smooth_v = _get_smoothed_v(obs)
|
|
67
67
|
lwp_interp = droplet.interpolate_lwp(obs)
|
|
68
68
|
fun = utils.array_to_probability
|
|
69
|
-
|
|
69
|
+
prob: dict = {
|
|
70
70
|
"width": fun(obs.width, 1, 0.3, invert=True) if hasattr(obs, "width") else 1,
|
|
71
71
|
"z_strong": fun(obs.z, 0, 8, invert=True),
|
|
72
72
|
"z_weak": fun(obs.z, -20, 8, invert=True),
|
|
@@ -78,6 +78,19 @@ def _get_probabilities(obs: ClassData) -> dict:
|
|
|
78
78
|
"lwp": utils.transpose(fun(lwp_interp, 0.15, 0.05, invert=True)),
|
|
79
79
|
"v_sigma": fun(obs.v_sigma, 0.01, 0.1),
|
|
80
80
|
}
|
|
81
|
+
if hasattr(obs, "sldr"):
|
|
82
|
+
prob.update(
|
|
83
|
+
{
|
|
84
|
+
"width_narrow": fun(obs.width, 0.3, 0.1, invert=True)
|
|
85
|
+
if hasattr(obs, "width")
|
|
86
|
+
else 1,
|
|
87
|
+
"z_insect": fun(obs.z, -30, 5, invert=True),
|
|
88
|
+
"temp_sldr": fun(obs.tw, 265, 3),
|
|
89
|
+
"v_sigma_sldr": fun(obs.v_sigma, 0.2, 0.15),
|
|
90
|
+
"v_sigma_mask": ma.getmaskarray(obs.v_sigma),
|
|
91
|
+
}
|
|
92
|
+
)
|
|
93
|
+
return prob
|
|
81
94
|
|
|
82
95
|
|
|
83
96
|
def _get_smoothed_v(
|
|
@@ -92,12 +105,26 @@ def _calc_prob_from_ldr(prob: dict) -> npt.NDArray:
|
|
|
92
105
|
"""This is the most reliable proxy for insects."""
|
|
93
106
|
if prob["ldr"] is not None:
|
|
94
107
|
return prob["ldr"] * prob["temp_loose"]
|
|
95
|
-
if
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
108
|
+
if prob["sldr"] is not None:
|
|
109
|
+
# SLDR alone is unreliable at low SNR. Use v_sigma * width as
|
|
110
|
+
# alternative proxy (insects show erratic motion + narrow width).
|
|
111
|
+
# When v_sigma is also unavailable, fall back to weak echo * narrow
|
|
112
|
+
# width -- temperature provides the main discrimination.
|
|
113
|
+
p_sldr = prob["sldr"]
|
|
114
|
+
p_vsig = prob["v_sigma_sldr"]
|
|
115
|
+
p_wn = prob["width_narrow"]
|
|
116
|
+
p_zi = prob["z_insect"]
|
|
117
|
+
# Echo fallback only where v_sigma is unavailable -- if v_sigma
|
|
118
|
+
# exists and is low, that's evidence against insects.
|
|
119
|
+
p_echo = np.where(prob["v_sigma_mask"], p_zi, 0)
|
|
120
|
+
p_proxy = np.maximum(p_sldr, np.maximum(p_vsig, p_echo))
|
|
121
|
+
p_total = p_proxy * p_wn * prob["temp_sldr"]
|
|
122
|
+
# High SLDR at warm temperatures are reliable as is
|
|
123
|
+
p_high_sldr = p_sldr * prob["temp_loose"]
|
|
124
|
+
high = p_high_sldr > 0.9
|
|
125
|
+
p_total[high] = p_high_sldr[high]
|
|
126
|
+
return p_total
|
|
127
|
+
|
|
101
128
|
return np.zeros(prob["z_strong"].shape)
|
|
102
129
|
|
|
103
130
|
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import argparse
|
|
2
2
|
import base64
|
|
3
|
+
import concurrent.futures
|
|
3
4
|
import datetime
|
|
4
5
|
import gzip
|
|
5
6
|
import hashlib
|
|
@@ -88,21 +89,51 @@ def _process_categorize(
|
|
|
88
89
|
input_files: dict, args: argparse.Namespace, client: APIClient
|
|
89
90
|
) -> str | None:
|
|
90
91
|
cat_filepath = _create_categorize_filepath(args)
|
|
92
|
+
instrument_prefs = _parse_instrument_preferences(args.instrument)
|
|
93
|
+
|
|
94
|
+
def fetch_instrument(product: str) -> tuple[str, str | None]:
|
|
95
|
+
source = instrument_prefs.get(product)
|
|
96
|
+
filepath = _fetch_product(args, product, client, source=source)
|
|
97
|
+
if filepath is None and source:
|
|
98
|
+
logging.info(
|
|
99
|
+
"Preferred instrument '%s' not found for %s, using available",
|
|
100
|
+
source,
|
|
101
|
+
product,
|
|
102
|
+
)
|
|
103
|
+
filepath = _fetch_product(args, product, client)
|
|
104
|
+
return product, filepath
|
|
91
105
|
|
|
92
|
-
|
|
93
|
-
|
|
106
|
+
def fetch_mwr() -> tuple[str, str | None]:
|
|
107
|
+
try:
|
|
108
|
+
return "mwr", _fetch_mwr(args, client)
|
|
109
|
+
except (CloudnetAPIError, KeyError, ValueError):
|
|
110
|
+
logging.info("LWP data not available, continuing without it")
|
|
111
|
+
return "mwr", None
|
|
112
|
+
|
|
113
|
+
def fetch_model() -> tuple[str, str | None]:
|
|
114
|
+
return "model", _fetch_model(args, client)
|
|
115
|
+
|
|
116
|
+
with concurrent.futures.ThreadPoolExecutor() as executor:
|
|
117
|
+
futures = [executor.submit(fetch_model)]
|
|
118
|
+
for product in ("radar", "lidar", "disdrometer"):
|
|
119
|
+
if product not in input_files:
|
|
120
|
+
futures.append(executor.submit(fetch_instrument, product))
|
|
121
|
+
|
|
122
|
+
for future in concurrent.futures.as_completed(futures):
|
|
123
|
+
product, filepath = future.result()
|
|
124
|
+
if filepath:
|
|
125
|
+
input_files[product] = filepath
|
|
126
|
+
|
|
127
|
+
# MWR runs after other downloads because its fallback chain may
|
|
128
|
+
# try to download the same radar file fetched above.
|
|
129
|
+
mwr_key, mwr_path = fetch_mwr()
|
|
130
|
+
if mwr_path:
|
|
131
|
+
input_files[mwr_key] = mwr_path
|
|
132
|
+
|
|
133
|
+
if "model" not in input_files:
|
|
94
134
|
logging.info("No model data available for this date.")
|
|
95
135
|
return None
|
|
96
136
|
|
|
97
|
-
for product in ("radar", "lidar", "disdrometer"):
|
|
98
|
-
if product not in input_files and (
|
|
99
|
-
filepath := _fetch_product(args, product, client)
|
|
100
|
-
):
|
|
101
|
-
input_files[product] = filepath
|
|
102
|
-
|
|
103
|
-
if mwr := _fetch_mwr(args, client):
|
|
104
|
-
input_files["mwr"] = mwr
|
|
105
|
-
|
|
106
137
|
try:
|
|
107
138
|
logging.info("Processing categorize...")
|
|
108
139
|
generate_categorize(cast("CategorizeInput", input_files), cat_filepath)
|
|
@@ -338,6 +369,20 @@ def _get_product_sources(
|
|
|
338
369
|
return source_products
|
|
339
370
|
|
|
340
371
|
|
|
372
|
+
def _parse_instrument_preferences(args: list[str] | None) -> dict[str, str]:
|
|
373
|
+
"""Parses instrument preferences like 'radar:mira-35' into a dict."""
|
|
374
|
+
if not args:
|
|
375
|
+
return {}
|
|
376
|
+
prefs = {}
|
|
377
|
+
for arg in args:
|
|
378
|
+
if ":" not in arg:
|
|
379
|
+
msg = f"Invalid instrument format '{arg}', expected 'product:instrument_id'"
|
|
380
|
+
raise argparse.ArgumentTypeError(msg)
|
|
381
|
+
product, instrument_id = arg.split(":", 1)
|
|
382
|
+
prefs[product] = instrument_id
|
|
383
|
+
return prefs
|
|
384
|
+
|
|
385
|
+
|
|
341
386
|
def _parse_instrument(s: str) -> tuple[str, str | None]:
|
|
342
387
|
if "[" in s and s.endswith("]"):
|
|
343
388
|
name = s[: s.index("[")]
|
|
@@ -382,7 +427,6 @@ def _fetch_product(
|
|
|
382
427
|
if m.instrument is not None and m.instrument.instrument_id == source
|
|
383
428
|
]
|
|
384
429
|
if not meta:
|
|
385
|
-
logging.info("No data available for %s", product)
|
|
386
430
|
return None
|
|
387
431
|
if len(meta) > 1:
|
|
388
432
|
logging.info(
|
|
@@ -560,6 +604,17 @@ def main() -> None:
|
|
|
560
604
|
type=lambda arg: _parse_model(arg, client),
|
|
561
605
|
help="Model to use in categorize.",
|
|
562
606
|
)
|
|
607
|
+
parser.add_argument(
|
|
608
|
+
"-i",
|
|
609
|
+
"--instrument",
|
|
610
|
+
type=str,
|
|
611
|
+
action="append",
|
|
612
|
+
help=(
|
|
613
|
+
"Preferred instrument for categorize input, e.g. "
|
|
614
|
+
"'radar:mira-35' or 'lidar:cl61d'. Can be specified multiple times."
|
|
615
|
+
),
|
|
616
|
+
default=None,
|
|
617
|
+
)
|
|
563
618
|
parser.add_argument("--input", type=Path, help="Input path", default="input/")
|
|
564
619
|
parser.add_argument("--output", type=Path, help="Output path", default="output/")
|
|
565
620
|
parser.add_argument(
|
|
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.89.0 → cloudnetpy-1.89.2}/cloudnetpy/categorize/attenuations/gas_attenuation.py
RENAMED
|
File without changes
|
{cloudnetpy-1.89.0 → cloudnetpy-1.89.2}/cloudnetpy/categorize/attenuations/liquid_attenuation.py
RENAMED
|
File without changes
|
{cloudnetpy-1.89.0 → cloudnetpy-1.89.2}/cloudnetpy/categorize/attenuations/melting_attenuation.py
RENAMED
|
File without changes
|
{cloudnetpy-1.89.0 → cloudnetpy-1.89.2}/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
|
{cloudnetpy-1.89.0 → cloudnetpy-1.89.2}/cloudnetpy/model_evaluation/products/advance_methods.py
RENAMED
|
File without changes
|
{cloudnetpy-1.89.0 → cloudnetpy-1.89.2}/cloudnetpy/model_evaluation/products/grid_methods.py
RENAMED
|
File without changes
|
{cloudnetpy-1.89.0 → cloudnetpy-1.89.2}/cloudnetpy/model_evaluation/products/model_products.py
RENAMED
|
File without changes
|
{cloudnetpy-1.89.0 → cloudnetpy-1.89.2}/cloudnetpy/model_evaluation/products/observation_products.py
RENAMED
|
File without changes
|
{cloudnetpy-1.89.0 → cloudnetpy-1.89.2}/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.89.0 → cloudnetpy-1.89.2}/cloudnetpy/model_evaluation/tests/e2e/process_cf/__init__.py
RENAMED
|
File without changes
|
{cloudnetpy-1.89.0 → cloudnetpy-1.89.2}/cloudnetpy/model_evaluation/tests/e2e/process_cf/main.py
RENAMED
|
File without changes
|
{cloudnetpy-1.89.0 → cloudnetpy-1.89.2}/cloudnetpy/model_evaluation/tests/e2e/process_cf/tests.py
RENAMED
|
File without changes
|
|
File without changes
|
{cloudnetpy-1.89.0 → cloudnetpy-1.89.2}/cloudnetpy/model_evaluation/tests/e2e/process_iwc/main.py
RENAMED
|
File without changes
|
{cloudnetpy-1.89.0 → cloudnetpy-1.89.2}/cloudnetpy/model_evaluation/tests/e2e/process_iwc/tests.py
RENAMED
|
File without changes
|
|
File without changes
|
{cloudnetpy-1.89.0 → cloudnetpy-1.89.2}/cloudnetpy/model_evaluation/tests/e2e/process_lwc/main.py
RENAMED
|
File without changes
|
{cloudnetpy-1.89.0 → cloudnetpy-1.89.2}/cloudnetpy/model_evaluation/tests/e2e/process_lwc/tests.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{cloudnetpy-1.89.0 → cloudnetpy-1.89.2}/cloudnetpy/model_evaluation/tests/unit/test_grid_methods.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{cloudnetpy-1.89.0 → cloudnetpy-1.89.2}/cloudnetpy/model_evaluation/tests/unit/test_plot_tools.py
RENAMED
|
File without changes
|
{cloudnetpy-1.89.0 → cloudnetpy-1.89.2}/cloudnetpy/model_evaluation/tests/unit/test_plotting.py
RENAMED
|
File without changes
|
|
File without changes
|
{cloudnetpy-1.89.0 → cloudnetpy-1.89.2}/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
|