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.
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.]]