├── .github └── workflows │ ├── docs.yaml │ ├── publish.yml │ └── test.yml ├── .gitignore ├── .pre-commit-config.yaml ├── ACKNOWLEDGEMENTS.md ├── CHANGELOG.md ├── CITATION.cff ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── MANIFEST.in ├── README.md ├── _typos.toml ├── cloudnetpy ├── __init__.py ├── categorize │ ├── __init__.py │ ├── atmos_utils.py │ ├── attenuation.py │ ├── attenuations │ │ ├── __init__.py │ │ ├── gas_attenuation.py │ │ ├── liquid_attenuation.py │ │ ├── melting_attenuation.py │ │ └── rain_attenuation.py │ ├── categorize.py │ ├── classify.py │ ├── containers.py │ ├── disdrometer.py │ ├── droplet.py │ ├── falling.py │ ├── freezing.py │ ├── insects.py │ ├── itu.py │ ├── lidar.py │ ├── melting.py │ ├── model.py │ ├── mwr.py │ └── radar.py ├── cli.py ├── cloudnetarray.py ├── concat_lib.py ├── constants.py ├── datasource.py ├── exceptions.py ├── instruments │ ├── __init__.py │ ├── basta.py │ ├── bowtie.py │ ├── ceilo.py │ ├── ceilometer.py │ ├── cl61d.py │ ├── cloudnet_instrument.py │ ├── copernicus.py │ ├── disdrometer │ │ ├── __init__.py │ │ ├── common.py │ │ ├── parsivel.py │ │ └── thies.py │ ├── fd12p.py │ ├── galileo.py │ ├── hatpro.py │ ├── instruments.py │ ├── lufft.py │ ├── mira.py │ ├── mrr.py │ ├── nc_lidar.py │ ├── nc_radar.py │ ├── pollyxt.py │ ├── radiometrics.py │ ├── rain_e_h3.py │ ├── rpg.py │ ├── rpg_reader.py │ ├── toa5.py │ ├── vaisala.py │ └── weather_station.py ├── metadata.py ├── model_evaluation │ ├── __init__.py │ ├── file_handler.py │ ├── metadata.py │ ├── model_metadata.py │ ├── plotting │ │ ├── __init__.py │ │ ├── plot_meta.py │ │ ├── plot_tools.py │ │ └── plotting.py │ ├── products │ │ ├── __init__.py │ │ ├── advance_methods.py │ │ ├── grid_methods.py │ │ ├── model_products.py │ │ ├── observation_products.py │ │ ├── product_resampling.py │ │ └── tools.py │ ├── pytest.ini │ ├── statistics │ │ ├── __init__.py │ │ └── statistical_methods.py │ ├── tests │ │ ├── __init__.py │ │ ├── data │ │ │ ├── 20190517_mace-head_categorize.nc │ │ │ ├── 20190517_mace-head_ecmwf.nc │ │ │ ├── 20190517_mace-head_iwc-Z-T-method.nc │ │ │ └── 20190517_mace-head_lwc-scaled-adiabatic.nc │ │ ├── e2e │ │ │ ├── __init__.py │ │ │ ├── conftest.py │ │ │ ├── process_cf │ │ │ │ ├── __init__.py │ │ │ │ ├── main.py │ │ │ │ └── tests.py │ │ │ ├── process_iwc │ │ │ │ ├── __init__.py │ │ │ │ ├── main.py │ │ │ │ └── tests.py │ │ │ └── process_lwc │ │ │ │ ├── __init__.py │ │ │ │ ├── main.py │ │ │ │ └── tests.py │ │ └── unit │ │ │ ├── __init__.py │ │ │ ├── conftest.py │ │ │ ├── test_advance_methods.py │ │ │ ├── test_grid_methods.py │ │ │ ├── test_model_products.py │ │ │ ├── test_observation_products.py │ │ │ ├── test_plot_tools.py │ │ │ ├── test_plotting.py │ │ │ ├── test_statistical_methods.py │ │ │ └── test_tools.py │ └── utils.py ├── output.py ├── plotting │ ├── __init__.py │ ├── plot_meta.py │ └── plotting.py ├── products │ ├── __init__.py │ ├── classification.py │ ├── der.py │ ├── drizzle.py │ ├── drizzle_error.py │ ├── drizzle_tools.py │ ├── epsilon.py │ ├── ier.py │ ├── iwc.py │ ├── lwc.py │ ├── mie_lu_tables.nc │ ├── mwr_tools.py │ └── product_tools.py ├── py.typed ├── utils.py └── version.py ├── docs ├── .gitignore ├── Makefile ├── index.html ├── requirements.txt ├── scripts │ ├── quickstart_download │ └── quickstart_generate └── source │ ├── _static │ ├── 20190423_mace-head_classification.png │ ├── 20230831_lindenberg_classification-9b74f4ac-target_classification.png │ ├── CLU_workflow.png │ ├── cloudnet_logo.png │ ├── cloudradar_cropped.jpeg │ ├── custom.css │ ├── dlidar.jpg │ ├── example_data.jpg │ ├── example_data.png │ ├── quickstart_classification.png │ ├── quickstart_lidar.png │ ├── quickstart_model.png │ ├── quickstart_mwr.png │ ├── quickstart_radar.png │ └── radar.jpg │ ├── _templates │ └── layout.html │ ├── api.rst │ ├── conf.py │ ├── fileformat.rst │ ├── index.rst │ ├── installation.rst │ ├── model-evaluation │ ├── _figs │ │ ├── 20190517_mace-head_cf_A_adv_ecmwf_single.png │ │ ├── 20190517_mace-head_cf_V_adv_ecmwf_single.png │ │ ├── 20190517_mace-head_cf_aerror_ecmwf.png │ │ ├── 20190517_mace-head_cf_area_ecmwf.png │ │ ├── 20190517_mace-head_cf_ecmwf_group.png │ │ ├── 20190517_mace-head_cf_hist_ecmwf.png │ │ ├── 20190517_mace-head_cf_vertical_ecmwf.png │ │ ├── 20190517_mace-head_classification.png │ │ ├── 20190517_mace-head_iwc-Z-T-method.png │ │ ├── 20190517_mace-head_iwc_ecmwf_group.png │ │ ├── 20190517_mace-head_lwc-scaled-adiabatic.png │ │ ├── 20190517_mace-head_lwc_ecmwf_group.png │ │ ├── L3_process.png │ │ └── custom.css │ └── overview.rst │ ├── overview.rst │ └── quickstart.rst ├── paper ├── paper.bib └── paper.md ├── pyproject.toml └── tests ├── __init__.py ├── e2e_test.py ├── source_data ├── ecmwf_model.nc ├── hatpro_mwr.nc ├── raw_chm15k_lidar.nc └── raw_mira_radar.mmclx └── unit ├── all_products_fun.py ├── conftest.py ├── data ├── basta │ └── basta_1a_cldradLz1R025m_v03_20210827_000000.nc ├── bowtie │ └── bowtie-trunc.nc ├── chm15k-2 │ └── 1-profile.nc ├── chm15k │ ├── 00100_A202010220005_CHM170137.nc │ └── 00100_A202010222015_CHM170137.nc ├── cl61d-2 │ ├── live_20230730_001125.nc │ ├── live_20230730_020625.nc │ └── live_20230730_052625.nc ├── cl61d │ ├── live_20210829_000020.nc │ ├── live_20210829_104420.nc │ ├── live_20210829_224520.nc │ ├── live_20210829_230720.nc │ ├── live_20210829_234321.nc │ ├── live_20210829_235520.nc │ └── live_20210830_035020.nc ├── copernicus │ └── copernicus-file-20220710.nc ├── cs135 │ ├── 20230612_ceilometer.txt │ └── ceilometer_L0_20250306.dat ├── epsilon │ ├── doppler-lidar-wind.nc │ └── doppler-lidar.nc ├── fd12p │ ├── PW040406.DAT │ ├── PW040712.DAT │ └── PW130121.DAT ├── galileo │ ├── galileo-file-1.nc │ └── galileo-file-2.nc ├── hatpro-iwv │ ├── 21021700.IWV │ └── 21021703.IWV ├── hatpro-lwp-iwv-2 │ ├── 21020600.IWV │ ├── 21020600.LWP │ └── 21020601.LWP ├── hatpro-lwp-iwv │ ├── 21060300.IWV │ ├── 21060300.LWP │ ├── 21060301.IWV │ └── 21060301.LWP ├── hatpro-lwp │ ├── 21012300.LWP │ ├── 21012323.LWP │ ├── ZENITH_200723.LWP │ └── cabauw-hatpro-20210726.LWP ├── hatpro-mwrpy-coeffs │ ├── HPT_NN_FI_Hyytiala_v110_v00110_n01.00.ret │ ├── IWV_NN_MA_FI_Hyytiala_v110_v00110_n01.00.ret │ ├── LWP_NN_MA_FI_Hyytiala_v110_v00110_n01.00.ret │ ├── SPC_NN_MA_FI_Hyytiala_v110_v00110_n01.00.ret │ ├── TPB_NN_FI_Hyytiala_v110_v00110_n01.00.ret │ └── TPT_NN_FI_Hyytiala_v110_v00110_n01.00.ret ├── hatpro-mwrpy │ ├── 230401.BLB │ ├── 230401.BRT │ ├── 230401.HKD │ ├── 230401.IRT │ └── 230401.MET ├── mira-masked │ └── 20231003_1400-trunc.mmclx ├── mira-nyquist │ └── 20200116_0000-trunc.mmclx ├── mira │ ├── 20210102_0000.mmclx │ └── 20210102_1400.mmclx ├── mira_inconsistent │ ├── file1.mmclx │ └── file2.mmclx ├── mira_stsr │ └── 20230201_0900_mbr7_stsr-trunc.znc ├── mira_znc │ ├── 20230201_0900_mbr5-trunc.mmclx │ └── 20230201_0900_mbr5-trunc.znc ├── mrr │ └── 20220124_180000.nc ├── parsivel │ ├── Lindenberg_Parsivel_20231204.log │ ├── bucharest-20240219.cvs │ ├── bucharest_0000000123_20231025221800.txt │ ├── granada.dat │ ├── hyytiala.txt │ ├── hyytiala2.txt │ ├── juelich.log │ ├── norunda.log │ ├── ny-alesund.log │ ├── palaiseau.txt │ ├── parsivel_bad.log │ └── warsaw.txt ├── pollyxt │ ├── 2021_09_17_Fri_CPV_00_00_31_att_bsc.nc │ ├── 2021_09_17_Fri_CPV_00_00_31_vol_depol.nc │ ├── 2021_09_17_Fri_CPV_06_00_31_att_bsc.nc │ ├── 2021_09_17_Fri_CPV_06_00_31_vol_depol.nc │ ├── 2021_09_17_Fri_CPV_12_00_31_att_bsc.nc │ ├── 2021_09_17_Fri_CPV_12_00_31_vol_depol.nc │ ├── 2021_09_17_Fri_CPV_18_00_31_att_bsc.nc │ ├── 2021_09_17_Fri_CPV_18_00_31_vol_depol.nc │ └── broken_1064_channel │ │ ├── truncated_2022_06_16_Thu_UWA_00_00_31_att_bsc.nc │ │ └── truncated_2022_06_16_Thu_UWA_00_00_31_vol_depol.nc ├── radiometrics │ ├── 20100926_0005.los │ ├── 20131220_1319.los │ ├── 20140106_1126.los │ ├── 2021-07-18_00-00-00_lv2.csv │ ├── 2021-10-06_00-04-08_lv2.csv │ └── 2024-01-22_00-04-09_lv2.csv ├── rain_e_h3 │ ├── 20241231_raine_lindenberg.csv │ └── Lindenberg_RainE_20230514.txt ├── rpg-fmcw-94-corrupted │ └── 230401_000001_P00_ZEN.LV1 ├── rpg-fmcw-94 │ ├── 201022_000002_P06_ZEN.LV1 │ ├── 201022_230001_P06_ZEN.LV1 │ ├── 201023_160000_P06_ZEN.LV1 │ └── BaseN_210913_001152_P01_PPI.LV1 ├── thies-lnm │ ├── 2021091507.txt │ ├── 2024041723-leipzig-lim.txt │ ├── 20241003_lnm_lindenberg.csv │ ├── 2025060200.txt │ ├── PAY_DDTH01_20240702_actris.csv │ └── THIES_LMP_2024_04_14_00_00.dat ├── vaisala │ ├── C5061800-first-invalid.DAT │ ├── cl31.DAT │ ├── cl31_badtime.DAT │ ├── cl51-corrupted-profile.cl │ ├── cl51.DAT │ └── ct25k.dat └── ws │ ├── 20250127_JOYCE_WST_01m.dat │ ├── Krova_aws_pqBARLog5_20240520.csv │ ├── Krova_aws_pqBARLog5_20240521.csv │ ├── WeatherStation_20240427.csv │ ├── WeatherStation_20250413.csv │ ├── bad-ws.asc │ ├── bad-ws2.asc │ ├── bucharest.csv │ ├── bucharest2.csv │ ├── galati.csv │ ├── granada.dat │ ├── hyy20240110swx.txt │ ├── lampedusa.rep │ └── palaiseau-ws.asc ├── lidar_fun.py ├── radar_fun.py ├── test_atmos.py ├── test_atmos_utils.py ├── test_basta.py ├── test_binning.py ├── test_bowtie.py ├── test_categorize.py ├── test_ceilo.py ├── test_ceilometer.py ├── test_cl61d.py ├── test_classification.py ├── test_classify.py ├── test_cloudnetarray.py ├── test_concat_lib.py ├── test_copernicus.py ├── test_cs135.py ├── test_datasource.py ├── test_der.py ├── test_disdrometer.py ├── test_drizzle.py ├── test_drizzle_error.py ├── test_droplet.py ├── test_epsilon.py ├── test_falling.py ├── test_fd12p.py ├── test_freezing.py ├── test_galileo.py ├── test_hatpro.py ├── test_hatpro_l1c.py ├── test_ier.py ├── test_insects.py ├── test_itu.py ├── test_iwc.py ├── test_lidar.py ├── test_lufft.py ├── test_lwc.py ├── test_melting.py ├── test_mira.py ├── test_model.py ├── test_mrr.py ├── test_mwr.py ├── test_netcdf_concatenation.py ├── test_output.py ├── test_plotting.py ├── test_pollyxt.py ├── test_product_tools.py ├── test_radar.py ├── test_radiometrics.py ├── test_rain_e_h3.py ├── test_rpg.py ├── test_utils.py ├── test_vaisala.py └── test_weather_station.py /.github/workflows/docs.yaml: -------------------------------------------------------------------------------- 1 | name: Generate documentation 2 | 3 | on: 4 | push: 5 | tags: 6 | - "v*.*.*" 7 | 8 | jobs: 9 | build: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - name: Checkout 13 | uses: actions/checkout@v4 14 | - name: Set up Python 15 | uses: actions/setup-python@v4 16 | with: 17 | python-version: "3.10" 18 | cache: pip 19 | - name: Install dependencies 20 | run: | 21 | pip install . 22 | pip install -r docs/requirements.txt 23 | - name: Build documentation 24 | run: | 25 | cd docs 26 | make html 27 | touch html/.nojekyll 28 | - name: Upload Pages artifact 29 | uses: actions/upload-pages-artifact@v3 30 | with: 31 | path: docs/html 32 | deploy: 33 | needs: build 34 | permissions: 35 | pages: write 36 | id-token: write 37 | environment: 38 | name: github-pages 39 | url: ${{ steps.deployment.outputs.page_url }} 40 | runs-on: ubuntu-latest 41 | steps: 42 | - name: Deploy to GitHub Pages 43 | id: deployment 44 | uses: actions/deploy-pages@v4 45 | -------------------------------------------------------------------------------- /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | name: Upload Python Package 2 | 3 | on: 4 | push: 5 | tags: 6 | - "v*.*.*" 7 | 8 | jobs: 9 | deploy: 10 | runs-on: ubuntu-latest 11 | permissions: 12 | contents: write 13 | id-token: write 14 | steps: 15 | - uses: actions/checkout@v4 16 | - name: Set up Python 17 | uses: actions/setup-python@v5 18 | with: 19 | python-version: "3.10" 20 | - name: Install dependencies 21 | run: python -m pip install build 22 | - name: Build package 23 | run: python -m build 24 | - name: Publish package 25 | uses: pypa/gh-action-pypi-publish@release/v1 26 | - name: Generate changelog 27 | run: | 28 | version=${GITHUB_REF#refs/tags/v} 29 | sed "0,/^## ${version//./\\.}/d;/^## /,\$d" CHANGELOG.md > ${{ github.workspace }}-CHANGELOG.txt 30 | echo "name=CloudnetPy $version" >> $GITHUB_OUTPUT 31 | id: changelog 32 | - name: Create release 33 | uses: softprops/action-gh-release@v1 34 | with: 35 | name: ${{ steps.changelog.outputs.name }} 36 | body_path: ${{ github.workspace }}-CHANGELOG.txt 37 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: CloudnetPy CI 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | build: 7 | strategy: 8 | matrix: 9 | os: [ubuntu-latest, windows-latest, macos-latest] 10 | python-version: ["3.10", "3.11", "3.12", "3.13"] 11 | 12 | runs-on: ${{ matrix.os }} 13 | steps: 14 | - uses: actions/checkout@v4 15 | - name: Set up Python ${{ matrix.python-version }} 16 | uses: actions/setup-python@v5 17 | with: 18 | python-version: ${{ matrix.python-version }} 19 | cache: "pip" 20 | - name: Install dependencies 21 | run: | 22 | pip install --upgrade pip 23 | pip install .[dev,test,extras] 24 | - name: Run pre-commit checks 25 | if: startsWith(matrix.os, 'ubuntu-') 26 | run: | 27 | pre-commit run --all-files --show-diff-on-failure 28 | - name: Run unit tests 29 | run: | 30 | pytest --flake-finder --flake-runs=2 31 | - name: Run E2E tests 32 | run: | 33 | python3 tests/e2e_test.py 34 | python3 cloudnetpy/model_evaluation/tests/e2e/process_cf/main.py 35 | python3 cloudnetpy/model_evaluation/tests/e2e/process_iwc/main.py 36 | python3 cloudnetpy/model_evaluation/tests/e2e/process_lwc/main.py 37 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | input/ 2 | output/ 3 | *.nc 4 | .idea/ 5 | .vscode/ 6 | venv/ 7 | *__pycache__* 8 | tests/source_data/ 9 | *egg-info* 10 | build/ 11 | dist/ 12 | .eggs/ 13 | scripts/ 14 | *~ 15 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | repos: 2 | - repo: https://github.com/pre-commit/pre-commit-hooks 3 | rev: v4.6.0 4 | hooks: 5 | - id: check-case-conflict 6 | - id: check-executables-have-shebangs 7 | - id: check-merge-conflict 8 | - id: check-shebang-scripts-are-executable 9 | - id: end-of-file-fixer 10 | exclude: ^tests/unit/data/ 11 | - id: fix-byte-order-marker 12 | exclude: ^tests/unit/data/ 13 | - id: mixed-line-ending 14 | args: ["--fix", "lf"] 15 | exclude: ^tests/unit/data/ 16 | - id: requirements-txt-fixer 17 | - id: trailing-whitespace 18 | exclude: ^tests/unit/data/ 19 | - repo: https://github.com/astral-sh/ruff-pre-commit 20 | rev: v0.6.5 21 | hooks: 22 | - id: ruff 23 | args: ["--fix"] 24 | - id: ruff-format 25 | - repo: local 26 | hooks: 27 | - id: mypy 28 | name: mypy 29 | entry: mypy 30 | language: system 31 | types: [python] 32 | require_serial: true 33 | - repo: https://github.com/pre-commit/mirrors-prettier 34 | rev: v3.1.0 35 | hooks: 36 | - id: prettier 37 | exclude: ^docs/source/_templates/ 38 | - repo: https://github.com/pappasam/toml-sort 39 | rev: v0.23.1 40 | hooks: 41 | - id: toml-sort-fix 42 | - repo: https://github.com/crate-ci/typos 43 | rev: v1.23.5 44 | hooks: 45 | - id: typos 46 | args: ["--force-exclude"] 47 | -------------------------------------------------------------------------------- /ACKNOWLEDGEMENTS.md: -------------------------------------------------------------------------------- 1 | # Acknowledgements 2 | 3 | CloudnetPy contains code and ideas from several people and would 4 | not exist without their prior contribution. 5 | 6 | Below is a partial list. Please add your name if it has been left off. 7 | 8 | - Hannes Griesche 9 | - Robin Hogan 10 | - Anthony Illingworth 11 | - Lauri Kangassalo 12 | - Anniina Korpinen 13 | - Ewan O'Connor 14 | - Willi Schimmel 15 | - Tuomas Siipola 16 | - Simo Tukiainen 17 | - Minttu Tuononen 18 | -------------------------------------------------------------------------------- /CITATION.cff: -------------------------------------------------------------------------------- 1 | cff-version: 1.2.0 2 | title: >- 3 | CloudnetPy: A Python package for processing cloud remote 4 | sensing data 5 | message: >- 6 | If you use this software, please cite our article in the 7 | Journal of Open Source Software. 8 | type: software 9 | authors: 10 | - family-names: Tukiainen 11 | given-names: Simo 12 | orcid: "https://orcid.org/0000-0002-0651-4622" 13 | affiliation: Finnish Meteorological Institute 14 | - given-names: Tuomas 15 | family-names: Siipola 16 | affiliation: Finnish Meteorological Institute 17 | orcid: "https://orcid.org/0009-0004-7757-0893" 18 | - family-names: Anniina 19 | given-names: Korpinen 20 | affiliation: Finnish Meteorological Institute 21 | - family-names: O'Connor 22 | given-names: Ewan 23 | orcid: "https://orcid.org/0000-0001-9834-5100" 24 | affiliation: Finnish Meteorological Institute 25 | contact: 26 | - family-names: Tukiainen 27 | given-names: Simo 28 | orcid: "https://orcid.org/0000-0002-0651-4622" 29 | affiliation: Finnish Meteorological Institute 30 | repository-code: "https://github.com/actris-cloudnet/cloudnetpy" 31 | abstract: >- 32 | CloudnetPy is Python software designed for producing 33 | vertical profiles of cloud properties from ground-based 34 | remote sensing measurements. 35 | keywords: 36 | - Python 37 | - cloud radar 38 | - lidar 39 | - microwave radiometer 40 | - remote sensing 41 | license: MIT 42 | doi: 10.5281/zenodo.3666030 43 | preferred-citation: 44 | type: article 45 | authors: 46 | - family-names: Tukiainen 47 | given-names: Simo 48 | orcid: "https://orcid.org/0000-0002-0651-4622" 49 | affiliation: Finnish Meteorological Institute 50 | - family-names: O'Connor 51 | given-names: Ewan 52 | orcid: "https://orcid.org/0000-0001-9834-5100" 53 | affiliation: Finnish Meteorological Institute 54 | - family-names: Anniina 55 | given-names: Korpinen 56 | affiliation: Finnish Meteorological Institute 57 | doi: 10.21105/joss.02123 58 | journal: Journal of Open Source Software 59 | publisher: 60 | name: Open Journals 61 | issn: 2475-9066 62 | start: 2123 63 | end: 2123 64 | title: "CloudnetPy: A Python package for processing cloud remote sensing data" 65 | issue: 53 66 | volume: 5 67 | year: 2020 68 | date-published: 2020-09-04 69 | url: https://joss.theoj.org/papers/10.21105/joss.02123 70 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, sex characteristics, gender identity and expression, 9 | level of experience, education, socio-economic status, nationality, personal 10 | appearance, race, religion, or sexual identity and orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | - Using welcoming and inclusive language 18 | - Being respectful of differing viewpoints and experiences 19 | - Gracefully accepting constructive criticism 20 | - Focusing on what is best for the community 21 | - Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | - The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | - Trolling, insulting/derogatory comments, and personal or political attacks 28 | - Public or private harassment 29 | - Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | - Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at cloudnet@fmi.fi. All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html 72 | 73 | [homepage]: https://www.contributor-covenant.org 74 | 75 | For answers to common questions about this code of conduct, see 76 | https://www.contributor-covenant.org/faq 77 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # How to Contribute to CloudnetPy 2 | 3 | First off, thanks for taking the time to contribute! :+1: 4 | 5 | ## Reporting bugs 6 | 7 | - Create a new [issue](https://github.com/actris-cloudnet/cloudnetpy/issues) 8 | - Describe the problem and steps to reproduce it 9 | - Suggest a fix if you can but this is not mandatory 10 | 11 | ## Proposing code improvements 12 | 13 | If you have a good idea how to improve the code without changing the actual outcome 14 | of the processing, you can directly create a [pull request](https://github.com/actris-cloudnet/cloudnetpy/pulls) (PR). 15 | Examples of these kinds of changes are, for example, better 16 | 17 | - implementations of the helper functions 18 | - docstrings, type hints, variable names etc. 19 | - unit tests 20 | 21 | Try to keep your pull requests small. This will increase the chance to be accepted. 22 | 23 | ## Proposing new features 24 | 25 | Changes that would alter the actual outcome of the processing need to be carefully 26 | reviewed and tested before accepting. Examples include, for example, modifications in the 27 | 28 | - methodology (e.g. replacing the wet bulb method with a newer version) 29 | - threshold values used in different functions / methods 30 | - constant values 31 | 32 | Also, for these kinds of proposals, you can open a new [issue](https://github.com/actris-cloudnet/cloudnetpy/issues) 33 | where the idea can be discussed before (possible) implementation. 34 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019-2022 Finnish Meteorological Institute 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | recursive-exclude tests * 2 | include cloudnetpy/products/mie_lu_tables.nc 3 | include cloudnetpy/py.typed 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # CloudnetPy 2 | 3 | [![CloudnetPy CI](https://github.com/actris-cloudnet/cloudnetpy/actions/workflows/test.yml/badge.svg)](https://github.com/actris-cloudnet/cloudnetpy/actions/workflows/test.yml) 4 | [![PyPI version](https://badge.fury.io/py/cloudnetpy.svg)](https://badge.fury.io/py/cloudnetpy) 5 | [![DOI](https://zenodo.org/badge/233602651.svg)](https://zenodo.org/badge/latestdoi/233602651) 6 | [![status](https://joss.theoj.org/papers/959971f196f617dddc0e7d8333ff22b7/status.svg)](https://joss.theoj.org/papers/959971f196f617dddc0e7d8333ff22b7) 7 | 8 | CloudnetPy is Python software designed for producing vertical profiles of cloud properties from ground-based 9 | remote sensing measurements. The Cloudnet processing combines data from cloud radar, optical lidar, 10 | microwave radiometer, and numerical weather prediction models. 11 | Measurements and model data are brought into a common grid and 12 | classified as ice, liquid, aerosol, insects, and so on. 13 | Subsequently, geophysical products such as ice water content can be 14 | retrieved in further processing steps. See [Illingworth et al. (2007)](https://doi.org/10.1175/BAMS-88-6-883) for more details about the concept. 15 | 16 | CloudnetPy is a rewritten version of the original Cloudnet MATLAB code. It features several revised methods, extensive documentation, and more. 17 | 18 | - CloudnetPy documentation: 19 | - Cloudnet data portal: 20 | 21 | ![CloudnetPy example output](https://raw.githubusercontent.com/actris-cloudnet/cloudnetpy/main/docs/source/_static/20230831_lindenberg_classification-9b74f4ac-target_classification.png) 22 | 23 | ## Installation 24 | 25 | ### Option 1: From PyPI 26 | 27 | ``` 28 | python3 -m pip install cloudnetpy 29 | ``` 30 | 31 | ### Option 2: From the source 32 | 33 | ```sh 34 | git clone https://github.com/actris-cloudnet/cloudnetpy 35 | cd cloudnetpy/ 36 | python3 -m venv venv 37 | source venv/bin/activate 38 | python3 -m pip install --upgrade pip 39 | python3 -m pip install . 40 | ``` 41 | 42 | ### Verification 43 | 44 | To verify the installation: 45 | 46 | ```sh 47 | cloudnetpy --help 48 | ``` 49 | 50 | ## Citing 51 | 52 | If you wish to acknowledge CloudnetPy in your publication, please cite: 53 | 54 | > Tukiainen et al., (2020). CloudnetPy: A Python package for processing cloud remote sensing data. Journal of Open Source Software, 5(53), 2123, https://doi.org/10.21105/joss.02123 55 | 56 | ## Contributing 57 | 58 | We encourage you to contribute to CloudnetPy! Please check out the [contribution guidelines](CONTRIBUTING.md) about how to proceed. 59 | 60 | ## Development 61 | 62 | Follow the installation instructions from the source above but install with the development dependencies and [pre-commit](https://pre-commit.com/) hooks: 63 | 64 | ```sh 65 | python3 -m pip install -e .[dev,test] 66 | pre-commit install 67 | ``` 68 | 69 | Run unit tests: 70 | 71 | ```sh 72 | python3 -m pytest 73 | python3 -m pytest tests/unit/test_hatpro.py 74 | ``` 75 | 76 | Run end-to-end tests: 77 | 78 | ```sh 79 | python3 tests/e2e_test.py 80 | for f in cloudnetpy/model_evaluation/tests/e2e/*/main.py; do $f; done 81 | ``` 82 | 83 | Force `pre-commit` checks (`ruff`, `mypy`, etc.) for all files: 84 | 85 | ```sh 86 | pre-commit run --all 87 | ``` 88 | 89 | ## License 90 | 91 | MIT 92 | -------------------------------------------------------------------------------- /_typos.toml: -------------------------------------------------------------------------------- 1 | [default] 2 | extend-ignore-re = ["Winddirection\\(degres\\)"] 3 | extend-ignore-identifiers-re = ["LinearNDInterpolator", "MOR", "NearestNDInterpolator", "morVisibility", "mor_visibility", "reshape_1d2nd"] 4 | 5 | [files] 6 | extend-exclude = ["tests/unit/data"] 7 | -------------------------------------------------------------------------------- /cloudnetpy/__init__.py: -------------------------------------------------------------------------------- 1 | from cloudnetpy.version import __version__ 2 | -------------------------------------------------------------------------------- /cloudnetpy/categorize/__init__.py: -------------------------------------------------------------------------------- 1 | from .categorize import generate_categorize 2 | -------------------------------------------------------------------------------- /cloudnetpy/categorize/attenuation.py: -------------------------------------------------------------------------------- 1 | from numpy import ma 2 | 3 | from cloudnetpy.categorize.attenuations import ( 4 | RadarAttenuation, 5 | gas_attenuation, 6 | liquid_attenuation, 7 | melting_attenuation, 8 | rain_attenuation, 9 | ) 10 | from cloudnetpy.categorize.containers import ClassificationResult, Observations 11 | 12 | 13 | def get_attenuations( 14 | data: Observations, classification: ClassificationResult 15 | ) -> RadarAttenuation: 16 | rain = rain_attenuation.calc_rain_attenuation(data, classification) 17 | gas = gas_attenuation.calc_gas_attenuation(data, classification) 18 | liquid = liquid_attenuation.LiquidAttenuation(data, classification).attenuation 19 | melting = melting_attenuation.calc_melting_attenuation(data, classification) 20 | 21 | liquid.amount[rain.attenuated] = ma.masked 22 | liquid.error[rain.attenuated] = ma.masked 23 | liquid.attenuated[rain.attenuated] = False 24 | liquid.uncorrected[rain.attenuated] = False 25 | 26 | return RadarAttenuation( 27 | gas=gas, 28 | liquid=liquid, 29 | rain=rain, 30 | melting=melting, 31 | ) 32 | -------------------------------------------------------------------------------- /cloudnetpy/categorize/attenuations/__init__.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | from typing import Annotated 3 | 4 | import numpy as np 5 | from numpy import ma 6 | from numpy.typing import NDArray 7 | 8 | from cloudnetpy import constants as con 9 | from cloudnetpy.utils import path_lengths_from_ground 10 | 11 | 12 | @dataclass 13 | class Attenuation: 14 | amount: Annotated[ma.MaskedArray, "float32"] 15 | error: Annotated[ma.MaskedArray, "float32"] 16 | attenuated: NDArray[np.bool_] 17 | uncorrected: NDArray[np.bool_] 18 | 19 | 20 | @dataclass 21 | class RadarAttenuation: 22 | gas: Attenuation 23 | liquid: Attenuation 24 | rain: Attenuation 25 | melting: Attenuation 26 | 27 | 28 | def calc_two_way_attenuation( 29 | height: np.ndarray, specific_attenuation: ma.MaskedArray 30 | ) -> ma.MaskedArray: 31 | """Calculates two-way attenuation (dB) for given specific attenuation 32 | (dB km-1) and height (m). 33 | """ 34 | path_lengths = path_lengths_from_ground(height) * con.M_TO_KM # km 35 | one_way_attenuation = specific_attenuation * path_lengths 36 | accumulated_attenuation = ma.cumsum(one_way_attenuation, axis=1) 37 | return accumulated_attenuation * con.TWO_WAY 38 | -------------------------------------------------------------------------------- /cloudnetpy/categorize/attenuations/gas_attenuation.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | 3 | from cloudnetpy.categorize.attenuations import ( 4 | Attenuation, 5 | calc_two_way_attenuation, 6 | ) 7 | from cloudnetpy.categorize.containers import ClassificationResult, Observations 8 | 9 | 10 | def calc_gas_attenuation( 11 | data: Observations, classification: ClassificationResult 12 | ) -> Attenuation: 13 | model_data = data.model.data_dense 14 | 15 | specific_attenuation = model_data["specific_gas_atten"].copy() 16 | saturated_attenuation = model_data["specific_saturated_gas_atten"] 17 | 18 | liquid_in_pixel = classification.category_bits.droplet 19 | specific_attenuation[liquid_in_pixel] = saturated_attenuation[liquid_in_pixel] 20 | 21 | two_way_attenuation = calc_two_way_attenuation( 22 | data.radar.height, specific_attenuation 23 | ) 24 | 25 | return Attenuation( 26 | amount=two_way_attenuation, 27 | error=two_way_attenuation * 0.1, 28 | attenuated=np.ones_like(two_way_attenuation, dtype=bool), 29 | uncorrected=np.zeros_like(two_way_attenuation, dtype=bool), 30 | ) 31 | -------------------------------------------------------------------------------- /cloudnetpy/categorize/attenuations/liquid_attenuation.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from numpy import ma 3 | 4 | import cloudnetpy.constants as con 5 | from cloudnetpy import utils 6 | from cloudnetpy.categorize import atmos_utils 7 | from cloudnetpy.categorize.attenuations import Attenuation, calc_two_way_attenuation 8 | from cloudnetpy.categorize.containers import ClassificationResult, Observations 9 | 10 | 11 | class LiquidAttenuation: 12 | """Class for calculating liquid attenuation. 13 | 14 | References: 15 | Hogan, Robin & Connor, Ewan. (2004). Facilitating cloud radar and lidar 16 | algorithms: the Cloudnet Instrument Synergy/Target Categorization product. 17 | """ 18 | 19 | def __init__(self, data: Observations, classification: ClassificationResult): 20 | self._model = data.model.data_dense 21 | self._liquid_in_pixel = classification.category_bits.droplet 22 | self._height = data.radar.height 23 | 24 | if data.mwr is not None: 25 | lwp = data.mwr.data["lwp"][:] 26 | lwp_error = data.mwr.data["lwp_error"][:] 27 | else: 28 | lwp = ma.masked_all(data.radar.time.size) 29 | lwp_error = ma.masked_all(data.radar.time.size) 30 | 31 | lwc_dz = atmos_utils.fill_clouds_with_lwc_dz( 32 | self._model["temperature"], self._model["pressure"], self._liquid_in_pixel 33 | ) 34 | 35 | two_way_attenuation = self._calc_liquid_atten(lwp, lwc_dz) 36 | two_way_attenuation_error = self._calc_liquid_atten_err(lwp_error, lwc_dz) 37 | 38 | attenuated = utils.ffill(self._liquid_in_pixel) 39 | 40 | two_way_attenuation[~attenuated] = ma.masked 41 | two_way_attenuation_error[~attenuated] = ma.masked 42 | 43 | uncorrected = attenuated & two_way_attenuation.mask 44 | 45 | self.attenuation = Attenuation( 46 | amount=two_way_attenuation, 47 | error=two_way_attenuation_error, 48 | attenuated=attenuated, 49 | uncorrected=uncorrected, 50 | ) 51 | 52 | def _calc_liquid_atten( 53 | self, lwp: ma.MaskedArray, lwc_dz: np.ndarray 54 | ) -> ma.MaskedArray: 55 | """Finds radar liquid attenuation.""" 56 | lwp = lwp.copy() 57 | lwp[lwp < 0] = 0 58 | lwc_adiabatic = atmos_utils.calc_adiabatic_lwc(lwc_dz, self._height) 59 | lwc_scaled = atmos_utils.normalize_lwc_by_lwp(lwc_adiabatic, lwp, self._height) 60 | return self._calc_two_way_attenuation(lwc_scaled) 61 | 62 | def _calc_liquid_atten_err( 63 | self, lwp_error: ma.MaskedArray, lwc_dz: np.ndarray 64 | ) -> ma.MaskedArray: 65 | """Finds radar liquid attenuation error.""" 66 | lwc_err_scaled = atmos_utils.normalize_lwc_by_lwp( 67 | lwc_dz, lwp_error, self._height 68 | ) 69 | return self._calc_two_way_attenuation(lwc_err_scaled) 70 | 71 | def _calc_two_way_attenuation(self, lwc_scaled: np.ndarray) -> ma.MaskedArray: 72 | """Calculates liquid attenuation (dB). 73 | 74 | Args: 75 | lwc_scaled: Liquid water content (kg m-3). 76 | 77 | """ 78 | specific_attenuation_rate = self._model["specific_liquid_atten"] 79 | specific_attenuation = specific_attenuation_rate * lwc_scaled * con.KG_TO_G 80 | return calc_two_way_attenuation(self._height, specific_attenuation) 81 | -------------------------------------------------------------------------------- /cloudnetpy/categorize/attenuations/melting_attenuation.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from numpy import ma 3 | 4 | import cloudnetpy.constants as con 5 | from cloudnetpy import utils 6 | from cloudnetpy.categorize.attenuations import ( 7 | Attenuation, 8 | ) 9 | from cloudnetpy.categorize.containers import ClassificationResult, Observations 10 | 11 | 12 | def calc_melting_attenuation( 13 | data: Observations, classification: ClassificationResult 14 | ) -> Attenuation: 15 | shape = classification.category_bits.melting.shape 16 | is_rain = classification.is_rain 17 | 18 | affected_region = classification.category_bits.freezing.copy() 19 | 20 | if data.disdrometer is None: 21 | affected_region[~is_rain, :] = False 22 | above_melting = utils.ffill(classification.category_bits.melting) 23 | affected_region[~above_melting] = False 24 | return Attenuation( 25 | amount=ma.masked_all(shape), 26 | error=ma.masked_all(shape), 27 | attenuated=affected_region, 28 | uncorrected=affected_region, 29 | ) 30 | 31 | rainfall_rate = data.disdrometer.data["rainfall_rate"][:] 32 | frequency = data.radar.radar_frequency 33 | 34 | attenuation_array = _calc_melting_attenuation(rainfall_rate, frequency) 35 | 36 | amount = affected_region * utils.transpose(attenuation_array) 37 | 38 | affected_region[amount == 0] = False 39 | 40 | amount[amount == 0] = ma.masked 41 | 42 | band = utils.get_wl_band(data.radar.radar_frequency) 43 | error_factor = {"Ka": 0.2, "W": 0.1}[band] 44 | 45 | error = amount * error_factor 46 | error[~affected_region] = ma.masked 47 | 48 | return Attenuation( 49 | amount=amount, 50 | error=error, 51 | attenuated=affected_region, 52 | uncorrected=affected_region & amount.mask, 53 | ) 54 | 55 | 56 | def _calc_melting_attenuation( 57 | rainfall_rate: np.ndarray, frequency: float 58 | ) -> np.ndarray: 59 | """Calculates total attenuation due to melting layer (dB). 60 | 61 | References: 62 | Li, H., & Moisseev, D. (2019). Melting layer attenuation 63 | at Ka- and W-bands as derived from multifrequency radar 64 | Doppler spectra observations. Journal of Geophysical 65 | Research: Atmospheres, 124, 9520–9533. https://doi.org/10.1029/2019JD030316 66 | 67 | """ 68 | band = utils.get_wl_band(frequency) 69 | if band == "Ka": 70 | a, b = 0.97, 0.61 71 | elif band == "W": 72 | a, b = 2.9, 0.42 73 | else: 74 | msg = "Radar frequency not supported" 75 | raise ValueError(msg) 76 | return a * (rainfall_rate * con.M_S_TO_MM_H) ** b 77 | -------------------------------------------------------------------------------- /cloudnetpy/categorize/attenuations/rain_attenuation.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from numpy import ma 3 | from numpy.typing import NDArray 4 | 5 | import cloudnetpy.constants as con 6 | from cloudnetpy import utils 7 | from cloudnetpy.categorize.attenuations import ( 8 | Attenuation, 9 | calc_two_way_attenuation, 10 | ) 11 | from cloudnetpy.categorize.containers import ClassificationResult, Observations 12 | 13 | 14 | def calc_rain_attenuation( 15 | data: Observations, classification: ClassificationResult 16 | ) -> Attenuation: 17 | affected_region, inducing_region = _find_regions(classification) 18 | shape = affected_region.shape 19 | 20 | if data.disdrometer is None: 21 | return Attenuation( 22 | amount=ma.masked_all(shape), 23 | error=ma.masked_all(shape), 24 | attenuated=affected_region, 25 | uncorrected=affected_region, 26 | ) 27 | 28 | rainfall_rate = data.disdrometer.data["rainfall_rate"][:].copy() 29 | rainfall_rate[classification.is_rain == 0] = ma.masked 30 | frequency = data.radar.radar_frequency 31 | 32 | specific_attenuation_array = _calc_rain_specific_attenuation( 33 | rainfall_rate, frequency 34 | ) 35 | 36 | specific_attenuation = utils.transpose(specific_attenuation_array) * ma.ones(shape) 37 | 38 | two_way_attenuation = calc_two_way_attenuation( 39 | data.radar.height, specific_attenuation 40 | ) 41 | 42 | two_way_attenuation[~inducing_region] = 0 43 | two_way_attenuation = ma.array(utils.ffill(two_way_attenuation.data)) 44 | two_way_attenuation[two_way_attenuation == 0] = ma.masked 45 | 46 | return Attenuation( 47 | amount=two_way_attenuation, 48 | error=two_way_attenuation * 0.2, 49 | attenuated=affected_region, 50 | uncorrected=np.zeros_like(affected_region, dtype=bool), 51 | ) 52 | 53 | 54 | def _find_regions( 55 | classification: ClassificationResult, 56 | ) -> tuple[NDArray[np.bool_], NDArray[np.bool_]]: 57 | """Finds regions where rain attenuation is present and can be corrected or not.""" 58 | warm_region = ~classification.category_bits.freezing 59 | is_rain = utils.transpose(classification.is_rain).astype(bool) 60 | affected_region = np.ones_like(warm_region, dtype=bool) * is_rain 61 | inducing_region = warm_region * is_rain 62 | return affected_region, inducing_region 63 | 64 | 65 | def _calc_rain_specific_attenuation( 66 | rainfall_rate: np.ndarray, frequency: float 67 | ) -> np.ndarray: 68 | """Calculates specific attenuation due to rain (dB km-1). 69 | 70 | References: 71 | Crane, R. (1980). Prediction of Attenuation by Rain. 72 | IEEE Transactions on Communications, 28(9), 1717–1733. 73 | doi:10.1109/tcom.1980.1094844 74 | """ 75 | if frequency > 8 and frequency < 12: 76 | alpha, beta = 0.0125, 1.18 77 | if frequency > 34 and frequency < 37: 78 | alpha, beta = 0.242, 1.04 79 | elif frequency > 93 and frequency < 96: 80 | alpha, beta = 0.95, 0.72 81 | else: 82 | msg = "Radar frequency not supported" 83 | raise ValueError(msg) 84 | return alpha * (rainfall_rate * con.M_S_TO_MM_H) ** beta 85 | -------------------------------------------------------------------------------- /cloudnetpy/categorize/disdrometer.py: -------------------------------------------------------------------------------- 1 | """Mwr module, containing the :class:`Mwr` class.""" 2 | 3 | import logging 4 | 5 | import numpy as np 6 | from numpy import ma 7 | from scipy.interpolate import interp1d 8 | 9 | from cloudnetpy.categorize.lidar import get_gap_ind 10 | from cloudnetpy.datasource import DataSource 11 | from cloudnetpy.exceptions import DisdrometerDataError 12 | 13 | 14 | class Disdrometer(DataSource): 15 | """Disdrometer class, child of DataSource. 16 | 17 | Args: 18 | ---- 19 | full_path: Cloudnet Level 1b disdrometer file. 20 | 21 | """ 22 | 23 | def __init__(self, full_path: str): 24 | super().__init__(full_path) 25 | self._init_rainfall_rate() 26 | 27 | def interpolate_to_grid(self, time_grid: np.ndarray) -> None: 28 | for key, array in self.data.items(): 29 | self.data[key].data = self._interpolate(array.data, time_grid) 30 | 31 | def _init_rainfall_rate(self) -> None: 32 | keys = ("rainfall_rate", "n_particles") 33 | for key in keys: 34 | if key not in self.dataset.variables: 35 | msg = f"variable {key} is missing" 36 | raise DisdrometerDataError(msg) 37 | self.append_data(self.dataset.variables[key][:], key) 38 | 39 | def _interpolate(self, y: ma.MaskedArray, x_new: np.ndarray) -> np.ndarray: 40 | time = self.time 41 | mask = ma.getmask(y) 42 | if mask is not ma.nomask: 43 | if np.all(mask): 44 | return ma.masked_all(x_new.shape) 45 | not_masked = ~mask 46 | y = y[not_masked] 47 | time = time[not_masked] 48 | fun = interp1d(time, y, fill_value="extrapolate") 49 | interpolated_array = ma.array(fun(x_new)) 50 | max_time = 1 / 60 # min -> fraction hour 51 | mask_ind = get_gap_ind(time, x_new, max_time) 52 | 53 | if len(mask_ind) > 0: 54 | msg = f"Unable to interpolate disdrometer for {len(mask_ind)} time steps" 55 | logging.warning(msg) 56 | interpolated_array[mask_ind] = ma.masked 57 | 58 | return interpolated_array 59 | -------------------------------------------------------------------------------- /cloudnetpy/categorize/lidar.py: -------------------------------------------------------------------------------- 1 | """Lidar module, containing the :class:`Lidar` class.""" 2 | 3 | import logging 4 | from typing import Literal 5 | 6 | import numpy as np 7 | from numpy import ma 8 | 9 | from cloudnetpy.datasource import DataSource 10 | from cloudnetpy.utils import interpolate_2d_nearest 11 | 12 | 13 | class Lidar(DataSource): 14 | """Lidar class, child of DataSource. 15 | 16 | Args: 17 | full_path: Cloudnet Level 1 lidar netCDF file. 18 | 19 | """ 20 | 21 | def __init__(self, full_path: str): 22 | super().__init__(full_path) 23 | self.append_data(self.getvar("beta"), "beta") 24 | self._add_meta() 25 | 26 | def interpolate_to_grid( 27 | self, time_new: np.ndarray, height_new: np.ndarray 28 | ) -> list[int]: 29 | """Interpolate beta using nearest neighbor.""" 30 | max_height = 100 # m 31 | max_time = 1 / 60 # min -> fraction hour 32 | 33 | if self.height is None: 34 | msg = "Unable to interpolate lidar: no height information" 35 | raise RuntimeError(msg) 36 | 37 | # Interpolate beta to new grid but ignore profiles that are completely masked 38 | beta = self.data["beta"][:] 39 | indices = [ind for ind, b in enumerate(beta) if ma.all(b) is not ma.masked] 40 | beta_interp = interpolate_2d_nearest( 41 | self.time[indices], 42 | self.height, 43 | beta[indices, :], 44 | time_new, 45 | height_new, 46 | ) 47 | # Mask data points that are too far from the original grid 48 | time_gap_ind = get_gap_ind(self.time[indices], time_new, max_time) 49 | height_gap_ind = get_gap_ind(self.height, height_new, max_height) 50 | self._mask_profiles(beta_interp, time_gap_ind, "time") 51 | self._mask_profiles(beta_interp, height_gap_ind, "height") 52 | self.data["beta"].data = beta_interp 53 | return time_gap_ind 54 | 55 | @staticmethod 56 | def _mask_profiles( 57 | data: ma.MaskedArray, ind: list[int], dim: Literal["time", "height"] 58 | ) -> None: 59 | prefix = f"Unable to interpolate lidar for {len(ind)}" 60 | if dim == "time" and ind: 61 | logging.warning("%s time steps", prefix) 62 | data[ind, :] = ma.masked 63 | elif dim == "height" and ind: 64 | logging.warning("%s altitudes", prefix) 65 | data[:, ind] = ma.masked 66 | 67 | def _add_meta(self) -> None: 68 | self.append_data(float(self.getvar("wavelength")), "lidar_wavelength") 69 | self.append_data(0.5, "beta_error") 70 | self.append_data(3.0, "beta_bias") 71 | 72 | 73 | def get_gap_ind(grid: np.ndarray, new_grid: np.ndarray, threshold: float) -> list[int]: 74 | return [ 75 | ind 76 | for ind, value in enumerate(new_grid) 77 | if np.min(np.abs(grid - value)) > threshold 78 | ] 79 | -------------------------------------------------------------------------------- /cloudnetpy/categorize/mwr.py: -------------------------------------------------------------------------------- 1 | """Mwr module, containing the :class:`Mwr` class.""" 2 | 3 | import numpy as np 4 | import numpy.ma as ma 5 | 6 | from cloudnetpy import utils 7 | from cloudnetpy.constants import G_TO_KG 8 | from cloudnetpy.datasource import DataSource 9 | 10 | 11 | class Mwr(DataSource): 12 | """Microwave radiometer class, child of DataSource. 13 | 14 | Args: 15 | full_path: Cloudnet Level 1b mwr file. 16 | 17 | """ 18 | 19 | def __init__(self, full_path: str): 20 | super().__init__(full_path) 21 | self._init_lwp_data() 22 | self._init_lwp_error() 23 | 24 | def rebin_to_grid(self, time_grid: np.ndarray) -> None: 25 | """Approximates lwp and its error in a grid using mean. 26 | 27 | Args: 28 | time_grid: 1D target time grid. 29 | 30 | """ 31 | for array in self.data.values(): 32 | array.rebin_data(self.time, time_grid) 33 | 34 | def _init_lwp_data(self) -> None: 35 | lwp = self.dataset.variables["lwp"][:] 36 | if "lwp_quality_flag" in self.dataset.variables: 37 | quality_flag = self.dataset.variables["lwp_quality_flag"][:] 38 | lwp[quality_flag != 0] = ma.masked 39 | self.append_data(lwp, "lwp") 40 | 41 | def _init_lwp_error(self) -> None: 42 | random_error, bias = 0.25, 20 43 | lwp_error = utils.l2norm(self.data["lwp"][:] * random_error, bias * G_TO_KG) 44 | self.append_data(lwp_error, "lwp_error", units="kg m-2") 45 | self.data["lwp_error"].comment = ( 46 | "This variable is a rough estimate of the one-standard-deviation\n" 47 | f"error in liquid water path, calculated as a combination of\n" 48 | f"a {bias} g m-2 linear error and a {round(random_error*100)} %\n" 49 | "fractional error." 50 | ) 51 | -------------------------------------------------------------------------------- /cloudnetpy/constants.py: -------------------------------------------------------------------------------- 1 | """Constants used in Cloudnet processing.""" 2 | 3 | from typing import Final 4 | 5 | # Triple point of water 6 | T0: Final = 273.16 7 | 8 | # Ratio of the molecular weight of water vapor to dry air 9 | MW_RATIO: Final = 0.62198 10 | 11 | # Specific gas constant for dry air (J kg-1 K-1) 12 | RS: Final = 287.058 13 | 14 | # ice density kg m-3 15 | RHO_ICE: Final = 917 16 | 17 | # Standard atmospheric pressure at sea level Pa 18 | P0: Final = 1013_25 19 | 20 | # other 21 | SPEED_OF_LIGHT: Final = 3.0e8 22 | SEC_IN_MINUTE: Final = 60 23 | SEC_IN_HOUR: Final = 3600 24 | SEC_IN_DAY: Final = 86400 25 | MM_TO_M: Final = 1e-3 26 | G_TO_KG: Final = 1e-3 27 | M_TO_KM: Final = 1e-3 28 | KG_TO_G: Final = 1e3 29 | M_TO_MM: Final = 1e3 30 | M_S_TO_MM_H: Final = SEC_IN_HOUR / MM_TO_M 31 | MM_H_TO_M_S: Final = 1 / M_S_TO_MM_H 32 | GHZ_TO_HZ: Final = 1e9 33 | HPA_TO_PA: Final = 100 34 | PA_TO_HPA: Final = 1 / HPA_TO_PA 35 | KM_H_TO_M_S: Final = 1000 / SEC_IN_HOUR 36 | TWO_WAY: Final = 2 37 | G: Final = 9.80665 38 | -------------------------------------------------------------------------------- /cloudnetpy/exceptions.py: -------------------------------------------------------------------------------- 1 | class CloudnetException(Exception): 2 | """Base class for exceptions in this module.""" 3 | 4 | 5 | class InconsistentDataError(CloudnetException): 6 | """Internal exception class.""" 7 | 8 | def __init__(self, msg: str): 9 | super().__init__(msg) 10 | 11 | 12 | class DisdrometerDataError(CloudnetException): 13 | """Internal exception class.""" 14 | 15 | def __init__(self, msg: str): 16 | super().__init__(msg) 17 | 18 | 19 | class RadarDataError(CloudnetException): 20 | """Internal exception class.""" 21 | 22 | def __init__(self, msg: str): 23 | super().__init__(msg) 24 | 25 | 26 | class LidarDataError(CloudnetException): 27 | """Internal exception class.""" 28 | 29 | def __init__(self, msg: str): 30 | super().__init__(msg) 31 | 32 | 33 | class PlottingError(CloudnetException): 34 | """Internal exception class.""" 35 | 36 | def __init__(self, msg: str): 37 | super().__init__(msg) 38 | 39 | 40 | class ModelDataError(CloudnetException): 41 | """Internal exception class.""" 42 | 43 | def __init__(self, msg: str = "Invalid model file: not enough proper profiles"): 44 | super().__init__(msg) 45 | 46 | 47 | class ValidTimeStampError(CloudnetException): 48 | """Internal exception class.""" 49 | 50 | def __init__(self, msg: str = "No valid timestamps found"): 51 | super().__init__(msg) 52 | 53 | 54 | class MissingInputFileError(CloudnetException): 55 | """Internal exception class.""" 56 | 57 | def __init__(self, msg: str = "Missing required input files"): 58 | super().__init__(msg) 59 | 60 | 61 | class HatproDataError(CloudnetException): 62 | """Internal exception class.""" 63 | 64 | def __init__(self, msg: str = "Invalid HATPRO file"): 65 | super().__init__(msg) 66 | 67 | 68 | class InvalidSourceFileError(CloudnetException): 69 | """Internal exception class.""" 70 | 71 | def __init__(self, msg: str = "Invalid source file"): 72 | super().__init__(msg) 73 | -------------------------------------------------------------------------------- /cloudnetpy/instruments/__init__.py: -------------------------------------------------------------------------------- 1 | from .basta import basta2nc 2 | from .bowtie import bowtie2nc 3 | from .ceilo import ceilo2nc 4 | from .copernicus import copernicus2nc 5 | from .disdrometer import parsivel2nc, thies2nc 6 | from .fd12p import fd12p2nc 7 | from .galileo import galileo2nc 8 | from .hatpro import hatpro2l1c, hatpro2nc 9 | from .instruments import Instrument 10 | from .mira import mira2nc 11 | from .mrr import mrr2nc 12 | from .pollyxt import pollyxt2nc 13 | from .radiometrics import radiometrics2nc 14 | from .rain_e_h3 import rain_e_h32nc 15 | from .rpg import rpg2nc 16 | from .weather_station import ws2nc 17 | -------------------------------------------------------------------------------- /cloudnetpy/instruments/cl61d.py: -------------------------------------------------------------------------------- 1 | """Module with a class for Lufft chm15k ceilometer.""" 2 | 3 | import logging 4 | 5 | import netCDF4 6 | 7 | from cloudnetpy import utils 8 | from cloudnetpy.exceptions import LidarDataError 9 | from cloudnetpy.instruments import instruments 10 | from cloudnetpy.instruments.nc_lidar import NcLidar 11 | 12 | 13 | class Cl61d(NcLidar): 14 | """Class for Vaisala CL61d ceilometer.""" 15 | 16 | def __init__( 17 | self, 18 | file_name: str, 19 | site_meta: dict, 20 | expected_date: str | None = None, 21 | ): 22 | super().__init__() 23 | self.file_name = file_name 24 | self.site_meta = site_meta 25 | self.expected_date = expected_date 26 | self.instrument = instruments.CL61D 27 | 28 | def read_ceilometer_file(self, calibration_factor: float | None = None) -> None: 29 | """Reads data and metadata from concatenated Vaisala CL61d netCDF file.""" 30 | with netCDF4.Dataset(self.file_name) as dataset: 31 | self.dataset = dataset 32 | self._fetch_attributes() 33 | self._fetch_zenith_angle("tilt_angle", default=3.0) 34 | self._fetch_range(reference="lower") 35 | self._fetch_lidar_variables(calibration_factor) 36 | self._fetch_time_and_date() 37 | self.dataset = None 38 | 39 | def _fetch_lidar_variables(self, calibration_factor: float | None = None) -> None: 40 | if self.dataset is None: 41 | msg = "No dataset found" 42 | raise RuntimeError(msg) 43 | beta_raw = self.dataset.variables["beta_att"][:] 44 | if utils.is_all_masked(beta_raw): 45 | msg = "All beta_raw values are masked. Check the input file(s)." 46 | raise LidarDataError(msg) 47 | if calibration_factor is None: 48 | logging.warning("Using default calibration factor") 49 | calibration_factor = 1 50 | beta_raw *= calibration_factor 51 | self.data["calibration_factor"] = float(calibration_factor) 52 | self.data["beta_raw"] = beta_raw 53 | self.data["depolarisation"] = ( 54 | self.dataset.variables["x_pol"][:] / self.dataset.variables["p_pol"][:] 55 | ) 56 | self.data["depolarisation_raw"] = self.data["depolarisation"].copy() 57 | 58 | def _fetch_attributes(self) -> None: 59 | self.serial_number = getattr(self.dataset, "instrument_serial_number", None) 60 | -------------------------------------------------------------------------------- /cloudnetpy/instruments/disdrometer/__init__.py: -------------------------------------------------------------------------------- 1 | from .common import ATTRIBUTES 2 | from .parsivel import parsivel2nc 3 | from .thies import thies2nc 4 | -------------------------------------------------------------------------------- /cloudnetpy/instruments/nc_lidar.py: -------------------------------------------------------------------------------- 1 | """Module with a class for Lufft chm15k ceilometer.""" 2 | 3 | import logging 4 | from typing import TYPE_CHECKING, Literal 5 | 6 | import numpy as np 7 | from numpy import ma 8 | 9 | from cloudnetpy import utils 10 | from cloudnetpy.instruments.ceilometer import Ceilometer 11 | 12 | if TYPE_CHECKING: 13 | import netCDF4 14 | 15 | 16 | class NcLidar(Ceilometer): 17 | """Class for all lidars using netCDF files.""" 18 | 19 | def __init__(self): 20 | super().__init__() 21 | self.dataset: netCDF4.Dataset | None = None 22 | 23 | def _fetch_range(self, reference: Literal["upper", "lower"]) -> None: 24 | if self.dataset is None: 25 | msg = "No dataset found" 26 | raise RuntimeError(msg) 27 | range_instrument = self.dataset.variables["range"][:] 28 | self.data["range"] = utils.edges2mid(range_instrument, reference) 29 | 30 | def _fetch_time_and_date(self) -> None: 31 | if self.dataset is None: 32 | msg = "No dataset found" 33 | raise RuntimeError(msg) 34 | time = self.dataset.variables["time"] 35 | self.data["time"] = time[:] 36 | epoch = utils.get_epoch(time.units) 37 | self.get_date_and_time(epoch) 38 | 39 | def _fetch_zenith_angle(self, key: str, default: float = 3.0) -> None: 40 | if self.dataset is None: 41 | msg = "No dataset found" 42 | raise RuntimeError(msg) 43 | if key in self.dataset.variables: 44 | zenith_angle = ma.median(self.dataset.variables[key][:]) 45 | else: 46 | zenith_angle = float(default) 47 | logging.warning("No zenith angle found, assuming %s degrees", zenith_angle) 48 | if zenith_angle == 0: 49 | logging.warning("Zenith angle 0 degrees - risk of specular reflection") 50 | self.data["zenith_angle"] = np.array(zenith_angle) 51 | -------------------------------------------------------------------------------- /cloudnetpy/instruments/toa5.py: -------------------------------------------------------------------------------- 1 | import csv 2 | import datetime 3 | from os import PathLike 4 | from typing import Any 5 | 6 | 7 | def read_toa5( 8 | filename: str | PathLike, 9 | ) -> tuple[dict[str, str], dict[str, str], list[dict[str, Any]]]: 10 | """Read ASCII data from Campbell Scientific datalogger such as CR1000. 11 | 12 | References: 13 | CR1000 Measurement and Control System. 14 | https://s.campbellsci.com/documents/us/manuals/cr1000.pdf 15 | """ 16 | with open(filename) as file: 17 | reader = csv.reader(file) 18 | origin_line = next(reader) 19 | if len(origin_line) == 0 or origin_line[0] != "TOA5": 20 | msg = "Invalid TOA5 file" 21 | raise ValueError(msg) 22 | header_line = next(reader) 23 | units_line = next(reader) 24 | process_line = next(reader) 25 | output = [] 26 | units = dict(zip(header_line, units_line, strict=False)) 27 | process = dict(zip(header_line, process_line, strict=False)) 28 | 29 | row_template: dict[str, Any] = {} 30 | for header in header_line: 31 | if "(" in header: 32 | row_template[header[: header.index("(")]] = [] 33 | 34 | for data_line in reader: 35 | row = row_template.copy() 36 | for key, value in zip(header_line, data_line, strict=False): 37 | parsed_value: Any = value 38 | if key == "TIMESTAMP": 39 | parsed_value = datetime.datetime.strptime( 40 | parsed_value, "%Y-%m-%d %H:%M:%S" 41 | ) 42 | elif key == "RECORD": 43 | parsed_value = int(parsed_value) 44 | if "(" in key: 45 | row[key[: key.index("(")]].append(parsed_value) 46 | else: 47 | row[key] = parsed_value 48 | output.append(row) 49 | return units, process, output 50 | -------------------------------------------------------------------------------- /cloudnetpy/model_evaluation/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/actris-cloudnet/cloudnetpy/7ca27b7f31018470fc60596063d51124b48ff6db/cloudnetpy/model_evaluation/__init__.py -------------------------------------------------------------------------------- /cloudnetpy/model_evaluation/model_metadata.py: -------------------------------------------------------------------------------- 1 | from typing import NamedTuple 2 | 3 | 4 | class ModelMetaData(NamedTuple): 5 | long_name: str | None = None 6 | cycle_var: str | None = None 7 | common_var: str | None = None 8 | cycle: str | None = None 9 | level: int | None = None 10 | model_name: str | None = None 11 | 12 | 13 | MODELS = { 14 | "ecmwf": ModelMetaData( 15 | model_name="ECMWF", 16 | long_name="European Centre for Medium-Range Weather Forecasts", 17 | level=88, 18 | ), 19 | "icon": ModelMetaData( 20 | model_name="ICON-Iglo", 21 | long_name="Icosahedral Nonhydrostatic Model", 22 | level=62, 23 | cycle="12-23, 24-35, 36-47", 24 | ), 25 | "era5": ModelMetaData( 26 | model_name="ERA5", 27 | long_name="Earth Re-Analysis System", 28 | level=88, 29 | cycle="1-12, 7-18", 30 | ), 31 | "harmonie-fmi-6-11": ModelMetaData( 32 | model_name="HARMONIE-AROME", 33 | long_name="the HIRLAM–ALADIN Research on Mesoscale Operational NWP in Euromed", # noqa: RUF001 34 | level=65, 35 | cycle="6-11", 36 | ), 37 | } 38 | 39 | VARIABLES = { 40 | "variables": ModelMetaData( 41 | common_var="time, level, latitude, longitude, horizontal_resolution", 42 | cycle_var="forecast_time, height", 43 | ), 44 | "T": ModelMetaData(long_name="temperature"), 45 | "p": ModelMetaData(long_name="pressure"), 46 | "h": ModelMetaData(long_name="height"), 47 | "iwc": ModelMetaData(long_name="qi"), 48 | "lwc": ModelMetaData(long_name="ql"), 49 | "cf": ModelMetaData(long_name="cloud_fraction"), 50 | } 51 | -------------------------------------------------------------------------------- /cloudnetpy/model_evaluation/plotting/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/actris-cloudnet/cloudnetpy/7ca27b7f31018470fc60596063d51124b48ff6db/cloudnetpy/model_evaluation/plotting/__init__.py -------------------------------------------------------------------------------- /cloudnetpy/model_evaluation/products/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/actris-cloudnet/cloudnetpy/7ca27b7f31018470fc60596063d51124b48ff6db/cloudnetpy/model_evaluation/products/__init__.py -------------------------------------------------------------------------------- /cloudnetpy/model_evaluation/products/product_resampling.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | import cloudnetpy.model_evaluation.products.tools as tl 4 | from cloudnetpy.model_evaluation.file_handler import ( 5 | add_time_attribute, 6 | add_var2ncfile, 7 | save_downsampled_file, 8 | update_attributes, 9 | ) 10 | from cloudnetpy.model_evaluation.products.advance_methods import AdvanceProductMethods 11 | from cloudnetpy.model_evaluation.products.grid_methods import ProductGrid 12 | from cloudnetpy.model_evaluation.products.model_products import ModelManager 13 | from cloudnetpy.model_evaluation.products.observation_products import ObservationManager 14 | from cloudnetpy.model_evaluation.utils import file_exists 15 | 16 | 17 | def process_L3_day_product( 18 | model: str, 19 | obs: str, 20 | model_files: list, 21 | product_file: str, 22 | output_file: str, 23 | uuid: str | None = None, 24 | *, 25 | overwrite: bool = False, 26 | ) -> str: 27 | """Main function to generate downsample of observations to match model grid. 28 | 29 | This function will generate a L3 product nc-file. It includes the information of 30 | downsampled observation products for each model cycles and model products 31 | and other variables of each cycles. 32 | 33 | Args: 34 | model (str): Name of model 35 | obs (str): Name of product to generate 36 | model_files (list): List of model + cycles file path(s) to be generated 37 | product_file (str): Source file path of L2 observation product 38 | output_file (str): Path and name of L3 day scale product output file 39 | keep_uuid (bool): If True, keeps the UUID of the old file, if that exists. 40 | Default is False when new UUID is generated. 41 | uuid (str): Set specific UUID for the file. 42 | overwrite (bool): If file exists, but still want to recreate it then True, 43 | default False 44 | 45 | Raises: 46 | RuntimeError: Failed to create the L3 product file. 47 | ValueError (Warning): No ice clouds in model data 48 | 49 | Notes: 50 | Model file(s) are given as a list to make all different cycles to be at same 51 | nc-file. If list includes more than one model file, nc-file is created within 52 | the first round. With rest of rounds, downsample observation and model data 53 | is added to same L3 day nc-file. 54 | 55 | Examples: 56 | >>> from cloudnetpy.model_evaluation.products.product_resampling import \ 57 | process_L3_day_product 58 | >>> product = 'cf' 59 | >>> model = 'ecmwf' 60 | >>> model_file = 'ecmwf.nc' 61 | >>> input_file = 220190517_mace-head_categorize.nchead_categorize.nc 62 | >>> output_file = 'cf_ecmwf.nc' 63 | >>> process_L3_day_product(model, product, [model_file], input_file, 64 | output_file) 65 | """ 66 | product_obj = ObservationManager(obs, product_file) 67 | tl.check_model_file_list(model, model_files) 68 | for m_file in model_files: 69 | model_obj = ModelManager( 70 | m_file, 71 | model, 72 | output_file, 73 | obs, 74 | check_file=not overwrite, 75 | ) 76 | try: 77 | AdvanceProductMethods(model_obj, m_file, product_obj) 78 | except ValueError as e: 79 | logging.info(e) 80 | ProductGrid(model_obj, product_obj) 81 | attributes = add_time_attribute(product_obj.date) 82 | update_attributes(model_obj.data, attributes) 83 | if not file_exists(output_file) or overwrite: 84 | tl.add_date(model_obj, product_obj) 85 | uuid_out = save_downsampled_file( 86 | f"{obs}_{model}", 87 | output_file, 88 | (model_obj, product_obj), 89 | (model_files, product_file), 90 | uuid, 91 | ) 92 | else: 93 | add_var2ncfile(model_obj, output_file) 94 | return uuid_out 95 | -------------------------------------------------------------------------------- /cloudnetpy/model_evaluation/products/tools.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | import logging 3 | from datetime import timedelta 4 | 5 | import numpy as np 6 | from numpy import ma 7 | 8 | from cloudnetpy.model_evaluation.products.model_products import ModelManager 9 | from cloudnetpy.model_evaluation.products.observation_products import ObservationManager 10 | 11 | 12 | def check_model_file_list(name: str, models: list) -> None: 13 | """Check that files in models are from same model and date.""" 14 | for m in models: 15 | if name not in m: 16 | logging.error("Invalid model file set") 17 | msg = f"{m} not from {name}" 18 | raise AttributeError(msg) 19 | 20 | 21 | def time2datetime(time: np.ndarray, date: datetime.datetime) -> np.ndarray: 22 | return np.asarray([date + timedelta(hours=float(t)) for t in time]) 23 | 24 | 25 | def rebin_edges(arr: np.ndarray) -> np.ndarray: 26 | """Rebins array bins by half and adds boundaries.""" 27 | new_arr = [(arr[i] + arr[i + 1]) / 2 for i in range(len(arr) - 1)] 28 | new_arr.insert(0, arr[0] - ((arr[0] + arr[1]) / 2)) 29 | new_arr.insert(len(new_arr), arr[-1] + (arr[-1] - arr[-2])) 30 | return np.array(new_arr) 31 | 32 | 33 | def calculate_advection_time( 34 | resolution: int, 35 | wind: ma.MaskedArray, 36 | sampling: int, 37 | ) -> np.ndarray: 38 | """Calculates time which variable takes to go through the time window. 39 | 40 | Notes: 41 | Wind speed is stronger in upper levels, so advection time is more 42 | there then lower levels. Effect is small in a mid-latitudes, 43 | but visible in a tropics. 44 | 45 | sampling = 1 -> hour, sampling 1/6 -> 10min 46 | """ 47 | t_adv = resolution * 1000 / wind / 60**2 48 | t_adv[t_adv.mask] = 0 49 | t_adv[t_adv > 1 / sampling] = 1 / sampling 50 | return np.asarray([[timedelta(hours=float(t)) for t in time] for time in t_adv]) 51 | 52 | 53 | def get_1d_indices( 54 | window: tuple, 55 | data: np.ndarray, 56 | mask: np.ndarray | None = None, 57 | ) -> np.ndarray: 58 | indices: np.ndarray = np.array((window[0] <= data) & (data < window[-1])) 59 | if mask is not None: 60 | indices[mask] = ma.masked 61 | return indices 62 | 63 | 64 | def get_adv_indices( 65 | model_t: int, 66 | adv_t: float, 67 | data: np.ndarray, 68 | mask: np.ndarray | None = None, 69 | ) -> np.ndarray: 70 | adv_indices = ((model_t - adv_t / 2) <= data) & (data < (model_t + adv_t / 2)) 71 | if mask is not None: 72 | adv_indices[mask] = ma.masked 73 | return adv_indices 74 | 75 | 76 | def get_obs_window_size(ind_x: np.ndarray, ind_y: np.ndarray) -> tuple | None: 77 | """Returns shape (tuple) of window area, where values are True.""" 78 | x = np.where(ind_x)[0] 79 | y = np.where(ind_y)[0] 80 | if np.any(x) and np.any(y): 81 | return x[-1] - x[0] + 1, y[-1] - y[0] + 1 82 | return None 83 | 84 | 85 | def add_date(model_obj: ModelManager, obs_obj: ObservationManager) -> None: 86 | for a in ("year", "month", "day"): 87 | model_obj.date.append(getattr(obs_obj.dataset, a)) 88 | 89 | 90 | def average_column_sum(data: np.ndarray) -> np.ndarray: 91 | """Returns average sum of columns which have any data.""" 92 | return np.nanmean(np.nansum(data, 1) > 0) 93 | -------------------------------------------------------------------------------- /cloudnetpy/model_evaluation/pytest.ini: -------------------------------------------------------------------------------- 1 | [pytest] 2 | markers = 3 | first_run 4 | append_data 5 | new_version 6 | append_fail 7 | reprocess 8 | -------------------------------------------------------------------------------- /cloudnetpy/model_evaluation/statistics/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/actris-cloudnet/cloudnetpy/7ca27b7f31018470fc60596063d51124b48ff6db/cloudnetpy/model_evaluation/statistics/__init__.py -------------------------------------------------------------------------------- /cloudnetpy/model_evaluation/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/actris-cloudnet/cloudnetpy/7ca27b7f31018470fc60596063d51124b48ff6db/cloudnetpy/model_evaluation/tests/__init__.py -------------------------------------------------------------------------------- /cloudnetpy/model_evaluation/tests/data/20190517_mace-head_categorize.nc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/actris-cloudnet/cloudnetpy/7ca27b7f31018470fc60596063d51124b48ff6db/cloudnetpy/model_evaluation/tests/data/20190517_mace-head_categorize.nc -------------------------------------------------------------------------------- /cloudnetpy/model_evaluation/tests/data/20190517_mace-head_ecmwf.nc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/actris-cloudnet/cloudnetpy/7ca27b7f31018470fc60596063d51124b48ff6db/cloudnetpy/model_evaluation/tests/data/20190517_mace-head_ecmwf.nc -------------------------------------------------------------------------------- /cloudnetpy/model_evaluation/tests/data/20190517_mace-head_iwc-Z-T-method.nc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/actris-cloudnet/cloudnetpy/7ca27b7f31018470fc60596063d51124b48ff6db/cloudnetpy/model_evaluation/tests/data/20190517_mace-head_iwc-Z-T-method.nc -------------------------------------------------------------------------------- /cloudnetpy/model_evaluation/tests/data/20190517_mace-head_lwc-scaled-adiabatic.nc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/actris-cloudnet/cloudnetpy/7ca27b7f31018470fc60596063d51124b48ff6db/cloudnetpy/model_evaluation/tests/data/20190517_mace-head_lwc-scaled-adiabatic.nc -------------------------------------------------------------------------------- /cloudnetpy/model_evaluation/tests/e2e/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/actris-cloudnet/cloudnetpy/7ca27b7f31018470fc60596063d51124b48ff6db/cloudnetpy/model_evaluation/tests/e2e/__init__.py -------------------------------------------------------------------------------- /cloudnetpy/model_evaluation/tests/e2e/conftest.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | args = ["site", "date", "input", "output", "full_path"] 4 | 5 | 6 | def pytest_addoption(parser) -> None: 7 | for arg in args: 8 | parser.addoption(f"--{arg}", action="store") 9 | 10 | 11 | @pytest.fixture() 12 | def params(request) -> dict: 13 | return {arg: request.config.getoption(f"--{arg}") for arg in args} 14 | -------------------------------------------------------------------------------- /cloudnetpy/model_evaluation/tests/e2e/process_cf/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/actris-cloudnet/cloudnetpy/7ca27b7f31018470fc60596063d51124b48ff6db/cloudnetpy/model_evaluation/tests/e2e/process_cf/__init__.py -------------------------------------------------------------------------------- /cloudnetpy/model_evaluation/tests/e2e/process_cf/main.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | import argparse 3 | import os 4 | import subprocess 5 | from os import path 6 | from tempfile import TemporaryDirectory 7 | 8 | from cloudnetpy.model_evaluation.products import product_resampling 9 | 10 | ROOT_PATH = os.path.abspath(os.curdir) 11 | SCRIPT_PATH = path.dirname(path.realpath(__file__)) 12 | test_file_model = ( 13 | f"{ROOT_PATH}/cloudnetpy/model_evaluation/tests/data/20190517_mace-head_ecmwf.nc" 14 | ) 15 | test_file_product = f"{ROOT_PATH}/cloudnetpy/model_evaluation/tests/data/20190517_mace-head_categorize.nc" 16 | 17 | 18 | def _process() -> None: 19 | tmp_dir = TemporaryDirectory() 20 | temp_file = f"{tmp_dir.name}/xx.nc" 21 | product_resampling.process_L3_day_product( 22 | "ecmwf", 23 | "cf", 24 | [test_file_model], 25 | test_file_product, 26 | temp_file, 27 | ) 28 | try: 29 | subprocess.call( 30 | [ 31 | "pytest", 32 | "-v", 33 | f"{SCRIPT_PATH}/tests.py", 34 | "--full_path", 35 | temp_file, 36 | ], 37 | ) 38 | except subprocess.CalledProcessError: 39 | raise 40 | tmp_dir.cleanup() 41 | 42 | 43 | def main() -> None: 44 | _process() 45 | 46 | 47 | if __name__ == "__main__": 48 | parser = argparse.ArgumentParser( 49 | description="Model evaluation Cloud fraction processing e2e test.", 50 | ) 51 | ARGS = parser.parse_args() 52 | main() 53 | -------------------------------------------------------------------------------- /cloudnetpy/model_evaluation/tests/e2e/process_cf/tests.py: -------------------------------------------------------------------------------- 1 | import netCDF4 2 | import pytest 3 | 4 | 5 | class TestCloudFractionProcessing: 6 | product = "cf" 7 | 8 | @pytest.fixture(autouse=True) 9 | def _fetch_params(self, params) -> None: 10 | self.full_path = params["full_path"] 11 | 12 | @pytest.mark.reprocess() 13 | def test_that_has_correct_attributes(self) -> None: 14 | nc = netCDF4.Dataset(self.full_path) 15 | assert nc.location == "Mace-Head" 16 | assert nc.year == "2019" 17 | assert nc.month == "05" 18 | assert nc.day == "17" 19 | assert nc.title == "Downsampled Cf of ecmwf from Mace-Head" 20 | assert nc.cloudnet_file_type == "l3-cf" 21 | assert nc.Conventions == "CF-1.8" 22 | assert ( 23 | nc.source 24 | == "Observation file: 20190517_mace-head_categorize.nc\necmwf file(s): 20190517_mace-head_ecmwf.nc" 25 | ) 26 | nc.close() 27 | 28 | @pytest.mark.reprocess() 29 | @pytest.mark.parametrize( 30 | "key", 31 | ["cf_V_ecmwf", "cf_A_ecmwf", "cf_V_adv_ecmwf", "cf_A_adv_ecmwf"], 32 | ) 33 | def test_that_has_correct_product_variables(self, key) -> None: 34 | nc = netCDF4.Dataset(self.full_path) 35 | assert key in nc.variables 36 | nc.close() 37 | 38 | @pytest.mark.reprocess() 39 | @pytest.mark.parametrize( 40 | "key", 41 | ["time", "level", "latitude", "longitude", "horizontal_resolution"], 42 | ) 43 | def test_that_has_correct_model_variables(self, key) -> None: 44 | nc = netCDF4.Dataset(self.full_path) 45 | assert key in nc.variables 46 | nc.close() 47 | 48 | @pytest.mark.reprocess() 49 | @pytest.mark.parametrize( 50 | "key", 51 | ["ecmwf_forecast_time", "ecmwf_height", "ecmwf_cf", "ecmwf_cf_cirrus"], 52 | ) 53 | def test_that_has_correct_cycle_variables(self, key) -> None: 54 | nc = netCDF4.Dataset(self.full_path) 55 | assert key in nc.variables 56 | nc.close() 57 | -------------------------------------------------------------------------------- /cloudnetpy/model_evaluation/tests/e2e/process_iwc/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/actris-cloudnet/cloudnetpy/7ca27b7f31018470fc60596063d51124b48ff6db/cloudnetpy/model_evaluation/tests/e2e/process_iwc/__init__.py -------------------------------------------------------------------------------- /cloudnetpy/model_evaluation/tests/e2e/process_iwc/main.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | import argparse 3 | import os 4 | import subprocess 5 | import sys 6 | from os import path 7 | from tempfile import TemporaryDirectory 8 | 9 | from cloudnetpy.model_evaluation.products import product_resampling 10 | 11 | ROOT_PATH = os.path.abspath(os.curdir) 12 | 13 | sys.path.append(f"{ROOT_PATH}/model_evaluation/products") 14 | SCRIPT_PATH = path.dirname(path.realpath(__file__)) 15 | 16 | test_file_model = ( 17 | f"{ROOT_PATH}/cloudnetpy/model_evaluation/tests/data/20190517_mace-head_ecmwf.nc" 18 | ) 19 | test_file_product = f"{ROOT_PATH}/cloudnetpy/model_evaluation/tests/data/20190517_mace-head_iwc-Z-T-method.nc" 20 | 21 | 22 | def _process() -> None: 23 | tmp_dir = TemporaryDirectory() 24 | temp_file = f"{tmp_dir.name}/xx.nc" 25 | product_resampling.process_L3_day_product( 26 | "ecmwf", 27 | "iwc", 28 | [test_file_model], 29 | test_file_product, 30 | temp_file, 31 | ) 32 | try: 33 | subprocess.call( 34 | [ 35 | "pytest", 36 | "-v", 37 | f"{SCRIPT_PATH}/tests.py", 38 | "--full_path", 39 | temp_file, 40 | ], 41 | ) 42 | except subprocess.CalledProcessError: 43 | raise 44 | tmp_dir.cleanup() 45 | 46 | 47 | def main() -> None: 48 | _process() 49 | 50 | 51 | if __name__ == "__main__": 52 | parser = argparse.ArgumentParser( 53 | description="Model evaluation Ice water content processing e2e test.", 54 | ) 55 | ARGS = parser.parse_args() 56 | main() 57 | -------------------------------------------------------------------------------- /cloudnetpy/model_evaluation/tests/e2e/process_iwc/tests.py: -------------------------------------------------------------------------------- 1 | import netCDF4 2 | import pytest 3 | 4 | 5 | class TestCloudFractionProcessing: 6 | product = "iwc" 7 | 8 | @pytest.fixture(autouse=True) 9 | def _fetch_params(self, params) -> None: 10 | self.full_path = params["full_path"] 11 | 12 | @pytest.mark.reprocess() 13 | def test_that_has_correct_attributes(self) -> None: 14 | nc = netCDF4.Dataset(self.full_path) 15 | assert nc.location == "Mace-Head" 16 | assert nc.year == "2019" 17 | assert nc.month == "05" 18 | assert nc.day == "17" 19 | assert nc.title == "Downsampled Iwc of ecmwf from Mace-Head" 20 | assert nc.cloudnet_file_type == "l3-iwc" 21 | assert nc.Conventions == "CF-1.8" 22 | assert ( 23 | nc.source 24 | == "Observation file: 20190517_mace-head_iwc-Z-T-method.nc\necmwf file(s): 20190517_mace-head_ecmwf.nc" 25 | ) 26 | nc.close() 27 | 28 | @pytest.mark.reprocess() 29 | @pytest.mark.parametrize( 30 | "key", 31 | [ 32 | "iwc_ecmwf", 33 | "iwc_att_ecmwf", 34 | "iwc_rain_ecmwf", 35 | "iwc_adv_ecmwf", 36 | "iwc_att_adv_ecmwf", 37 | "iwc_rain_adv_ecmwf", 38 | ], 39 | ) 40 | def test_that_has_correct_product_variables(self, key) -> None: 41 | nc = netCDF4.Dataset(self.full_path) 42 | assert key in nc.variables 43 | nc.close() 44 | 45 | @pytest.mark.reprocess() 46 | @pytest.mark.parametrize( 47 | "key", 48 | ["time", "level", "latitude", "longitude", "horizontal_resolution"], 49 | ) 50 | def test_that_has_correct_model_variables(self, key) -> None: 51 | nc = netCDF4.Dataset(self.full_path) 52 | assert key in nc.variables 53 | nc.close() 54 | 55 | @pytest.mark.reprocess() 56 | @pytest.mark.parametrize( 57 | "key", 58 | ["ecmwf_forecast_time", "ecmwf_height", "ecmwf_iwc"], 59 | ) 60 | def test_that_has_correct_cycle_variables(self, key) -> None: 61 | nc = netCDF4.Dataset(self.full_path) 62 | assert key in nc.variables 63 | nc.close() 64 | -------------------------------------------------------------------------------- /cloudnetpy/model_evaluation/tests/e2e/process_lwc/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/actris-cloudnet/cloudnetpy/7ca27b7f31018470fc60596063d51124b48ff6db/cloudnetpy/model_evaluation/tests/e2e/process_lwc/__init__.py -------------------------------------------------------------------------------- /cloudnetpy/model_evaluation/tests/e2e/process_lwc/main.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | import argparse 3 | import os 4 | import subprocess 5 | from os import path 6 | from tempfile import TemporaryDirectory 7 | 8 | from cloudnetpy.model_evaluation.products import product_resampling 9 | 10 | ROOT_PATH = os.path.abspath(os.curdir) 11 | SCRIPT_PATH = path.dirname(path.realpath(__file__)) 12 | test_file_model = ( 13 | f"{ROOT_PATH}/cloudnetpy/model_evaluation/tests/data/20190517_mace-head_ecmwf.nc" 14 | ) 15 | test_file_product = f"{ROOT_PATH}/cloudnetpy/model_evaluation/tests/data/20190517_mace-head_lwc-scaled-adiabatic.nc" 16 | 17 | 18 | def _process() -> None: 19 | tmp_dir = TemporaryDirectory() 20 | temp_file = f"{tmp_dir.name}/xx.nc" 21 | product_resampling.process_L3_day_product( 22 | "ecmwf", 23 | "lwc", 24 | [test_file_model], 25 | test_file_product, 26 | temp_file, 27 | ) 28 | try: 29 | subprocess.call( 30 | [ 31 | "pytest", 32 | "-v", 33 | f"{SCRIPT_PATH}/tests.py", 34 | "--full_path", 35 | temp_file, 36 | ], 37 | ) 38 | except subprocess.CalledProcessError: 39 | raise 40 | tmp_dir.cleanup() 41 | 42 | 43 | def main() -> None: 44 | _process() 45 | 46 | 47 | if __name__ == "__main__": 48 | parser = argparse.ArgumentParser( 49 | description="Model evaluation liquid water content processing e2e test.", 50 | ) 51 | ARGS = parser.parse_args() 52 | main() 53 | -------------------------------------------------------------------------------- /cloudnetpy/model_evaluation/tests/e2e/process_lwc/tests.py: -------------------------------------------------------------------------------- 1 | import netCDF4 2 | import pytest 3 | 4 | 5 | class TestCloudFractionProcessing: 6 | product = "lwc" 7 | 8 | @pytest.fixture(autouse=True) 9 | def _fetch_params(self, params) -> None: 10 | self.full_path = params["full_path"] 11 | 12 | @pytest.mark.reprocess() 13 | def test_that_has_correct_attributes(self) -> None: 14 | nc = netCDF4.Dataset(self.full_path) 15 | assert nc.location == "Mace-Head" 16 | assert nc.year == "2019" 17 | assert nc.month == "05" 18 | assert nc.day == "17" 19 | assert nc.title == "Downsampled Lwc of ecmwf from Mace-Head" 20 | assert nc.cloudnet_file_type == "l3-lwc" 21 | assert nc.Conventions == "CF-1.8" 22 | assert ( 23 | nc.source 24 | == "Observation file: 20190517_mace-head_lwc-scaled-adiabatic.nc\necmwf file(s): 20190517_mace-head_ecmwf.nc" 25 | ) 26 | nc.close() 27 | 28 | @pytest.mark.reprocess() 29 | @pytest.mark.parametrize("key", ["lwc_ecmwf", "lwc_adv_ecmwf"]) 30 | def test_that_has_correct_product_variables(self, key) -> None: 31 | nc = netCDF4.Dataset(self.full_path) 32 | assert key in nc.variables 33 | nc.close() 34 | 35 | @pytest.mark.reprocess() 36 | @pytest.mark.parametrize( 37 | "key", 38 | ["time", "level", "latitude", "longitude", "horizontal_resolution"], 39 | ) 40 | def test_that_has_correct_model_variables(self, key) -> None: 41 | nc = netCDF4.Dataset(self.full_path) 42 | assert key in nc.variables 43 | nc.close() 44 | 45 | @pytest.mark.reprocess() 46 | @pytest.mark.parametrize( 47 | "key", 48 | ["ecmwf_forecast_time", "ecmwf_height", "ecmwf_lwc"], 49 | ) 50 | def test_that_has_correct_cycle_variables(self, key) -> None: 51 | nc = netCDF4.Dataset(self.full_path) 52 | assert key in nc.variables 53 | nc.close() 54 | -------------------------------------------------------------------------------- /cloudnetpy/model_evaluation/tests/unit/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/actris-cloudnet/cloudnetpy/7ca27b7f31018470fc60596063d51124b48ff6db/cloudnetpy/model_evaluation/tests/unit/__init__.py -------------------------------------------------------------------------------- /cloudnetpy/model_evaluation/tests/unit/test_model_products.py: -------------------------------------------------------------------------------- 1 | import netCDF4 2 | import numpy as np 3 | import pytest 4 | from numpy import testing 5 | 6 | from cloudnetpy.model_evaluation.products.model_products import ModelManager 7 | 8 | MODEL = "ecmwf" 9 | OUTPUT_FILE = "" 10 | PRODUCT = "iwc" 11 | 12 | 13 | @pytest.mark.parametrize( 14 | "cycle, model, answer", 15 | [("test_file_12-23", "icon", "_12-23"), ("test_file", "ecmwf", "")], 16 | ) 17 | def test_read_cycle_name(cycle, model, answer, model_file) -> None: 18 | obj = ModelManager(str(model_file), model, OUTPUT_FILE, PRODUCT) 19 | x = obj._read_cycle_name(cycle) 20 | assert x == answer 21 | 22 | 23 | def test_get_cf(model_file) -> None: 24 | obj = ModelManager(str(model_file), MODEL, OUTPUT_FILE, PRODUCT) 25 | obj._get_cf() 26 | assert f"{MODEL}_cf" in obj.data 27 | 28 | 29 | def test_get_iwc(model_file) -> None: 30 | obj = ModelManager(str(model_file), MODEL, OUTPUT_FILE, PRODUCT) 31 | obj._get_iwc() 32 | assert f"{MODEL}_iwc" in obj.data 33 | 34 | 35 | def test_get_lwc(model_file) -> None: 36 | obj = ModelManager(str(model_file), MODEL, OUTPUT_FILE, "lwc") 37 | obj._get_lwc() 38 | assert f"{MODEL}_lwc" in obj.data 39 | 40 | 41 | def test_read_config(model_file) -> None: 42 | obj = ModelManager(str(model_file), MODEL, OUTPUT_FILE, PRODUCT) 43 | var = obj.get_model_var_names(("p",)) 44 | assert "pressure" in var 45 | var = obj.get_model_var_names(("T",)) 46 | assert "temperature" in var 47 | 48 | 49 | @pytest.mark.parametrize("key", ["pressure", "temperature"]) 50 | def test_set_variables(key, model_file) -> None: 51 | obj = ModelManager(str(model_file), MODEL, OUTPUT_FILE, PRODUCT) 52 | var = obj.getvar(key) 53 | x = netCDF4.Dataset(model_file).variables[key] 54 | testing.assert_almost_equal(x, var) 55 | 56 | 57 | @pytest.mark.parametrize("p, T, q", [(1, 2, 3), (20, 40, 80), (0.3, 0.6, 0.9)]) 58 | def test_calc_water_content(p, T, q, model_file) -> None: 59 | obj = ModelManager(str(model_file), MODEL, OUTPUT_FILE, PRODUCT) 60 | x = q * p / (287 * T) 61 | testing.assert_almost_equal(x, obj._calc_water_content(q, p, T)) 62 | 63 | 64 | @pytest.mark.parametrize( 65 | "key", 66 | ["time", "level", "horizontal_resolution", "latitude", "longitude"], 67 | ) 68 | def test_add_common_variables_false(key, model_file) -> None: 69 | obj = ModelManager(str(model_file), MODEL, OUTPUT_FILE, PRODUCT) 70 | obj._is_file = False 71 | obj._add_variables() 72 | assert key in obj.data 73 | 74 | 75 | @pytest.mark.parametrize( 76 | "key", 77 | ["time", "level", "horizontal_resolution", "latitude", "longitude"], 78 | ) 79 | def test_add_common_variables_true(key, model_file, regrid_file) -> None: 80 | obj = ModelManager(str(model_file), MODEL, regrid_file, PRODUCT) 81 | obj._is_file = True 82 | obj._add_variables() 83 | assert key not in obj.data 84 | 85 | 86 | @pytest.mark.parametrize("key", ["height", "forecast_time"]) 87 | def test_add_cycle_variables_no_products(key, model_file) -> None: 88 | obj = ModelManager(str(model_file), MODEL, OUTPUT_FILE, PRODUCT) 89 | obj._is_file = False 90 | obj._add_variables() 91 | assert f"{MODEL}_{key}" in obj.data 92 | 93 | 94 | def test_cut_off_extra_levels(model_file) -> None: 95 | obj = ModelManager(str(model_file), MODEL, OUTPUT_FILE, PRODUCT) 96 | data = np.array([np.arange(100), np.arange(100)]) 97 | compare = np.array([np.arange(88), np.arange(88)]) 98 | x = obj.cut_off_extra_levels(data) 99 | testing.assert_array_almost_equal(x, compare) 100 | 101 | 102 | def test_calculate_wind_speed(model_file) -> None: 103 | obj = ModelManager(str(model_file), MODEL, OUTPUT_FILE, PRODUCT) 104 | u = obj.getvar("uwind") 105 | v = obj.getvar("vwind") 106 | compare = np.sqrt(u.data**2 + v.data**2) # type: ignore 107 | x = obj._calculate_wind_speed() 108 | testing.assert_array_almost_equal(x, compare) 109 | -------------------------------------------------------------------------------- /cloudnetpy/model_evaluation/tests/unit/test_plotting.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from cloudnetpy.model_evaluation.plotting import plotting as pl 4 | 5 | MODEL = "ecmwf" 6 | 7 | 8 | class VariableInfo: 9 | def __init__(self): 10 | self.name = "Product" 11 | 12 | 13 | @pytest.mark.parametrize("key", ["cf_V", "cf_A", "cf_V_adv", "cf_A_adv"]) 14 | def test_get_cf_title(key) -> None: 15 | var = VariableInfo() 16 | field_name = key + "_" + MODEL 17 | value = "Product, Volume" 18 | if "A" in key: 19 | value = "Product, Area" 20 | x = pl._get_cf_title(field_name, var) 21 | assert x == value 22 | 23 | 24 | @pytest.mark.parametrize("key", ["cf_V", "cf_A", "cf_V_adv", "cf_A_adv"]) 25 | def test_get_cf_title_cycle(key) -> None: 26 | var = VariableInfo() 27 | field_name = key + "_" + MODEL + "_001" 28 | value = "Product, Volume" 29 | if "A" in key: 30 | value = "Product, Area" 31 | x = pl._get_cf_title(field_name, var) 32 | assert x == value 33 | 34 | 35 | @pytest.mark.parametrize( 36 | "key, value", 37 | [ 38 | ("iwc", "Product"), 39 | ("iwc_att", "Product with good attenuation"), 40 | ("iwc_rain", "Product with rain"), 41 | ("iwc_adv", "Product"), 42 | ("iwc_att_adv", "Product with good attenuation"), 43 | ("iwc_rain_adv", "Product with rain"), 44 | ], 45 | ) 46 | def test_get_iwc_title(key, value) -> None: 47 | var = VariableInfo() 48 | field_name = key + "_" + MODEL 49 | x = pl._get_iwc_title(field_name, var) 50 | assert x == value 51 | 52 | 53 | @pytest.mark.parametrize( 54 | "key, value", 55 | [ 56 | ("iwc", "Product"), 57 | ("iwc_att", "Product with good attenuation"), 58 | ("iwc_rain", "Product with rain"), 59 | ("iwc_adv", "Product"), 60 | ("iwc_att_adv", "Product with good attenuation"), 61 | ("iwc_rain_adv", "Product with rain"), 62 | ], 63 | ) 64 | def test_get_iwc_title_cycle(key, value) -> None: 65 | var = VariableInfo() 66 | field_name = key + "_" + MODEL + "_001" 67 | x = pl._get_iwc_title(field_name, var) 68 | assert x == value 69 | 70 | 71 | def test_get_product_title() -> None: 72 | var = VariableInfo() 73 | value = "Product" 74 | x = pl._get_product_title(var) 75 | assert x == value 76 | 77 | 78 | def test_get_product_title_cycle() -> None: 79 | var = VariableInfo() 80 | value = "Product" 81 | x = pl._get_product_title(var) 82 | assert x == value 83 | 84 | 85 | @pytest.mark.parametrize( 86 | "key, title", 87 | [("lwc", "Product"), ("lwc_adv", "Product (Advection time)")], 88 | ) 89 | def test_get_stat_titles(key, title) -> None: 90 | field_name = key + "_" + MODEL 91 | var = VariableInfo() 92 | x = pl._get_stat_titles(field_name, key, var) 93 | assert x == title 94 | 95 | 96 | @pytest.mark.parametrize("key", ["cf_V", "cf_A", "cf_V_adv", "cf_A_adv"]) 97 | def test_get_cf_title_stat(key) -> None: 98 | field_name = key + "_" + MODEL 99 | var = VariableInfo() 100 | x = pl._get_cf_title_stat(field_name, var) 101 | value = "Product volume" 102 | if "A" in key: 103 | value = "Product area" 104 | assert x == value 105 | 106 | 107 | @pytest.mark.parametrize( 108 | "key, value", 109 | [ 110 | ("iwc", "Product"), 111 | ("iwc_att", "Product with good attenuation"), 112 | ("iwc_rain", "Product with rain"), 113 | ], 114 | ) 115 | def test_get_iwc_title_stat(key, value) -> None: 116 | field_name = key + "_" + MODEL 117 | var = VariableInfo() 118 | x = pl._get_iwc_title_stat(field_name, var) 119 | assert x == value 120 | 121 | 122 | @pytest.mark.parametrize("key", ["lwc"]) 123 | def test_get_product_title_stat(key) -> None: 124 | var = VariableInfo() 125 | x = pl._get_product_title_stat(var) 126 | assert x == "Product" 127 | -------------------------------------------------------------------------------- /cloudnetpy/model_evaluation/utils.py: -------------------------------------------------------------------------------- 1 | import os 2 | from pathlib import Path 3 | 4 | 5 | def file_exists(file_path: str) -> bool: 6 | return Path.is_file(Path(file_path)) and os.path.getsize(file_path) > 0 7 | -------------------------------------------------------------------------------- /cloudnetpy/plotting/__init__.py: -------------------------------------------------------------------------------- 1 | from .plot_meta import PlotMeta 2 | from .plotting import Dimensions, PlotParameters, generate_figure, plot_2d 3 | -------------------------------------------------------------------------------- /cloudnetpy/products/__init__.py: -------------------------------------------------------------------------------- 1 | from .classification import generate_classification 2 | from .der import generate_der 3 | from .drizzle import generate_drizzle 4 | from .ier import generate_ier 5 | from .iwc import generate_iwc 6 | from .lwc import generate_lwc 7 | from .mwr_tools import generate_mwr_lhumpro, generate_mwr_multi, generate_mwr_single 8 | -------------------------------------------------------------------------------- /cloudnetpy/products/mie_lu_tables.nc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/actris-cloudnet/cloudnetpy/7ca27b7f31018470fc60596063d51124b48ff6db/cloudnetpy/products/mie_lu_tables.nc -------------------------------------------------------------------------------- /cloudnetpy/py.typed: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/actris-cloudnet/cloudnetpy/7ca27b7f31018470fc60596063d51124b48ff6db/cloudnetpy/py.typed -------------------------------------------------------------------------------- /cloudnetpy/version.py: -------------------------------------------------------------------------------- 1 | MAJOR = 1 2 | MINOR = 79 3 | PATCH = 0 4 | __version__ = f"{MAJOR}.{MINOR}.{PATCH}" 5 | -------------------------------------------------------------------------------- /docs/.gitignore: -------------------------------------------------------------------------------- 1 | /*.mmclx 2 | /*.nc 3 | /*.LWP 4 | /html/ 5 | /doctrees/ 6 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Minimal makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = -E -a 6 | SPHINXBUILD = sphinx-build 7 | SOURCEDIR = source 8 | BUILDDIR = . 9 | 10 | # Put it first so that "make" without argument is like "make help". 11 | help: 12 | @$(SPHINXBUILD) -E help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 13 | 14 | .PHONY: help Makefile 15 | 16 | # Catch-all target: route all unknown targets to Sphinx using the new 17 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). 18 | %: Makefile 19 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 20 | -------------------------------------------------------------------------------- /docs/index.html: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /docs/requirements.txt: -------------------------------------------------------------------------------- 1 | sphinx==7.2.6 2 | sphinx-rtd-theme==2.0.0 3 | -------------------------------------------------------------------------------- /docs/scripts/quickstart_download: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | curl -O https://cloudnet.fmi.fi/api/download/raw/62905a7c-4e18-474f-8532-bb59f39ca4ff/20230729_0000.mmclx.gz 3 | gunzip 20230729_0000.mmclx.gz 4 | curl -O https://cloudnet.fmi.fi/api/download/raw/7d1909f3-c73f-4de9-a771-e6795751e495/CHM15kxLMU_20230729.nc 5 | curl -O https://cloudnet.fmi.fi/api/download/raw/490704b2-7533-4137-979b-f197a6c72e17/230729.LWP 6 | curl -O https://cloudnet.fmi.fi/api/download/product/856b5a84-155b-427d-b914-09238c206c02/20230729_munich_ecmwf.nc 7 | -------------------------------------------------------------------------------- /docs/scripts/quickstart_generate: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | from cloudnetpy.categorize import generate_categorize 4 | from cloudnetpy.instruments import ceilo2nc, hatpro2nc, mira2nc 5 | from cloudnetpy.plotting import generate_figure 6 | from cloudnetpy.products import generate_classification 7 | 8 | uuid = mira2nc("20230729_0000.mmclx", "radar.nc", {"name": "Munich"}) 9 | 10 | generate_figure( 11 | "radar.nc", 12 | ["Zh"], 13 | show=False, 14 | output_filename="source/_static/quickstart_radar.png", 15 | ) 16 | 17 | uuid = ceilo2nc( 18 | "CHM15kxLMU_20230729.nc", "lidar.nc", {"name": "Munich", "altitude": 538} 19 | ) 20 | generate_figure( 21 | "lidar.nc", 22 | ["beta"], 23 | show=False, 24 | output_filename="source/_static/quickstart_lidar.png", 25 | ) 26 | 27 | 28 | uuid, valid_files = hatpro2nc( 29 | ".", "mwr.nc", {"name": "Munich", "altitude": 538}, date="2023-07-29" 30 | ) 31 | generate_figure( 32 | "mwr.nc", ["lwp"], show=False, output_filename="source/_static/quickstart_mwr.png" 33 | ) 34 | 35 | generate_figure( 36 | "20230729_munich_ecmwf.nc", 37 | ["cloud_fraction"], 38 | show=False, 39 | output_filename="source/_static/quickstart_model.png", 40 | ) 41 | 42 | input_files = { 43 | "radar": "radar.nc", 44 | "lidar": "lidar.nc", 45 | "model": "20230729_munich_ecmwf.nc", 46 | "mwr": "mwr.nc", 47 | } 48 | uuid = generate_categorize(input_files, "categorize.nc") 49 | 50 | uuid = generate_classification("categorize.nc", "classification.nc") 51 | generate_figure( 52 | "classification.nc", 53 | ["target_classification"], 54 | show=False, 55 | output_filename="source/_static/quickstart_classification.png", 56 | ) 57 | -------------------------------------------------------------------------------- /docs/source/_static/20190423_mace-head_classification.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/actris-cloudnet/cloudnetpy/7ca27b7f31018470fc60596063d51124b48ff6db/docs/source/_static/20190423_mace-head_classification.png -------------------------------------------------------------------------------- /docs/source/_static/20230831_lindenberg_classification-9b74f4ac-target_classification.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/actris-cloudnet/cloudnetpy/7ca27b7f31018470fc60596063d51124b48ff6db/docs/source/_static/20230831_lindenberg_classification-9b74f4ac-target_classification.png -------------------------------------------------------------------------------- /docs/source/_static/CLU_workflow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/actris-cloudnet/cloudnetpy/7ca27b7f31018470fc60596063d51124b48ff6db/docs/source/_static/CLU_workflow.png -------------------------------------------------------------------------------- /docs/source/_static/cloudnet_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/actris-cloudnet/cloudnetpy/7ca27b7f31018470fc60596063d51124b48ff6db/docs/source/_static/cloudnet_logo.png -------------------------------------------------------------------------------- /docs/source/_static/cloudradar_cropped.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/actris-cloudnet/cloudnetpy/7ca27b7f31018470fc60596063d51124b48ff6db/docs/source/_static/cloudradar_cropped.jpeg -------------------------------------------------------------------------------- /docs/source/_static/custom.css: -------------------------------------------------------------------------------- 1 | .wy-nav-content { 2 | max-width: 1000px !important; 3 | } 4 | -------------------------------------------------------------------------------- /docs/source/_static/dlidar.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/actris-cloudnet/cloudnetpy/7ca27b7f31018470fc60596063d51124b48ff6db/docs/source/_static/dlidar.jpg -------------------------------------------------------------------------------- /docs/source/_static/example_data.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/actris-cloudnet/cloudnetpy/7ca27b7f31018470fc60596063d51124b48ff6db/docs/source/_static/example_data.jpg -------------------------------------------------------------------------------- /docs/source/_static/example_data.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/actris-cloudnet/cloudnetpy/7ca27b7f31018470fc60596063d51124b48ff6db/docs/source/_static/example_data.png -------------------------------------------------------------------------------- /docs/source/_static/quickstart_classification.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/actris-cloudnet/cloudnetpy/7ca27b7f31018470fc60596063d51124b48ff6db/docs/source/_static/quickstart_classification.png -------------------------------------------------------------------------------- /docs/source/_static/quickstart_lidar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/actris-cloudnet/cloudnetpy/7ca27b7f31018470fc60596063d51124b48ff6db/docs/source/_static/quickstart_lidar.png -------------------------------------------------------------------------------- /docs/source/_static/quickstart_model.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/actris-cloudnet/cloudnetpy/7ca27b7f31018470fc60596063d51124b48ff6db/docs/source/_static/quickstart_model.png -------------------------------------------------------------------------------- /docs/source/_static/quickstart_mwr.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/actris-cloudnet/cloudnetpy/7ca27b7f31018470fc60596063d51124b48ff6db/docs/source/_static/quickstart_mwr.png -------------------------------------------------------------------------------- /docs/source/_static/quickstart_radar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/actris-cloudnet/cloudnetpy/7ca27b7f31018470fc60596063d51124b48ff6db/docs/source/_static/quickstart_radar.png -------------------------------------------------------------------------------- /docs/source/_static/radar.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/actris-cloudnet/cloudnetpy/7ca27b7f31018470fc60596063d51124b48ff6db/docs/source/_static/radar.jpg -------------------------------------------------------------------------------- /docs/source/_templates/layout.html: -------------------------------------------------------------------------------- 1 | {% extends "!layout.html" %} 2 | {% block extrahead %} 3 | 4 | {% endblock %} 5 | -------------------------------------------------------------------------------- /docs/source/conf.py: -------------------------------------------------------------------------------- 1 | # noqa: INP001 2 | import os 3 | import sys 4 | 5 | sys.path.insert(0, os.path.abspath("../..")) 6 | sys.path.insert(0, os.path.abspath("../../cloudnetpy/")) 7 | 8 | # Configuration file for the Sphinx documentation builder. 9 | # 10 | # For the full list of built-in configuration values, see the documentation: 11 | # https://www.sphinx-doc.org/en/master/usage/configuration.html 12 | 13 | # -- Project information ----------------------------------------------------- 14 | # https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information 15 | 16 | # -- Project information ----------------------------------------------------- 17 | 18 | project = "CloudnetPy" 19 | copyright = "2022, Finnish Meteorological Institute" # noqa: A001 20 | author = "Finnish Meteorological Institute" 21 | 22 | # -- General configuration --------------------------------------------------- 23 | # https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration 24 | 25 | extensions = [ 26 | "sphinx.ext.autodoc", 27 | "sphinx.ext.doctest", 28 | "sphinx.ext.imgmath", 29 | "sphinx.ext.viewcode", 30 | "sphinx.ext.napoleon", 31 | "sphinx.ext.todo", 32 | "sphinx.ext.autosummary", 33 | "sphinx.ext.autosectionlabel", 34 | ] 35 | 36 | templates_path = ["_templates"] 37 | exclude_patterns = ["_temp/*"] 38 | autodoc_member_order = "bysource" 39 | 40 | 41 | # -- Options for HTML output ------------------------------------------------- 42 | # https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output 43 | 44 | html_theme = "sphinx_rtd_theme" 45 | html_static_path = ["_static"] 46 | 47 | napoleon_google_docstring = True 48 | -------------------------------------------------------------------------------- /docs/source/fileformat.rst: -------------------------------------------------------------------------------- 1 | File format description 2 | ======================= 3 | 4 | Moved to https://cloudnet.fmi.fi/products/ 5 | -------------------------------------------------------------------------------- /docs/source/index.rst: -------------------------------------------------------------------------------- 1 | .. cloudnet documentation master file, created by 2 | sphinx-quickstart on Wed Dec 5 21:38:46 2018. 3 | You can adapt this file completely to your liking, but it should at least 4 | contain the root `toctree` directive. 5 | 6 | ======================== 7 | CloudnetPy documentation 8 | ======================== 9 | 10 | Welcome! This is the documentation for CloudnetPy, the Python 11 | implementation of the Cloudnet processing scheme. 12 | 13 | 14 | .. toctree:: 15 | :maxdepth: 4 16 | 17 | overview 18 | installation 19 | quickstart 20 | api 21 | model-evaluation/overview 22 | guide 23 | 24 | 25 | 26 | Indices and tables 27 | ================== 28 | 29 | * :ref:`genindex` 30 | * :ref:`modindex` 31 | * :ref:`search` 32 | -------------------------------------------------------------------------------- /docs/source/installation.rst: -------------------------------------------------------------------------------- 1 | ========================= 2 | Installation instructions 3 | ========================= 4 | 5 | CloudnetPy can be installed on any computer having Python 3.10 or higher. 6 | The actual installation procedure depends on the operating system. The 7 | instructions below are for Ubuntu 22.04. 8 | 9 | Python installation 10 | ------------------- 11 | 12 | .. code-block:: console 13 | 14 | $ sudo apt update && sudo apt upgrade 15 | $ sudo apt install python3-venv python3-pip python3-tk 16 | 17 | Virtual environment 18 | ------------------- 19 | 20 | Create a new virtual environment and activate it: 21 | 22 | .. code-block:: console 23 | 24 | $ python3 -m venv venv 25 | $ source venv/bin/activate 26 | 27 | 28 | Pip-based installation 29 | ---------------------- 30 | 31 | CloudnetPy is available from Python Package Index, `PyPI 32 | `_. 33 | Use Python's package manager, `pip `_, 34 | to install CloudnetPy package into the virtual environment: 35 | 36 | .. code-block:: console 37 | 38 | (venv)$ pip3 install cloudnetpy 39 | 40 | CloudnetPy is now ready for use from that virtual environment. 41 | -------------------------------------------------------------------------------- /docs/source/model-evaluation/_figs/20190517_mace-head_cf_A_adv_ecmwf_single.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/actris-cloudnet/cloudnetpy/7ca27b7f31018470fc60596063d51124b48ff6db/docs/source/model-evaluation/_figs/20190517_mace-head_cf_A_adv_ecmwf_single.png -------------------------------------------------------------------------------- /docs/source/model-evaluation/_figs/20190517_mace-head_cf_V_adv_ecmwf_single.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/actris-cloudnet/cloudnetpy/7ca27b7f31018470fc60596063d51124b48ff6db/docs/source/model-evaluation/_figs/20190517_mace-head_cf_V_adv_ecmwf_single.png -------------------------------------------------------------------------------- /docs/source/model-evaluation/_figs/20190517_mace-head_cf_aerror_ecmwf.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/actris-cloudnet/cloudnetpy/7ca27b7f31018470fc60596063d51124b48ff6db/docs/source/model-evaluation/_figs/20190517_mace-head_cf_aerror_ecmwf.png -------------------------------------------------------------------------------- /docs/source/model-evaluation/_figs/20190517_mace-head_cf_area_ecmwf.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/actris-cloudnet/cloudnetpy/7ca27b7f31018470fc60596063d51124b48ff6db/docs/source/model-evaluation/_figs/20190517_mace-head_cf_area_ecmwf.png -------------------------------------------------------------------------------- /docs/source/model-evaluation/_figs/20190517_mace-head_cf_ecmwf_group.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/actris-cloudnet/cloudnetpy/7ca27b7f31018470fc60596063d51124b48ff6db/docs/source/model-evaluation/_figs/20190517_mace-head_cf_ecmwf_group.png -------------------------------------------------------------------------------- /docs/source/model-evaluation/_figs/20190517_mace-head_cf_hist_ecmwf.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/actris-cloudnet/cloudnetpy/7ca27b7f31018470fc60596063d51124b48ff6db/docs/source/model-evaluation/_figs/20190517_mace-head_cf_hist_ecmwf.png -------------------------------------------------------------------------------- /docs/source/model-evaluation/_figs/20190517_mace-head_cf_vertical_ecmwf.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/actris-cloudnet/cloudnetpy/7ca27b7f31018470fc60596063d51124b48ff6db/docs/source/model-evaluation/_figs/20190517_mace-head_cf_vertical_ecmwf.png -------------------------------------------------------------------------------- /docs/source/model-evaluation/_figs/20190517_mace-head_classification.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/actris-cloudnet/cloudnetpy/7ca27b7f31018470fc60596063d51124b48ff6db/docs/source/model-evaluation/_figs/20190517_mace-head_classification.png -------------------------------------------------------------------------------- /docs/source/model-evaluation/_figs/20190517_mace-head_iwc-Z-T-method.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/actris-cloudnet/cloudnetpy/7ca27b7f31018470fc60596063d51124b48ff6db/docs/source/model-evaluation/_figs/20190517_mace-head_iwc-Z-T-method.png -------------------------------------------------------------------------------- /docs/source/model-evaluation/_figs/20190517_mace-head_iwc_ecmwf_group.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/actris-cloudnet/cloudnetpy/7ca27b7f31018470fc60596063d51124b48ff6db/docs/source/model-evaluation/_figs/20190517_mace-head_iwc_ecmwf_group.png -------------------------------------------------------------------------------- /docs/source/model-evaluation/_figs/20190517_mace-head_lwc-scaled-adiabatic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/actris-cloudnet/cloudnetpy/7ca27b7f31018470fc60596063d51124b48ff6db/docs/source/model-evaluation/_figs/20190517_mace-head_lwc-scaled-adiabatic.png -------------------------------------------------------------------------------- /docs/source/model-evaluation/_figs/20190517_mace-head_lwc_ecmwf_group.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/actris-cloudnet/cloudnetpy/7ca27b7f31018470fc60596063d51124b48ff6db/docs/source/model-evaluation/_figs/20190517_mace-head_lwc_ecmwf_group.png -------------------------------------------------------------------------------- /docs/source/model-evaluation/_figs/L3_process.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/actris-cloudnet/cloudnetpy/7ca27b7f31018470fc60596063d51124b48ff6db/docs/source/model-evaluation/_figs/L3_process.png -------------------------------------------------------------------------------- /docs/source/model-evaluation/_figs/custom.css: -------------------------------------------------------------------------------- 1 | .wy-nav-content { 2 | max-width: 1000px !important; 3 | } 4 | -------------------------------------------------------------------------------- /docs/source/model-evaluation/overview.rst: -------------------------------------------------------------------------------- 1 | ================ 2 | Model evaluation 3 | ================ 4 | 5 | CloudnetPy has work-in-progress model evaluation functionality. It aims to 6 | connect Cloudnet observation products with simulation products from various 7 | models and compare those. The main goal of the system is to point out 8 | capability and lacks of different simulated cloud variables. 9 | 10 | Model evaluation analysis will be done with several different time scales. The shortest 11 | and the most important products are day scale downsampled products. 12 | The day scale products are not jet the model evaluation products, but day scale 13 | is the key step to create evaluation analysis between observations and model simulations. 14 | At a later state of software developing also time length of month, season, year and decade 15 | will be created based on day scale products. 16 | 17 | Example chain of Model evaluation day scale processing of Cloud fraction, Ice water content and 18 | Liquid water content. 19 | 20 | .. figure:: _figs/20190517_mace-head_classification.png 21 | :width: 500 px 22 | :align: center 23 | 24 | Cloudnet L2 product 'Classification'. It is generated by using Cloudnet Categorize bits. 25 | Same bits are used for generating 'observed' Cloud fraction, which can't be observed. 26 | Cloud fraction is a post-process variable of models. 27 | 28 | .. figure:: _figs/20190517_mace-head_iwc-Z-T-method.png 29 | :width: 500 px 30 | :align: center 31 | 32 | Cloudnet L2 product 'Ice water content'. Models are not producing exact the same variable but 33 | mixing ration of ice at grid point. 34 | Comparable product is calculated using temperature, pressure and moisture from model. 35 | 36 | .. figure:: _figs/20190517_mace-head_lwc-scaled-adiabatic.png 37 | :width: 500 px 38 | :align: center 39 | 40 | Cloudnet L2 product 'Liquid water content'. Models are not producing exact the same variable but 41 | mixing ration of liquid at grid point. 42 | Comparable product is calculated using temperature, pressure and moisture from. 43 | 44 | Time-height grid of observation products is always thicker than grid of models so 45 | observation data is downsampled by averaging to be same size as model grid. 46 | Downsampled observation and simulations are L3 day scale products. 47 | 48 | Examples of L3 day products: model simulation vs. downsampled observation comparisons 49 | of Cloud fraction, IWC and LWC. 50 | 51 | .. figure:: _figs/20190517_mace-head_cf_ecmwf_group.png 52 | :width: 500 px 53 | :align: center 54 | 55 | Observated and simulated Cloud fraction. Used model ECMWF. 56 | 57 | .. figure:: _figs/20190517_mace-head_iwc_ecmwf_group.png 58 | :width: 500 px 59 | :align: center 60 | 61 | Observated and simulated IWC. Used model ECMWF. 62 | 63 | .. figure:: _figs/20190517_mace-head_lwc_ecmwf_group.png 64 | :width: 500 px 65 | :align: center 66 | 67 | Observated and simulated LWC. Used model ECMWF. 68 | 69 | 70 | For ensuring the most suitable version of observation downsampling, there are couple version 71 | of specific products. For all products there is down sample to height-advection time grid version. 72 | For cloud fraction there is couple different methods for generating observation and 73 | also high level cloud filtered version. For IWC there is different masked versions. 74 | 75 | 76 | Example of Model evaluation Workflow chart 77 | 78 | .. figure:: _figs/L3_process.png 79 | :width: 500 px 80 | :align: center 81 | 82 | 83 | 84 | See also: 85 | 86 | - Cloudnet data portal: https://cloudnet.fmi.fi/ 87 | - ACTRIS home: http://actris.eu/ 88 | - ACTRIS data portal: http://actris.nilu.no/ 89 | -------------------------------------------------------------------------------- /docs/source/overview.rst: -------------------------------------------------------------------------------- 1 | ======== 2 | Overview 3 | ======== 4 | 5 | Cloudnet processing scheme 6 | -------------------------- 7 | 8 | CloudnetPy (`Tukiainen 2020`_) is a Python package implementing the so-called Cloudnet processing scheme 9 | (`Illingworth 2007`_). Cloudnet processing produces vertical profiles of cloud properties 10 | from the ground-based remote sensing measurements. Cloud radar, optical lidar, microwave radiometer 11 | and thermodynamical (model or radiosonde) data are combined to accurately characterize 12 | clouds up to 15 km with high temporal and vertical resolution. 13 | 14 | .. figure:: _static/example_data.png 15 | :width: 500 px 16 | :align: center 17 | 18 | Example of input data used in Cloudnet processing: Radar reflectivity factor (top), mean 19 | doppler velocity (2nd), lidar backscatter coefficient (3rd), 20 | and liquid water path from microwave radiometer (bottom). 21 | 22 | The measurement and model data are brought into common grid and classified as ice, 23 | liquid, aerosol, insects, and so on. Then, geophysical products such as ice water content 24 | can be retrieved in further processing steps. A more detailed description can be 25 | found in `Illingworth 2007`_ and references in it. 26 | 27 | .. note:: 28 | 29 | Near real-time Cloudnet data can be accessed at https://cloudnet.fmi.fi. 30 | 31 | Statement of need 32 | ----------------- 33 | 34 | In the forthcoming years, Cloudnet will be one of the key components in `ACTRIS`_ (Aerosol, 35 | Clouds and Trace Gases Research Infrastructure), where the Cloudnet framework will be used 36 | to process gigabytes of cloud remote sensing data per day in near real time. The ACTRIS 37 | RI is now in its implementation phase and aims to be fully operational in 2025. 38 | 39 | To fulfill requirements from ACTRIS, a robust, open software that can reliably process 40 | large amounts of data is needed. The CloudnetPy software package is aimed to perform operational 41 | ACTRIS cloud remote sensing processing, providing quality controlled data products from 42 | around 15 measurement sites in Europe. Unlike the original proprietary Cloudnet software, 43 | CloudnetPy is open source and includes tests, documentation and user-friendly API that 44 | the research community can use to further develop the existing methods and to create 45 | new products. 46 | 47 | .. _Tukiainen 2020: https://doi.org/10.21105/joss.02123 48 | .. _Illingworth 2007: https://journals.ametsoc.org/doi/abs/10.1175/BAMS-88-6-883 49 | .. _ACTRIS: http://actris.eu/ 50 | 51 | .. important:: 52 | 53 | CloudnetPy is a rewritten Python implementation of the original Matlab processing prototype code. 54 | CloudnetPy features several revised methods and bug fixes, open source codebase, 55 | netCDF4 file format and extensive documentation. 56 | 57 | Operational Cloudnet processing will have CloudnetPy in its core but additionally include a 58 | calibration database and comprehensive quality control / assurance procedures: 59 | 60 | .. figure:: _static/CLU_workflow.png 61 | :width: 650 px 62 | :align: center 63 | 64 | Workflow of operational Cloudnet processing in ACTRIS. 65 | 66 | 67 | See also: 68 | 69 | - Cloudnet data portal: https://cloudnet.fmi.fi/ 70 | - CloudnetPy source: https://github.com/actris-cloudnet/cloudnetpy 71 | - ACTRIS home: http://actris.eu/ 72 | - ACTRIS data portal: http://actris.nilu.no/ 73 | -------------------------------------------------------------------------------- /paper/paper.bib: -------------------------------------------------------------------------------- 1 | @article{IllingworthEtAl07, 2 | author = {Illingworth, A.~J. and Hogan, R.~J. and O'Connor, E.~J. and Bouniol, D. 3 | and Brooks, M.~E. and Delanoé, J. and Donovan, D.~P. and Eastment, J.~D. 4 | and Gaussiat, N. and Goddard, J.~W.~F. and Haeffelin, M. and Baltink, H. Klein 5 | and Krasnov, O.~A. and Pelon, J. and Piriou, J.~M. and Protat, A. 6 | and Russchenberg, H.~W.~J. and Seifert, A. and Tompkins, A.~M. 7 | and van Zadelhoff, G.~J. and Vinit, F. and Willén, U. 8 | and Wilson, D.~R. and Wrench, C.~L.}, 9 | title = {Cloudnet: Continuous Evaluation of Cloud Profiles in Seven Operational 10 | Models Using Ground-Based Observations}, 11 | journal = {Bulletin of the American Meteorological Society}, 12 | volume = {88}, 13 | number = {6}, 14 | pages = {883-898}, 15 | year = {2007}, 16 | doi = {10.1175/BAMS-88-6-883}, 17 | URL = {https://doi.org/10.1175/BAMS-88-6-883}, 18 | } 19 | 20 | @article{HoganEtAl06, 21 | author = {Hogan, R.~J. and Mittermaier, M.~P. and Illingworth, A.~J.}, 22 | title = {The Retrieval of Ice Water Content from Radar Reflectivity Factor and Temperature and Its Use in Evaluating a Mesoscale Model}, 23 | journal = {Journal of Applied Meteorology and Climatology}, 24 | volume = {45}, 25 | number = {2}, 26 | pages = {301-317}, 27 | year = {2006}, 28 | doi = {10.1175/JAM2340.1}, 29 | URL = {https://doi.org/10.1175/JAM2340.1} 30 | } 31 | 32 | @article{OConnorEtAl05, 33 | author = {O’Connor, E.~J. and Hogan, R.~J. and Illingworth, A.~J.}, 34 | title = {Retrieving Stratocumulus Drizzle Parameters Using Doppler Radar and Lidar}, 35 | journal = {Journal of Applied Meteorology}, 36 | volume = {44}, 37 | number = {1}, 38 | pages = {14-27}, 39 | year = {2005}, 40 | doi = {10.1175/JAM-2181.1}, 41 | URL = {https://doi.org/10.1175/JAM-2181.1}, 42 | } 43 | 44 | @book{ACTRIS_handbook, 45 | author = {Häme, S. and Saporano, G. and Kivekäs, N. and Kaukolehto, M. and Rodriguez, E.}, 46 | title = {ACTRIS Stakeholder Handbook 2018}, 47 | year = {2018}, 48 | isbn = {978-952-336-066-2}, 49 | publisher = {Painotalo Trinket Oy}, 50 | adsurl = {www.actris.eu}, 51 | } 52 | 53 | @article{TuononenEtAl19, 54 | AUTHOR = {Tuononen, M. and O'Connor, E. J. and Sinclair, V. A.}, 55 | TITLE = {Evaluating solar radiation forecast uncertainty}, 56 | JOURNAL = {Atmospheric Chemistry and Physics}, 57 | VOLUME = {19}, 58 | YEAR = {2019}, 59 | NUMBER = {3}, 60 | PAGES = {1985--2000}, 61 | URL = {https://www.atmos-chem-phys.net/19/1985/2019/}, 62 | DOI = {10.5194/acp-19-1985-2019} 63 | } 64 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [project] 2 | name = "cloudnetpy" 3 | description = "Python package for Cloudnet processing" 4 | authors = [{name = "Simo Tukiainen"}] 5 | readme = "README.md" 6 | license = {file = "LICENSE"} 7 | requires-python = ">=3.10" 8 | classifiers = [ 9 | "Development Status :: 5 - Production/Stable", 10 | "Intended Audience :: Science/Research", 11 | "License :: OSI Approved :: MIT License", 12 | "Operating System :: OS Independent", 13 | "Programming Language :: Python :: 3", 14 | "Topic :: Scientific/Engineering :: Atmospheric Science", 15 | ] 16 | dependencies = [ 17 | "ceilopyter", 18 | "doppy>=0.5.0", 19 | "matplotlib", 20 | "mwrpy>=1.3.0", 21 | "netCDF4", 22 | "requests", 23 | "rpgpy>=0.14.5", 24 | "scikit-image", 25 | "scipy", 26 | ] 27 | dynamic = ["version"] 28 | 29 | [project.optional-dependencies] 30 | test = [ 31 | "cloudnetpy_qc", 32 | "mypy", 33 | "pytest", 34 | "pytest-flakefinder", 35 | "ruff", 36 | "types-requests", 37 | ] 38 | dev = ["pre-commit", "release-version"] 39 | extras = ["voodoonet>=0.1.7"] 40 | 41 | [project.scripts] 42 | cloudnetpy = "cloudnetpy.cli:main" 43 | 44 | [project.urls] 45 | Homepage = "https://github.com/actris-cloudnet/cloudnetpy" 46 | Documentation = "https://actris-cloudnet.github.io/cloudnetpy/" 47 | Repository = "https://github.com/actris-cloudnet/cloudnetpy" 48 | Changelog = "https://github.com/actris-cloudnet/cloudnetpy/blob/main/CHANGELOG.md" 49 | 50 | [tool.mypy] 51 | check_untyped_defs = true 52 | 53 | [[tool.mypy.overrides]] 54 | module = ["mpl_toolkits.*", "scipy.*", "voodoonet.*"] 55 | ignore_missing_imports = true 56 | 57 | [tool.release-version] 58 | filename = "cloudnetpy/version.py" 59 | pattern = ["MAJOR = (?P\\d+)", "MINOR = (?P\\d+)", "PATCH = (?P\\d+)"] 60 | changelog = "CHANGELOG.md" 61 | 62 | [tool.ruff.lint] 63 | exclude = ["cloudnetpy/model_evaluation/tests/*", "tests/*"] 64 | select = ["ALL"] 65 | ignore = [ 66 | "ANN", # missing types, use mypy for this 67 | "C9", # too complex, fix me later 68 | "COM812", # Formatting 69 | "D100", # allow missing documentation 70 | "D101", # allow missing documentation 71 | "D102", # allow missing documentation 72 | "D103", # allow missing documentation 73 | "D104", # allow missing documentation 74 | "D105", # allow missing documentation 75 | "D106", # allow missing documentation 76 | "D107", # allow missing documentation 77 | "D205", # allow long multi-line summary 78 | "D211", # conflicting documentation rule 79 | "D213", # conflicting documentation rule 80 | "DTZ00", # do not check timezone info 81 | "FIX002", # TODOs 82 | "ISC001", # Formatter wants me 83 | "N8", # uppercase variable names 84 | "PD011", # false positive 85 | "PERF", # try except in loop 86 | "PLR", # too many arguments etc. 87 | "PTH", # use pathlib, fix me later 88 | "RUF002", # unicode in doc string 89 | "TD002", # TODOs 90 | "TD003", # TODOs 91 | ] 92 | 93 | [tool.ruff.lint.extend-per-file-ignores] 94 | "__init__.py" = ["F401"] 95 | 96 | [tool.ruff.lint.pydocstyle] 97 | convention = "google" 98 | 99 | [tool.setuptools.dynamic] 100 | version = {attr = "cloudnetpy.version.__version__"} 101 | 102 | [tool.setuptools.packages] 103 | find = {} 104 | 105 | [tool.tomlsort] 106 | trailing_comma_inline_array = true 107 | sort_inline_arrays = true 108 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/actris-cloudnet/cloudnetpy/7ca27b7f31018470fc60596063d51124b48ff6db/tests/__init__.py -------------------------------------------------------------------------------- /tests/source_data/ecmwf_model.nc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/actris-cloudnet/cloudnetpy/7ca27b7f31018470fc60596063d51124b48ff6db/tests/source_data/ecmwf_model.nc -------------------------------------------------------------------------------- /tests/source_data/hatpro_mwr.nc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/actris-cloudnet/cloudnetpy/7ca27b7f31018470fc60596063d51124b48ff6db/tests/source_data/hatpro_mwr.nc -------------------------------------------------------------------------------- /tests/source_data/raw_chm15k_lidar.nc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/actris-cloudnet/cloudnetpy/7ca27b7f31018470fc60596063d51124b48ff6db/tests/source_data/raw_chm15k_lidar.nc -------------------------------------------------------------------------------- /tests/source_data/raw_mira_radar.mmclx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/actris-cloudnet/cloudnetpy/7ca27b7f31018470fc60596063d51124b48ff6db/tests/source_data/raw_mira_radar.mmclx -------------------------------------------------------------------------------- /tests/unit/conftest.py: -------------------------------------------------------------------------------- 1 | from datetime import date 2 | 3 | import netCDF4 4 | import numpy as np 5 | import pytest 6 | 7 | DIMENSIONS = ("time", "height", "model_time", "model_height") 8 | TEST_ARRAY = np.arange(5) # Simple array for all dimension and variables 9 | 10 | 11 | @pytest.fixture() 12 | def test_array(): 13 | return TEST_ARRAY 14 | 15 | 16 | @pytest.fixture(scope="session") 17 | def file_metadata(): 18 | """Some example global metadata to test file.""" 19 | year, month, day = "2019", "05", "23" 20 | return { 21 | "year": year, 22 | "month": month, 23 | "day": day, 24 | "location": "Kumpula", 25 | "case_date": date(int(year), int(month), int(day)), 26 | "altitude_km": 0.5, 27 | "cloudnet_file_type": "classification", 28 | } 29 | 30 | 31 | @pytest.fixture(scope="session") 32 | def nc_file(tmpdir_factory, file_metadata): 33 | """Creates a simple netCDF file for testing.""" 34 | file_name = tmpdir_factory.mktemp("data").join("file.nc") 35 | with netCDF4.Dataset(file_name, "w", format="NETCDF4_CLASSIC") as root_grp: 36 | _create_dimensions(root_grp) 37 | _create_dimension_variables(root_grp) 38 | _create_global_attributes(root_grp, file_metadata) 39 | _create_variable( 40 | root_grp, 41 | "altitude", 42 | file_metadata["altitude_km"], 43 | "km", 44 | dim=[], 45 | ) 46 | _create_variable(root_grp, "test_array", TEST_ARRAY, "m s-1") 47 | _create_variable(root_grp, "range", TEST_ARRAY, "km") 48 | return file_name 49 | 50 | 51 | @pytest.fixture(scope="session") 52 | def raw_mira_file(tmpdir_factory, file_metadata): 53 | """Creates a fake raw mira netCDF file for testing.""" 54 | file_name = tmpdir_factory.mktemp("data").join("mira_file.nc") 55 | with netCDF4.Dataset(file_name, "w", format="NETCDF4_CLASSIC") as root_grp: 56 | _create_dimensions(root_grp) 57 | _create_dimension_variables(root_grp) 58 | _create_variable(root_grp, "test_array", TEST_ARRAY, "m s-1") 59 | _create_variable( 60 | root_grp, 61 | "range", 62 | [191.872, 230.2464, 268.6208, 306.9952, 345.3696], 63 | "m", 64 | ) 65 | root_grp.variables["time"][:] = [ 66 | 1590278403, 67 | 1590278406, 68 | 1590278410, 69 | 1590278413, 70 | 1590278417, 71 | ] 72 | for var in ("Zg", "VELg", "RMSg", "LDRg", "SNRg"): 73 | _create_variable(root_grp, var, TEST_ARRAY, "m") 74 | return file_name 75 | 76 | 77 | def _create_dimensions(root_grp): 78 | n_dim = len(TEST_ARRAY) 79 | for dim_name in DIMENSIONS: 80 | root_grp.createDimension(dim_name, n_dim) 81 | 82 | 83 | def _create_dimension_variables(root_grp): 84 | for dim_name in DIMENSIONS: 85 | x = root_grp.createVariable(dim_name, "f8", (dim_name,)) 86 | x[:] = TEST_ARRAY 87 | if dim_name == "height": 88 | x.units = "m" 89 | 90 | 91 | def _create_global_attributes(root_grp, meta): 92 | for key in ("year", "month", "day", "location", "cloudnet_file_type"): 93 | setattr(root_grp, key, meta[key]) 94 | 95 | 96 | def _create_variable(root_grp, name, value, units, dtype="f8", dim=("time",)): 97 | x = root_grp.createVariable(name, dtype, dim) 98 | x[:] = value 99 | x.units = units 100 | -------------------------------------------------------------------------------- /tests/unit/data/basta/basta_1a_cldradLz1R025m_v03_20210827_000000.nc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/actris-cloudnet/cloudnetpy/7ca27b7f31018470fc60596063d51124b48ff6db/tests/unit/data/basta/basta_1a_cldradLz1R025m_v03_20210827_000000.nc -------------------------------------------------------------------------------- /tests/unit/data/bowtie/bowtie-trunc.nc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/actris-cloudnet/cloudnetpy/7ca27b7f31018470fc60596063d51124b48ff6db/tests/unit/data/bowtie/bowtie-trunc.nc -------------------------------------------------------------------------------- /tests/unit/data/chm15k-2/1-profile.nc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/actris-cloudnet/cloudnetpy/7ca27b7f31018470fc60596063d51124b48ff6db/tests/unit/data/chm15k-2/1-profile.nc -------------------------------------------------------------------------------- /tests/unit/data/chm15k/00100_A202010220005_CHM170137.nc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/actris-cloudnet/cloudnetpy/7ca27b7f31018470fc60596063d51124b48ff6db/tests/unit/data/chm15k/00100_A202010220005_CHM170137.nc -------------------------------------------------------------------------------- /tests/unit/data/chm15k/00100_A202010222015_CHM170137.nc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/actris-cloudnet/cloudnetpy/7ca27b7f31018470fc60596063d51124b48ff6db/tests/unit/data/chm15k/00100_A202010222015_CHM170137.nc -------------------------------------------------------------------------------- /tests/unit/data/cl61d-2/live_20230730_001125.nc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/actris-cloudnet/cloudnetpy/7ca27b7f31018470fc60596063d51124b48ff6db/tests/unit/data/cl61d-2/live_20230730_001125.nc -------------------------------------------------------------------------------- /tests/unit/data/cl61d-2/live_20230730_020625.nc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/actris-cloudnet/cloudnetpy/7ca27b7f31018470fc60596063d51124b48ff6db/tests/unit/data/cl61d-2/live_20230730_020625.nc -------------------------------------------------------------------------------- /tests/unit/data/cl61d-2/live_20230730_052625.nc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/actris-cloudnet/cloudnetpy/7ca27b7f31018470fc60596063d51124b48ff6db/tests/unit/data/cl61d-2/live_20230730_052625.nc -------------------------------------------------------------------------------- /tests/unit/data/cl61d/live_20210829_000020.nc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/actris-cloudnet/cloudnetpy/7ca27b7f31018470fc60596063d51124b48ff6db/tests/unit/data/cl61d/live_20210829_000020.nc -------------------------------------------------------------------------------- /tests/unit/data/cl61d/live_20210829_104420.nc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/actris-cloudnet/cloudnetpy/7ca27b7f31018470fc60596063d51124b48ff6db/tests/unit/data/cl61d/live_20210829_104420.nc -------------------------------------------------------------------------------- /tests/unit/data/cl61d/live_20210829_224520.nc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/actris-cloudnet/cloudnetpy/7ca27b7f31018470fc60596063d51124b48ff6db/tests/unit/data/cl61d/live_20210829_224520.nc -------------------------------------------------------------------------------- /tests/unit/data/cl61d/live_20210829_230720.nc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/actris-cloudnet/cloudnetpy/7ca27b7f31018470fc60596063d51124b48ff6db/tests/unit/data/cl61d/live_20210829_230720.nc -------------------------------------------------------------------------------- /tests/unit/data/cl61d/live_20210829_234321.nc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/actris-cloudnet/cloudnetpy/7ca27b7f31018470fc60596063d51124b48ff6db/tests/unit/data/cl61d/live_20210829_234321.nc -------------------------------------------------------------------------------- /tests/unit/data/cl61d/live_20210829_235520.nc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/actris-cloudnet/cloudnetpy/7ca27b7f31018470fc60596063d51124b48ff6db/tests/unit/data/cl61d/live_20210829_235520.nc -------------------------------------------------------------------------------- /tests/unit/data/cl61d/live_20210830_035020.nc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/actris-cloudnet/cloudnetpy/7ca27b7f31018470fc60596063d51124b48ff6db/tests/unit/data/cl61d/live_20210830_035020.nc -------------------------------------------------------------------------------- /tests/unit/data/copernicus/copernicus-file-20220710.nc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/actris-cloudnet/cloudnetpy/7ca27b7f31018470fc60596063d51124b48ff6db/tests/unit/data/copernicus/copernicus-file-20220710.nc -------------------------------------------------------------------------------- /tests/unit/data/epsilon/doppler-lidar-wind.nc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/actris-cloudnet/cloudnetpy/7ca27b7f31018470fc60596063d51124b48ff6db/tests/unit/data/epsilon/doppler-lidar-wind.nc -------------------------------------------------------------------------------- /tests/unit/data/epsilon/doppler-lidar.nc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/actris-cloudnet/cloudnetpy/7ca27b7f31018470fc60596063d51124b48ff6db/tests/unit/data/epsilon/doppler-lidar.nc -------------------------------------------------------------------------------- /tests/unit/data/fd12p/PW040406.DAT: -------------------------------------------------------------------------------- 1 | 06.04.2004 05:51:21 FD 100 9163 9298 L 52 52 40 0.18 50.30 112 2 | 06.04.2004 05:52:21 FD 100 8475 9199 L 52 52 52 0.26 50.31 112 3 | 06.04.2004 05:53:21 FD 100 8088 9024 L 52 52 52 0.21 50.31 112 4 | -------------------------------------------------------------------------------- /tests/unit/data/fd12p/PW040712.DAT: -------------------------------------------------------------------------------- 1 | 12.07.2004 07:48:59 FD 100 3430 3412 L 52 52 53 0.12 7.98 0 2 | 12.07.2004 07:49:59 FD 100 3834 3433 L 52 52 53 0.18 7.9? 0 3 | 12.07.2004 07:50:59 FD 100 3693 3450 L 52 52 53 0.25 7.99 0 4 | -------------------------------------------------------------------------------- /tests/unit/data/fd12p/PW130121.DAT: -------------------------------------------------------------------------------- 1 | 21.01.2013 15:56 FD 100 1791 1862 S 72 72 72 0.43 71.36 350 2 | 21.01.2013 15:57 FD 100 1897 1844 S 72 72 72 0.40 71.36 351 3 | 21.01.2013 15:58 FD 101 ///// ///// /// // // // ////// ////// //// 4 | -------------------------------------------------------------------------------- /tests/unit/data/galileo/galileo-file-1.nc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/actris-cloudnet/cloudnetpy/7ca27b7f31018470fc60596063d51124b48ff6db/tests/unit/data/galileo/galileo-file-1.nc -------------------------------------------------------------------------------- /tests/unit/data/galileo/galileo-file-2.nc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/actris-cloudnet/cloudnetpy/7ca27b7f31018470fc60596063d51124b48ff6db/tests/unit/data/galileo/galileo-file-2.nc -------------------------------------------------------------------------------- /tests/unit/data/hatpro-iwv/21021700.IWV: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/actris-cloudnet/cloudnetpy/7ca27b7f31018470fc60596063d51124b48ff6db/tests/unit/data/hatpro-iwv/21021700.IWV -------------------------------------------------------------------------------- /tests/unit/data/hatpro-iwv/21021703.IWV: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/actris-cloudnet/cloudnetpy/7ca27b7f31018470fc60596063d51124b48ff6db/tests/unit/data/hatpro-iwv/21021703.IWV -------------------------------------------------------------------------------- /tests/unit/data/hatpro-lwp-iwv-2/21020600.IWV: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/actris-cloudnet/cloudnetpy/7ca27b7f31018470fc60596063d51124b48ff6db/tests/unit/data/hatpro-lwp-iwv-2/21020600.IWV -------------------------------------------------------------------------------- /tests/unit/data/hatpro-lwp-iwv-2/21020600.LWP: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/actris-cloudnet/cloudnetpy/7ca27b7f31018470fc60596063d51124b48ff6db/tests/unit/data/hatpro-lwp-iwv-2/21020600.LWP -------------------------------------------------------------------------------- /tests/unit/data/hatpro-lwp-iwv-2/21020601.LWP: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/actris-cloudnet/cloudnetpy/7ca27b7f31018470fc60596063d51124b48ff6db/tests/unit/data/hatpro-lwp-iwv-2/21020601.LWP -------------------------------------------------------------------------------- /tests/unit/data/hatpro-lwp-iwv/21060300.IWV: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/actris-cloudnet/cloudnetpy/7ca27b7f31018470fc60596063d51124b48ff6db/tests/unit/data/hatpro-lwp-iwv/21060300.IWV -------------------------------------------------------------------------------- /tests/unit/data/hatpro-lwp-iwv/21060300.LWP: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/actris-cloudnet/cloudnetpy/7ca27b7f31018470fc60596063d51124b48ff6db/tests/unit/data/hatpro-lwp-iwv/21060300.LWP -------------------------------------------------------------------------------- /tests/unit/data/hatpro-lwp-iwv/21060301.IWV: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/actris-cloudnet/cloudnetpy/7ca27b7f31018470fc60596063d51124b48ff6db/tests/unit/data/hatpro-lwp-iwv/21060301.IWV -------------------------------------------------------------------------------- /tests/unit/data/hatpro-lwp-iwv/21060301.LWP: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/actris-cloudnet/cloudnetpy/7ca27b7f31018470fc60596063d51124b48ff6db/tests/unit/data/hatpro-lwp-iwv/21060301.LWP -------------------------------------------------------------------------------- /tests/unit/data/hatpro-lwp/21012300.LWP: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/actris-cloudnet/cloudnetpy/7ca27b7f31018470fc60596063d51124b48ff6db/tests/unit/data/hatpro-lwp/21012300.LWP -------------------------------------------------------------------------------- /tests/unit/data/hatpro-lwp/21012323.LWP: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/actris-cloudnet/cloudnetpy/7ca27b7f31018470fc60596063d51124b48ff6db/tests/unit/data/hatpro-lwp/21012323.LWP -------------------------------------------------------------------------------- /tests/unit/data/hatpro-lwp/ZENITH_200723.LWP: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/actris-cloudnet/cloudnetpy/7ca27b7f31018470fc60596063d51124b48ff6db/tests/unit/data/hatpro-lwp/ZENITH_200723.LWP -------------------------------------------------------------------------------- /tests/unit/data/hatpro-lwp/cabauw-hatpro-20210726.LWP: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/actris-cloudnet/cloudnetpy/7ca27b7f31018470fc60596063d51124b48ff6db/tests/unit/data/hatpro-lwp/cabauw-hatpro-20210726.LWP -------------------------------------------------------------------------------- /tests/unit/data/hatpro-mwrpy/230401.BLB: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/actris-cloudnet/cloudnetpy/7ca27b7f31018470fc60596063d51124b48ff6db/tests/unit/data/hatpro-mwrpy/230401.BLB -------------------------------------------------------------------------------- /tests/unit/data/hatpro-mwrpy/230401.BRT: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/actris-cloudnet/cloudnetpy/7ca27b7f31018470fc60596063d51124b48ff6db/tests/unit/data/hatpro-mwrpy/230401.BRT -------------------------------------------------------------------------------- /tests/unit/data/hatpro-mwrpy/230401.HKD: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/actris-cloudnet/cloudnetpy/7ca27b7f31018470fc60596063d51124b48ff6db/tests/unit/data/hatpro-mwrpy/230401.HKD -------------------------------------------------------------------------------- /tests/unit/data/hatpro-mwrpy/230401.IRT: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/actris-cloudnet/cloudnetpy/7ca27b7f31018470fc60596063d51124b48ff6db/tests/unit/data/hatpro-mwrpy/230401.IRT -------------------------------------------------------------------------------- /tests/unit/data/hatpro-mwrpy/230401.MET: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/actris-cloudnet/cloudnetpy/7ca27b7f31018470fc60596063d51124b48ff6db/tests/unit/data/hatpro-mwrpy/230401.MET -------------------------------------------------------------------------------- /tests/unit/data/mira-masked/20231003_1400-trunc.mmclx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/actris-cloudnet/cloudnetpy/7ca27b7f31018470fc60596063d51124b48ff6db/tests/unit/data/mira-masked/20231003_1400-trunc.mmclx -------------------------------------------------------------------------------- /tests/unit/data/mira-nyquist/20200116_0000-trunc.mmclx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/actris-cloudnet/cloudnetpy/7ca27b7f31018470fc60596063d51124b48ff6db/tests/unit/data/mira-nyquist/20200116_0000-trunc.mmclx -------------------------------------------------------------------------------- /tests/unit/data/mira/20210102_0000.mmclx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/actris-cloudnet/cloudnetpy/7ca27b7f31018470fc60596063d51124b48ff6db/tests/unit/data/mira/20210102_0000.mmclx -------------------------------------------------------------------------------- /tests/unit/data/mira/20210102_1400.mmclx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/actris-cloudnet/cloudnetpy/7ca27b7f31018470fc60596063d51124b48ff6db/tests/unit/data/mira/20210102_1400.mmclx -------------------------------------------------------------------------------- /tests/unit/data/mira_inconsistent/file1.mmclx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/actris-cloudnet/cloudnetpy/7ca27b7f31018470fc60596063d51124b48ff6db/tests/unit/data/mira_inconsistent/file1.mmclx -------------------------------------------------------------------------------- /tests/unit/data/mira_inconsistent/file2.mmclx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/actris-cloudnet/cloudnetpy/7ca27b7f31018470fc60596063d51124b48ff6db/tests/unit/data/mira_inconsistent/file2.mmclx -------------------------------------------------------------------------------- /tests/unit/data/mira_stsr/20230201_0900_mbr7_stsr-trunc.znc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/actris-cloudnet/cloudnetpy/7ca27b7f31018470fc60596063d51124b48ff6db/tests/unit/data/mira_stsr/20230201_0900_mbr7_stsr-trunc.znc -------------------------------------------------------------------------------- /tests/unit/data/mira_znc/20230201_0900_mbr5-trunc.mmclx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/actris-cloudnet/cloudnetpy/7ca27b7f31018470fc60596063d51124b48ff6db/tests/unit/data/mira_znc/20230201_0900_mbr5-trunc.mmclx -------------------------------------------------------------------------------- /tests/unit/data/mira_znc/20230201_0900_mbr5-trunc.znc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/actris-cloudnet/cloudnetpy/7ca27b7f31018470fc60596063d51124b48ff6db/tests/unit/data/mira_znc/20230201_0900_mbr5-trunc.znc -------------------------------------------------------------------------------- /tests/unit/data/mrr/20220124_180000.nc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/actris-cloudnet/cloudnetpy/7ca27b7f31018470fc60596063d51124b48ff6db/tests/unit/data/mrr/20220124_180000.nc -------------------------------------------------------------------------------- /tests/unit/data/parsivel/ny-alesund.log: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/actris-cloudnet/cloudnetpy/7ca27b7f31018470fc60596063d51124b48ff6db/tests/unit/data/parsivel/ny-alesund.log -------------------------------------------------------------------------------- /tests/unit/data/parsivel/palaiseau.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/actris-cloudnet/cloudnetpy/7ca27b7f31018470fc60596063d51124b48ff6db/tests/unit/data/parsivel/palaiseau.txt -------------------------------------------------------------------------------- /tests/unit/data/parsivel/warsaw.txt: -------------------------------------------------------------------------------- 1 | 06.08.2021;00:00:00;0,750;88,69;62;RA;R;21,446;8931;22890;60;14;0,95;23,9;3,990;0;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;1;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;1;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;1;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;1;4;2;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;1;;3;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;2;2;3;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;1;4;5;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;1;3;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;2;4;2;;;;1;;;;;;;;;;;;;;;;;;;;;;;;;;;2;3;;;;1;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;2;2;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;2;1;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;1;1;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;1;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 2 | 3 | 06.08.2021;00:00:10;0,844;88,69;62;RA;R;23,147;10590;22894;59;14;0,04;23,9;7,010;0;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;1;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;1;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;1;1;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;3;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;2;1;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;1;2;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;2;2;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;3;1;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;3;4;3;2;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;3;3;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;5;2;1;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;1;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;1;;3;;1;;;;;;;;;;;;;;;;;;;;;;;;;;;;;1;;1;;3;1;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 4 | 5 | 06.08.2021;00:00:20;1,865;88,70;62;RA;R;27,129;6746;22736;104;14;0,00;23,9;25,090;0;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;1;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;1;1;1;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;1;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;4;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;3;4;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;2;2;1;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;1;9;7;1;1;;;;;;;;;;;;;;;;;;;;;;;;;;;;2;12;6;2;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;11;4;2;;;1;;;;;;;;;;;;;;;;;;;;;;;;;;;;6;1;;;2;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;2;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;1;2;;;2;;;;;;;;;;;;;;;;;;;;;;;;;;;;;2;;;1;2;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;1;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;1;1;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 6 | 7 | -------------------------------------------------------------------------------- /tests/unit/data/pollyxt/2021_09_17_Fri_CPV_00_00_31_att_bsc.nc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/actris-cloudnet/cloudnetpy/7ca27b7f31018470fc60596063d51124b48ff6db/tests/unit/data/pollyxt/2021_09_17_Fri_CPV_00_00_31_att_bsc.nc -------------------------------------------------------------------------------- /tests/unit/data/pollyxt/2021_09_17_Fri_CPV_00_00_31_vol_depol.nc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/actris-cloudnet/cloudnetpy/7ca27b7f31018470fc60596063d51124b48ff6db/tests/unit/data/pollyxt/2021_09_17_Fri_CPV_00_00_31_vol_depol.nc -------------------------------------------------------------------------------- /tests/unit/data/pollyxt/2021_09_17_Fri_CPV_06_00_31_att_bsc.nc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/actris-cloudnet/cloudnetpy/7ca27b7f31018470fc60596063d51124b48ff6db/tests/unit/data/pollyxt/2021_09_17_Fri_CPV_06_00_31_att_bsc.nc -------------------------------------------------------------------------------- /tests/unit/data/pollyxt/2021_09_17_Fri_CPV_06_00_31_vol_depol.nc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/actris-cloudnet/cloudnetpy/7ca27b7f31018470fc60596063d51124b48ff6db/tests/unit/data/pollyxt/2021_09_17_Fri_CPV_06_00_31_vol_depol.nc -------------------------------------------------------------------------------- /tests/unit/data/pollyxt/2021_09_17_Fri_CPV_12_00_31_att_bsc.nc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/actris-cloudnet/cloudnetpy/7ca27b7f31018470fc60596063d51124b48ff6db/tests/unit/data/pollyxt/2021_09_17_Fri_CPV_12_00_31_att_bsc.nc -------------------------------------------------------------------------------- /tests/unit/data/pollyxt/2021_09_17_Fri_CPV_12_00_31_vol_depol.nc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/actris-cloudnet/cloudnetpy/7ca27b7f31018470fc60596063d51124b48ff6db/tests/unit/data/pollyxt/2021_09_17_Fri_CPV_12_00_31_vol_depol.nc -------------------------------------------------------------------------------- /tests/unit/data/pollyxt/2021_09_17_Fri_CPV_18_00_31_att_bsc.nc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/actris-cloudnet/cloudnetpy/7ca27b7f31018470fc60596063d51124b48ff6db/tests/unit/data/pollyxt/2021_09_17_Fri_CPV_18_00_31_att_bsc.nc -------------------------------------------------------------------------------- /tests/unit/data/pollyxt/2021_09_17_Fri_CPV_18_00_31_vol_depol.nc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/actris-cloudnet/cloudnetpy/7ca27b7f31018470fc60596063d51124b48ff6db/tests/unit/data/pollyxt/2021_09_17_Fri_CPV_18_00_31_vol_depol.nc -------------------------------------------------------------------------------- /tests/unit/data/pollyxt/broken_1064_channel/truncated_2022_06_16_Thu_UWA_00_00_31_att_bsc.nc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/actris-cloudnet/cloudnetpy/7ca27b7f31018470fc60596063d51124b48ff6db/tests/unit/data/pollyxt/broken_1064_channel/truncated_2022_06_16_Thu_UWA_00_00_31_att_bsc.nc -------------------------------------------------------------------------------- /tests/unit/data/pollyxt/broken_1064_channel/truncated_2022_06_16_Thu_UWA_00_00_31_vol_depol.nc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/actris-cloudnet/cloudnetpy/7ca27b7f31018470fc60596063d51124b48ff6db/tests/unit/data/pollyxt/broken_1064_channel/truncated_2022_06_16_Thu_UWA_00_00_31_vol_depol.nc -------------------------------------------------------------------------------- /tests/unit/data/radiometrics/20100926_0005.los: -------------------------------------------------------------------------------- 1 | RETRIEVAL COEFFICIENTS: 2 | Vapor c0 = .005 c1 = 21.647 c2 = -12.897 3 | Liquid c0 = -.002 c1 = -.291 c2 = .622 4 | Mean atm temp vapor = 274.090 liquid = 270.700 5 | Cosmic background temp = 2.730 6 | Vapor to path delay conversion factor = 6.50 7 | Radio phase delay c0 = .057 c1 = 140.582 c2 = -82.016 8 | 9 | date time TbSky23 TbSky31 TkBB VapCM LiqCM DelCM AZact ELact Tau23 Tau31 Tamb Rh Pres Rain 10 | 09/26/10 00:06:18 56.70 35.85 296.99 3.104 .0156 20.41 .0 90.0 .2218 .1319 233.1 ****** 1024.0 -.01 11 | 09/26/10 00:06:47 66.93 44.42 296.98 3.668 .0247 24.14 .0 59.9 .2700 .1691 233.2 ****** 1024.0 .00 12 | 09/26/10 00:07:16 69.75 51.45 296.98 3.558 .0403 23.48 .0 120.2 .2837 .2007 233.1 ****** 1024.0 -.01 13 | 09/26/10 00:07:44 59.29 40.38 296.97 3.112 .0242 20.50 .0 90.0 .2337 .1514 233.1 ****** 1024.0 -.01 14 | 09/26/10 00:08:13 79.53 52.99 296.96 4.528 .0304 29.79 .0 45.0 .3327 .2077 233.1 ****** 1024.0 -.01 15 | 09/26/10 00:08:42 82.03 60.56 296.96 4.351 .0487 28.71 .0 135.0 .3456 .2431 233.1 ****** 1024.0 -.01 16 | -------------------------------------------------------------------------------- /tests/unit/data/radiometrics/20131220_1319.los: -------------------------------------------------------------------------------- 1 | RETRIEVAL COEFFICIENTS: 2 | Vapor c0 = .006 c1 = 21.424 c2 = -12.668 3 | Liquid c0 = -.003 c1 = -.387 c2 = .668 4 | Mean atm temp vapor = 264.740 liquid = 260.960 5 | Cosmic background temp = 2.730 6 | Vapor to path delay conversion factor = 6.50 7 | Radio phase delay c0 = .055 c1 = 144.304 c2 = -84.868 8 | 9 | date time TbSky23 TbSky31 TkBB VapCM LiqCM DelCM AZact ELact Tau23 Tau31 Tamb Rh Pres Rain 10 | 12/20/13 23:16:31 20.95 14.28 284.97 .970 .0002 6.57 .0 90.0 .0721 .0458 276.1 100.45 1024.0 .23 11 | 12/20/13 23:17:00 21.21 -669.66 284.96 17.813 .0000 119.41 .0 90.0 .0731-1.2820 276.1 100.47 1024.0 .23 12 | 12/20/13 23:17:29 21.39 13.69 284.96 1.040 .0000 7.04 .0 90.0 .0739 .0434 276.1 100.45 1024.0 .23 13 | -------------------------------------------------------------------------------- /tests/unit/data/radiometrics/20140106_1126.los: -------------------------------------------------------------------------------- 1 | RETRIEVAL COEFFICIENTS: 2 | Vapor c0 = .006 c1 = 21.367 c2 = -12.590 3 | Liquid c0 = -.003 c1 = -.393 c2 = .660 4 | Mean atm temp vapor = 263.450 liquid = 259.600 5 | Cosmic background temp = 2.730 6 | Vapor to path delay conversion factor = 6.50 7 | Radio phase delay c0 = .056 c1 = 144.178 c2 = -84.467 8 | 9 | date time TbSky23 TbSky31 TkBB VapCM LiqCM DelCM AZact ELact Tau23 Tau31 Tamb Rh Pres Rain 10 | 01/06/14 11:28:07 39.80 21.89 285.67 2.307 .0000 15.62 .0 90.0 .1534 .0775 278.7 100.45 1024.0 .23 11 | 01/06/14 11:28:36 44.37 24.98 285.68 2.583 .0000 17.49 .0 59.9 .1740 .0906 278.7 100.43 1024.0 .23 12 | 01/06/14 11:29:06 44.67 27.55 285.69 2.474 .0000 16.76 .0 120.2 .1754 .1016 278.7 100.45 1024.0 .23 13 | 01/06/14 11:29:35 38.64 20.86 285.69 2.250 .0000 15.24 .0 90.0 .1482 .0732 278.7 100.42 1024.0 .23 14 | 01/06/14 11:30:03 38.66 20.11 285.70 2.292 .0000 15.52 .0 90.0 .1483 .0701 278.8 100.48 1024.0 .23 15 | 01/06/14 11:30:32 38.91 21.34 285.70 2.251 .0000 15.24 .0 90.0 .1494 .0752 278.8 100.45 1024.0 .23 16 | 01/06/14 11:31:01 38.28 124.53 285.71 .000 .3640 .00 .0 90.0 .1466 .6427 278.8 100.43 1024.0 .23 17 | -------------------------------------------------------------------------------- /tests/unit/data/radiometrics/2021-07-18_00-00-00_lv2.csv: -------------------------------------------------------------------------------- 1 | Record,Date/Time,10,Tamb(K),Rh(%),Pres(mb),Tir(K),Rain,Vint(cm),Lqint(mm), 0.00, 0.10, 0.20, 0.30, 0.40, 0.50, 0.60, 0.70, 0.80, 0.90, 1.00, 1.25, 1.50, 1.75, 2.00, 2.25, 2.50, 2.75, 3.00, 3.25, 3.50, 3.75, 4.00, 4.25, 4.50, 4.75, 5.00, 5.25, 5.50, 5.75, 6.00, 6.25, 6.50, 6.75, 7.00, 7.25, 7.50, 7.75, 8.00, 8.25, 8.50, 8.75, 9.00, 9.25, 9.50, 9.75,10.00 2 | 1,07/18/21 00:01:12,11,297.0, 63.5, 999.6,263.4,N, 3.74, 0.03,297.0,294.5,293.8,293.5,293.0,292.7,292.2,291.7,291.3,290.7,290.1,288.5,286.7,284.9,283.0,281.4,279.5,277.7,275.9,274.4,272.9,271.4,269.8,268.2,266.6,265.0,263.4,262.2,261.0,259.7,258.4,256.9,255.3,253.4,251.6,249.7,247.8,245.8,244.0,242.0,240.0,238.2,236.3,234.4,232.5,230.6,228.9 3 | 2,07/18/21 00:01:12,12,297.0, 63.5, 999.6,263.4,N, 3.74, 0.03,13.81,12.66,12.16,11.91,11.48,11.23,11.21,11.39,10.86,10.58,10.15, 9.60, 8.61, 7.80, 7.14, 6.57, 5.98, 5.36, 4.81, 4.42, 4.56, 4.37, 4.27, 4.05, 3.53, 3.46, 3.19, 2.78, 2.28, 1.93, 1.59, 1.23, 0.88, 0.74, 0.57, 0.44, 0.31, 0.24, 0.16, 0.12, 0.09, 0.07, 0.05, 0.03, 0.03, 0.03, 0.02 4 | 3,07/18/21 00:01:12,13,297.0, 63.5, 999.6,263.4,N, 3.74, 0.03, 63.9, 67.6, 68.0, 67.7, 67.1, 66.8, 68.7, 72.0, 70.2, 71.0, 70.6, 73.2, 73.6, 74.3, 76.6, 78.2, 80.3, 81.6, 82.5, 83.5, 95.6,100.0,100.0,100.0,100.0,100.0,100.0,100.0,100.0,100.0, 97.6, 84.6, 69.1, 67.6, 60.3, 54.8, 45.9, 41.7, 33.2, 28.8, 26.9, 22.8, 20.6, 16.1, 20.7, 18.9, 21.5 5 | 4,07/18/21 00:01:12,14,297.0, 63.5, 999.6,263.4,N, 3.74, 0.03, 0.01, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.01, 0.01, 0.01, 0.02, 0.02, 0.03, 0.03, 0.05, 0.04, 0.03, 0.06, 0.04, 0.05, 0.07, 0.07, 0.04, 0.03, 0.03, 0.03, 0.02, 0.02, 0.02, 0.00, 0.01, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.01 6 | 5,07/18/21 00:06:19,11,297.0, 64.0, 999.6,265.1,N, 3.75, 0.03,297.0,294.7,293.7,293.2,292.5,292.0,291.4,290.8,290.3,289.6,289.0,287.5,285.7,284.1,282.4,280.9,279.2,277.6,275.9,274.6,273.2,271.8,270.3,268.8,267.2,265.7,264.2,263.1,261.9,260.6,259.3,257.8,256.2,254.4,252.6,250.7,248.9,247.0,245.1,243.1,241.1,239.4,237.4,235.5,233.6,231.7,230.0 7 | 6,07/18/21 00:06:19,12,297.0, 64.0, 999.6,265.1,N, 3.75, 0.03,13.82,12.78,12.34,12.21,11.96,11.83,11.84,12.03,11.53,11.25,10.79,10.11, 8.97, 7.99, 7.20, 6.52, 5.84, 5.21, 4.64, 4.24, 4.30, 4.11, 3.96, 3.73, 3.25, 3.13, 2.84, 2.46, 2.02, 1.73, 1.43, 1.10, 0.79, 0.67, 0.52, 0.40, 0.29, 0.23, 0.16, 0.12, 0.09, 0.07, 0.05, 0.03, 0.03, 0.03, 0.02 8 | 7,07/18/21 00:06:19,13,297.0, 64.0, 999.6,265.1,N, 3.75, 0.03, 64.3, 67.8, 69.2, 70.6, 71.9, 73.2, 76.1, 80.2, 79.2, 80.3, 79.9, 82.1, 81.4, 80.2, 80.5, 80.0, 80.0, 79.8, 79.4, 79.2, 88.4, 93.2,100.0,100.0,100.0,100.0,100.0,100.0, 94.4, 89.1, 81.7, 70.4, 57.7, 55.9, 50.2, 46.1, 39.1, 36.2, 30.1, 25.8, 24.2, 20.7, 18.7, 14.8, 18.6, 16.7, 18.8 9 | 8,07/18/21 00:06:19,14,297.0, 64.0, 999.6,265.1,N, 3.75, 0.03, 0.01, 0.00, 0.00, 0.01, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.01, 0.01, 0.01, 0.02, 0.03, 0.03, 0.03, 0.05, 0.04, 0.03, 0.06, 0.04, 0.06, 0.07, 0.07, 0.04, 0.03, 0.03, 0.03, 0.02, 0.02, 0.02, 0.00, 0.01, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.01 10 | -------------------------------------------------------------------------------- /tests/unit/data/rain_e_h3/20241231_raine_lindenberg.csv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/actris-cloudnet/cloudnetpy/7ca27b7f31018470fc60596063d51124b48ff6db/tests/unit/data/rain_e_h3/20241231_raine_lindenberg.csv -------------------------------------------------------------------------------- /tests/unit/data/rain_e_h3/Lindenberg_RainE_20230514.txt: -------------------------------------------------------------------------------- 1 | 14.05.2023;00:00:39;0.000;0.406;0;9.56;11.50;0;0;0;60;2582;rain[e]H3;Lindenberg ;MF ;ACTRIS 2 | 14.05.2023;00:01:39;0.000;0.406;0;9.50;11.44;0;0;0;60;2582;rain[e]H3;Lindenberg ;MF ;ACTRIS 3 | 14.05.2023;00:02:39;0.000;0.406;0;9.44;11.44;0;0;0;60;2582;rain[e]H3;Lindenberg ;MF ;ACTRIS 4 | 14.05.2023;00:03:39;0.000;0.406;0;9.37;11.37;0;0;0;60;2582;rain[e]H3;Lindenberg ;MF ;ACTRIS 5 | 14.05.2023;00:04:39;0.000;0.406;0;9.31;11.31;0;0;0;60;2582;rain[e]H3;Lindenberg ;MF ;ACTRIS 6 | 14.05.2023;00:05:39;0.000;0.406;0;9.25;11.31;0;0;0;60;2582;rain[e]H3;Lindenberg ;MF ;ACTRIS 7 | 14.05.2023;00:06:39;0.000;0.406;0;9.19;11.25;0;0;0;60;2582;rain[e]H3;Lindenberg ;MF ;ACTRIS 8 | 14.05.2023;00:07:39;0.000;0.406;0;9.12;11.19;0;0;0;60;2582;rain[e]H3;Lindenberg ;MF ;ACTRIS 9 | 14.05.2023;00:08:39;0.000;0.406;0;9.12;11.19;0;0;0;60;2582;rain[e]H3;Lindenberg ;MF ;ACTRIS 10 | 14.05.2023;00:09:39;0.000;0.406;0;9.06;11.12;0;0;0;60;2582;rain[e]H3;Lindenberg ;MF ;ACTRIS 11 | 14.05.2023;00:10:39;0.000;0.406;0;9.00;11.06;0;0;0;60;2582;rain[e]H3;Lindenberg ;MF ;ACTRIS 12 | 14.05.2023;00:11:39;0.000;0.406;0;9.06;11.06;0;0;0;60;2582;rain[e]H3;Lindenberg ;MF ;ACTRIS 13 | 14.05.2023;00:12:39;0.000;0.406;0;9.12;11.12;0;0;0;60;2582;rain[e]H3;Lindenberg ;MF ;ACTRIS 14 | 14.05.2023;00:13:39;0.000;0.406;0;9.06;11.12;0;0;0;60;2582;rain[e]H3;Lindenberg ;MF ;ACTRIS 15 | 14.05.2023;00:14:39;0.000;0.406;0;9.06;11.06;0;0;0;60;2582;rain[e]H3;Lindenberg ;MF ;ACTRIS 16 | 14.05.2023;00:15:39;0.000;0.406;0;9.06;11.06;0;0;0;60;2582;rain[e]H3;Lindenberg ;MF ;ACTRIS 17 | 14.05.2023;00:16:39;0.000;0.406;0;9.00;11.00;0;0;0;60;2582;rain[e]H3;Lindenberg ;MF ;ACTRIS 18 | 14.05.2023;00:17:39;0.000;0.406;0;8.94;10.94;0;0;0;60;2582;rain[e]H3;Lindenberg ;MF ;ACTRIS 19 | -------------------------------------------------------------------------------- /tests/unit/data/rpg-fmcw-94-corrupted/230401_000001_P00_ZEN.LV1: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/actris-cloudnet/cloudnetpy/7ca27b7f31018470fc60596063d51124b48ff6db/tests/unit/data/rpg-fmcw-94-corrupted/230401_000001_P00_ZEN.LV1 -------------------------------------------------------------------------------- /tests/unit/data/rpg-fmcw-94/201022_000002_P06_ZEN.LV1: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/actris-cloudnet/cloudnetpy/7ca27b7f31018470fc60596063d51124b48ff6db/tests/unit/data/rpg-fmcw-94/201022_000002_P06_ZEN.LV1 -------------------------------------------------------------------------------- /tests/unit/data/rpg-fmcw-94/201022_230001_P06_ZEN.LV1: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/actris-cloudnet/cloudnetpy/7ca27b7f31018470fc60596063d51124b48ff6db/tests/unit/data/rpg-fmcw-94/201022_230001_P06_ZEN.LV1 -------------------------------------------------------------------------------- /tests/unit/data/rpg-fmcw-94/201023_160000_P06_ZEN.LV1: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/actris-cloudnet/cloudnetpy/7ca27b7f31018470fc60596063d51124b48ff6db/tests/unit/data/rpg-fmcw-94/201023_160000_P06_ZEN.LV1 -------------------------------------------------------------------------------- /tests/unit/data/rpg-fmcw-94/BaseN_210913_001152_P01_PPI.LV1: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/actris-cloudnet/cloudnetpy/7ca27b7f31018470fc60596063d51124b48ff6db/tests/unit/data/rpg-fmcw-94/BaseN_210913_001152_P01_PPI.LV1 -------------------------------------------------------------------------------- /tests/unit/data/thies-lnm/2024041723-leipzig-lim.txt: -------------------------------------------------------------------------------- 1 | 00;0747;2.50;17.04.2024;23:00:50;00;00;NP ;000.000;00;00;NP ;000.000;000.000;000.000;0087.76;99999;-9.9;100;0.0;0;0; 2 | 00;0747;2.50;17.04.2024;23:01:50;00;00;NP ;000.000;00;00;NP ;000.000;000.000;000.000;0087.76;99999;-9.9;100;0.0;0;0; 3 | 00;0747;2.50;17.04.2024;23:02:50;00;00;NP ;000.000;00;00;NP ;000.000;000.000;000.000;0087.76;99999;-9.9;100;0.0;0;0; 4 | -------------------------------------------------------------------------------- /tests/unit/data/thies-lnm/20241003_lnm_lindenberg.csv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/actris-cloudnet/cloudnetpy/7ca27b7f31018470fc60596063d51124b48ff6db/tests/unit/data/thies-lnm/20241003_lnm_lindenberg.csv -------------------------------------------------------------------------------- /tests/unit/data/vaisala/cl51-corrupted-profile.cl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/actris-cloudnet/cloudnetpy/7ca27b7f31018470fc60596063d51124b48ff6db/tests/unit/data/vaisala/cl51-corrupted-profile.cl -------------------------------------------------------------------------------- /tests/unit/data/vaisala/ct25k.dat: -------------------------------------------------------------------------------- 1 | 2 | -2020-10-29 23:59:18 3 | CT02073 4 | 10 01220 ///// ///// 00000100 5 | 100 N 99 +22 85 200 +15 6 LF7HN1 172 6 | 0000008000C000A000A000900080007000600050004000400040003000300030003 7 | 0160003000400040004000400040005000400040006000500070005000600070008 8 | 0320009000C000E0012004B00260270084506EF028B0071000D0004FFFE00000000 9 | 048FFFFFFFF00010000000000000001000000000000FFFF00000000000100000000 10 | 0640001FFFF000000000001FFFF000000000000000000000001FFFE0001FFFE0004 11 | 080FFFE0001FFFF0000000100000000FFFF000100000000FFFF00010000FFFFFFFF 12 | 09600000000000000000003FFFF000000000000FFFE00010000000000000001FFFE 13 | 112000000020001FFFE000000000001000000000000000000000000FFFFFFFF0000 14 | 12800000000FFFF0000FFFF000100000001000200000000000000000000FFFE0000 15 | 1440000000000000000FFFE000100000000FFFF0001000100000000000000000000 16 | 160FFFF0000000000000001000100000000FFFF0000000000000001FFFD00020000 17 | 176000000010000FFFF0000000000000001FFFE00010001FFFF00010000FFFF0000 18 | 19200000001FFFF000000010000FFFE0000000100010000FFFF0000000000000001 19 | 208FFFF0000000000020000FFFE000000000001FFFE00000001FFFE00000002FFFF 20 | 2240000FFFF0003FFFD0000FFFF00000002000000000001000000000001FFFD0000 21 | 24000000000000000000000FFFE0002000000000000000000000000000000000000 22 | 8 104 0 /// 0 /// 0 /// 23 |  24 | 25 | -2020-10-29 23:59:33 26 | CT02073 27 | 10 01220 ///// ///// 00000100 28 | 100 N 99 +21 85 200 +15 6 LF7HN1 176 29 | 0000007000C000B000A000900080007000600040004000400030003000300030003 30 | 0160004000500040004000400050004000500050005000500070004000500080008 31 | 03200050009000B000A000A0015013E08D307CC0310008F00170001000000000001 32 | 048FFFD0002FFFF0000000200000002FFFF0000FFFF000100000001000000000000 33 | 0640000FFFF0002FFFF00000001000000000000FFFF000000000000FFFF00000000 34 | 08000000000000000000002FFFD0002000000020000000000000000000000000000 35 | 096FFFE0000FFFF00010000000000000000FFFF0001FFFF000200000000FFFFFFFF 36 | 112FFFF000100000001FFFE00010000000000000000FFFF0001FFFD000200020000 37 | 1280000000200010000FFFF0001000200000000FFFE0001FFFF0000FFFE00000000 38 | 144000100000000000000000000FFFF0001000000000000000000010001FFFF0002 39 | 160FFFF0000FFFE000000000001FFFF00000000000000000000000000000000FFFF 40 | 17600020001FFFF000000000003FFFF0000000000020000FFFF00020000FFFE0000 41 | 192FFFE00000000000000000000000000000000FFFF00020001FFFE000000000001 42 | 208FFFE00000000FFFF00000000000000000000000000000000000000010000FFFF 43 | 2240001FFFE0000FFFF0000FFFF000100000000FFFF00000000FFFF00000000FFFE 44 | 2400000FFFF0000FFFE000000000001000000000000000100000000000000000000 45 | 8 104 0 /// 0 /// 0 /// 46 |  47 | 48 | -2020-10-29 23:59:48 49 | CT02073 50 | 10 01190 ///// ///// 00000100 51 | 100 N 100 +21 85 200 +15 6 LF7HN1 168 52 | 0000005000C000B000A000A000A0009000700050004000400040003000300040003 53 | 0160005000300050003000400040004000400040005000600050007000500060006 54 | 03200080008000A000E0019006903D007890562026E00BB0030000E000200000000 55 | 048000000000001000000000000000000000001000000000000FFFF000100000000 56 | 06400010000FFFF000000000000000100000001FFFF0002FFFE00010000FFFFFFFF 57 | 08000000000000000000000000100000000FFFF0000000000000000000000000000 58 | 09600010002FFFFFFFFFFFF000000000000000000020000000000000002FFFD0001 59 | 112FFFF000100000000FFFE00000000FFFFFFFE0000000000000000000100000000 60 | 12800010000000000000000FFFF0000000000000001FFFE00010000FFFE0003FFFF 61 | 14400000000000000000000000000000001FFFF00000000FFFE00000000FFFF0000 62 | 16000000001FFFF000000000000FFFE0001FFFF000000000000FFFE000000000000 63 | 1760000000000000003FFFE0002000000000001000000000000FFFE000000010001 64 | 1920000000000000000FFFF00000002FFFF00000000FFFF00010001FFFF00000000 65 | 20800010000000000020000000000000000000000010000FFFF0000000000000001 66 | 22400000000000000000000000000010001FFFFFFFF0000FFFF0000000000010000 67 | 240000100000000000000000000FFFF0002FFFF0003FFFE00000000000000000000 68 | 8 104 0 /// 0 /// 0 /// 69 |  70 | -------------------------------------------------------------------------------- /tests/unit/data/ws/20250127_JOYCE_WST_01m.dat: -------------------------------------------------------------------------------- 1 | "TOA5","JOYCE_WST","CR300","16480","CR310.Std.10.07","CPU:joyce_20240403.CR300","19693","JOYCE_WST_01m" 2 | "TIMESTAMP","RECORD","AirTC_Avg","RH","WS_ms_S_WVT","WindDir_D1_WVT","WS_ms_Max","WSDiag","BV_BP_Avg","BV_Temp_Avg","BV_Qual_Min","BattV_Avg","PTemp_C_Avg","BV_BP_SL_Avg" 3 | "TS","RN","Deg C","%","meters/Second","Deg","meters/Second","unitless","hPa","deg C","unitless","Volts","Deg C","hPa" 4 | "","","Avg","Smp","WVc","WVc","Max","Smp","Avg","Avg","Min","Avg","Avg","Avg" 5 | "2025-01-27 00:01:00",429741,9.77,72.09,3.182,133.7,3.89,0,982.1,8.12,6.88,13.78,16.03,995.3639 6 | "2025-01-27 00:02:00",429742,9.78,72.16,2.84,141.7,3.39,0,982.1,8.14,6.87,13.78,16.05,995.3639 7 | "2025-01-27 00:03:00",429743,9.77,72.17,2.978,150.8,4.02,0,982.1,8.15,6.87,13.76,16.06,995.3639 8 | "2025-01-27 00:04:00",429744,9.75,72.18,2.032,131,3.34,0,982.0583,8.16,6.87,13.78,16.07,995.3223 9 | "2025-01-27 00:05:00",429745,9.75,72.22,2.335,140.6,2.79,0,982,8.17,6.88,13.78,16.08,995.2639 10 | "2025-01-27 00:06:00",429746,9.74,72.22,2.575,147.2,3.4,0,982,8.19,6.89,13.79,16.1,995.2639 11 | "2025-01-27 00:07:00",429747,9.74,72.23,2.626,137.4,3.57,0,981.925,8.2,6.88,13.78,16.11,995.189 12 | "2025-01-27 00:08:00",429748,9.74,72.25,3.228,138.3,4.23,0,981.9,8.21,6.88,13.81,16.12,995.1639 13 | "2025-01-27 00:09:00",429749,9.74,72.24,2.337,124.8,3.76,0,981.8667,8.22,6.88,13.82,16.13,995.1306 14 | -------------------------------------------------------------------------------- /tests/unit/data/ws/WeatherStation_20240427.csv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/actris-cloudnet/cloudnetpy/7ca27b7f31018470fc60596063d51124b48ff6db/tests/unit/data/ws/WeatherStation_20240427.csv -------------------------------------------------------------------------------- /tests/unit/data/ws/WeatherStation_20250413.csv: -------------------------------------------------------------------------------- 1 | # Weather data for: 2025-04-13 2 | # Data are 10 min averages of temperature, wind speed, humidity and rain, all at 2m AGL, and wind speed and direction at 10m AGL 3 | # Station location: TEPAK (34.677°N, 33.038°E) 4 | # For information concerning those files, contact email : george.kotsias@eratosthenes.org.cy 5 | # Col. 1 : Date Time (yyyy-mm-ddThh:mm:ss) 6 | # Col. 2 : Air temperature (°C) 7 | # Col. 3 : Wind speed at 2m (m/s) 8 | # Col. 4 : Relative humidity (%) 9 | # Col. 5 : Total precipitation (mm) 10 | # Col. 6 : Wind speed at 10m (m/s) 11 | # Col. 7 : Wind direction at 10m (degrees) 12 | # 01 02 03 04 05 06 07 13 | 14 | 2025-04-13T23:10:00 9.3 0.3 79 0.0 0.5 295.313 15 | 2025-04-13T23:20:00 9.2 0.3 77 0.0 0.9 290.655 16 | 2025-04-13T23:30:00 9.0 0.1 77 0.0 1.0 317.658 17 | 2025-04-13T23:40:00 8.8 0.1 77 0.0 0.8 330.625 18 | 2025-04-13T23:50:00 8.7 0.1 77 0.0 0.3 305.159 19 | -------------------------------------------------------------------------------- /tests/unit/data/ws/bad-ws.asc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/actris-cloudnet/cloudnetpy/7ca27b7f31018470fc60596063d51124b48ff6db/tests/unit/data/ws/bad-ws.asc -------------------------------------------------------------------------------- /tests/unit/data/ws/bad-ws2.asc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/actris-cloudnet/cloudnetpy/7ca27b7f31018470fc60596063d51124b48ff6db/tests/unit/data/ws/bad-ws2.asc -------------------------------------------------------------------------------- /tests/unit/data/ws/granada.dat: -------------------------------------------------------------------------------- 1 | "TOA5","CR1000XSeries","CR1000X","23644","CR1000X.Std.05.01","CPU:AGORA_UGR.CR1X","54351","meteo" 2 | "TIMESTAMP","RECORD","air_t_Avg","rh_Avg","pressure_Avg","wind_speed_avg","wind_dir_avg","wind_dir_std","rain_Tot" 3 | "TS","RN","degC","%","hPa","m/s","Deg","Deg","mm" 4 | "","","Avg","Avg","Avg","WVc","WVc","WVc","Tot" 5 | "2024-04-19 00:00:00",2439,16.85,63.06,939.9695,0.683,198.6,24.07,0 6 | "2024-04-19 00:01:00",2440,16.97,62.19,939.9655,0.404,189.9,7.254,0 7 | "2024-04-19 00:02:00",2441,17.03,61.72,939.9606,0.41,189.3,14.17,0 8 | "2024-04-19 00:03:00",2442,17.03,61.84,939.9526,0.321,194.6,12.48,0 9 | "2024-04-19 00:04:00",2443,17.1,61.42,939.9345,0.207,132.3,6.553,0 10 | "2024-04-19 00:05:00",2444,17.14,61.26,939.9221,0.135,150.3,13.61,0 11 | "2024-04-19 00:06:00",2445,17.13,61.2,939.9007,0.362,165.9,31.17,0 12 | "2024-04-19 00:07:00",2446,17.15,61.02,939.8865,0.717,141.7,30.42,0 13 | "2024-04-19 00:08:00",2447,17.22,60.83,939.8637,0.751,153.5,7.944,0 14 | "2024-04-19 00:09:00",2448,17.23,60.8,939.8492,1.248,150.4,8.3,0 15 | -------------------------------------------------------------------------------- /tests/unit/data/ws/lampedusa.rep: -------------------------------------------------------------------------------- 1 | 250119 000007,VALID,ICOS, 14.58, 59, 6.72, 1012.57, 10.7, 190, 9.5, 193, 0.0, 0.0, 2 | 250119 000107,VALID,ICOS, 14.59, 58, 6.34, 1012.54, 9.7, 194, 9.4, 193, 0.0, 0.0, 3 | 250119 000207,VALID,ICOS, 14.61, 58, 6.36, 1012.52, 8.6, 187, 9.4, 193, 0.0, 0.0, 4 | 250119 000307,VALID,ICOS, 14.70, 56, 6.03, 1012.50, 11.0, 186, 9.5, 193, 0.0, 0.0, 5 | 250119 000407,VALID,ICOS, 14.71, 55, 5.77, 1012.48, 10.3, 190, 9.5, 192, 0.0, 0.0, 6 | -------------------------------------------------------------------------------- /tests/unit/data/ws/palaiseau-ws.asc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/actris-cloudnet/cloudnetpy/7ca27b7f31018470fc60596063d51124b48ff6db/tests/unit/data/ws/palaiseau-ws.asc -------------------------------------------------------------------------------- /tests/unit/lidar_fun.py: -------------------------------------------------------------------------------- 1 | import netCDF4 2 | import numpy as np 3 | from numpy import ma 4 | 5 | 6 | class LidarFun: 7 | """Common tests for all lidars.""" 8 | 9 | def __init__(self, nc: netCDF4.Dataset, site_meta: dict, date: str, uuid): 10 | self.nc = nc 11 | self.site_meta = site_meta 12 | self.date = date 13 | self.uuid = uuid 14 | 15 | def test_data_types(self): 16 | for key in self.nc.variables.keys(): 17 | value = self.nc.variables[key].dtype 18 | assert value in ("float32", "float64"), f"{value} - {key}" 19 | 20 | def test_axis(self): 21 | assert self.nc.variables["range"].axis == "Z" 22 | for key in self.nc.variables.keys(): 23 | if key not in ("time", "range"): 24 | assert hasattr(self.nc.variables[key], "axis") is False 25 | 26 | def test_variable_values(self): 27 | assert 900 < self.nc.variables["wavelength"][:] < 1065 28 | assert 0 <= self.nc.variables["zenith_angle"][:] < 90 29 | assert np.all( 30 | ( 31 | self.nc.variables["height"][:] 32 | - self.site_meta["altitude"] 33 | - self.nc.variables["range"][:] 34 | ) 35 | <= 1e-3, 36 | ) 37 | assert ma.min(self.nc.variables["beta"][:]) > 0 38 | 39 | def test_global_attributes(self): 40 | assert self.nc.cloudnet_file_type == "lidar" 41 | -------------------------------------------------------------------------------- /tests/unit/radar_fun.py: -------------------------------------------------------------------------------- 1 | import netCDF4 2 | import numpy as np 3 | 4 | 5 | class RadarFun: 6 | """Common tests for all radars.""" 7 | 8 | def __init__(self, nc: netCDF4.Dataset, site_meta: dict, date: str, uuid): 9 | self.nc = nc 10 | self.site_meta = site_meta 11 | self.date = date 12 | self.uuid = uuid 13 | 14 | def test_variable_names(self): 15 | keys = { 16 | "Zh", 17 | "v", 18 | "radar_frequency", 19 | "range", 20 | "height", 21 | } 22 | for key in keys: 23 | assert key in self.nc.variables, key 24 | 25 | def test_axis(self): 26 | assert self.nc.variables["range"].axis == "Z" 27 | for key in self.nc.variables.keys(): 28 | if key not in ("time", "range"): 29 | assert hasattr(self.nc.variables[key], "axis") is False 30 | 31 | def test_variable_values(self): 32 | if "zenith_angle" in self.nc.variables: 33 | assert 0 <= np.all(self.nc.variables["zenith_angle"][:]) < 10 34 | assert np.all( 35 | ( 36 | self.nc.variables["height"][:] 37 | - self.site_meta["altitude"] 38 | - self.nc.variables["range"][:] 39 | ) 40 | <= 1e-3, 41 | ) 42 | 43 | def test_global_attributes(self): 44 | assert self.nc.cloudnet_file_type == "radar" 45 | -------------------------------------------------------------------------------- /tests/unit/test_atmos.py: -------------------------------------------------------------------------------- 1 | """This module contains unit tests for atmos-module.""" 2 | 3 | import numpy as np 4 | import pytest 5 | from numpy import ma 6 | from numpy.testing import assert_array_almost_equal, assert_array_equal 7 | 8 | from cloudnetpy.categorize import atmos_utils 9 | 10 | 11 | @pytest.mark.parametrize("t, p, res", [(270, 85513, 0.001415 * 1e-3)]) 12 | def test_calc_lwc_change_rate(t, p, res): 13 | myres = atmos_utils.calc_lwc_change_rate(np.array(t), np.array(p)) 14 | assert_array_almost_equal(res, myres, decimal=4) 15 | 16 | 17 | def test_find_cloud_bases(): 18 | x = np.array([[0, 1, 1, 1], [0, 0, 1, 0], [1, 0, 1, 0], [1, 1, 0, 0], [0, 0, 0, 1]]) 19 | b = np.array([[0, 1, 0, 0], [0, 0, 1, 0], [1, 0, 1, 0], [1, 0, 0, 0], [0, 0, 0, 1]]) 20 | assert_array_almost_equal(atmos_utils.find_cloud_bases(x), b) 21 | 22 | 23 | def test_find_cloud_tops(): 24 | x = np.array([[0, 1, 1, 1], [0, 0, 1, 0], [1, 0, 1, 0], [1, 1, 0, 0], [1, 1, 0, 1]]) 25 | b = np.array([[0, 0, 0, 1], [0, 0, 1, 0], [1, 0, 1, 0], [0, 1, 0, 0], [0, 1, 0, 1]]) 26 | assert_array_almost_equal(atmos_utils.find_cloud_tops(x), b) 27 | 28 | 29 | def test_find_lowest_cloud_bases(): 30 | cloud_mask = np.array( 31 | [ 32 | [0, 1, 0, 1], 33 | [0, 1, 1, 0], 34 | [1, 0, 1, 1], 35 | [1, 0, 0, 0], 36 | [0, 0, 0, 0], 37 | [1, 1, 1, 1], 38 | [0, 0, 1, 1], 39 | ], 40 | ) 41 | height = np.array([1.0, 2.0, 3.0, 4.0]) 42 | expected = ma.array([2.0, 2.0, 1.0, 1.0, 0.0, 1.0, 3.0], mask=[0, 0, 0, 0, 1, 0, 0]) 43 | result = atmos_utils.find_lowest_cloud_bases(cloud_mask, height) 44 | assert_array_almost_equal(result.data, expected.data) 45 | assert_array_almost_equal(result.mask, expected.mask) 46 | 47 | 48 | def test_find_highest_cloud_tops(): 49 | cloud_mask = np.array( 50 | [ 51 | [0, 1, 0, 1], 52 | [0, 1, 1, 0], 53 | [1, 0, 1, 1], 54 | [1, 0, 0, 0], 55 | [0, 0, 0, 0], 56 | [1, 1, 1, 1], 57 | [0, 0, 1, 1], 58 | ], 59 | ) 60 | height = np.array([1.0, 2.0, 3.0, 4.0]) 61 | expected = ma.array([4.0, 3.0, 4.0, 1.0, 0.0, 4.0, 4.0], mask=[0, 0, 0, 0, 1, 0, 0]) 62 | result = atmos_utils.find_highest_cloud_tops(cloud_mask, height) 63 | assert_array_almost_equal(result.data, expected.data) 64 | assert_array_almost_equal(result.mask, expected.mask) 65 | 66 | 67 | def test_distribute_lwp_to_liquid_clouds(): 68 | lwc = np.array([[1, 1, 1], [2, 2, 2]]) 69 | lwp = np.array([2.0, 8]) 70 | height = np.array([1, 2, 3]) 71 | result = atmos_utils.normalize_lwc_by_lwp(lwc, lwp, height) 72 | correct = [[2 / 3, 2 / 3, 2 / 3], [16 / 6, 16 / 6, 16 / 6]] 73 | assert_array_equal(result, correct) 74 | -------------------------------------------------------------------------------- /tests/unit/test_atmos_utils.py: -------------------------------------------------------------------------------- 1 | """This module contains unit tests for atmos-module.""" 2 | 3 | import numpy as np 4 | import pytest 5 | from numpy.testing import assert_array_almost_equal 6 | 7 | from cloudnetpy.categorize import atmos_utils 8 | 9 | 10 | @pytest.mark.parametrize( 11 | "t, res", 12 | [ 13 | (300, 3533), 14 | (280, 991), 15 | ], 16 | ) 17 | def test_saturation_vapor_pressure(t, res): 18 | """Unit tests for atmos.saturation_vapor_pressure().""" 19 | cnet = atmos_utils.calc_saturation_vapor_pressure(np.array(t)) 20 | assert_array_almost_equal(cnet, res, decimal=0) 21 | 22 | 23 | @pytest.mark.parametrize( 24 | "t, p, q, res", 25 | [ 26 | (280, 101330, 0.001, 273.21), 27 | (250, 90000, 0.001, 251.03), 28 | ], 29 | ) 30 | def test_wet_bulb(t, p, q, res): 31 | """Unit tests for atmos.wet_bulb().""" 32 | model = { 33 | "temperature": np.array(t), 34 | "pressure": np.array(p), 35 | "q": np.array(q), 36 | } 37 | cnet = atmos_utils.calc_wet_bulb_temperature(model) 38 | assert_array_almost_equal(cnet / 10, res / 10, decimal=1) 39 | 40 | 41 | def test_calc_adiabatic_lwc(): 42 | lwc_dz = np.array([[0, 0, 2.1, 2.1, 0, 3.2, 3.2], [0, 2.0, 2.0, 0, 1.5, 1.5, 0]]) 43 | 44 | height = np.array([10, 12, 14, 16, 18, 20, 22]) 45 | 46 | adiabatic_lwc = atmos_utils.calc_adiabatic_lwc(lwc_dz, height) 47 | 48 | expected = np.array([[0, 0, 4.2, 8.4, 0, 6.4, 12.8], [0, 4, 8, 0, 3, 6, 0]]) 49 | 50 | assert_array_almost_equal(adiabatic_lwc, expected, decimal=1) 51 | -------------------------------------------------------------------------------- /tests/unit/test_basta.py: -------------------------------------------------------------------------------- 1 | from os import path 2 | from tempfile import TemporaryDirectory 3 | 4 | import netCDF4 5 | import pytest 6 | 7 | from cloudnetpy.exceptions import ValidTimeStampError 8 | from cloudnetpy.instruments import basta2nc 9 | from tests.unit.all_products_fun import Check 10 | from tests.unit.radar_fun import RadarFun 11 | 12 | SCRIPT_PATH = path.dirname(path.realpath(__file__)) 13 | filename = f"{SCRIPT_PATH}/data/basta/basta_1a_cldradLz1R025m_v03_20210827_000000.nc" 14 | 15 | 16 | class TestBASTA(Check): 17 | site_meta = { 18 | "name": "Palaiseau", 19 | "latitude": 48.717, 20 | "longitude": 2.209, 21 | "altitude": 158, 22 | } 23 | date = "2021-08-27" 24 | temp_dir = TemporaryDirectory() 25 | temp_path = temp_dir.name + "/test.nc" 26 | uuid = basta2nc(filename, temp_path, site_meta) 27 | 28 | def test_variable_names(self): 29 | keys = { 30 | "Zh", 31 | "v", 32 | "time", 33 | "range", 34 | "radar_frequency", 35 | "height", 36 | "nyquist_velocity", 37 | "latitude", 38 | "longitude", 39 | "altitude", 40 | "zenith_angle", 41 | "radar_pitch", 42 | "radar_yaw", 43 | "radar_roll", 44 | } 45 | assert set(self.nc.variables.keys()) == keys 46 | 47 | def test_variables(self): 48 | assert self.nc.variables["radar_frequency"][:].data == 95.0 # Hard coded 49 | 50 | def test_common_radar(self): 51 | radar_fun = RadarFun(self.nc, self.site_meta, self.date, self.uuid) 52 | for name, method in RadarFun.__dict__.items(): 53 | if "test_" in name: 54 | getattr(radar_fun, name)() 55 | 56 | def test_geolocation_from_source_file(self, tmp_path): 57 | test_path = tmp_path / "geo.nc" 58 | meta_without_geolocation = {"name": "Kumpula"} 59 | basta2nc(filename, test_path, meta_without_geolocation) 60 | with netCDF4.Dataset(test_path) as nc: 61 | for key in ("latitude", "longitude", "altitude"): 62 | assert key in nc.variables 63 | assert nc.variables[key][:] > 0 64 | 65 | def test_global_attributes(self): 66 | assert self.nc.source == "BASTA" 67 | assert self.nc.title == "BASTA cloud radar from Palaiseau" 68 | 69 | def test_wrong_date_validation(self, tmp_path): 70 | with pytest.raises(ValidTimeStampError): 71 | basta2nc( 72 | filename, 73 | tmp_path / "invalid.nc", 74 | self.site_meta, 75 | date="2021-01-04", 76 | ) 77 | 78 | def test_uuid_from_user(self, tmp_path): 79 | test_path = tmp_path / "uuid.nc" 80 | uuid_from_user = "kissa" 81 | uuid = basta2nc(filename, test_path, self.site_meta, uuid=uuid_from_user) 82 | with netCDF4.Dataset(test_path) as nc: 83 | assert nc.file_uuid == uuid_from_user 84 | assert uuid == uuid_from_user 85 | -------------------------------------------------------------------------------- /tests/unit/test_bowtie.py: -------------------------------------------------------------------------------- 1 | from os import path 2 | from tempfile import TemporaryDirectory 3 | 4 | import numpy as np 5 | import pytest 6 | 7 | from cloudnetpy.exceptions import ValidTimeStampError 8 | from cloudnetpy.instruments import bowtie2nc 9 | from tests.unit.all_products_fun import Check 10 | from tests.unit.radar_fun import RadarFun 11 | 12 | SCRIPT_PATH = path.dirname(path.realpath(__file__)) 13 | FILEPATH = f"{SCRIPT_PATH}/data/bowtie/bowtie-trunc.nc" 14 | 15 | 16 | class TestBowtie2nc(Check): 17 | site_meta = { 18 | "altitude": 16, 19 | "latitude": 6.1, 20 | "longitude": -25.9, 21 | "name": "RV Meteor", 22 | } 23 | temp_dir = TemporaryDirectory() 24 | temp_path = temp_dir.name + "/bowtie.nc" 25 | uuid = bowtie2nc(FILEPATH, temp_path, site_meta) 26 | date = "2024-08-22" 27 | 28 | def test_variables(self): 29 | assert np.isclose( 30 | self.nc.variables["radar_frequency"][:].data, 31 | 94, 32 | ) 33 | 34 | def test_common_radar(self): 35 | radar_fun = RadarFun(self.nc, self.site_meta, self.date, self.uuid) 36 | for name, method in RadarFun.__dict__.items(): 37 | if "test_" in name: 38 | getattr(radar_fun, name)() 39 | 40 | def test_global_attributes(self): 41 | assert self.nc.source == "RPG-Radiometer Physics RPG-FMCW-94" 42 | assert self.nc.title == f'RPG-FMCW-94 cloud radar from {self.site_meta["name"]}' 43 | 44 | def test_range(self): 45 | for key in ("range", "height"): 46 | assert np.all(self.nc.variables[key][:] > 0) 47 | 48 | def test_correct_date_validation(self, tmp_path): 49 | test_path = tmp_path / "date.nc" 50 | bowtie2nc(FILEPATH, test_path, self.site_meta, date=self.date) 51 | 52 | def test_wrong_date_validation(self, tmp_path): 53 | test_path = tmp_path / "invalid.nc" 54 | with pytest.raises(ValidTimeStampError): 55 | bowtie2nc( 56 | FILEPATH, 57 | test_path, 58 | self.site_meta, 59 | date="2021-01-03", 60 | ) 61 | -------------------------------------------------------------------------------- /tests/unit/test_categorize.py: -------------------------------------------------------------------------------- 1 | import os 2 | from tempfile import TemporaryDirectory 3 | 4 | import netCDF4 5 | 6 | from cloudnetpy.categorize import generate_categorize 7 | from cloudnetpy.instruments import ceilo2nc, mira2nc 8 | from tests.unit.all_products_fun import Check 9 | 10 | SCRIPT_PATH = os.path.dirname(os.path.realpath(__file__)) 11 | filepath = f"{SCRIPT_PATH}/../source_data" 12 | 13 | 14 | class TestCategorize(Check): 15 | date = "2021-11-20" 16 | site_meta = { 17 | "name": "Munich", 18 | "altitude": 538, 19 | "latitude": 48.5, 20 | "longitude": 11.5, 21 | } 22 | 23 | temp_dir = TemporaryDirectory() 24 | radar_path = temp_dir.name + "/radar.nc" 25 | lidar_path = temp_dir.name + "/lidar.nc" 26 | 27 | uuid_radar = mira2nc(f"{filepath}/raw_mira_radar.mmclx", radar_path, site_meta) 28 | uuid_lidar = ceilo2nc(f"{filepath}/raw_chm15k_lidar.nc", lidar_path, site_meta) 29 | 30 | input_files = { 31 | "radar": radar_path, 32 | "lidar": lidar_path, 33 | "mwr": f"{filepath}/hatpro_mwr.nc", 34 | "model": f"{filepath}/ecmwf_model.nc", 35 | } 36 | 37 | temp_path = temp_dir.name + "/categorize.nc" 38 | uuid = generate_categorize(input_files, temp_path) 39 | 40 | def test_global_attributes(self): 41 | with netCDF4.Dataset(self.temp_path) as nc: 42 | assert nc.title == "Cloud categorization products from Munich" 43 | 44 | def test_categorize_without_lwp(self): 45 | input_files_without_mwr = self.input_files.copy() 46 | del input_files_without_mwr["mwr"] 47 | temp_path = self.temp_dir.name + "/categorize_without_lwp.nc" 48 | generate_categorize(input_files_without_mwr, temp_path) 49 | -------------------------------------------------------------------------------- /tests/unit/test_ceilo.py: -------------------------------------------------------------------------------- 1 | """This module contains unit tests for ceilo-module.""" 2 | 3 | from os import path 4 | 5 | import netCDF4 6 | import pytest 7 | from numpy.testing import assert_almost_equal 8 | 9 | from cloudnetpy.instruments import ceilo 10 | 11 | SCRIPT_PATH = path.dirname(path.realpath(__file__)) 12 | 13 | 14 | def test_find_ceilo_model_jenoptik(): 15 | file = f"{SCRIPT_PATH}/data/chm15k/00100_A202010220005_CHM170137.nc" 16 | assert ceilo._find_ceilo_model(file) == "chm15k" 17 | 18 | 19 | def test_find_ceilo_model_cl61d(): 20 | file = f"{SCRIPT_PATH}/data/cl61d/live_20210829_224520.nc" 21 | assert ceilo._find_ceilo_model(file) == "cl61d" 22 | 23 | 24 | @pytest.mark.parametrize( 25 | "fix, result", 26 | [ 27 | ("CL01", "cl31_or_cl51"), 28 | ("CL02", "cl31_or_cl51"), 29 | ("CT02", "ct25k"), 30 | ], 31 | ) 32 | def test_find_ceilo_model_vaisala(fix, result, tmpdir): 33 | file_name = "/".join((str(tmpdir), "ceilo.txt")) 34 | f = open(file_name, "w") 35 | f.write("row\n") 36 | f.write("\n") 37 | f.write("-2020-04-10 00:00:58\n") 38 | f.write(f"\x01{fix}\n") 39 | f.close() 40 | assert ceilo._find_ceilo_model(str(file_name)) == result 41 | 42 | 43 | def test_cl51_reading(tmp_path): 44 | output_file = tmp_path / "cl51.nc" 45 | file = f"{SCRIPT_PATH}/data/vaisala/cl51.DAT" 46 | ceilo.ceilo2nc(file, output_file, {"name": "Norunda", "altitude": 100}) 47 | with netCDF4.Dataset(output_file) as nc: 48 | assert nc.source == "Vaisala CL51" 49 | assert nc.cloudnet_file_type == "lidar" 50 | assert nc.location == "Norunda" 51 | assert nc.year == "2020" 52 | assert nc.month == "11" 53 | assert nc.day == "15" 54 | assert nc.variables["time"].shape == (2,) 55 | assert nc.variables["zenith_angle"][:].all() < 5 56 | assert_almost_equal(nc.variables["altitude"][:], 100) 57 | 58 | 59 | def test_cl31_reading(tmp_path): 60 | output_file = tmp_path / "cl31.nc" 61 | file = f"{SCRIPT_PATH}/data/vaisala/cl31.DAT" 62 | ceilo.ceilo2nc(file, output_file, {"name": "Norunda", "altitude": 100}) 63 | with netCDF4.Dataset(output_file) as nc: 64 | assert nc.source == "Vaisala CL31" 65 | assert_almost_equal(nc.variables["wavelength"][:], 910) 66 | -------------------------------------------------------------------------------- /tests/unit/test_classification.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from numpy.testing import assert_array_equal 3 | 4 | from cloudnetpy.products import classification 5 | from cloudnetpy.products.product_tools import CategoryBits, QualityBits 6 | 7 | 8 | class CategorizeBits: 9 | def __init__(self): 10 | self.category_bits = CategoryBits( 11 | droplet=np.asarray([[1, 0, 1, 1, 1, 1], [0, 1, 1, 1, 0, 0]], dtype=bool), 12 | falling=np.asarray([[0, 0, 0, 0, 1, 0], [0, 0, 0, 1, 1, 1]], dtype=bool), 13 | freezing=np.asarray([[0, 0, 1, 1, 0, 0], [0, 1, 1, 1, 0, 1]], dtype=bool), 14 | melting=np.asarray([[1, 0, 1, 0, 0, 0], [1, 1, 0, 0, 0, 0]], dtype=bool), 15 | aerosol=np.asarray([[1, 0, 1, 0, 0, 0], [0, 0, 0, 0, 0, 0]], dtype=bool), 16 | insect=np.asarray([[0, 1, 0, 0, 0, 0], [0, 0, 1, 0, 0, 0]], dtype=bool), 17 | ) 18 | self.quality_bits = QualityBits( 19 | radar=np.asarray([[0, 0, 0, 1, 1, 1], [1, 0, 0, 1, 1, 1]], dtype=bool), 20 | lidar=np.asarray([[1, 1, 1, 1, 0, 0], [1, 1, 0, 1, 1, 0]], dtype=bool), 21 | clutter=np.asarray([[0, 0, 1, 1, 0, 0], [0, 0, 0, 0, 0, 0]], dtype=bool), 22 | molecular=np.asarray( 23 | [[1, 0, 0, 1, 0, 0], [0, 1, 0, 0, 0, 0]], 24 | dtype=bool, 25 | ), 26 | attenuated_liquid=np.asarray( 27 | [[1, 1, 1, 0, 0, 1], [1, 1, 1, 0, 0, 0]], 28 | dtype=bool, 29 | ), 30 | corrected_liquid=np.asarray( 31 | [[1, 0, 0, 0, 0, 0], [1, 1, 0, 0, 0, 0]], 32 | dtype=bool, 33 | ), 34 | attenuated_rain=np.zeros((2, 6), dtype=bool), 35 | corrected_rain=np.zeros((2, 6), dtype=bool), 36 | attenuated_melting=np.zeros((2, 6), dtype=bool), 37 | corrected_melting=np.zeros((2, 6), dtype=bool), 38 | ) 39 | 40 | 41 | def test_get_target_classification(): 42 | bits = CategorizeBits() 43 | target_classification = classification._get_target_classification(bits) # type: ignore 44 | expected = np.array([[8, 9, 8, 0, 3, 1], [6, 7, 9, 5, 2, 4]]) 45 | assert_array_equal(target_classification, expected) 46 | 47 | 48 | def test_get_detection_status(): 49 | bits = CategorizeBits() 50 | detection_status = classification._get_detection_status(bits) # type: ignore 51 | expected = np.array([[9, 4, 8, 8, 5, 2], [7, 9, 4, 3, 3, 5]]) 52 | assert_array_equal(detection_status, expected) 53 | 54 | 55 | def test_find_cloud_mask(): 56 | target_classification = np.array([0, 1, 2, 3, 4, 5, 6, 7]) 57 | cloud_mask = classification._find_cloud_mask(target_classification) 58 | expected = np.array([0, 1, 0, 1, 1, 1, 0, 0]) 59 | assert_array_equal(cloud_mask, expected) 60 | -------------------------------------------------------------------------------- /tests/unit/test_datasource.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from numpy.testing import assert_array_equal 3 | 4 | from cloudnetpy.datasource import DataSource 5 | 6 | 7 | class TestDataSource: 8 | @pytest.fixture(autouse=True) 9 | def init_tests(self, nc_file): 10 | self.obj = DataSource(nc_file) 11 | 12 | def test_init_altitude(self, file_metadata): 13 | assert self.obj.altitude == file_metadata["altitude_km"] * 1000 14 | 15 | def test_getvar(self, test_array): 16 | assert_array_equal(self.obj.getvar("model_height"), test_array) 17 | 18 | def test_get_date(self): 19 | assert_array_equal(self.obj.get_date(), ["2019", "05", "23"]) 20 | 21 | def test_getvar_missing(self): 22 | with pytest.raises(RuntimeError): 23 | self.obj.getvar("not_existing_variable") 24 | 25 | def test_init_time(self, test_array): 26 | assert_array_equal(self.obj.time, test_array) 27 | 28 | def test_close(self): 29 | assert self.obj.dataset.isopen() is True 30 | self.obj.close() 31 | assert self.obj.dataset.isopen() is False 32 | 33 | def test_to_m(self, test_array): 34 | alt = self.obj.dataset.variables["range"] 35 | assert_array_equal(self.obj.to_m(alt), test_array * 1000) 36 | 37 | def test_to_km(self, test_array): 38 | alt = self.obj.dataset.variables["height"] 39 | assert_array_equal(self.obj.to_km(alt), test_array / 1000) 40 | 41 | def test_get_height_m(self, test_array): 42 | assert self.obj.height is not None 43 | assert_array_equal(self.obj.height, test_array) 44 | -------------------------------------------------------------------------------- /tests/unit/test_drizzle_error.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import pytest 3 | from numpy import testing 4 | 5 | from cloudnetpy.products import drizzle_error as de 6 | 7 | DRIZZLE_PARAMETERS = { 8 | "Do": np.array([[0.0001, 0.01, 0.000001], [0.001, 0.000001, 0.0001]]), 9 | } 10 | DRIZZLE_INDICES = { 11 | "drizzle": np.array([[1, 1, 1], [1, 1, 1]], dtype=bool), 12 | "small": np.array([[1, 0, 0], [0, 0, 1]], dtype=bool), 13 | "tiny": np.array([[0, 0, 1], [0, 1, 0]], dtype=bool), 14 | } 15 | ERROR_INPUT = (np.array([[0.01, 0.34, 0.5], [0.2, 0.3, 0.56]]), 0.14) 16 | BIAS_INPUT = (0.01, 0.57) 17 | 18 | 19 | @pytest.mark.parametrize( 20 | "key, value", 21 | [ 22 | ("drizzle", [False, True, True, True]), 23 | ("small", [False, True, False, False]), 24 | ("tiny", [False, False, False, True]), 25 | ], 26 | ) 27 | def test_get_drizzle_indices(key, value): 28 | dia = np.array([-1, 2 * 1e-5, 1, 1e-6]) 29 | d = de._get_drizzle_indices(dia) 30 | testing.assert_array_equal(d[key], value) 31 | 32 | 33 | @pytest.mark.parametrize( 34 | "key", 35 | ["Do_error", "drizzle_lwc_error", "drizzle_lwf_error", "S_error"], 36 | ) 37 | def test_calc_parameter_errors(key): 38 | x = de._calc_parameter_errors(DRIZZLE_INDICES, ERROR_INPUT) 39 | assert key in x.keys() 40 | 41 | 42 | @pytest.mark.parametrize("key", ["Do_bias", "drizzle_lwc_bias", "drizzle_lwf_bias"]) 43 | def test_calc_parameter_biases(key): 44 | x = de._calc_parameter_biases(BIAS_INPUT) 45 | assert key in x.keys() 46 | 47 | 48 | @pytest.fixture() 49 | def results(): 50 | errors = de._calc_parameter_errors(DRIZZLE_INDICES, ERROR_INPUT) 51 | biases = de._calc_parameter_biases(BIAS_INPUT) 52 | return {**errors, **biases} 53 | 54 | 55 | @pytest.mark.parametrize("key", ["drizzle_N_error", "v_drizzle_error", "mu_error"]) 56 | def test_add_supplementary_errors(results, key): 57 | x = de._add_supplementary_errors(results, DRIZZLE_INDICES, ERROR_INPUT) 58 | assert key in x.keys() 59 | 60 | 61 | def test_calc_v_error(results): 62 | results["Do_error"] = np.array([[2, 2, 2], [2, 2, 2]]) 63 | x = de._add_supplementary_errors(results, DRIZZLE_INDICES, ERROR_INPUT) 64 | testing.assert_almost_equal(x["v_drizzle_error"][DRIZZLE_INDICES["tiny"]], 4) 65 | 66 | 67 | @pytest.mark.parametrize("key", ["drizzle_N_bias", "v_drizzle_bias"]) 68 | def test_add_supplementary_biases(results, key): 69 | x = de._add_supplementary_biases(results, BIAS_INPUT) 70 | assert key in x.keys() 71 | 72 | 73 | def test_calc_error(): 74 | from cloudnetpy.utils import l2norm_weighted 75 | 76 | expected = l2norm_weighted(ERROR_INPUT, 1, (1,)) 77 | testing.assert_almost_equal(de._calc_error(1, (1,), ERROR_INPUT), expected) 78 | 79 | 80 | def test_stack_errors(): 81 | DRIZZLE_INDICES["drizzle"] = np.array([[0, 1, 1], [1, 1, 0]], dtype=bool) 82 | expected = np.ma.array(ERROR_INPUT[0], mask=[[1, 0, 0], [0, 0, 1]]) 83 | x = de._stack_errors(ERROR_INPUT[0], DRIZZLE_INDICES) 84 | testing.assert_array_almost_equal(x, expected) 85 | 86 | 87 | @pytest.mark.parametrize( 88 | "x, result", 89 | [ 90 | (-1000, -1), 91 | (-100, -0.99999), 92 | (-10, -0.9), 93 | (1000, np.exp(100 / 10 * np.log(10)) - 1), 94 | ], 95 | ) 96 | def test_db2lin(x, result): 97 | testing.assert_array_almost_equal(de.db2lin(x), result, decimal=3) 98 | 99 | 100 | @pytest.mark.parametrize("x, result", [(1e6, 60), (1e5, 50), (1e4, 40), (-100.0, -10)]) 101 | def test_lin2db(x, result): 102 | testing.assert_array_almost_equal(de.lin2db(x), result, decimal=3) 103 | -------------------------------------------------------------------------------- /tests/unit/test_epsilon.py: -------------------------------------------------------------------------------- 1 | import glob 2 | from os import path 3 | from tempfile import TemporaryDirectory 4 | from pathlib import Path 5 | 6 | import netCDF4 7 | 8 | from cloudnetpy.products.epsilon import generate_epsilon_from_lidar 9 | from tests.unit.all_products_fun import Check 10 | 11 | SCRIPT_PATH = path.dirname(path.realpath(__file__)) 12 | data_dir = Path(f"{SCRIPT_PATH}/data/epsilon/") 13 | 14 | 15 | class TestEpsilonLidar(Check): 16 | temp_dir = TemporaryDirectory() 17 | temp_path = temp_dir.name + "/test.nc" 18 | doppler_lidar_file = data_dir / "doppler-lidar.nc" 19 | doppler_lidar_wind_file = data_dir / "doppler-lidar-wind.nc" 20 | uuid = generate_epsilon_from_lidar( 21 | doppler_lidar_file, doppler_lidar_wind_file, temp_path, None 22 | ) 23 | 24 | date = "2024-04-13" 25 | site_meta = { 26 | "name": "Jülich", 27 | "altitude": 111, 28 | "latitude": 50.908, 29 | "longitude": 6.413, 30 | } 31 | 32 | def test_default_processing(self, tmp_path): 33 | with netCDF4.Dataset(self.temp_path) as nc: 34 | assert nc["epsilon"][:].size == 39360 35 | assert 1e-4 < nc["epsilon"][:].mean() < 3e-4 36 | assert nc.cloudnet_file_type == "epsilon-lidar" 37 | -------------------------------------------------------------------------------- /tests/unit/test_falling.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import pytest 3 | from numpy import ma 4 | from numpy.testing import assert_array_equal 5 | 6 | from cloudnetpy.categorize import falling 7 | 8 | 9 | class Obs: 10 | def __init__(self): 11 | self.z = ma.array( 12 | [[0.4, 0.5, 0.6, 0.7, 0.5, 0.5], [0.5, 0.5, 0.5, 0.5, 0.5, 0.5]], 13 | mask=[[0, 0, 0, 1, 0, 0], [0, 1, 0, 0, 0, 0]], 14 | ) 15 | 16 | self.is_clutter = np.array([[1, 0, 0, 0, 0, 0], [1, 1, 0, 0, 0, 0]]) 17 | 18 | self.beta = ma.array( 19 | [ 20 | [1e-8, 1e-8, 1e-5, 1e-8, 1e-8, 1e-3], 21 | [1e-8, 1e-8, 1e-5, 1e-5, 1e-8, 1e-8], 22 | ], 23 | mask=[[0, 0, 0, 1, 0, 0], [0, 1, 1, 0, 0, 0]], 24 | ) 25 | 26 | self.tw = ma.array( 27 | [[250, 250, 250, 250, 250, 250], [250, 250, 250, 250, 250, 270]], 28 | ) 29 | 30 | self.height = np.array([1999, 2002, 3000, 4000, 5000, 6000]) 31 | 32 | self.altitude = 0 33 | 34 | 35 | def test_find_falling_from_radar(): 36 | obs = Obs() 37 | is_insects = np.array([[0, 1, 0, 1, 0, 0], [0, 0, 1, 1, 0, 0]]) 38 | result = np.array([[0, 0, 1, 0, 1, 1], [0, 0, 0, 0, 1, 1]]) 39 | assert_array_equal(falling._find_falling_from_radar(obs, is_insects), result) # type: ignore 40 | 41 | 42 | def test_find_cold_aerosols(): 43 | obs = Obs() 44 | result = np.array([[0, 0, 0, 0, 1, 1], [0, 0, 0, 0, 1, 0]]) 45 | is_liquid = np.array([[0, 1, 1, 0, 0, 0], [0, 1, 0, 1, 0, 0]]) 46 | assert_array_equal(falling._find_cold_aerosols(obs, is_liquid), result) # type: ignore 47 | 48 | 49 | @pytest.mark.parametrize( 50 | "z, ind_top, result", 51 | [ 52 | (ma.masked_array([1, 1, 1, 1], mask=[0, 0, 0, 0]), 2, False), 53 | (ma.masked_array([1, 1, 1, 1], mask=[0, 0, 0, 0]), 2, False), 54 | (ma.masked_array([1, 1, 1, 1], mask=[0, 0, 0, 0]), 3, False), 55 | (ma.masked_array([1, 1, 1, 1], mask=[1, 1, 1, 1]), 3, False), 56 | (ma.masked_array([1, 1, 1, 1], mask=[0, 0, 0, 1]), 2, True), 57 | ], 58 | ) 59 | def test_is_z_missing_above_liquid(z, ind_top, result): 60 | assert falling._is_z_missing_above_liquid(z, ind_top) == result 61 | 62 | 63 | @pytest.mark.parametrize( 64 | "z, ind_base, ind_top, result", 65 | [ 66 | (ma.masked_array([1, 1, 1, 1], mask=[0, 0, 0, 0]), 1, 2, False), 67 | (ma.masked_array([1, 1, 2, 1], mask=[0, 0, 0, 0]), 1, 2, True), 68 | (ma.masked_array([1, 2, 1, 1], mask=[0, 0, 0, 0]), 1, 2, False), 69 | (ma.masked_array([1, 1, 2, 3], mask=[0, 0, 1, 0]), 1, 3, True), 70 | (ma.masked_array([1, 2, 3, 4], mask=[0, 1, 1, 1]), 1, 3, False), 71 | ], 72 | ) 73 | def test_is_z_increasing(z, ind_top, ind_base, result): 74 | assert falling._is_z_increasing(z, ind_base, ind_top) == result 75 | 76 | 77 | def test_fix_liquid_dominated_radar(): 78 | obs = Obs() 79 | 80 | is_liquid = np.array([[1, 1, 1, 0, 0, 0], [0, 0, 0, 1, 1, 0]]) 81 | 82 | falling_from_radar = np.array([[0, 1, 0, 0, 1, 1], [0, 0, 0, 0, 1, 1]]) 83 | 84 | result = np.array([[0, 0, 0, 0, 1, 1], [0, 0, 0, 0, 1, 1]]) 85 | 86 | fixed = falling._fix_liquid_dominated_radar(obs, falling_from_radar, is_liquid) # type: ignore 87 | 88 | assert_array_equal(fixed, result) 89 | -------------------------------------------------------------------------------- /tests/unit/test_fd12p.py: -------------------------------------------------------------------------------- 1 | import os 2 | from tempfile import TemporaryDirectory 3 | 4 | import pytest 5 | from numpy.testing import assert_array_equal 6 | from cloudnetpy.exceptions import ValidTimeStampError 7 | from cloudnetpy.instruments import fd12p2nc 8 | from tests.unit.all_products_fun import Check 9 | import numpy as np 10 | 11 | SCRIPT_PATH = os.path.dirname(os.path.realpath(__file__)) 12 | 13 | SITE_META = { 14 | "name": "Lindenberg", 15 | "latitude": 52.208, 16 | "longitude": 14.118, 17 | "altitude": 104, 18 | } 19 | 20 | 21 | class FD12P(Check): 22 | temp_dir = TemporaryDirectory() 23 | temp_path = temp_dir.name + "/test.nc" 24 | 25 | def test_rainfall_amount(self): 26 | assert self.nc.variables["precipitation_amount"][0] == 0.0 27 | assert (np.diff(self.nc.variables["precipitation_amount"][:]) >= 0).all() 28 | 29 | def test_global_attributes(self): 30 | assert self.nc.cloudnet_file_type == "weather-station" 31 | assert ( 32 | self.nc.title 33 | == f"FD12P present weather sensor from {self.site_meta['name']}" 34 | ) 35 | assert self.nc.source == "Vaisala FD12P" 36 | assert self.nc.year == self.date[:4] 37 | assert self.nc.month == self.date[5:7] 38 | assert self.nc.day == self.date[8:10] 39 | assert self.nc.location == self.site_meta["name"] 40 | 41 | 42 | class TestFD12P_1(FD12P): 43 | date = "2004-04-06" 44 | temp_dir = TemporaryDirectory() 45 | temp_path = temp_dir.name + "/test.nc" 46 | site_meta = SITE_META 47 | filename = f"{SCRIPT_PATH}/data/fd12p/PW040406.DAT" 48 | uuid = fd12p2nc(filename, temp_path, site_meta) 49 | 50 | def test_dimensions(self): 51 | assert self.nc.dimensions["time"].size == 3 52 | 53 | 54 | class TestFD12P_2(FD12P): 55 | date = "2004-07-12" 56 | temp_dir = TemporaryDirectory() 57 | temp_path = temp_dir.name + "/test.nc" 58 | site_meta = SITE_META 59 | filename = f"{SCRIPT_PATH}/data/fd12p/PW040712.DAT" 60 | uuid = fd12p2nc(filename, temp_path, site_meta) 61 | 62 | def test_dimensions(self): 63 | assert self.nc.dimensions["time"].size == 2 64 | 65 | def test_visibility(self): 66 | assert_array_equal(self.nc["visibility"], [3430, 3693]) 67 | 68 | 69 | class TestFD12P_3(FD12P): 70 | date = "2013-01-21" 71 | temp_dir = TemporaryDirectory() 72 | temp_path = temp_dir.name + "/test.nc" 73 | temp_path2 = temp_dir.name + "/test2.nc" 74 | site_meta = SITE_META 75 | filename = f"{SCRIPT_PATH}/data/fd12p/PW130121.DAT" 76 | uuid = fd12p2nc(filename, temp_path, site_meta) 77 | 78 | def test_dimensions(self): 79 | assert self.nc.dimensions["time"].size == 2 80 | 81 | def test_invalid_date(self): 82 | with pytest.raises(ValidTimeStampError): 83 | fd12p2nc( 84 | self.filename, 85 | self.temp_path2, 86 | SITE_META, 87 | date="2022-01-05", 88 | ) 89 | -------------------------------------------------------------------------------- /tests/unit/test_galileo.py: -------------------------------------------------------------------------------- 1 | from os import path 2 | from tempfile import TemporaryDirectory 3 | 4 | import netCDF4 5 | import numpy as np 6 | import pytest 7 | 8 | from cloudnetpy.exceptions import ValidTimeStampError, RadarDataError 9 | from cloudnetpy.instruments import galileo 10 | from tests.unit.all_products_fun import Check 11 | from tests.unit.radar_fun import RadarFun 12 | 13 | SCRIPT_PATH = path.dirname(path.realpath(__file__)) 14 | FILEPATH = f"{SCRIPT_PATH}/data/galileo/" 15 | 16 | 17 | class TestGalileo2nc(Check): 18 | site_meta = { 19 | "name": "Chilbolton", 20 | "latitude": 51.144, 21 | "longitude": -1.439, 22 | "altitude": 85, 23 | } 24 | temp_dir = TemporaryDirectory() 25 | temp_path = temp_dir.name + "/galileo.nc" 26 | uuid = galileo.galileo2nc(FILEPATH, temp_path, site_meta) 27 | date = "2023-03-08" 28 | 29 | def test_variable_names(self): 30 | keys = { 31 | "Zh", 32 | "v", 33 | "width", 34 | "ldr", 35 | "SNR", 36 | "time", 37 | "range", 38 | "radar_frequency", 39 | "nyquist_velocity", 40 | "latitude", 41 | "longitude", 42 | "altitude", 43 | "zenith_angle", 44 | "azimuth_angle", 45 | "height", 46 | "beamwidthH", 47 | "beamwidthV", 48 | "antenna_diameter", 49 | "snr_limit", 50 | } 51 | assert set(self.nc.variables.keys()) == keys 52 | 53 | def test_variables(self): 54 | assert np.isclose( 55 | self.nc.variables["radar_frequency"][:].data, 56 | 94.0, 57 | atol=0.1, 58 | ) # Hard coded 59 | assert np.all( 60 | np.isclose(self.nc.variables["zenith_angle"][:].data, 0.0, atol=0.1), 61 | ) 62 | 63 | def test_common_radar(self): 64 | radar_fun = RadarFun(self.nc, self.site_meta, self.date, self.uuid) 65 | for name, method in RadarFun.__dict__.items(): 66 | if "test_" in name: 67 | getattr(radar_fun, name)() 68 | 69 | def test_long_names(self): 70 | data = [ 71 | ("SNR", "Signal-to-noise ratio"), 72 | ] 73 | for key, expected in data: 74 | if key in self.nc.variables: 75 | value = self.nc.variables[key].long_name 76 | assert value == expected, f"{value} != {expected}" 77 | 78 | def test_global_attributes(self): 79 | assert self.nc.source == "RAL Space Galileo" 80 | assert self.nc.title == f'Galileo cloud radar from {self.site_meta["name"]}' 81 | 82 | def test_filename_argument(self, tmp_path): 83 | test_path = tmp_path / "date.nc" 84 | filename = f"{FILEPATH}galileo-file-1.nc" 85 | with pytest.raises(RadarDataError): 86 | galileo.galileo2nc(filename, test_path, self.site_meta) 87 | 88 | def test_correct_date_validation(self, tmp_path): 89 | test_path = tmp_path / "date.nc" 90 | galileo.galileo2nc(FILEPATH, test_path, self.site_meta, date=self.date) 91 | 92 | def test_wrong_date_validation(self, tmp_path): 93 | test_path = tmp_path / "invalid.nc" 94 | with pytest.raises(ValidTimeStampError): 95 | galileo.galileo2nc(FILEPATH, test_path, self.site_meta, date="2021-01-03") 96 | 97 | def test_uuid_from_user(self, tmp_path): 98 | test_path = tmp_path / "uuid.nc" 99 | uuid_from_user = "kissa" 100 | uuid = galileo.galileo2nc( 101 | FILEPATH, 102 | test_path, 103 | self.site_meta, 104 | uuid=uuid_from_user, 105 | ) 106 | with netCDF4.Dataset(test_path) as nc: 107 | assert nc.file_uuid == uuid_from_user 108 | assert uuid == uuid_from_user 109 | -------------------------------------------------------------------------------- /tests/unit/test_hatpro_l1c.py: -------------------------------------------------------------------------------- 1 | import glob 2 | from os import path 3 | from tempfile import TemporaryDirectory 4 | 5 | import netCDF4 6 | 7 | from cloudnetpy.instruments import hatpro 8 | from tests.unit.all_products_fun import Check 9 | 10 | SCRIPT_PATH = path.dirname(path.realpath(__file__)) 11 | file_path = f"{SCRIPT_PATH}/data/hatpro-mwrpy/" 12 | 13 | 14 | class TestHatpro2nc(Check): 15 | coeff_files = glob.glob(f"{SCRIPT_PATH}/data/hatpro-mwrpy-coeffs/*.ret") 16 | 17 | site_meta = { 18 | "name": "hyytiala", 19 | "latitude": 61.844, 20 | "longitude": 24.287, 21 | "altitude": 150, 22 | "coefficientFiles": coeff_files, 23 | "coefficientLinks": coeff_files, 24 | } 25 | 26 | date = "2023-04-01" 27 | temp_dir = TemporaryDirectory() 28 | temp_path = temp_dir.name + "/test.nc" 29 | uuid = hatpro.hatpro2l1c(file_path, temp_path, site_meta, date=date) 30 | 31 | def test_default_processing(self, tmp_path): 32 | with netCDF4.Dataset(self.temp_path) as nc: 33 | assert nc.cloudnet_file_type == "mwr-l1c" 34 | -------------------------------------------------------------------------------- /tests/unit/test_insects.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from numpy import ma 3 | from numpy.testing import assert_array_equal 4 | 5 | from cloudnetpy.categorize import insects 6 | 7 | 8 | class Obs: 9 | def __init__(self): 10 | self.time = np.array([1, 2, 3, 4]) 11 | self.radar_type = "MIRA-35" 12 | self.lwp = np.array([1, 2, 3, 4]) 13 | self.v = ma.array( 14 | [[0, 1, 1, 0], [0, 1, -99, 0]], 15 | mask=[[0, 0, 0, 0], [1, 0, 1, 0]], 16 | ) 17 | self.is_rain = np.array([0, 0, 0, 1]) 18 | 19 | 20 | def test_calc_prob_from_ldr(): 21 | prob = {"z": 0.5, "temp_loose": 0.5, "ldr": 0.5, "temp_strict": 0.5} 22 | assert insects._calc_prob_from_ldr(prob) == 0.5**2 23 | 24 | 25 | def test_calc_prob_from_all(): 26 | prob = { 27 | "z": 0.5, 28 | "temp_strict": 0.5, 29 | "v": 0.5, 30 | "width": 0.5, 31 | "z_weak": 0.5, 32 | } 33 | assert insects._calc_prob_from_all(prob) == 0.5**4 34 | 35 | 36 | def test_adjust_for_radar(): 37 | prob = {"lwp": np.array([0.5, 0.5, 0.5, 0.5])} 38 | prob_from_others = np.array([1.0, 1.0, 1.0, 1.0]) 39 | obs = Obs() 40 | assert_array_equal( 41 | insects._adjust_for_radar(obs, prob, prob_from_others), # type: ignore 42 | np.array([0.5, 0.5, 0.5, 0.5]), 43 | ) 44 | 45 | 46 | def test_fill_missing_pixels(): 47 | prob_from_ldr = np.array( 48 | [ 49 | [0.0, 0.0, 0.0, 1.0, 1.0, 0.0, 0.0], 50 | [0.0, 0.0, 0.0, 1.0, 1.0, 0.0, 0.0], 51 | ], 52 | ) 53 | prob_from_others = np.array( 54 | [ 55 | [0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5], 56 | [0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5], 57 | ], 58 | ) 59 | result = np.array( 60 | [ 61 | [0.5, 0.5, 0.5, 1.0, 1.0, 0.5, 0.5], 62 | [0.5, 0.5, 0.5, 1.0, 1.0, 0.5, 0.5], 63 | ], 64 | ) 65 | assert_array_equal( 66 | insects._fill_missing_pixels(prob_from_ldr, prob_from_others), 67 | result, 68 | ) 69 | 70 | 71 | def test_get_smoothed_v(): 72 | obs = Obs() 73 | result = ma.array([[0, 1, 1, 0], [0, 1, -99, 0]], mask=[[0, 0, 0, 0], [1, 0, 1, 0]]) 74 | assert_array_equal(insects._get_smoothed_v(obs, sigma=(0, 0)), result) # type: ignore 75 | 76 | 77 | def test_screen_insets(): 78 | obs = Obs() 79 | insect_prob = np.ones((4, 4)) 80 | insect_prob_no_ldr = np.ones((4, 4)) * 0.5 81 | melting_layer = np.array([[0, 0, 1, 0], [0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0]]) 82 | liquid_layers = np.array([[0, 0, 1, 0], [0, 1, 1, 0], [0, 0, 0, 0], [0, 0, 0, 0]]) 83 | result = np.array([[1, 1, 0, 0], [1, 0, 0, 0], [1, 1, 1, 1], [0, 0, 0, 0]]) 84 | screened_prob = insects._screen_insects( 85 | insect_prob, 86 | insect_prob_no_ldr, 87 | melting_layer, 88 | liquid_layers, 89 | obs, 90 | ) 91 | assert_array_equal(screened_prob, result) 92 | -------------------------------------------------------------------------------- /tests/unit/test_lidar.py: -------------------------------------------------------------------------------- 1 | import netCDF4 2 | import numpy as np 3 | import pytest 4 | from numpy import ma 5 | from numpy.testing import assert_array_equal 6 | 7 | from cloudnetpy.categorize.lidar import Lidar, get_gap_ind 8 | 9 | WAVELENGTH = 900.0 10 | 11 | 12 | @pytest.fixture(scope="session") 13 | def fake_lidar_file(tmpdir_factory): 14 | """Creates a simple lidar file for testing.""" 15 | file_name = tmpdir_factory.mktemp("data").join("radar_file.nc") 16 | with netCDF4.Dataset(file_name, "w", format="NETCDF4_CLASSIC") as root_grp: 17 | n_time, n_height = 4, 4 18 | root_grp.createDimension("time", n_time) 19 | root_grp.createDimension("height", n_height) 20 | root_grp.createVariable("time", "f8", "time")[:] = np.arange(n_time) 21 | var = root_grp.createVariable("height", "f8", "height") 22 | var[:] = np.arange(n_height) 23 | var.units = "km" 24 | root_grp.createVariable("wavelength", "f8")[:] = WAVELENGTH 25 | root_grp.createVariable("latitude", "f8")[:] = 60.43 26 | root_grp.createVariable("longitude", "f8")[:] = 25.4 27 | var = root_grp.createVariable("altitude", "f8") 28 | var[:] = 120.3 29 | var.units = "m" 30 | var = root_grp.createVariable("beta", "f8", ("time", "height")) 31 | var[:] = ma.array( 32 | [[1, 2, 3, 4], [1, 2, 3, 4], [1, 2, 3, 4], [1, 2, 3, 4]], 33 | dtype=float, 34 | mask=[[0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0]], 35 | ) 36 | return file_name 37 | 38 | 39 | def test_init(fake_lidar_file): 40 | obj = Lidar(fake_lidar_file) 41 | assert obj.data["lidar_wavelength"].data == WAVELENGTH 42 | assert obj.data["beta_bias"].data == 3 43 | assert obj.data["beta_error"].data == 0.5 44 | 45 | 46 | def test_rebin(fake_lidar_file): 47 | obj = Lidar(fake_lidar_file) 48 | time_new = np.array([1.1, 2.1]) 49 | height_new = np.array([505, 1501]) 50 | ind = obj.interpolate_to_grid(time_new, height_new) 51 | result = np.array([[2, 3], [2, 3]]) 52 | assert_array_equal(obj.data["beta"].data, result) 53 | assert ind == [0, 1] 54 | 55 | 56 | @pytest.mark.parametrize( 57 | "original_grid, new_grid, threshold, expected", 58 | [ 59 | (np.array([1, 2, 3, 4, 5]), np.array([1.9, 2.2, 3.1, 4.0, 4.9]), 1, []), 60 | (np.array([1, 2, 3, 4, 5]), np.array([10, 20, 30, 40, 50]), 1, [0, 1, 2, 3, 4]), 61 | ( 62 | np.array([1, 2, 3, 4, 5]), 63 | np.array([1.1, 2.1, 3.2, 4.2, 5.3]), 64 | 0.15, 65 | [2, 3, 4], 66 | ), 67 | (np.array([]), np.array([]), 0.5, []), 68 | ], 69 | ) 70 | def test_get_gap_ind( 71 | original_grid: np.ndarray, 72 | new_grid: np.ndarray, 73 | threshold: float, 74 | expected: np.ndarray, 75 | ): 76 | assert get_gap_ind(original_grid, new_grid, threshold) == expected 77 | -------------------------------------------------------------------------------- /tests/unit/test_melting.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import pytest 3 | from numpy import ma 4 | from numpy.testing import assert_array_equal 5 | 6 | from cloudnetpy.categorize import melting 7 | 8 | 9 | @pytest.mark.parametrize( 10 | "model, result", 11 | [ 12 | ("gdas1", (-8, 6)), 13 | ("ecmwf", (-4, 3)), 14 | ("some_icon_version", (-4, 3)), 15 | ("some_harmonie_version", (-4, 3)), 16 | ], 17 | ) 18 | def test_find_model_temperature_range(model, result): 19 | assert melting._find_model_temperature_range(model) == result 20 | 21 | 22 | @pytest.mark.parametrize( 23 | "t_prof, t_range, result", 24 | [ 25 | (np.array([500, 200]), (-10, 10), []), 26 | (np.array([500, 400]), (-10, 10), []), 27 | (np.array([220, 210, 200]), (-10, 10), []), 28 | (np.array([272, 100]), (-10, 10), [0]), 29 | (np.array([500, 272]), (-10, 10), [1]), 30 | (np.array([280, 270, 260]), (-5, 2), [1]), 31 | (np.linspace(275, 270, 10), (-4, -3), [9]), 32 | (np.array([300, 290, 280, 270, 260, 250, 240]), (-5, 5), [3]), 33 | (np.array([274, 272]), (-10, 10), [0, 1]), 34 | (np.array([274, 272, 100, 50]), (-10, 10), [0, 1]), 35 | (np.array([270, 275, 260, 250, 240]), (-10, 10), [0, 1]), 36 | (np.array([500, 400, 274, 272]), (-10, 10), [2, 3]), 37 | (np.array([500, 400, 274, 272, 100, 50]), (-10, 10), [2, 3]), 38 | (np.array([300, 290, 280, 270, 260, 250, 240]), (-10, 10), [2, 3]), 39 | (np.array([290, 280, 270, 275, 260, 250, 240]), (-10, 10), [1, 2, 3]), 40 | (np.array([500, 400, 273, 420, 272, 200]), (-10, 10), [2, 3, 4]), 41 | (np.array([200, 272, 420, 274, 273, 150]), (-10, 10), [1, 2, 3, 4]), 42 | ( 43 | np.array([200, 272, 420, 274, 100, 273, 100]), 44 | (-10, 10), 45 | [1, 2, 3, 4, 5], 46 | ), 47 | ], 48 | ) 49 | def test_get_temp_indices(t_prof, t_range, result): 50 | indices = melting._get_temp_indices(t_prof, t_range) 51 | assert_array_equal(indices, result) 52 | 53 | 54 | class Obs: 55 | def __init__(self): 56 | self.tw = np.tile(np.linspace(275, 270, 10), (2, 1)) 57 | self.tw[:, -1] = 250 58 | self.ldr = ma.array( 59 | [ 60 | [1, 1, 1, 3, 150, 3, 1, 1, 1, 1], 61 | [1, 1, 1, 3, 150, 3, 1, 1, 1, 1], 62 | ], 63 | ) 64 | self.v = ma.array( 65 | [ 66 | [-1, -1, -4, -2, -2, -1, 0, 0, 0, 0], 67 | [-1, -1, -4, -2, -2, -1, 0, 0, 0, 0], 68 | ], 69 | ) 70 | self.z = ma.array( 71 | [[1, 1, 1, 1, 1, 3, 1, 1, 1, 1], [1, 1, 1, 1, 1, 3, 1, 1, 1, 1]], 72 | ) 73 | self.model_type = "ecmwf" 74 | 75 | 76 | def test_find_melting_layer(): 77 | obs = Obs() 78 | layer = melting.find_melting_layer(obs, smooth=False) # type: ignore 79 | result = np.array([[0, 0, 0, 1, 1, 0, 0, 0, 0, 0], [0, 0, 0, 1, 1, 0, 0, 0, 0, 0]]) 80 | assert_array_equal(layer, result) 81 | -------------------------------------------------------------------------------- /tests/unit/test_mrr.py: -------------------------------------------------------------------------------- 1 | from os import path 2 | from tempfile import TemporaryDirectory 3 | 4 | import netCDF4 5 | import pytest 6 | 7 | from cloudnetpy.exceptions import ValidTimeStampError 8 | from cloudnetpy.instruments import mrr2nc 9 | from tests.unit.all_products_fun import Check 10 | 11 | SCRIPT_PATH = path.dirname(path.realpath(__file__)) 12 | filename = f"{SCRIPT_PATH}/data/mrr/20220124_180000.nc" 13 | 14 | 15 | class TestMrrPro(Check): 16 | site_meta = { 17 | "name": "Palaiseau", 18 | "latitude": 50, 19 | "longitude": 104.5, 20 | "altitude": 50, 21 | } 22 | date = "2022-01-24" 23 | temp_dir = TemporaryDirectory() 24 | temp_path = temp_dir.name + "/test.nc" 25 | uuid = mrr2nc(filename, temp_path, site_meta) 26 | 27 | def test_variable_names(self): 28 | keys = { 29 | "Zh", 30 | "v", 31 | "width", 32 | "pia", 33 | "lwc", 34 | "rainfall_rate", 35 | "time", 36 | "range", 37 | "radar_frequency", 38 | "height", 39 | "latitude", 40 | "longitude", 41 | "altitude", 42 | "zenith_angle", 43 | } 44 | assert set(self.nc.variables.keys()) == keys 45 | 46 | def test_variables(self): 47 | assert self.nc.variables["radar_frequency"].units == "GHz" 48 | assert ( 49 | abs(self.nc.variables["radar_frequency"][:].data - 24.23) < 0.001 50 | ) # Hard coded 51 | assert self.nc.variables["altitude"][:] == 50 52 | 53 | def test_global_attributes(self): 54 | assert self.nc.source == "METEK MRR-PRO" 55 | assert self.nc.title == "MRR-PRO rain radar from Palaiseau" 56 | assert self.nc.serial_number == "0511107367" 57 | 58 | def test_wrong_date(self, tmp_path): 59 | with pytest.raises(ValidTimeStampError): 60 | mrr2nc( 61 | filename, 62 | tmp_path / "invalid.nc", 63 | self.site_meta, 64 | date="2021-01-04", 65 | ) 66 | 67 | def test_uuid_from_user(self, tmp_path): 68 | test_path = tmp_path / "uuid.nc" 69 | uuid_from_user = "e58134f9-073d-4b10-b6c9-a91cf2229322" 70 | uuid = mrr2nc(filename, test_path, self.site_meta, uuid=uuid_from_user) 71 | with netCDF4.Dataset(test_path) as nc: 72 | assert nc.file_uuid == uuid_from_user 73 | assert uuid == uuid_from_user 74 | -------------------------------------------------------------------------------- /tests/unit/test_mwr.py: -------------------------------------------------------------------------------- 1 | import netCDF4 2 | import numpy as np 3 | import pytest 4 | from numpy.testing import assert_array_equal 5 | 6 | from cloudnetpy.categorize.mwr import Mwr 7 | 8 | 9 | @pytest.fixture(scope="session") 10 | def fake_mwr_file(tmpdir_factory): 11 | """Creates a simple mwr for testing.""" 12 | file_name = tmpdir_factory.mktemp("data").join("mwr_file.nc") 13 | with netCDF4.Dataset(file_name, "w", format="NETCDF4_CLASSIC") as root_grp: 14 | n_time = 5 15 | root_grp.createDimension("time", n_time) 16 | var = root_grp.createVariable("time", "f8", "time") 17 | var[:] = np.arange(n_time) 18 | var = root_grp.createVariable("lwp", "f8", "time") 19 | var[:] = np.array([0.1, 2.5, -0.1, 0.2, 0.0]) 20 | var.units = "g / m^2" 21 | return file_name 22 | 23 | 24 | def test_rebin_to_grid(fake_mwr_file): 25 | obj = Mwr(fake_mwr_file) 26 | time_new = np.array([1.4, 2.8]) 27 | obj.rebin_to_grid(time_new) 28 | result = np.array([1.2, 0.2]) 29 | assert_array_equal(obj.data["lwp"][:], result) 30 | 31 | 32 | @pytest.fixture(scope="session") 33 | def bad_mwr_file(tmpdir_factory): 34 | """Creates invalid mwr file for testing.""" 35 | file_name = tmpdir_factory.mktemp("data").join("mwr_file.nc") 36 | with netCDF4.Dataset(file_name, "w", format="NETCDF4_CLASSIC") as root_grp: 37 | root_grp.createDimension("time", 5) 38 | var = root_grp.createVariable("xxx", "f8", "time") 39 | var[:] = np.array([0.1, 2.5, -0.1, 0.2, 0.0]) 40 | return file_name 41 | 42 | 43 | def test_init_lwp_data(fake_mwr_file): 44 | obj = Mwr(fake_mwr_file) 45 | result = np.array([0.1, 2.5, -0.1, 0.2, 0.0]) 46 | assert_array_equal(obj.data["lwp"][:], result) 47 | 48 | 49 | def test_init_lwp_error(fake_mwr_file): 50 | obj = Mwr(fake_mwr_file) 51 | lwp = obj.data["lwp"][:] 52 | lwp_error = obj.data["lwp_error"][:] 53 | assert_array_equal(lwp_error, np.sqrt((0.25 * lwp) ** 2 + 0.02**2)) 54 | 55 | 56 | def test_missing_variable(bad_mwr_file): 57 | with pytest.raises(RuntimeError): 58 | Mwr(bad_mwr_file) 59 | -------------------------------------------------------------------------------- /tests/unit/test_product_tools.py: -------------------------------------------------------------------------------- 1 | import netCDF4 2 | import numpy as np 3 | import pytest 4 | from numpy.testing import assert_array_equal 5 | 6 | from cloudnetpy.products import product_tools 7 | 8 | 9 | @pytest.fixture() 10 | def fake_categorize_file(tmpdir_factory): 11 | """Creates a simple categorize for testing.""" 12 | file_name = tmpdir_factory.mktemp("data").join("categorize.nc") 13 | with netCDF4.Dataset(file_name, "w", format="NETCDF4_CLASSIC") as root_grp: 14 | n_points = 7 15 | root_grp.createDimension("time", n_points) 16 | var = root_grp.createVariable("time", "f8", "time") 17 | var[:] = np.arange(n_points) 18 | var = root_grp.createVariable("category_bits", "i4", "time") 19 | var[:] = [0, 1, 2, 4, 8, 16, 32] 20 | var = root_grp.createVariable("quality_bits", "i4", "time") 21 | var[:] = [0, 1, 2, 4, 8, 16, 32] 22 | return file_name 23 | 24 | 25 | # def test_category_bits(fake_categorize_file): 26 | # obj = product_tools.CategorizeBits(fake_categorize_file) 27 | # for key in obj.category_keys: 28 | # assert sum(obj.category_bits[key]) == 1 29 | 30 | 31 | # def test_quality_bits(fake_categorize_file): 32 | # obj = product_tools.CategorizeBits(fake_categorize_file) 33 | # for key in obj.quality_keys: 34 | # assert sum(obj.quality_bits[key]) == 1 35 | 36 | 37 | def test_read_nc_fields(fake_categorize_file): 38 | assert_array_equal( 39 | product_tools.read_nc_field(fake_categorize_file, "time"), 40 | np.arange(7), 41 | ) 42 | -------------------------------------------------------------------------------- /tests/unit/test_rain_e_h3.py: -------------------------------------------------------------------------------- 1 | import os 2 | from tempfile import TemporaryDirectory 3 | 4 | from cloudnetpy.cloudnetarray import CloudnetArray 5 | import pytest 6 | 7 | from cloudnetpy.exceptions import ValidTimeStampError 8 | from cloudnetpy.instruments import rain_e_h32nc 9 | from tests.unit.all_products_fun import Check 10 | import numpy as np 11 | from numpy import ma 12 | 13 | SCRIPT_PATH = os.path.dirname(os.path.realpath(__file__)) 14 | 15 | SITE_META = { 16 | "name": "Palaiseau", 17 | "latitude": 50, 18 | "longitude": 104.5, 19 | "altitude": 50, 20 | } 21 | 22 | 23 | class RainEH3(Check): 24 | temp_dir = TemporaryDirectory() 25 | temp_path = temp_dir.name + "/test.nc" 26 | 27 | def test_rainfall_amount(self): 28 | assert self.nc.variables["rainfall_amount"][0] == 0.0 29 | assert (np.diff(self.nc.variables["rainfall_amount"][:]) >= 0).all() 30 | 31 | def test_global_attributes(self): 32 | assert self.nc.cloudnet_file_type == "rain-gauge" 33 | assert self.nc.title == f"rain[e]H3 rain-gauge from {self.site_meta['name']}" 34 | assert self.nc.source == "LAMBRECHT meteo GmbH rain[e]H3" 35 | assert self.nc.year == self.date[:4] 36 | assert self.nc.month == self.date[5:7] 37 | assert self.nc.day == self.date[8:10] 38 | assert self.nc.location == self.site_meta["name"] 39 | 40 | 41 | class TestRainEH3(RainEH3): 42 | date = "2024-12-31" 43 | temp_dir = TemporaryDirectory() 44 | temp_path = temp_dir.name + "/test.nc" 45 | site_meta = SITE_META 46 | filename = f"{SCRIPT_PATH}/data/rain_e_h3/20241231_raine_lindenberg.csv" 47 | uuid = rain_e_h32nc(filename, temp_path, site_meta) 48 | 49 | def test_dimensions(self): 50 | assert self.nc.dimensions["time"].size == 22 51 | 52 | 53 | class TestRainEH3File2(RainEH3): 54 | date = "2023-05-14" 55 | temp_dir = TemporaryDirectory() 56 | temp_path = temp_dir.name + "/test.nc" 57 | site_meta = SITE_META 58 | filename = f"{SCRIPT_PATH}/data/rain_e_h3/Lindenberg_RainE_20230514.txt" 59 | uuid = rain_e_h32nc(filename, temp_path, site_meta) 60 | 61 | def test_dimensions(self): 62 | assert self.nc.dimensions["time"].size == 18 63 | 64 | 65 | class TestDateArgument(RainEH3): 66 | date = "2024-12-31" 67 | temp_dir = TemporaryDirectory() 68 | temp_path = temp_dir.name + "/test.nc" 69 | filename = f"{SCRIPT_PATH}/data/rain_e_h3/20241231_raine_lindenberg.csv" 70 | site_meta = SITE_META 71 | uuid = rain_e_h32nc(filename, temp_path, site_meta, date=date) 72 | 73 | def test_invalid_date(self): 74 | with pytest.raises(ValidTimeStampError): 75 | rain_e_h32nc( 76 | self.filename, 77 | self.temp_path, 78 | SITE_META, 79 | date="2022-01-05", 80 | ) 81 | --------------------------------------------------------------------------------