Sina ALizadeh
Sina ALizadeh

Reputation: 23

Filter a numpy ndarray using another numpy ndarray

I have two numpy ndarrays of the same shape (15081, 56724, 3, 3). What I want to do is as follows:

Say we have a cross section of the first array, array1[1, 1, :, :], looks like this:

[[120, 110, 220],
 [ 85,  99,  72],
 [197,  80,  75]]

I want to convert it to a boolean in a way that the max of each row is True and the rest is False. In the whole array, this corresponds to axis=3. So the array looks like this after conversion:

[[False, False,  True],
 [False,  True, False],
 [ True, False, False]]

Now I want to filter the other array, array2, using this boolean array to have something that looks like below. I only want to keeping those values of array2 that correspond to True in array1 and set the rest to zero.

[[  0,   0,  65],
 [  0, 179,   0],
 [125,   0,   0]]

I can do this using a loop but it takes an age (even more). I expect something like numpy.where(array1.is_max(axis=3), True, False), but there is no function like is_max in python, besides this way the axis 3 is collapsed and I cannot filter array2 using array1.

Upvotes: 2

Views: 151

Answers (2)

Mad Physicist
Mad Physicist

Reputation: 114548

In numpy, is_max is approximately argmax:

indices = array1.argmax(axis=-1)

Instead of a mask, this will give you a linear index, so you can do

array2b = np.zeros_like(array2)
index = np.indices(array2.shape[:-1], sparse=True) + (indices,)
array2b[index] = array1[index]

You can do something similar with np.take_along_axis and np.put_along_axis:

indices = np.expand_dims(array1.argmax(-1), array1.ndim - 1)
array2b = np.zeros_like(array2)
np.put_along_axis(array2b, indices, np.take_along_axis(array2, indices, -1), -1)

If you want a mask-based approach, you can create the mask like this:

mask = array1 != array1.max(-1, keepdims=True)

Now you can set all elements to zero directly:

array2[mask] = 0

Alternatively you can do something like

mask = array1 == array1.max(-1, keepdims=True)
array2 *= mask

Update

From your description in the comments, you are looking for a different operation entirely. You can start by thresholding array1 (which I assume represents the difference between the blurred and original image):

mask = array1 >= 100  # find a threshold that works
array2 *= mask

OR

mask = array1 < 100
array2[mask] = 0

You may also be looking for local minima in an image. You can get those by finding pixels that are bigger than their surroundings. To do that, run a non-linear filter on the image, like scipy.ndimage.maximum_filter:

 mask = array1 == scipy.ndimage.maximum_filter(array1, 5)
 array2 *= mask

Upvotes: 3

Bedir Yilmaz
Bedir Yilmaz

Reputation: 4083

Can be done with

array2[array1 != np.tile(np.max(array1,axis=1), (array1.shape[0],1)).T] = 0

Here is how.

For your particular example, i would suggest to use np.tile, where you can create a repeating pattern out of the elements in your 1-D mask.

Suppose we have your matrix a with values

array1 = [[120, 110, 220]
          [85, 99, 72]
          [197, 80, 75]]

Then the maximum values in every row of your matrix would be

maxes = np.max(array1,axis=1)

which will have the values

array([220,  99, 197])

now, what we need is to use each of these values in each row per each row element. Simply put, we need to compare 220 with each element in the first row. This means that actually we need to repeat the value 220, to be able to perform the operation in a single vector, which can be done with:

np.tile(maxes, (array1.shape[0],1)).T

which gives us

array([[220, 220, 220],
       [ 99,  99,  99],
       [197, 197, 197]])

filtering is rather straightforward:

a != np.tile(maxes, (3,1)).T 

is sufficient to create the mask.

Long story short, the operation can be done in a single liner as following:

array2[array1 != np.tile(maxes, (a.shape[0],1)).T] = 0

which enables a to have following values

array2 = array([[0, 0, 65]
                [0, 179, 0]
                [125, 0, 0]])

Upvotes: 0

Related Questions