Your size of the array is a typical screen size, so I guess that cells are pixel values in the range [0, 1). Now, pixel values are never multiplied by each other. If they were, operations would depend on the range (e.g., [0, 1) or [0, 255]), but they never do. So I would assume that when you say “reduce by 10% of a cell” you mean “subtract 10% of a cell”. But even so, the operation remains dependent on the order it is applied to the cells, because the usual way of calculating the total variation of a cell first and then applying it (like in a convolution) would cause some cell values to become negative (e.g., 0.251 - 8 * 0.1 * 0.999) , which does not make sense if they are pixels.
Let me assume for now that you really want to multiply cells by each other and by a factor, and that you want to do that by first having each cell affected by its neighbor number 0 (your numbering), then by its neighbor number 1, and so on for neighbors number 2, 3, 5, 7 and 8. As a rule, it's easier to define this kind of operations from the “point of view” of the target cells than from that of the source cells. Since numpy operates quickly on full arrays (or views thereof), the way to do this is to shift all neighbors in the position of the cell that is to be modified. Numpy has no shift()
, but it has a roll()
which for our purpose is just as good, because we don't care about the boundary cells, that, as per your comment, can be restored to the original value as a last step. Here is the code:
import numpy as np
arr = np.random.rand(720, 1440)
threshold = 0.25
factor = 0.1
# 0 1 2
# neighbors: 3 5
# 6 7 8
# ∆y ∆x axes
arr0 = np.where(arr > threshold, arr * np.roll(arr, (1, 1), (0, 1)) * factor, arr)
arr1 = np.where(arr0 > threshold, arr0 * np.roll(arr0, 1, 0 ) * factor, arr0)
arr2 = np.where(arr1 > threshold, arr1 * np.roll(arr1, (1, -1), (0, 1)) * factor, arr1)
arr3 = np.where(arr2 > threshold, arr2 * np.roll(arr2, 1, 1 ) * factor, arr2)
arr5 = np.where(arr3 > threshold, arr3 * np.roll(arr3, -1, 1 ) * factor, arr3)
arr6 = np.where(arr5 > threshold, arr5 * np.roll(arr5, (-1, 1), (0, 1)) * factor, arr5)
arr7 = np.where(arr6 > threshold, arr6 * np.roll(arr6, -1, 0 ) * factor, arr6)
res = np.where(arr7 > threshold, arr7 * np.roll(arr7, (-1, -1), (0, 1)) * factor, arr7)
# fix the boundary:
res[:, 0] = arr[:, 0]
res[:, -1] = arr[:, -1]
res[ 0, :] = arr[ 0, :]
res[-1, :] = arr[-1, :]
Please note that even so, the main steps are different from what you do in your solution. But they necessarily are, because rewriting your solution in numpy would cause arrays to be read and written to in the same operation, and this is not something that numpy can do in a predictable way.
If you should change your mind, and decide to subtract instead of multiplying, you only need to change the column of *
s before np.roll
to a column of -
s. But this would only be the first step in the direction of a proper convolution (a common and important operation on 2D images), for which you would need to completely reformulate your question, though.
Two notes: in your example code you indexed the array like arr[x][y]
, but in numpy arrays, by default, the leftmost index is the most slowly varying one, i.e., in 2D, the vertical one, so that the correct indexing is arr[y][x]
. This is confirmed by the order of the sizes of your array. Secondly, in images, matrices, and in numpy, the vertical dimension is usually represented as increasing downwards. This causes your numbering of the neighbors to differ from mine. Just multiply the vertical shifts by -1 if necessary.
EDIT
Here is an alternative implementation that yields exactly the same results. It is slightly faster, but modifies the array in place:
arr[1:-1, 1:-1] = np.where(arr[1:-1, 1:-1] > threshold, arr[1:-1, 1:-1] * arr[ :-2, :-2] * factor, arr[1:-1, 1:-1])
arr[1:-1, 1:-1] = np.where(arr[1:-1, 1:-1] > threshold, arr[1:-1, 1:-1] * arr[ :-2, 1:-1] * factor, arr[1:-1, 1:-1])
arr[1:-1, 1:-1] = np.where(arr[1:-1, 1:-1] > threshold, arr[1:-1, 1:-1] * arr[ :-2, 2: ] * factor, arr[1:-1, 1:-1])
arr[1:-1, 1:-1] = np.where(arr[1:-1, 1:-1] > threshold, arr[1:-1, 1:-1] * arr[1:-1, :-2] * factor, arr[1:-1, 1:-1])
arr[1:-1, 1:-1] = np.where(arr[1:-1, 1:-1] > threshold, arr[1:-1, 1:-1] * arr[1:-1, 2: ] * factor, arr[1:-1, 1:-1])
arr[1:-1, 1:-1] = np.where(arr[1:-1, 1:-1] > threshold, arr[1:-1, 1:-1] * arr[2: , :-2] * factor, arr[1:-1, 1:-1])
arr[1:-1, 1:-1] = np.where(arr[1:-1, 1:-1] > threshold, arr[1:-1, 1:-1] * arr[2: , 1:-1] * factor, arr[1:-1, 1:-1])
arr[1:-1, 1:-1] = np.where(arr[1:-1, 1:-1] > threshold, arr[1:-1, 1:-1] * arr[2: , 2: ] * factor, arr[1:-1, 1:-1])