def _convolve_2d_cupy(data, kernel): data = data.astype(cupy.float32) out = cupy.empty(data.shape, dtype='f4') out[:, :] = cupy.nan griddim, blockdim = cuda_args(data.shape) _convolve_2d_cuda[griddim, blockdim](data, kernel, cupy.asarray(out)) return out
def _evi_cupy(nir_data, red_data, blue_data, c1, c2, soil_factor, gain): griddim, blockdim = cuda_args(nir_data.shape) out = cupy.empty(nir_data.shape, dtype='f4') out[:] = cupy.nan args = (nir_data, red_data, blue_data, c1, c2, soil_factor, gain, out) _evi_gpu[griddim, blockdim](*args) return out
def _run_cupy(data, azimuth, angle_altitude): x, y = np.gradient(data.get()) x = cupy.asarray(x, dtype=x.dtype) y = cupy.asarray(y, dtype=y.dtype) altituderad = angle_altitude * np.pi / 180. sin_altituderad = np.sin(altituderad) cos_altituderad = np.cos(altituderad) griddim, blockdim = cuda_args(data.shape) arctan_part = cupy.empty(data.shape, dtype='f4') _gpu_calc[griddim, blockdim](x, y, arctan_part) slope = np.pi / 2. - np.arctan(arctan_part) sin_slope = np.sin(slope) sin_part = sin_altituderad * sin_slope azimuthrad = (360.0 - azimuth) * np.pi / 180. aspect = (azimuthrad - np.pi / 2.) - np.arctan2(-x, y) cos_aspect = np.cos(aspect) cos_slope = np.cos(slope) cos_part = cupy.empty(data.shape, dtype='f4') _gpu_cos_part[griddim, blockdim](cos_altituderad, cos_slope, cos_aspect, cos_part) shaded = sin_part + cos_part out = (shaded + 1) / 2 out[0, :] = cupy.nan out[-1, :] = cupy.nan out[:, 0] = cupy.nan out[:, -1] = cupy.nan return out
def _terrain_gpu(height_map, seed, x_range=(0, 1), y_range=(0, 1)): NOISE_LAYERS = ((1 / 2**i, (2**i, 2**i)) for i in range(16)) noise = cupy.empty_like(height_map, dtype=np.float32) griddim, blockdim = cuda_args(height_map.shape) for i, (m, (xfreq, yfreq)) in enumerate(NOISE_LAYERS): # cupy.random.seed(seed+i) # p = cupy.random.permutation(2**20) # use numpy.random then transfer data to GPU to ensure the same result # when running numpy backed and cupy backed data array. np.random.seed(seed + i) p = cupy.asarray(np.random.permutation(2**20)) p = cupy.append(p, p) _perlin_gpu[griddim, blockdim](p, x_range[0] * xfreq, x_range[1] * xfreq, y_range[0] * yfreq, y_range[1] * yfreq, m, noise) height_map += noise height_map /= (1.00 + 0.50 + 0.25 + 0.13 + 0.06 + 0.03) height_map = height_map**3 return height_map
def _run_cupy_binary(data, values): values_cupy = cupy.asarray(values) out = cupy.empty(data.shape, dtype='f4') out[:] = cupy.nan griddim, blockdim = cuda_args(data.shape) _run_gpu_binary[griddim, blockdim](data, values_cupy, out) return out
def convolve_2d(image, kernel, pad=True, use_cuda=True): """Function to call the 2D convolution via Numba. The Numba convolution function does not account for an edge so if we wish to take this into account, will pad the image array. """ # Don't allow padding on (1, 1) kernel if (kernel.shape[0] == 1 and kernel.shape[1] == 1): pad = False if pad: pad_rows = kernel.shape[0] // 2 pad_cols = kernel.shape[1] // 2 pad_width = ((pad_rows, pad_rows), (pad_cols, pad_cols)) else: # If padding is not desired, set pads to 0 pad_rows = 0 pad_cols = 0 pad_width = 0 padded_image = np.pad(image, pad_width=pad_width, mode="reflect") result = np.empty_like(padded_image) if has_cuda() and use_cuda: griddim, blockdim = cuda_args(padded_image.shape) _convolve_2d_cuda[griddim, blockdim](result, kernel, padded_image) else: result = _convolve_2d(kernel, padded_image) if pad: result = result[pad_rows:-pad_rows, pad_cols:-pad_cols] if result.shape != image.shape: raise ValueError("Output and input rasters are not the same shape.") return result
def _run_cupy(data: cupy.ndarray) -> cupy.ndarray: data = data.astype(cupy.float32) griddim, blockdim = cuda_args(data.shape) out = cupy.empty(data.shape, dtype='f4') out[:] = cupy.nan _run_gpu[griddim, blockdim](data, out) return out
def savi(nir_agg, red_agg, soil_factor=1.0, name='savi', use_cuda=True, use_cupy=True): """Returns Soil Adjusted Vegetation Index (SAVI). Parameters ---------- nir_agg : DataArray near-infrared band data red_agg : DataArray red band data soil_factor : float soil adjustment factor between -1.0 and 1.0. when set to zero, savi will return the same as ndvi Returns ------- data: DataArray Notes: ------ Algorithm References: - https://www.sciencedirect.com/science/article/abs/pii/003442578890106X """ _check_is_dataarray(nir_agg, 'near-infrared') _check_is_dataarray(red_agg, 'red') if not red_agg.shape == nir_agg.shape: raise ValueError("red_agg and nir_agg expected to have equal shapes") if soil_factor > 1.0 or soil_factor < -1.0: raise ValueError("soil factor must be between (-1.0, 1.0)") nir_data = nir_agg.data red_data = red_agg.data if has_cuda() and use_cuda: griddim, blockdim = cuda_args(nir_data.shape) soil_factor_arr = np.array([float(soil_factor)], dtype='f4') out = np.empty(nir_data.shape, dtype='f4') out[:] = np.nan if use_cupy: import cupy out = cupy.asarray(out) _savi_gpu[griddim, blockdim](nir_data, red_data, soil_factor_arr, out) else: out = _savi(nir_agg.data, red_agg.data, soil_factor) return DataArray(out, name=name, coords=nir_agg.coords, dims=nir_agg.dims, attrs=nir_agg.attrs)
def _run_cupy_bin(data, bins, new_values): bins_cupy = cupy.asarray(bins) new_values_cupy = cupy.asarray(new_values) out = cupy.empty(data.shape, dtype='f4') out[:] = cupy.nan griddim, blockdim = cuda_args(data.shape) _run_gpu_bin[griddim, blockdim](data, bins_cupy, new_values_cupy, out) return out
def _mean_cupy(data, excludes): out = cupy.zeros_like(data) out[:, :] = cupy.nan griddim, blockdim = cuda_args(data.shape) out = cupy.empty(data.shape, dtype='f4') out[:] = cupy.nan _mean_gpu[griddim, blockdim](data, cupy.asarray(excludes), out) return out
def _run_cupy(data: cupy.ndarray, cellsize_x: Union[int, float], cellsize_y: Union[int, float]) -> cupy.ndarray: cellsize_x_arr = cupy.array([float(cellsize_x)], dtype='f4') cellsize_y_arr = cupy.array([float(cellsize_y)], dtype='f4') griddim, blockdim = cuda_args(data.shape) out = cupy.empty(data.shape, dtype='f4') out[:] = cupy.nan _run_gpu[griddim, blockdim](data, cellsize_x_arr, cellsize_y_arr, out) return out
def sipi(nir_agg, red_agg, blue_agg, name='sipi', use_cuda=True, use_cupy=True): """Computes Structure Insensitive Pigment Index which helpful in early disease detection Parameters ---------- nir_agg : DataArray near-infrared band data green_agg : DataArray green band data Returns ------- data: DataArray Notes: ------ Algorithm References: https://en.wikipedia.org/wiki/Enhanced_vegetation_index """ _check_is_dataarray(nir_agg, 'near-infrared') _check_is_dataarray(red_agg, 'red') _check_is_dataarray(blue_agg, 'blue') if not red_agg.shape == nir_agg.shape == blue_agg.shape: raise ValueError("input layers expected to have equal shapes") nir_data = nir_agg.data red_data = red_agg.data blue_data = blue_agg.data if has_cuda() and use_cuda: griddim, blockdim = cuda_args(nir_data.shape) out = np.empty(nir_data.shape, dtype='f4') out[:] = np.nan if use_cupy: import cupy out = cupy.asarray(out) _sipi_gpu[griddim, blockdim](nir_data, red_data, blue_data, out) else: out = _sipi(nir_data, red_data, blue_data) return DataArray(out, name=name, coords=nir_agg.coords, dims=nir_agg.dims, attrs=nir_agg.attrs)
def _run_cupy_bin(data, bins, new_values): # replace inf by nan to avoid classify these values as we want to treat them as outliers data = cupy.where(data == cupy.inf, cupy.nan, data) data = cupy.where(data == -cupy.inf, cupy.nan, data) bins_cupy = cupy.asarray(bins) new_values_cupy = cupy.asarray(new_values) out = cupy.empty(data.shape, dtype='f4') out[:] = cupy.nan griddim, blockdim = cuda_args(data.shape) _run_gpu_bin[griddim, blockdim](data, bins_cupy, new_values_cupy, out) return out
def _run_cupy(data: cupy.ndarray, cellsize: Union[int, float]) -> cupy.ndarray: cellsize_arr = cupy.array([float(cellsize)], dtype='f4') # TODO: add padding griddim, blockdim = cuda_args(data.shape) out = cupy.empty(data.shape, dtype='f4') out[:] = cupy.nan _run_gpu[griddim, blockdim](data, cellsize_arr, out) return out
def ebbi(red_agg, swir_agg, tir_agg, name='ebbi', use_cuda=True, use_cupy=True): """Computes Enhanced Built-Up and Bareness Index Parameters ---------- red_agg : DataArray red band data swir_agg : DataArray shortwave infrared band data tir_agg : DataArray thermal infrared band data Returns ------- data: DataArray Notes: ------ Algorithm References: https://rdrr.io/cran/LSRS/man/EBBI.html """ _check_is_dataarray(red_agg, 'red') _check_is_dataarray(swir_agg, 'swir') _check_is_dataarray(tir_agg, 'thermal infrared') if not red_agg.shape == swir_agg.shape == tir_agg.shape: raise ValueError("input layers expected to have equal shapes") red_data = red_agg.data swir_data = swir_agg.data tir_data = tir_agg.data if has_cuda() and use_cuda: griddim, blockdim = cuda_args(red_data.shape) out = np.empty(red_data.shape, dtype='f4') out[:] = np.nan if use_cupy: import cupy out = cupy.asarray(out) _sipi_gpu[griddim, blockdim](red_data, swir_data, tir_data, out) else: out = _sipi(red_data, swir_data, tir_data) return DataArray(out, name=name, coords=red_agg.coords, dims=red_agg.dims, attrs=red_agg.attrs)
def _run_normalized_ratio(arr1, arr2, use_cuda=True, use_cupy=True): if has_cuda() and use_cuda: griddim, blockdim = cuda_args(arr1.shape) out = np.empty(arr1.shape, dtype='f4') out[:] = np.nan if use_cupy: import cupy out = cupy.asarray(out) _normalized_ratio_gpu[griddim, blockdim](arr1, arr2, out) else: out = _normalized_ratio(arr1, arr2) return out
def _perlin_cupy(data: cupy.ndarray, freq: tuple, seed: int) -> cupy.ndarray: # cupy.random.seed(seed) # p = cupy.random.permutation(2**20) # use numpy.random then transfer data to GPU to ensure the same result # when running numpy backed and cupy backed data array. np.random.seed(seed) p = cupy.asarray(np.random.permutation(2**20)) p = cupy.append(p, p) griddim, blockdim = cuda_args(data.shape) _perlin_gpu[griddim, blockdim](p, 0, freq[0], 0, freq[1], 1, data) minimum = cupy.amin(data) maximum = cupy.amax(data) data[:] = (data - minimum) / (maximum - minimum) return data
def _run_cupy(d_data, azimuth, angle_altitude): # Precompute constant values shared between all threads altituderad = angle_altitude * np.pi / 180. sin_altituderad = np.sin(altituderad) cos_altituderad = np.cos(altituderad) azimuthrad = (360.0 - azimuth) * np.pi / 180. # Allocate output buffer and launch kernel with appropriate dimensions output = cupy.empty(d_data.shape, np.float32) griddim, blockdim = cuda_args(d_data.shape) _gpu_calc_numba[griddim, blockdim](d_data, output, sin_altituderad, cos_altituderad, azimuthrad) # Fill borders with nans. output[0, :] = cupy.nan output[-1, :] = cupy.nan output[:, 0] = cupy.nan output[:, -1] = cupy.nan return output
def _hotspots_cupy(raster, kernel): if not (issubclass(raster.data.dtype.type, cupy.integer) or issubclass(raster.data.dtype.type, cupy.floating)): raise ValueError("data type must be integer or float") data = raster.data.astype(cupy.float32) # apply kernel to raster values mean_array = convolve_2d(data, kernel / kernel.sum()) # calculate z-scores global_mean = cupy.nanmean(data) global_std = cupy.nanstd(data) if global_std == 0: raise ZeroDivisionError( "Standard deviation of the input raster values is 0.") z_array = (mean_array - global_mean) / global_std out = cupy.zeros_like(z_array, dtype=cupy.int8) griddim, blockdim = cuda_args(z_array.shape) _run_gpu_hotspots[griddim, blockdim](z_array, out) return out
def _run_cupy(data: cupy.ndarray, cellsize_x: Union[int, float], cellsize_y: Union[int, float]) -> cupy.ndarray: cellsize_x_arr = cupy.array([float(cellsize_x)], dtype='f4') cellsize_y_arr = cupy.array([float(cellsize_y)], dtype='f4') pad_rows = 3 // 2 pad_cols = 3 // 2 pad_width = ((pad_rows, pad_rows), (pad_cols, pad_cols)) slope_data = np.pad(data, pad_width=pad_width, mode="reflect") griddim, blockdim = cuda_args(slope_data.shape) slope_agg = cupy.empty(slope_data.shape, dtype='f4') slope_agg[:] = cupy.nan _run_gpu[griddim, blockdim](slope_data, cellsize_x_arr, cellsize_y_arr, slope_agg) out = slope_agg[pad_rows:-pad_rows, pad_cols:-pad_cols] return out
def _gci_cupy(nir_data, green_data): griddim, blockdim = cuda_args(nir_data.shape) out = cupy.empty(nir_data.shape, dtype='f4') out[:] = cupy.nan _gci_gpu[griddim, blockdim](nir_data, green_data, out) return out
def aspect(agg, name='aspect', use_cuda=True, pad=True, use_cupy=True): """Returns downward slope direction in compass degrees (0 - 360) with 0 at 12 o'clock. Parameters ---------- agg : DataArray Returns ------- data: DataArray Notes: ------ Algorithm References: - http://desktop.arcgis.com/en/arcmap/10.3/tools/spatial-analyst-toolbox/how-aspect-works.htm#ESRI_SECTION1_4198691F8852475A9F4BC71246579FAA - Burrough, P. A., and McDonell, R. A., 1998. Principles of Geographical Information Systems (Oxford University Press, New York), pp 406 """ if not isinstance(agg, DataArray): raise TypeError("agg must be instance of DataArray") if has_cuda() and use_cuda: if pad: pad_rows = 3 // 2 pad_cols = 3 // 2 pad_width = ((pad_rows, pad_rows), (pad_cols, pad_cols)) else: # If padding is not desired, set pads to 0 pad_rows = 0 pad_cols = 0 pad_width = 0 data = np.pad(agg.data, pad_width=pad_width, mode="reflect") griddim, blockdim = cuda_args(data.shape) out = np.empty(data.shape, dtype='f4') out[:] = np.nan if use_cupy: import cupy out = cupy.asarray(out) _horn_aspect_cuda[griddim, blockdim](data, out) if pad: out = out[pad_rows:-pad_rows, pad_cols:-pad_cols] elif isinstance(agg.data, da.Array): out = agg.data.map_overlap(_horn_aspect, depth=(1, 1), boundary=np.nan, meta=np.array(())) else: out = _horn_aspect(agg.data) return DataArray(out, name=name, dims=agg.dims, coords=agg.coords, attrs=agg.attrs)
def _ebbi_cupy(red_data, swir_data, tir_data): griddim, blockdim = cuda_args(red_data.shape) out = cupy.empty(red_data.shape, dtype='f4') out[:] = cupy.nan _ebbi_gpu[griddim, blockdim](red_data, swir_data, tir_data, out) return out
def _focal_stats_func_cupy(data, kernel, func=_focal_max_cuda): out = cupy.empty(data.shape, dtype='f4') out[:, :] = cupy.nan griddim, blockdim = cuda_args(data.shape) func[griddim, blockdim](data, kernel, cupy.asarray(out)) return out
def convolve_2d(image: xr.DataArray, kernel, pad=True, use_cuda=True) -> xr.DataArray: """ Calculates, for all inner cells of an array, the 2D convolution of each cell via Numba. To account for edge cells, a pad can be added to the image array. Convolution is frequently used for image processing, such as smoothing, sharpening, and edge detection of images by elimatig spurious data or enhancing features in the data. Parameters: ---------- image: xarray.DataArray 2D array of values to processed and padded. kernel: array-like object Impulse kernel, determines area to apply impulse function for each cell. pad: Boolean To compute edges set to True. use-cuda: Boolean For parallel computing set to True. Returns: ---------- convolve_agg: xarray.DataArray 2D array representation of the impulse function. All other input attributes are preserverd. Examples: ---------- Imports >>> import numpy as np >>> import xarray as xr >>> from xrspatial import convolution, focal Create Data Array >>> agg = xr.DataArray(np.array([[0, 0, 0, 0, 0, 0, 0], >>> [0, 0, 2, 4, 0, 8, 0], >>> [0, 2, 2, 4, 6, 8, 0], >>> [0, 4, 4, 4, 6, 8, 0], >>> [0, 6, 6, 6, 6, 8, 0], >>> [0, 8, 8, 8, 8, 8, 0], >>> [0, 0, 0, 0, 0, 0, 0]]), >>> dims = ["lat", "lon"], >>> attrs = dict(res = 1)) >>> height, width = agg.shape >>> _lon = np.linspace(0, width - 1, width) >>> _lat = np.linspace(0, height - 1, height) >>> agg["lon"] = _lon >>> agg["lat"] = _lat Create Kernel >>> kernel = focal.circle_kernel(1, 1, 1) Create Convolution Data Array >>> print(convolution.convolve_2d(agg, kernel)) [[ 0. 0. 4. 8. 0. 16. 0.] [ 0. 4. 8. 10. 18. 16. 16.] [ 4. 8. 14. 20. 24. 30. 16.] [ 8. 16. 20. 24. 30. 30. 16.] [12. 24. 30. 30. 34. 30. 16.] [16. 22. 30. 30. 30. 24. 16.] [ 0. 16. 16. 16. 16. 16. 0.]] """ # Don't allow padding on (1, 1) kernel if (kernel.shape[0] == 1 and kernel.shape[1] == 1): pad = False if pad: pad_rows = kernel.shape[0] // 2 pad_cols = kernel.shape[1] // 2 pad_width = ((pad_rows, pad_rows), (pad_cols, pad_cols)) else: # If padding is not desired, set pads to 0 pad_rows = 0 pad_cols = 0 pad_width = 0 padded_image = np.pad(image, pad_width=pad_width, mode="reflect") result = np.empty_like(padded_image) if has_cuda() and use_cuda: griddim, blockdim = cuda_args(padded_image.shape) _convolve_2d_cuda[griddim, blockdim](result, kernel, padded_image) else: result = _convolve_2d(kernel, padded_image) if pad: result = result[pad_rows:-pad_rows, pad_cols:-pad_cols] if result.shape != image.shape: raise ValueError("Output and input rasters are not the same shape.") return result
def _savi_cupy(nir_data, red_data, soil_factor): griddim, blockdim = cuda_args(nir_data.shape) out = cupy.empty(nir_data.shape, dtype='f4') out[:] = cupy.nan _savi_gpu[griddim, blockdim](nir_data, red_data, soil_factor, out) return out
def _run_normalized_ratio_cupy(arr1, arr2): griddim, blockdim = cuda_args(arr1.shape) out = cupy.empty(arr1.shape, dtype='f4') out[:] = cupy.nan _normalized_ratio_gpu[griddim, blockdim](arr1, arr2, out) return out
def _arvi_cupy(nir_data, red_data, blue_data): griddim, blockdim = cuda_args(nir_data.shape) out = cupy.empty(nir_data.shape, dtype='f4') out[:] = cupy.nan _arvi_gpu[griddim, blockdim](nir_data, red_data, blue_data, out) return out