How to apply a disc shaped mask to a NumPy array?
Solution 1
I would do it like this, where (a, b) is the center of your mask:
import numpy as np
a, b = 1, 1
n = 7
r = 3
y,x = np.ogrid[-a:n-a, -b:n-b]
mask = x*x + y*y <= r*r
array = np.ones((n, n))
array[mask] = 255
Solution 2
You could use scipy's convolve function, which has the benefit of allowing you to place any particular mask, aka kernel, on any number of given coordinates in your array, all at once:
import numpy as np
from scipy.ndimage.filters import convolve
First create a coordinate array with the coordinate of where you want the mask (kernel) to be centered marked as 2
background = np.ones((10,10))
background[5,5] = 2
print(background)
[[ 1. 1. 1. 1. 1. 1. 1. 1. 1. 1.]
[ 1. 1. 1. 1. 1. 1. 1. 1. 1. 1.]
[ 1. 1. 1. 1. 1. 1. 1. 1. 1. 1.]
[ 1. 1. 1. 1. 1. 1. 1. 1. 1. 1.]
[ 1. 1. 1. 1. 1. 1. 1. 1. 1. 1.]
[ 1. 1. 1. 1. 1. 2. 1. 1. 1. 1.]
[ 1. 1. 1. 1. 1. 1. 1. 1. 1. 1.]
[ 1. 1. 1. 1. 1. 1. 1. 1. 1. 1.]
[ 1. 1. 1. 1. 1. 1. 1. 1. 1. 1.]
[ 1. 1. 1. 1. 1. 1. 1. 1. 1. 1.]]
Create your mask:
y,x = np.ogrid[-3: 3+1, -3: 3+1]
mask = x**2+y**2 <= 3**2
mask = 254*mask.astype(float)
print(mask)
[[ 0. 0. 0. 254. 0. 0. 0.]
[ 0. 254. 254. 254. 254. 254. 0.]
[ 0. 254. 254. 254. 254. 254. 0.]
[ 254. 254. 254. 254. 254. 254. 254.]
[ 0. 254. 254. 254. 254. 254. 0.]
[ 0. 254. 254. 254. 254. 254. 0.]
[ 0. 0. 0. 254. 0. 0. 0.]]
Convolve the two images:
b = convolve(background, mask)-sum(sum(mask))+1
print(b)
[[ 1. 1. 1. 1. 1. 1. 1. 1. 1. 1.]
[ 1. 1. 1. 1. 1. 1. 1. 1. 1. 1.]
[ 1. 1. 1. 1. 1. 255. 1. 1. 1. 1.]
[ 1. 1. 1. 255. 255. 255. 255. 255. 1. 1.]
[ 1. 1. 1. 255. 255. 255. 255. 255. 1. 1.]
[ 1. 1. 255. 255. 255. 255. 255. 255. 255. 1.]
[ 1. 1. 1. 255. 255. 255. 255. 255. 1. 1.]
[ 1. 1. 1. 255. 255. 255. 255. 255. 1. 1.]
[ 1. 1. 1. 1. 1. 255. 1. 1. 1. 1.]
[ 1. 1. 1. 1. 1. 1. 1. 1. 1. 1.]]
Note that the convolve function entries do not commute, i.e. convolve(a,b) != convolve(b,a)
Note also that if your point is near an edge, the algo does not reproduce the kernel at the coordinate. To get around this you can pad the background by the largest axis of your kernel, apply the convolution, then remove the padding.
Now, you can map any kernel to any number of points in an array, but note that if two kernels overlap, they add at the overlap. You can threshold this if you need.
Solution 3
I just wanted to share with everyone a slightly more advanced application of this technique that I just had to face.
My problem was to apply this circular kernel to compute the mean of all the values surrounding each point in a 2D matrix. The kernel generated can be passed to scipy's generic filter in the following way:
import numpy as np
from scipy.ndimage.filters import generic_filter as gf
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
circular_mean = gf(data, np.mean, footprint=kernel)
Hope this helps!
Solution 4
To put it one convenient function:
def cmask(index,radius,array):
a,b = index
nx,ny = array.shape
y,x = np.ogrid[-a:nx-a,-b:ny-b]
mask = x*x + y*y <= radius*radius
return(sum(array[mask]))
Returns the pixel sum within radius, or return(array[mask] = 2) for whatever need.
Solution 5
Did you try making a mask or zeroes and ones and then using per-element array multiplication? This is the canonical way, more or less.
Also, are you certain you want a mix of numbers and booleans in a numpy array? NumPy, as the name implies, works best with numbers.
user816555
Updated on May 04, 2021Comments
-
user816555 about 3 years
I have an array like this:
>>> np.ones((8,8)) array([[ 1., 1., 1., 1., 1., 1., 1., 1.], [ 1., 1., 1., 1., 1., 1., 1., 1.], [ 1., 1., 1., 1., 1., 1., 1., 1.], [ 1., 1., 1., 1., 1., 1., 1., 1.], [ 1., 1., 1., 1., 1., 1., 1., 1.], [ 1., 1., 1., 1., 1., 1., 1., 1.], [ 1., 1., 1., 1., 1., 1., 1., 1.], [ 1., 1., 1., 1., 1., 1., 1., 1.]])
I'm creating a disc shaped mask with radius 3 thus:
y,x = np.ogrid[-3: 3+1, -3: 3+1] mask = x**2+y**2 <= 3**2
This gives:
>> mask array([[False, False, False, True, False, False, False], [False, True, True, True, True, True, False], [False, True, True, True, True, True, False], [ True, True, True, True, True, True, True], [False, True, True, True, True, True, False], [False, True, True, True, True, True, False], [False, False, False, True, False, False, False]], dtype=bool)
Now, I want to be able to apply this mask to my array, using any element as a center point. So, for example, with center point at (1,1), I want to get an array like:
>>> new_arr array([[ True, True, True, True, 1., 1., 1., 1.], [ True, True, True, True, True, 1., 1., 1.], [ True, True, True, True, 1., 1., 1., 1.], [ True, True, True, True, 1., 1., 1., 1.], [ 1., True, 1., 1., 1., 1., 1., 1.], [ 1., 1., 1., 1., 1., 1., 1., 1.], [ 1., 1., 1., 1., 1., 1., 1., 1.], [ 1., 1., 1., 1., 1., 1., 1., 1.]])
Is there an easy way to apply this mask?
Edit: I shouldn't have mixed booleans and floats - it was misleading.
>>> new_arr array([[ 255., 255., 255., 255., 1., 1., 1., 1.], [ 255., 255., 255., 255., 255., 1., 1., 1.], [ 255., 255., 255., 255., 1., 1., 1., 1.], [ 255., 255., 255., 255., 1., 1., 1., 1.], [ 1., 255., 1., 1., 1., 1., 1., 1.], [ 1., 1., 1., 1., 1., 1., 1., 1.], [ 1., 1., 1., 1., 1., 1., 1., 1.], [ 1., 1., 1., 1., 1., 1., 1., 1.]])
This is more the result I require.
array[mask] = 255
will mask the array using center point (0+radius,0+radius).
However, I'd like to be able to place any size mask at any point (y,x) and have it automatically trimmed to fit.
-
mac over 12 yearsIt works... but that's quite a hack, with array duplication and changing the dtype of it... Multiplication by 0/1 is the canonical way as suggested by @9000.
-
user816555 over 12 yearsI'm sorry about the confusion regarding the number/booleans mix. Hopefully the question isn't as misleading anymore. Could you explain your first sentence more?
-
jcollado over 12 years@mac Yes, I agree. I expected to get some feedback from the OP to find out what he's really looking for.
-
user816555 over 12 yearsI'm sorry for being misleading. I clarified my question in my post. What I want is a way to get the elements in the original array that are covered by the mask, given a center point (y,x) for the mask. Then I can manipulate them as required.
-
12944qwerty about 3 yearsHow does this answer the question?
-
12944qwerty about 3 yearsThis does not provide an answer to the question. You can search for similar questions, or refer to the related and linked questions on the right-hand side of the page to find an answer. If you have a related but different question, ask a new question, and include a link to this one to help provide context. See the tour
-
Hoppeduppeanut about 3 yearsWhile this might answer the question, if possible you should edit your answer to include an explanation of how this code block answers the question. This helps to provide context and makes your answer much more useful for future readers.