Cortar múltiples cuadros de matriz numpy con múltiples y1: y2, x1: x2
Tengo una gran variedad de marcos múltiples (multiple_frames) y quiero cortar la altura y el ancho de cada marco con diferentes y1, y2, x1, x2 para dibujar un cuadrado de "1" en cada marco. (slice_yyxx) es una matriz numerosa y contiene una matriz de y1, y2, x1, x2 para cada fotograma.
slice_yyxx = np.array(slice_yyxx).astype(int)
nbr_frame = slice_yyxx.shape[0]
multiple_frames = np.zeros(shape=(nbr_frame, target_shape[0], target_shape[1], target_shape[2]))
print(multiple_frames.shape)
# (5, 384, 640, 1)
print(slice_yyxx)
# Value ok
print(slice_yyxx.shape)
# (5, 4)
# Then 5 array of coord like [y1, y2, x1, x2] for slice each frames
print(slice_yyxx.dtype)
# np.int64
multiple_frames[:, slice_yyxx[:,0]:slice_yyxx[:,1], slice_yyxx[:,2]:slice_yyxx[:,3]] = 1
# ERROR: TypeError: only integer scalar arrays can be converted to a scalar index
Respuestas
La verdadera pregunta aquí es cómo convertir cortes arbitrarios en algo que pueda usar en múltiples dimensiones sin hacer bucles. Yo diría que el truco consiste en utilizar una combinación inteligente de indexación elegante arange, y repeat.
El objetivo es crear una matriz de índices de filas y columnas que corresponda a cada dimensión. Tomemos un caso simple que es fácil de visualizar: un conjunto de 3 cuadros de matrices 3x3, donde queremos asignar a las submatrices 2x2 superior izquierda e inferior derecha a los dos primeros cuadros, y todo al último cuadro :
multi_array = np.zeros((3, 3, 3))
slice_rrcc = np.array([[0, 2, 0, 2], [1, 3, 1, 3], [0, 3, 0, 3]])
Propongamos los índices que coincidan con cada uno, así como los tamaños y formas:
nframes = slice_rrcc.shape[0] # 3
nrows = np.diff(slice_rrcc[:, :2], axis=1).ravel() # [2, 2, 3]
ncols = np.diff(slice_rrcc[:, 2:], axis=1).ravel() # [2, 2, 3]
sizes = nrows * ncols # [4, 4, 9]
Necesitamos los siguientes índices sofisticados para poder realizar la tarea:
frame_index = np.array([0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2])
row_index = np.array([0, 0, 1, 1, 1, 1, 2, 2, 0, 0, 0, 1, 1, 1, 2, 2, 2])
col_index = np.array([0, 1, 0, 1, 1, 2, 1, 2, 0, 1, 2, 0, 1, 2, 0, 1, 2])
Si podemos obtener las matrices frame_index
, row_index
y col_index
, podemos establecer los datos para cada segmento de la siguiente manera:
multi_array[frame_index, row_index, col_index] = 1
frame_index
index es fácil de obtener:
frame_index = np.repeat(np.arange(nframes), sizes)
row_index
toma un poco más de trabajo. Debe generar un conjunto de nrows
índices para cada fotograma individual y repetirlos ncols
veces. Puede hacer esto generando un rango continuo y reiniciando el conteo en cada cuadro usando la resta:
row_range = np.arange(nrows.sum())
row_offsets = np.zeros_like(row_range)
row_offsets[np.cumsum(nrows[:-1])] = nrows[:-1]
row_index = row_range - np.cumsum(row_offsets) + np.repeat(slice_rrcc[:, 0], nrows)
segments = np.repeat(ncols, nrows)
row_index = np.repeat(row_index, segments)
col_index
será menos trivial todavía. Debe generar una secuencia para cada fila con el desplazamiento correcto y repetirla en trozos para cada fila y luego para cada fotograma. El enfoque es similar al de row_index
, con un índice elegante adicional para obtener el orden correcto:
col_index_index = np.arange(sizes.sum())
col_index_resets = np.cumsum(segments[:-1])
col_index_offsets = np.zeros_like(col_index_index)
col_index_offsets[col_index_resets] = segments[:-1]
col_index_offsets[np.cumsum(sizes[:-1])] -= ncols[:-1]
col_index_index -= np.cumsum(col_index_offsets)
col_range = np.arange(ncols.sum())
col_offsets = np.zeros_like(col_range)
col_offsets[np.cumsum(ncols[:-1])] = ncols[:-1]
col_index = col_range - np.cumsum(col_offsets) + np.repeat(slice_rrcc[:, 2], ncols)
col_index = col_index[col_index_index]
Con esta formulación, incluso puede intensificarla y especificar un valor diferente para cada cuadro. Si desea asignar values = [1, 2, 3]
a los marcos en mi ejemplo, simplemente haga
multi_array[frame_index, row_index, col_index] = np.repeat(values, sizes)
Veremos si hay una forma más eficiente de hacer esto. Una parte sobre la que pregunté está aquí .
Punto de referencia
Una comparación de su bucle con mi solución vectorizada para nframes
en {10, 100, 1000} y ancho y alto de multi_array
en {100, 1000, 10000}
:
def set_slices_loop(arr, slice_rrcc):
for a, s in zip(arr, slice_rrcc):
a[s[0]:s[1], s[2]:s[3]] = 1
np.random.seed(0xABCDEF)
for nframes in [10, 100, 1000]:
for dim in [10, 32, 100]:
print(f'Size = {nframes}x{dim}x{dim}')
arr = np.zeros((nframes, dim, dim), dtype=int)
slice = np.zeros((nframes, 4), dtype=int)
slice[:, ::2] = np.random.randint(0, dim - 1, size=(nframes, 2))
slice[:, 1::2] = np.random.randint(slice[:, ::2] + 1, dim, size=(nframes, 2))
%timeit set_slices_loop(arr, slice)
arr[:] = 0
%timeit set_slices(arr, slice)
Los resultados están abrumadoramente a favor del bucle, con la única excepción de un gran número de fotogramas y tamaños de fotogramas pequeños. La mayoría de los casos "normales" son un orden de magnitud más rápidos con el bucle:
Bucle
| Dimension |
| 100 | 1000 | 10000 |
--------+---------+---------+---------+
F 10 | 33.8 µs | 35.8 µs | 43.4 µs |
r -----+---------+---------+---------+
a 100 | 310 µs | 331 µs | 401 µs |
m -----+---------+---------+---------+
e 1000 | 3.09 ms | 3.31 ms | 4.27 ms |
--------+---------+---------+---------+
Vectorizado
| Dimension |
| 100 | 1000 | 10000 |
--------+---------+---------+---------+
F 10 | 225 µs | 266 µs | 545 µs |
r -----+---------+---------+---------+
a 100 | 312 µs | 627 µs | 4.11 ms |
m -----+---------+---------+---------+
e 1000 | 1.07 ms | 4.63 ms | 48.5 ms |
--------+---------+---------+---------+
TL; DR
Se puede hacer, pero no se recomienda:
def set_slices(arr, slice_rrcc, value):
nframes = slice_rrcc.shape[0]
nrows = np.diff(slice_rrcc[:, :2], axis=1).ravel()
ncols = np.diff(slice_rrcc[:, 2:], axis=1).ravel()
sizes = nrows * ncols
segments = np.repeat(ncols, nrows)
frame_index = np.repeat(np.arange(nframes), sizes)
row_range = np.arange(nrows.sum())
row_offsets = np.zeros_like(row_range)
row_offsets[np.cumsum(nrows[:-1])] = nrows[:-1]
row_index = row_range - np.cumsum(row_offsets) + np.repeat(slice_rrcc[:, 0], nrows)
row_index = np.repeat(row_index, segments)
col_index_index = np.arange(sizes.sum())
col_index_resets = np.cumsum(segments[:-1])
col_index_offsets = np.zeros_like(col_index_index)
col_index_offsets[col_index_resets] = segments[:-1]
col_index_offsets[np.cumsum(sizes[:-1])] -= ncols[:-1]
col_index_index -= np.cumsum(col_index_offsets)
col_range = np.arange(ncols.sum())
col_offsets = np.zeros_like(col_range)
col_offsets[np.cumsum(ncols[:-1])] = ncols[:-1]
col_index = col_range - np.cumsum(col_offsets) + np.repeat(slice_rrcc[:, 2], ncols)
col_index = col_index[col_index_index]
if values.size == 1:
arr[frame_index, row_index, col_index] = value
else:
arr[frame_index, row_index, col_index] = np.repeat(values, sizes)
Esta es una publicación de evaluación comparativa que utiliza un benchitpaquete (pocas herramientas de evaluación comparativa empaquetadas juntas; descargo de responsabilidad: soy su autor) para evaluar las soluciones propuestas.
Estamos set_slices
haciendo una evaluación comparativa de la solución de @Mad Physicist con arr[frame_index, row_index, col_index] = 1
y set_slices_loop
sin cambios para obtener el tiempo de ejecución (sec)
.
np.random.seed(0xABCDEF)
in_ = {}
for nframes in [10, 100, 1000]:
for dim in [10, 32, 100]:
arr = np.zeros((nframes, dim, dim), dtype=int)
slice = np.zeros((nframes, 4), dtype=int)
slice[:, ::2] = np.random.randint(0, dim - 1, size=(nframes, 2))
slice[:, 1::2] = np.random.randint(slice[:, ::2] + 1, dim, size=(nframes, 2))
in_[(nframes, dim)] = [arr, slice]
import benchit
funcs = [set_slices, set_slices_loop]
t = benchit.timings(funcs, in_, input_name=['NumFrames', 'Dim'], multivar=True)
t.plot(sp_argID=1, logx=True, save='timings.png')
