-1

I'm working on a particle system simulation in which many thousands of particles randomly walk until they attach to a growing cluster. To visualize this cluster, I'm storing the cluster particle positions as 1s in a numpy array initially full of 0s and then using the matshow function. The 0s are colored white and the 1s are colored black.

I've noticed in these simulations that a situation like the following might occur (the actual arrays are much larger, 1000x1000 or larger):

array([[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., 0., 0., 0.],
       [0., 0., 1., 1., 1., 1., 1., 1., 0., 0.],
       [0., 0., 1., 1., 0., 0., 1., 1., 0., 0.],
       [0., 0., 1., 1., 0., 0., 1., 1., 0., 0.],
       [0., 0., 1., 1., 1., 1., 1., 1., 0., 0.],
       [0., 0., 0., 1., 1., 1., 1., 0., 0., 0.],
       [0., 0., 0., 0., 0., 0., 0., 0., 0., 0.],
       [0., 0., 0., 0., 0., 0., 0., 0., 0., 0.]])

where particles (1s) attach in a such a way so that a "pocket" (of 0s) is formed. In the images of these clusters they appear as tiny holes. Here's what this looks like for a smaller cluster:

enter image description here

If you squint, you can see a handful of the "holes" I'm talking about. These correspond to 0s completely surrounded by 1s in the associated numpy array.

My question: How can I write a function that will:

  1. Count the number of 0s that are completely surrounded by 1s
  2. Change the 0s that are completely surrounded by 1s into -1s (I have a reason for wanting them to be -1s instead)

My first pass at writing such a function works for small/simple arrays like the example above, but fails to work for the actual cluster arrays in the simulation:

def holes(matrix):
    num_holes = 0
    for row in range(matrix.shape[0]):
        for col in range(matrix.shape[0]):
            if matrix[row, col] == 0: 
                can_escape = True
                
                path_down = matrix[row, col:]
                path_up = np.flip(matrix[:row+1, col])
                path_right = matrix[row:, col]
                path_left = np.flip(matrix[row, :col+1])
                
                if 1 in path_down[1:] and 1 in path_up[1:] and 1 in path_left[1:] and 1 in path_right[1:]:
                    can_escape = False
                
                if can_escape is False: 
                    matrix[row, col] = -1
                    num_holes += 1
                    
    return num_holes

I recognize this function will fail to do what I want it to do for situations like this, where a "cleft" occurs:

    array([[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., 0., 1., 1., 1., 1., 0., 0.],
           [0., 0., 1., 0., 0., 0., 1., 1., 0., 0.],
           [0., 0., 1., 0., 0., 0., 1., 1., 0., 0.],
           [0., 0., 1., 0., 1., 1., 1., 1., 0., 0.],
           [0., 0., 0., 1., 1., 1., 1., 0., 0., 0.],
           [0., 0., 0., 0., 0., 0., 0., 0., 0., 0.],
           [0., 0., 0., 0., 0., 0., 0., 0., 0., 0.]])

Calling my attempt on this would give:

    array([[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., 0., 1., 1., 1., 1., 0., 0.],
           [0., 0., 1., 0., -1., -1., 1., 1., 0., 0.],
           [0., 0., 1., 0., -1., -1., 1., 1., 0., 0.],
           [0., 0., 1., 0., 1., 1., 1., 1., 0., 0.],
           [0., 0., 0., 1., 1., 1., 1., 0., 0., 0.],
           [0., 0., 0., 0., 0., 0., 0., 0., 0., 0.],
           [0., 0., 0., 0., 0., 0., 0., 0., 0., 0.]])

which is incorrect, since this configuration does not actually have a pocket of 0s.

How can I do this correctly, and perhaps efficiently? Like I said, the simulation is running on arrays at least of size 1000x1000.

Alex
  • 83
  • 9
  • Just check if row and col are valid which means inside the box. So if I used the top left value of 0. I need to check 8 values surrounding it if the values are outside the box just make it 1. – Arundeep Chohan Jul 18 '20 at 21:09

1 Answers1

1

You can use the Flood Fill algorithm.
This algorithm is working like the "Fill" feature in paint and finding all the pixels with the same color in an image. Use it to replace all the outside zeros with -1, than replace all remaining zeros with 1, than turn all the -1 to zeros.

Building on this answer with an implementation to flood fill, your algorithm will be something like:

def floodfill(matrix, x, y, original_value, new_value):
    if matrix[x][y] == original_value:  
        matrix[x][y] = new_value
        #recursively invoke flood fill on all surrounding cells:
        if x > 0:
            floodfill(matrix,x-1,y)
        if x < len(matrix[y]) - 1:
            floodfill(matrix,x+1,y)
        if y > 0:
            floodfill(matrix,x,y-1)
        if y < len(matrix) - 1:
            floodfill(matrix,x,y+1)

def main():
    # Assuming 0, 0 has 0 in it - turning all the outside zeros to -1
    floodfill(matrix, 0, 0, 0, -1)

    for i in range(len(matrix)):
        for j in range(len(matrix[i])):
            if matrix[i][j] == -1:
                matrix[i][j] = 0
            if matrix[i][j] == 0:
                matrix[i][j] = 1
Yonlif
  • 1,770
  • 1
  • 13
  • 31
  • Very interesting. Working off this currently, I will post the solution should it work, thank you! I would have never come to this on my own. – Alex Jul 18 '20 at 21:22