Search code examples
pythonnumpymatrixscipyscikit-image

Filter 2D array and return co-ordinates from intermediate


I have a 2D array of zeros with some positive integers at (1,6) and (2,7):

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

And I want to filter the array by a custom kernel:

[[1 0 1]
 [0 1 0]
 [0 1 0]]

I want to filter the array with this kernel and when 2 or 3 of the ones in this kernel are multiplied by a positive integer, I want it to return the co-ordinates of the ones that were multiplied by 0.

I know from image analysis that it's easy to convolve a 2D array by a kernel but it doesn't yield the intermediate results. On the above 2D array, it would return (1,8) and (3,7).

Is there some package functions that I can use to make this process simple and easy, or will I have to implement it myself? As always, all help is appreciated


Solution

  • This is a numpy implementation of it to start with. You can increase performance probably by modifying it.

    Here, num_ones is the lower and upper number of ones in the kernel you would like to filter, referring to when 2 or 3 of the ones in this kernel are multiplied by a positive integer

    a = np.array([[0.,0.,0.,0.,0.,0.,0.,0.,0.,0.,0.,0.,0.,0.,0.,0.,0.],
     [0.,0.,0.,0.,0.,0.,2.,0.,0.,0.,0.,0.,0.,0.,0.,0.,0.],
     [0.,0.,0.,0.,0.,0.,0.,2.,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.]])
    
    kernel = np.array([[1.,0.,1.],\
     [0.,1.,0.],\
     [0.,1.,0.]])
    
    sub_shape = kernel.shape
    #throshold of number of kernel ones to have non-zero value
    num_ones = [2,3]
    
    #divide the matrix into sub_matrices of kernel size
    view_shape = tuple(np.subtract(a.shape, sub_shape) + 1) + sub_shape
    strides = a.strides + a.strides
    sub_matrices = np.lib.stride_tricks.as_strided(a,view_shape,strides)
    #convert non_zero elements to 1 (dummy representation)
    sub_matrices[sub_matrices>0.] = 1.
    
    #Do convolution
    m = np.einsum('ij,klij->kl',kernel,sub_matrices)
    
    #find sub_matrices that satisfy non-zero elements' condition
    filt = np.argwhere(np.logical_and(m>=num_ones[0], m<=num_ones[1]))
    #for each sub_matix find the zero elements located in non-zero elements of kernel
    output = []
    for [i,j] in filt:
      output.append(np.argwhere((sub_matrices[i,j,:,:]==0)*kernel) + [i, j])
    

    output is an array of indices arrays where each array is indices where your condition is met per kernel application in each location [i,j] of your image. If you wish to aggregate them all, you can stack all arrays and take a unique list of it. I am not sure how you would like the output be in case of multiple occurrences.

    output:

    output =
    [[1 8]
     [3 7]] 
    

    UPDATE: regarding einsum:

    I would recommend this post about einsum to learn: Understanding NumPy's einsum

    sub_matrices is a 4-dimensional array. sub_matrices[k,l,:,:] is sub matrix of a starting at position [k,l] and shape of kernel. (later we changed all non-zero values of it to 1 for our purpose)

    m = np.einsum('ij,klij->kl',kernel,sub_matrices) multiplies two dimensions i and j of kernel into last two dimensions i and j of sub_matrices array (in other words, it element-wise multiplies kernel to sub matrices sub_matrices[k,l,:,:]) and sums all elements into m[k,l]. This is known as 2D convolution of kernel into a.