How to convert raw pointers to lightweight python datatype using pybind11?
Asked Answered
I

1

10

Consider this little pybind11 wrapper + test:

setup.py

from pybind11.setup_helpers import Pybind11Extension
from pybind11.setup_helpers import build_ext
from setuptools import setup

setup(
    name="wrapper",
    ext_modules=[Pybind11Extension("wrapper", ["wrapper.cpp"])],
    cmdclass={"build_ext": build_ext},
)

wrapper.cpp

#include <pybind11/pybind11.h>
#include <pybind11/stl.h>
#include <vector>

namespace py = pybind11;

struct Mesh2d {
public:
  Mesh2d(int numVerts, int numTris)
      : numVertices(numVerts), numTriangles(numTris) {
    vertices = new float[numVerts * 2];
    indices = new int[numTris * 3];
  }

  ~Mesh2d() {
    delete vertices;
    delete indices;
  }

  void makeVertex(int i, float x, float y) {
    vertices[i * 2 + 0] = x;
    vertices[i * 2 + 1] = y;
  }

  void makeTriangle(int i, int a, int b, int c) {
    indices[i * 3 + 0] = a;
    indices[i * 3 + 1] = b;
    indices[i * 3 + 2] = c;
  }

  float *vertices;
  int *indices;
  int numVertices;
  int numTriangles;
};

PYBIND11_MODULE(wrapper, m) {
  m.doc() = "mcve";
  py::class_<Mesh2d>(m, "Mesh2d")
      .def(py::init<int, int>())
      .def("make_vertex", &Mesh2d::makeVertex)
      .def("make_triangle", &Mesh2d::makeTriangle)
      .def_readonly("vertices", &Mesh2d::vertices)
      .def_readonly("indices", &Mesh2d::indices);
}

test.py

from wrapper import Mesh2d

def test():
    m = Mesh2d(3, 1)
    m.make_vertex(0, -1.0, -1.0)
    m.make_vertex(1, 1.0, -1.0)
    m.make_vertex(2, 0.0, 1.0)
    m.make_triangle(0, 0, 1, 2)
    print(m.vertices.__class__)
    print(m.indices.__class__)

test()

Question

Right now because I'm not doing anything special to neither vertices nor indices the __class__ I'm getting in python land is <class 'float'> and <class 'int'> respectively. What'd be the proper pybind11 way to convert these raw pointers to some suitable object such as:

array.array("f", vertices)
array.array("I", indices)

(ctypes.c_float * 6)(*vertices)
(ctypes.c_uint * 3)(*indices)

memoryview(struct.pack("6f", *vertices))
memoryview(struct.pack("3I", *indices))

struct.pack("6f", *vertices)
struct.pack("3I", *indices)

My end goal here would be being able to generate really complex heavy data on c++ side (heavy data that will be modified in realtime). So having little overhead on the wrapper (ideally without any unnecesary copy to python data structures, just giving the raw pointer).

Reference

https://pybind11.readthedocs.io/en/stable/advanced/functions.html#return-value-policies

Imprudent answered 8/5, 2021 at 16:4 Comment(1)
pybind11.readthedocs.io/en/stable/advanced/pycpp/numpy.html# -- the memory view approach at the bottom is probably the lightest. Or turn them into full numpy arrays like i've done here for OpenCV Mats.Masthead
U
4

If I'm not mistaken, you should be able to use a numpy array view of the memory for pretty cheap (if not almost no overhead). Here's a full example.

#include <pybind11/pybind11.h>
#include <pybind11/numpy.h>
#include <pybind11/stl.h>
#include <vector>

namespace py = pybind11;

struct Mesh2d {
public:
  Mesh2d(int numVerts, int numTris)
      : numVertices(numVerts), numTriangles(numTris) {
    vertices = new float[numVerts * 2];
    indices = new int[numTris * 3];

    // these constructors are just views of the memory and will not copy the data
    // (nor take ownership of the pointer)
    // You could also make these shapes {numVerts, 2} and {numTris, 3} or whatever else is helpful to you.
    pyVertices = py::array_t<float>({numVerts * 2}, vertices);
    pyIndices = py::array_t<int>({numTris * 3}, indices);
  }

  ~Mesh2d() {
    delete vertices;
    delete indices;
  }

  void makeVertex(int i, float x, float y) {
    vertices[i * 2 + 0] = x;
    vertices[i * 2 + 1] = y;
  }

  void makeTriangle(int i, int a, int b, int c) {
    indices[i * 3 + 0] = a;
    indices[i * 3 + 1] = b;
    indices[i * 3 + 2] = c;
  }

  float *vertices;
  int *indices;
  py::array_t<float> pyVertices;
  py::array_t<int> pyIndices;
  int numVertices;
  int numTriangles;
};

PYBIND11_MODULE(wrapper, m) {
  m.doc() = "mcve";
  py::class_<Mesh2d>(m, "Mesh2d")
      .def(py::init<int, int>())
      .def("make_vertex", &Mesh2d::makeVertex)
      .def("make_triangle", &Mesh2d::makeTriangle)
      .def_readonly("vertices", &Mesh2d::pyVertices)
      .def_readonly("indices", &Mesh2d::pyIndices);
}

The constructor being used for py::array_t is here. I would also point you to xtensor if you're looking for something numpy like in C++.

Unreserved answered 3/11, 2021 at 1:17 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.