Массив подкласса numpy

У меня проблема. Проблема в том, что я хочу создать подкласс массива numpy, а затем создать массив объектов этого типа. Когда я ссылаюсь на элемент в этом массиве, я хочу, чтобы он по-прежнему был экземпляром этого подкласса. Вместо этого это экземпляр массива numpy.

Вот тест, который не проходит:

import numpy as np


class ImageWrapper(np.ndarray):

    def __new__(cls, image_data):
        assert image_data.ndim in (2, 3)
        return image_data.view(cls)

    @property
    def n_colours(self): 
        return 1 if self.ndim==2 else self.shape[2]


n_frames = 10
frames = [ImageWrapper(np.random.randint(255, size = (20, 15, 3)).astype('uint8')) for _ in xrange(n_frames)]
video = np.array(frames)
assert video[0].n_colours == 3

Дает мне: AttributeError: объект 'numpy.ndarray' не имеет атрибута 'n_colours'

Как я могу заставить это работать?

Вещи, которые уже пробовали:

  • Установка subok=True при построении видео — это работает только при построении массива из одного экземпляра объекта подкласса, а не списка.
  • Установка dtype=object или dtype=ImageWrapper не работает

Я понимаю, что могу просто сделать видео списком, но было бы предпочтительнее сохранить его в виде массива numpy по другим причинам.


person Peter    schedule 22.07.2014    source источник
comment
Проблема в том, что когда вы вызываете array для списка 3D-массивов, вы получаете 4D-массив, а не 1D-массив, полный 3D-массивов. Очевидно, что массив 4D не может быть ImageWrapper, так что это ndarray, поэтому любой его фрагмент также является ndarray, независимо от того, откуда изначально были получены данные. Вопрос в том, почему вы хотите, чтобы это был массив? 1D-массив из object не теряет всех преимуществ numpy по сравнению с нативными списками, но теряет много из них, и если вы можете назвать другие причины, в вашем последнем предложении это может помочь.   -  person abarnert    schedule 23.07.2014
comment
Кроме того, есть ли причина, по которой ваш дизайн должен предписывать, что четырехмерный массив не может быть ImageWrapper? Ваш n_colours должен был бы вернуть массив из N-3 измерений вместо скаляра, если N>3 (или просто вызвать исключение), но в противном случае, в чем была бы проблема? Потому что это сильно упростило бы задачу…   -  person abarnert    schedule 23.07.2014
comment
другие причины не являются веской причиной, просто это часть интерфейса, где массивы являются ожидаемым типом данных. В этом случае подойдет массив объектов, предложенный Хайме.   -  person Peter    schedule 23.07.2014
comment
Но не будет ли работать подкласс 4D-массива? Мне кажется, что если у вас есть код, который хочет массив, он захочет транслировать n_colours и crop и downsample и так далее по этому массиву. 4D-массив дает вам все это бесплатно (по крайней мере, на стороне вызывающей стороны; вы должны быть осторожны на стороне реализации, как показывает Bi Rico); с массивом объектов, которые являются массивами, вам нужно вручную обернуть несвязанный метод в ufunc, чтобы что-то сделать. (Если вы не планируете просто перебирать объекты, в таком случае зачем использовать массив?)   -  person abarnert    schedule 23.07.2014


Ответы (2)


Чего бы вы ни пытались достичь, вероятно, есть лучший способ сделать это, чем создание подкласса ndarray. Но учитывая это, ваш массив может иметь тип object, хотя вы должны быть осторожны при его создании. Это работает:

>>> video = np.empty((len(frames),), dtype=object)
>>> video[:] = frames
>>> video[0].n_colours
3

Но это не так:

>>> video = np.array(frames, dtype=object)
>>> video[0].n_colours
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'numpy.ndarray' object has no attribute 'n_colours'
person Jaime    schedule 23.07.2014
comment
Это работает для меня, спасибо. Настоящая причина этого в том, что у нас странный формат изображения (YUV), и мы хотим, чтобы к нему были привязаны методы для таких вещей, как обрезка и понижение частоты дискретизации. Причина создания подклассов, а не обертывания, более сомнительна - в основном это потому, что мы работаем на python, но хотим притвориться, что работаем на Java, а наш интерфейс требует, чтобы данные, передаваемые между модулями, имели тип массива. - person Peter; 23.07.2014
comment
@Peter: YUV не такой уж странный или необычный. Кроме того, кажется, что для вас было бы лучше, если бы ImageWrapper мог содержать массив ›3D, и в этом случае вызов методов на нем эффективно транслировал бы вызов по каждому подмассиву 3D, а не заставлял вас выполнять итерацию над каждым 3D-подмассивом вручную. (Это то, что я предлагал в своем комментарии, и я почти уверен, что Би Рико думал в своем ответе.) И наоборот, если вы действительно хотите заставить себя перебирать массивы, вам на самом деле лучше со списком их, чем массив. - person abarnert; 23.07.2014

numpy.array просто недостаточно сложен, чтобы справиться с этим случаем. subok=True указывает функции проходить через подклассы, но вы не передаете ей подкласс ndarray, вы передаете ей список (который оказывается заполненным экземплярами подкласса ndarray). Вы можете получить что-то вроде того, что вы ожидаете, сделав это:

import numpy as np


class ImageWrapper(np.ndarray):

    def __new__(cls, image_data):
        assert 2 <= image_data.ndim <= 4
        return image_data.view(cls)

    @property
    def n_colours(self): 
        return 1 if self.ndim==2 else self.shape[-1]


n_frames = 10
frame_shape = (20, 15, 3)
video = ImageWrapper(np.empty((n_frames,) + frame_shape, dtype='uint8'))
for i in xrange(n_frames):
    video[i] = np.random.randint(255, size=(20, 15, 3))
assert video[0].n_colours == 3

Обратите внимание, что мне пришлось обновить ImageWrapper, чтобы в качестве входных данных можно было использовать массивы 4d.

person Bi Rico    schedule 22.07.2014