检测python / cython类属性的部分更新

时间:2017-10-01 05:46:02

标签: python cython

以下是该方案:

作为我的2D渲染cython库的重构过程的一部分,我正在移除无关的" getter / setter"功能使其更加用户友好。我的渲染器执行的任务之一是在屏幕上显示图像。目前,我加载图像并使用一些SDL2_image函数提取其像素数据。然后,每当通过" setter"修改某些图像像素数据时函数,我会通知OpenGL需要将更新的数据从CPU复制到GPU。我的目标是使这个"簿记"通过将图像的像素暴露为可直接修改的属性并且具有CPU< =>来更隐蔽用户。 GPU同步发生在幕后。

当前代码:

以下是我的Image类的相关cython代码(仅包含实现.pyx文件的完整性且仅与切线相关):

image.pxd

from libs.sdl2 cimport *
from libc.stdint cimport uint8_t, uint32_t, int64_t
from libc.stdlib cimport malloc, free
from cpython cimport bool
from cython.view cimport array as cvarray

cdef class Image:
    cdef unsigned int[:, :, :] pixels
    cdef readonly int width
    cdef readonly int height

image.pyx

from libs.sdl2 cimport *
from libc.stdint cimport uint8_t, uint32_t, int64_t
from libc.stdlib cimport malloc, free
from cpython cimport bool
from cython.view cimport array as cvarray

cdef int rgba_size = 4

cdef class Image:

    def __cinit__(self, int width, int height, unsigned int[:, :, :] pixels=None):
        self.width = width
        self.height = height
        self.pixels = pixels

    def __dealloc__(Image self):
        pass

    @property
    def pixels(self):
        return self.pixels

    @pixels.setter
    def pixels(self, unsigned int[:, :, :] new_pixels):
        self.pixels = new_pixels
        print("updated")

    @staticmethod
    def from_url(str image_url):
        """
        1. Load in an image to an SDL_Surface using SDL_Image module
        2. Convert the image to the SDL_PIXELFORMAT_ABGR8888 format (right channels for OpenGL's RGBA)
        3. Flip the image as OpenGL spec states texture's BOTTOM-left = (0, 0)!
        """
        cdef Image out
        cdef char * err
        cdef bytes b_image_url = bytes(image_url, "utf-8")
        cdef SDL_Surface *surf = IMG_Load(b_image_url)
        if <int>surf == 0:
            err = IMG_GetError()
            raise ValueError(err)

        cdef SDL_Surface *rgba_surf = SDL_ConvertSurfaceFormat(surf, SDL_PIXELFORMAT_ABGR8888, 0)
        cdef int width = rgba_surf.w
        cdef int height = rgba_surf.h
        cdef uint32_t *pixel_data = <uint32_t *>rgba_surf.pixels

        cdef unsigned int[:, :, :] pixels
        pixels = cvarray(shape=(width, height, rgba_size), itemsize=sizeof(int), format="I")
        cdef int x, y, flipped = 0
        cdef uint32_t abgr = 0
        for y in xrange(height):
            for x in xrange(width):
                flipped = (height - 1 - y) * width + x
                abgr = pixel_data[flipped]
                pixels[x, y, 0] = abgr >> 0 & 0xFF
                pixels[x, y, 1] = abgr >> 8 & 0xFF
                pixels[x, y, 2] = abgr >> 16 & 0xFF
                pixels[x, y, 3] = abgr >> 24 & 0xFF

        SDL_FreeSurface(surf)
        SDL_FreeSurface(rgba_surf)
        out = Image(width, height, pixels=pixels)
        return out

这就是我尝试使用我的课程的方式:

from graphics.image import Image
a = Image.from_url("./images/crate.png")
a.pixels[0, 0, 0] = 255#please trigger something that says that pixels has been partially updated!

问题当然是因为只修改了pixels的单个值,所以不会触发@pixels.setter装饰器。触发它的唯一方法似乎是替换整个内存视图。

TL; DR:给定一个暴露的,可公开修改的类属性,它是一个list / array / np.arrray / memoryview /具有多个子值的东西,有没有办法透明地检测是否或不是说在该课程中属性已经部分更新了吗?

1 个答案:

答案 0 :(得分:2)

我认为你可以通过属性和上下文管理器的组合(对于with语句)获得合理有效的东西

首先保持你的getter和setter在问题中。修改你的getter,使它返回一个只读的numpy数组:

def writeable_pixels(self):
    class WriteablePixelsCtx:
        def __enter__(self2):
            return self._pixels # get the underlying (writeable) memoryview
        def __exit__(self2,*args):
            # update changes 
            # handle any exceptions appropriately (see the main Python documentation on __exit__ for this)
   return WriteablePixelsCtx()

因此不应该有任何“不受监控”的变化。设置器保持不变,只允许完全替换像素。

现在定义一个返回上下文管理器对象的函数:

with im.writeable_pixels() as pixels:
    pixels[i,j] = something

然后你可以这样做:

cdef

并且在阻止结束时,您可以通过退出一次有效地处理所有更改。

我喜欢这个:

  • 可能会在最后通过大量更新进行一系列小改动
  • 可以在Cython中有效地写入像素(这是一个内存视图,所以你只需pixels一个局部变量作为内存视图)

我不喜欢的是:

  • 两个不同的轴机制有点混乱
  • 在Cython中无法快速读取访问权限 - 内存浏览不适用于只读数组
  • 您可以设法在with块之后写入已保存的{{1}}对象。您可以通过添加额外的间接层来避免这种情况,但在Cython中会明显变慢。