Skip to content

Commit bc9d09d

Browse files
authored
refactoring aspect (#156)
* refactoring aspect * fixed aspect gpu tests * updated supported features in readme
1 parent ee8bb84 commit bc9d09d

File tree

3 files changed

+150
-328
lines changed

3 files changed

+150
-328
lines changed

README.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,7 @@ In the GIS world, rasters are used for representing continuous phenomena (e.g. e
126126

127127
| Name | NumPy xr.DataArray | Dask xr.DataArray | CuPy GPU xr.DataArray | Dask GPU xr.DataArray |
128128
|:----------:|:----------------------:|:--------------------:|:-------------------:|:------:|
129-
| [Aspect](xrspatial/aspect.py) | YES | NO | YES | NO |
129+
| [Aspect](xrspatial/aspect.py) | YES | YES | YES | NO |
130130
| [Curvature](xrspatial/curvature.py) | YES | NO | NO | NO |
131131
| [Hillshade](xrspatial/hillshade.py) | YES | YES | NO | NO |
132132
| [Slope](xrspatial/slope.py) | YES | YES | YES | NO |

xrspatial/aspect.py

+85-44
Original file line numberDiff line numberDiff line change
@@ -3,21 +3,33 @@
33
import numpy as np
44
import numba as nb
55

6+
from functools import partial
7+
68
import dask.array as da
79

810
from numba import cuda
911

12+
import xarray as xr
1013
from xarray import DataArray
1114

1215
from xrspatial.utils import ngjit
1316
from xrspatial.utils import has_cuda
1417
from xrspatial.utils import cuda_args
18+
from xrspatial.utils import is_cupy_backed
19+
20+
21+
# 3rd-party
22+
try:
23+
import cupy
24+
except ImportError:
25+
class cupy(object):
26+
ndarray = False
1527

1628
RADIAN = 180 / np.pi
1729

1830

1931
@ngjit
20-
def _horn_aspect(data):
32+
def _cpu(data):
2133
out = np.zeros_like(data, dtype=np.float64)
2234
out[:] = np.nan
2335
rows, cols = data.shape
@@ -53,7 +65,7 @@ def _horn_aspect(data):
5365

5466

5567
@cuda.jit(device=True)
56-
def _gpu_aspect(arr):
68+
def _gpu(arr):
5769

5870
a = arr[0, 0]
5971
b = arr[0, 1]
@@ -91,18 +103,66 @@ def _gpu_aspect(arr):
91103

92104

93105
@cuda.jit
94-
def _horn_aspect_cuda(arr, out):
95-
minus_one = nb.float32(-1.)
106+
def _run_gpu(arr, out):
96107
i, j = cuda.grid(2)
97108
di = 1
98109
dj = 1
99110
if (i-di >= 1 and i+di < out.shape[0] - 1 and
100111
j-dj >= 1 and j+dj < out.shape[1] - 1):
101-
out[i, j] = _gpu_aspect(arr[i-di:i+di+1, j-dj:j+dj+1])
112+
out[i, j] = _gpu(arr[i-di:i+di+1, j-dj:j+dj+1])
113+
114+
115+
def _run_cupy(data: cupy.ndarray) -> cupy.ndarray:
116+
117+
pad_rows = 3 // 2
118+
pad_cols = 3 // 2
119+
pad_width = ((pad_rows, pad_rows),
120+
(pad_cols, pad_cols))
121+
122+
_data = np.pad(data, pad_width=pad_width, mode="reflect")
123+
124+
griddim, blockdim = cuda_args(_data.shape)
125+
agg = cupy.empty(_data.shape, dtype='f4')
126+
agg[:] = cupy.nan
127+
128+
_run_gpu[griddim, blockdim](_data, agg)
129+
out = agg[pad_rows:-pad_rows, pad_cols:-pad_cols]
130+
return out
131+
132+
133+
def _run_dask_cupy(data:da.Array) -> da.Array:
134+
135+
msg = 'Upstream bug in dask prevents cupy backed arrays'
136+
raise NotImplementedError(msg)
137+
138+
# add any func args
139+
# TODO: probably needs cellsize args
140+
_func = partial(_run_cupy)
141+
142+
out = data.map_overlap(_func,
143+
depth=(1, 1),
144+
boundary=cupy.nan,
145+
dtype=cupy.float32,
146+
meta=cupy.array(()))
147+
return out
148+
149+
150+
def _run_numpy(data:np.ndarray)-> np.ndarray:
151+
out = _cpu(data)
152+
return out
102153

103154

155+
def _run_dask_numpy(data:da.Array) -> da.Array:
156+
_func = partial(_cpu)
104157

105-
def aspect(agg, name='aspect', use_cuda=True, pad=True, use_cupy=True):
158+
out = data.map_overlap(_func,
159+
depth=(1, 1),
160+
boundary=np.nan,
161+
meta=np.array(()))
162+
return out
163+
164+
165+
def aspect(agg: xr.DataArray, name:str ='aspect'):
106166
"""Returns downward slope direction in compass degrees (0 - 360) with 0 at 12 o'clock.
107167
108168
Parameters
@@ -120,46 +180,27 @@ def aspect(agg, name='aspect', use_cuda=True, pad=True, use_cupy=True):
120180
- Burrough, P. A., and McDonell, R. A., 1998. Principles of Geographical Information Systems (Oxford University Press, New York), pp 406
121181
"""
122182

123-
if not isinstance(agg, DataArray):
124-
raise TypeError("agg must be instance of DataArray")
183+
# numpy case
184+
if isinstance(agg.data, np.ndarray):
185+
out = _run_numpy(agg.data)
125186

126-
if has_cuda() and use_cuda:
127-
128-
if pad:
129-
pad_rows = 3 // 2
130-
pad_cols = 3 // 2
131-
pad_width = ((pad_rows, pad_rows),
132-
(pad_cols, pad_cols))
133-
else:
134-
# If padding is not desired, set pads to 0
135-
pad_rows = 0
136-
pad_cols = 0
137-
pad_width = 0
138-
139-
data = np.pad(agg.data, pad_width=pad_width, mode="reflect")
140-
141-
griddim, blockdim = cuda_args(data.shape)
142-
out = np.empty(data.shape, dtype='f4')
143-
out[:] = np.nan
144-
145-
if use_cupy:
146-
import cupy
147-
out = cupy.asarray(out)
148-
149-
_horn_aspect_cuda[griddim, blockdim](data, out)
150-
if pad:
151-
out = out[pad_rows:-pad_rows, pad_cols:-pad_cols]
187+
# cupy case
188+
elif has_cuda() and isinstance(agg.data, cupy.ndarray):
189+
out = _run_cupy(agg.data)
152190

191+
# dask + cupy case
192+
elif has_cuda() and isinstance(agg.data, da.Array) and is_cupy_backed(agg):
193+
out = _run_dask_cupy(agg.data)
194+
195+
# dask + numpy case
153196
elif isinstance(agg.data, da.Array):
154-
out = agg.data.map_overlap(_horn_aspect,
155-
depth=(1, 1),
156-
boundary=np.nan,
157-
meta=np.array(()))
197+
out = _run_dask_numpy(agg.data)
198+
158199
else:
159-
out = _horn_aspect(agg.data)
200+
raise TypeError('Unsupported Array Type: {}'.format(type(agg.data)))
160201

161-
return DataArray(out,
162-
name=name,
163-
dims=agg.dims,
164-
coords=agg.coords,
165-
attrs=agg.attrs)
202+
return xr.DataArray(out,
203+
name=name,
204+
coords=agg.coords,
205+
dims=agg.dims,
206+
attrs=agg.attrs)

0 commit comments

Comments
 (0)