python - español - numpy types
Accediendo a celdas vecinas para una matriz numpy (9)
Al analizar el problema en otros más pequeños, vemos que esa solución real @jakevdp hace el trabajo, pero se olvida de verificar el término mask<0.25
después de la convolución con la máscara, de modo que algunos valores puedan caer más tarde detrás de 0.25 (tal vez hay 8 pruebas para cada píxel), por lo que debe haber un bucle for, a menos que haya una función incorporada para la que no escuché ...
Aquí está mi propuesta:
# x or y first depends if u want rows or cols , .. different results
for x in range(arr.shape[1]-3):
for y in range(arr.shape[0]-3):
k = arr[y:y+3,x:x+3]
arr[y:y+3,x:x+3] = k/10**(k>0.25)
¿Cómo puedo acceder y modificar las 8 celdas circundantes para una matriz de numpy 2D de una manera eficiente?
Tengo una matriz numpy 2D como esta:
arr = np.random.rand(720, 1440)
Para cada celda de la cuadrícula, quiero reducir en un 10% la celda central, las 8 celdas circundantes (menos para celdas de esquina), pero solo si el valor de la celda circundante excede de 0.25. Sospecho que la única forma de hacerlo es usar un bucle for, pero me gustaría ver si hay soluciones mejores o más rápidas.
- EDITAR: Para la solución basada en bucle:
arr = np.random.rand(720, 1440)
for (x, y), value in np.ndenumerate(arr):
# Find 10% of current cell
reduce_by = value * 0.1
# Reduce the nearby 8 cells by ''reduce_by'' but only if the cell value exceeds 0.25
# [0] [1] [2]
# [3] [*] [5]
# [6] [7] [8]
# * refers to current cell
# cell [0]
arr[x-1][y+1] = arr[x-1][y+1] * reduce_by if arr[x-1][y+1] > 0.25 else arr[x-1][y+1]
# cell [1]
arr[x][y+1] = arr[x][y+1] * reduce_by if arr[x][y+1] > 0.25 else arr[x][y+1]
# cell [2]
arr[x+1][y+1] = arr[x+1][y+1] * reduce_by if arr[x+1][y+1] > 0.25 else arr[x+1][y+1]
# cell [3]
arr[x-1][y] = arr[x-1][y] * reduce_by if arr[x-1][y] > 0.25 else arr[x-1][y]
# cell [4] or current cell
# do nothing
# cell [5]
arr[x+1][y] = arr[x+1][y] * reduce_by if arr[x+1][y] > 0.25 else arr[x+1][y]
# cell [6]
arr[x-1][y-1] = arr[x-1][y-1] * reduce_by if arr[x-1][y-1] > 0.25 else arr[x-1][y-1]
# cell [7]
arr[x][y-1] = arr[x][y-1] * reduce_by if arr[x][y-1] > 0.25 else arr[x][y-1]
# cell [8]
arr[x+1][y-1] = arr[x+1][y-1] * reduce_by if arr[x+1][y-1] > 0.25 else arr[x+1][y-1]
EDIT: ah, veo que cuando dices "reducir" quieres decir multiplicar, no restar. Tampoco reconocí que desea reducciones a compuestos, lo que no hace esta solución. Entonces es incorrecto, pero lo dejaré en caso de que sea útil.
Puede hacer esto de una manera vectorizada utilizando scipy.signal.convolve2d
:
import numpy as np
from scipy.signal import convolve2d
arr = np.random.rand(720, 1440)
mask = np.zeros((arr.shape[0] + 2, arr.shape[1] + 2))
mask[1:-1, 1:-1] = arr
mask[mask < 0.25] = 0
conv = np.ones((3, 3))
conv[1, 1] = 0
arr -= 0.1 * convolve2d(mask, conv, mode=''valid'')
Esto viene de pensar en su problema al revés: cada cuadrado debe tener 0.1 veces todos los valores circundantes que se le restan. La matriz de conv
codifica esto y la deslizamos sobre la matriz de mask
utilizando scipy.signal.convolve2d
para acumular los valores que deben restarse.
Esta respuesta asume que realmente desea hacer exactamente lo que escribió en su pregunta. Bueno, casi exactamente, ya que su código falla porque los índices se salen de los límites. La forma más fácil de solucionarlo es agregar condiciones, como, por ejemplo,
if x > 0 and y < y_max:
arr[x-1][y+1] = ...
La razón por la que la operación principal no se puede vectorizar mediante numpy o scipy es que todas las celdas están "reducidas" por algunas celdas vecinas que ya han sido "reducidas". Numpy o scipy utilizarían los valores no afectados de los vecinos en cada operación. En mi otra respuesta, muestro cómo hacer esto con numpy si se le permite agrupar operaciones en 8 pasos, cada uno a lo largo de la dirección de un vecino en particular, pero cada uno de ellos usa el valor no afectado en ese paso para ese vecino. Como dije, aquí supongo que tiene que proceder secuencialmente.
Antes de continuar, permítame intercambiar y
en su código. Su matriz tiene un tamaño de pantalla típico, donde 720 es la altura y 1440 el ancho. Las imágenes generalmente se almacenan por filas, y el índice más a la derecha en un ndarray es, por defecto, el que varía más rápidamente, por lo que todo tiene sentido. Es cierto que es contraintuitivo, pero la indexación correcta es arr[y, x]
.
La optimización principal que se puede aplicar a su código (que reduce el tiempo de ejecución de ~ 9 s a ~ 3,9 s en mi Mac) es no asignar una celda a sí misma cuando no es necesaria, junto con la multiplicación in situ y con [y, x]
lugar de [y][x]
indexación. Me gusta esto:
y_size, x_size = arr.shape
y_max, x_max = y_size - 1, x_size - 1
for (y, x), value in np.ndenumerate(arr):
reduce_by = value * 0.1
if y > 0 and x < x_max:
if arr[y - 1, x + 1] > 0.25: arr[y - 1, x + 1] *= reduce_by
if x < x_max:
if arr[y , x + 1] > 0.25: arr[y , x + 1] *= reduce_by
if y < y_max and x < x_max:
if arr[y + 1, x + 1] > 0.25: arr[y + 1, x + 1] *= reduce_by
if y > 0:
if arr[y - 1, x ] > 0.25: arr[y - 1, x ] *= reduce_by
if y < y_max:
if arr[y + 1, x ] > 0.25: arr[y + 1, x ] *= reduce_by
if y > 0 and x > 0:
if arr[y - 1, x - 1] > 0.25: arr[y - 1, x - 1] *= reduce_by
if x > 0:
if arr[y , x - 1] > 0.25: arr[y , x - 1] *= reduce_by
if y < y_max and x > 0:
if arr[y + 1, x - 1] > 0.25: arr[y + 1, x - 1] *= reduce_by
La otra optimización (que reduce el tiempo de ejecución hasta ~ 3.0 s en mi Mac) es evitar las comprobaciones de límites utilizando una matriz con celdas de límite adicionales. No nos importa qué valor contenga el límite, porque nunca se utilizará. Aquí está el código:
y_size, x_size = arr.shape
arr1 = np.empty((y_size + 2, x_size + 2))
arr1[1:-1, 1:-1] = arr
for y in range(1, y_size + 1):
for x in range(1, x_size + 1):
reduce_by = arr1[y, x] * 0.1
if arr1[y - 1, x + 1] > 0.25: arr1[y - 1, x + 1] *= reduce_by
if arr1[y , x + 1] > 0.25: arr1[y , x + 1] *= reduce_by
if arr1[y + 1, x + 1] > 0.25: arr1[y + 1, x + 1] *= reduce_by
if arr1[y - 1, x ] > 0.25: arr1[y - 1, x ] *= reduce_by
if arr1[y + 1, x ] > 0.25: arr1[y + 1, x ] *= reduce_by
if arr1[y - 1, x - 1] > 0.25: arr1[y - 1, x - 1] *= reduce_by
if arr1[y , x - 1] > 0.25: arr1[y , x - 1] *= reduce_by
if arr1[y + 1, x - 1] > 0.25: arr1[y + 1, x - 1] *= reduce_by
arr = arr1[1:-1, 1:-1]
Para los registros, si las operaciones se pudieran vectorizar usando números o datos, la aceleración con respecto a esta solución sería al menos un factor de 35 (medido en mi Mac).
NB: si numpy realizó operaciones en segmentos de matriz de forma secuencial, lo siguiente produciría factoriales (es decir, productos de enteros positivos hasta un número), pero no:
>>> import numpy as np
>>> arr = np.arange(1, 11)
>>> arr
array([ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10])
>>> arr[1:] *= arr[:-1]
>>> arr
array([ 1, 2, 6, 12, 20, 30, 42, 56, 72, 90])
Intenta usar pandas
import pandas as pd
# create random array as pandas DataFrame
df = pd.DataFrame(pd.np.random.rand(720, 1440))
# define the centers location for each 9x9
Center_Locations = (df.index % 3 == 1,
df.columns.values % 3 == 1)
# new values for the centers, to be use later
df_center = df.iloc[Center_Locations] * 1.25
# change the df, include center
df = df * 0.9
# replacing only the centers values
df.iloc[Center_Locations] = df_center
No es posible evitar el bucle porque la reducción se realiza secuencialmente, no en paralelo.
Aquí está mi implementación. Para cada (i,j)
cree una vista de bloque 3x3 de a
centrado en a[i,j]
(el valor del cual lo establezco temporalmente en 0 para que esté por debajo del umbral, ya que no queremos reducirlo) . Para el (i,j)
en el límite, el bloque es 2x2 en las esquinas y 2x3 o 3x2 en otras partes. Luego el bloque queda enmascarado por el umbral y los elementos no enmascarados se multiplican por a_ij*0.1
.
def reduce(a, threshold=0.25, r=0.1):
for (i, j), a_ij in np.ndenumerate(a):
a[i,j] = 0
block = a[0 if i == 0 else (i-1):i+2, 0 if j == 0 else (j-1):j+2]
np.putmask(block, block>threshold, block*a_ij*r)
a[i,j] = a_ij
return a
Tenga en cuenta que la reducción también se realiza desde las celdas de límite en las celdas que las rodean, es decir, el bucle comienza desde la primera esquina de la matriz, a[0, 0]
que tiene 3 vecinos: a[0,1]
, a[1,0]
y a[1,1]
, que se reducen en a[0,0]*0.1
si son> 0,25. Luego va a la celda a[0,1]
que tiene 5 vecinos, etc. Si desea operar estrictamente en celdas que tienen 8 vecinas, es decir, una ventana de tamaño 3x3, el bucle debe ir de a[1,1]
a a[-2, -2]
, y la función debe modificarse de la siguiente manera:
def reduce_(a, threshold=0.25, r=0.1):
'''''' without borders -- as in OP''s solution''''''
for (i, j), a_ij in np.ndenumerate(a[1:-1,1:-1]):
block = a[i:i+3, j:j+3]
mask = ~np.diag([False, True, False])*(block > threshold)
np.putmask(block, mask, block*a_ij*r)
return a
Ejemplo:
>>> a = np.random.rand(4, 4)
array([[0.55197876, 0.95840616, 0.88332771, 0.97894739],
[0.06717366, 0.39165116, 0.10248439, 0.42335457],
[0.73611318, 0.09655115, 0.79041814, 0.40971255],
[0.34336608, 0.39239233, 0.14236677, 0.92172401]])
>>> reduce(a.copy())
array([[0.00292008, 0.05290198, 0.00467298, 0.00045746],
[0.06717366, 0.02161831, 0.10248439, 0.00019783],
[0.00494474, 0.09655115, 0.00170875, 0.00419891],
[0.00016979, 0.00019403, 0.14236677, 0.0001575 ]])
>>> reduce_(a.copy())
array([[0.02161831, 0.03753609, 0.03459563, 0.01003268],
[0.06717366, 0.00401381, 0.10248439, 0.00433872],
[0.02882996, 0.09655115, 0.03095682, 0.00419891],
[0.00331524, 0.00378859, 0.14236677, 0.00285336]])
Otro ejemplo para la matriz 3x2:
>>> a = np.random.rand(3, 2)
array([[0.17246979, 0.42743388],
[0.1911065 , 0.41250723],
[0.73389051, 0.22333497]])
>>> reduce(a.copy())
array([[0.17246979, 0.00737194],
[0.1911065 , 0.0071145 ],
[0.01402513, 0.22333497]])
>>> reduce_(a.copy()) # same as a because there are no cells with 8 neighbors
array([[0.17246979, 0.42743388],
[0.1911065 , 0.41250723],
[0.73389051, 0.22333497]])
No se necesitan bucles, evita los bucles de pitón habituales, son muy lentos. Para una mayor eficiencia, confíe en la compilación de Numpy en operaciones matriciales, funciones "universales", filtros, máscaras y condiciones siempre que pueda. https://realpython.com/numpy-array-programmin Para cálculos complicados, la vectorización no es tan mala. Consulte algunos gráficos y puntos de referencia. Forma más eficiente de asignar funciones a través de una matriz numpy (simplemente no la use para operaciones matriciales más simples, como la cuadratura de celdas , las funciones incorporadas superarán el rendimiento
Es fácil ver que cada celda interna se multiplicaría en .9 hasta 8 veces debido a 8 vecinos (que se reduce en .1), y además se debe a que es una celda central, pero no puede reducirse por debajo de .25 / .9 = 5/18. Para el borde y la esquina, el número de celdas disminuye las fells a 6 y 3 veces.
Por lo tanto
x1 = 700 # for debugging use lesser arrays
x2 = 1400
neighbors = 8 # each internal cell has 8 neighbors
for i in range(neighbors):
view1 = arr[1:-1, 1:-1] # internal cells only
arr [1:x1, 1:-1] = np.multiply(view1,.9, where = view1 > .25)
arr [1:-1, 1:-1] *= .9
Los bordes y las esquinas se tratan de la misma manera con los vecinos = 5 y 3 respectivamente y diferentes vistas. Supongo que los tres casos se pueden unir en una fórmula complicada, pero la aceleración sería moderada, ya que los bordes y las esquinas toman una pequeña fracción de todas las celdas.
Aquí utilicé un pequeño bucle, pero solo 8 repeticiones. Debería poder deshacerse del bucle también, usando las funciones power, log, integer part y max, dando como resultado un poco torpe, pero algo más rápido de una línea, algo alrededor
numpy.multiply( view1, x ** numpy.max( numpy.ceil( (numpy.log (* view1/x... / log(.9)
También podemos probar otra técnica útil, la vectorización. La vectorización está construyendo una función que luego se puede aplicar a todos los elementos de la matriz.
Para un cambio, permite que los márgenes / umbrales preestablecidos encuentren el coeficiente exacto para multiplicar. Aquí es cómo se ve el código
n = 8
decrease_by = numpy.logspace(1,N,num=n, base=x, endpoint=False)
margins = decrease_by * .25
# to do : save border rows for further analysis, skip this for simplicity now
view1 = a [1: -1, 1: -1]
def decrease(x):
k = numpy.searchsorted(margin, a)
return x * decrease_by[k]
f = numpy.vectorize(decrease)
f(view1)
Observación 1 Se puede intentar usar diferentes combinaciones de enfoques, por ejemplo, usar márgenes precomputados con aritmética matricial en lugar de vectorización. Quizás haya incluso más trucos para acelerar ligeramente cada una de las soluciones anteriores o combinaciones de las anteriores.
Nota 2 PyTorch tiene muchas similitudes con la funcionalidad de Numpy, pero puede beneficiarse enormemente de la GPU. Si tienes una GPU decente considera PyTorch. Hubo un intento de numpy basado en gpu (gluon, gnumpy abandonado, minpy) Más en https://stsievert.com/blog/2016/07/01/numpy-gpu/
Podemos hacerlo utilizando índices lineales. Como se describe, su implementación depende de cómo itere a través de la matriz. Así que asumo que queremos arreglar la matriz, averiguar por qué multiplicar cada elemento, luego simplemente aplicar la multiplicación. Así que no importa cómo vamos a través de la matriz.
¿Cuánto multiplicar cada elemento viene dado por:
1 if a[i,j] < 0.25 else np.prod(neighbours_a*0.1)
así que primero analizaremos toda la matriz y obtendremos los 8 vecinos de cada elemento, los multiplicaremos juntos, con un factor de 0.1 ^ 8, y luego aplicaremos una multiplicación condicional de esos valores con a.
Para ello utilizaremos la indexación lineal, y los compensaremos. Entonces, para una matriz con m filas, n columnas, el elemento i, jth tiene un índice lineal i n + j. Para bajar una fila, solo podemos agregar n como (i + 1), el elemento jth tiene un índice lineal (i + 1) n + j = (i n + j) + n. Esta aritmática proporciona una buena manera de obtener vecinos de cada punto, ya que todos los vecinos son compensaciones fijas de cada punto.
import numpy as np
# make some random array
columns = 3
rows = 3
a = np.random.random([rows, columns])
# this contains all the reduce by values, as well as padding values of 1.
# on the top, bot left and right. we pad the array so we dont have to worry
# about edge cases, when gathering neighbours.
pad_row, pad_col = [1, 1], [1,1]
reduce_by = np.pad(a*0.1, [pad_row, pad_col], ''constant'', constant_values=1.)
# build linear indices into the [row + 2, column + 2] array.
pad_offset = 1
linear_inds_col = np.arange(pad_offset, columns + pad_offset)
linear_row_offsets = np.arange(pad_offset, rows + pad_offset)*(columns + 2*pad_offset)
linear_inds_for_array = linear_inds_col[None, :] + linear_row_offsets[:, None]
# get all posible row, col offsets, as linear offsets. We start by making
# normal indices eg. [-1, 1] up 1 row, along 1 col, then make these into single
# linear offsets such as -1*(columns + 2) + 1 for the [-1, 1] example
offsets = np.array(np.meshgrid([1, -1, 0], [1, -1, 0])).T.reshape([-1, 2])[:-1, :]
offsets[:,0] *= (columns + 2*pad_offset)
offsets = offsets.sum(axis=1)
# to every element in the flat linear indices we made, we just have to add
# the corresponding linear offsets, to get the neighbours
linear_inds_for_neighbours = linear_inds_for_array[:,:,None] + offsets[None,None,:]
# we can take these values from reduce by and multiply along the channels
# then the resulting [rows, columns] matrix will contain the potential
# total multiplicative factor to reduce by (if a[i,j] > 0.25)
relavent_values = np.take(reduce_by, linear_inds_for_neighbours)
reduce_by = np.prod(relavent_values, axis=2)
# do reduction
val_numpy = np.where(a > 0.25, a*reduce_by, a)
# check same as loop
val_loop = np.copy(a)
for i in range(rows):
for j in range(columns):
reduce_by = a[i,j]*0.1
for off_row in range(-1, 2):
for off_col in range(-1, 2):
if off_row == 0 and off_col == 0:
continue
if 0 <= (i + off_row) <= rows - 1 and 0 <= (j + off_col) <= columns - 1:
mult = reduce_by if a[i + off_row, j + off_col] > 0.25 else 1.
val_loop[i + off_row, j + off_col] *= mult
print(''a'')
print(a)
print(''reduced np'')
print(val_numpy)
print(''reduce loop'')
print(val_loop)
print(''equal {}''.format(np.allclose(val_numpy, val_loop)))
Su tamaño de la matriz es un tamaño de pantalla típico, por lo que supongo que las celdas son valores de píxeles en el rango [0, 1). Ahora, los valores de píxeles nunca se multiplican entre sí. Si lo fueran, las operaciones dependerían del rango (por ejemplo, [0, 1) o [0, 255]), pero nunca lo hacen. Así que supongo que cuando dices "reducir en un 10% de una celda" quieres decir "restar un 10% de una celda". Pero aún así, la operación sigue dependiendo del orden en que se aplique a las celdas, porque la forma habitual de calcular primero la variación total de una celda y luego aplicarla (como en una convolución) causaría que algunos valores de celda se vuelvan negativos ( por ejemplo, 0.251 - 8 * 0.1 * 0.999), lo cual no tiene sentido si son píxeles.
Permítame suponer por ahora que realmente desea multiplicar las celdas entre sí y por un factor, y que desea hacerlo al hacer que cada celda se vea afectada por su número vecino 0 (su numeración), luego por su número vecino 1, y así sucesivamente para los vecinos número 2, 3, 5, 7 y 8. Como regla, es más fácil definir este tipo de operaciones desde el "punto de vista" de las celdas de destino que desde las celdas de origen. Dado que numpy opera rápidamente en matrices completas (o vistas de las mismas), la forma de hacerlo es desplazar a todos los vecinos en la posición de la celda que se va a modificar. Numpy no tiene shift()
, pero tiene un roll()
que para nuestro propósito es tan bueno, porque no nos importan las celdas de contorno, que, según su comentario, se pueden restaurar al valor original como un último paso. Aquí está el código:
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, :]
Tenga en cuenta que aun así, los pasos principales son diferentes de lo que hace en su solución. Pero necesariamente lo son, porque al volver a escribir su solución en números, los arreglos se leerán y se escribirán en la misma operación, y esto no es algo que los números puedan hacer de una manera predecible.
Si debe cambiar de opinión y decidir restar en lugar de multiplicarse, solo necesita cambiar la columna de *
s antes de np.roll
a una columna de -
s. Pero este solo sería el primer paso en la dirección de una convolución adecuada (una operación común e importante en imágenes 2D), para la cual necesitaría reformular completamente su pregunta.
Dos notas: en su código de ejemplo, indexó la matriz como arr[x][y]
, pero en matrices numpy, por defecto, el índice que está más a la izquierda es el que varía más lentamente, es decir, en 2D, el vertical, de modo que La indexación correcta es arr[y][x]
. Esto se confirma por el orden de los tamaños de su matriz. En segundo lugar, en imágenes, matrices y en números, la dimensión vertical suele representarse como un aumento hacia abajo. Esto hace que su numeración de los vecinos difiera de la mía. Solo multiplica los desplazamientos verticales por -1 si es necesario.
EDITAR
Aquí hay una implementación alternativa que produce exactamente los mismos resultados. Es un poco más rápido, pero modifica la matriz en su lugar:
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])
Por favor aclara tu pregunta
- ¿Realmente se pretende que una iteración de bucle dependa de la otra, como lo menciona @jakevdp en los comentarios?
- Si este es el caso, ¿cómo deben ser exactamente los píxeles del borde que deben ser manejados? Esto afectará a todo el resultado debido a la dependencia de una iteración de bucle a otras
- Agregue una implementación de referencia de trabajo (está obteniendo un error fuera de límites en su implementación de referencia)
Bordes intactos, iteraciones dependientes de bucles.
No veo otra manera que usar un compilador de esta manera. En este ejemplo, uso Numba
, pero también puedes hacer lo mismo en Cython
si esto está preverred.
import numpy as np
import numba as nb
@nb.njit(fastmath=True)
def without_borders(arr):
for x in range(1,arr.shape[0]-1):
for y in range(1,arr.shape[1]-1):
# Find 10% of current cell
reduce_by = arr[x,y] * 0.1
# Reduce the nearby 8 cells by ''reduce_by'' but only if the cell value exceeds 0.25
# [0] [1] [2]
# [3] [*] [5]
# [6] [7] [8]
# * refers to current cell
# cell [0]
arr[x-1][y+1] = arr[x-1][y+1] * reduce_by if arr[x-1][y+1] > 0.25 else arr[x-1][y+1]
# cell [1]
arr[x][y+1] = arr[x][y+1] * reduce_by if arr[x][y+1] > 0.25 else arr[x][y+1]
# cell [2]
arr[x+1][y+1] = arr[x+1][y+1] * reduce_by if arr[x+1][y+1] > 0.25 else arr[x+1][y+1]
# cell [3]
arr[x-1][y] = arr[x-1][y] * reduce_by if arr[x-1][y] > 0.25 else arr[x-1][y]
# cell [4] or current cell
# do nothing
# cell [5]
arr[x+1][y] = arr[x+1][y] * reduce_by if arr[x+1][y] > 0.25 else arr[x+1][y]
# cell [6]
arr[x-1][y-1] = arr[x-1][y-1] * reduce_by if arr[x-1][y-1] > 0.25 else arr[x-1][y-1]
# cell [7]
arr[x][y-1] = arr[x][y-1] * reduce_by if arr[x][y-1] > 0.25 else arr[x][y-1]
# cell [8]
arr[x+1][y-1] = arr[x+1][y-1] * reduce_by if arr[x+1][y-1] > 0.25 else arr[x+1][y-1]
return arr
Tiempos
arr = np.random.rand(720, 1440)
#non-compiled verson: 6.7s
#compiled version: 6ms (the first call takes about 450ms due to compilation overhead)
Esto es realmente fácil de hacer y da un factor de 1000x. Dependiendo de los primeros 3 puntos, puede haber algunas optimizaciones más posibles.