Python массив, как выбрать блок столбцов и строк через определенное расстояние без цикла - PullRequest
1 голос
/ 11 июля 2020

Я хотел бы выбрать определенные столбцы и строки из большого 2D-массива. Например, я хочу выбирать N = 64 столбцов после каждых D = 128 столбцов, если бы мой большой массив имел форму (384,384), это привело бы к меньшей (256, 256) матрице, в основном потому, что я хочу удалить избыточные данные из большая матрица.

Мой код выглядит так, как показано ниже, проблема в том, что я не знаю, как избежать явной индексации (здесь 4 раза в каждом направлении, на самом деле может быть реализовано как al oop с помощью generi c размер) красивым способом, по возможности без использования петель. Также в этом примере я начинаю выделение с 0-го столбца, в общем, его можно начать с произвольного столбца.

row_mask = np.zeros(rows, dtype=bool)  # e.g. rows = 384
col_mask = np.zeros(cols, dtype=bool)  # e.g. cols = 384

N = 64
D = 128
# explicit selection of columns and rows
row_mask[0:N] = 1
row_mask[D:D + N] = 1
row_mask[D * 2:D * 2 + N] = 1
row_mask[-N:] = 1
col_mask[0:N] = 1
col_mask[D:D + N] = 1
col_mask[D * 2:D * 2 + N] = 1
col_mask[-N:] = 1

#Image of (384, 384), image of (256, 256)
image = Image[np.ix_(row_mask, col_mask)]

Ответы [ 5 ]

2 голосов
/ 12 июля 2020

На самом деле, для этого примера с относительно большими плитками гораздо эффективнее использовать нарезку для l oop, чем избегать для l oop с помощью гораздо более дорогостоящей причудливой индексации:

from scipy.misc import face
from timeit import timeit

img = face()

def fancy():
    D,N=128,64
    r_mask = np.arange(img.shape[0]) % D < N
    c_mask = np.arange(img.shape[1]) % D < N
    return img[r_mask[:, None] & c_mask].reshape(np.count_nonzero(r_mask), np.count_nonzero(c_mask),3)

def loopy():
    di,dj=64,64
    DI,DJ=128,128
    return np.block([[[img[i:i+di,j:j+dj]] for j in range(0,img.shape[1],DJ)] for i in range(0,img.shape[0],DI)])

(fancy()==loopy()).all()
# True
timeit(loopy,number=100)*10
# 0.763049490051344
timeit(fancy,number=100)*10
# 5.845791429746896
1 голос
/ 12 июля 2020

Вы можете построить совершенно общее решение с причудливой индексацией, используя широковещательное сложение и упорядочение.

Возьмем одномерный случай:

arr = np.random.randint(10, size=973)

S = arr.shape[0]
N = 64
D = 128

# how many D-sized chunks?
nd = np.ceil(S / D)
# how many indices to chop from the end? I.e., which part of the last chunk doesn't fit in S?
nn = N - S + (nd - 1) * D

index = (np.arange(N) + D * np.arange(nd)[:, None]).ravel()[:-nn]
result = arr[index]

В 2D это будет выглядеть как

arr = np.random.randint(10, size=(1024, 768))

S = np.array(arr.shape)
N = 64
D = 128

nd = np.ceil(S / D)
nn = N - S + (nd - 1) * D

r_index = (np.arange(N) + D * np.arange(nd[0])[:, None]).ravel()[:-nn[0]]
c_index = (np.arange(N) + D * np.arange(nd[1])[:, None]).ravel()[:-nn[1]]
result = arr[np.ix_(r_index, c_index)]

Вы можете расширить это до N измерений с помощью небольшого трюка с широковещательной передачей и понимания небольшого списка:

arr = np.random.randint(10, size=(128, 200, 64))

S = np.array(arr.shape)
N = 64  # Could be array with different value for each dimension
D = 128 # Same with this

nd = np.ceil(S / D)
nn = N - S + (nd - 1) * D

Скорее всего, вы получите рваный массив индексов все, поэтому было бы разумно переключиться на список:

index = [(np.arange(N) + D * np.arange(ndx)[:, None]).ravel()[:-nnx] for ndx, nnx in zip(nd, nn)]
result = arr[np.ix_(*index)]
0 голосов
/ 12 июля 2020

Вероятно, самый простой способ избежать циклов - использовать оператор по модулю:

img = ...
r_mask = (np.arange(img.shape[0] % D < N)
c_mask = (np.arange(img.shape[0] % D < N)
result = img[r_mask[:, None] & c_mask].reshape(np.count_nonzero(r_mask), np.count_nonzero(c_mask)]

Или в исходной записи:

result = img[np.ix_(r_mask, c_mask)]

Каждая половина маски представляет собой массив совпадает с соответствующим размером img, который устанавливает для первых N элементов каждого блока размером D True, а для остальных - False. Широковещательная передача гарантирует, что две половины объединены в маску с такими же размерами, как img.

Этот метод довольно хорошо обобщается для произвольных размеров, хотя в этом случае вам придется запустить al oop:

mask = np.ones(arr.shape, dtype=bool)
dims = np.empty(arr.ndim)
for i, k in enumerate (mask.shape[::-1]):
    m = (np.arange(k) % D < N)
    mask &= np.expand_dims(m, np.arange(i))
    dims[i] = np.count_nonzero(m)
result = arr[mask].reshape(dims[::-1])
0 голосов
/ 11 июля 2020

Предполагая, что каждая строка в вашей таблице имеет 384 столбца, вы можете использовать для l oop:

for row in table:
    row = row[:64] + row[192:256]
0 голосов
/ 11 июля 2020

Вы можете добавить np.arange(N) к каждому значению [0, D, ...], а затем объединить его с частью [-N:].

import numpy as np

N = 64
D = 128
shape = (384, 384)
axis = 0
rows = np.union1d(
    np.arange(shape[axis] - N, shape[axis]),
    np.add.outer(np.arange(0, shape[axis], D), np.arange(N)).ravel(),
)
axis = 1
cols = np.union1d(
    np.arange(shape[axis] - N, shape[axis]),
    np.add.outer(np.arange(0, shape[axis], D), np.arange(N)).ravel(),
)
image = Image[np.ix_(rows, cols)]
...