简体   繁体   中英

Counting and changing the 0s in a matrix that are surrounded by 1s - Python

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:

在此处输入图像描述

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.

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM