How to buffer raster pixels by their values?
Here is a pure raster solution in Python 2.7
using numpy
and scipy
:
import numpy as np
from scipy import ndimage
import matplotlib.pyplot as plt
#create tree location matrix with values indicating crown radius
A = np.zeros((120,320))
A[60,40] = 1
A[60,80] = 2
A[60,120] = 3
A[60,160] = 4
A[60,200] = 5
A[60,240] = 6
A[60,280] = 7
#plot tree locations
fig = plt.figure()
plt.imshow(A, interpolation='none')
plt.colorbar()
#find unique values
unique_vals = np.unique(A)
unique_vals = unique_vals[unique_vals > 0]
# create circular kernel
def createKernel(radius):
kernel = np.zeros((2*radius+1, 2*radius+1))
y,x = np.ogrid[-radius:radius+1, -radius:radius+1]
mask = x**2 + y**2 <= radius**2
kernel[mask] = 1
return kernel
#apply binary dilation sequentially to each unique crown radius value
C = np.zeros(A.shape).astype(bool)
for k, radius in enumerate(unique_vals):
B = ndimage.morphology.binary_dilation(A == unique_vals[k], structure=createKernel(radius))
C = C | B #combine masks
#plot resulting mask
fig = plt.figure()
plt.imshow(C, interpolation='none')
plt.show()
Input:
Output:
Vector-based approach
This task can be done in three steps:
Raster To Point
;Buffer
(using theVALUE
field as buffer field);Feature To Raster
.
Note: using the buffer field avoids the calculation of a buffer for each crown radius value.
Raster-based approach
Avoiding the vector-based solution, this problem suggests to use a kind of Cellular Automata based on the nearest neighbours. Assuming that all the black pixels are zeros, the pixels are squared and their size is equal to 1 (or, alternatively, are opportunely scaled), the rules to adopt are very simple:
- If the pixel value (
VALUE
) is greater than 1, its value becomesVALUE-1
and then consider its surrounding pixels. If their values are less thanVALUE-1
, these pixels born or grow and their value becomesVALUE-1
. Otherwise, these pixels survive and are left unchanged. - If
VALUE<=1
, do nothing (the pixel is dead!).
These rules have to be applied until all the pixels are dead, i.e. their values is equal to 0 or 1. So N-1
times, where N
is the maximum value you have in the input raster.
This approach can be quite easily implemented with a bit of Python and numpy.
It is a challenging question to do this in raster because you don't have the opportunity to use the value of the pixel for defining the size of the buffer. Therefore you would need to do the focal filter for each value, as you already said.
Here is a possible answer to do it with only 3 filters (I couldn't find less), but not perfectly as mentioned by Whuber : your buffers will be truncated when trees are close to each others.
1) EDIT : Euclidian allocation (this does not completely solve the issue, as it cuts the buffers in the vicinity of smaller trees, but it is better than the artefacts of my first solution).
2) euclidian distance around each pixel
3) raster calculator (map algebra) with a conditional statement
Con("allocation_result" > ("distance_result" / pixel_size) , 1 , 0)
Note that you can adjust the condtion depending on your needs in terms of radius (with or withot the central pixel)