pythonmultidimensional-arraycythonwrapper

Cython: Pass a 2D array from Python to a C and retrieve it


I am trying to build a wrapper to a camera driver in written C using Cython. I am new to Cython (started 2 weeks ago). After a lot of struggle, I could successfully develop wrappers for structures, 1D arrays but now I am stuck with 2D arrays.

One of the camera's C APIs takes a 2D array pointer as input and assigns the captured image to it. This function needs to be called from Python and the output image needs to be processed/displayed in Python. After going through the Cython docs and various posts on stack-overflow, I ended up with more confusion. I could not figure out how to pass 2D arrays between Python and the C. The driver api looks (somewhat) like this:

driver.h

void assign_values2D(double **matrix, unsigned int row_size, unsigned int column_size);

c_driver.pyd

cdef extern from "driver.h":
    void assign_values2D(double **matrix, unsigned int row_size, unsigned int column_size)

test.pyx

from c_driver import assign_values2D
import numpy as np
cimport numpy as np
cimport cython
from libc.stdlib cimport malloc, free
import ctypes

@cython.boundscheck(False)
@cython.wraparound(False)
def assignValues2D(self, np.ndarray[np.double_t,ndim=2,mode='c']mat):
    row_size,column_size = np.shape(mat)
    cdef np.ndarray[double, ndim=2, mode="c"] temp_mat = np.ascontiguousarray(mat, dtype = ctypes.c_double)
    cdef double ** mat_pointer = <double **>malloc(column_size * sizeof(double*))
    if not mat_pointer:
        raise MemoryError
    try:
        for i in range(row_size):
            mat_pointer[i] = &temp_mat[i, 0]

        assign_values2D(<double **> &mat_pointer[0], row_size, column_size)
        return np.array(mat)
    finally:
        free(mat_pointer)

test_camera.py

b = np.zeros((5,5), dtype=np.float) # sample code
print "B Before = "
print b
assignValues2D(b)
print "B After = "
print b

When compiled, it gives the error:

Error compiling Cython file:
------------------------------------------------------------
...
    if not mat_pointer:
        raise MemoryError
    try:
        for i in range(row_size):
            mat_pointer[i] = &temp_mat[i, 0]
                ^
 ------------------------------------------------------------
 test.pyx:120:21: Cannot take address of Python variable

In fact, the above code was taken from a stack-overflow post. I have tried several other ways but none of them are working. Please let me know how I can get the 2D image into Python. Thanks in advance.


Solution

  • You need to type i:

    cdef int i
    

    (Alternatively you can type row_size and it also works)

    Once it knows that i is an int then it can work out the type that indexing the tmp_map gives and so the & operator works.


    Normally it's pretty good about figuring out the type of loop variables like i, but I think the issue is that it can't deduce the type of row_size so it decided it can't deduce the type of i since it is deduced from range(row_size). Because of that it can't deduce the type of temp_mat[i,0].


    I suspect you also you also want to change the return statement to return np.array(temp_mat) - the code you have will likely work most of the time but occasionally np.ascontinuousarray will have to make a copy and mat won't be changed.