cloudnetpy 1.97.0__tar.gz → 1.97.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.97.0/cloudnetpy.egg-info → cloudnetpy-1.97.2}/PKG-INFO +1 -1
- {cloudnetpy-1.97.0 → cloudnetpy-1.97.2}/cloudnetpy/disdronator/parsivel.py +39 -0
- {cloudnetpy-1.97.0 → cloudnetpy-1.97.2}/cloudnetpy/instruments/kazr.py +26 -3
- {cloudnetpy-1.97.0 → cloudnetpy-1.97.2}/cloudnetpy/model_evaluation/metadata.py +3 -1
- {cloudnetpy-1.97.0 → cloudnetpy-1.97.2}/cloudnetpy/model_evaluation/products/advance_methods.py +35 -23
- {cloudnetpy-1.97.0 → cloudnetpy-1.97.2}/cloudnetpy/model_evaluation/products/grid_methods.py +23 -15
- {cloudnetpy-1.97.0 → cloudnetpy-1.97.2}/cloudnetpy/model_evaluation/products/observation_products.py +8 -4
- {cloudnetpy-1.97.0 → cloudnetpy-1.97.2}/cloudnetpy/model_evaluation/products/tools.py +11 -0
- {cloudnetpy-1.97.0 → cloudnetpy-1.97.2}/cloudnetpy/model_evaluation/tests/unit/test_advance_methods.py +26 -6
- {cloudnetpy-1.97.0 → cloudnetpy-1.97.2}/cloudnetpy/model_evaluation/tests/unit/test_grid_methods.py +9 -0
- {cloudnetpy-1.97.0 → cloudnetpy-1.97.2}/cloudnetpy/model_evaluation/tests/unit/test_observation_products.py +2 -1
- {cloudnetpy-1.97.0 → cloudnetpy-1.97.2}/cloudnetpy/model_evaluation/tests/unit/test_tools.py +14 -0
- {cloudnetpy-1.97.0 → cloudnetpy-1.97.2}/cloudnetpy/plotting/plot_meta.py +1 -1
- {cloudnetpy-1.97.0 → cloudnetpy-1.97.2}/cloudnetpy/plotting/plotting.py +1 -0
- {cloudnetpy-1.97.0 → cloudnetpy-1.97.2}/cloudnetpy/products/lwc.py +15 -8
- {cloudnetpy-1.97.0 → cloudnetpy-1.97.2}/cloudnetpy/version.py +1 -1
- {cloudnetpy-1.97.0 → cloudnetpy-1.97.2/cloudnetpy.egg-info}/PKG-INFO +1 -1
- {cloudnetpy-1.97.0 → cloudnetpy-1.97.2}/LICENSE +0 -0
- {cloudnetpy-1.97.0 → cloudnetpy-1.97.2}/MANIFEST.in +0 -0
- {cloudnetpy-1.97.0 → cloudnetpy-1.97.2}/README.md +0 -0
- {cloudnetpy-1.97.0 → cloudnetpy-1.97.2}/cloudnetpy/__init__.py +0 -0
- {cloudnetpy-1.97.0 → cloudnetpy-1.97.2}/cloudnetpy/arm.py +0 -0
- {cloudnetpy-1.97.0 → cloudnetpy-1.97.2}/cloudnetpy/categorize/__init__.py +0 -0
- {cloudnetpy-1.97.0 → cloudnetpy-1.97.2}/cloudnetpy/categorize/atmos_utils.py +0 -0
- {cloudnetpy-1.97.0 → cloudnetpy-1.97.2}/cloudnetpy/categorize/attenuation.py +0 -0
- {cloudnetpy-1.97.0 → cloudnetpy-1.97.2}/cloudnetpy/categorize/attenuations/__init__.py +0 -0
- {cloudnetpy-1.97.0 → cloudnetpy-1.97.2}/cloudnetpy/categorize/attenuations/gas_attenuation.py +0 -0
- {cloudnetpy-1.97.0 → cloudnetpy-1.97.2}/cloudnetpy/categorize/attenuations/liquid_attenuation.py +0 -0
- {cloudnetpy-1.97.0 → cloudnetpy-1.97.2}/cloudnetpy/categorize/attenuations/melting_attenuation.py +0 -0
- {cloudnetpy-1.97.0 → cloudnetpy-1.97.2}/cloudnetpy/categorize/attenuations/rain_attenuation.py +0 -0
- {cloudnetpy-1.97.0 → cloudnetpy-1.97.2}/cloudnetpy/categorize/categorize.py +0 -0
- {cloudnetpy-1.97.0 → cloudnetpy-1.97.2}/cloudnetpy/categorize/classify.py +0 -0
- {cloudnetpy-1.97.0 → cloudnetpy-1.97.2}/cloudnetpy/categorize/containers.py +0 -0
- {cloudnetpy-1.97.0 → cloudnetpy-1.97.2}/cloudnetpy/categorize/disdrometer.py +0 -0
- {cloudnetpy-1.97.0 → cloudnetpy-1.97.2}/cloudnetpy/categorize/droplet.py +0 -0
- {cloudnetpy-1.97.0 → cloudnetpy-1.97.2}/cloudnetpy/categorize/falling.py +0 -0
- {cloudnetpy-1.97.0 → cloudnetpy-1.97.2}/cloudnetpy/categorize/freezing.py +0 -0
- {cloudnetpy-1.97.0 → cloudnetpy-1.97.2}/cloudnetpy/categorize/insects.py +0 -0
- {cloudnetpy-1.97.0 → cloudnetpy-1.97.2}/cloudnetpy/categorize/lidar.py +0 -0
- {cloudnetpy-1.97.0 → cloudnetpy-1.97.2}/cloudnetpy/categorize/melting.py +0 -0
- {cloudnetpy-1.97.0 → cloudnetpy-1.97.2}/cloudnetpy/categorize/model.py +0 -0
- {cloudnetpy-1.97.0 → cloudnetpy-1.97.2}/cloudnetpy/categorize/mwr.py +0 -0
- {cloudnetpy-1.97.0 → cloudnetpy-1.97.2}/cloudnetpy/categorize/radar.py +0 -0
- {cloudnetpy-1.97.0 → cloudnetpy-1.97.2}/cloudnetpy/cli.py +0 -0
- {cloudnetpy-1.97.0 → cloudnetpy-1.97.2}/cloudnetpy/cloudnetarray.py +0 -0
- {cloudnetpy-1.97.0 → cloudnetpy-1.97.2}/cloudnetpy/concat_lib.py +0 -0
- {cloudnetpy-1.97.0 → cloudnetpy-1.97.2}/cloudnetpy/constants.py +0 -0
- {cloudnetpy-1.97.0 → cloudnetpy-1.97.2}/cloudnetpy/datasource.py +0 -0
- {cloudnetpy-1.97.0 → cloudnetpy-1.97.2}/cloudnetpy/disdronator/__init__.py +0 -0
- {cloudnetpy-1.97.0 → cloudnetpy-1.97.2}/cloudnetpy/disdronator/lpm.py +0 -0
- {cloudnetpy-1.97.0 → cloudnetpy-1.97.2}/cloudnetpy/disdronator/process.py +0 -0
- {cloudnetpy-1.97.0 → cloudnetpy-1.97.2}/cloudnetpy/disdronator/rd80.py +0 -0
- {cloudnetpy-1.97.0 → cloudnetpy-1.97.2}/cloudnetpy/disdronator/utils.py +0 -0
- {cloudnetpy-1.97.0 → cloudnetpy-1.97.2}/cloudnetpy/exceptions.py +0 -0
- {cloudnetpy-1.97.0 → cloudnetpy-1.97.2}/cloudnetpy/instruments/__init__.py +0 -0
- {cloudnetpy-1.97.0 → cloudnetpy-1.97.2}/cloudnetpy/instruments/arm_ceilo.py +0 -0
- {cloudnetpy-1.97.0 → cloudnetpy-1.97.2}/cloudnetpy/instruments/arm_ld.py +0 -0
- {cloudnetpy-1.97.0 → cloudnetpy-1.97.2}/cloudnetpy/instruments/arm_utils.py +0 -0
- {cloudnetpy-1.97.0 → cloudnetpy-1.97.2}/cloudnetpy/instruments/basta.py +0 -0
- {cloudnetpy-1.97.0 → cloudnetpy-1.97.2}/cloudnetpy/instruments/bowtie.py +0 -0
- {cloudnetpy-1.97.0 → cloudnetpy-1.97.2}/cloudnetpy/instruments/ceilo.py +0 -0
- {cloudnetpy-1.97.0 → cloudnetpy-1.97.2}/cloudnetpy/instruments/ceilometer.py +0 -0
- {cloudnetpy-1.97.0 → cloudnetpy-1.97.2}/cloudnetpy/instruments/cl61d.py +0 -0
- {cloudnetpy-1.97.0 → cloudnetpy-1.97.2}/cloudnetpy/instruments/cloudnet_instrument.py +0 -0
- {cloudnetpy-1.97.0 → cloudnetpy-1.97.2}/cloudnetpy/instruments/copernicus.py +0 -0
- {cloudnetpy-1.97.0 → cloudnetpy-1.97.2}/cloudnetpy/instruments/da10.py +0 -0
- {cloudnetpy-1.97.0 → cloudnetpy-1.97.2}/cloudnetpy/instruments/dealias.py +0 -0
- {cloudnetpy-1.97.0 → cloudnetpy-1.97.2}/cloudnetpy/instruments/disdrometer.py +0 -0
- {cloudnetpy-1.97.0 → cloudnetpy-1.97.2}/cloudnetpy/instruments/fd12p.py +0 -0
- {cloudnetpy-1.97.0 → cloudnetpy-1.97.2}/cloudnetpy/instruments/galileo.py +0 -0
- {cloudnetpy-1.97.0 → cloudnetpy-1.97.2}/cloudnetpy/instruments/hatpro.py +0 -0
- {cloudnetpy-1.97.0 → cloudnetpy-1.97.2}/cloudnetpy/instruments/instruments.py +0 -0
- {cloudnetpy-1.97.0 → cloudnetpy-1.97.2}/cloudnetpy/instruments/lufft.py +0 -0
- {cloudnetpy-1.97.0 → cloudnetpy-1.97.2}/cloudnetpy/instruments/mira.py +0 -0
- {cloudnetpy-1.97.0 → cloudnetpy-1.97.2}/cloudnetpy/instruments/mmcr.py +0 -0
- {cloudnetpy-1.97.0 → cloudnetpy-1.97.2}/cloudnetpy/instruments/mrr.py +0 -0
- {cloudnetpy-1.97.0 → cloudnetpy-1.97.2}/cloudnetpy/instruments/mwr3c.py +0 -0
- {cloudnetpy-1.97.0 → cloudnetpy-1.97.2}/cloudnetpy/instruments/mwrlos.py +0 -0
- {cloudnetpy-1.97.0 → cloudnetpy-1.97.2}/cloudnetpy/instruments/nc_lidar.py +0 -0
- {cloudnetpy-1.97.0 → cloudnetpy-1.97.2}/cloudnetpy/instruments/nc_radar.py +0 -0
- {cloudnetpy-1.97.0 → cloudnetpy-1.97.2}/cloudnetpy/instruments/pollyxt.py +0 -0
- {cloudnetpy-1.97.0 → cloudnetpy-1.97.2}/cloudnetpy/instruments/radiometrics.py +0 -0
- {cloudnetpy-1.97.0 → cloudnetpy-1.97.2}/cloudnetpy/instruments/rain_e_h3.py +0 -0
- {cloudnetpy-1.97.0 → cloudnetpy-1.97.2}/cloudnetpy/instruments/rpg.py +0 -0
- {cloudnetpy-1.97.0 → cloudnetpy-1.97.2}/cloudnetpy/instruments/rpg_reader.py +0 -0
- {cloudnetpy-1.97.0 → cloudnetpy-1.97.2}/cloudnetpy/instruments/toa5.py +0 -0
- {cloudnetpy-1.97.0 → cloudnetpy-1.97.2}/cloudnetpy/instruments/vaisala.py +0 -0
- {cloudnetpy-1.97.0 → cloudnetpy-1.97.2}/cloudnetpy/instruments/weather_radar.py +0 -0
- {cloudnetpy-1.97.0 → cloudnetpy-1.97.2}/cloudnetpy/instruments/weather_station.py +0 -0
- {cloudnetpy-1.97.0 → cloudnetpy-1.97.2}/cloudnetpy/metadata.py +0 -0
- {cloudnetpy-1.97.0 → cloudnetpy-1.97.2}/cloudnetpy/model_evaluation/__init__.py +0 -0
- {cloudnetpy-1.97.0 → cloudnetpy-1.97.2}/cloudnetpy/model_evaluation/file_handler.py +0 -0
- {cloudnetpy-1.97.0 → cloudnetpy-1.97.2}/cloudnetpy/model_evaluation/model_metadata.py +0 -0
- {cloudnetpy-1.97.0 → cloudnetpy-1.97.2}/cloudnetpy/model_evaluation/plotting/__init__.py +0 -0
- {cloudnetpy-1.97.0 → cloudnetpy-1.97.2}/cloudnetpy/model_evaluation/plotting/plot_meta.py +0 -0
- {cloudnetpy-1.97.0 → cloudnetpy-1.97.2}/cloudnetpy/model_evaluation/plotting/plot_tools.py +0 -0
- {cloudnetpy-1.97.0 → cloudnetpy-1.97.2}/cloudnetpy/model_evaluation/plotting/plotting.py +0 -0
- {cloudnetpy-1.97.0 → cloudnetpy-1.97.2}/cloudnetpy/model_evaluation/products/__init__.py +0 -0
- {cloudnetpy-1.97.0 → cloudnetpy-1.97.2}/cloudnetpy/model_evaluation/products/model_products.py +0 -0
- {cloudnetpy-1.97.0 → cloudnetpy-1.97.2}/cloudnetpy/model_evaluation/products/product_resampling.py +0 -0
- {cloudnetpy-1.97.0 → cloudnetpy-1.97.2}/cloudnetpy/model_evaluation/statistics/__init__.py +0 -0
- {cloudnetpy-1.97.0 → cloudnetpy-1.97.2}/cloudnetpy/model_evaluation/statistics/statistical_methods.py +0 -0
- {cloudnetpy-1.97.0 → cloudnetpy-1.97.2}/cloudnetpy/model_evaluation/tests/__init__.py +0 -0
- {cloudnetpy-1.97.0 → cloudnetpy-1.97.2}/cloudnetpy/model_evaluation/tests/e2e/__init__.py +0 -0
- {cloudnetpy-1.97.0 → cloudnetpy-1.97.2}/cloudnetpy/model_evaluation/tests/e2e/conftest.py +0 -0
- {cloudnetpy-1.97.0 → cloudnetpy-1.97.2}/cloudnetpy/model_evaluation/tests/e2e/process_cf/__init__.py +0 -0
- {cloudnetpy-1.97.0 → cloudnetpy-1.97.2}/cloudnetpy/model_evaluation/tests/e2e/process_cf/main.py +0 -0
- {cloudnetpy-1.97.0 → cloudnetpy-1.97.2}/cloudnetpy/model_evaluation/tests/e2e/process_cf/tests.py +0 -0
- {cloudnetpy-1.97.0 → cloudnetpy-1.97.2}/cloudnetpy/model_evaluation/tests/e2e/process_iwc/__init__.py +0 -0
- {cloudnetpy-1.97.0 → cloudnetpy-1.97.2}/cloudnetpy/model_evaluation/tests/e2e/process_iwc/main.py +0 -0
- {cloudnetpy-1.97.0 → cloudnetpy-1.97.2}/cloudnetpy/model_evaluation/tests/e2e/process_iwc/tests.py +0 -0
- {cloudnetpy-1.97.0 → cloudnetpy-1.97.2}/cloudnetpy/model_evaluation/tests/e2e/process_lwc/__init__.py +0 -0
- {cloudnetpy-1.97.0 → cloudnetpy-1.97.2}/cloudnetpy/model_evaluation/tests/e2e/process_lwc/main.py +0 -0
- {cloudnetpy-1.97.0 → cloudnetpy-1.97.2}/cloudnetpy/model_evaluation/tests/e2e/process_lwc/tests.py +0 -0
- {cloudnetpy-1.97.0 → cloudnetpy-1.97.2}/cloudnetpy/model_evaluation/tests/unit/__init__.py +0 -0
- {cloudnetpy-1.97.0 → cloudnetpy-1.97.2}/cloudnetpy/model_evaluation/tests/unit/conftest.py +0 -0
- {cloudnetpy-1.97.0 → cloudnetpy-1.97.2}/cloudnetpy/model_evaluation/tests/unit/test_model_products.py +0 -0
- {cloudnetpy-1.97.0 → cloudnetpy-1.97.2}/cloudnetpy/model_evaluation/tests/unit/test_plot_tools.py +0 -0
- {cloudnetpy-1.97.0 → cloudnetpy-1.97.2}/cloudnetpy/model_evaluation/tests/unit/test_plotting.py +0 -0
- {cloudnetpy-1.97.0 → cloudnetpy-1.97.2}/cloudnetpy/model_evaluation/tests/unit/test_statistical_methods.py +0 -0
- {cloudnetpy-1.97.0 → cloudnetpy-1.97.2}/cloudnetpy/model_evaluation/utils.py +0 -0
- {cloudnetpy-1.97.0 → cloudnetpy-1.97.2}/cloudnetpy/output.py +0 -0
- {cloudnetpy-1.97.0 → cloudnetpy-1.97.2}/cloudnetpy/plotting/__init__.py +0 -0
- {cloudnetpy-1.97.0 → cloudnetpy-1.97.2}/cloudnetpy/products/__init__.py +0 -0
- {cloudnetpy-1.97.0 → cloudnetpy-1.97.2}/cloudnetpy/products/classification.py +0 -0
- {cloudnetpy-1.97.0 → cloudnetpy-1.97.2}/cloudnetpy/products/cod.py +0 -0
- {cloudnetpy-1.97.0 → cloudnetpy-1.97.2}/cloudnetpy/products/der.py +0 -0
- {cloudnetpy-1.97.0 → cloudnetpy-1.97.2}/cloudnetpy/products/drizzle.py +0 -0
- {cloudnetpy-1.97.0 → cloudnetpy-1.97.2}/cloudnetpy/products/drizzle_error.py +0 -0
- {cloudnetpy-1.97.0 → cloudnetpy-1.97.2}/cloudnetpy/products/drizzle_tools.py +0 -0
- {cloudnetpy-1.97.0 → cloudnetpy-1.97.2}/cloudnetpy/products/epsilon_lidar.py +0 -0
- {cloudnetpy-1.97.0 → cloudnetpy-1.97.2}/cloudnetpy/products/epsilon_radar.py +0 -0
- {cloudnetpy-1.97.0 → cloudnetpy-1.97.2}/cloudnetpy/products/ier.py +0 -0
- {cloudnetpy-1.97.0 → cloudnetpy-1.97.2}/cloudnetpy/products/iwc.py +0 -0
- {cloudnetpy-1.97.0 → cloudnetpy-1.97.2}/cloudnetpy/products/iwc_weather_radar.py +0 -0
- {cloudnetpy-1.97.0 → cloudnetpy-1.97.2}/cloudnetpy/products/mie_lu_tables.nc +0 -0
- {cloudnetpy-1.97.0 → cloudnetpy-1.97.2}/cloudnetpy/products/mwr_tools.py +0 -0
- {cloudnetpy-1.97.0 → cloudnetpy-1.97.2}/cloudnetpy/products/product_tools.py +0 -0
- {cloudnetpy-1.97.0 → cloudnetpy-1.97.2}/cloudnetpy/py.typed +0 -0
- {cloudnetpy-1.97.0 → cloudnetpy-1.97.2}/cloudnetpy/utils.py +0 -0
- {cloudnetpy-1.97.0 → cloudnetpy-1.97.2}/cloudnetpy.egg-info/SOURCES.txt +0 -0
- {cloudnetpy-1.97.0 → cloudnetpy-1.97.2}/cloudnetpy.egg-info/dependency_links.txt +0 -0
- {cloudnetpy-1.97.0 → cloudnetpy-1.97.2}/cloudnetpy.egg-info/entry_points.txt +0 -0
- {cloudnetpy-1.97.0 → cloudnetpy-1.97.2}/cloudnetpy.egg-info/requires.txt +0 -0
- {cloudnetpy-1.97.0 → cloudnetpy-1.97.2}/cloudnetpy.egg-info/top_level.txt +0 -0
- {cloudnetpy-1.97.0 → cloudnetpy-1.97.2}/docs/source/conf.py +0 -0
- {cloudnetpy-1.97.0 → cloudnetpy-1.97.2}/pyproject.toml +0 -0
- {cloudnetpy-1.97.0 → cloudnetpy-1.97.2}/setup.cfg +0 -0
|
@@ -237,6 +237,32 @@ LEIPZIG_KEYS = {
|
|
|
237
237
|
FLOAT_KEYS = {1, 2, 7, 16, 17, 24, 30, 31, 33, 34, 35}
|
|
238
238
|
INT_KEYS = {3, 4, 8, 9, 10, 11, 12, 18, 25, 26, 27, 28, 60}
|
|
239
239
|
|
|
240
|
+
KOLSASS_TELEGRAM: list[str | int | None] = [
|
|
241
|
+
"%Y-%m-%d %H:%M:%S",
|
|
242
|
+
18,
|
|
243
|
+
20,
|
|
244
|
+
25,
|
|
245
|
+
1,
|
|
246
|
+
2,
|
|
247
|
+
7,
|
|
248
|
+
9,
|
|
249
|
+
35,
|
|
250
|
+
11,
|
|
251
|
+
90,
|
|
252
|
+
91,
|
|
253
|
+
93,
|
|
254
|
+
13,
|
|
255
|
+
14,
|
|
256
|
+
15,
|
|
257
|
+
16,
|
|
258
|
+
17,
|
|
259
|
+
10,
|
|
260
|
+
12,
|
|
261
|
+
26,
|
|
262
|
+
27,
|
|
263
|
+
28,
|
|
264
|
+
]
|
|
265
|
+
|
|
240
266
|
|
|
241
267
|
def _read_lines(
|
|
242
268
|
telegram: Sequence[str | int | None],
|
|
@@ -387,6 +413,17 @@ def _read_granada(filename: str | PathLike) -> ParsivelOutput:
|
|
|
387
413
|
return _read_lines(telegram, content, b",", b".")
|
|
388
414
|
|
|
389
415
|
|
|
416
|
+
def _read_kolsass(filename: str | PathLike) -> ParsivelOutput:
|
|
417
|
+
with open(filename, "rb") as f:
|
|
418
|
+
_header = f.readline()
|
|
419
|
+
content = f.read()
|
|
420
|
+
# Fix "0-9" to "-9" (e.g. 2025-01-01)
|
|
421
|
+
content = re.sub(rb",0-(\d),", rb",-\1,", content)
|
|
422
|
+
# Remove decimal part from integers (e.g. 2025-01-02)
|
|
423
|
+
content = re.sub(rb"(\d+)\.0,", rb"\1,", content, flags=re.MULTILINE)
|
|
424
|
+
return _read_lines(KOLSASS_TELEGRAM, content, b",", b".")
|
|
425
|
+
|
|
426
|
+
|
|
390
427
|
def _read_headerless(
|
|
391
428
|
filename: str | PathLike,
|
|
392
429
|
telegram: Sequence[int | str | None],
|
|
@@ -516,6 +553,8 @@ def _read_parsivel(
|
|
|
516
553
|
head = f.read(50)
|
|
517
554
|
if head.startswith(b"datetime_utc;"):
|
|
518
555
|
return _read_pyatmoslogger(filename)
|
|
556
|
+
if head.startswith(b"datetime_utc,sensor status,sensor time,"):
|
|
557
|
+
return _read_kolsass(filename)
|
|
519
558
|
if head.startswith(b"Date;Time;"):
|
|
520
559
|
return _read_asdo(filename)
|
|
521
560
|
if head.startswith(b'"TOA5"'):
|
|
@@ -164,10 +164,10 @@ class Kazr(CloudnetInstrument):
|
|
|
164
164
|
|
|
165
165
|
def read_files(self) -> None:
|
|
166
166
|
"""Reads and concatenates all input files."""
|
|
167
|
+
with netCDF4.Dataset(self._find_reference_file()) as nc:
|
|
168
|
+
self._init_metadata(nc)
|
|
167
169
|
for file in self.files:
|
|
168
170
|
with netCDF4.Dataset(file) as nc:
|
|
169
|
-
if not self.keymap:
|
|
170
|
-
self._init_metadata(nc)
|
|
171
171
|
try:
|
|
172
172
|
self._read_file(nc)
|
|
173
173
|
except (KeyError, ValueError) as err:
|
|
@@ -350,6 +350,24 @@ class Kazr(CloudnetInstrument):
|
|
|
350
350
|
msg = "All radar data are masked"
|
|
351
351
|
raise RadarDataError(msg)
|
|
352
352
|
|
|
353
|
+
def _find_reference_file(self) -> Path:
|
|
354
|
+
"""Returns the first file using the range grid with the most profiles."""
|
|
355
|
+
n_profiles: dict[int, int] = {}
|
|
356
|
+
first_file: dict[int, Path] = {}
|
|
357
|
+
for file in self.files:
|
|
358
|
+
with netCDF4.Dataset(file) as nc:
|
|
359
|
+
if "range" not in nc.dimensions or "time" not in nc.dimensions:
|
|
360
|
+
continue
|
|
361
|
+
n_range = nc.dimensions["range"].size
|
|
362
|
+
n_profiles[n_range] = (
|
|
363
|
+
n_profiles.get(n_range, 0) + nc.dimensions["time"].size
|
|
364
|
+
)
|
|
365
|
+
first_file.setdefault(n_range, file)
|
|
366
|
+
if not first_file:
|
|
367
|
+
msg = "No valid KAZR files found"
|
|
368
|
+
raise ValidTimeStampError(msg)
|
|
369
|
+
return first_file[max(n_profiles, key=lambda k: n_profiles[k])]
|
|
370
|
+
|
|
353
371
|
def _init_metadata(self, nc: netCDF4.Dataset) -> None:
|
|
354
372
|
if "reflectivity" in nc.variables:
|
|
355
373
|
self.keymap = KEYMAP_CFR
|
|
@@ -366,6 +384,7 @@ class Kazr(CloudnetInstrument):
|
|
|
366
384
|
if bias is not None:
|
|
367
385
|
self.offset_applied = _parse_bias(bias)
|
|
368
386
|
self.serial_number = getattr(nc, "serial_number", None) or None
|
|
387
|
+
self.date = _parse_time_units(nc["time"].units).date()
|
|
369
388
|
self.append_data(np.array(nc["range"][:], dtype=float), "range")
|
|
370
389
|
self.append_data(0.0, "zenith_angle")
|
|
371
390
|
self.append_data(self._read_frequency(nc), "radar_frequency")
|
|
@@ -421,7 +440,11 @@ class Kazr(CloudnetInstrument):
|
|
|
421
440
|
@staticmethod
|
|
422
441
|
def _read_frequency(nc: netCDF4.Dataset) -> float:
|
|
423
442
|
if "frequency" in nc.variables:
|
|
424
|
-
|
|
443
|
+
frequency = ma.mean(nc["frequency"][:])
|
|
444
|
+
if not ma.is_masked(frequency) and np.isfinite(frequency):
|
|
445
|
+
return float(frequency / 1e9) # Hz -> GHz
|
|
446
|
+
# E.g. bnfkazr2cfrgeqcM1.b1 files have only a fill value
|
|
447
|
+
logging.warning("Radar frequency missing from file, using default")
|
|
425
448
|
value = _parse_global_attribute(nc, "radar_operating_frequency")
|
|
426
449
|
if value is not None:
|
|
427
450
|
return value
|
|
@@ -171,7 +171,9 @@ REGRID_PRODUCT_ATTRIBUTES = {
|
|
|
171
171
|
"This variable is the observed mean ice water content derived from radar\n"
|
|
172
172
|
"reflectivity factor averaged onto the model grid with height and time.\n"
|
|
173
173
|
"The formula has been applied where the categorization data has\n"
|
|
174
|
-
"diagnosed that the radar echo is due to ice
|
|
174
|
+
"diagnosed that the radar echo is due to ice; pixels without ice count\n"
|
|
175
|
+
"as zero and unreliable retrievals (e.g. rain) are excluded, so the\n"
|
|
176
|
+
"value is a grid-box mean comparable to the model ice water content."
|
|
175
177
|
),
|
|
176
178
|
dimensions=("time", "level"),
|
|
177
179
|
),
|
{cloudnetpy-1.97.0 → cloudnetpy-1.97.2}/cloudnetpy/model_evaluation/products/advance_methods.py
RENAMED
|
@@ -6,6 +6,7 @@ from numpy import ma
|
|
|
6
6
|
|
|
7
7
|
import cloudnetpy.utils as cl_tools
|
|
8
8
|
from cloudnetpy.constants import T_FREEZING
|
|
9
|
+
from cloudnetpy.exceptions import InvalidSourceFileError
|
|
9
10
|
from cloudnetpy.model_evaluation.model_metadata import MODEL_PREFIX
|
|
10
11
|
from cloudnetpy.model_evaluation.products.model_products import ModelManager
|
|
11
12
|
from cloudnetpy.model_evaluation.products.observation_products import ObservationManager
|
|
@@ -15,6 +16,8 @@ from cloudnetpy.products.product_tools import (
|
|
|
15
16
|
z_to_iwc,
|
|
16
17
|
)
|
|
17
18
|
|
|
19
|
+
MAX_WIND_SHEAR = 20.0 # m s-1 km-1
|
|
20
|
+
|
|
18
21
|
|
|
19
22
|
class AdvanceProductMethods:
|
|
20
23
|
"""Class that adds advance methods of product to nc-file.
|
|
@@ -53,7 +56,8 @@ class AdvanceProductMethods:
|
|
|
53
56
|
t_screened = self.remove_extra_levels(temperature - T_FREEZING)
|
|
54
57
|
iwc, lwc = (self._model_obj.get_water_content(var) for var in ["iwc", "lwc"])
|
|
55
58
|
coeffs = self.set_frequency_parameters()
|
|
56
|
-
|
|
59
|
+
# Same dielectric factor scaling of Z as in the IWC product.
|
|
60
|
+
z_sen = self.fit_z_sensitivity(h) + 10 * np.log10(coeffs.K2liquid0 / 0.93)
|
|
57
61
|
cf_filtered = self.filter_high_iwc_low_cf(cf, iwc, lwc)
|
|
58
62
|
cloud_iwc, ice_ind = self.find_ice_in_clouds(cf_filtered, iwc, lwc)
|
|
59
63
|
variance_iwc = self.iwc_variance(h, ice_ind)
|
|
@@ -102,15 +106,14 @@ class AdvanceProductMethods:
|
|
|
102
106
|
if self._obs_obj.z_sensitivity is None:
|
|
103
107
|
msg = "No z_sensitivity in observation file"
|
|
104
108
|
raise ValueError(msg)
|
|
105
|
-
if self._obs_obj.
|
|
106
|
-
msg = "
|
|
107
|
-
raise
|
|
109
|
+
if self._obs_obj.height_agl is None:
|
|
110
|
+
msg = "Observation file is missing height or altitude"
|
|
111
|
+
raise InvalidSourceFileError(msg)
|
|
108
112
|
# Interpolate at the model level heights: rebinning assumes evenly
|
|
109
113
|
# spaced levels, which maps stretched model levels to wrong heights.
|
|
110
|
-
obs_height =
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
obs_height = np.asarray(ma.filled(obs_height, np.nan), dtype=float)
|
|
114
|
+
obs_height = np.asarray(
|
|
115
|
+
ma.filled(self._obs_obj.height_agl, np.nan), dtype=float
|
|
116
|
+
)
|
|
114
117
|
z_obs = np.asarray(ma.filled(self._obs_obj.z_sensitivity, np.nan), dtype=float)
|
|
115
118
|
is_valid = np.isfinite(obs_height) & np.isfinite(z_obs)
|
|
116
119
|
model_height = np.asarray(ma.filled(h, np.nan), dtype=float)
|
|
@@ -167,36 +170,45 @@ class AdvanceProductMethods:
|
|
|
167
170
|
v = self._model_obj.getvar("vwind")
|
|
168
171
|
u = self.remove_extra_levels(u)
|
|
169
172
|
v = self.remove_extra_levels(v)
|
|
170
|
-
w_shear = self.calculate_wind_shear(
|
|
173
|
+
w_shear = self.calculate_wind_shear(u, v, height)
|
|
171
174
|
return self.calculate_variance_iwc(w_shear, ice_ind)
|
|
172
175
|
|
|
173
176
|
def calculate_variance_iwc(
|
|
174
177
|
self, w_shear: npt.NDArray, ice_ind: tuple
|
|
175
178
|
) -> npt.NDArray:
|
|
179
|
+
"""Fractional variance of IWC within a grid box (Hogan and Illingworth
|
|
180
|
+
2003, eq. 11), with the horizontal resolution in km and the wind shear
|
|
181
|
+
in m s-1 km-1. The shear is capped and the constant is -1.03 as in the
|
|
182
|
+
original Cloudnet code (the published equation has -0.93).
|
|
183
|
+
"""
|
|
184
|
+
shear = np.minimum(w_shear[ice_ind], MAX_WIND_SHEAR)
|
|
176
185
|
return 10 ** (
|
|
177
|
-
0.3 * np.log10(self._model_obj.resolution_h)
|
|
178
|
-
- 0.04 * w_shear[ice_ind]
|
|
179
|
-
- 1.03
|
|
186
|
+
0.3 * np.log10(self._model_obj.resolution_h) - 0.04 * shear - 1.03
|
|
180
187
|
)
|
|
181
188
|
|
|
182
189
|
@staticmethod
|
|
183
190
|
def calculate_wind_shear(
|
|
184
|
-
wind: npt.NDArray,
|
|
185
191
|
u: npt.NDArray,
|
|
186
192
|
v: npt.NDArray,
|
|
187
193
|
height: npt.NDArray,
|
|
188
194
|
) -> npt.NDArray:
|
|
189
|
-
|
|
190
|
-
|
|
195
|
+
"""Magnitude of the vertical shear of the horizontal wind (m s-1 km-1)
|
|
196
|
+
from (time, level) arrays.
|
|
197
|
+
"""
|
|
198
|
+
height_km = height / 1000
|
|
199
|
+
gradients = []
|
|
200
|
+
for w in (u, v):
|
|
191
201
|
grad_w = np.zeros(w.shape)
|
|
192
|
-
grad_w[0
|
|
193
|
-
grad_w[1:-1
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
202
|
+
grad_w[:, 0] = (w[:, 1] - w[:, 0]) / (height_km[:, 1] - height_km[:, 0])
|
|
203
|
+
grad_w[:, 1:-1] = (w[:, 2:] - w[:, :-2]) / (
|
|
204
|
+
height_km[:, 2:] - height_km[:, :-2]
|
|
205
|
+
)
|
|
206
|
+
grad_w[:, -1] = (w[:, -1] - w[:, -2]) / (
|
|
207
|
+
height_km[:, -1] - height_km[:, -2]
|
|
208
|
+
)
|
|
209
|
+
gradients.append(grad_w)
|
|
210
|
+
|
|
211
|
+
return np.sqrt(np.power(gradients[0], 2) + np.power(gradients[1], 2))
|
|
200
212
|
|
|
201
213
|
@staticmethod
|
|
202
214
|
def calculate_iwc_distribution(
|
{cloudnetpy-1.97.0 → cloudnetpy-1.97.2}/cloudnetpy/model_evaluation/products/grid_methods.py
RENAMED
|
@@ -2,7 +2,7 @@ import numpy as np
|
|
|
2
2
|
import numpy.typing as npt
|
|
3
3
|
from numpy import ma
|
|
4
4
|
|
|
5
|
-
from cloudnetpy import
|
|
5
|
+
from cloudnetpy.exceptions import InvalidSourceFileError
|
|
6
6
|
from cloudnetpy.model_evaluation.products import tools as tl
|
|
7
7
|
from cloudnetpy.model_evaluation.products.model_products import ModelManager
|
|
8
8
|
from cloudnetpy.model_evaluation.products.observation_products import ObservationManager
|
|
@@ -24,7 +24,12 @@ class ProductGrid:
|
|
|
24
24
|
self._obs_obj = obs_obj
|
|
25
25
|
self._date = obs_obj.date
|
|
26
26
|
self._obs_time = tl.time2datetime(obs_obj.time, self._date)
|
|
27
|
-
|
|
27
|
+
# Model heights are above ground; the observation file's height is
|
|
28
|
+
# above mean sea level.
|
|
29
|
+
if obs_obj.height_agl is None:
|
|
30
|
+
msg = "Observation file is missing height or altitude"
|
|
31
|
+
raise InvalidSourceFileError(msg)
|
|
32
|
+
self._obs_height = np.asarray(obs_obj.height_agl)
|
|
28
33
|
self._obs_data = obs_obj.data[obs_obj.product][:]
|
|
29
34
|
self.model_obj = model_obj
|
|
30
35
|
self._model_time = model_obj.time
|
|
@@ -34,21 +39,22 @@ class ProductGrid:
|
|
|
34
39
|
ma.array(model_obj.wind),
|
|
35
40
|
1,
|
|
36
41
|
)
|
|
37
|
-
|
|
38
|
-
|
|
42
|
+
self._time_windows = [
|
|
43
|
+
tuple(tl.time2datetime(np.array(edges), self._date))
|
|
44
|
+
for edges in tl.time_windows(self._model_time)
|
|
45
|
+
]
|
|
39
46
|
self._generate_downsample_product()
|
|
40
47
|
|
|
41
48
|
def _generate_downsample_product(self) -> None:
|
|
42
49
|
"""Downsampling products are generated with different averaging methods
|
|
43
50
|
for a selected size of model time-height window.
|
|
51
|
+
|
|
52
|
+
Grid cells without any observation pixels stay masked.
|
|
44
53
|
"""
|
|
45
54
|
product_dict, product_adv_dict = self._get_method_storage()
|
|
46
55
|
model_t = tl.time2datetime(self._model_time, self._date)
|
|
47
|
-
for i in
|
|
48
|
-
x_ind = tl.get_1d_indices(
|
|
49
|
-
(self._time_steps[i], self._time_steps[i + 1]),
|
|
50
|
-
self._obs_time,
|
|
51
|
-
)
|
|
56
|
+
for i, window in enumerate(self._time_windows):
|
|
57
|
+
x_ind = tl.get_1d_indices(window, self._obs_time)
|
|
52
58
|
y_steps = tl.rebin_edges(self._model_height[i])
|
|
53
59
|
for j in range(len(y_steps) - 1):
|
|
54
60
|
x_ind_adv = tl.get_adv_indices(
|
|
@@ -91,19 +97,21 @@ class ProductGrid:
|
|
|
91
97
|
|
|
92
98
|
def _cf_method_storage(self) -> tuple[dict, dict]:
|
|
93
99
|
cf_dict = {
|
|
94
|
-
"cf_V": ma.
|
|
95
|
-
"cf_A": ma.
|
|
100
|
+
"cf_V": ma.masked_all(self._model_height.shape),
|
|
101
|
+
"cf_A": ma.masked_all(self._model_height.shape),
|
|
96
102
|
}
|
|
97
103
|
cf_adv_dict = {
|
|
98
|
-
"cf_V_adv": ma.
|
|
99
|
-
"cf_A_adv": ma.
|
|
104
|
+
"cf_V_adv": ma.masked_all(self._model_height.shape),
|
|
105
|
+
"cf_A_adv": ma.masked_all(self._model_height.shape),
|
|
100
106
|
}
|
|
101
107
|
return cf_dict, cf_adv_dict
|
|
102
108
|
|
|
103
109
|
def _product_method_storage(self) -> tuple[dict, dict]:
|
|
104
|
-
product_dict = {
|
|
110
|
+
product_dict = {
|
|
111
|
+
f"{self._obs_obj.product}": ma.masked_all(self._model_height.shape)
|
|
112
|
+
}
|
|
105
113
|
product_adv_dict = {
|
|
106
|
-
f"{self._obs_obj.product}_adv": ma.
|
|
114
|
+
f"{self._obs_obj.product}_adv": ma.masked_all(self._model_height.shape),
|
|
107
115
|
}
|
|
108
116
|
return product_dict, product_adv_dict
|
|
109
117
|
|
{cloudnetpy-1.97.0 → cloudnetpy-1.97.2}/cloudnetpy/model_evaluation/products/observation_products.py
RENAMED
|
@@ -103,10 +103,14 @@ class ObservationManager(DataSource):
|
|
|
103
103
|
"""Keeps only reliable ice water content retrievals.
|
|
104
104
|
|
|
105
105
|
Status 1 is a reliable retrieval and status 3 is a retrieval with the
|
|
106
|
-
radar corrected for liquid, rain and melting attenuation
|
|
107
|
-
|
|
106
|
+
radar corrected for liquid, rain and melting attenuation. Status 0 (no
|
|
107
|
+
ice) is set to zero so that the regridded value is a grid-box mean,
|
|
108
|
+
comparable to the model. Everything else (uncorrected attenuation,
|
|
109
|
+
lidar-only, rain) is masked out.
|
|
108
110
|
"""
|
|
109
111
|
iwc = self.getvar("iwc")
|
|
110
|
-
|
|
111
|
-
|
|
112
|
+
status = ma.filled(self.getvar("iwc_retrieval_status"), -1)
|
|
113
|
+
no_ice = status == 0
|
|
114
|
+
invalid = (ma.getmaskarray(iwc) & ~no_ice) | ~np.isin(status, (0, 1, 3))
|
|
115
|
+
iwc = ma.array(ma.filled(iwc, 0), mask=invalid)
|
|
112
116
|
self.append_data(iwc, "iwc")
|
|
@@ -13,6 +13,17 @@ def time2datetime(time: npt.NDArray, date: datetime.datetime) -> npt.NDArray:
|
|
|
13
13
|
return np.asarray([date + timedelta(hours=float(t)) for t in time])
|
|
14
14
|
|
|
15
15
|
|
|
16
|
+
def time_windows(time: npt.NDArray) -> list[tuple[float, float]]:
|
|
17
|
+
"""Averaging window around each model time step.
|
|
18
|
+
|
|
19
|
+
Each window is +/- half the smallest time step, so a gap in the time
|
|
20
|
+
vector (e.g. a missing forecast run) does not widen the windows.
|
|
21
|
+
"""
|
|
22
|
+
time = np.asarray(time, dtype=float)
|
|
23
|
+
half = np.min(np.diff(time)) / 2 if time.size > 1 else 0.5
|
|
24
|
+
return [(t - half, t + half) for t in time]
|
|
25
|
+
|
|
26
|
+
|
|
16
27
|
def rebin_edges(arr: npt.NDArray) -> npt.NDArray:
|
|
17
28
|
"""Rebins array bins by half and adds boundaries."""
|
|
18
29
|
new_arr = [(arr[i] + arr[i + 1]) / 2 for i in range(len(arr) - 1)]
|
|
@@ -229,19 +229,39 @@ def test_calculate_variance_iwc(obs_file, model_file) -> None:
|
|
|
229
229
|
testing.assert_array_almost_equal(result, expected)
|
|
230
230
|
|
|
231
231
|
|
|
232
|
+
def test_calculate_variance_iwc_shear_cap(obs_file, model_file) -> None:
|
|
233
|
+
obs = ObservationManager(PRODUCT, str(obs_file))
|
|
234
|
+
model = ModelManager(str(model_file), MODEL, PRODUCT)
|
|
235
|
+
advance = AdvanceProductMethods(model, obs)
|
|
236
|
+
shear = np.array([[20.0, 50.0]])
|
|
237
|
+
result = advance.calculate_variance_iwc(shear, (np.array([0, 0]), np.array([0, 1])))
|
|
238
|
+
assert result[0] == result[1]
|
|
239
|
+
|
|
240
|
+
|
|
232
241
|
def test_calculate_wind_shear(obs_file, model_file) -> None:
|
|
233
242
|
obs = ObservationManager(PRODUCT, str(obs_file))
|
|
234
243
|
model = ModelManager(str(model_file), MODEL, PRODUCT)
|
|
235
244
|
advance = AdvanceProductMethods(model, obs)
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
height = np.array([[1, 1, 1, 1], [2, 2, 2, 2], [3, 3, 3, 3]])
|
|
240
|
-
expected = np.array([[2, 2.83, 2.24,
|
|
241
|
-
result = advance.calculate_wind_shear(
|
|
245
|
+
# (time, level) arrays; shear is the vertical gradient in m s-1 km-1
|
|
246
|
+
u = np.array([[1, 2, 0, 1], [-1, 0, 1, -1], [1, 0, 1, -1]]).T
|
|
247
|
+
v = np.array([[1, 0, 1, -1], [1, 2, -1, 0], [1, 2, 0, 1]]).T
|
|
248
|
+
height = np.array([[1, 1, 1, 1], [2, 2, 2, 2], [3, 3, 3, 3]]).T * 1000
|
|
249
|
+
expected = np.array([[2, 2.83, 2.24, 2.24], [0, 1.41, 0.71, 1.41], [2, 0, 1, 1]]).T
|
|
250
|
+
result = advance.calculate_wind_shear(u, v, height)
|
|
242
251
|
testing.assert_array_almost_equal(np.round(result, 2), expected)
|
|
243
252
|
|
|
244
253
|
|
|
254
|
+
def test_calculate_wind_shear_constant_height_in_time(obs_file, model_file) -> None:
|
|
255
|
+
obs = ObservationManager(PRODUCT, str(obs_file))
|
|
256
|
+
model = ModelManager(str(model_file), MODEL, PRODUCT)
|
|
257
|
+
advance = AdvanceProductMethods(model, obs)
|
|
258
|
+
u = np.array([[1.0, 3.0, 4.0], [1.0, 3.0, 4.0]])
|
|
259
|
+
v = np.zeros_like(u)
|
|
260
|
+
height = np.array([[0.0, 1000.0, 2000.0], [0.0, 1000.0, 2000.0]])
|
|
261
|
+
result = advance.calculate_wind_shear(u, v, height)
|
|
262
|
+
testing.assert_array_almost_equal(result, [[2, 1.5, 1], [2, 1.5, 1]])
|
|
263
|
+
|
|
264
|
+
|
|
245
265
|
def test_calculate_iwc_distribution(obs_file, model_file) -> None:
|
|
246
266
|
obs = ObservationManager(PRODUCT, str(obs_file))
|
|
247
267
|
model = ModelManager(str(model_file), MODEL, PRODUCT)
|
{cloudnetpy-1.97.0 → cloudnetpy-1.97.2}/cloudnetpy/model_evaluation/tests/unit/test_grid_methods.py
RENAMED
|
@@ -2,6 +2,7 @@ import numpy as np
|
|
|
2
2
|
import pytest
|
|
3
3
|
from numpy import ma, testing
|
|
4
4
|
|
|
5
|
+
from cloudnetpy.exceptions import InvalidSourceFileError
|
|
5
6
|
from cloudnetpy.model_evaluation.products.grid_methods import ProductGrid
|
|
6
7
|
from cloudnetpy.model_evaluation.products.model_products import ModelManager
|
|
7
8
|
from cloudnetpy.model_evaluation.products.observation_products import ObservationManager
|
|
@@ -42,6 +43,14 @@ def test_generate_regrid_product(model_file, obs_file, product, variables) -> No
|
|
|
42
43
|
assert var in model.data
|
|
43
44
|
|
|
44
45
|
|
|
46
|
+
def test_missing_height_agl_raises(model_file, obs_file) -> None:
|
|
47
|
+
obs = ObservationManager(PRODUCT, str(obs_file))
|
|
48
|
+
model = ModelManager(str(model_file), MODEL, PRODUCT)
|
|
49
|
+
obs.height_agl = None
|
|
50
|
+
with pytest.raises(InvalidSourceFileError):
|
|
51
|
+
ProductGrid(model, obs)
|
|
52
|
+
|
|
53
|
+
|
|
45
54
|
@pytest.mark.parametrize("key, value", [("iwc", 1), ("lwc", 1), ("cf", 2)])
|
|
46
55
|
def test_get_method_storage(key, value, model_file, obs_file) -> None:
|
|
47
56
|
obs = ObservationManager(key, str(obs_file))
|
|
@@ -113,6 +113,7 @@ def test_mask_iwc(obs_file) -> None:
|
|
|
113
113
|
obs = ObservationManager("iwc", str(obs_file))
|
|
114
114
|
iwc_status = obs.getvar("iwc_retrieval_status")
|
|
115
115
|
expected = ma.copy(obs.getvar("iwc"))
|
|
116
|
-
expected[
|
|
116
|
+
expected[iwc_status == 0] = 0
|
|
117
|
+
expected[~np.isin(iwc_status, (0, 1, 3))] = ma.masked
|
|
117
118
|
obs._mask_iwc()
|
|
118
119
|
testing.assert_array_almost_equal(expected, obs.data["iwc"][:])
|
{cloudnetpy-1.97.0 → cloudnetpy-1.97.2}/cloudnetpy/model_evaluation/tests/unit/test_tools.py
RENAMED
|
@@ -128,3 +128,17 @@ def test_obs_windows_size_first_index() -> None:
|
|
|
128
128
|
result = tools.get_obs_window_size(ind_x, ind_y)
|
|
129
129
|
assert result is not None
|
|
130
130
|
testing.assert_almost_equal(result, (1, 1))
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
def test_time_windows_even_spacing() -> None:
|
|
134
|
+
result = tools.time_windows(np.array([0.0, 1.0, 2.0]))
|
|
135
|
+
testing.assert_array_almost_equal(result, [(-0.5, 0.5), (0.5, 1.5), (1.5, 2.5)])
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
def test_time_windows_gap() -> None:
|
|
139
|
+
result = tools.time_windows(np.array([0.0, 1.0, 24.0]))
|
|
140
|
+
testing.assert_array_almost_equal(result, [(-0.5, 0.5), (0.5, 1.5), (23.5, 24.5)])
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
def test_time_windows_single() -> None:
|
|
144
|
+
testing.assert_array_almost_equal(tools.time_windows(np.array([3.0])), [(2.5, 3.5)])
|
|
@@ -464,6 +464,7 @@ class Plot:
|
|
|
464
464
|
"air_pressure": (multiply, 0.01, "hPa"),
|
|
465
465
|
"relative_humidity": (multiply, 100, "%"),
|
|
466
466
|
"rh": (multiply, 100, "%"),
|
|
467
|
+
"lwc_error": (multiply, 100, "%"),
|
|
467
468
|
"rainfall_amount": (multiply, con.M_TO_MM, "mm"),
|
|
468
469
|
"snowfall_amount": (multiply, con.M_TO_MM, "mm"),
|
|
469
470
|
"precipitation_amount": (multiply, con.M_TO_MM, "mm"),
|
|
@@ -117,7 +117,7 @@ class LwcSource(DataSource):
|
|
|
117
117
|
) -> None:
|
|
118
118
|
self.append_data(lwc, "lwc", units="kg m-3")
|
|
119
119
|
self.append_data(status, "lwc_retrieval_status")
|
|
120
|
-
self.append_data(error, "lwc_error", units="
|
|
120
|
+
self.append_data(error, "lwc_error", units="1")
|
|
121
121
|
|
|
122
122
|
@staticmethod
|
|
123
123
|
def _get_atmosphere(
|
|
@@ -348,15 +348,16 @@ class LwcError:
|
|
|
348
348
|
return self._fill_error_array(combined_error)
|
|
349
349
|
|
|
350
350
|
def _calc_lwc_relative_error(self) -> npt.NDArray:
|
|
351
|
+
# Half a range gate uncertainty in cloud base and top height
|
|
351
352
|
lwc_gradient = self._calc_lwc_gradient()
|
|
352
353
|
error = lwc_gradient / self.lwc / 2
|
|
353
354
|
return self._limit_error(error, 5)
|
|
354
355
|
|
|
355
356
|
def _calc_lwc_gradient(self) -> npt.NDArray:
|
|
357
|
+
"""Returns absolute vertical gradient of LWC per range gate."""
|
|
356
358
|
if not isinstance(self.lwc, ma.MaskedArray):
|
|
357
359
|
self.lwc = ma.array(self.lwc)
|
|
358
|
-
|
|
359
|
-
return utils.l2norm(*gradient_elements)
|
|
360
|
+
return np.abs(np.gradient(self.lwc.filled(0), axis=1))
|
|
360
361
|
|
|
361
362
|
def _calc_lwp_relative_error(self) -> npt.NDArray:
|
|
362
363
|
err = self.lwc_source.lwp_error
|
|
@@ -412,9 +413,15 @@ COMMENTS = {
|
|
|
412
413
|
" liquid layers is difficult to ascertain."
|
|
413
414
|
),
|
|
414
415
|
"lwc_error": (
|
|
415
|
-
"This variable is an estimate of the random error
|
|
416
|
-
"
|
|
417
|
-
"
|
|
416
|
+
"This variable is an estimate of the one-standard-deviation random error\n"
|
|
417
|
+
"in liquid water content, expressed as a fraction of the liquid water\n"
|
|
418
|
+
"content. It combines the relative uncertainty in the microwave radiometer\n"
|
|
419
|
+
"liquid water path retrieval (limited to 10) and the uncertainty in cloud\n"
|
|
420
|
+
"base and cloud top height, taken as half a range gate and estimated from\n"
|
|
421
|
+
"the vertical gradient of liquid water content (limited to 5). If the liquid\n"
|
|
422
|
+
"layer is detected by the lidar only, there is the potential for cloud top\n"
|
|
423
|
+
"height to be underestimated. The error contribution from using the model\n"
|
|
424
|
+
"temperature and pressure at cloud base is assumed to be negligible."
|
|
418
425
|
),
|
|
419
426
|
"lwc_retrieval_status": (
|
|
420
427
|
"This variable describes whether a retrieval was performed for each pixel,\n"
|
|
@@ -469,9 +476,9 @@ LWC_ATTRIBUTES = {
|
|
|
469
476
|
dimensions=("time", "height"),
|
|
470
477
|
),
|
|
471
478
|
"lwc_error": MetaData(
|
|
472
|
-
long_name="
|
|
479
|
+
long_name="Relative random error in liquid water content",
|
|
473
480
|
comment=COMMENTS["lwc_error"],
|
|
474
|
-
units="
|
|
481
|
+
units="1",
|
|
475
482
|
dimensions=("time", "height"),
|
|
476
483
|
),
|
|
477
484
|
"lwc_retrieval_status": MetaData(
|
|
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.97.0 → cloudnetpy-1.97.2}/cloudnetpy/categorize/attenuations/gas_attenuation.py
RENAMED
|
File without changes
|
{cloudnetpy-1.97.0 → cloudnetpy-1.97.2}/cloudnetpy/categorize/attenuations/liquid_attenuation.py
RENAMED
|
File without changes
|
{cloudnetpy-1.97.0 → cloudnetpy-1.97.2}/cloudnetpy/categorize/attenuations/melting_attenuation.py
RENAMED
|
File without changes
|
{cloudnetpy-1.97.0 → cloudnetpy-1.97.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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{cloudnetpy-1.97.0 → cloudnetpy-1.97.2}/cloudnetpy/model_evaluation/products/model_products.py
RENAMED
|
File without changes
|
{cloudnetpy-1.97.0 → cloudnetpy-1.97.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
|
{cloudnetpy-1.97.0 → cloudnetpy-1.97.2}/cloudnetpy/model_evaluation/tests/e2e/process_cf/__init__.py
RENAMED
|
File without changes
|
{cloudnetpy-1.97.0 → cloudnetpy-1.97.2}/cloudnetpy/model_evaluation/tests/e2e/process_cf/main.py
RENAMED
|
File without changes
|
{cloudnetpy-1.97.0 → cloudnetpy-1.97.2}/cloudnetpy/model_evaluation/tests/e2e/process_cf/tests.py
RENAMED
|
File without changes
|
|
File without changes
|
{cloudnetpy-1.97.0 → cloudnetpy-1.97.2}/cloudnetpy/model_evaluation/tests/e2e/process_iwc/main.py
RENAMED
|
File without changes
|
{cloudnetpy-1.97.0 → cloudnetpy-1.97.2}/cloudnetpy/model_evaluation/tests/e2e/process_iwc/tests.py
RENAMED
|
File without changes
|
|
File without changes
|
{cloudnetpy-1.97.0 → cloudnetpy-1.97.2}/cloudnetpy/model_evaluation/tests/e2e/process_lwc/main.py
RENAMED
|
File without changes
|
{cloudnetpy-1.97.0 → cloudnetpy-1.97.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.97.0 → cloudnetpy-1.97.2}/cloudnetpy/model_evaluation/tests/unit/test_plot_tools.py
RENAMED
|
File without changes
|
{cloudnetpy-1.97.0 → cloudnetpy-1.97.2}/cloudnetpy/model_evaluation/tests/unit/test_plotting.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
|