Home > Net >  How to convert Python float array using ctypes?
How to convert Python float array using ctypes?

Time:01-06

I have neither experience in C nor in the ctypes library.

I have a program written in C with a DLL called image_lib. In the DLL header, the following function is defined

int Generate_Hologram(unsigned char *Array, unsigned char* WFC, float *x_spots, float *y_spots, float *z_spots, float *I_spots, int N_spots, int ApplyAffine);

In Python, I have my array called x_spots and some other arrays:

# coordinate locations
x_spots = np.arange(-1, 2, 1, dtype=float)
x_spots = np.arange(-1, 2, 1, dtype=float)
z_spots = np.arange([0] * num_tweezers.value, dtype=float)

# Intensities
int_spots = np.arange([1] * num_tweezers.value, dtype=float)

# Image for the GPU to compute the hologram on
Image = np.empty([width.value*height.value*bytpesPerPixel], np.uint8, 'C');

# Create a blank vector to hold the wavefront correction
WFC = np.empty([width.value*height.value*bytpesPerPixel], np.uint8, 'C');

I have (based on the single example the manufacturer gives)

image_lib.Generate_Hologram(Image.ctpyes.data_as(POINTER(c_ubyte)), WFC.ctypes.data_as(POINTER(c_ubyte)), ....
                           )

How do I pass x_floats to this function? I guess

x_spots.ctypes.data_as(POINTER(c_float))

CodePudding user response:

One problem I can spot in your code is the size of your floats.

Note that it doesn't really matter as a pointer to what you pass pointers to your numpy data to your function. I, for one, am used to pass data as arr.ctypes.data_as(ctypes.c_void_p).

This is dynamic, so it is not like C compiler could check anything. And, at least in this case, there is no dynamic checking neither that in makes senses to consider this numpy array data as a pointer to this type.

So, saying correctly or not as POINTER to what you want to pass a numpy array to a function that expect a float * doesn't protect you against error.

It doesn't neither imply any conversion. The pointer is just passed to the C function, it is up to it to interpret correctly, and that has been done statically, when you typed the parameter float *.

So, it is not there (in the code you seem to be wondering about) that you can ensure that the C function get the bunch of float it expect. Whatever, the C function will get a pointer to the numpy data.

The problem you have in your code is that data in x_spots are not float (as in the C meaning of the word float; that is 32 bits floating point numbers). They are most likely double

I say "most likely" because I am not an expert in different python interpreters. I know that, depending on the interpreter, native float type of python may be float32, or float64 or even something else. In the most classical CPython, they are float64. And I am not sure neither what it means for numpy when dtypes is the native type float. But well, with my cpython, a np.array([1,2,3], dtype=float) is a numpy array made of float64. So, not 100% sure if that is a sure thing, or if it exists python interpreter in which that array would be made of float32. It is because I am unsure of that, that, anyway, I never ever use float as an argument for dtype. I use np.float32 or np.float64.

But, well, most likely (or even surely) your x_spots is made of float64.

And then, it doesn't matter a pointer to what you say it is when you pass x_spots.ctypes.data_as(POINTER(...)), the pointer will be a pointer to those float64. That is, in C wording, to double. And your C function will treat it as a pointer to float (with no warning whatsoever: warning occur at compilation time, and we are past that).

So, long story. But conclusion is, either you

  • Change your C function to accept double * as parameter for this x_spots thing. That's probably the best way, with your, probably 64 bits computer. But you seem to say that you can't really modify the C function
  • So, alternatively, you can ensure that your data is made of float (in C meaning), that is create your x_spots with a dtype=np.float32.

Note that even if you choose the first case (double *), it would be better to change also dtype to explicitly set it to np.float64.

And once you have done that, it doesn't matter if you pass your argument as x_spots.ctypes.data_as(c_void_p), as x_spots.ctypes.data_as(POINTER(c_float)), as x_spots.ctypes.data_as(POINTER(c_double)), or even x_spots.ctypes.data_as(POINTER(c_char)).

(I strongly encourage you, of course, not to use a false type as POINTER() arg, but that is for human readability of your code; from execution change point of view, it wouldn't change the result)

So tl;dr

  • Change type of parameter of C function from float * to double *
  • Or, change dtype of your numpy arrays from float to np.float32

CodePudding user response:

Here is a minimal example to pass C float as a parameter using numpy. It is an exercise for the OP to apply it to their function. Note that the C function must assume a fixed size or be passed the length of the array in some form.

Also note that the dtype is declared as ct.c_float since Python float is typically 64-bit and C float is typically 32-bit. Make sure the type sizes agree.

test.c

#include <stdio.h>

__declspec(dllexport) // for Windows
void func(float* p, size_t size) {
    for(size_t i = 0; i < size;   i)
        printf("p[%zu] = %f\n", i, p[i]);
}

test.py

import ctypes as ct
import numpy as np

dll = ct.CDLL('./test')
# The helper function "ndpointer" can declare the expected type
# and either number of dimensions expected or the shape of the
# numpy array.  ctypes will then require that array and type check
# the parameter. 
dll.func.argtypes = np.ctypeslib.ndpointer(dtype=ct.c_float, ndim=1),
dll.func.restype = None

x_spots = np.arange(-1, 2, 1, dtype=ct.c_float)
dll.func(x_spots, len(x_spots))

Output:

p[0] = -1.000000
p[1] = 0.000000
p[2] = 1.000000
  • Related