6

I am looking for the following. I have a numpy array which is labeled as regions. The numpy array represents a segmented image. A region is a number of adjacent cells with the same value. Each region has its own unique value. A simplified version with 3 regions would look like this:

x = np.array([[1, 1, 1], [1, 1, 2], [2, 2, 2], [3, 3, 3]], np.int32)

output:

array([[1, 1, 1],
       [1, 1, 2],
       [2, 2, 2],
       [3, 3, 3]])

In the above example we have 3 separate regions, each labeled with an unique value (1,2,3 in this case).

What I want is the value of adjacent (neighbor) regions for each individual region. So in this case:

  • Region 1 is adjacent to region 2
  • Region 2 is adjacent to region 1 and 3
  • Region 3 is adjacent to region 2

What would be the most elegant and fastest way of achieving this?

Many thanks!

cf2
  • 581
  • 1
  • 7
  • 17

2 Answers2

5

I understand that the task is to return all distinct entries of the array that are adjacent to a given number (such as 2). One way to achieve this with NumPy methods is to use roll to shift the given region by one unit up, down, left, and right. The logical OR of the shifted regions is taken, and all distinct elements that match this condition are returned. It then remains to remove the region itself, since it's not considered its own neighbor.

Since roll re-introduces the values that move beyond array's bounds at the opposite ends (which is not desired here), an additional step is to replace this row or column with False.

import numpy as np

x = np.array([[1, 1, 1], [1, 1, 2], [2, 2, 2], [3, 3, 3]], np.int32)
region = 2   # number of region whose neighbors we want

y = x == region  # convert to Boolean

rolled = np.roll(y, 1, axis=0)          # shift down
rolled[0, :] = False             
z = np.logical_or(y, rolled)

rolled = np.roll(y, -1, axis=0)         # shift up 
rolled[-1, :] = False
z = np.logical_or(z, rolled)

rolled = np.roll(y, 1, axis=1)          # shift right
rolled[:, 0] = False
z = np.logical_or(z, rolled)

rolled = np.roll(y, -1, axis=1)         # shift left
rolled[:, -1] = False
z = np.logical_or(z, rolled)

neighbors = set(np.unique(np.extract(z, x))) - set([region])
print(neighbors)
  • This works perfectly. I've also tested it on a large dataset, where the regions are randomly numbered and there it also works as it should. Many thanks for your solution! – cf2 Jun 29 '16 at 08:06
3

If the regions are labelled with small integers (from 0 to n ideally), the labels can be used to index into a result array:

n = x.max()
tmp = np.zeros((n+1, n+1), bool)

# check the vertical adjacency
a, b = x[:-1, :], x[1:, :]
tmp[a[a!=b], b[a!=b]] = True

# check the horizontal adjacency
a, b = x[:, :-1], x[:, 1:]
tmp[a[a!=b], b[a!=b]] = True

# register adjacency in both directions (up, down) and (left,right)
result = (tmp | tmp.T)

For the example array in the question:

In [58]: result.astype(int)
Out[58]: 
array([[0, 0, 0, 0],
       [0, 0, 1, 0],
       [0, 1, 0, 1],
       [0, 0, 1, 0]])

In [60]: np.column_stack(np.nonzero(result))
Out[60]: 
array([[1, 2],
       [2, 1],
       [2, 3],
       [3, 2]])

In [361]: # Assuming labels start from `1`
          [np.flatnonzero(row) for row in result[1:]]
Out[361]: [array([2]), array([1, 3]), array([2])]
  • I really like your solution as it returns an indexed results array for all regions. However my regions are randomly numbered and because of this your solution unfortunately doesn't work on my dataset. Thanks for the effort though! I will keep this solution for when I have a dataset with ordered regions. – cf2 Jun 29 '16 at 08:10