2

I'm trying to pass a C struct from C++ into python as a numpy array using boost::python. I know there are simplier ways to pass C structs to python, but the reason I want to use numpy arrays is that solution is part of a reflection model, so the data type needs to be generated dynamically from a string.

I can serialize the struct passing it to python as a string and call numpy.fromstring() in python which successfully converts it into a numpy array. However, this is not very efficient because converting it to a string copies the data instead of passing it to python by reference.

For example:

#include <boost/python.hpp>

using namespace boost::python;

struct MyRecord
{
    uint32_t myInt;
    char myString[4];
    double myDouble;
};

class MyBaseClass
    :   public wrapper<MyBaseClass>
{
public:
    void myCallback(const MyRecord& data)
    {
        object func = get_override("myCallback");
        if (func) {
            std::string dataStr(reinterpret_cast<const char*>(data), sizeof(data));
            func(dataStr, "[('myInt','<u4'),('myString','|S4'),('myDouble','<f8')]");
        }
    }
};

BOOST_PYTHON_MODULE(example1)
{
    class_<MyBaseClass>("MyBaseClass")
        .def("myCallback", &MyBaseClass::myCallback);
}

#!/usr/bin/env python

import numpy
from example1 import MyBaseClass

class MyClass(MyBaseClass):
    def myCallback(self, dataStr, dtypeStr):
        dt = numpy.dtype(eval(dtypeStr))
        data = numpy.fromstring(dataStr, dt)
        print data # This is now a numpy array

What I'd really like to be able to do is to convert the struct to a numpy array in C++ and pass it by reference directly to python.

I've experimented with the boost::python::numeric::array class, but I'm having problems getting it to convert the C++ type into an numpy array. The constructor is throwing: "TypeError: No to_python (by-value) converter found for C++ type: MyRecord"

There some example code:

#include <boost/python.hpp>
#include <boost/python/exec.hpp>
#include <boost/python/numeric.hpp>

using namespace boost::python;

struct MyRecord
{
    uint32_t myInt;
    char myString[4];
    double myDouble;
};

class MyBaseClass
    :   public wrapper<MyBaseClass>
{
public:
    void myCallback(const MyRecord& data)
    {
        object func = get_override("myCallback");
        if (func) {
            object dtype = exec("eval(\"[('myInt','<u4'),('myString','|S4'),('myDouble','<f8')]\")");
            func(numeric::array(data, dtype));  // numeric::array throws
        }
    }
};

BOOST_PYTHON_MODULE(example2)
{
    class_<MyBaseClass>("MyBaseClass")
        .def("myCallback", &MyBaseClass::myCallback);
}

#!/usr/bin/env python

import numpy
from example2 import MyBaseClass

class MyClass(MyBaseClass):
    def myCallback(self, data):
        print data # This is a numpy array passed from C++

Thanks,

Paul

Black Box
  • 61
  • 5

1 Answers1

3

Ok, I've managed to answer my own question. It wasn't straight-forward, but it works now...

#include <numpy/arrayobject.h>

void MyBaseClass::myCallback(const MyRecord& data)
{
    object func = get_override("myCallback");
    if (func) {
        PyArray_Descr* dtype;
        PyObject* op = Py_BuildValue("[(s,s),(s,s),(s,s)]", "myInt", "<u4", "myString", "|S4", "myDouble", "<f8");
        PyArray_DescrConverter(op, &dtype);
        Py_DECREF(op);
        PyObject* pya = PyArray_FromString(const_cast<char*>(reinterpret_cast<const char*>(&data)), sizeof(data), dtype, 1, NULL);
        // PyObject_Print(pya, stdout, 0);
        numeric::array bpa(static_cast<numeric::array>(handle<>(pya)));
        func(bpa);
    }
}

BOOST_PYTHON_MODULE(example3)
{
    import_array()
    class_<MyBaseClass>("MyBaseClass")
        .def("myCallback", &MyBaseClass::myCallback);
}

Thanks again,

Paul

Black Box
  • 61
  • 5