1+ # std lib
12from functools import partial
3+ from math import sqrt
4+ from typing import Union
25
3- import numpy as np
4-
5- from xarray import DataArray
6+ # 3rd-party
7+ try :
8+ import cupy
9+ except ImportError :
10+ class cupy (object ):
11+ ndarray = False
612
713import dask .array as da
814
15+ from numba import cuda
16+
17+ import numpy as np
18+ import xarray as xr
919
10- def _hillshade (data , azimuth = 225 , angle_altitude = 25 ):
20+ # local modules
21+ from xrspatial .utils import cuda_args
22+ from xrspatial .utils import has_cuda
23+ from xrspatial .utils import is_cupy_backed
24+
25+
26+ def _run_numpy (data , azimuth = 225 , angle_altitude = 25 ):
1127 azimuth = 360.0 - azimuth
1228 x , y = np .gradient (data )
1329 slope = np .pi / 2. - np .arctan (np .sqrt (x * x + y * y ))
@@ -18,7 +34,71 @@ def _hillshade(data, azimuth=225, angle_altitude=25):
1834 result = (shaded + 1 ) / 2
1935 result [(0 , - 1 ), :] = np .nan
2036 result [:, (0 , - 1 )] = np .nan
21- return data
37+ return result
38+
39+
40+ def _run_dask_numpy (data , azimuth , angle_altitude ):
41+ _func = partial (_run_numpy , azimuth = azimuth , angle_altitude = angle_altitude )
42+ out = data .map_overlap (_func ,
43+ depth = (1 , 1 ),
44+ boundary = np .nan ,
45+ meta = np .array (()))
46+ return out
47+
48+
49+ @cuda .jit
50+ def _gpu_calc (x , y , out ):
51+ i , j = cuda .grid (2 )
52+ if i < out .shape [0 ] and j < out .shape [1 ]:
53+ out [i , j ] = sqrt (x [i , j ] * x [i , j ] + y [i , j ] * y [i , j ])
54+
55+
56+ @cuda .jit
57+ def _gpu_cos_part (cos_altituderad , cos_slope , cos_aspect , out ):
58+ i , j = cuda .grid (2 )
59+ if i < out .shape [0 ] and j < out .shape [1 ]:
60+ out [i , j ] = cos_altituderad * cos_slope [i , j ] * cos_aspect [i , j ]
61+
62+
63+ def _run_cupy (data , azimuth , angle_altitude ):
64+ x , y = np .gradient (data .get ())
65+ x = cupy .asarray (x , dtype = x .dtype )
66+ y = cupy .asarray (y , dtype = y .dtype )
67+
68+ altituderad = angle_altitude * np .pi / 180.
69+ sin_altituderad = np .sin (altituderad )
70+ cos_altituderad = np .cos (altituderad )
71+
72+ griddim , blockdim = cuda_args (data .shape )
73+ arctan_part = cupy .empty (data .shape , dtype = 'f4' )
74+ _gpu_calc [griddim , blockdim ](x , y , arctan_part )
75+
76+ slope = np .pi / 2. - np .arctan (arctan_part )
77+ sin_slope = np .sin (slope )
78+ sin_part = sin_altituderad * sin_slope
79+
80+ azimuthrad = (360.0 - azimuth ) * np .pi / 180.
81+ aspect = (azimuthrad - np .pi / 2. ) - np .arctan2 (- x , y )
82+ cos_aspect = np .cos (aspect )
83+ cos_slope = np .cos (slope )
84+
85+ cos_part = cupy .empty (data .shape , dtype = 'f4' )
86+ _gpu_cos_part [griddim , blockdim ](cos_altituderad , cos_slope ,
87+ cos_aspect , cos_part )
88+ shaded = sin_part + cos_part
89+ out = (shaded + 1 ) / 2
90+
91+ out [0 , :] = cupy .nan
92+ out [- 1 , :] = cupy .nan
93+ out [:, 0 ] = cupy .nan
94+ out [:, - 1 ] = cupy .nan
95+
96+ return out
97+
98+
99+ def _run_dask_cupy (data , azimuth , angle_altitude ):
100+ msg = 'Upstream bug in dask prevents cupy backed arrays'
101+ raise NotImplementedError (msg )
22102
23103
24104def hillshade (agg , azimuth = 225 , angle_altitude = 25 , name = 'hillshade' ):
@@ -32,14 +112,6 @@ def hillshade(agg, azimuth=225, angle_altitude=25, name='hillshade'):
32112 azimuth : int, optional (default: 315)
33113 The angle between the north vector and the perpendicular projection
34114 of the light source down onto the horizon specified in degrees.
35- cmap : list of colors or matplotlib.colors.Colormap, optional
36- The colormap to use. Can be either a list of colors (in any of the
37- formats described above), or a matplotlib colormap object.
38- Default is `["lightgray", "black"]`
39- alpha : int, optional
40- Value between 0 - 255 representing the alpha value of pixels which contain
41- data (i.e. non-nan values). Regardless of this value, `NaN` values are
42- set to fully transparent.
43115
44116 Returns
45117 -------
@@ -51,14 +123,27 @@ def hillshade(agg, azimuth=225, angle_altitude=25, name='hillshade'):
51123 - http://geoexamples.blogspot.com/2014/03/shaded-relief-images-using-gdal-python.html
52124 """
53125
54- if isinstance (agg .data , da .Array ):
55- _func = partial (_hillshade , azimuth = azimuth , angle_altitude = angle_altitude )
56- out = agg .data .map_overlap (_func ,
57- depth = (1 , 1 ),
58- boundary = np .nan ,
59- meta = np .array (()))
126+ # numpy case
127+ if isinstance (agg .data , np .ndarray ):
128+ out = _run_numpy (agg .data , azimuth , angle_altitude )
129+
130+ # cupy case
131+ elif has_cuda () and isinstance (agg .data , cupy .ndarray ):
132+ out = _run_cupy (agg .data , azimuth , angle_altitude )
133+
134+ # dask + cupy case
135+ elif has_cuda () and isinstance (agg .data , da .Array ) and is_cupy_backed (agg ):
136+ out = _run_dask_cupy (agg .data , azimuth , angle_altitude )
137+
138+ # dask + numpy case
139+ elif isinstance (agg .data , da .Array ):
140+ out = _run_dask_numpy (agg .data , azimuth , angle_altitude )
141+
60142 else :
61- out = _hillshade ( agg .data , azimuth , angle_altitude )
143+ raise TypeError ( 'Unsupported Array Type: {}' . format ( type ( agg .data )) )
62144
63- return DataArray (out , name = name , dims = agg .dims ,
64- coords = agg .coords , attrs = agg .attrs )
145+ return xr .DataArray (out ,
146+ name = name ,
147+ coords = agg .coords ,
148+ dims = agg .dims ,
149+ attrs = agg .attrs )
0 commit comments