How to apply a disc shaped mask to a NumPy array?

36,418

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.

Share:
36,418
user816555
Author by

user816555

Updated on May 04, 2021

Comments

  • user816555
    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
    mac over 12 years
    It 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
    user816555 over 12 years
    I'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
    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
    user816555 over 12 years
    I'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
    12944qwerty about 3 years
    How does this answer the question?
  • 12944qwerty
    12944qwerty about 3 years
    This 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
    Hoppeduppeanut about 3 years
    While 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.