Steve O'Neill
Steve O'Neill

Reputation: 95

Cython fused dtype memoryview won't complie

I have some code where I (am trying to) do roughly the following:

ctypedef fused Raster_t:
    numpy.uint8_t
    numpy.uint16_t

cdef class MyClass:
    
    # shape is (rows, cols, channels)
    cdef Raster_t[:,:,:] the_raster

    def __init__(self, raster):
        self.the_raster = raster
        self.dtype = raster.dtype

    def do_some_work(self):

        cdef Raster_t[:,:,:] out_raster_memview        

        out_raster = numpy.empty((some_rows, some_cols, some_channels), dtype=self.dtype)
        
        out_raster_memview = out_raster

        #do cool stuff with values from self.the_raster
        #and write values of the same type into out_raster_memview

        #Most basic usage would be something like:
        out_raster_memview[0,0,0] = self.the_raster[0,0,0]
  

But I can't even get something like this to compile. I've tried adding a dummy variable to the do_some_work function like:

def do_some_work(self):
    self._do_some_work(0)

def _do_some_work(self, Raster_t dummy)

to try to get Cython to accept the thing as suggested here: Cython caching with fused type

I've tried moving the local cdef into a class member, cdef/un-cdefing things, and a bazillion little tweaks but all I get are vague compile errors like

(tree fragment):16:27: Syntax Error in C variable definition

Or things like

AttributeError: 'MemoryViewSliceType' object has no attribute 'dtype_name'

like: https://github.com/cython/cython/issues/1605

But adding 'const' doesn't seem to help either.

So I guess the question: How do I get the fused dtype to evaluate at runtime on an input numpy array?

################################################## Edit1:

Thanks to yut23, I've converted the class-attribute part of this problem into a local problem like the following:

ctypedef fused Raster_t
    numpy.uint8_t
    numpy.uint16_t

cdef class MyClass:
    cdef object the_raster

    def __init__(self, raster):
        self.the_raster = raster
        self.dtype = raster.dtype

    def do_some_work(self):
        
        cdef Raster_t[:,:,:] out_raster_memview
        cdef Raster_t[:,:,:] in_raster_memview

        out_raster = numpy.empty((rows, cols, channels), dtype=self.dtype)

        out_raster_memview = out_raster
        in_raster_memview = self.the_raster

Now the problem becomes:

out_raster_memview = out_raster
                    ^
------------------------------------------
myfile:lineno:Cannot coerce to a type that is not specialized

How does one specialize the 'out_raster'? 'out_raster' is a python object/numpy array which I thought has full runtime-available typing info. It seems like this code should fail if I used the wrong dtype at runtime (maybe not crash but produce other bugs due to wrong buffer length/striding/etc), like if I used numpy.int64_t which isn't included in my 'Raster_t'. But I'm confused as to why this doesn't work if I provide a uint8 or uint16 array?

####################################### Edit2:

My guess is (Edit3: My guess was slightly wrong - perhaps the more accurate information would be from DavidW's comment - "fused types need to be deducible from the function arguments") that Cython cannot handle the actual type checking between a fused type and the actual python/numpy array, so I reorganized the code to look like so:

def do_some_work(self):
    cdef numpy.uint8_t[:,:,:] raster_memview_uint8
    cdef numpy.uint8_t[:,:,:] out_raster_memview_uint8
    cdef numpy.uint16_t[:,:,:] raster_memview_uint16
    cdef numpy.uint16_t[:,:,:] out_raster_memview_uint16
    
    if self.dtype == numpy.uint8:
        self._do_some_work(raster_memview_uint8, out_raster_memview_uint8)
    if self.dtype == numpy.uint16:
        self._do_some_work(raster_memview_uint16, out_raster_memview_uint16)

def _do_some_work(self, 
                  Raster_t[:,:,:] out_raster_memview,
                  Raster_t[:,:,:] raster_memview):
    # whatever complex stuff I was going to do in the body

I think this mostly solves the problem, but this still seems like a workaround with what seems like unnecessary code duplication still. On the bright side, at least I don't have to duplicate the body of "_do_some_work()", which is the real hard part and its just some boilerplate for every new type in the selection part.

I don't know the right protocol, but if the StackOverflow gurus dont mind I'd like to leave this open maybe for some Cython guru to come along and tell me if there is a better/cleaner way? This seems like maybe an 80% solution.

Upvotes: 1

Views: 87

Answers (1)

yut23
yut23

Reputation: 3064

As per the fused types docs, fused types aren't supported as attributes of extension types (aka cdef classes) as of the current version (3.1.0a0). There's already an issue on the Cython github repo requesting this feature: https://github.com/cython/cython/issues/3283.

Upvotes: 3

Related Questions