Home > Net >  Extend 2D numpy mask by n cells in all directions, efficiently
Extend 2D numpy mask by n cells in all directions, efficiently

Time:09-23

I have a MxN numpy array of boolean data representing a cloud mask for GIS data. I want to expand the mask by shifting it in all directions by n cells. It is a simple way of removing cloud shadows (which would be next to cloud pixels) in my mask. These numpy arrays are typically between 1000x1000 and 10000x10000 pixels.

My question is similar to this one but in 2 dimensions.

My very simple but inefficient solution looks like this:

def expand_for(arr, shiftx=1, shifty=1):
    arr_b = arr.copy().astype(bool)
    for i in range(arr.shape[0]):
        for j in range(arr.shape[1]):
            if(arr[i,j]):
                i_min, i_max = max(i-shifty, 0), min(i shifty 1, arr.shape[0])
                j_min, j_max = max(j-shiftx, 0), min(j shiftx 1, arr.shape[1])
                arr_b[i_min:i_max, j_min:j_max] = True
    return arr_b

Another solution is:

def shift_array(arr, x, y):
    d, u, r, l = max(y, 0), max(-y, 0), max(x, 0), max(-x, 0)
    ret = np.pad(arr, ((d, u), (r, l)), mode='constant')
    return ret[u or None: -d or None, l or None: -r or None]

def expand_array(arr, shiftx=1, shifty=1):
    return np.dstack([shift_array(arr, x, y)
                      for x in range(-shiftx, shiftx   1) for y in range(-shifty, shifty   1)]).any(axis=2)

But the complexity increases with the size of shiftx and shifty.

Here is a very simple example of what I'm trying to do:

a = np.array([[0,0,0,0,0],
             [0,0,0,0,0],
             [0,0,1,0,0],
             [0,0,0,0,0],
             [0,0,0,0,0]])
expand_for(a, 1, 1)

>>> array([[False, False, False, False, False],
       [False,  True,  True,  True, False],
       [False,  True,  True,  True, False],
       [False,  True,  True,  True, False],
       [False, False, False, False, False]])

I have been using this code to generate more complex test data:

N = 100    # or 1000, 10000...
a = np.random.random((N,N))
a = (a < 0.30)*1

CodePudding user response:

This can be done using the cv2 library by applying a dilation to the array. For example:

import numpy as np

N = 10

# the initial array
a = np.zeros((N, N), dtype='uint8')
a[[2, 4, 9], [2, 4, 7]] = 1
print(a)

This gives:

[[0 0 0 0 0 0 0 0 0 0]
 [0 0 0 0 0 0 0 0 0 0]
 [0 0 1 0 0 0 0 0 0 0]
 [0 0 0 0 0 0 0 0 0 0]
 [0 0 0 0 1 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 0 0 0]
 [0 0 0 0 0 0 0 1 0 0]]

Expand every occurrence of 1 to a 3x3 subarray of 1's:

import cv2

kernel = np.ones((3, 3), dtype='uint8')
out = cv2.dilate(a, kernel)
print(out)

The result:

[[0 0 0 0 0 0 0 0 0 0]
 [0 1 1 1 0 0 0 0 0 0]
 [0 1 1 1 0 0 0 0 0 0]
 [0 1 1 1 1 1 0 0 0 0]
 [0 0 0 1 1 1 0 0 0 0]
 [0 0 0 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 0]
 [0 0 0 0 0 0 1 1 1 0]
 [0 0 0 0 0 0 1 1 1 0]]

If you want the result to be an array of Boolean values, then you can change its data type:

out = out.astype('bool')
  • Related