Как сохранить логическую маску в качестве атрибута класса Cython? - PullRequest
3 голосов
/ 06 октября 2019

Недавно мне не удалось сохранить логическую маску сохранения в качестве атрибута класса Cython. В реальном коде мне нужна эта маска для более эффективного выполнения задач. Ниже приведен пример кода:

core.pyx

import numpy as np
cimport numpy as np

cdef class MyClass():
    cdef public np.uint8_t[:] mask # uint8 has the same data structure of a boolean array
    cdef public np.float64_t[:] data

    def __init__(self, size):
        self.data = np.random.rand(size).astype(np.float64)
        self.mask = np.zeros(size, np.uint8)

script.py

import numpy as np
import pyximport
pyximport.install(setup_args={'include_dirs': np.get_include()})

from core import MyClass

mc = MyClass(1000000)
mc.mask = np.asarray(mc.data) > 0.5 

Ошибка

Когда я запускаю script.pyон успешно компилирует Cython, но выдает ошибку:

Traceback (most recent call last):
  File "script.py", line 8, in <module>
    mc.mask = np.asarray(mc.data) > 0.5
  File "core.pyx", line 6, in core.MyClass.mask.__set__
    cdef public np.uint8_t[:] mask
ValueError: Does not understand character buffer dtype format string ('?')

Обходной путь

Мой текущий обходной путь - передать маску всем необходимым функциям, используя cast=True, например:

cpdef func(MyClass mc, np.ndarray[np.uint8_t, ndim=1, cast=True] mask):
    return np.asarray(mc.data)[mask]

Вопрос

Есть ли какие-либо идеи о том, как маска может храниться в классе Cython?

Ответы [ 2 ]

2 голосов
/ 06 октября 2019

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

np.asarray(mc.data)[mask]
# or
mc.data.base[mask] # if you're sure it's always a view of something that supports boolean indexing)

Я не думаю, что это изменится с обновлением Cython, которое упоминает @ead. Я подозреваю, что причина этого в том, что, вероятно, довольно легко выполнить присваивание (mc.data[mask] = x), но не очевидно, какой тип должен возвращаться mc.data[mask] - это не просмотр памяти.

Поэтому, что бы вы ни делали, это будет связано с каким-то грязным кодом.


Для части Назначения для просмотра памяти можно выполнить с помощью

mc.mask = (np.asarray(mc.data) > 0.5).view(np.uint8)

и вернуть его Numpy. массив bool с:

np.asarray(mc.mask).view(np.bool)

ни один из которых не должен включать в себя копирование.


Если бы я это проектировал, я бы оставил просмотры памяти закрытыми (для использования только на Cython)) и имеют обычные атрибуты объекта, которые просто содержат базовые массивы Numpy для интерфейса Python. Вы могли бы использовать property, чтобы поддерживать их в синхронизации (и выполнять приведение):

cdef class MyClass:
    cdef np.uint8_t[:] mask_mview
    cdef object _mask

    @property
    def mask(self):
        return np.asarray(self._mask).view(np.bool)

    @mask.setter
    def mask(self, value):
        self._mask = value
        self.mask_view = value.view(np.uint8)

    # and the same for data

Таким образом, у вас есть представление о памяти, которое можно использовать для вещей, которые хорошо подходят для представлений о памяти (итерируя быстро по элементамэлемент в Cython), доступ к простому массиву Numpy для Python, и эти два синхронизируются (по крайней мере, через интерфейс Python).

1 голос
/ 06 октября 2019

Лучшим вариантом (если вы не хотите использовать обходной путь), вероятно, является ожидание выпуска Cython 0.29.14. Этот сбой был исправлен и, вероятно, будет частью 0.29.14 .

Следующий минимальный пример

%%cython
import numpy as np
cimport numpy as np
cdef np.uint8_t[:] mask  = np.random.rand(20)>.5

не удастся импортировать собычное

ValueError: Не понимает строку формата dtype буфера символов ('?')

для Cython 0.29.13, но работает с текущим состоянием с 0.29.x-ветвь на github (или master).

...