def data_focal_stats(): data = np.arange(16).reshape(4, 4) kernel = custom_kernel(np.array([[1, 0, 0], [0, 1, 0], [0, 0, 0]])) expected_result = np.asarray([ # mean [[0, 1, 2, 3.], [4, 2.5, 3.5, 4.5], [8, 6.5, 7.5, 8.5], [12, 10.5, 11.5, 12.5]], # max [[0, 1, 2, 3.], [4, 5, 6, 7.], [8, 9, 10, 11.], [12, 13, 14, 15.]], # min [[0, 1, 2, 3.], [4, 0, 1, 2.], [8, 4, 5, 6.], [12, 8, 9, 10.]], # range [[0, 0, 0, 0.], [0, 5, 5, 5.], [0, 5, 5, 5.], [0, 5, 5, 5.]], # std [[0, 0, 0, 0.], [0, 2.5, 2.5, 2.5], [0, 2.5, 2.5, 2.5], [0, 2.5, 2.5, 2.5]], # var [[0, 0, 0, 0.], [0, 6.25, 6.25, 6.25], [0, 6.25, 6.25, 6.25], [0, 6.25, 6.25, 6.25]], # sum [[0, 1, 2, 3.], [4, 5, 7, 9.], [8, 13, 15, 17.], [12, 21, 23, 25.]] ]) return data, kernel, expected_result
def apply(raster, kernel, func=calc_mean): """ Returns Mean filtered array using a user-created window. Parameters ---------- raster : xarray.DataArray 2D array of input values to be filtered. kernel : Numpy Array 2D array where values of 1 indicate the kernel. func : xrspatial.focal.calc_mean Function which takes an input array and returns an array. Returns ------- agg : xarray.DataArray of same type as `raster` 2D aggregate array of filtered values. Examples -------- .. plot:: :include-source: import datashader as ds import matplotlib.pyplot as plt from xrspatial import generate_terrain, aspect from xrspatial.focal import apply from xrspatial.convolution import circle_kernel # Create Canvas W = 500 H = 300 cvs = ds.Canvas(plot_width = W, plot_height = H, x_range = (-20e6, 20e6), y_range = (-20e6, 20e6)) # Generate Example Terrain terrain_agg = generate_terrain(canvas = cvs) terrain_agg = terrain_agg.assign_attrs( { 'Description': 'Example Terrain', 'units': 'km', 'Max Elevation': '4000', } ) terrain_agg = terrain_agg.rename({'x': 'lon', 'y': 'lat'}) # Edit Attributes terrain_agg = terrain_agg.rename('Elevation') # Create Kernel kernel = circle_kernel(10, 10, 100) # Apply Kernel agg = apply(raster = terrain_agg, kernel = kernel) # Edit Attributes agg = agg.assign_attrs({'Description': 'Example Filtered Terrain'}) # Plot Terrain terrain_agg.plot(cmap = 'terrain', aspect = 2, size = 4) plt.title("Terrain") plt.ylabel("latitude") plt.xlabel("longitude") # Plot Filtered Terrain agg.plot(cmap = 'terrain', aspect = 2, size = 4) plt.title("Filtered Terrain") plt.ylabel("latitude") plt.xlabel("longitude") .. sourcecode:: python >>> print(terrain_agg[200:203, 200:202]) <xarray.DataArray 'Elevation' (lat: 3, lon: 2)> array([[1264.02249454, 1261.94748873], [1285.37061171, 1282.48046696], [1306.02305679, 1303.40657515]]) Coordinates: * lon (lon) float64 -3.96e+06 -3.88e+06 * lat (lat) float64 6.733e+06 6.867e+06 7e+06 Attributes: res: 1 Description: Example Terrain units: km Max Elevation: 4000 >>> print(agg[200:203, 200:202]) <xarray.DataArray (lat: 3, lon: 2)> array([[1307.19361419, 1302.6913412 ], [1323.55780616, 1318.75925071], [1342.3309894 , 1336.93787754]]) Coordinates: * lon (lon) float64 -3.96e+06 -3.88e+06 * lat (lat) float64 6.733e+06 6.867e+06 7e+06 Attributes: res: 1 Description: Example Filtered Terrain units: km Max Elevation: 4000 """ # validate raster if not isinstance(raster, DataArray): raise TypeError("`raster` must be instance of DataArray") if raster.ndim != 2: raise ValueError("`raster` must be 2D") # Validate the kernel kernel = custom_kernel(kernel) # apply kernel to raster values # if raster is a numpy or dask with numpy backed data array, # the function func must be a @ngjit out = _apply(raster.data.astype(float), kernel, func) result = DataArray(out, coords=raster.coords, dims=raster.dims, attrs=raster.attrs) return result
def apply(raster, kernel, func=_calc_mean, name='focal_apply'): """ Returns custom function applied array using a user-created window. Parameters ---------- raster : xarray.DataArray 2D array of input values to be filtered. Can be a NumPy backed, or Dask with NumPy backed DataArray. kernel : numpy.ndarray 2D array where values of 1 indicate the kernel. func : callable, default=xrspatial.focal._calc_mean Function which takes an input array and returns an array. Returns ------- agg : xarray.DataArray of same type as `raster` 2D aggregate array of filtered values. Examples -------- Focal apply works with NumPy backed xarray DataArray .. sourcecode:: python >>> import numpy as np >>> import xarray as xr >>> from xrspatial.convolution import circle_kernel >>> from xrspatial.focal import apply >>> data = np.arange(20, dtype=np.float64).reshape(4, 5) >>> raster = xr.DataArray(data, dims=['y', 'x'], name='raster') >>> print(raster) <xarray.DataArray 'raster' (y: 4, x: 5)> array([[ 0., 1., 2., 3., 4.], [ 5., 6., 7., 8., 9.], [10., 11., 12., 13., 14.], [15., 16., 17., 18., 19.]]) Dimensions without coordinates: y, x >>> kernel = circle_kernel(2, 2, 3) >>> kernel array([[0., 1., 0.], [1., 1., 1.], [0., 1., 0.]]) >>> # apply kernel mean by default >>> apply_mean_agg = apply(raster, kernel) >>> apply_mean_agg <xarray.DataArray 'focal_apply' (y: 4, x: 5)> array([[ 2. , 2.25 , 3.25 , 4.25 , 5.33333333], [ 5.25 , 6. , 7. , 8. , 8.75 ], [10.25 , 11. , 12. , 13. , 13.75 ], [13.66666667, 14.75 , 15.75 , 16.75 , 17. ]]) Dimensions without coordinates: y, x Focal apply works with Dask with NumPy backed xarray DataArray. Note that if input raster is a numpy or dask with numpy backed data array, the applied function must be decorated with ``numba.jit`` xrspatial already provides ``ngjit`` decorator, where: ``ngjit = numba.jit(nopython=True, nogil=True)`` .. sourcecode:: python >>> from xrspatial.utils import ngjit >>> from xrspatial.convolution import custom_kernel >>> kernel = custom_kernel(np.array([ [0, 1, 0], [0, 1, 1], [0, 1, 0], ])) >>> weight = np.array([ [0, 0.5, 0], [0, 1, 0.5], [0, 0.5, 0], ]) >>> @ngjit >>> def func(kernel_data): ... weight = np.array([ ... [0, 0.5, 0], ... [0, 1, 0.5], ... [0, 0.5, 0], ... ]) ... return np.nansum(kernel_data * weight) >>> import dask.array as da >>> data_da = da.from_array(np.ones((6, 4), dtype=np.float64), chunks=(3, 2)) >>> raster_da = xr.DataArray(data_da, dims=['y', 'x'], name='raster_da') >>> print(raster_da) <xarray.DataArray 'raster_da' (y: 6, x: 4)> dask.array<array, shape=(6, 4), dtype=float64, chunksize=(3, 2), chunktype=numpy.ndarray> # noqa Dimensions without coordinates: y, x >>> apply_func_agg = apply(raster_da, kernel, func) >>> print(apply_func_agg) <xarray.DataArray 'focal_apply' (y: 6, x: 4)> dask.array<_trim, shape=(6, 4), dtype=float64, chunksize=(3, 2), chunktype=numpy.ndarray> # noqa Dimensions without coordinates: y, x >>> print(apply_func_agg.compute()) <xarray.DataArray 'focal_apply' (y: 6, x: 4)> array([[2. , 2. , 2. , 1.5], [2.5, 2.5, 2.5, 2. ], [2.5, 2.5, 2.5, 2. ], [2.5, 2.5, 2.5, 2. ], [2.5, 2.5, 2.5, 2. ], [2. , 2. , 2. , 1.5]]) Dimensions without coordinates: y, x """ # validate raster if not isinstance(raster, DataArray): raise TypeError("`raster` must be instance of DataArray") if raster.ndim != 2: raise ValueError("`raster` must be 2D") # Validate the kernel kernel = custom_kernel(kernel) # apply kernel to raster values # if raster is a numpy or dask with numpy backed data array, # the function func must be a @ngjit mapper = ArrayTypeFunctionMapping( numpy_func=_apply_numpy, cupy_func=lambda *args: not_implemented_func( *args, messages='apply() does not support cupy backed DataArray.'), dask_func=_apply_dask_numpy, dask_cupy_func=lambda *args: not_implemented_func( *args, messages= 'apply() does not support dask with cupy backed DataArray.'), ) out = mapper(raster)(raster.data, kernel, func) result = DataArray(out, name=name, coords=raster.coords, dims=raster.dims, attrs=raster.attrs) return result
def focal_stats(agg, kernel, stats_funcs=[ 'mean', 'max', 'min', 'range', 'std', 'var', 'sum' ]): """ Calculates statistics of the values within a specified focal neighborhood for each pixel in an input raster. The statistics types are Mean, Maximum, Minimum, Range, Standard deviation, Variation and Sum. Parameters ---------- agg : xarray.DataArray 2D array of input values to be analysed. Can be a NumPy backed, Cupy backed, or Dask with NumPy backed DataArray. kernel : numpy.array 2D array where values of 1 indicate the kernel. stats_funcs: list of string List of statistics types to be calculated. Default set to ['mean', 'max', 'min', 'range', 'std', 'var', 'sum']. Returns ------- stats_agg : xarray.DataArray of same type as `agg` 3D array with dimensions of `(stat, y, x)` and with values indicating the focal stats. Examples -------- .. sourcecode:: python >>> import numpy as np >>> import xarray as xr >>> from xrspatial.convolution import circle_kernel >>> kernel = circle_kernel(1, 1, 1) >>> kernel array([[0., 1., 0.], [1., 1., 1.], [0., 1., 0.]]) >>> data = np.array([ [0, 0, 0, 0, 0, 0], [1, 1, 2, 2, 1, 1], [2, 2, 1, 1, 2, 2], [3, 3, 0, 0, 3, 3], ]) >>> from xrspatial.focal import focal_stats >>> focal_stats(xr.DataArray(data), kernel, stats_funcs=['min', 'sum']) <xarray.DataArray 'focal_apply' (stats: 2, dim_0: 4, dim_1: 6)> array([[[0., 0., 0., 0., 0., 0.], [0., 0., 0., 0., 0., 0.], [1., 1., 0., 0., 1., 1.], [2., 0., 0., 0., 0., 2.]], [[1., 1., 2., 2., 1., 1.], [4., 6., 6., 6., 6., 4.], [8., 9., 6., 6., 9., 8.], [8., 8., 4., 4., 8., 8.]]]) Coordinates: * stats (stats) object 'min' 'sum' Dimensions without coordinates: dim_0, dim_1 """ # validate raster if not isinstance(agg, DataArray): raise TypeError("`agg` must be instance of DataArray") if agg.ndim != 2: raise ValueError("`agg` must be 2D") # Validate the kernel kernel = custom_kernel(kernel) mapper = ArrayTypeFunctionMapping( numpy_func=_focal_stats_cpu, cupy_func=_focal_stats_cupy, dask_func=_focal_stats_cpu, dask_cupy_func=lambda *args: not_implemented_func( *args, messages='focal_stats() does not support dask with cupy backed DataArray.'), ) result = mapper(agg)(agg, kernel, stats_funcs) return result
def test_kernel_custom_kernel_invalid_shape(): kernel = np.ones((4, 6)) with pytest.raises(ValueError): custom_kernel(kernel)
def test_kernel_custom_kernel_invalid_type(): kernel = [1, 0, 0] # only arrays are accepted, not lists with pytest.raises(ValueError): custom_kernel(kernel)
def setup(self, nx, kernelsize, type): ny = nx // 2 self.agg = get_xr_dataarray((ny, nx), type) kernel_w, kernel_h = kernelsize self.kernel = custom_kernel(np.ones((kernel_h, kernel_w)))