cloudnetpy 1.97.1__tar.gz → 1.97.3__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.1/cloudnetpy.egg-info → cloudnetpy-1.97.3}/PKG-INFO +3 -3
- {cloudnetpy-1.97.1 → cloudnetpy-1.97.3}/README.md +1 -1
- {cloudnetpy-1.97.1 → cloudnetpy-1.97.3}/cloudnetpy/disdronator/parsivel.py +39 -0
- {cloudnetpy-1.97.1 → cloudnetpy-1.97.3}/cloudnetpy/instruments/ceilo.py +20 -3
- {cloudnetpy-1.97.1 → cloudnetpy-1.97.3}/cloudnetpy/instruments/instruments.py +8 -0
- {cloudnetpy-1.97.1 → cloudnetpy-1.97.3}/cloudnetpy/instruments/kazr.py +26 -3
- {cloudnetpy-1.97.1 → cloudnetpy-1.97.3}/cloudnetpy/instruments/lufft.py +1 -0
- {cloudnetpy-1.97.1 → cloudnetpy-1.97.3}/cloudnetpy/instruments/vaisala.py +25 -2
- {cloudnetpy-1.97.1 → cloudnetpy-1.97.3}/cloudnetpy/model_evaluation/metadata.py +3 -1
- {cloudnetpy-1.97.1 → cloudnetpy-1.97.3}/cloudnetpy/model_evaluation/products/advance_methods.py +35 -23
- {cloudnetpy-1.97.1 → cloudnetpy-1.97.3}/cloudnetpy/model_evaluation/products/grid_methods.py +23 -15
- {cloudnetpy-1.97.1 → cloudnetpy-1.97.3}/cloudnetpy/model_evaluation/products/observation_products.py +8 -4
- {cloudnetpy-1.97.1 → cloudnetpy-1.97.3}/cloudnetpy/model_evaluation/products/tools.py +11 -0
- {cloudnetpy-1.97.1 → cloudnetpy-1.97.3}/cloudnetpy/model_evaluation/tests/unit/test_advance_methods.py +26 -6
- {cloudnetpy-1.97.1 → cloudnetpy-1.97.3}/cloudnetpy/model_evaluation/tests/unit/test_grid_methods.py +9 -0
- {cloudnetpy-1.97.1 → cloudnetpy-1.97.3}/cloudnetpy/model_evaluation/tests/unit/test_observation_products.py +2 -1
- {cloudnetpy-1.97.1 → cloudnetpy-1.97.3}/cloudnetpy/model_evaluation/tests/unit/test_tools.py +14 -0
- {cloudnetpy-1.97.1 → cloudnetpy-1.97.3}/cloudnetpy/version.py +1 -1
- {cloudnetpy-1.97.1 → cloudnetpy-1.97.3/cloudnetpy.egg-info}/PKG-INFO +3 -3
- {cloudnetpy-1.97.1 → cloudnetpy-1.97.3}/cloudnetpy.egg-info/requires.txt +1 -1
- {cloudnetpy-1.97.1 → cloudnetpy-1.97.3}/pyproject.toml +1 -1
- {cloudnetpy-1.97.1 → cloudnetpy-1.97.3}/LICENSE +0 -0
- {cloudnetpy-1.97.1 → cloudnetpy-1.97.3}/MANIFEST.in +0 -0
- {cloudnetpy-1.97.1 → cloudnetpy-1.97.3}/cloudnetpy/__init__.py +0 -0
- {cloudnetpy-1.97.1 → cloudnetpy-1.97.3}/cloudnetpy/arm.py +0 -0
- {cloudnetpy-1.97.1 → cloudnetpy-1.97.3}/cloudnetpy/categorize/__init__.py +0 -0
- {cloudnetpy-1.97.1 → cloudnetpy-1.97.3}/cloudnetpy/categorize/atmos_utils.py +0 -0
- {cloudnetpy-1.97.1 → cloudnetpy-1.97.3}/cloudnetpy/categorize/attenuation.py +0 -0
- {cloudnetpy-1.97.1 → cloudnetpy-1.97.3}/cloudnetpy/categorize/attenuations/__init__.py +0 -0
- {cloudnetpy-1.97.1 → cloudnetpy-1.97.3}/cloudnetpy/categorize/attenuations/gas_attenuation.py +0 -0
- {cloudnetpy-1.97.1 → cloudnetpy-1.97.3}/cloudnetpy/categorize/attenuations/liquid_attenuation.py +0 -0
- {cloudnetpy-1.97.1 → cloudnetpy-1.97.3}/cloudnetpy/categorize/attenuations/melting_attenuation.py +0 -0
- {cloudnetpy-1.97.1 → cloudnetpy-1.97.3}/cloudnetpy/categorize/attenuations/rain_attenuation.py +0 -0
- {cloudnetpy-1.97.1 → cloudnetpy-1.97.3}/cloudnetpy/categorize/categorize.py +0 -0
- {cloudnetpy-1.97.1 → cloudnetpy-1.97.3}/cloudnetpy/categorize/classify.py +0 -0
- {cloudnetpy-1.97.1 → cloudnetpy-1.97.3}/cloudnetpy/categorize/containers.py +0 -0
- {cloudnetpy-1.97.1 → cloudnetpy-1.97.3}/cloudnetpy/categorize/disdrometer.py +0 -0
- {cloudnetpy-1.97.1 → cloudnetpy-1.97.3}/cloudnetpy/categorize/droplet.py +0 -0
- {cloudnetpy-1.97.1 → cloudnetpy-1.97.3}/cloudnetpy/categorize/falling.py +0 -0
- {cloudnetpy-1.97.1 → cloudnetpy-1.97.3}/cloudnetpy/categorize/freezing.py +0 -0
- {cloudnetpy-1.97.1 → cloudnetpy-1.97.3}/cloudnetpy/categorize/insects.py +0 -0
- {cloudnetpy-1.97.1 → cloudnetpy-1.97.3}/cloudnetpy/categorize/lidar.py +0 -0
- {cloudnetpy-1.97.1 → cloudnetpy-1.97.3}/cloudnetpy/categorize/melting.py +0 -0
- {cloudnetpy-1.97.1 → cloudnetpy-1.97.3}/cloudnetpy/categorize/model.py +0 -0
- {cloudnetpy-1.97.1 → cloudnetpy-1.97.3}/cloudnetpy/categorize/mwr.py +0 -0
- {cloudnetpy-1.97.1 → cloudnetpy-1.97.3}/cloudnetpy/categorize/radar.py +0 -0
- {cloudnetpy-1.97.1 → cloudnetpy-1.97.3}/cloudnetpy/cli.py +0 -0
- {cloudnetpy-1.97.1 → cloudnetpy-1.97.3}/cloudnetpy/cloudnetarray.py +0 -0
- {cloudnetpy-1.97.1 → cloudnetpy-1.97.3}/cloudnetpy/concat_lib.py +0 -0
- {cloudnetpy-1.97.1 → cloudnetpy-1.97.3}/cloudnetpy/constants.py +0 -0
- {cloudnetpy-1.97.1 → cloudnetpy-1.97.3}/cloudnetpy/datasource.py +0 -0
- {cloudnetpy-1.97.1 → cloudnetpy-1.97.3}/cloudnetpy/disdronator/__init__.py +0 -0
- {cloudnetpy-1.97.1 → cloudnetpy-1.97.3}/cloudnetpy/disdronator/lpm.py +0 -0
- {cloudnetpy-1.97.1 → cloudnetpy-1.97.3}/cloudnetpy/disdronator/process.py +0 -0
- {cloudnetpy-1.97.1 → cloudnetpy-1.97.3}/cloudnetpy/disdronator/rd80.py +0 -0
- {cloudnetpy-1.97.1 → cloudnetpy-1.97.3}/cloudnetpy/disdronator/utils.py +0 -0
- {cloudnetpy-1.97.1 → cloudnetpy-1.97.3}/cloudnetpy/exceptions.py +0 -0
- {cloudnetpy-1.97.1 → cloudnetpy-1.97.3}/cloudnetpy/instruments/__init__.py +0 -0
- {cloudnetpy-1.97.1 → cloudnetpy-1.97.3}/cloudnetpy/instruments/arm_ceilo.py +0 -0
- {cloudnetpy-1.97.1 → cloudnetpy-1.97.3}/cloudnetpy/instruments/arm_ld.py +0 -0
- {cloudnetpy-1.97.1 → cloudnetpy-1.97.3}/cloudnetpy/instruments/arm_utils.py +0 -0
- {cloudnetpy-1.97.1 → cloudnetpy-1.97.3}/cloudnetpy/instruments/basta.py +0 -0
- {cloudnetpy-1.97.1 → cloudnetpy-1.97.3}/cloudnetpy/instruments/bowtie.py +0 -0
- {cloudnetpy-1.97.1 → cloudnetpy-1.97.3}/cloudnetpy/instruments/ceilometer.py +0 -0
- {cloudnetpy-1.97.1 → cloudnetpy-1.97.3}/cloudnetpy/instruments/cl61d.py +0 -0
- {cloudnetpy-1.97.1 → cloudnetpy-1.97.3}/cloudnetpy/instruments/cloudnet_instrument.py +0 -0
- {cloudnetpy-1.97.1 → cloudnetpy-1.97.3}/cloudnetpy/instruments/copernicus.py +0 -0
- {cloudnetpy-1.97.1 → cloudnetpy-1.97.3}/cloudnetpy/instruments/da10.py +0 -0
- {cloudnetpy-1.97.1 → cloudnetpy-1.97.3}/cloudnetpy/instruments/dealias.py +0 -0
- {cloudnetpy-1.97.1 → cloudnetpy-1.97.3}/cloudnetpy/instruments/disdrometer.py +0 -0
- {cloudnetpy-1.97.1 → cloudnetpy-1.97.3}/cloudnetpy/instruments/fd12p.py +0 -0
- {cloudnetpy-1.97.1 → cloudnetpy-1.97.3}/cloudnetpy/instruments/galileo.py +0 -0
- {cloudnetpy-1.97.1 → cloudnetpy-1.97.3}/cloudnetpy/instruments/hatpro.py +0 -0
- {cloudnetpy-1.97.1 → cloudnetpy-1.97.3}/cloudnetpy/instruments/mira.py +0 -0
- {cloudnetpy-1.97.1 → cloudnetpy-1.97.3}/cloudnetpy/instruments/mmcr.py +0 -0
- {cloudnetpy-1.97.1 → cloudnetpy-1.97.3}/cloudnetpy/instruments/mrr.py +0 -0
- {cloudnetpy-1.97.1 → cloudnetpy-1.97.3}/cloudnetpy/instruments/mwr3c.py +0 -0
- {cloudnetpy-1.97.1 → cloudnetpy-1.97.3}/cloudnetpy/instruments/mwrlos.py +0 -0
- {cloudnetpy-1.97.1 → cloudnetpy-1.97.3}/cloudnetpy/instruments/nc_lidar.py +0 -0
- {cloudnetpy-1.97.1 → cloudnetpy-1.97.3}/cloudnetpy/instruments/nc_radar.py +0 -0
- {cloudnetpy-1.97.1 → cloudnetpy-1.97.3}/cloudnetpy/instruments/pollyxt.py +0 -0
- {cloudnetpy-1.97.1 → cloudnetpy-1.97.3}/cloudnetpy/instruments/radiometrics.py +0 -0
- {cloudnetpy-1.97.1 → cloudnetpy-1.97.3}/cloudnetpy/instruments/rain_e_h3.py +0 -0
- {cloudnetpy-1.97.1 → cloudnetpy-1.97.3}/cloudnetpy/instruments/rpg.py +0 -0
- {cloudnetpy-1.97.1 → cloudnetpy-1.97.3}/cloudnetpy/instruments/rpg_reader.py +0 -0
- {cloudnetpy-1.97.1 → cloudnetpy-1.97.3}/cloudnetpy/instruments/toa5.py +0 -0
- {cloudnetpy-1.97.1 → cloudnetpy-1.97.3}/cloudnetpy/instruments/weather_radar.py +0 -0
- {cloudnetpy-1.97.1 → cloudnetpy-1.97.3}/cloudnetpy/instruments/weather_station.py +0 -0
- {cloudnetpy-1.97.1 → cloudnetpy-1.97.3}/cloudnetpy/metadata.py +0 -0
- {cloudnetpy-1.97.1 → cloudnetpy-1.97.3}/cloudnetpy/model_evaluation/__init__.py +0 -0
- {cloudnetpy-1.97.1 → cloudnetpy-1.97.3}/cloudnetpy/model_evaluation/file_handler.py +0 -0
- {cloudnetpy-1.97.1 → cloudnetpy-1.97.3}/cloudnetpy/model_evaluation/model_metadata.py +0 -0
- {cloudnetpy-1.97.1 → cloudnetpy-1.97.3}/cloudnetpy/model_evaluation/plotting/__init__.py +0 -0
- {cloudnetpy-1.97.1 → cloudnetpy-1.97.3}/cloudnetpy/model_evaluation/plotting/plot_meta.py +0 -0
- {cloudnetpy-1.97.1 → cloudnetpy-1.97.3}/cloudnetpy/model_evaluation/plotting/plot_tools.py +0 -0
- {cloudnetpy-1.97.1 → cloudnetpy-1.97.3}/cloudnetpy/model_evaluation/plotting/plotting.py +0 -0
- {cloudnetpy-1.97.1 → cloudnetpy-1.97.3}/cloudnetpy/model_evaluation/products/__init__.py +0 -0
- {cloudnetpy-1.97.1 → cloudnetpy-1.97.3}/cloudnetpy/model_evaluation/products/model_products.py +0 -0
- {cloudnetpy-1.97.1 → cloudnetpy-1.97.3}/cloudnetpy/model_evaluation/products/product_resampling.py +0 -0
- {cloudnetpy-1.97.1 → cloudnetpy-1.97.3}/cloudnetpy/model_evaluation/statistics/__init__.py +0 -0
- {cloudnetpy-1.97.1 → cloudnetpy-1.97.3}/cloudnetpy/model_evaluation/statistics/statistical_methods.py +0 -0
- {cloudnetpy-1.97.1 → cloudnetpy-1.97.3}/cloudnetpy/model_evaluation/tests/__init__.py +0 -0
- {cloudnetpy-1.97.1 → cloudnetpy-1.97.3}/cloudnetpy/model_evaluation/tests/e2e/__init__.py +0 -0
- {cloudnetpy-1.97.1 → cloudnetpy-1.97.3}/cloudnetpy/model_evaluation/tests/e2e/conftest.py +0 -0
- {cloudnetpy-1.97.1 → cloudnetpy-1.97.3}/cloudnetpy/model_evaluation/tests/e2e/process_cf/__init__.py +0 -0
- {cloudnetpy-1.97.1 → cloudnetpy-1.97.3}/cloudnetpy/model_evaluation/tests/e2e/process_cf/main.py +0 -0
- {cloudnetpy-1.97.1 → cloudnetpy-1.97.3}/cloudnetpy/model_evaluation/tests/e2e/process_cf/tests.py +0 -0
- {cloudnetpy-1.97.1 → cloudnetpy-1.97.3}/cloudnetpy/model_evaluation/tests/e2e/process_iwc/__init__.py +0 -0
- {cloudnetpy-1.97.1 → cloudnetpy-1.97.3}/cloudnetpy/model_evaluation/tests/e2e/process_iwc/main.py +0 -0
- {cloudnetpy-1.97.1 → cloudnetpy-1.97.3}/cloudnetpy/model_evaluation/tests/e2e/process_iwc/tests.py +0 -0
- {cloudnetpy-1.97.1 → cloudnetpy-1.97.3}/cloudnetpy/model_evaluation/tests/e2e/process_lwc/__init__.py +0 -0
- {cloudnetpy-1.97.1 → cloudnetpy-1.97.3}/cloudnetpy/model_evaluation/tests/e2e/process_lwc/main.py +0 -0
- {cloudnetpy-1.97.1 → cloudnetpy-1.97.3}/cloudnetpy/model_evaluation/tests/e2e/process_lwc/tests.py +0 -0
- {cloudnetpy-1.97.1 → cloudnetpy-1.97.3}/cloudnetpy/model_evaluation/tests/unit/__init__.py +0 -0
- {cloudnetpy-1.97.1 → cloudnetpy-1.97.3}/cloudnetpy/model_evaluation/tests/unit/conftest.py +0 -0
- {cloudnetpy-1.97.1 → cloudnetpy-1.97.3}/cloudnetpy/model_evaluation/tests/unit/test_model_products.py +0 -0
- {cloudnetpy-1.97.1 → cloudnetpy-1.97.3}/cloudnetpy/model_evaluation/tests/unit/test_plot_tools.py +0 -0
- {cloudnetpy-1.97.1 → cloudnetpy-1.97.3}/cloudnetpy/model_evaluation/tests/unit/test_plotting.py +0 -0
- {cloudnetpy-1.97.1 → cloudnetpy-1.97.3}/cloudnetpy/model_evaluation/tests/unit/test_statistical_methods.py +0 -0
- {cloudnetpy-1.97.1 → cloudnetpy-1.97.3}/cloudnetpy/model_evaluation/utils.py +0 -0
- {cloudnetpy-1.97.1 → cloudnetpy-1.97.3}/cloudnetpy/output.py +0 -0
- {cloudnetpy-1.97.1 → cloudnetpy-1.97.3}/cloudnetpy/plotting/__init__.py +0 -0
- {cloudnetpy-1.97.1 → cloudnetpy-1.97.3}/cloudnetpy/plotting/plot_meta.py +0 -0
- {cloudnetpy-1.97.1 → cloudnetpy-1.97.3}/cloudnetpy/plotting/plotting.py +0 -0
- {cloudnetpy-1.97.1 → cloudnetpy-1.97.3}/cloudnetpy/products/__init__.py +0 -0
- {cloudnetpy-1.97.1 → cloudnetpy-1.97.3}/cloudnetpy/products/classification.py +0 -0
- {cloudnetpy-1.97.1 → cloudnetpy-1.97.3}/cloudnetpy/products/cod.py +0 -0
- {cloudnetpy-1.97.1 → cloudnetpy-1.97.3}/cloudnetpy/products/der.py +0 -0
- {cloudnetpy-1.97.1 → cloudnetpy-1.97.3}/cloudnetpy/products/drizzle.py +0 -0
- {cloudnetpy-1.97.1 → cloudnetpy-1.97.3}/cloudnetpy/products/drizzle_error.py +0 -0
- {cloudnetpy-1.97.1 → cloudnetpy-1.97.3}/cloudnetpy/products/drizzle_tools.py +0 -0
- {cloudnetpy-1.97.1 → cloudnetpy-1.97.3}/cloudnetpy/products/epsilon_lidar.py +0 -0
- {cloudnetpy-1.97.1 → cloudnetpy-1.97.3}/cloudnetpy/products/epsilon_radar.py +0 -0
- {cloudnetpy-1.97.1 → cloudnetpy-1.97.3}/cloudnetpy/products/ier.py +0 -0
- {cloudnetpy-1.97.1 → cloudnetpy-1.97.3}/cloudnetpy/products/iwc.py +0 -0
- {cloudnetpy-1.97.1 → cloudnetpy-1.97.3}/cloudnetpy/products/iwc_weather_radar.py +0 -0
- {cloudnetpy-1.97.1 → cloudnetpy-1.97.3}/cloudnetpy/products/lwc.py +0 -0
- {cloudnetpy-1.97.1 → cloudnetpy-1.97.3}/cloudnetpy/products/mie_lu_tables.nc +0 -0
- {cloudnetpy-1.97.1 → cloudnetpy-1.97.3}/cloudnetpy/products/mwr_tools.py +0 -0
- {cloudnetpy-1.97.1 → cloudnetpy-1.97.3}/cloudnetpy/products/product_tools.py +0 -0
- {cloudnetpy-1.97.1 → cloudnetpy-1.97.3}/cloudnetpy/py.typed +0 -0
- {cloudnetpy-1.97.1 → cloudnetpy-1.97.3}/cloudnetpy/utils.py +0 -0
- {cloudnetpy-1.97.1 → cloudnetpy-1.97.3}/cloudnetpy.egg-info/SOURCES.txt +0 -0
- {cloudnetpy-1.97.1 → cloudnetpy-1.97.3}/cloudnetpy.egg-info/dependency_links.txt +0 -0
- {cloudnetpy-1.97.1 → cloudnetpy-1.97.3}/cloudnetpy.egg-info/entry_points.txt +0 -0
- {cloudnetpy-1.97.1 → cloudnetpy-1.97.3}/cloudnetpy.egg-info/top_level.txt +0 -0
- {cloudnetpy-1.97.1 → cloudnetpy-1.97.3}/docs/source/conf.py +0 -0
- {cloudnetpy-1.97.1 → cloudnetpy-1.97.3}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: cloudnetpy
|
|
3
|
-
Version: 1.97.
|
|
3
|
+
Version: 1.97.3
|
|
4
4
|
Summary: Python package for Cloudnet processing
|
|
5
5
|
Author: Simo Tukiainen
|
|
6
6
|
License: MIT License
|
|
@@ -39,7 +39,7 @@ Requires-Python: >=3.11
|
|
|
39
39
|
Description-Content-Type: text/markdown
|
|
40
40
|
License-File: LICENSE
|
|
41
41
|
Requires-Dist: atmoslib>=2.4.2
|
|
42
|
-
Requires-Dist: ceilopyter>=0.2.
|
|
42
|
+
Requires-Dist: ceilopyter>=0.2.3
|
|
43
43
|
Requires-Dist: cloudnet-api-client
|
|
44
44
|
Requires-Dist: doppy>=0.5.0
|
|
45
45
|
Requires-Dist: matplotlib
|
|
@@ -66,7 +66,7 @@ Dynamic: license-file
|
|
|
66
66
|
|
|
67
67
|
[](https://github.com/actris-cloudnet/cloudnetpy/actions/workflows/test.yml)
|
|
68
68
|
[](https://pypi.org/project/cloudnetpy/)
|
|
69
|
-
[](https://doi.org/10.5281/zenodo.3666030)
|
|
70
70
|
[](https://joss.theoj.org/papers/959971f196f617dddc0e7d8333ff22b7)
|
|
71
71
|
|
|
72
72
|
CloudnetPy is Python software designed for producing vertical profiles of cloud properties from ground-based
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
[](https://github.com/actris-cloudnet/cloudnetpy/actions/workflows/test.yml)
|
|
4
4
|
[](https://pypi.org/project/cloudnetpy/)
|
|
5
|
-
[](https://doi.org/10.5281/zenodo.3666030)
|
|
6
6
|
[](https://joss.theoj.org/papers/959971f196f617dddc0e7d8333ff22b7)
|
|
7
7
|
|
|
8
8
|
CloudnetPy is Python software designed for producing vertical profiles of cloud properties from ground-based
|
|
@@ -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"'):
|
|
@@ -9,18 +9,18 @@ from uuid import UUID
|
|
|
9
9
|
|
|
10
10
|
import netCDF4
|
|
11
11
|
import numpy as np
|
|
12
|
-
from ceilopyter import read_ct25k
|
|
12
|
+
from ceilopyter import read_ct25k, read_ld40
|
|
13
13
|
from numpy import ma
|
|
14
14
|
|
|
15
15
|
from cloudnetpy import output
|
|
16
16
|
from cloudnetpy.instruments.cl61d import Cl61d
|
|
17
17
|
from cloudnetpy.instruments.da10 import Da10
|
|
18
18
|
from cloudnetpy.instruments.lufft import LufftCeilo
|
|
19
|
-
from cloudnetpy.instruments.vaisala import ClCeilo, Cs135, Ct25k
|
|
19
|
+
from cloudnetpy.instruments.vaisala import ClCeilo, Cs135, Ct25k, Ld40
|
|
20
20
|
from cloudnetpy.metadata import COMMON_ATTRIBUTES, MetaData
|
|
21
21
|
from cloudnetpy.utils import get_uuid
|
|
22
22
|
|
|
23
|
-
CeiloObject = ClCeilo | Ct25k | LufftCeilo | Cl61d | Cs135 | Da10
|
|
23
|
+
CeiloObject = ClCeilo | Ct25k | LufftCeilo | Cl61d | Cs135 | Da10 | Ld40
|
|
24
24
|
|
|
25
25
|
|
|
26
26
|
def ceilo2nc(
|
|
@@ -90,6 +90,20 @@ def ceilo2nc(
|
|
|
90
90
|
c_obj = read_ct25k(full_path, calibration_factor, range_corrected)
|
|
91
91
|
ceilo_obj.data["beta"] = c_obj.beta
|
|
92
92
|
ceilo_obj.data["beta_raw"] = c_obj.beta_raw
|
|
93
|
+
ceilo_obj.data["beta_smooth"] = c_obj.beta_smooth
|
|
94
|
+
ceilo_obj.data["time"] = c_obj.time
|
|
95
|
+
ceilo_obj.data["range"] = c_obj.range
|
|
96
|
+
if c_obj.zenith_angle is not None:
|
|
97
|
+
ceilo_obj.data["zenith_angle"] = np.median(c_obj.zenith_angle)
|
|
98
|
+
ceilo_obj.data["calibration_factor"] = c_obj.calibration_factor
|
|
99
|
+
ceilo_obj.sort_time()
|
|
100
|
+
ceilo_obj.screen_date()
|
|
101
|
+
ceilo_obj.convert_to_fraction_hour()
|
|
102
|
+
elif isinstance(ceilo_obj, Ld40):
|
|
103
|
+
c_obj = read_ld40(full_path, calibration_factor)
|
|
104
|
+
ceilo_obj.data["beta"] = c_obj.beta
|
|
105
|
+
ceilo_obj.data["beta_raw"] = c_obj.beta_raw
|
|
106
|
+
ceilo_obj.data["beta_smooth"] = c_obj.beta_smooth
|
|
93
107
|
ceilo_obj.data["time"] = c_obj.time
|
|
94
108
|
ceilo_obj.data["range"] = c_obj.range
|
|
95
109
|
if c_obj.zenith_angle is not None:
|
|
@@ -174,6 +188,7 @@ def _initialize_ceilo(
|
|
|
174
188
|
"chm15k",
|
|
175
189
|
"cs135",
|
|
176
190
|
"da10",
|
|
191
|
+
"ld40",
|
|
177
192
|
):
|
|
178
193
|
msg = f"Invalid ceilometer model: {site_meta['model']}"
|
|
179
194
|
raise ValueError(msg)
|
|
@@ -193,6 +208,8 @@ def _initialize_ceilo(
|
|
|
193
208
|
return Da10(full_path, site_meta, date)
|
|
194
209
|
if model == "cs135":
|
|
195
210
|
return Cs135(full_path, site_meta, date)
|
|
211
|
+
if model == "ld40":
|
|
212
|
+
return Ld40(full_path, site_meta, date)
|
|
196
213
|
return LufftCeilo(full_path, site_meta, date)
|
|
197
214
|
|
|
198
215
|
|
|
@@ -51,6 +51,14 @@ CT25K = Instrument(
|
|
|
51
51
|
wavelength=905.0,
|
|
52
52
|
)
|
|
53
53
|
|
|
54
|
+
LD40 = Instrument(
|
|
55
|
+
manufacturer="Vaisala",
|
|
56
|
+
domain="lidar",
|
|
57
|
+
category="ceilometer",
|
|
58
|
+
model="LD40",
|
|
59
|
+
wavelength=855.0,
|
|
60
|
+
)
|
|
61
|
+
|
|
54
62
|
CL61D = Instrument(
|
|
55
63
|
manufacturer="Vaisala",
|
|
56
64
|
domain="lidar",
|
|
@@ -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
|
|
@@ -34,6 +34,7 @@ class LufftCeilo(Ceilometer):
|
|
|
34
34
|
ceilo = read_chm15k(self.file_name, calibration_factor)
|
|
35
35
|
self.data["beta"] = ceilo.beta
|
|
36
36
|
self.data["beta_raw"] = ceilo.beta_raw
|
|
37
|
+
self.data["beta_smooth"] = ceilo.beta_smooth
|
|
37
38
|
self.data["time"] = ceilo.time
|
|
38
39
|
self.data["range"] = ceilo.range
|
|
39
40
|
if ceilo.zenith_angle is not None:
|
|
@@ -7,7 +7,7 @@ from os import PathLike
|
|
|
7
7
|
import ceilopyter.version
|
|
8
8
|
import numpy as np
|
|
9
9
|
import numpy.typing as npt
|
|
10
|
-
from ceilopyter import read_cl_file, read_cs_file
|
|
10
|
+
from ceilopyter import read_cl_file, read_cs_file
|
|
11
11
|
|
|
12
12
|
from cloudnetpy.exceptions import ValidTimeStampError
|
|
13
13
|
from cloudnetpy.instruments import instruments
|
|
@@ -118,13 +118,31 @@ class Ct25k(VaisalaCeilo):
|
|
|
118
118
|
site_meta: dict,
|
|
119
119
|
expected_date: datetime.date | None = None,
|
|
120
120
|
) -> None:
|
|
121
|
-
super().__init__(
|
|
121
|
+
super().__init__(no_reader, full_path, site_meta, expected_date)
|
|
122
122
|
self._store_ceilometer_info()
|
|
123
123
|
|
|
124
124
|
def _store_ceilometer_info(self) -> None:
|
|
125
125
|
self.instrument = instruments.CT25K
|
|
126
126
|
|
|
127
127
|
|
|
128
|
+
class Ld40(VaisalaCeilo):
|
|
129
|
+
"""Class for Vaisala LD40 ceilometer."""
|
|
130
|
+
|
|
131
|
+
noise_param = NoiseParam(noise_min=0.7e-7, noise_smooth_min=1.2e-8)
|
|
132
|
+
|
|
133
|
+
def __init__(
|
|
134
|
+
self,
|
|
135
|
+
full_path: str | PathLike,
|
|
136
|
+
site_meta: dict,
|
|
137
|
+
expected_date: datetime.date | None = None,
|
|
138
|
+
) -> None:
|
|
139
|
+
super().__init__(no_reader, full_path, site_meta, expected_date)
|
|
140
|
+
self._store_ceilometer_info()
|
|
141
|
+
|
|
142
|
+
def _store_ceilometer_info(self) -> None:
|
|
143
|
+
self.instrument = instruments.LD40
|
|
144
|
+
|
|
145
|
+
|
|
128
146
|
class Cs135(VaisalaCeilo):
|
|
129
147
|
"""Class for Campbell Scientific CS135 ceilometer."""
|
|
130
148
|
|
|
@@ -140,3 +158,8 @@ class Cs135(VaisalaCeilo):
|
|
|
140
158
|
|
|
141
159
|
def _store_ceilometer_info(self) -> None:
|
|
142
160
|
self.instrument = instruments.CS135
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
def no_reader() -> None:
|
|
164
|
+
msg = "No ceilometer reader"
|
|
165
|
+
raise RuntimeError(msg)
|
|
@@ -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.1 → cloudnetpy-1.97.3}/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.1 → cloudnetpy-1.97.3}/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.1 → cloudnetpy-1.97.3}/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.1 → cloudnetpy-1.97.3}/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.1 → cloudnetpy-1.97.3}/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)])
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: cloudnetpy
|
|
3
|
-
Version: 1.97.
|
|
3
|
+
Version: 1.97.3
|
|
4
4
|
Summary: Python package for Cloudnet processing
|
|
5
5
|
Author: Simo Tukiainen
|
|
6
6
|
License: MIT License
|
|
@@ -39,7 +39,7 @@ Requires-Python: >=3.11
|
|
|
39
39
|
Description-Content-Type: text/markdown
|
|
40
40
|
License-File: LICENSE
|
|
41
41
|
Requires-Dist: atmoslib>=2.4.2
|
|
42
|
-
Requires-Dist: ceilopyter>=0.2.
|
|
42
|
+
Requires-Dist: ceilopyter>=0.2.3
|
|
43
43
|
Requires-Dist: cloudnet-api-client
|
|
44
44
|
Requires-Dist: doppy>=0.5.0
|
|
45
45
|
Requires-Dist: matplotlib
|
|
@@ -66,7 +66,7 @@ Dynamic: license-file
|
|
|
66
66
|
|
|
67
67
|
[](https://github.com/actris-cloudnet/cloudnetpy/actions/workflows/test.yml)
|
|
68
68
|
[](https://pypi.org/project/cloudnetpy/)
|
|
69
|
-
[](https://doi.org/10.5281/zenodo.3666030)
|
|
70
70
|
[](https://joss.theoj.org/papers/959971f196f617dddc0e7d8333ff22b7)
|
|
71
71
|
|
|
72
72
|
CloudnetPy is Python software designed for producing vertical profiles of cloud properties from ground-based
|
|
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.1 → cloudnetpy-1.97.3}/cloudnetpy/categorize/attenuations/gas_attenuation.py
RENAMED
|
File without changes
|
{cloudnetpy-1.97.1 → cloudnetpy-1.97.3}/cloudnetpy/categorize/attenuations/liquid_attenuation.py
RENAMED
|
File without changes
|
{cloudnetpy-1.97.1 → cloudnetpy-1.97.3}/cloudnetpy/categorize/attenuations/melting_attenuation.py
RENAMED
|
File without changes
|
{cloudnetpy-1.97.1 → cloudnetpy-1.97.3}/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
|
{cloudnetpy-1.97.1 → cloudnetpy-1.97.3}/cloudnetpy/model_evaluation/products/model_products.py
RENAMED
|
File without changes
|
{cloudnetpy-1.97.1 → cloudnetpy-1.97.3}/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.1 → cloudnetpy-1.97.3}/cloudnetpy/model_evaluation/tests/e2e/process_cf/__init__.py
RENAMED
|
File without changes
|
{cloudnetpy-1.97.1 → cloudnetpy-1.97.3}/cloudnetpy/model_evaluation/tests/e2e/process_cf/main.py
RENAMED
|
File without changes
|
{cloudnetpy-1.97.1 → cloudnetpy-1.97.3}/cloudnetpy/model_evaluation/tests/e2e/process_cf/tests.py
RENAMED
|
File without changes
|
|
File without changes
|
{cloudnetpy-1.97.1 → cloudnetpy-1.97.3}/cloudnetpy/model_evaluation/tests/e2e/process_iwc/main.py
RENAMED
|
File without changes
|
{cloudnetpy-1.97.1 → cloudnetpy-1.97.3}/cloudnetpy/model_evaluation/tests/e2e/process_iwc/tests.py
RENAMED
|
File without changes
|
|
File without changes
|
{cloudnetpy-1.97.1 → cloudnetpy-1.97.3}/cloudnetpy/model_evaluation/tests/e2e/process_lwc/main.py
RENAMED
|
File without changes
|
{cloudnetpy-1.97.1 → cloudnetpy-1.97.3}/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.1 → cloudnetpy-1.97.3}/cloudnetpy/model_evaluation/tests/unit/test_plot_tools.py
RENAMED
|
File without changes
|
{cloudnetpy-1.97.1 → cloudnetpy-1.97.3}/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
|
|
File without changes
|