Пересечение множества 2D NP-массивов для определения зон
используя этот небольшой воспроизводимый пример, я до сих пор не смог создать новый целочисленный массив из 3 массивов, который содержит уникальные группировки во всех трех входных массивах.
массивы связаны с топографическими свойствами:
import numpy as np
asp = np.array([8,1,1,2,7,8,2,3,7,6,4,3,6,5,5,4]).reshape((4,4)) #aspect
slp = np.array([9,10,10,9,9,12,12,9,10,11,11,9,9,9,9,9]).reshape((4,4)) #slope
elv = np.array([13,14,14,13,14,15,16,14,14,15,16,14,13,14,14,13]).reshape((4,4)) #elevation
идея заключается в том, что географические контуры разбиты на 3 разные свойства применения ГИС:
- 1-8 для аспекта (1=Северная облицовка, 2=Северо-Восточная облицовка и т. д.)
- 9-12 на склоне (9=пологому склону...12=крутой склон)
- 13-16 для высоты (13=самые низкие высоты...16=самые высокие высоты)
небольшая графика ниже пытается изобразить вид результата, который я ищу (массив, показанный в левом нижнем углу). Обратите внимание, что" ответ", приведенный на графике, является только одним возможным ответом. Меня не волнует окончательное расположение целых чисел в результирующем массиве, пока конечный массив содержит целое число в каждом индексе строки / столбца, которое идентифицирует уникальный группировки.
например, индексы массива в [0,1] и [0,2] имеют один и тот же аспект, наклон и высоту и поэтому получают один и тот же целочисленный идентификатор в результирующем массиве.
тут включает в себя есть встроенная процедура для такого рода вещей?
5 ответов
Это можно сделать с помощью numpy.unique()
а затем отображение, как:
код:
combined = 10000 * asp + 100 * slp + elv
unique = dict(((v, i + 1) for i, v in enumerate(np.unique(combined))))
combined_unique = np.vectorize(unique.get)(combined)
Тестовый Код:
import numpy as np
asp = np.array([8, 1, 1, 2, 7, 8, 2, 3, 7, 6, 4, 3, 6, 5, 5, 4]).reshape((4, 4)) # aspect
slp = np.array([9, 10, 10, 9, 9, 12, 12, 9, 10, 11, 11, 9, 9, 9, 9, 9]).reshape((4, 4)) # slope
elv = np.array([13, 14, 14, 13, 14, 15, 16, 14, 14, 15, 16, 14, 13, 14, 14, 13]).reshape((4, 4))
combined = 10000 * asp + 100 * slp + elv
unique = dict(((v, i + 1) for i, v in enumerate(np.unique(combined))))
combined_unique = np.vectorize(unique.get)(combined)
print(combined_unique)
результаты:
[[12 1 1 2]
[10 13 3 4]
[11 9 6 4]
[ 8 7 7 5]]
каждое местоположение в сетке связано с кортежем, состоящим из одного значения из
asp
, slp
и elv
. Например, в верхнем левом углу есть кортеж (8,9,13)
.
Мы хотели бы сопоставить этот кортеж с числом, которое однозначно идентифицирует этот кортеж.
один из способов сделать это-подумать о (8,9,13)
как индекс в массив 3D
np.arange(9*13*17).reshape(9,13,17)
. Этот конкретный массив был выбран
для размещения наибольших значений в asp
, slp
и elv
:
In [107]: asp.max()+1
Out[107]: 9
In [108]: slp.max()+1
Out[108]: 13
In [110]: elv.max()+1
Out[110]: 17
теперь мы можем сопоставить кортеж (8,9,13) с числом 1934:
In [113]: x = np.arange(9*13*17).reshape(9,13,17)
In [114]: x[8,9,13]
Out[114]: 1934
если мы сделаем это для каждого местоположения в сетке, то получим уникальный номер для каждого местоположения. Мы могли бы закончить прямо здесь, позволив этим уникальным номерам служить ярлыками.
или мы можем генерировать меньшие целочисленные метки (начиная с 0 и увеличивая на 1)
используя np.unique
С
return_inverse=True
:
uniqs, labels = np.unique(vals, return_inverse=True)
labels = labels.reshape(vals.shape)
так, для пример,
import numpy as np
asp = np.array([8,1,1,2,7,8,2,3,7,6,4,3,6,5,5,4]).reshape((4,4)) #aspect
slp = np.array([9,10,10,9,9,12,12,9,10,11,11,9,9,9,9,9]).reshape((4,4)) #slope
elv = np.array([13,14,14,13,14,15,16,14,14,15,16,14,13,14,14,13]).reshape((4,4)) #elevation
x = np.arange(9*13*17).reshape(9,13,17)
vals = x[asp, slp, elv]
uniqs, labels = np.unique(vals, return_inverse=True)
labels = labels.reshape(vals.shape)
доходность
array([[11, 0, 0, 1],
[ 9, 12, 2, 3],
[10, 8, 5, 3],
[ 7, 6, 6, 4]])
вышеуказанный метод работает отлично, пока значения в asp
, slp
и elv
небольшие целые числа. Если бы целые числа были слишком большими, произведение их максимумов могло бы переполнить максимально допустимое значение, которое можно передать в np.arange
. Более того, создание такого большого массива было бы неэффективным.
Если значения были терки, то они не могут быть интерпретированы как индексы в 3D массив x
.
поэтому для решения этих проблем, используйте np.unique
для преобразования значений в asp
, slp
и elv
сначала уникальные целочисленные метки:
indices = [ np.unique(arr, return_inverse=True)[1].reshape(arr.shape) for arr in [asp, slp, elv] ]
M = np.array([item.max()+1 for item in indices])
x = np.arange(M.prod()).reshape(M)
vals = x[indices]
uniqs, labels = np.unique(vals, return_inverse=True)
labels = labels.reshape(vals.shape)
который дает тот же результат, что и показано выше, но работает, даже если asp
, slp
, elv
были поплавки и / или большие целые числа.
наконец, мы можем избежать образования np.arange
:
x = np.arange(M.prod()).reshape(M)
vals = x[indices]
путем вычисления vals
как продукт индексов и шаги:
M = np.r_[1, M[:-1]]
strides = M.cumprod()
indices = np.stack(indices, axis=-1)
vals = (indices * strides).sum(axis=-1)
итак, все вместе:
import numpy as np
asp = np.array([8,1,1,2,7,8,2,3,7,6,4,3,6,5,5,4]).reshape((4,4)) #aspect
slp = np.array([9,10,10,9,9,12,12,9,10,11,11,9,9,9,9,9]).reshape((4,4)) #slope
elv = np.array([13,14,14,13,14,15,16,14,14,15,16,14,13,14,14,13]).reshape((4,4)) #elevation
def find_labels(*arrs):
indices = [np.unique(arr, return_inverse=True)[1] for arr in arrs]
M = np.array([item.max()+1 for item in indices])
M = np.r_[1, M[:-1]]
strides = M.cumprod()
indices = np.stack(indices, axis=-1)
vals = (indices * strides).sum(axis=-1)
uniqs, labels = np.unique(vals, return_inverse=True)
labels = labels.reshape(arrs[0].shape)
return labels
print(find_labels(asp, slp, elv))
# [[ 3 7 7 0]
# [ 6 10 12 4]
# [ 8 9 11 4]
# [ 2 5 5 1]]
Это похоже на аналогичную проблему для маркировки уникальных областей в изображении. Это функция, которую я написал для этого, хотя сначала вам нужно будет объединить 3 массива в 1 3D-массив.
def labelPix(pix):
height, width, _ = pix.shape
pixRows = numpy.reshape(pix, (height * width, 3))
unique, counts = numpy.unique(pixRows, return_counts = True, axis = 0)
unique = [list(elem) for elem in unique]
labeledPix = numpy.zeros((height, width), dtype = int)
offset = 0
for index, zoneArray in enumerate(unique):
index += offset
zone = list(zoneArray)
zoneArea = (pix == zone).all(-1)
elementsArray, numElements = scipy.ndimage.label(zoneArea)
elementsArray[elementsArray!=0] += offset
labeledPix[elementsArray!=0] = elementsArray[elementsArray!=0]
offset += numElements
return labeledPix
это будет обозначать уникальные 3-значные комбинации, а также присваивать отдельные метки зонам, которые имеют ту же комбинацию 3-значных, но не контактируют друг с другом.
asp = numpy.array([8,1,1,2,7,8,2,3,7,6,4,3,6,5,5,4]).reshape((4,4)) #aspect
slp = numpy.array([9,10,10,9,9,12,12,9,10,11,11,9,9,9,9,9]).reshape((4,4)) #slope
elv = numpy.array([13,14,14,13,14,15,16,14,14,15,16,14,13,14,14,13]).reshape((4,4)) #elevation
pix = numpy.zeros((4,4,3))
pix[:,:,0] = asp
pix[:,:,1] = slp
pix[:,:,2] = elv
print(labelPix(pix))
возвращает:
[[ 0 1 1 2]
[10 12 3 4]
[11 9 6 4]
[ 8 7 7 5]]
вот простой метод Python с использованием itertools.groupby
. Для этого требуется, чтобы входные данные были списками 1D, но это не должно быть серьезной проблемой. Стратегия состоит в том, чтобы объединить списки вместе с номером индекса, а затем отсортировать результирующие столбцы. Затем мы группируем одинаковые столбцы вместе, игнорируя номер индекса при сравнении столбцов. Затем мы собираем номера индексов из каждой группы и используем их для построения конечного результата список.
from itertools import groupby
def show(label, seq):
print(label, ' '.join(['{:2}'.format(u) for u in seq]))
asp = [8, 1, 1, 2, 7, 8, 2, 3, 7, 6, 4, 3, 6, 5, 5, 4]
slp = [9, 10, 10, 9, 9, 12, 12, 9, 10, 11, 11, 9, 9, 9, 9, 9]
elv = [13, 14, 14, 13, 14, 15, 16, 14, 14, 15, 16, 14, 13, 14, 14, 13]
size = len(asp)
a = sorted(zip(asp, slp, elv, range(size)))
groups = sorted([u[-1] for u in g] for _, g in groupby(a, key=lambda t:t[:-1]))
final = [0] * size
for i, g in enumerate(groups, 1):
for j in g:
final[j] = i
show('asp', asp)
show('slp', slp)
show('elv', elv)
show('out', final)
выход
asp 8 1 1 2 7 8 2 3 7 6 4 3 6 5 5 4
slp 9 10 10 9 9 12 12 9 10 11 11 9 9 9 9 9
elv 13 14 14 13 14 15 16 14 14 15 16 14 13 14 14 13
out 1 2 2 3 4 5 6 7 8 9 10 7 11 12 12 13
нет необходимости делать второй вид, мы могли бы просто использовать простой список comp
groups = [[u[-1] for u in g] for _, g in groupby(a, key=lambda t:t[:-1])]
или выражение генератор
groups = ([u[-1] for u in g] for _, g in groupby(a, key=lambda t:t[:-1]))
Я сделал это только для того, чтобы мой вывод соответствовал выходу в вопросе.
вот один из способов решить эту проблему с помощью поиска на основе словаря.
from collections import defaultdict
import itertools
group_dict = defaultdict(list)
idx_count = 0
for a, s, e in np.nditer((asp, slp, elv)):
asp_tuple = (a.tolist(), s.tolist(), e.tolist())
if asp_tuple not in group_dict:
group_dict[asp_tuple] = [idx_count+1]
idx_count += 1
else:
group_dict[asp_tuple].append(group_dict[asp_tuple][-1])
list1d = list(itertools.chain(*list(group_dict.values())))
np.array(list1d).reshape(4, 4)
# result
array([[ 1, 2, 2, 3],
[ 4, 5, 6, 7],
[ 7, 8, 9, 10],
[11, 12, 12, 13]])