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

2019-01-02 18:21发布

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 centre 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.

6条回答
回忆,回不去的记忆
2楼-- · 2019-01-02 18:37

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.

查看更多
爱死公子算了
3楼-- · 2019-01-02 18:39

To get the same result as in your example, you can do something like this:

>>> new_arr = np.array(ones, dtype=object)
>>> new_arr[mask[2:, 2:]] = True
>>> print new_arr
array([[True, True, True, True, 1.0, 1.0, 1.0, 1.0],
       [True, True, True, True, True, 1.0, 1.0, 1.0],
       [True, True, True, True, 1.0, 1.0, 1.0, 1.0],
       [True, True, True, True, 1.0, 1.0, 1.0, 1.0],
       [1.0, True, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0],
       [1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0],
       [1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0],
       [1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0]], dtype=object)
查看更多
倾城一夜雪
4楼-- · 2019-01-02 18:49

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!

查看更多
步步皆殇っ
5楼-- · 2019-01-02 18:54

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
查看更多
何处买醉
6楼-- · 2019-01-02 18:55

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.

查看更多
千与千寻千般痛.
7楼-- · 2019-01-02 19:01

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.

查看更多
登录 后发表回答