Нарезать несколько кадров массива numpy с несколькими y1: y2, x1: x2

Aug 19 2020

У меня есть массив из нескольких кадров (multiple_frames), и я хочу разрезать высоту и ширину каждого кадра с разными y1, y2, x1, x2, чтобы нарисовать квадрат «1» в каждом кадре. (slice_yyxx) представляет собой массив с несколькими значениями и содержит по одному массиву y1, y2, x1, x2 для каждого кадра.

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

Ответы

1 MadPhysicist Aug 20 2020 at 17:52

Настоящий вопрос здесь в том, как преобразовать произвольные фрагменты во что-то, что можно использовать в нескольких измерениях без зацикливания. Я бы сказал, что хитрость состоит в том, чтобы использовать умную комбинацию причудливой индексации arange, и repeat.

Цель состоит в том, чтобы создать массив индексов строк и столбцов, соответствующих каждому измерению. Давайте возьмем простой случай, который легко визуализировать: 3-кадровый набор матриц 3x3, где мы хотим назначить верхний левый и нижний правый подмассивы 2x2 первым двум кадрам, а все это - последнему кадру. :

multi_array = np.zeros((3, 3, 3))
slice_rrcc = np.array([[0, 2, 0, 2], [1, 3, 1, 3], [0, 3, 0, 3]])

Давайте придумаем индексы, соответствующие каждому, а также размеры и формы:

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]

Чтобы выполнить задание, нам нужны следующие причудливые индексы:

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])

Если мы можем получить массивы frame_index, row_indexи col_index, мы можем установить данные для каждого сегмента следующим образом:

multi_array[frame_index, row_index, col_index] = 1

frame_index index легко получить:

frame_index = np.repeat(np.arange(nframes), sizes)

row_indexтребуется немного больше работы. Вам необходимо создать набор nrowsиндексов для каждого отдельного кадра и повторить их несколько ncolsраз. Вы можете сделать это, сгенерировав непрерывный диапазон и перезапустив счет в каждом кадре с помощью вычитания:

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будет еще менее тривиально. Вам необходимо создать последовательность для каждой строки с правильным смещением и повторять ее по частям для каждой строки, а затем для каждого кадра. Подход аналогичен подходу для row_index, с дополнительным причудливым индексом для правильного порядка:

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]

Используя эту формулировку, вы можете даже увеличить ее и указать другое значение для каждого кадра. Если вы хотите назначить values = [1, 2, 3]кадрам в моем примере, просто выполните

multi_array[frame_index, row_index, col_index] = np.repeat(values, sizes)

Посмотрим, есть ли более эффективный способ сделать это. Одна часть, о которой я спросил, здесь .

Контрольный показатель

Сравнение вашего цикла с моим векторизованным решением для nframesв {10, 100, 1000} и шириной и высотой multi_arrayв {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)

Результаты в подавляющем большинстве случаев в пользу цикла, за единственным исключением очень большого количества кадров и небольших размеров кадров. В большинстве «нормальных» случаев с циклом на порядок быстрее:

Зацикливание

        |          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 |
--------+---------+---------+---------+

Векторизованный

        |          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

Можно, но не рекомендуется:

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

Это сообщение о тестировании с использованием benchitпакета (несколько инструментов для тестирования собраны вместе; отказ от ответственности: я его автор) для тестирования предлагаемых решений.

Мы set_slicesпроводим сравнительный анализ с помощью решения @Mad Physicist с любыми изменениями arr[frame_index, row_index, col_index] = 1и set_slices_loopбез них, чтобы получить время выполнения (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')