2

I Have a (much bigger) Matrix that looks like this one:

  0     0     4     0     0     1
  1     0     0     0     1     0
  2     1     0     1     0     0
  0     0     0     0     0     0
 -1     0    -2    -3     0     0
  0     0    -1    -2     0    -2
  0    -5     0    -1     0    -1

That must turn into something like this one:

  0     1     1     1     0     1
  0     0     1     0     1     1
  1     1     1     1     1     1
  1     1     1     1     1     1
  1     1     1     1     1     1
  0     1     0     0     1     1
  1     1     0     0     1     0

So, each cell in the first one mat stores how many neighbour (in a column-wise manner) pixels must be erased (the resulting one is a boolean mask for this purpose). If the value in the first matrix is positive, the pixels must be erased upwards, in the negative case, downwards.

I've already implemented this with a double for, which turns out to be prohibitively slow...

So, there's a way to do this with a numpy global method?

(I'm using python 3.8, but I can actually use any version)

EDIT:

The double for loop that i've used: Where "pixels_to_erase" is the first mat and "mask_array" is the second

for i in range(nRows):
        for j in range(nCols):
            signal = np.sign(pixels_to_erase[i,j])

            if signal > 0:
                upper_bound = i - pixels_to_erase
                if upper_bound < 0:
                    upper_bound = 0
                lower_bound = i
            else:
                upper_bound = i
                lower_bound = i + pixels_to_erase
                if lower_bound > max_row:
                    lower_bound = max_row

            mask_array[upper_bound:lower_bound,j] = 0

(actually it's an adaptated part of a much bigger code, that I can post in a gist, if needed)

  • can you share the for loop you have done so far? – Tarifazo Feb 12 '20 at 12:19
  • @Mstaino i've made an edit with the snippet adapted from the code I'm implementing. – Kauê de Moraes Vestena Feb 12 '20 at 12:30
  • What does it mean for a pixel to be erased? I don't understand how the 2nd array is constructed from the first one. – Ardweaden Feb 12 '20 at 12:54
  • @Ardweaden I'm using it in a Image manipulation context. So I can use the second one to specify which pixels in a third mat (the image) will be filled with black. – Kauê de Moraes Vestena Feb 12 '20 at 13:02
  • @Ardweaden look at the pixel with zero-indexed row,column (4,0) and its downward neighbour (5,0) in both matrices. The pixel (4,0) itself will not be "erased", but the (5,0) will... – Kauê de Moraes Vestena Feb 12 '20 at 13:27
  • What are the approximate dimensions of the *"much bigger matrix"* please? And is there some maximum number of neighbours that could need zeroing? – Mark Setchell Feb 12 '20 at 13:45
  • @MarkSetchell approximately 10000 x 10000... There are no maximum, it can specify an interval from the beginning to the end of the image, so at the pixel (10000,0) it can have the 10000 value stored (then the upward pixel must have 9999, then 9998...). It is a mask of occlusion caused by building lean in aerial photos used for photogrammetry... – Kauê de Moraes Vestena Feb 12 '20 at 14:23
  • Have you tried `numba`? Example here.. https://stackoverflow.com/a/60080177/2836621 – Mark Setchell Feb 12 '20 at 14:30

1 Answers1

1

If the numbers in each positions are relatively small, a loop on the distinct values would probably give you much better performance:

import numpy as np

m = np.array([
    [0,    0,     4,     0,     0,     1],
    [1,    0,     0,     0,     1,     0],
    [2,    1,     0,     1,     0,     0],
    [0,    0,     0,     0,     0,     0],
    [-1,   0,    -2,    -3,     0,     0],
    [0,    0,    -1,    -2,     0,    -2],
    [0,   -5,     0 ,   -1,     0,    -1] ])

result = np.ones(np.shape(m), dtype=int)
for d in np.unique(m):
    if d == 0: continue
    mask  = m if d < 0 else m[::-1,:]
    mask  = np.cumsum(np.cumsum(mask==d,axis=0),axis=0)-1
    mask  = 1 - (mask>0)*(mask<=abs(d))
    if d>0 : mask = mask[::-1,:]
    result *= mask 
    #print(d)
    #print(mask)

print(result)

This processes each erasure value individually and generates a mask for the upward or downward value. The result is the intersection of all the masks.

However ... looking at the pattern of numbers I suspect that the erasure numbers will always be in increasing/decreasing sequence around a given point. This would lend itself to a much simpler (and faster) solution where you only need to process the previous and next lines to determine if points need to be cleared:

result = np.ones(np.shape(m), dtype=int)
result[1:,:]  *= m[:-1,:]>=0
result[:-1,:] *= m[1:,:] <=0
print(result)
Alain T.
  • 40,517
  • 4
  • 31
  • 51
  • Yeah, for each column there is a row that above it, all the erasure values will be positive, and below all are negative, this particular cell will always be zero-valued. But the row number varies for each column, actually is a linestring that divides the entire matrix in two. But we can have a linestring that will be farther than the northern bound for example, and then all values should be negative. – Kauê de Moraes Vestena Feb 13 '20 at 11:53
  • I will test your solution, but the numbers can be relatively large numbers... – Kauê de Moraes Vestena Feb 13 '20 at 11:59