pythonarraysnumpyindexingnumpy-ndarray

Indexing overlapping areas of a numpy array


I have a 2D array of zeros (called labels) and a list of its coordinates (called centers). For each of the centers I would like to put a progressive number in a 5x5 cluster around it inside the label array.

Should two centers be so close that the corresponding clusters are overlapping, then I would like the labels arrays to give precedence to the lower label value. In other words, if cluster 2 is overlapping cluster 1, then I want cluster 1 to be 5x5 and cluster 2 to be smaller.

I have managed to code this procedure as follow:

import numpy as np

labels = np.zeros((10,20))
n_rows, n_cols = labels.shape

centers = [(4,4), (7,7), (5,10), (5,18)]


for i, center in enumerate(centers, start=1):
    # find the coordinates for the 5x5 cluster centered in center.
    cluster = np.ix_(np.arange(max(center[0]-2,0), min(center[0]+3,n_rows),1),
                     np.arange(max(center[1]-2, 0), min(center[1]+3,n_cols),1))
    
    # create a temporary label array with all zeros
    temp_label = np.zeros_like(labels)
    
    # set the label value in the temporary array in the position corresponding to the cluster 
    temp_label[cluster] = i
    
    # apply some boolean algebra
    # (labels == 0) is a bool array corresponding to all positions that are not yet belonging to a label
    # (labels == 0) * temp_label is like the temp_label, but only where the labels array is still free
    # adding the labels back is ensuring that all previous labels are also counted. 
    labels = (labels == 0) * temp_label + labels


print(labels)

Output:

[[0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 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. 1. 1. 1. 1. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
 [0. 0. 1. 1. 1. 1. 1. 0. 3. 3. 3. 3. 3. 0. 0. 0. 4. 4. 4. 4.]
 [0. 0. 1. 1. 1. 1. 1. 0. 3. 3. 3. 3. 3. 0. 0. 0. 4. 4. 4. 4.]
 [0. 0. 1. 1. 1. 1. 1. 2. 2. 2. 3. 3. 3. 0. 0. 0. 4. 4. 4. 4.]
 [0. 0. 1. 1. 1. 1. 1. 2. 2. 2. 3. 3. 3. 0. 0. 0. 4. 4. 4. 4.]
 [0. 0. 0. 0. 0. 2. 2. 2. 2. 2. 3. 3. 3. 0. 0. 0. 4. 4. 4. 4.]
 [0. 0. 0. 0. 0. 2. 2. 2. 2. 2. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
 [0. 0. 0. 0. 0. 2. 2. 2. 2. 2. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]]

This snippet is actually doing what I want. Cluster 1 and 4 are full, while cluster 2 and 3 that are touching each other are only partial. To achieve this, I have to pass through a temporary array each time.

What would be a better solution?

Unfortunately reversing the center lists is not an option, because in the real analysis case, the center list is generated one element at the time.


Solution

  • Setup:

    import numpy as np
    from numpy.lib.stride_tricks import sliding_window_view as swv
    
    R, C = 10, 20
    centers = [(4,4), (7,7), (5,10), (5,18)]
    H, W = 5, 5
    

    Solution:

    buffer = np.zeros(((R + H), (C + W)))
    labels = buffer[H//2:-H//2, W//2:-W//2]
    view = swv(buffer, (H, W), writeable=True)
    
    for lbl, (r, c) in enumerate(centers, start=1):
        patch = view[r, c]
        patch[patch == 0] = lbl
    
    >>> print(labels)
    [[0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 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. 1. 1. 1. 1. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
     [0. 0. 1. 1. 1. 1. 1. 0. 3. 3. 3. 3. 3. 0. 0. 0. 4. 4. 4. 4.]
     [0. 0. 1. 1. 1. 1. 1. 0. 3. 3. 3. 3. 3. 0. 0. 0. 4. 4. 4. 4.]
     [0. 0. 1. 1. 1. 1. 1. 2. 2. 2. 3. 3. 3. 0. 0. 0. 4. 4. 4. 4.]
     [0. 0. 1. 1. 1. 1. 1. 2. 2. 2. 3. 3. 3. 0. 0. 0. 4. 4. 4. 4.]
     [0. 0. 0. 0. 0. 2. 2. 2. 2. 2. 3. 3. 3. 0. 0. 0. 4. 4. 4. 4.]
     [0. 0. 0. 0. 0. 2. 2. 2. 2. 2. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
     [0. 0. 0. 0. 0. 2. 2. 2. 2. 2. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]]