├── .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 | [](https://github.com/actris-cloudnet/cloudnetpy/actions/workflows/test.yml)
4 | [](https://badge.fury.io/py/cloudnetpy)
5 | [](https://zenodo.org/badge/latestdoi/233602651)
6 | [](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 | 
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 |
--------------------------------------------------------------------------------