Cortar múltiples cuadros de matriz numpy con múltiples y1: y2, x1: x2

Aug 19 2020

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

1 MadPhysicist Aug 20 2020 at 17:52

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_indexy 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_indextoma un poco más de trabajo. Debe generar un conjunto de nrowsíndices para cada fotograma individual y repetirlos ncolsveces. 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_indexserá 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 nframesen {10, 100, 1000} y ancho y alto de multi_arrayen {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)
1 Divakar Aug 20 2020 at 22:22

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_sliceshaciendo una evaluación comparativa de la solución de @Mad Physicist con arr[frame_index, row_index, col_index] = 1y set_slices_loopsin 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')