How to pass a Numpy array into a cffi function and how to get one back out?

Question:

I am developing an audio algorithm using Python and Numpy. Now I want to speed up that algorithm by implementing a part of it in C. In the past, I have done this using cython. Now I want to do the same thing using the new cffi.

For testing purposes, I wrote a trivial C function:

void copy(float *in, float *out, int len) {
    for (int i=0; i<len; i++) {
        out[i] = in[i];
    }
}

Now I want to create two numpy arrays and have those be processed by this function.
I figured out a way to do that:

import numpy as np
from cffi import FFI

ffi = FFI()
ffi.cdef("void copy(float *in, float *out, int len);")
C = ffi.dlopen("/path/to/copy.dll")

float_in = ffi.new("float[16]")
float_out = ffi.new("float[16]")

arr_in = 42*np.ones(16, dtype=np.float32)

float_in[0:16] = arr_in[0:16]
C.copy(float_in, float_out, 16)
arr_out = np.frombuffer(ffi.buffer(float_out, 16*4), dtype=np.float32)

However, I would like to improve this code:

  1. Is there a way to directly access the underlying float buffers of the numpy arrays without copying them?
  2. ffi.buffer is very convenient for quickly converting to contents of a C array to a Numpy array. Is there an equivalent way for quickly converting a numpy array into a C array without copying the individual elements?
  3. For some applications, float_in[0:16] = arr_in[0:16] is a convenient way of accessing data. The opposite, arr_out[0:16] = float_out[0:16] does not work however. Why not?
Asked By: bastibe

||

Answers:

The ctypes attribute of ndarray can interact with the ctypes module, for example, ndarray.ctypes.data is the data address of the array, you can cast it to a float * pointer,
and then pass the pointer to the C function.

import numpy as np
from cffi import FFI

ffi = FFI()
ffi.cdef("void copy(float *in, float *out, int len);")
C = ffi.dlopen("ccode.dll")

a = 42*np.ones(16, dtype=np.float32)
b = np.zeros_like(a)
pa = ffi.cast("float *", a.ctypes.data)
pb = ffi.cast("float *", b.ctypes.data)

C.copy(pa, pb, len(a))
print b

For your question 3:

I think ffi array doesn’t provide numpy the necessary information to access it’s inner buffer. So numpy try to convert it to a float number which failed.

The best solution I can thinks is convert it to list first:

float_in[0:16] = list(arr_in[0:16])
Answered By: HYRY

the data in a numpy array can be accessed via it’s array interface:

import numpy as np
import cffi
ffi = cffi.FFI()

a = np.zeros(42)
data = a.__array_interface__['data'][0]
cptr = ffi.cast ( "double*" , data )

now you have a cffi pointer type, which you can pass to your copy routine. note that this is a basic approach; numpy arrays may not contain their data in flat memory, so if your ndarray is structured, you will have to consider it’s shape and strides. If it’s all flat, though, this is sufficient.

Answered By: Kay F. Jahnke

An update to this: modern versions of CFFI have ffi.from_buffer(), which turns any buffer object (like a numpy array) to a char * FFI pointer. You can now do directly:

cptr = ffi.cast("float *", ffi.from_buffer(my_np_array))

or directly as arguments to the call (the char * is casted automatically to float *):

C.copy(ffi.from_buffer(arr_in), ffi.from_buffer(arr_out), 16)
Answered By: Armin Rigo

After you got a flat result array from cffi,
you also could reshape the array with given strides via numpy like this:

a=np.ones(24); a.shape = (2, 3, 4)

or

a=np.ones(24); b = a.reshape(2, 3, 4)

This is for example helpful if you want to have nested lists for further python processing (like in blenders sverchok addon)

More complex example:

Say you want to have a list of lists of vertices with 3 floats each, and have created a cdata float array like this:

 cverts = ffi.new("float [][3]", nverts * num)

as output parameter for a function like:

lib.myfunction(... other input...., num, nverts, cverts)

Cutting this list of verts into num sub-lists of nverts verts each you could do as follows then:

flat_size = 4 * 3 * nverts * num
verts = np.frombuffer(ffi.buffer(cverts, flat_size), dtype=np.float32)
verts.shape = (num, nverts, 3)
verts = verts.tolist()

verts should look for example like [[[1,2,3],[4,5,6]],[[7,8,9],[10,11,12]]] then.

Answered By: scorpion81

Since CFFI version 1.12, you can create an appropriately typed pointer to a NumPy array with a single call to FFI.from_buffer:

array = np.zeros(16, dtype=np.float32)
pointer = ffi.from_buffer("float[]", array)

C code that writes to the array behind this pointer will directly mutate the original NumPy array. There is no need to "get the result out".

You may want to call numpy.ascontiguousarray before passing it to the buffer if there is a chance the array does not have a C_CONTIGUOUS memory layout.

Answered By: drhagen
Categories: questions Tags: , , ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.