4

I'm looking to solve the following problem. I have a numpy array which is labeled to regions from 1 to n. Let's say this is the array:

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

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

A region are the combined cells in the numpy array with a unique value. So in this example x has 5 regions; region 1 which consists of 5 cells, region 2 which consists of 3 cells etc. Now, I determine the adjacent regions of each region with the following lines of code:

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)

result = result.astype(int)
np.column_stack(np.nonzero(result))
resultlist = [np.flatnonzero(row) for row in result[1:]]

Which gives me a list of each region with its adjacent regions:

[array([2, 4, 5], dtype=int64),
 array([1, 3, 4, 5], dtype=int64),
 array([2, 4, 5], dtype=int64),
 array([1, 2, 3], dtype=int64),
 array([1, 2, 3], dtype=int64)]

Which works really well. However, I would like to count the amount of cells of each adjacent region and return this output. So, for region 2, in this example would mean a total of 7 adjacent regions (three 1s, two 4s, one 3 and one 5). Therefore:

  • 2 is surrounded for 43% by 1
  • 2 is surround for 14 % by 5
  • 2 is surrounded for 14% by 3
  • 2 is surrounded for 29% by 4

How could I best adjust the above code to include the amount of cells for each adjacent region? Many thanks guys!

cf2
  • 581
  • 1
  • 7
  • 17
  • What is a region? – vz0 Sep 06 '16 at 10:45
  • A region are the combined cells in the numpy array with a unique value. So in this example x has 5 regions; region 1 which consists of 5 cells, region 2 which consists of 3 cells etc. – cf2 Sep 06 '16 at 10:59
  • What has to be the shape of the region? There are six, not five, adjacent number 1's. – vz0 Sep 06 '16 at 11:01

1 Answers1

3

Here is a vectorized solution using the numpy_indexed package (note; it isn't vectorized over the regions, but it is vectorized over the pixels, which is the useful thing to do assuming n_pixels >> n_regions):

neighbors = np.concatenate([x[:, :-1].flatten(), x[:, +1:].flatten(), x[+1:, :].flatten(), x[:-1, :].flatten()])
centers   = np.concatenate([x[:, +1:].flatten(), x[:, :-1].flatten(), x[:-1, :].flatten(), x[+1:, :].flatten()])
valid = neighbors != centers

import numpy_indexed as npi
regions, neighbors_per_regions = npi.group_by(centers[valid], neighbors[valid])
for region, neighbors_per_region in zip(regions, neighbors_per_regions):
    print(region)
    unique_neighbors, neighbor_counts = npi.count(neighbors_per_region)
    print(unique_neighbors, neighbor_counts / neighbor_counts.sum() * 100)

Or for a solution which is fully vectorized over both pixels and regions:

(neighbors, centers), counts  = npi.count((neighbors[valid], centers[valid]))
region_group = group_by(centers)
regions, neighbors_per_region = region_group.sum(counts)
fractions = counts / neighbors_per_region[region_group.inverse]
for q in zip(centers, neighbors, fractions): print(q)
Eelco Hoogendoorn
  • 10,459
  • 1
  • 44
  • 42
  • Thanks for your reply! Neat and fast method which almost works. The only problem is that it also counts its own region. How can I exclude the region itself from being counted with its neighbors? For example, now region 1 also sees the six region 1 cells as its neighbor: `1 (array([1, 2, 4, 5]), array([ 0.66666667, 0.22222222, 0.05555556, 0.05555556]))` Many thanks again! Your solutions are always great. – cf2 Sep 06 '16 at 12:49
  • Ah yeah didn't think of that! Untested, but I think this should solve it: unique_neighbors, neighbor_counts = npi.count(n[n != region]) – Eelco Hoogendoorn Sep 06 '16 at 12:57
  • Solved indeed! Your numpy-indexed package is truly awesome! Many thanks again Eelco for your fast reply and great help :) – cf2 Sep 06 '16 at 13:08
  • come to think of it, is more efficient to do the filtering logic before the grouping; something along the lines of selected = centers.flatten() != neighbors.flatten() – Eelco Hoogendoorn Sep 06 '16 at 13:17
  • Thanks for the addition. I ran into a problem today when implementing the function on a larger dataset with unequal dimensions (6695 x 10000). The neighbors and center outputs then generate 4 arrays with neighbors instead of 1. How could I implement this on an image with unequal dimensions? Thankyou! – cf2 Sep 07 '16 at 08:16
  • This should do it I think – Eelco Hoogendoorn Sep 07 '16 at 08:24