Extract indices of a 2D binary array

290 views Asked by At

I have a numpy array (data) consisting of 0 and 1.

import numpy as np

data = np.array([[1, 1, 1, 1, 1, 1],
                 [1, 1, 1, 1, 1, 0],
                 [1, 1, **1**, 1, 1, 0],
                 [1, 1, 1, 1, 1, 1],
                 [1, 1, 1, 1, 1, 0],
                 [1, 1, 1, 1, 1, 1]])

I want to extract the indices where '1' is surrounded by neighboring 5*5 elements consisting of 1s.

The expected index is shown by asterisks, i.e, (3,3). An answer in the form of a boolean array is also okay.

[[False False False False False False]
 [False False False False False False]
 [False False **True** False False False]
 [False False False False False False]
 [False False False False False False]
 [False False False False False False]]

I tried as

from scipy.ndimage.morphology import binary_erosion


kernel = np.ones((5,5))

result = binary_erosion(data, kernel)
print result

[[False False False False False False]
 [False False False False False False]
 [False False  True False False False]
 [False False  True False False False]
 [False False False False False False]
 [False False False False False False]]

It produced two 'True' positions but I want only one at (3,3).

How to do it?

EDIT: The solution shown in the linked question too gives the unexpected answer.

2

There are 2 answers

2
rth On BEST ANSWER

Alternatively to binary_erosion, you could use the scipy.ndimage.generic_filter to achieve this,

from scipy.ndimage import generic_filter
generic_filter(data, np.all, size=(5,5), mode='constant', cval=0).astype(np.bool)

However you would obtain the same results as above,

array([[0, 0, 0, 0, 0, 0],
       [0, 0, 0, 0, 0, 0],
       [0, 0, 1, 0, 0, 0],
       [0, 0, 1, 0, 0, 0],
       [0, 0, 0, 0, 0, 0],
       [0, 0, 0, 0, 0, 0]])  # using 1, 0 instead of True, False for readability

because it is the correct answer to your question. The 6 rows in your example matrix are symmetric, and with the given kernel, there would be 2 symmetric True element in the result.

If you want to avoid this, you could use a non symmetric-kernel size: size=(6,5), although this produces one true element in row 3 not row 2. This could be fixed, manually padding the kernel array with zeros when using binary_erosion.

0
Imanol Luengo On

Another approach similar to the one of @rth

As your data is made of 1 and 0, you can put it in an int array and use a uniform_filter to achieve the same:

>>> mask = uniform_filter(data, 5, mode='constant')
>>> mask
array([[0, 0, 0, 0, 0, 0],
       [0, 0, 0, 0, 0, 0],
       [0, 0, 1, 0, 0, 0],
       [0, 0, 1, 0, 0, 0],
       [0, 0, 0, 0, 0, 0],
       [0, 0, 0, 0, 0, 0]])

The mean of ones that are not surrounded by ones is less than 1 and is rounded to 0 as it is an integer array.

To get the first index you could do something like:

>>> y, x = np.where(mask)
>>> y[0], x[0]
(2, 2)