Iris beberapa bingkai larik numpy dengan beberapa y1: y2, x1: x2

Aug 19 2020

Saya memiliki array numpy dari beberapa frame (multiple_frames) dan saya ingin mengiris tinggi dan lebar setiap frame dengan y1, y2, x1, x2 yang berbeda untuk menggambar kuadrat "1" di setiap frame. (slice_yyxx) adalah larik numpy dan berisi satu larik y1, y2, x1, x2 untuk setiap frame.

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

Jawaban

1 MadPhysicist Aug 20 2020 at 17:52

Pertanyaan sebenarnya di sini adalah bagaimana mengubah irisan sembarang menjadi sesuatu yang dapat Anda gunakan di berbagai dimensi tanpa perulangan. Saya akan mengandaikan bahwa triknya adalah menggunakan kombinasi cerdas dari pengindeksan mewah arange,, dan repeat.

Tujuannya adalah untuk membuat larik indeks baris dan kolom yang sesuai dengan setiap dimensi. Mari kita ambil kasus sederhana yang mudah untuk divisualisasikan: set 3-frame matriks 3x3, di mana kita ingin menetapkan sub-array 2x2 kiri atas dan kanan bawah ke dua frame pertama, dan semuanya ke frame terakhir :

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

Mari kita buat indeks yang cocok dengan masing-masing, serta ukuran dan bentuknya:

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]

Kami membutuhkan indeks mewah berikut untuk dapat melakukan tugas:

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

Jika kita dapat memperoleh array frame_index, row_indexdan col_index, kita dapat mengatur data untuk setiap segmen sebagai berikut:

multi_array[frame_index, row_index, col_index] = 1

frame_index indeks mudah didapat:

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

row_indexmembutuhkan sedikit lebih banyak pekerjaan. Anda perlu membuat satu set nrowsindeks untuk setiap frame individu, dan mengulanginya ncolsberkali - kali. Anda dapat melakukan ini dengan membuat rentang kontinu dan memulai ulang penghitungan di setiap bingkai menggunakan pengurangan:

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_indexakan menjadi lebih sepele lagi. Anda perlu membuat urutan untuk setiap baris dengan offset yang tepat, dan mengulanginya dalam potongan untuk setiap baris, lalu untuk setiap frame. Pendekatannya mirip dengan untuk row_index, dengan indeks mewah tambahan untuk mendapatkan pesanan yang benar:

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]

Dengan menggunakan formulasi ini, Anda bahkan dapat meningkatkannya dan menentukan nilai yang berbeda untuk setiap frame. Jika Anda ingin menetapkan values = [1, 2, 3]bingkai dalam contoh saya, lakukan saja

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

Kami akan melihat apakah ada cara yang lebih efisien untuk melakukan ini. Satu bagian yang saya tanyakan ada di sini .

Tolok ukur

Perbandingan loop Anda vs solusi vektorisasi saya untuk nframesdalam {10, 100, 1000} dan lebar dan tinggi multi_arraydalam {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)

Hasilnya sangat mendukung loop, dengan satu-satunya pengecualian untuk jumlah frame yang sangat besar dan ukuran frame yang kecil. Sebagian besar kasus "normal" adalah urutan besarnya lebih cepat dengan perulangan:

Pendauran

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

Vektorisasi

        |          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

Bisa dilakukan, tapi tidak disarankan:

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

Ini adalah posting benchmarking menggunakan benchitpaket (beberapa alat benchmarking dikemas bersama; disclaimer: Saya penulisnya) untuk membandingkan solusi yang diusulkan.

Kami melakukan benchmarking set_slicesdari soln @Mad Physicist dengan arr[frame_index, row_index, col_index] = 1dan set_slices_looptanpa perubahan apa pun untuk mendapatkan runtime (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')