Skip to content

Commit 8f2753a

Browse files
ENH: Accessors (#21)
* DOC: add importers.md, documentation on importers * ENH: add accessors.py submodule, add Accessors showcase Co-authored-by: Max Grover <[email protected]>
1 parent faf4504 commit 8f2753a

File tree

5 files changed

+364
-0
lines changed

5 files changed

+364
-0
lines changed

docs/history.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
* Improve installation and contribution guide, update README.md with more badges, add version and date of release to docs, update install process ({pull}`19`) by [@kmuehlbauer](https://github.com/kmuehlbauer)
66
* Add minimal documentation for CfRadial1 and ODIM_H5 importers ({pull}`20`) by [@kmuehlbauer](https://github.com/kmuehlbauer)
7+
* Add accessors.py submodule, add accessors showcase ({pull}`21`) by [@kmuehlbauer](https://github.com/kmuehlbauer)
78

89
## 0.5.0 (2022-09-14)
910

docs/usage.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import xradar
1515
:caption: Get started
1616
1717
importers
18+
notebooks/Accessors
1819
```
1920

2021
```{toctree}

examples/notebooks/Accessors.ipynb

Lines changed: 311 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,311 @@
1+
{
2+
"cells": [
3+
{
4+
"cell_type": "markdown",
5+
"id": "b5dbd013-e1bb-47bf-906c-1806092a9eb7",
6+
"metadata": {},
7+
"source": [
8+
"# Accessors"
9+
]
10+
},
11+
{
12+
"cell_type": "markdown",
13+
"id": "50eb9058-05ec-496f-951b-e15e75668daa",
14+
"metadata": {},
15+
"source": [
16+
"To extend `xarray.DataArray` and `xarray.Dataset`\n",
17+
"xradar aims to provide accessors which downstream libraries can hook into.\n",
18+
"\n",
19+
"Those accessors are yet to be defined. For starters we could implement purpose-based\n",
20+
"accessors (like `.vis`, `.kdp` or `.trafo`) on `xarray.DataArray` level.\n",
21+
"\n",
22+
"To not have to import downstream packages a similar approach to xarray.backends using\n",
23+
"`importlib.metadata.entry_points` could be facilitated.\n",
24+
"\n",
25+
"In this notebook the creation of such an accessor is showcased."
26+
]
27+
},
28+
{
29+
"cell_type": "code",
30+
"execution_count": null,
31+
"id": "514e9883-ca69-47b2-bced-35611c704342",
32+
"metadata": {},
33+
"outputs": [],
34+
"source": [
35+
"import os\n",
36+
"import xarray as xr\n",
37+
"import numpy as np\n",
38+
"import xarray as xr\n",
39+
"import xradar as xd\n",
40+
"from urllib.parse import urljoin\n",
41+
"from urllib.request import urlretrieve"
42+
]
43+
},
44+
{
45+
"cell_type": "markdown",
46+
"id": "aaddab67-6a24-4dbf-8646-b260a76983fc",
47+
"metadata": {},
48+
"source": [
49+
"## Import Data"
50+
]
51+
},
52+
{
53+
"cell_type": "code",
54+
"execution_count": null,
55+
"id": "fb75b7c5-9522-4029-849f-bcc702ad555a",
56+
"metadata": {},
57+
"outputs": [],
58+
"source": [
59+
"def fetch_odim_file():\n",
60+
" fname = \"odim_data.nc\"\n",
61+
" if not os.path.exists(fname):\n",
62+
" base_url = \"https://raw.githubusercontent.com/wradlib/wradlib-data/main/hdf5/\"\n",
63+
" filename = \"71_20181220_060628.pvol.h5\"\n",
64+
" url = urljoin(base_url, filename)\n",
65+
" urlretrieve(url, filename=fname)\n",
66+
" return fname\n",
67+
"\n",
68+
"\n",
69+
"filename = fetch_odim_file()"
70+
]
71+
},
72+
{
73+
"cell_type": "markdown",
74+
"id": "0ed04dfa-eccc-4ca6-8864-25ce467518e2",
75+
"metadata": {
76+
"tags": []
77+
},
78+
"source": [
79+
"### Open data"
80+
]
81+
},
82+
{
83+
"cell_type": "code",
84+
"execution_count": null,
85+
"id": "780c1f2f-db6b-4ea6-85d5-1066d01511cc",
86+
"metadata": {},
87+
"outputs": [],
88+
"source": [
89+
"ds = xr.open_dataset(filename, group=\"dataset1\", engine=\"odim\")\n",
90+
"display(ds.DBZH.values)"
91+
]
92+
},
93+
{
94+
"cell_type": "markdown",
95+
"id": "7f042c04-7355-4774-97f3-beb86c77d5fe",
96+
"metadata": {},
97+
"source": [
98+
"### Plot DBZH"
99+
]
100+
},
101+
{
102+
"cell_type": "code",
103+
"execution_count": null,
104+
"id": "a60e5fa1-8f31-46e2-a497-4477b4513485",
105+
"metadata": {},
106+
"outputs": [],
107+
"source": [
108+
"ds.DBZH.plot()"
109+
]
110+
},
111+
{
112+
"cell_type": "markdown",
113+
"id": "5989665a-f33d-4403-9635-a786701978bb",
114+
"metadata": {},
115+
"source": [
116+
"## Define two example functions\n",
117+
"\n",
118+
"Functions copied verbatim from wradlib."
119+
]
120+
},
121+
{
122+
"cell_type": "code",
123+
"execution_count": null,
124+
"id": "86f082bc-82e9-4cf6-9919-f3455cb1ee80",
125+
"metadata": {},
126+
"outputs": [],
127+
"source": [
128+
"def _decibel(x):\n",
129+
" \"\"\"Calculates the decibel representation of the input values\n",
130+
"\n",
131+
" :math:`dBZ=10 \\\\cdot \\\\log_{10} z`\n",
132+
"\n",
133+
" Parameters\n",
134+
" ----------\n",
135+
" x : float or :class:`numpy:numpy.ndarray`\n",
136+
" (must not be <= 0.)\n",
137+
"\n",
138+
" Examples\n",
139+
" --------\n",
140+
" >>> from wradlib.trafo import decibel\n",
141+
" >>> print(decibel(100.))\n",
142+
" 20.0\n",
143+
" \"\"\"\n",
144+
" return 10.0 * np.log10(x)\n",
145+
"\n",
146+
"\n",
147+
"def _idecibel(x):\n",
148+
" \"\"\"Calculates the inverse of input decibel values\n",
149+
"\n",
150+
" :math:`z=10^{x \\\\over 10}`\n",
151+
"\n",
152+
" Parameters\n",
153+
" ----------\n",
154+
" x : float or :class:`numpy:numpy.ndarray`\n",
155+
"\n",
156+
" Examples\n",
157+
" --------\n",
158+
" >>> from wradlib.trafo import idecibel\n",
159+
" >>> print(idecibel(10.))\n",
160+
" 10.0\n",
161+
"\n",
162+
" \"\"\"\n",
163+
" return 10.0 ** (x / 10.0)"
164+
]
165+
},
166+
{
167+
"cell_type": "markdown",
168+
"id": "d8010d2b-a388-4f9d-b831-cbb708997f5b",
169+
"metadata": {},
170+
"source": [
171+
"## Function dictionaries\n",
172+
"\n",
173+
"To show the import of the functions, we put them in different dictionaries as we would get them via `entry_points`. \n",
174+
"\n",
175+
"This is what the downstream libraries would have to provide."
176+
]
177+
},
178+
{
179+
"cell_type": "code",
180+
"execution_count": null,
181+
"id": "90072b9e-63da-42ee-9780-cf6e5763f03f",
182+
"metadata": {},
183+
"outputs": [],
184+
"source": [
185+
"package_1_func = {\"trafo\": {\"decibel\": _decibel}}\n",
186+
"package_2_func = {\"trafo\": {\"idecibel\": _idecibel}}"
187+
]
188+
},
189+
{
190+
"cell_type": "markdown",
191+
"id": "d5136dcc-41c5-4216-a1f6-c881e36a9c5c",
192+
"metadata": {},
193+
"source": [
194+
"## xradar internal functionality\n",
195+
"\n",
196+
"This is how xradar would need to treat that input data."
197+
]
198+
},
199+
{
200+
"cell_type": "code",
201+
"execution_count": null,
202+
"id": "6a3672f7-dd47-4320-ab5f-f49e470d22e1",
203+
"metadata": {},
204+
"outputs": [],
205+
"source": [
206+
"downstream_functions = [package_1_func, package_2_func]\n",
207+
"xradar_accessors = [\"trafo\"]"
208+
]
209+
},
210+
{
211+
"cell_type": "code",
212+
"execution_count": null,
213+
"id": "e1834590-0e30-4642-92b6-c761a2ba647a",
214+
"metadata": {},
215+
"outputs": [],
216+
"source": [
217+
"package_functions = {}\n",
218+
"for accessor in xradar_accessors:\n",
219+
" package_functions[accessor] = {}\n",
220+
" for dfuncs in downstream_functions:\n",
221+
" package_functions[accessor].update(dfuncs[accessor])\n",
222+
"print(package_functions)"
223+
]
224+
},
225+
{
226+
"cell_type": "markdown",
227+
"id": "1ccea2b6-3dbf-4195-a363-b3063ef88775",
228+
"metadata": {},
229+
"source": [
230+
"## Create and register accessor\n",
231+
"\n",
232+
"We bundle the different steps into one function, ``create_xradar_dataarray_accessor``."
233+
]
234+
},
235+
{
236+
"cell_type": "code",
237+
"execution_count": null,
238+
"id": "3a6f1530-3ab4-4d7e-8654-3fa9b3c40567",
239+
"metadata": {},
240+
"outputs": [],
241+
"source": [
242+
"for accessor in xradar_accessors:\n",
243+
" xd.accessors.create_xradar_dataarray_accessor(accessor, package_functions[accessor])"
244+
]
245+
},
246+
{
247+
"cell_type": "markdown",
248+
"id": "d6835753-a9f3-45e4-89be-29eeeda73d58",
249+
"metadata": {},
250+
"source": [
251+
"## Convert DBZH to linear and plot"
252+
]
253+
},
254+
{
255+
"cell_type": "code",
256+
"execution_count": null,
257+
"id": "b660c563-0dfa-4617-b29d-02b1fc8522d1",
258+
"metadata": {},
259+
"outputs": [],
260+
"source": [
261+
"z = ds.DBZH.trafo.idecibel()\n",
262+
"z.plot()"
263+
]
264+
},
265+
{
266+
"cell_type": "markdown",
267+
"id": "7d5505b2-892d-452a-a22b-fa21783a3598",
268+
"metadata": {},
269+
"source": [
270+
"## Convert z to decibel and plot()"
271+
]
272+
},
273+
{
274+
"cell_type": "code",
275+
"execution_count": null,
276+
"id": "44698c87-e97b-4538-9dcd-07f6821988c3",
277+
"metadata": {},
278+
"outputs": [],
279+
"source": [
280+
"dbz = z.trafo.decibel()\n",
281+
"display(dbz)"
282+
]
283+
},
284+
{
285+
"cell_type": "code",
286+
"execution_count": null,
287+
"id": "faf7c0a3-a975-42e9-ab13-93c725783964",
288+
"metadata": {},
289+
"outputs": [],
290+
"source": [
291+
"dbz.plot()"
292+
]
293+
}
294+
],
295+
"metadata": {
296+
"language_info": {
297+
"codemirror_mode": {
298+
"name": "ipython",
299+
"version": 3
300+
},
301+
"file_extension": ".py",
302+
"mimetype": "text/x-python",
303+
"name": "python",
304+
"nbconvert_exporter": "python",
305+
"pygments_lexer": "ipython3",
306+
"version": "3.9.13"
307+
}
308+
},
309+
"nbformat": 4,
310+
"nbformat_minor": 5
311+
}

xradar/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
__version__ = "999"
1919

2020
# import subpackages
21+
from . import accessors # noqa
2122
from . import io # noqa
2223
from . import model # noqa
2324
from . import util # noqa

xradar/accessors.py

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
#!/usr/bin/env python
2+
# Copyright (c) 2022, openradar developers.
3+
# Distributed under the MIT License. See LICENSE for more info.
4+
5+
"""
6+
XRadar Accessors
7+
================
8+
9+
To extend :py:class:`xarray:xarray.DataArray` and :py:class:`xarray:xarray.Dataset`
10+
xradar provides accessors which downstream libraries can hook into.
11+
12+
This module contains the functionality to create those accessors.
13+
14+
.. autosummary::
15+
:nosignatures:
16+
:toctree: generated/
17+
18+
{}
19+
"""
20+
21+
__all__ = ["create_xradar_dataarray_accessor"]
22+
23+
__doc__ = __doc__.format("\n ".join(__all__))
24+
25+
import xarray as xr
26+
27+
28+
def accessor_constructor(self, xarray_obj):
29+
self._obj = xarray_obj
30+
31+
32+
def create_function(func):
33+
def function(self):
34+
return func(self._obj)
35+
36+
return function
37+
38+
39+
def create_methods(funcs):
40+
methods = {}
41+
for name, func in funcs.items():
42+
methods[name] = create_function(func)
43+
return methods
44+
45+
46+
def create_xradar_dataarray_accessor(name, funcs):
47+
methods = {"__init__": accessor_constructor} | create_methods(funcs)
48+
cls_name = "".join([name.capitalize(), "Accessor"])
49+
accessor = type(cls_name, (object,), methods)
50+
return xr.register_dataarray_accessor(name)(accessor)

0 commit comments

Comments
 (0)