Python / numpy: удаление пустой (нулевой) границы трехмерного массива - PullRequest
0 голосов
/ 07 февраля 2019

У меня есть 3D-массив.Это можно представить как изображение (точнее, это значения точек поля).Я хочу удалить границу (0 значений, обратите внимание, что возможны отрицательные значения) во всех измерениях.Ограничение состоит в том, что размер остается одинаковым для всех молекул, например.Я хочу удалить границу только в том случае, если «самая большая» запись в этом измерении все еще находится внутри границы.Таким образом, весь набор данных (маленький, размер не является проблемой) должен быть принят во внимание.

Пример в 2D:

0  0  0  0  0
0  1  0  0  0
0  1  1  0  0
0  0  0  0  0
0  0  0  0  0

0  0  0  0  0
0  0  0  0  0
0  0  1  0  0
0  0  0  1  0
0  0  0  1  0

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

Результат будет ниже:

1  0  0
1  1  0
0  0  0
0  0  0

0  0  0
0  1  0
0  0  1
0  0  1

Поскольку я не эксперт, у меня возникают проблемы при определении алгоритма длядостичь моей потребности.Мне нужно будет найти минимальный и максимальный индекс в каждом измерении, который не равен 0, а затем использовать его для обрезки массива.

Похоже на это , но в 3D и кадрирование должно учитыватьучтите весь набор данных.

Как мне этого добиться?

ОБНОВЛЕНИЕ 13 февраля 2019 г .:

Итак, я попробовал 3 ответа здесь (один, который, кажется, был удаленкоторый использовал zip), Martins и norok2s отвечают.Выходные размеры одинаковы, поэтому я предполагаю, что все они работают.

Я выбираю решение Martins, потому что могу легко извлечь ограничивающий прямоугольник, чтобы применить его к тестовому набору.

ОБНОВЛЕНИЕ 25 февраля:

Если кто-то все еще наблюдает это, я хотел бы получить дополнительную информацию.Как уже говорилось, это на самом деле не изображения, а «значения полей», означающие float, а не изображения в градациях серого (uint8), что означает, что мне нужно использовать как минимум float16, а для этого просто требуется слишком много памяти.(У меня есть 48 ГБ, но этого недостаточно даже для 50% тренировочного набора).

Ответы [ 3 ]

0 голосов
/ 07 февраля 2019

Обновление:

На основе решения Мартина, использующего min / max и np.where, но обобщая его на любое измерение, вы можете сделать это следующим образом:

def bounds_per_dimension(ndarray):
    return map(
        lambda e: range(e.min(), e.max() + 1),
        np.where(ndarray != 0)
    )

def zero_trim_ndarray(ndarray):
    return ndarray[np.ix_(*bounds_per_dimension(ndarray))]

d = np.array([[
    [0, 0, 0, 0, 0],
    [0, 1, 0, 0, 0],
    [0, 1, 1, 0, 0],
    [0, 0, 0, 0, 0],
    [0, 0, 0, 0, 0],
], [
    [0, 0, 0, 0, 0],
    [0, 0, 0, 0, 0],
    [0, 0, 1, 0, 0],
    [0, 0, 0, 1, 0],
    [0, 0, 0, 1, 0],
]])

zero_trim_ndarray(d)
0 голосов
/ 07 февраля 2019

Вы можете увидеть свою проблему как усечение для определенного ограничивающего прямоугольника в массиве, сформированном путем помещения всех фигур, которые у вас есть, в один массив.

Поэтому, если у вас есть n-мерная функция усечения, решениепросто применить это.

Один из способов реализации этого будет:

import numpy as np

def trim(arr, mask):
    bounding_box = tuple(
        slice(np.min(indexes), np.max(indexes) + 1)
        for indexes in np.where(mask))
    return arr[bounding_box]

Несколько более гибкое решение (где вы можете указать, на какую ось действовать) доступно в FlyingCircus ( Отказ от ответственности : я являюсь основным автором пакета).

Итак, если у вас есть список n-dim массивов (в arrs), выможно сначала сложить их, используя np.stack(), а затем обрезать результат:

import numpy as np

arr = np.stack(arrs, -1)
trimmed_arr = trim(arr, arr != 0)

, который затем можно отделить обратно, используя np.split(), например:

trimmed_list = np.split(trimmed_arr, arr.shape[-1], -1)

РЕДАКТИРОВАТЬ:

Я только что понял, что это по существу тот же подход, что и другие ответы, за исключением того, что он выглядит намного чище для меня.

0 голосов
/ 07 февраля 2019

Попробуйте это: - это основной алгоритм.Я не совсем понимаю, какие стороны вы хотите извлечь из ваших примеров, но приведенный ниже алгоритм должен быть очень простым для изменения в соответствии с вашими потребностями

Примечание: Этот алгоритм извлекает CUBE, где все границы нулевых значений «удалены»,Таким образом, на каждой стороне куба есть какое-то значение! = 0

import numpy as np

# testing dataset
d = np.zeros(shape = [5,5,5]) 

# fill some values
d[3,2,1]=1
d[3,3,1]=1
d[1,3,1]=1
d[1,3,4]=1

# find indexes in all axis
xs,ys,zs = np.where(d!=0) 
# for 4D object
# xs,ys,zs,as = np.where(d!=0) 

# extract cube with extreme limits of where are the values != 0
result = d[min(xs):max(xs)+1,min(ys):max(ys)+1,min(zs):max(zs)+1] 
# for 4D object
# result = d[min(xs):max(xs)+1,min(ys):max(ys)+1,min(zs):max(zs)+1,min(as):max(as)+1]

>>> result.shape
(3, 2, 4)

Случай 1:

d = np.zeros(shape = [5,5,5])

d[3,2,1]=1
# ...  just one value

>>> result.shape # works

(1,1,1)

Случай 2: # ошибка, случай - только нули - полученное 3D не имеет измерений ->ошибка

d = np.zeros(shape = [5,5,5]) # no values except zeros
>>> result.shape


Traceback (most recent call last):
  File "C:\Users\zzz\Desktop\py.py", line 7, in <module>
    result = d[min(xs):max(xs)+1,min(ys):max(ys)+1,min(zs):max(zs)+1]
ValueError: min() arg is an empty sequence

РЕДАКТИРОВАТЬ: поскольку мое решение не получило достаточной любви и понимания, я приведу пример для тела 4-го измерения, где 3 измерения свободны для изображения, а в 4-м измерении хранятся изображения

import numpy as np


class ImageContainer(object):
    def __init__(self,first_image):
        self.container =  np.uint8(np.expand_dims(np.array(first_image), axis=0))

    def add_image(self,image):
        #print(image.shape)
        temp = np.uint8(np.expand_dims(np.array(image), axis=0))
        #print(temp.shape)
        self.container  = np.concatenate((self.container,temp),axis = 0)
        print('container shape',self.container.shape)

# Create image container storage

image = np.zeros(shape = [5,5,3]) # some image
image[2,2,1]=1 # put something random in it
container = ImageContainer(image)
image = np.zeros(shape = [5,5,3]) # some image
image[2,2,2]=1
container.add_image(image)
image = np.zeros(shape = [5,5,3]) # some image
image[2,3,0]=1    # if we set [2,2,0] = 1, we can expect all images will have just 1x1 pixel size
container.add_image(image)
image = np.zeros(shape = [5,5,3]) # some image
image[2,2,1]=1
container.add_image(image)
>>> container.container.shape
('container shape', (4, 5, 5, 3)) # 4 images, size 5x5, 3 channels


# remove borders to all images at once
xs,ys,zs,zzs = np.where(container.container!=0) 
# for 4D object

# extract cube with extreme limits of where are the values != 0
result = container.container[min(xs):max(xs)+1,min(ys):max(ys)+1,min(zs):max(zs)+1,min(zzs):max(zzs)+1]

>>> print('Final shape:',result.shape) 


('Final shape', (4, 1, 2, 3)) # 4 images, size: 1x2, 3 channels
...