destabd7
destabd7

Reputation: 13

Calculate number of cells with 2 neighboors or more, within a numpy array

I'm trying to achieve a similar goal to this question. The objective is to count the number of 1s with at least two direct neighbours (up, down, left, right), the only difference with my version is that there must be at least one cross or corner in the sequence.

For example,

array([[0, 0, 0, 0, 0, 1],
       [0, 1,*1, 1, 0, 0],
       [0, 0, 0, 0, 0, 0],
       [1, 0, 0, 0, 0, 0],
       [1, 1, 0, 1, 1, 0],
       [1, 0, 0, 0, 1, 0]])

The starred *1 does not count as its neighbours do not form a corner or a cross. Nor does the top right 1 as it doesn't have two neighbours. The answer would be 2 (bottom right and left).

Here's what I tried:

import numpy as np
a = np.zeros((6,6), dtype=np.int)
a[0,5] = 1
a[1,1] = a[1,2] = a[1,3] = 1
a[4,4] = a[5,4] = a[4,3] = 1
a[3,0] = a[4,0] = a[5,0] = a[4,1] = 1

from scipy import ndimage
kernel = ndimage.generate_binary_structure(2, 1)
kernel[1, 1] = 0

b = convolve(a, kernel, mode="constant")
c = b[a>0]

# At least 2 neighbours
return len(c[c >= 2])

But it still counts the starred *1 from the second row (returns 3 instead of 2)!

Thanks for any help!

Upvotes: 1

Views: 158

Answers (2)

Paul Panzer
Paul Panzer

Reputation: 53069

Your criterion can be restated "at least one neighbor horizontally and at least one neighbor vertically". So do whatever neighbor detection you'll end up choosing not in 2D but twice in 1D, once horizontally and once vertically:

For example, using scipy.ndimage which provides convolve1d:

import scipy.ndimage as sn

(sn.convolve1d(in_,(1,3,1),axis=0,mode="constant")>>2) & (sn.convolve1d(in_,(1,3,1),axis=1,mode="constant")>>2)
# array([[0, 0, 0, 0, 0, 0],
#        [0, 0, 0, 0, 0, 0],
#        [0, 0, 0, 0, 0, 0],
#        [0, 0, 0, 0, 0, 0],
#        [1, 0, 0, 0, 1, 0],
#        [0, 0, 0, 0, 0, 0]])

(in_ is the example array in OP.)

Upvotes: 2

GrimTrigger
GrimTrigger

Reputation: 591

You are attempting a 2d-convolution. You may want to try a "cross-shaped" kernel, which would result in a 3 or more (=1+2, 1 from the 1 at the center, 2 from the neighbors) for the relevant cells. signal.convolve2d from scipy accomplishes this, like so:

import numpy as np
from scipy import ndimage
from scipy import signal

a = np.zeros((6,6), dtype=np.int)
a[0,5] = 1
a[1,1] = a[1,2] = a[1,3] = 1
a[4,4] = a[5,4] = a[4,3] = 1
a[3,0] = a[4,0] = a[5,0] = a[4,1] = 1

print(a)

#kernel = ndimage.generate_binary_structure(2, 1)
#kernel[1, 1] = 0
kernel = np.array([[0,1,0], [1,1,1], [0,1,0]])

#b = np.convolve(a, kernel, mode="constant")
#c = b[a>0]
b = signal.convolve2d(a, kernel, mode='same')

# At least 2 neighbours
#return len(c[c >= 2])
print(b)
print(np.where(b > 2, b, 0))

which produces (I have filtered the results with np.where):

[[0 0 0 0 0 1]
 [0 1 1 1 0 0]
 [0 0 0 0 0 0]
 [1 0 0 0 0 0]
 [1 1 0 1 1 0]
 [1 0 0 0 1 0]]
[[0 1 1 1 1 1]
 [1 2 3 2 1 1]
 [1 1 1 1 0 0]
 [2 2 0 1 1 0]
 [4 2 2 2 3 1]
 [2 2 0 2 2 1]]
[[0 0 0 0 0 0]
 [0 0 3 0 0 0]
 [0 0 0 0 0 0]
 [0 0 0 0 0 0]
 [4 0 0 0 3 0]
 [0 0 0 0 0 0]]

The locations of the 1s have been correctly identified (3 or 4s).

Upvotes: 1

Related Questions