pyodim 0.4__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pyodim-0.4/.gitignore +130 -0
- pyodim-0.4/LICENSE +21 -0
- pyodim-0.4/PKG-INFO +108 -0
- pyodim-0.4/README.md +86 -0
- pyodim-0.4/pyodim/__init__.py +1 -0
- pyodim-0.4/pyodim/__version__.py +1 -0
- pyodim-0.4/pyodim/pyodim.py +511 -0
- pyodim-0.4/pyodim.egg-info/PKG-INFO +108 -0
- pyodim-0.4/pyodim.egg-info/SOURCES.txt +12 -0
- pyodim-0.4/pyodim.egg-info/dependency_links.txt +1 -0
- pyodim-0.4/pyodim.egg-info/requires.txt +5 -0
- pyodim-0.4/pyodim.egg-info/top_level.txt +1 -0
- pyodim-0.4/setup.cfg +4 -0
- pyodim-0.4/setup.py +41 -0
pyodim-0.4/.gitignore
ADDED
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
# Byte-compiled / optimized / DLL files
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*$py.class
|
|
5
|
+
|
|
6
|
+
# C extensions
|
|
7
|
+
*.so
|
|
8
|
+
|
|
9
|
+
# Distribution / packaging
|
|
10
|
+
.Python
|
|
11
|
+
build/
|
|
12
|
+
develop-eggs/
|
|
13
|
+
dist/
|
|
14
|
+
downloads/
|
|
15
|
+
eggs/
|
|
16
|
+
.eggs/
|
|
17
|
+
lib/
|
|
18
|
+
lib64/
|
|
19
|
+
parts/
|
|
20
|
+
sdist/
|
|
21
|
+
var/
|
|
22
|
+
wheels/
|
|
23
|
+
pip-wheel-metadata/
|
|
24
|
+
share/python-wheels/
|
|
25
|
+
*.egg-info/
|
|
26
|
+
.installed.cfg
|
|
27
|
+
*.egg
|
|
28
|
+
MANIFEST
|
|
29
|
+
|
|
30
|
+
# PyInstaller
|
|
31
|
+
# Usually these files are written by a python script from a template
|
|
32
|
+
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
|
33
|
+
*.manifest
|
|
34
|
+
*.spec
|
|
35
|
+
|
|
36
|
+
# Installer logs
|
|
37
|
+
pip-log.txt
|
|
38
|
+
pip-delete-this-directory.txt
|
|
39
|
+
|
|
40
|
+
# Unit test / coverage reports
|
|
41
|
+
htmlcov/
|
|
42
|
+
.tox/
|
|
43
|
+
.nox/
|
|
44
|
+
.coverage
|
|
45
|
+
.coverage.*
|
|
46
|
+
.cache
|
|
47
|
+
nosetests.xml
|
|
48
|
+
coverage.xml
|
|
49
|
+
*.cover
|
|
50
|
+
*.py,cover
|
|
51
|
+
.hypothesis/
|
|
52
|
+
.pytest_cache/
|
|
53
|
+
|
|
54
|
+
# Translations
|
|
55
|
+
*.mo
|
|
56
|
+
*.pot
|
|
57
|
+
|
|
58
|
+
# Django stuff:
|
|
59
|
+
*.log
|
|
60
|
+
local_settings.py
|
|
61
|
+
db.sqlite3
|
|
62
|
+
db.sqlite3-journal
|
|
63
|
+
|
|
64
|
+
# Flask stuff:
|
|
65
|
+
instance/
|
|
66
|
+
.webassets-cache
|
|
67
|
+
|
|
68
|
+
# Scrapy stuff:
|
|
69
|
+
.scrapy
|
|
70
|
+
|
|
71
|
+
# Sphinx documentation
|
|
72
|
+
docs/_build/
|
|
73
|
+
|
|
74
|
+
# PyBuilder
|
|
75
|
+
target/
|
|
76
|
+
|
|
77
|
+
# Jupyter Notebook
|
|
78
|
+
.ipynb_checkpoints
|
|
79
|
+
|
|
80
|
+
# IPython
|
|
81
|
+
profile_default/
|
|
82
|
+
ipython_config.py
|
|
83
|
+
|
|
84
|
+
# pyenv
|
|
85
|
+
.python-version
|
|
86
|
+
|
|
87
|
+
# pipenv
|
|
88
|
+
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
|
89
|
+
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
|
90
|
+
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
|
91
|
+
# install all needed dependencies.
|
|
92
|
+
#Pipfile.lock
|
|
93
|
+
|
|
94
|
+
# PEP 582; used by e.g. github.com/David-OConnor/pyflow
|
|
95
|
+
__pypackages__/
|
|
96
|
+
|
|
97
|
+
# Celery stuff
|
|
98
|
+
celerybeat-schedule
|
|
99
|
+
celerybeat.pid
|
|
100
|
+
|
|
101
|
+
# SageMath parsed files
|
|
102
|
+
*.sage.py
|
|
103
|
+
|
|
104
|
+
# Environments
|
|
105
|
+
.env
|
|
106
|
+
.venv
|
|
107
|
+
env/
|
|
108
|
+
venv/
|
|
109
|
+
ENV/
|
|
110
|
+
env.bak/
|
|
111
|
+
venv.bak/
|
|
112
|
+
|
|
113
|
+
# Spyder project settings
|
|
114
|
+
.spyderproject
|
|
115
|
+
.spyproject
|
|
116
|
+
|
|
117
|
+
# Rope project settings
|
|
118
|
+
.ropeproject
|
|
119
|
+
|
|
120
|
+
# mkdocs documentation
|
|
121
|
+
/site
|
|
122
|
+
|
|
123
|
+
# mypy
|
|
124
|
+
.mypy_cache/
|
|
125
|
+
.dmypy.json
|
|
126
|
+
dmypy.json
|
|
127
|
+
|
|
128
|
+
# Pyre type checker
|
|
129
|
+
.pyre/
|
|
130
|
+
.vscode/settings.json
|
pyodim-0.4/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2020 Valentin Louf
|
|
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.
|
pyodim-0.4/PKG-INFO
ADDED
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: pyodim
|
|
3
|
+
Version: 0.4
|
|
4
|
+
Summary: An ODIM hdf5 file reader.
|
|
5
|
+
Home-page: https://github.com/vlouf/pyodim
|
|
6
|
+
Author: Valentin Louf
|
|
7
|
+
Author-email: valentin.louf@bom.gov.au
|
|
8
|
+
Project-URL: Bug Reports, https://github.com/vlouf/pyodim/issues
|
|
9
|
+
Project-URL: Source, https://github.com/vlouf/pyodim/
|
|
10
|
+
Keywords: odim h5 file reader
|
|
11
|
+
Classifier: Development Status :: 4 - Beta
|
|
12
|
+
Classifier: Intended Audience :: Science/Research
|
|
13
|
+
Classifier: Topic :: Scientific/Engineering :: Atmospheric Science
|
|
14
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.7
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
20
|
+
Description-Content-Type: text/markdown
|
|
21
|
+
License-File: LICENSE
|
|
22
|
+
|
|
23
|
+
# PYODIM
|
|
24
|
+
|
|
25
|
+
A simple ODIM H5 (Opera Data Information Model - Hierarchical Data Format
|
|
26
|
+
version 5) radar reader in Python. It outputs the radar date in an xarray
|
|
27
|
+
Dataset. The goal is to be as barebone as possible while providing an xarray,
|
|
28
|
+
so that it can easily scale up (production) or just a very lean reader for
|
|
29
|
+
quicklooks into the data.
|
|
30
|
+
|
|
31
|
+
## Example
|
|
32
|
+
|
|
33
|
+
```python
|
|
34
|
+
|
|
35
|
+
import pyodim
|
|
36
|
+
|
|
37
|
+
filename = "/path/to/radar/file.h5"
|
|
38
|
+
rset = pyodim.read_odim(filename, lazy_load=True) # Lazy load does not load the data in memory to save time if you are interested in only a specific sweep
|
|
39
|
+
radar = rset[0].compute() # All sweeps are in a list (elevation ascending), so the 1st item of the list is the bottom sweep.
|
|
40
|
+
print(radar)
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
This code will output the radar data for the first sweep (lowest elevation
|
|
44
|
+
scan) in the radar file as an xarray Dataset, for example:
|
|
45
|
+
|
|
46
|
+
```python
|
|
47
|
+
<xarray.Dataset>
|
|
48
|
+
Dimensions: (azimuth: 360, range: 1283, elevation: 1, time: 360)
|
|
49
|
+
Coordinates:
|
|
50
|
+
* range (range) float32 125.0 375.0 625.0 ... 3.204e+05 3.206e+05
|
|
51
|
+
* azimuth (azimuth) float32 0.01111 1.011 2.011 ... 357.0 358.0 359.0
|
|
52
|
+
* elevation (elevation) float32 0.5
|
|
53
|
+
* time (time) datetime64[ns] 2024-01-16T02:49:29.337047353 ... 2024-...
|
|
54
|
+
Data variables: (12/17)
|
|
55
|
+
DBZH (azimuth, range) float64 nan nan nan nan nan ... nan nan nan nan
|
|
56
|
+
SNRH (azimuth, range) float64 nan nan nan nan nan ... nan nan nan nan
|
|
57
|
+
VRADH (azimuth, range) float64 nan nan nan nan nan ... nan nan nan nan
|
|
58
|
+
WRADH (azimuth, range) float64 nan nan nan nan nan ... nan nan nan nan
|
|
59
|
+
KDP (azimuth, range) float64 nan nan nan nan nan ... nan nan nan nan
|
|
60
|
+
... ...
|
|
61
|
+
x (azimuth, range) float32 0.02424 0.07271 ... -5.595e+03
|
|
62
|
+
y (azimuth, range) float32 125.0 375.0 ... 3.203e+05 3.206e+05
|
|
63
|
+
z (azimuth, range) float32 45.09 47.27 ... 2.84e+03 2.842e+03
|
|
64
|
+
longitude (azimuth, range) float32 144.8 144.8 144.8 ... 144.7 144.7 144.7
|
|
65
|
+
latitude (azimuth, range) float32 -37.85 -37.85 -37.85 ... -34.97 -34.96
|
|
66
|
+
Attributes: (12/15)
|
|
67
|
+
Conventions: ODIM_H5/V2_4
|
|
68
|
+
latitude: -37.85200119018555
|
|
69
|
+
longitude: 144.75199890136722
|
|
70
|
+
height: 44.0
|
|
71
|
+
date: 2024-01-16T02:45:21
|
|
72
|
+
object: PVOL
|
|
73
|
+
... ...
|
|
74
|
+
beamwV: 1.0
|
|
75
|
+
wavelength: 10.489999771118164
|
|
76
|
+
NI: 36.5052
|
|
77
|
+
highprf: 696.0
|
|
78
|
+
start_time: 20240116_024928
|
|
79
|
+
end_time: 20240116_024958
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
PYODIM reads the ODIM radar data and metadata. It also provides some quality of
|
|
83
|
+
life feature like automatically creating the x, y, z and latitude/longitude
|
|
84
|
+
coordinates for all radar gates (using the azimuthal equidistant - `aeqd` - projection). By
|
|
85
|
+
default it uses `dask.delayed` to *lazily* load the data, meaning that it will
|
|
86
|
+
not actually load all the sweeps in memory, as for many applications we often
|
|
87
|
+
don't need all the radar sweeps. Instead, by calling the `.compute()` method
|
|
88
|
+
like in the example above, PYODIM will only read and load in memory the sweep
|
|
89
|
+
that you want, saving time. If you want to read all the sweeps at once in a
|
|
90
|
+
list, then just set `lazy_load=False`.
|
|
91
|
+
|
|
92
|
+
## Dependencies
|
|
93
|
+
|
|
94
|
+
Mandatory:
|
|
95
|
+
- [numpy][1]
|
|
96
|
+
- [xarray][2]
|
|
97
|
+
- [pyproj][3]
|
|
98
|
+
- [dask][4]
|
|
99
|
+
|
|
100
|
+
And optionally (it will automatically populate the fields metadata using pyart
|
|
101
|
+
if available):
|
|
102
|
+
- [Py-ART][5]
|
|
103
|
+
|
|
104
|
+
[1]: http://www.scipy.org/
|
|
105
|
+
[2]: http://numba.pydata.org
|
|
106
|
+
[3]: https://pypi.org/project/pyproj/
|
|
107
|
+
[4]: https://www.dask.org/
|
|
108
|
+
[5]: https://github.com/ARM-DOE/pyart
|
pyodim-0.4/README.md
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
# PYODIM
|
|
2
|
+
|
|
3
|
+
A simple ODIM H5 (Opera Data Information Model - Hierarchical Data Format
|
|
4
|
+
version 5) radar reader in Python. It outputs the radar date in an xarray
|
|
5
|
+
Dataset. The goal is to be as barebone as possible while providing an xarray,
|
|
6
|
+
so that it can easily scale up (production) or just a very lean reader for
|
|
7
|
+
quicklooks into the data.
|
|
8
|
+
|
|
9
|
+
## Example
|
|
10
|
+
|
|
11
|
+
```python
|
|
12
|
+
|
|
13
|
+
import pyodim
|
|
14
|
+
|
|
15
|
+
filename = "/path/to/radar/file.h5"
|
|
16
|
+
rset = pyodim.read_odim(filename, lazy_load=True) # Lazy load does not load the data in memory to save time if you are interested in only a specific sweep
|
|
17
|
+
radar = rset[0].compute() # All sweeps are in a list (elevation ascending), so the 1st item of the list is the bottom sweep.
|
|
18
|
+
print(radar)
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
This code will output the radar data for the first sweep (lowest elevation
|
|
22
|
+
scan) in the radar file as an xarray Dataset, for example:
|
|
23
|
+
|
|
24
|
+
```python
|
|
25
|
+
<xarray.Dataset>
|
|
26
|
+
Dimensions: (azimuth: 360, range: 1283, elevation: 1, time: 360)
|
|
27
|
+
Coordinates:
|
|
28
|
+
* range (range) float32 125.0 375.0 625.0 ... 3.204e+05 3.206e+05
|
|
29
|
+
* azimuth (azimuth) float32 0.01111 1.011 2.011 ... 357.0 358.0 359.0
|
|
30
|
+
* elevation (elevation) float32 0.5
|
|
31
|
+
* time (time) datetime64[ns] 2024-01-16T02:49:29.337047353 ... 2024-...
|
|
32
|
+
Data variables: (12/17)
|
|
33
|
+
DBZH (azimuth, range) float64 nan nan nan nan nan ... nan nan nan nan
|
|
34
|
+
SNRH (azimuth, range) float64 nan nan nan nan nan ... nan nan nan nan
|
|
35
|
+
VRADH (azimuth, range) float64 nan nan nan nan nan ... nan nan nan nan
|
|
36
|
+
WRADH (azimuth, range) float64 nan nan nan nan nan ... nan nan nan nan
|
|
37
|
+
KDP (azimuth, range) float64 nan nan nan nan nan ... nan nan nan nan
|
|
38
|
+
... ...
|
|
39
|
+
x (azimuth, range) float32 0.02424 0.07271 ... -5.595e+03
|
|
40
|
+
y (azimuth, range) float32 125.0 375.0 ... 3.203e+05 3.206e+05
|
|
41
|
+
z (azimuth, range) float32 45.09 47.27 ... 2.84e+03 2.842e+03
|
|
42
|
+
longitude (azimuth, range) float32 144.8 144.8 144.8 ... 144.7 144.7 144.7
|
|
43
|
+
latitude (azimuth, range) float32 -37.85 -37.85 -37.85 ... -34.97 -34.96
|
|
44
|
+
Attributes: (12/15)
|
|
45
|
+
Conventions: ODIM_H5/V2_4
|
|
46
|
+
latitude: -37.85200119018555
|
|
47
|
+
longitude: 144.75199890136722
|
|
48
|
+
height: 44.0
|
|
49
|
+
date: 2024-01-16T02:45:21
|
|
50
|
+
object: PVOL
|
|
51
|
+
... ...
|
|
52
|
+
beamwV: 1.0
|
|
53
|
+
wavelength: 10.489999771118164
|
|
54
|
+
NI: 36.5052
|
|
55
|
+
highprf: 696.0
|
|
56
|
+
start_time: 20240116_024928
|
|
57
|
+
end_time: 20240116_024958
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
PYODIM reads the ODIM radar data and metadata. It also provides some quality of
|
|
61
|
+
life feature like automatically creating the x, y, z and latitude/longitude
|
|
62
|
+
coordinates for all radar gates (using the azimuthal equidistant - `aeqd` - projection). By
|
|
63
|
+
default it uses `dask.delayed` to *lazily* load the data, meaning that it will
|
|
64
|
+
not actually load all the sweeps in memory, as for many applications we often
|
|
65
|
+
don't need all the radar sweeps. Instead, by calling the `.compute()` method
|
|
66
|
+
like in the example above, PYODIM will only read and load in memory the sweep
|
|
67
|
+
that you want, saving time. If you want to read all the sweeps at once in a
|
|
68
|
+
list, then just set `lazy_load=False`.
|
|
69
|
+
|
|
70
|
+
## Dependencies
|
|
71
|
+
|
|
72
|
+
Mandatory:
|
|
73
|
+
- [numpy][1]
|
|
74
|
+
- [xarray][2]
|
|
75
|
+
- [pyproj][3]
|
|
76
|
+
- [dask][4]
|
|
77
|
+
|
|
78
|
+
And optionally (it will automatically populate the fields metadata using pyart
|
|
79
|
+
if available):
|
|
80
|
+
- [Py-ART][5]
|
|
81
|
+
|
|
82
|
+
[1]: http://www.scipy.org/
|
|
83
|
+
[2]: http://numba.pydata.org
|
|
84
|
+
[3]: https://pypi.org/project/pyproj/
|
|
85
|
+
[4]: https://www.dask.org/
|
|
86
|
+
[5]: https://github.com/ARM-DOE/pyart
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from .pyodim import read_odim
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = '0.3'
|
|
@@ -0,0 +1,511 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Natively reading ODIM H5 radar files in Python.
|
|
3
|
+
|
|
4
|
+
@title: pyodim
|
|
5
|
+
@author: Valentin Louf <valentin.louf@bom.gov.au>
|
|
6
|
+
@institutions: Bureau of Meteorology and Monash University.
|
|
7
|
+
@creation: 21/01/2020
|
|
8
|
+
@date: 19/02/2021
|
|
9
|
+
|
|
10
|
+
.. autosummary::
|
|
11
|
+
:toctree: generated/
|
|
12
|
+
|
|
13
|
+
_to_str
|
|
14
|
+
buffer
|
|
15
|
+
cartesian_to_geographic
|
|
16
|
+
check_nyquist
|
|
17
|
+
coord_from_metadata
|
|
18
|
+
field_metadata
|
|
19
|
+
generate_timestamp
|
|
20
|
+
get_dataset_metadata
|
|
21
|
+
get_root_metadata
|
|
22
|
+
radar_coordinates_to_xyz
|
|
23
|
+
read_odim_slice
|
|
24
|
+
read_odim
|
|
25
|
+
"""
|
|
26
|
+
import datetime
|
|
27
|
+
import traceback
|
|
28
|
+
from typing import Dict, List, Tuple
|
|
29
|
+
|
|
30
|
+
import dask
|
|
31
|
+
import h5py
|
|
32
|
+
import pyproj
|
|
33
|
+
import pandas as pd
|
|
34
|
+
import numpy as np
|
|
35
|
+
import xarray as xr
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def _to_str(t: bytes) -> str:
|
|
39
|
+
"""
|
|
40
|
+
Transform binary into string.
|
|
41
|
+
"""
|
|
42
|
+
return t.decode("utf-8")
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def buffer(func):
|
|
46
|
+
"""
|
|
47
|
+
Decorator to catch and kill error message. Almost want to name the function
|
|
48
|
+
dont_fail.
|
|
49
|
+
"""
|
|
50
|
+
|
|
51
|
+
def wrapper(*args, **kwargs):
|
|
52
|
+
try:
|
|
53
|
+
rslt = func(*args, **kwargs)
|
|
54
|
+
except Exception:
|
|
55
|
+
traceback.print_exc()
|
|
56
|
+
rslt = None
|
|
57
|
+
return rslt
|
|
58
|
+
|
|
59
|
+
return wrapper
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def cartesian_to_geographic(x: np.ndarray, y: np.ndarray, lon0: float, lat0: float) -> Tuple[np.ndarray, np.ndarray]:
|
|
63
|
+
"""
|
|
64
|
+
Transform cartesian coordinates to lat/lon using the Azimuth Equidistant
|
|
65
|
+
projection.
|
|
66
|
+
|
|
67
|
+
Parameters:
|
|
68
|
+
===========
|
|
69
|
+
x: ndarray
|
|
70
|
+
x-axis cartesian coordinates.
|
|
71
|
+
y: ndarray
|
|
72
|
+
y-axis cartesian coordinates. Same dimension as x
|
|
73
|
+
lon0: float
|
|
74
|
+
Radar site longitude.
|
|
75
|
+
lat0: float
|
|
76
|
+
Radar site latitude.
|
|
77
|
+
|
|
78
|
+
Returns:
|
|
79
|
+
lon: ndarray
|
|
80
|
+
Longitude of each gate.
|
|
81
|
+
lat: ndarray
|
|
82
|
+
Latitude of each gate.
|
|
83
|
+
"""
|
|
84
|
+
georef = pyproj.Proj(f"+proj=aeqd +lon_0={lon0} +lat_0={lat0} +ellps=WGS84")
|
|
85
|
+
lon, lat = georef(x, y, inverse=True)
|
|
86
|
+
lon = lon.astype(np.float32)
|
|
87
|
+
lat = lat.astype(np.float32)
|
|
88
|
+
return lon, lat
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
@buffer
|
|
92
|
+
def check_nyquist(dset: xr.Dataset) -> None:
|
|
93
|
+
"""
|
|
94
|
+
Check if the dataset Nyquist velocity corresponds to the PRF information.
|
|
95
|
+
"""
|
|
96
|
+
wavelength = dset.attrs["wavelength"]
|
|
97
|
+
prf = dset.attrs["highprf"]
|
|
98
|
+
nyquist = dset.attrs["NI"]
|
|
99
|
+
ny_int = 1e-2 * prf * wavelength / 4
|
|
100
|
+
|
|
101
|
+
assert np.abs(nyquist - ny_int) < 0.5, "Nyquist not consistent with PRF"
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def coord_from_metadata(metadata: Dict) -> Tuple[np.ndarray, np.ndarray, np.ndarray]:
|
|
105
|
+
"""
|
|
106
|
+
Create the radar coordinates from the ODIM H5 metadata specification.
|
|
107
|
+
|
|
108
|
+
Parameter:
|
|
109
|
+
==========
|
|
110
|
+
metadata: dict()
|
|
111
|
+
Metadata dictionnary containing the specific ODIM H5 keys: astart,
|
|
112
|
+
nrays, nbins, rstart, rscale, elangle.
|
|
113
|
+
|
|
114
|
+
Returns:
|
|
115
|
+
========
|
|
116
|
+
r: ndarray<nbins>
|
|
117
|
+
Sweep range
|
|
118
|
+
azimuth: ndarray<nrays>
|
|
119
|
+
Sweep azimuth
|
|
120
|
+
elev: float
|
|
121
|
+
Sweep elevation
|
|
122
|
+
"""
|
|
123
|
+
da = 360 / metadata["nrays"]
|
|
124
|
+
azimuth = np.linspace(metadata["astart"] + da / 2, 360 - da, metadata["nrays"], dtype=np.float32)
|
|
125
|
+
|
|
126
|
+
# rstart is in KM !!! STUPID.
|
|
127
|
+
rstart_center = 1e3 * metadata["rstart"] + metadata["rscale"] / 2
|
|
128
|
+
r = np.arange(
|
|
129
|
+
rstart_center, rstart_center + metadata["nbins"] * metadata["rscale"], metadata["rscale"], dtype=np.float32
|
|
130
|
+
)
|
|
131
|
+
|
|
132
|
+
elev = np.array([metadata["elangle"]], dtype=np.float32)
|
|
133
|
+
return r, azimuth, elev
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def field_metadata(quantity_name: str) -> Dict:
|
|
137
|
+
"""
|
|
138
|
+
Populate metadata for common fields using Py-ART get_metadata() function.
|
|
139
|
+
(Optionnal).
|
|
140
|
+
|
|
141
|
+
Parameter:
|
|
142
|
+
==========
|
|
143
|
+
quantity_name: str
|
|
144
|
+
ODIM H5 quantity attribute name.
|
|
145
|
+
|
|
146
|
+
Returns:
|
|
147
|
+
========
|
|
148
|
+
attrs: dict()
|
|
149
|
+
Metadata dictionnary.
|
|
150
|
+
"""
|
|
151
|
+
try:
|
|
152
|
+
from pyart.config import get_metadata
|
|
153
|
+
except Exception:
|
|
154
|
+
return {}
|
|
155
|
+
ODIM_H5_FIELD_NAMES = {
|
|
156
|
+
"TH": "total_power", # uncorrected reflectivity, horizontal
|
|
157
|
+
"TV": "total_power", # uncorrected reflectivity, vertical
|
|
158
|
+
"DBZH": "reflectivity", # corrected reflectivity, horizontal
|
|
159
|
+
"DBZH_CLEAN": "reflectivity", # corrected reflectivity, horizontal
|
|
160
|
+
"DBZV": "reflectivity", # corrected reflectivity, vertical
|
|
161
|
+
"CLASS": "hydrometeor_classification",
|
|
162
|
+
"ZDR": "differential_reflectivity", # differential reflectivity
|
|
163
|
+
"RHOHV": "cross_correlation_ratio",
|
|
164
|
+
"LDR": "linear_polarization_ratio",
|
|
165
|
+
"PHIDP": "differential_phase",
|
|
166
|
+
"KDP": "specific_differential_phase",
|
|
167
|
+
"SQI": "normalized_coherent_power",
|
|
168
|
+
"SNR": "signal_to_noise_ratio",
|
|
169
|
+
"SNRH": "signal_to_noise_ratio",
|
|
170
|
+
"VRAD": "velocity", # radial velocity, marked for deprecation in ODIM HDF5 2.2
|
|
171
|
+
"VRADH": "velocity", # radial velocity, horizontal polarisation
|
|
172
|
+
"VRADDH": "corrected_velocity", # radial velocity, horizontal polarisation
|
|
173
|
+
"VRADV": "velocity", # radial velocity, vertical polarisation
|
|
174
|
+
"WRAD": "spectrum_width",
|
|
175
|
+
"QIND": "quality_index",
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
try:
|
|
179
|
+
fname = ODIM_H5_FIELD_NAMES[quantity_name]
|
|
180
|
+
attrs = get_metadata(fname)
|
|
181
|
+
attrs.pop("coordinates")
|
|
182
|
+
except KeyError:
|
|
183
|
+
return {}
|
|
184
|
+
|
|
185
|
+
return attrs
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
def generate_timestamp(stime: str, etime: str, nrays: int, a1gate: int) -> np.ndarray:
|
|
189
|
+
"""
|
|
190
|
+
Generate timestamp for each ray.
|
|
191
|
+
|
|
192
|
+
Parameters:
|
|
193
|
+
===========
|
|
194
|
+
stime: str
|
|
195
|
+
Sweep starting time.
|
|
196
|
+
etime:
|
|
197
|
+
Sweep ending time.
|
|
198
|
+
nrays: int
|
|
199
|
+
Number of rays in sweep.
|
|
200
|
+
a1gate: int
|
|
201
|
+
Azimuth of the ray measured first by the radar.
|
|
202
|
+
|
|
203
|
+
Returns:
|
|
204
|
+
========
|
|
205
|
+
trange: Timestamp<nrays>
|
|
206
|
+
Timestamp for each ray.
|
|
207
|
+
"""
|
|
208
|
+
sdtime = datetime.datetime.strptime(stime, "%Y%m%d_%H%M%S")
|
|
209
|
+
edtime = datetime.datetime.strptime(etime, "%Y%m%d_%H%M%S")
|
|
210
|
+
trange = pd.date_range(sdtime, edtime, nrays)
|
|
211
|
+
|
|
212
|
+
return np.roll(trange, a1gate)
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
def get_dataset_metadata(hfile, dataset: str = "dataset1") -> Tuple[Dict, Dict]:
|
|
216
|
+
"""
|
|
217
|
+
Get the dataset metadata of the ODIM H5 file.
|
|
218
|
+
|
|
219
|
+
Parameters:
|
|
220
|
+
===========
|
|
221
|
+
hfile: h5py.File
|
|
222
|
+
H5 file identifier.
|
|
223
|
+
dataset: str
|
|
224
|
+
Key of the dataset for which to extract the metadata
|
|
225
|
+
|
|
226
|
+
Returns:
|
|
227
|
+
========
|
|
228
|
+
metadata: dict
|
|
229
|
+
General metadata of the dataset.
|
|
230
|
+
coordinates_metadata: dict
|
|
231
|
+
Coordinates-specific metadata.
|
|
232
|
+
"""
|
|
233
|
+
metadata = dict()
|
|
234
|
+
coordinates_metadata = dict()
|
|
235
|
+
# General metadata
|
|
236
|
+
for k in ["NI", "highprf", "product"]:
|
|
237
|
+
try:
|
|
238
|
+
metadata[k] = hfile[f"/{dataset}/how"].attrs[k]
|
|
239
|
+
except Exception:
|
|
240
|
+
pass
|
|
241
|
+
|
|
242
|
+
sdate = _to_str(hfile[f"/{dataset}/what"].attrs["startdate"])
|
|
243
|
+
stime = _to_str(hfile[f"/{dataset}/what"].attrs["starttime"])
|
|
244
|
+
try:
|
|
245
|
+
edate = _to_str(hfile[f"/{dataset}/what"].attrs["enddate"])
|
|
246
|
+
etime = _to_str(hfile[f"/{dataset}/what"].attrs["endtime"])
|
|
247
|
+
except Exception:
|
|
248
|
+
edate = sdate
|
|
249
|
+
etime = stime
|
|
250
|
+
metadata["start_time"] = f"{sdate}_{stime}"
|
|
251
|
+
metadata["end_time"] = f"{edate}_{etime}"
|
|
252
|
+
|
|
253
|
+
# Coordinates:
|
|
254
|
+
try:
|
|
255
|
+
coordinates_metadata["astart"] = hfile[f"/{dataset}/how"].attrs["astart"]
|
|
256
|
+
except KeyError:
|
|
257
|
+
# Optional coordinates (!).
|
|
258
|
+
coordinates_metadata["astart"] = 0
|
|
259
|
+
coordinates_metadata["a1gate"] = hfile[f"/{dataset}/where"].attrs["a1gate"]
|
|
260
|
+
coordinates_metadata["nrays"] = hfile[f"/{dataset}/where"].attrs["nrays"]
|
|
261
|
+
|
|
262
|
+
coordinates_metadata["rstart"] = hfile[f"/{dataset}/where"].attrs["rstart"]
|
|
263
|
+
coordinates_metadata["rscale"] = hfile[f"/{dataset}/where"].attrs["rscale"]
|
|
264
|
+
coordinates_metadata["nbins"] = hfile[f"/{dataset}/where"].attrs["nbins"]
|
|
265
|
+
|
|
266
|
+
coordinates_metadata["elangle"] = hfile[f"/{dataset}/where"].attrs["elangle"]
|
|
267
|
+
|
|
268
|
+
return metadata, coordinates_metadata
|
|
269
|
+
|
|
270
|
+
|
|
271
|
+
def get_root_metadata(hfile) -> Dict:
|
|
272
|
+
"""
|
|
273
|
+
Get the metadata at the root of the ODIM H5 file.
|
|
274
|
+
|
|
275
|
+
Parameters:
|
|
276
|
+
===========
|
|
277
|
+
hfile: h5py.File
|
|
278
|
+
H5 file identifier.
|
|
279
|
+
|
|
280
|
+
Returns:
|
|
281
|
+
========
|
|
282
|
+
rootmetadata: dict
|
|
283
|
+
Metadata at the root of the ODIM H5 file.
|
|
284
|
+
"""
|
|
285
|
+
rootmetadata = {}
|
|
286
|
+
# Root
|
|
287
|
+
try:
|
|
288
|
+
rootmetadata["Conventions"] = _to_str(hfile.attrs["Conventions"])
|
|
289
|
+
except KeyError:
|
|
290
|
+
rootmetadata["Conventions"] = None
|
|
291
|
+
|
|
292
|
+
# Where
|
|
293
|
+
rootmetadata["latitude"] = hfile["/where"].attrs["lat"]
|
|
294
|
+
rootmetadata["longitude"] = hfile["/where"].attrs["lon"]
|
|
295
|
+
rootmetadata["height"] = hfile["/where"].attrs["height"]
|
|
296
|
+
|
|
297
|
+
# What
|
|
298
|
+
try:
|
|
299
|
+
sdate = _to_str(hfile["/what"].attrs["date"])
|
|
300
|
+
stime = _to_str(hfile["/what"].attrs["time"])
|
|
301
|
+
rootmetadata["date"] = datetime.datetime.strptime(sdate + stime, "%Y%m%d%H%M%S").isoformat()
|
|
302
|
+
except KeyError:
|
|
303
|
+
rootmetadata["date"] = None
|
|
304
|
+
for k in ["object", "source", "version"]:
|
|
305
|
+
try:
|
|
306
|
+
rootmetadata[k] = _to_str(hfile["/what"].attrs[k])
|
|
307
|
+
except KeyError:
|
|
308
|
+
pass
|
|
309
|
+
|
|
310
|
+
# How
|
|
311
|
+
for k in ["beamwH", "beamwV", "rpm", "wavelength"]:
|
|
312
|
+
try:
|
|
313
|
+
rootmetadata[k] = hfile["/how"].attrs[k]
|
|
314
|
+
except KeyError:
|
|
315
|
+
pass
|
|
316
|
+
|
|
317
|
+
try:
|
|
318
|
+
rootmetadata["copyright"] = _to_str(hfile["/how"].attrs["copyright"])
|
|
319
|
+
except KeyError:
|
|
320
|
+
pass
|
|
321
|
+
|
|
322
|
+
return rootmetadata
|
|
323
|
+
|
|
324
|
+
|
|
325
|
+
def radar_coordinates_to_xyz(
|
|
326
|
+
r: np.ndarray, azimuth: np.ndarray, elevation: np.ndarray
|
|
327
|
+
) -> Tuple[np.ndarray, np.ndarray, np.ndarray]:
|
|
328
|
+
"""
|
|
329
|
+
Transform radar coordinates to cartesian coordinates.
|
|
330
|
+
|
|
331
|
+
Parameters:
|
|
332
|
+
===========
|
|
333
|
+
r: ndarray<nbins>
|
|
334
|
+
Sweep range.
|
|
335
|
+
azimuth: ndarray<nrays>
|
|
336
|
+
Sweep azimuth.
|
|
337
|
+
elevation: float
|
|
338
|
+
Sweep elevation.
|
|
339
|
+
|
|
340
|
+
Returns:
|
|
341
|
+
========
|
|
342
|
+
x, y, z: ndarray<nrays, nbins>
|
|
343
|
+
XYZ cartesian coordinates.
|
|
344
|
+
"""
|
|
345
|
+
# To proper spherical coordinates.
|
|
346
|
+
theta = np.deg2rad(90 - elevation)
|
|
347
|
+
phi = 450 - azimuth
|
|
348
|
+
phi[phi >= 360] -= 360
|
|
349
|
+
phi = np.deg2rad(phi)
|
|
350
|
+
|
|
351
|
+
R, PHI = np.meshgrid(r, phi)
|
|
352
|
+
R = R.astype(np.float32)
|
|
353
|
+
PHI = PHI.astype(np.float32)
|
|
354
|
+
|
|
355
|
+
x = R * np.sin(theta) * np.cos(PHI)
|
|
356
|
+
y = R * np.sin(theta) * np.sin(PHI)
|
|
357
|
+
z = R * np.cos(theta)
|
|
358
|
+
|
|
359
|
+
return x, y, z
|
|
360
|
+
|
|
361
|
+
|
|
362
|
+
def read_odim_slice(
|
|
363
|
+
odim_file: str, nslice: int = 0, include_fields: List = [], exclude_fields: List = [], check_NI: bool = False
|
|
364
|
+
) -> xr.Dataset:
|
|
365
|
+
"""
|
|
366
|
+
Read into an xarray dataset one sweep of the ODIM file.
|
|
367
|
+
|
|
368
|
+
Parameters:
|
|
369
|
+
===========
|
|
370
|
+
odim_file: str
|
|
371
|
+
ODIM H5 filename.
|
|
372
|
+
nslice: int
|
|
373
|
+
Slice number we want to extract (start indexing at 0).
|
|
374
|
+
include_fields: list
|
|
375
|
+
Specific fields to be exclusively read.
|
|
376
|
+
exclude_fields: list
|
|
377
|
+
Specific fields to be excluded from reading.
|
|
378
|
+
check_NI: bool
|
|
379
|
+
Check NI parameter in ODIM file and compare it to the PRF.
|
|
380
|
+
|
|
381
|
+
Returns:
|
|
382
|
+
========
|
|
383
|
+
dataset: xarray.Dataset
|
|
384
|
+
xarray dataset of one sweep of the ODIM file.
|
|
385
|
+
"""
|
|
386
|
+
# if nslice == 0:
|
|
387
|
+
# raise ValueError('Slice numbering start at 1.')
|
|
388
|
+
if type(include_fields) is not list:
|
|
389
|
+
raise TypeError("Argument `include_fields` should be a list")
|
|
390
|
+
|
|
391
|
+
with h5py.File(odim_file, "r") as hfile:
|
|
392
|
+
# Number of sweep in dataset
|
|
393
|
+
nsweep = len([k for k in hfile["/"].keys() if k.startswith("dataset")])
|
|
394
|
+
assert nslice <= nsweep, f"Wrong slice number asked. Only {nsweep} available."
|
|
395
|
+
|
|
396
|
+
# Order datasets by increasing elevations.
|
|
397
|
+
sweeps = dict()
|
|
398
|
+
for key in hfile["/"].keys():
|
|
399
|
+
if key.startswith("dataset"):
|
|
400
|
+
sweeps[key] = hfile[f"/{key}/where"].attrs["elangle"]
|
|
401
|
+
|
|
402
|
+
sorted_keys = sorted(sweeps, key=lambda k: sweeps[k])
|
|
403
|
+
rootkey = sorted_keys[nslice]
|
|
404
|
+
|
|
405
|
+
# Retrieve dataset metadata and coordinates metadata.
|
|
406
|
+
metadata, coordinates_metadata = get_dataset_metadata(hfile, rootkey)
|
|
407
|
+
|
|
408
|
+
dataset = xr.Dataset()
|
|
409
|
+
dataset.attrs = get_root_metadata(hfile)
|
|
410
|
+
dataset.attrs.update(metadata)
|
|
411
|
+
if check_NI:
|
|
412
|
+
try:
|
|
413
|
+
check_nyquist(dataset)
|
|
414
|
+
except AssertionError:
|
|
415
|
+
print("Nyquist not consistent with PRF")
|
|
416
|
+
pass
|
|
417
|
+
|
|
418
|
+
for datakey in hfile[f"/{rootkey}"].keys():
|
|
419
|
+
if not datakey.startswith("data"):
|
|
420
|
+
continue
|
|
421
|
+
|
|
422
|
+
try:
|
|
423
|
+
gain = hfile[f"/{rootkey}/{datakey}/what"].attrs["gain"]
|
|
424
|
+
except KeyError:
|
|
425
|
+
gain = 1
|
|
426
|
+
try:
|
|
427
|
+
nodata = hfile[f"/{rootkey}/{datakey}/what"].attrs["nodata"]
|
|
428
|
+
except KeyError:
|
|
429
|
+
nodata = np.NaN
|
|
430
|
+
try:
|
|
431
|
+
offset = hfile[f"/{rootkey}/{datakey}/what"].attrs["offset"]
|
|
432
|
+
except KeyError:
|
|
433
|
+
offset = 0
|
|
434
|
+
name = _to_str(hfile[f"/{rootkey}/{datakey}/what"].attrs["quantity"])
|
|
435
|
+
# Check if field should be read.
|
|
436
|
+
if len(include_fields) > 0:
|
|
437
|
+
if name not in include_fields:
|
|
438
|
+
continue
|
|
439
|
+
if name in exclude_fields:
|
|
440
|
+
continue
|
|
441
|
+
|
|
442
|
+
dtype = hfile[f"/{rootkey}/{datakey}/data"][:].dtype
|
|
443
|
+
data_value = hfile[f"/{rootkey}/{datakey}/data"][:].astype(float).copy()
|
|
444
|
+
data_value[np.abs(data_value - nodata) <= 0.1] = np.NaN
|
|
445
|
+
data_value = gain * data_value + offset
|
|
446
|
+
data_value[np.abs(data_value - nodata * gain) <= 0.1] = np.NaN
|
|
447
|
+
dataset = dataset.merge({name: (("azimuth", "range"), data_value)})
|
|
448
|
+
dataset[name].attrs = field_metadata(name)
|
|
449
|
+
dataset[name].attrs["gain"] = np.float32(gain)
|
|
450
|
+
dataset[name].attrs["nodata"] = nodata
|
|
451
|
+
dataset[name].attrs["offset"] = offset
|
|
452
|
+
dataset[name].attrs["dtype"] = dtype
|
|
453
|
+
|
|
454
|
+
time = generate_timestamp(
|
|
455
|
+
metadata["start_time"], metadata["end_time"], coordinates_metadata["nrays"], coordinates_metadata["a1gate"]
|
|
456
|
+
)
|
|
457
|
+
r, azi, elev = coord_from_metadata(coordinates_metadata)
|
|
458
|
+
x, y, z = radar_coordinates_to_xyz(r, azi, elev)
|
|
459
|
+
longitude, latitude = cartesian_to_geographic(x, y, dataset.attrs["longitude"], dataset.attrs["latitude"])
|
|
460
|
+
|
|
461
|
+
dataset = dataset.merge(
|
|
462
|
+
{
|
|
463
|
+
"range": (("range"), r),
|
|
464
|
+
"azimuth": (("azimuth"), azi),
|
|
465
|
+
"elevation": (("elevation"), elev),
|
|
466
|
+
"time": (("time"), time),
|
|
467
|
+
"x": (("azimuth", "range"), x),
|
|
468
|
+
"y": (("azimuth", "range"), y),
|
|
469
|
+
"z": (("azimuth", "range"), z + dataset.attrs["height"]),
|
|
470
|
+
"longitude": (("azimuth", "range"), longitude),
|
|
471
|
+
"latitude": (("azimuth", "range"), latitude),
|
|
472
|
+
}
|
|
473
|
+
)
|
|
474
|
+
|
|
475
|
+
return dataset
|
|
476
|
+
|
|
477
|
+
|
|
478
|
+
def read_odim(odim_file: str, lazy_load: bool = True, **kwargs) -> List:
|
|
479
|
+
"""
|
|
480
|
+
Read an ODIM H5 file.
|
|
481
|
+
|
|
482
|
+
Parameters:
|
|
483
|
+
===========
|
|
484
|
+
odim_file: str
|
|
485
|
+
ODIM H5 filename.
|
|
486
|
+
lazy_load: bool
|
|
487
|
+
Lazily load the data if true, read and load in memory the entire dataset
|
|
488
|
+
if false.
|
|
489
|
+
include_fields: list
|
|
490
|
+
Specific fields to be exclusively read.
|
|
491
|
+
exclude_fields: list
|
|
492
|
+
Specific fields to be excluded from reading.
|
|
493
|
+
|
|
494
|
+
Returns:
|
|
495
|
+
========
|
|
496
|
+
radar: list
|
|
497
|
+
List of xarray datasets, each item in a the list is one sweep of the
|
|
498
|
+
radar data (ordered from lowest elevation scan to highest).
|
|
499
|
+
"""
|
|
500
|
+
with h5py.File(odim_file, "r") as hfile:
|
|
501
|
+
nsweep = len([k for k in hfile["/"].keys() if k.startswith("dataset")])
|
|
502
|
+
|
|
503
|
+
radar = []
|
|
504
|
+
for sweep in range(0, nsweep):
|
|
505
|
+
c = dask.delayed(read_odim_slice)(odim_file, sweep, **kwargs)
|
|
506
|
+
radar.append(c)
|
|
507
|
+
|
|
508
|
+
if not lazy_load:
|
|
509
|
+
radar = [r.compute() for r in radar]
|
|
510
|
+
|
|
511
|
+
return radar
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: pyodim
|
|
3
|
+
Version: 0.4
|
|
4
|
+
Summary: An ODIM hdf5 file reader.
|
|
5
|
+
Home-page: https://github.com/vlouf/pyodim
|
|
6
|
+
Author: Valentin Louf
|
|
7
|
+
Author-email: valentin.louf@bom.gov.au
|
|
8
|
+
Project-URL: Bug Reports, https://github.com/vlouf/pyodim/issues
|
|
9
|
+
Project-URL: Source, https://github.com/vlouf/pyodim/
|
|
10
|
+
Keywords: odim h5 file reader
|
|
11
|
+
Classifier: Development Status :: 4 - Beta
|
|
12
|
+
Classifier: Intended Audience :: Science/Research
|
|
13
|
+
Classifier: Topic :: Scientific/Engineering :: Atmospheric Science
|
|
14
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.7
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
20
|
+
Description-Content-Type: text/markdown
|
|
21
|
+
License-File: LICENSE
|
|
22
|
+
|
|
23
|
+
# PYODIM
|
|
24
|
+
|
|
25
|
+
A simple ODIM H5 (Opera Data Information Model - Hierarchical Data Format
|
|
26
|
+
version 5) radar reader in Python. It outputs the radar date in an xarray
|
|
27
|
+
Dataset. The goal is to be as barebone as possible while providing an xarray,
|
|
28
|
+
so that it can easily scale up (production) or just a very lean reader for
|
|
29
|
+
quicklooks into the data.
|
|
30
|
+
|
|
31
|
+
## Example
|
|
32
|
+
|
|
33
|
+
```python
|
|
34
|
+
|
|
35
|
+
import pyodim
|
|
36
|
+
|
|
37
|
+
filename = "/path/to/radar/file.h5"
|
|
38
|
+
rset = pyodim.read_odim(filename, lazy_load=True) # Lazy load does not load the data in memory to save time if you are interested in only a specific sweep
|
|
39
|
+
radar = rset[0].compute() # All sweeps are in a list (elevation ascending), so the 1st item of the list is the bottom sweep.
|
|
40
|
+
print(radar)
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
This code will output the radar data for the first sweep (lowest elevation
|
|
44
|
+
scan) in the radar file as an xarray Dataset, for example:
|
|
45
|
+
|
|
46
|
+
```python
|
|
47
|
+
<xarray.Dataset>
|
|
48
|
+
Dimensions: (azimuth: 360, range: 1283, elevation: 1, time: 360)
|
|
49
|
+
Coordinates:
|
|
50
|
+
* range (range) float32 125.0 375.0 625.0 ... 3.204e+05 3.206e+05
|
|
51
|
+
* azimuth (azimuth) float32 0.01111 1.011 2.011 ... 357.0 358.0 359.0
|
|
52
|
+
* elevation (elevation) float32 0.5
|
|
53
|
+
* time (time) datetime64[ns] 2024-01-16T02:49:29.337047353 ... 2024-...
|
|
54
|
+
Data variables: (12/17)
|
|
55
|
+
DBZH (azimuth, range) float64 nan nan nan nan nan ... nan nan nan nan
|
|
56
|
+
SNRH (azimuth, range) float64 nan nan nan nan nan ... nan nan nan nan
|
|
57
|
+
VRADH (azimuth, range) float64 nan nan nan nan nan ... nan nan nan nan
|
|
58
|
+
WRADH (azimuth, range) float64 nan nan nan nan nan ... nan nan nan nan
|
|
59
|
+
KDP (azimuth, range) float64 nan nan nan nan nan ... nan nan nan nan
|
|
60
|
+
... ...
|
|
61
|
+
x (azimuth, range) float32 0.02424 0.07271 ... -5.595e+03
|
|
62
|
+
y (azimuth, range) float32 125.0 375.0 ... 3.203e+05 3.206e+05
|
|
63
|
+
z (azimuth, range) float32 45.09 47.27 ... 2.84e+03 2.842e+03
|
|
64
|
+
longitude (azimuth, range) float32 144.8 144.8 144.8 ... 144.7 144.7 144.7
|
|
65
|
+
latitude (azimuth, range) float32 -37.85 -37.85 -37.85 ... -34.97 -34.96
|
|
66
|
+
Attributes: (12/15)
|
|
67
|
+
Conventions: ODIM_H5/V2_4
|
|
68
|
+
latitude: -37.85200119018555
|
|
69
|
+
longitude: 144.75199890136722
|
|
70
|
+
height: 44.0
|
|
71
|
+
date: 2024-01-16T02:45:21
|
|
72
|
+
object: PVOL
|
|
73
|
+
... ...
|
|
74
|
+
beamwV: 1.0
|
|
75
|
+
wavelength: 10.489999771118164
|
|
76
|
+
NI: 36.5052
|
|
77
|
+
highprf: 696.0
|
|
78
|
+
start_time: 20240116_024928
|
|
79
|
+
end_time: 20240116_024958
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
PYODIM reads the ODIM radar data and metadata. It also provides some quality of
|
|
83
|
+
life feature like automatically creating the x, y, z and latitude/longitude
|
|
84
|
+
coordinates for all radar gates (using the azimuthal equidistant - `aeqd` - projection). By
|
|
85
|
+
default it uses `dask.delayed` to *lazily* load the data, meaning that it will
|
|
86
|
+
not actually load all the sweeps in memory, as for many applications we often
|
|
87
|
+
don't need all the radar sweeps. Instead, by calling the `.compute()` method
|
|
88
|
+
like in the example above, PYODIM will only read and load in memory the sweep
|
|
89
|
+
that you want, saving time. If you want to read all the sweeps at once in a
|
|
90
|
+
list, then just set `lazy_load=False`.
|
|
91
|
+
|
|
92
|
+
## Dependencies
|
|
93
|
+
|
|
94
|
+
Mandatory:
|
|
95
|
+
- [numpy][1]
|
|
96
|
+
- [xarray][2]
|
|
97
|
+
- [pyproj][3]
|
|
98
|
+
- [dask][4]
|
|
99
|
+
|
|
100
|
+
And optionally (it will automatically populate the fields metadata using pyart
|
|
101
|
+
if available):
|
|
102
|
+
- [Py-ART][5]
|
|
103
|
+
|
|
104
|
+
[1]: http://www.scipy.org/
|
|
105
|
+
[2]: http://numba.pydata.org
|
|
106
|
+
[3]: https://pypi.org/project/pyproj/
|
|
107
|
+
[4]: https://www.dask.org/
|
|
108
|
+
[5]: https://github.com/ARM-DOE/pyart
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
.gitignore
|
|
2
|
+
LICENSE
|
|
3
|
+
README.md
|
|
4
|
+
setup.py
|
|
5
|
+
pyodim/__init__.py
|
|
6
|
+
pyodim/__version__.py
|
|
7
|
+
pyodim/pyodim.py
|
|
8
|
+
pyodim.egg-info/PKG-INFO
|
|
9
|
+
pyodim.egg-info/SOURCES.txt
|
|
10
|
+
pyodim.egg-info/dependency_links.txt
|
|
11
|
+
pyodim.egg-info/requires.txt
|
|
12
|
+
pyodim.egg-info/top_level.txt
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
pyodim
|
pyodim-0.4/setup.cfg
ADDED
pyodim-0.4/setup.py
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
|
|
4
|
+
from os import path
|
|
5
|
+
from setuptools import find_packages, setup
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
here = path.abspath(path.dirname(__file__))
|
|
9
|
+
# Get the long description from the README file
|
|
10
|
+
with open(path.join(here, "README.md"), encoding="utf-8") as f:
|
|
11
|
+
long_description = f.read()
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
setup(
|
|
15
|
+
name="pyodim",
|
|
16
|
+
version="0.4",
|
|
17
|
+
description="An ODIM hdf5 file reader.",
|
|
18
|
+
long_description=long_description,
|
|
19
|
+
long_description_content_type="text/markdown",
|
|
20
|
+
url="https://github.com/vlouf/pyodim",
|
|
21
|
+
author="Valentin Louf",
|
|
22
|
+
author_email="valentin.louf@bom.gov.au",
|
|
23
|
+
classifiers=[
|
|
24
|
+
"Development Status :: 4 - Beta",
|
|
25
|
+
"Intended Audience :: Science/Research",
|
|
26
|
+
"Topic :: Scientific/Engineering :: Atmospheric Science",
|
|
27
|
+
"License :: OSI Approved :: MIT License",
|
|
28
|
+
"Programming Language :: Python :: 3.7",
|
|
29
|
+
"Programming Language :: Python :: 3.8",
|
|
30
|
+
"Programming Language :: Python :: 3.9",
|
|
31
|
+
"Programming Language :: Python :: 3.10",
|
|
32
|
+
"Programming Language :: Python :: 3.11",
|
|
33
|
+
],
|
|
34
|
+
keywords="odim h5 file reader", # Optional
|
|
35
|
+
packages=find_packages(exclude=["contrib", "docs", "tests"]),
|
|
36
|
+
install_requires= ["numpy", "dask", "xarray", "h5py", "pyproj"],
|
|
37
|
+
project_urls={
|
|
38
|
+
"Bug Reports": "https://github.com/vlouf/pyodim/issues",
|
|
39
|
+
"Source": "https://github.com/vlouf/pyodim/",
|
|
40
|
+
},
|
|
41
|
+
)
|