Numpy, counting unique neighbours in 2d array
I am trying to count the number of neighbours for each element in a 2d numpy array that differ from the element itself (4neighbourhood in this case, but 8neighbourhood is also interesting).
Something like this:
input labels:
[[1 1 1 2 2 2 2]
[1 1 1 2 2 2 2]
[1 1 1 2 2 2 2]
[1 1 3 3 3 5 5]
[4 4 4 3 3 5 5]
[4 4 4 3 3 5 5]] (6, 7)
count of unique neighbour labels:
[[0 0 1 1 0 0 0]
[0 0 1 1 0 0 0]
[0 0 2 2 1 1 1]
[1 2 2 1 2 2 1]
[1 1 1 1 1 1 0]
[0 0 1 1 1 1 0]] (6, 7)
I have the code below, and out of curiosity I am wondering if there is a better way to achieve this, perhaps without the for loops?
import numpy as np
import cv2
labels_image = np.array([
[1,1,1,2,2,2,2],
[1,1,1,2,2,2,2],
[1,1,1,2,2,2,2],
[1,1,3,3,3,5,5],
[4,4,4,3,3,5,5],
[4,4,4,3,3,5,5]])
print('input labels:\n', labels_image, labels_image.shape)
# Make a border, otherwise neighbours are counted as wrapped values from the other side
labels_image = cv2.copyMakeBorder(labels_image, 1, 1, 1, 1, cv2.BORDER_REPLICATE)
offsets = [(1, 0), (0, 1), (0, 1), (1, 0)] # 4 neighbourhood
# Stack labels_image with one shifted per offset so we get a 3d array
# where each zvalue corresponds to one of the neighbours
stacked = np.dstack(np.roll(np.roll(labels_image, i, axis=0), j, axis=1) for i, j in offsets)
# count number of unique neighbours, also take the border away again
labels_image = np.array([[(len(np.unique(stacked[i,j]))  1)
for j in range(1, labels_image.shape[1]  1)]
for i in range(1, labels_image.shape[0]  1)])
print('count of unique neighbour labels:\n', labels_image, labels_image.shape)
I tried using np.unique with the return_counts
and axis
arguments, but could not get it to work.
1 answer

Here's one approach 
import itertools def count_nunique_neighbors(ar): a = np.pad(ar, (1,1), mode='reflect') c = a[1:1,1:1] top = a[:2,1:1] bottom = a[2:,1:1] left = a[1:1,:2] right = a[1:1,2:] ineq = [top!= c,bottom!= c, left!= c, right!= c] count = ineq[0].astype(int) + ineq[1] + ineq[2] + ineq[3] blck = [top, bottom, left, right] for i,j in list(itertools.combinations(range(4), r=2)): count = ((blck[i] == blck[j]) & ineq[j]) return count
Sample run 
In [22]: a Out[22]: array([[1, 1, 1, 2, 2, 2, 2], [1, 1, 1, 2, 2, 2, 2], [1, 1, 1, 2, 2, 2, 2], [1, 1, 3, 3, 3, 5, 5], [4, 4, 4, 3, 3, 5, 5], [4, 4, 4, 3, 3, 5, 5]]) In [23]: count_nunique_neighbors(a) Out[23]: array([[0, 0, 1, 1, 0, 0, 0], [0, 0, 1, 1, 0, 0, 0], [0, 0, 2, 2, 1, 1, 1], [1, 2, 2, 1, 2, 2, 1], [1, 1, 1, 1, 1, 1, 0], [0, 0, 1, 1, 1, 1, 0]])