|
| 1 | +# std lib |
| 2 | +from functools import partial |
| 3 | +from typing import Union |
| 4 | + |
| 5 | +# 3rd-party |
| 6 | +try: |
| 7 | + import cupy |
| 8 | +except ImportError: |
| 9 | + class cupy(object): |
| 10 | + ndarray = False |
| 11 | + |
| 12 | +import dask.array as da |
| 13 | + |
| 14 | +from numba import cuda |
| 15 | + |
1 | 16 | import numpy as np |
2 | 17 | import xarray as xr |
3 | | -from numba import stencil, vectorize |
4 | 18 |
|
| 19 | +# local modules |
| 20 | +from xrspatial.utils import cuda_args |
| 21 | +from xrspatial.utils import get_dataarray_resolution |
| 22 | +from xrspatial.utils import has_cuda |
| 23 | +from xrspatial.utils import ngjit |
| 24 | +from xrspatial.utils import is_cupy_backed |
| 25 | + |
| 26 | + |
| 27 | +@ngjit |
| 28 | +def _cpu(data, cellsize): |
| 29 | + out = np.empty(data.shape, np.float64) |
| 30 | + out[:, :] = np.nan |
| 31 | + rows, cols = data.shape |
| 32 | + for y in range(1, rows - 1): |
| 33 | + for x in range(1, cols - 1): |
| 34 | + d = (data[y + 1, x] + data[y - 1, x]) / 2 - data[y, x] |
| 35 | + e = (data[y, x + 1] + data[y, x - 1]) / 2 - data[y, x] |
| 36 | + out[y, x] = -2 * (d + e) * 100 / (cellsize * cellsize) |
| 37 | + return out |
| 38 | + |
| 39 | + |
| 40 | +def _run_numpy(data: np.ndarray, |
| 41 | + cellsize: Union[int, float]) -> np.ndarray: |
| 42 | + # TODO: handle border edge effect |
| 43 | + out = _cpu(data, cellsize) |
| 44 | + return out |
| 45 | + |
| 46 | + |
| 47 | +def _run_dask_numpy(data: da.Array, |
| 48 | + cellsize: Union[int, float]) -> da.Array: |
| 49 | + _func = partial(_cpu, |
| 50 | + cellsize=cellsize) |
| 51 | + |
| 52 | + out = data.map_overlap(_func, |
| 53 | + depth=(1, 1), |
| 54 | + boundary=np.nan, |
| 55 | + meta=np.array(())) |
| 56 | + return out |
| 57 | + |
| 58 | + |
| 59 | +@cuda.jit(device=True) |
| 60 | +def _gpu(arr, cellsize): |
| 61 | + d = (arr[1, 0] + arr[1, 2]) / 2 - arr[1, 1] |
| 62 | + e = (arr[0, 1] + arr[2, 1]) / 2 - arr[1, 1] |
| 63 | + curv = -2 * (d + e) * 100 / (cellsize[0] * cellsize[0]) |
| 64 | + return curv |
5 | 65 |
|
6 | | -@stencil |
7 | | -def kernel_D(arr): |
8 | | - return (arr[0, -1] + arr[0, 1]) / 2 - arr[0, 0] |
9 | 66 |
|
| 67 | +@cuda.jit |
| 68 | +def _run_gpu(arr, cellsize, out): |
| 69 | + i, j = cuda.grid(2) |
| 70 | + di = 1 |
| 71 | + dj = 1 |
| 72 | + if (i - di >= 0 and i + di <= out.shape[0] - 1 and |
| 73 | + j - dj >= 0 and j + dj <= out.shape[1] - 1): |
| 74 | + out[i, j] = _gpu(arr[i - di:i + di + 1, j - dj:j + dj + 1], cellsize) |
10 | 75 |
|
11 | | -@stencil |
12 | | -def kernel_E(arr): |
13 | | - return (arr[-1, 0] + arr[1, 0]) / 2 - arr[0, 0] |
14 | 76 |
|
| 77 | +def _run_cupy(data: cupy.ndarray, |
| 78 | + cellsize: Union[int, float]) -> cupy.ndarray: |
15 | 79 |
|
16 | | -@vectorize(["float64(float64, float64)"], nopython=True, target="parallel") |
17 | | -def _curvature(matrix_D, matrix_E): |
18 | | - curv = -2 * (matrix_D + matrix_E) * 100 |
19 | | - return curv |
| 80 | + cellsize_arr = cupy.array([float(cellsize)], dtype='f4') |
| 81 | + |
| 82 | + # TODO: add padding |
| 83 | + griddim, blockdim = cuda_args(data.shape) |
| 84 | + out = cupy.empty(data.shape, dtype='f4') |
| 85 | + out[:] = cupy.nan |
20 | 86 |
|
| 87 | + _run_gpu[griddim, blockdim](data, cellsize_arr, out) |
21 | 88 |
|
22 | | -def curvature(raster): |
23 | | - """Compute the curvature (second derivatives) of a raster surface. |
| 89 | + return out |
| 90 | + |
| 91 | + |
| 92 | +def _run_dask_cupy(data: da.Array, |
| 93 | + cellsize: Union[int, float]) -> da.Array: |
| 94 | + msg = 'Upstream bug in dask prevents cupy backed arrays' |
| 95 | + raise NotImplementedError(msg) |
| 96 | + |
| 97 | + |
| 98 | +def curvature(agg, name='curvature'): |
| 99 | + """Compute the curvature (second derivatives) of a agg surface. |
24 | 100 |
|
25 | 101 | Parameters |
26 | 102 | ---------- |
27 | | - raster: xarray.DataArray |
28 | | - 2D input raster image with shape=(height, width) |
| 103 | + agg: xarray.xr.DataArray |
| 104 | + 2D input agg image with shape=(height, width) |
29 | 105 |
|
30 | 106 | Returns |
31 | 107 | ------- |
32 | | - curvature: xarray.DataArray |
| 108 | + curvature: xarray.xr.DataArray |
33 | 109 | Curvature image with shape=(height, width) |
34 | 110 | """ |
35 | 111 |
|
36 | | - if not isinstance(raster, xr.DataArray): |
37 | | - raise TypeError("`raster` must be instance of DataArray") |
38 | | - |
39 | | - if raster.ndim != 2: |
40 | | - raise ValueError("`raster` must be 2D") |
41 | | - |
42 | | - if not (issubclass(raster.values.dtype.type, np.integer) or |
43 | | - issubclass(raster.values.dtype.type, np.floating)): |
44 | | - raise ValueError( |
45 | | - "`raster` must be an array of integers or float") |
46 | | - |
47 | | - raster_values = raster.values |
| 112 | + cellsize_x, cellsize_y = get_dataarray_resolution(agg) |
| 113 | + cellsize = (cellsize_x + cellsize_y) / 2 |
48 | 114 |
|
49 | | - cell_size_x = 1 |
50 | | - cell_size_y = 1 |
| 115 | + # numpy case |
| 116 | + if isinstance(agg.data, np.ndarray): |
| 117 | + out = _run_numpy(agg.data, cellsize) |
51 | 118 |
|
52 | | - # calculate cell size from input `raster` |
53 | | - for dim in raster.dims: |
54 | | - if (dim.lower().count('x')) > 0 or (dim.lower().count('lon')) > 0: |
55 | | - # dimension of x-coordinates |
56 | | - if len(raster[dim]) > 1: |
57 | | - cell_size_x = raster[dim].values[1] - raster[dim].values[0] |
58 | | - elif (dim.lower().count('y')) > 0 or (dim.lower().count('lat')) > 0: |
59 | | - # dimension of y-coordinates |
60 | | - if len(raster[dim]) > 1: |
61 | | - cell_size_y = raster[dim].values[1] - raster[dim].values[0] |
| 119 | + # cupy case |
| 120 | + elif has_cuda() and isinstance(agg.data, cupy.ndarray): |
| 121 | + out = _run_cupy(agg.data, cellsize) |
62 | 122 |
|
63 | | - cell_size = (cell_size_x + cell_size_y) / 2 |
| 123 | + # dask + numpy case |
| 124 | + elif isinstance(agg.data, da.Array): |
| 125 | + out = _run_dask_numpy(agg.data, cellsize) |
64 | 126 |
|
65 | | - matrix_D = kernel_D(raster_values) |
66 | | - matrix_E = kernel_E(raster_values) |
| 127 | + # dask + cupy case |
| 128 | + elif has_cuda() and isinstance(agg.data, da.Array) and is_cupy_backed(agg): |
| 129 | + out = _run_dask_cupy(agg.data, cellsize) |
67 | 130 |
|
68 | | - curvature_values = _curvature(matrix_D, matrix_E) / (cell_size * cell_size) |
69 | | - result = xr.DataArray(curvature_values, |
70 | | - coords=raster.coords, |
71 | | - dims=raster.dims, |
72 | | - attrs=raster.attrs) |
| 131 | + else: |
| 132 | + raise TypeError('Unsupported Array Type: {}'.format(type(agg.data))) |
73 | 133 |
|
74 | | - return result |
| 134 | + return xr.DataArray(out, |
| 135 | + name=name, |
| 136 | + coords=agg.coords, |
| 137 | + dims=agg.dims, |
| 138 | + attrs=agg.attrs) |
0 commit comments