From 104b6a7e84ca75131c1c42c2fffe94ea3d35a4bf Mon Sep 17 00:00:00 2001 From: Chase Dwelle Date: Mon, 31 Aug 2020 19:29:40 -0400 Subject: [PATCH 1/9] Adding annulus kernel --- xrspatial/focal.py | 47 ++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 43 insertions(+), 4 deletions(-) diff --git a/xrspatial/focal.py b/xrspatial/focal.py index 3e1a3fc0..6c829273 100644 --- a/xrspatial/focal.py +++ b/xrspatial/focal.py @@ -109,11 +109,11 @@ def _gen_ellipse_kernel(half_w, half_h): return ellipse.astype(float) -def _get_kernel(cellsize_x, cellsize_y, shape='circle', radius=10000): +def _get_kernel(cellsize_x, cellsize_y, shape='circle', radius=10000, r2=None): # validate shape - if shape not in ['circle']: + if shape not in ['circle', 'annulus']: raise ValueError( - "Kernel shape must be \'circle\'") + "Kernel shape must be \'circle\' or \'annulus\'") # validate radius, convert radius to meters r = _get_distance(str(radius)) @@ -122,6 +122,45 @@ def _get_kernel(cellsize_x, cellsize_y, shape='circle', radius=10000): kernel_half_w = int(r / cellsize_x) kernel_half_h = int(r / cellsize_y) kernel = _gen_ellipse_kernel(kernel_half_w, kernel_half_h) + elif shape == 'annulus': + r2 = _get_distance(str(r2)) + + if r2 > r: + r_outer = r2 + r_inner = r + else: + r_outer = r + r_inner = r2 + + if r_outer - r_inner < np.sqrt((cellsize_x / 2)**2 + \ + (cellsize_y / 2)**2): + warnings.warn('Annulus radii are closer than cellsize distance.', + Warning) + + kernel_half_w_outer = int(r_outer / cellsize_x) + kernel_half_h_outer = int(r_outer / cellsize_y) + kernel_outer = _gen_ellipse_kernel(kernel_half_w_outer, + kernel_half_h_outer) + + kernel_half_w_inner = int(r_inner / cellsize_x) + kernel_half_h_inner = int(r_inner / cellsize_y) + kernel_inner = _gen_ellipse_kernel(kernel_half_w_inner, + kernel_half_h_inner) + + # Pad kernel_inner to the same shape and centered in kernel_outer + pad_vals = np.array(kernel_outer.shape) - np.array(kernel_inner.shape) + pad_kernel = np.pad(kernel_inner, + # Pad ((before_rows, after_rows), + # (before_cols, after_cols)) + pad_width=((pad_vals[0] // 2, pad_vals[0] // 2), + (pad_vals[1] // 2, pad_vals[1] // 2)), + mode='constant', + constant_values=0) + # Get annulus by subtracting inner from outer + kernel = kernel_outer - pad_kernel + # Add focal point back to kernel + kernel[kernel.shape[0] // 2, kernel.shape[1] // 2] = 1 + return kernel @@ -342,7 +381,7 @@ def hotspots(raster, x='x', y='y', kernel_shape='circle', kernel_radius=10000): cellsize_x, cellsize_y = _calc_cellsize(raster, x=x, y=y) # create kernel mask array kernel = _get_kernel(cellsize_x, cellsize_y, - shape=kernel_shape, radius=kernel_radius) + shape=kernel_shape, radius=kernel_radius, r2=r2) # apply kernel to raster values mean_array = _apply(raster.values.astype(float), kernel, calc_mean) From e0bd1a0dbc87c25ce3fb827f1e2820f6eb974196 Mon Sep 17 00:00:00 2001 From: Chase Dwelle Date: Mon, 31 Aug 2020 19:30:39 -0400 Subject: [PATCH 2/9] Allow outputting of z-scores --- xrspatial/focal.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/xrspatial/focal.py b/xrspatial/focal.py index 6c829273..9227f63c 100644 --- a/xrspatial/focal.py +++ b/xrspatial/focal.py @@ -333,7 +333,7 @@ def _hotspots(z_array): return out -def hotspots(raster, x='x', y='y', kernel_shape='circle', kernel_radius=10000): +def hotspots(raster, x='x', y='y', kernel_shape='circle', kernel_radius=10000, zscore=False, r2=None): """Identify statistically significant hot spots and cold spots in an input raster. To be a statistically significant hot spot, a feature will have a high value and be surrounded by other features with high values as well. @@ -354,6 +354,7 @@ def hotspots(raster, x='x', y='y', kernel_shape='circle', kernel_radius=10000): raster: xarray.DataArray Input raster image with shape=(height, width) kernel: Kernel + zscore: If True, return z-scores instead of hotspots Returns ------- @@ -394,7 +395,13 @@ def hotspots(raster, x='x', y='y', kernel_shape='circle', kernel_radius=10000): "of the input raster values is 0.") z_array = (mean_array - global_mean) / global_std - out = _hotspots(z_array) + # Return z-scores if desired, otherwise return hotspots + # hotspots is default + if zscore: + out = z_array + else: + out = _hotspots(z_array) + result = DataArray(out, coords=raster.coords, dims=raster.dims, From d662b1eed88aa2e7e9946d0eeb2edd92f9b6e853 Mon Sep 17 00:00:00 2001 From: Chase Dwelle Date: Mon, 31 Aug 2020 19:33:54 -0400 Subject: [PATCH 3/9] Return absolute cell size. Relates to #124 --- xrspatial/focal.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/xrspatial/focal.py b/xrspatial/focal.py index 9227f63c..62bb262a 100644 --- a/xrspatial/focal.py +++ b/xrspatial/focal.py @@ -94,7 +94,8 @@ def _calc_cellsize(raster, x='x', y='y'): cellsize_x = _to_meters(cellsize_x, unit) cellsize_y = _to_meters(cellsize_y, unit) - return cellsize_x, cellsize_y + # When converting from lnglat_to_meters, could have negative cellsize in y + return cellsize_x, np.abs(cellsize_y) def _gen_ellipse_kernel(half_w, half_h): From 78213178c4f340fd72204f50fcc16bf7926d96da Mon Sep 17 00:00:00 2001 From: Chase Dwelle Date: Mon, 31 Aug 2020 19:38:10 -0400 Subject: [PATCH 4/9] Change type check to np.floating --- xrspatial/focal.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/xrspatial/focal.py b/xrspatial/focal.py index 62bb262a..b73053b9 100644 --- a/xrspatial/focal.py +++ b/xrspatial/focal.py @@ -370,7 +370,7 @@ def hotspots(raster, x='x', y='y', kernel_shape='circle', kernel_radius=10000, z raise ValueError("`raster` must be 2D") if not (issubclass(raster.values.dtype.type, np.integer) or - issubclass(raster.values.dtype.type, np.float)): + issubclass(raster.values.dtype.type, np.floating)): raise ValueError( "`raster` must be an array of integers or float") From 05e073b6dad2adb7160ac22b1d0e23e30081f540 Mon Sep 17 00:00:00 2001 From: Chase Dwelle Date: Tue, 1 Sep 2020 20:29:32 -0400 Subject: [PATCH 5/9] Remove focal point from annulus kernel. --- xrspatial/focal.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/xrspatial/focal.py b/xrspatial/focal.py index b73053b9..8cad4e08 100644 --- a/xrspatial/focal.py +++ b/xrspatial/focal.py @@ -159,8 +159,6 @@ def _get_kernel(cellsize_x, cellsize_y, shape='circle', radius=10000, r2=None): constant_values=0) # Get annulus by subtracting inner from outer kernel = kernel_outer - pad_kernel - # Add focal point back to kernel - kernel[kernel.shape[0] // 2, kernel.shape[1] // 2] = 1 return kernel From 88397a92b432ec138c0dc435dac13426bd6bdd14 Mon Sep 17 00:00:00 2001 From: Chase Dwelle Date: Wed, 2 Sep 2020 22:48:10 -0400 Subject: [PATCH 6/9] Remove z-score from hotspots --- xrspatial/focal.py | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/xrspatial/focal.py b/xrspatial/focal.py index 8cad4e08..cba7cf31 100644 --- a/xrspatial/focal.py +++ b/xrspatial/focal.py @@ -332,12 +332,12 @@ def _hotspots(z_array): return out -def hotspots(raster, x='x', y='y', kernel_shape='circle', kernel_radius=10000, zscore=False, r2=None): +def hotspots(raster, kernel, x='x', y='y'): """Identify statistically significant hot spots and cold spots in an input raster. To be a statistically significant hot spot, a feature will have a high value and be surrounded by other features with high values as well. Neighborhood of a feature defined by the input kernel, which currently - support a shape of circle and a radius in meters. + support a shape of circle, annulus, or custom kernel. The result should be a raster with the following 7 values: 90 for 90% confidence high value cluster @@ -353,7 +353,6 @@ def hotspots(raster, x='x', y='y', kernel_shape='circle', kernel_radius=10000, z raster: xarray.DataArray Input raster image with shape=(height, width) kernel: Kernel - zscore: If True, return z-scores instead of hotspots Returns ------- @@ -394,12 +393,7 @@ def hotspots(raster, x='x', y='y', kernel_shape='circle', kernel_radius=10000, z "of the input raster values is 0.") z_array = (mean_array - global_mean) / global_std - # Return z-scores if desired, otherwise return hotspots - # hotspots is default - if zscore: - out = z_array - else: - out = _hotspots(z_array) + out = _hotspots(z_array) result = DataArray(out, coords=raster.coords, From 68eb3efe63a9f4f3281c6523eae068b3563b6c6f Mon Sep 17 00:00:00 2001 From: Chase Dwelle Date: Wed, 2 Sep 2020 22:48:41 -0400 Subject: [PATCH 7/9] Refactored kernels for focal statistics. --- xrspatial/focal.py | 184 ++++++++++++++++++++++++++++++--------------- 1 file changed, 123 insertions(+), 61 deletions(-) diff --git a/xrspatial/focal.py b/xrspatial/focal.py index cba7cf31..e12582ef 100644 --- a/xrspatial/focal.py +++ b/xrspatial/focal.py @@ -79,7 +79,7 @@ def _get_distance(distance_str): return meters -def _calc_cellsize(raster, x='x', y='y'): +def calc_cellsize(raster, x='x', y='y'): if 'unit' in raster.attrs: unit = raster.attrs['unit'] else: @@ -110,56 +110,57 @@ def _gen_ellipse_kernel(half_w, half_h): return ellipse.astype(float) -def _get_kernel(cellsize_x, cellsize_y, shape='circle', radius=10000, r2=None): - # validate shape - if shape not in ['circle', 'annulus']: - raise ValueError( - "Kernel shape must be \'circle\' or \'annulus\'") - +def circular_kernel(cellsize_x, cellsize_y, radius): # validate radius, convert radius to meters r = _get_distance(str(radius)) - if shape == 'circle': - # convert radius (meter) to pixel - kernel_half_w = int(r / cellsize_x) - kernel_half_h = int(r / cellsize_y) - kernel = _gen_ellipse_kernel(kernel_half_w, kernel_half_h) - elif shape == 'annulus': - r2 = _get_distance(str(r2)) - if r2 > r: - r_outer = r2 - r_inner = r - else: - r_outer = r - r_inner = r2 - - if r_outer - r_inner < np.sqrt((cellsize_x / 2)**2 + \ - (cellsize_y / 2)**2): - warnings.warn('Annulus radii are closer than cellsize distance.', - Warning) - - kernel_half_w_outer = int(r_outer / cellsize_x) - kernel_half_h_outer = int(r_outer / cellsize_y) - kernel_outer = _gen_ellipse_kernel(kernel_half_w_outer, - kernel_half_h_outer) - - kernel_half_w_inner = int(r_inner / cellsize_x) - kernel_half_h_inner = int(r_inner / cellsize_y) - kernel_inner = _gen_ellipse_kernel(kernel_half_w_inner, - kernel_half_h_inner) - - # Pad kernel_inner to the same shape and centered in kernel_outer - pad_vals = np.array(kernel_outer.shape) - np.array(kernel_inner.shape) - pad_kernel = np.pad(kernel_inner, - # Pad ((before_rows, after_rows), - # (before_cols, after_cols)) - pad_width=((pad_vals[0] // 2, pad_vals[0] // 2), - (pad_vals[1] // 2, pad_vals[1] // 2)), - mode='constant', - constant_values=0) - # Get annulus by subtracting inner from outer - kernel = kernel_outer - pad_kernel + kernel_half_w = int(r / cellsize_x) + kernel_half_h = int(r / cellsize_y) + kernel = _gen_ellipse_kernel(kernel_half_w, kernel_half_h) + return kernel + +def annulus_kernel(cellsize_x, cellsize_y, outer_radius, inner_radius): + + # validate radii, convert to meters + r2 = _get_distance(str(outer_radius)) + r1 = _get_distance(str(inner_radius)) + + # Validate that outer radius is indeed outer radius + if r2 > r1: + r_outer = r2 + r_inner = r1 + else: + r_outer = r1 + r_inner = r2 + + if r_outer - r_inner < np.sqrt((cellsize_x / 2)**2 + \ + (cellsize_y / 2)**2): + warnings.warn('Annulus radii are closer than cellsize distance.', + Warning) + + kernel_half_w_outer = int(r_outer / cellsize_x) + kernel_half_h_outer = int(r_outer / cellsize_y) + kernel_outer = _gen_ellipse_kernel(kernel_half_w_outer, + kernel_half_h_outer) + + kernel_half_w_inner = int(r_inner / cellsize_x) + kernel_half_h_inner = int(r_inner / cellsize_y) + kernel_inner = _gen_ellipse_kernel(kernel_half_w_inner, + kernel_half_h_inner) + + # Need to pad kernel_inner to get it the same shape and centered + # in kernel_outer + pad_vals = np.array(kernel_outer.shape) - np.array(kernel_inner.shape) + pad_kernel = np.pad(kernel_inner, + # Pad ((before_rows, after_rows), + # (before_cols, after_cols)) + pad_width=((pad_vals[0] // 2, pad_vals[0] // 2), + (pad_vals[1] // 2, pad_vals[1] // 2)), + mode='constant', + constant_values=0) + # Get annulus by subtracting inner from outer + kernel = kernel_outer - pad_kernel return kernel @@ -283,8 +284,81 @@ def _apply(data, kernel_array, func): return out -def apply(raster, x='x', y='y', kernel_shape='circle', kernel_radius=10000, - func=calc_mean): +def _validate_kernel_shape(custom_kernel=None, shape=None, radius=None, + outer_radius=None, inner_radius=None): + + + if (shape == 'circle' and isinstance(_get_distance(str(radius)), float)): + if (custom_kernel is not None or + outer_radius is not None or + inner_radius is not None): + raise ValueError( + "Received additional arguments for kernel creation", + """Circular kernel must be specified by `shape='circle'` and a + valid `radius`. + """ + ) + elif (shape == 'annulus' and + isinstance(_get_distance(str(outer_radius)), float) and + isinstance(_get_distance(str(inner_radius)), float) + ): + if (custom_kernel is not None or radius is not None): + raise ValueError( + "Received additional arguments for kernel creation", + """Annulus kernel must be specified by `shape='annulus'` and a + valid `outer_radius` and `inner_radius`. + """ + ) + elif custom_kernel is not None: + rows, cols = custom_kernel.shape + if (rows % 2 == 0 or cols % 2 == 0): + raise ValueError( + "Received custom kernel with improper dimensions.", + """A custom kernel needs to have an odd shape, the + supplied kernel has {} rows and {} columns. + """.format(rows, cols) + ) + if (shape is not None or + radius is not None or + outer_radius is not None or + inner_radius is not None + ): + raise ValueError( + "Received additional arguments for kernel creation.", + """A custom kernel needs to be supplied by itself.""" + ) + else: + raise ValueError( + "Did not receive an appropriate kernel declaration", + """Valid kernel types are: + `shape` \in ('circle', 'annulus') + `shape='circle'` with `radius` + `shape='annulus'` with `outer_radius` and `inner_radius` + `custom_kernel`: a 2D kernel with odd dimensions + """ + ) + + +def create_kernel(cellsize_x=None, cellsize_y=None, custom_kernel=None, + shape=None, radius=None, + outer_radius=None, inner_radius=None): + # Validate the passed kernel arguments + _validate_kernel_shape(custom_kernel, shape, radius, + outer_radius, inner_radius) + + if shape == 'circle': + kernel = circular_kernel(cellsize_x, cellsize_y, radius) + elif shape == 'annulus': + kernel = annulus_kernel(cellsize_x, cellsize_y, + outer_radius, inner_radius) + # Ensure that custome kernel is numpy array + elif isinstance(custom_kernel, np.ndarray): + kernel = custom_kernel + + return kernel + + +def apply(raster, kernel, x='x', y='y', func=calc_mean): """ """ @@ -305,12 +379,6 @@ def apply(raster, x='x', y='y', kernel_shape='circle', kernel_radius=10000, raise ValueError("raster.coords should be named as coordinates:" "(%s, %s)".format(y, x)) - # calculate cellsize along the x and y axis - cellsize_x, cellsize_y = _calc_cellsize(raster, x=x, y=y) - # create kernel mask array - kernel = _get_kernel(cellsize_x, cellsize_y, - shape=kernel_shape, radius=kernel_radius) - # apply kernel to raster values out = _apply(raster.values.astype(float), kernel, func) @@ -376,12 +444,6 @@ def hotspots(raster, kernel, x='x', y='y'): raise ValueError("raster.coords should be named as coordinates:" "(%s, %s)".format(y, x)) - # calculate cellsize along the x and y axis - cellsize_x, cellsize_y = _calc_cellsize(raster, x=x, y=y) - # create kernel mask array - kernel = _get_kernel(cellsize_x, cellsize_y, - shape=kernel_shape, radius=kernel_radius, r2=r2) - # apply kernel to raster values mean_array = _apply(raster.values.astype(float), kernel, calc_mean) From 0e3d9ec726a862888a6b7402be9664087020a740 Mon Sep 17 00:00:00 2001 From: Chase Dwelle Date: Wed, 2 Sep 2020 22:49:00 -0400 Subject: [PATCH 8/9] Adding tests for refactored focal statistics. --- xrspatial/tests/test_focal.py | 83 ++++++++++++++++++++++++++++------- 1 file changed, 67 insertions(+), 16 deletions(-) diff --git a/xrspatial/tests/test_focal.py b/xrspatial/tests/test_focal.py index 78203959..9be32ff0 100644 --- a/xrspatial/tests/test_focal.py +++ b/xrspatial/tests/test_focal.py @@ -2,7 +2,14 @@ import numpy as np from xrspatial import mean -from xrspatial.focal import apply, calc_mean, calc_sum, hotspots +from xrspatial.focal import ( + apply, + create_kernel, + calc_mean, + calc_sum, + hotspots, + calc_cellsize, +) import pytest @@ -54,33 +61,51 @@ def test_mean_transfer_function(): da_mean[:, -1] = data_random[:, -1] assert abs(da_mean.mean() - data_random.mean()) < 10**-3 - -def test_apply(): +def test_kernel(): n, m = 6, 6 raster = xr.DataArray(np.ones((n, m)), dims=['y', 'x']) raster['x'] = np.linspace(0, n, n) raster['y'] = np.linspace(0, m, m) - # invalid shape + cellsize_x, cellsize_y = calc_cellsize(raster) + + # Passing extra kernel arguments for `circle` with pytest.raises(Exception) as e_info: - apply(raster, x='x', y='y', kernel_shape='line') + create_kernel(cellsize_x, cellsize_y, + shape='circle', radius=2, outer_radius=4) assert e_info - # invalid radius distance unit + # Passing extra kernel arguments for `annulus` with pytest.raises(Exception) as e_info: - apply(raster, x='x', y='y', kernel_radius='10 inch') + create_kernel(cellsize_x, cellsize_y, + shape='annulus', radius=2, inner_radisu=2, outer_radius=4) assert e_info - # non positive distance + # Passing custom kernel with even dimensions with pytest.raises(Exception) as e_info: - apply(raster, x='x', y='y', kernel_radius=0) + create_kernel(cellsize_x, cellsize_y, + custom_kernel=np.ones((2, 2))) assert e_info - # invalid dims + # Invalid kernel shape with pytest.raises(Exception) as e_info: - apply(raster, x='lon', y='lat') + create_kernel(cellsize_x, cellsize_y, shape='line') assert e_info + # invalid radius distance unit + with pytest.raises(Exception) as e_info: + create_kernel(cellsize_x, cellsize_y, + shape='circle', radius='10 inch') + assert e_info + + +def test_apply(): + n, m = 6, 6 + raster = xr.DataArray(np.ones((n, m)), dims=['y', 'x']) + raster['x'] = np.linspace(0, n, n) + raster['y'] = np.linspace(0, m, m) + cellsize_x, cellsize_y = calc_cellsize(raster) + # test apply() with calc_sum and calc_mean function # add some nan pixels nan_cells = [(i, i) for i in range(n)] @@ -88,7 +113,8 @@ def test_apply(): raster[cell[0], cell[1]] = np.nan # kernel array = [[1]] - sum_output_1 = apply(raster, kernel_radius=1, func=calc_sum) + kernel = create_kernel(custom_kernel=np.ones((1, 1))) + sum_output_1 = apply(raster, kernel, func=calc_sum) # np.nansum(np.array([np.nan])) = 0.0 expected_out_sum_1 = np.array([[0., 1., 1., 1., 1., 1.], [1., 0., 1., 1., 1., 1.], @@ -99,7 +125,7 @@ def test_apply(): assert np.all(sum_output_1.values == expected_out_sum_1) # np.nanmean(np.array([np.nan])) = nan - mean_output_1 = apply(raster, kernel_radius=1, func=calc_mean) + mean_output_1 = apply(raster, kernel, func=calc_mean) for cell in nan_cells: assert np.isnan(mean_output_1[cell[0], cell[1]]) # remaining cells are 1s @@ -111,7 +137,9 @@ def test_apply(): # kernel array: [[0, 1, 0], # [1, 1, 1], # [0, 1, 0]] - sum_output_2 = apply(raster, kernel_radius=2, func=calc_sum) + kernel = create_kernel(cellsize_x=cellsize_x, cellsize_y=cellsize_y, + shape='circle', radius=2.0) + sum_output_2 = apply(raster, kernel, func=calc_sum) expected_out_sum_2 = np.array([[2., 2., 4., 4., 4., 3.], [2., 4., 3., 5., 5., 4.], [4., 3., 4., 3., 5., 4.], @@ -121,16 +149,39 @@ def test_apply(): assert np.all(sum_output_2.values == expected_out_sum_2) - mean_output_2 = apply(raster, kernel_radius=2, func=calc_mean) + mean_output_2 = apply(raster, kernel, func=calc_mean) expected_mean_output_2 = np.ones((n, m)) assert np.all(mean_output_2.values == expected_mean_output_2) + # kernel array: [[0, 1, 0], + # [1, 0, 1], + # [0, 1, 0]] + kernel = create_kernel(cellsize_x=cellsize_x, cellsize_y=cellsize_y, + shape='annulus', outer_radius=2.0, inner_radius=0.5) + sum_output_3 = apply(raster, kernel, func=calc_sum) + expected_out_sum_3 = np.array([[2., 1., 3., 3., 3., 2.], + [1., 4., 2., 4., 4., 3.], + [3., 2., 4., 2., 4., 3.], + [3., 4., 2., 4., 2., 3.], + [3., 4., 4., 2., 4., 1.], + [2., 3., 3., 3., 1., 2.]]) + + assert np.all(sum_output_3.values == expected_out_sum_3) + + mean_output_3 = apply(raster, kernel, func=calc_mean) + expected_mean_output_3 = np.ones((n, m)) + assert np.all(mean_output_3.values == expected_mean_output_3) + def test_hotspot(): n, m = 10, 10 raster = xr.DataArray(np.zeros((n, m), dtype=float), dims=['y', 'x']) raster['x'] = np.linspace(0, n, n) raster['y'] = np.linspace(0, m, m) + cellsize_x, cellsize_y = calc_cellsize(raster) + + kernel = create_kernel(cellsize_x=cellsize_x, cellsize_y=cellsize_y, + shape="circle", radius=2) all_idx = zip(*np.where(raster.values == 0)) @@ -153,7 +204,7 @@ def test_hotspot(): no_significant_region = [id for id in all_idx if id not in hot_region and id not in cold_region] - hotspots_output = hotspots(raster, kernel_radius=2) + hotspots_output = hotspots(raster, kernel) # check output's properties # output must be an xarray DataArray From 644a37be6a241939ccdd7ac0962e2a0c0deb0124 Mon Sep 17 00:00:00 2001 From: Chase Dwelle Date: Thu, 3 Sep 2020 07:59:04 -0400 Subject: [PATCH 9/9] Simplify annulus kernel function by referencing circular kernel --- xrspatial/focal.py | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/xrspatial/focal.py b/xrspatial/focal.py index e12582ef..b6f58494 100644 --- a/xrspatial/focal.py +++ b/xrspatial/focal.py @@ -139,15 +139,9 @@ def annulus_kernel(cellsize_x, cellsize_y, outer_radius, inner_radius): warnings.warn('Annulus radii are closer than cellsize distance.', Warning) - kernel_half_w_outer = int(r_outer / cellsize_x) - kernel_half_h_outer = int(r_outer / cellsize_y) - kernel_outer = _gen_ellipse_kernel(kernel_half_w_outer, - kernel_half_h_outer) - - kernel_half_w_inner = int(r_inner / cellsize_x) - kernel_half_h_inner = int(r_inner / cellsize_y) - kernel_inner = _gen_ellipse_kernel(kernel_half_w_inner, - kernel_half_h_inner) + # Get the two circular kernels for the annulus + kernel_outer = circular_kernel(cellsize_x, cellsize_y, outer_radius) + kernel_inner = circular_kernel(cellsize_x, cellsize_y, inner_radius) # Need to pad kernel_inner to get it the same shape and centered # in kernel_outer