Search code examples
pythonarraysnumpyindices

numpy 2D array: get indices of all entries that are connected and share the same value


I have a 2D numpy Array filled with integer-values from 0 to N, how can i get the indices of all entries that are directly connected and share the same value.

Addition: Most of the entries are zero and can be ignored!

Example Input array:

[ 0 0 0 0 0 ]
[ 1 1 0 1 1 ]
[ 0 1 0 1 1 ]
[ 1 0 0 0 0 ]
[ 2 2 2 2 2 ]

Wished output indices:

1: [ [1 0] [1 1] [2 1] [3 0] ] # first 1 cluster
   [ [1 3] [1 4] [2 3] [2 4] ] # second 1 cluster

2: [ [4 0] [4 1] [4 2] [4 3] [4 4] ] # only 2 cluster

the formating of the output arrays is not important, i just need separated value clusters where it is possible to address the single indices

What i was first thinking of is:

N = numberClusters
x = myArray

for c in range(N):
   for i in np.where(x==c):
         # fill output array with i

but this misses the separation of clusters that have the same value


Solution

  • You can use skimage.measure.label (install it with pip install scikit-image, if needed) for this:

    import numpy as np
    from skimage import measure
    
    # Setup some data
    np.random.seed(42)
    img = np.random.choice([0, 1, 2], (5, 5), [0.7, 0.2, 0.1])
    # [[2 0 2 2 0]
    #  [0 2 1 2 2]
    #  [2 2 0 2 1]
    #  [0 1 1 1 1]
    #  [0 0 1 1 0]]
    
    # Label each region, considering only directly adjacent pixels connected
    img_labeled = measure.label(img, connectivity=1)
    # [[1 0 2 2 0]
    #  [0 3 4 2 2]
    #  [3 3 0 2 5]
    #  [0 5 5 5 5]
    #  [0 0 5 5 0]]
    
    # Get the indices for each region, excluding zeros
    idx = [np.where(img_labeled == label)
           for label in np.unique(img_labeled)
           if label]
    # [(array([0]), array([0])),
    #  (array([0, 0, 1, 1, 2]), array([2, 3, 3, 4, 3])),
    #  (array([1, 2, 2]), array([1, 0, 1])),
    #  (array([1]), array([2])),
    #  (array([2, 3, 3, 3, 3, 4, 4]), array([4, 1, 2, 3, 4, 2, 3]))]
    
    # Get the bounding boxes of each region (ignoring zeros)
    bboxes = [area.bbox for area in measure.regionprops(img_labeled)]
    # [(0, 0, 1, 1),
    #  (0, 2, 3, 5),
    #  (1, 0, 3, 2),
    #  (1, 2, 2, 3),
    #  (2, 1, 5, 5)]
    

    The bounding boxes can be found using the very helpful function skimage.measure.regionprops, which contains a plethora of information on the regions. For the bounding box it returns a tuple of (min_row, min_col, max_row, max_col), where pixels belonging to the bounding box are in the half-open interval [min_row; max_row) and [min_col; max_col).