C++ - Boost - Converting std::vector to Boost.Python Numpy ndarray

You have a C++ std::vector and you want to convert it to a Boost.Python Numpy ndarray.

But, once the ndarray got, you want to get back to the C++ array.

How to do that?

Let's see that in this Boost.Python tutorial.

First of all

We need to install Python 3 and Boost on your computer.

So in order to have the exact same software and libraries installed in the exact same locations, I suggest to follow the 2 following tutorials:

  1. Pythonhttps://www.badprog.com/python-3-installing-on-windows-10
  2. Boosthttps://www.badprog.com/c-boost-setting-up-on-windows-10

C++, std::vector, Boost.Python, Numpy and ndarray

From C++ array to ndarray

In order to use data, from a standard C++ vector to a Numpy ndarray, we need to get the address of the first element in this vector.

The reason is that we are going to use the from_data() method to transform our C++ vector to a Python array.

And this from_data() method needs the address of the first element of an array.

So it would have been possible to use the following: 

  • &originalVector[0]

But we will prefer using:

  • originalVector.data()

Once we have our C++ array, it's quite easy to convert it into a Numpy ndarray.

Shape and strides

In the from_data() method we'll set the 

  • shape with : python::make_tuple(1, sizeOfVector)
  • strides with: python::make_tuple(sizeof(float) * 0sizeof(float) * 1)

In this example:

  • 1 = shape(0), the number of rows;
  • sizeOfVector = shape(1), the number of columns.

Then

  • sizeof(float) * 0 = strides(0) = the number of element to jump from a row to the next one
  • sizeof(float) * 1 = strides(1) = the number of element to jump from a column to the next one

So to get the number of elements in the ndarray we can use shape(1) because it holds the size of the vector.

From ndarray to C++ array

Once we get an ndarray, if we want to come back to a C++ array, we have to use the reinterpret_cast expression.

Indeed, the ndarray::get_data() method returns a char*.

So we use this mechanism to tranform this char* into the type of our choice.

In our case into a float*.

Then with this float* we can do whatever we want.

In the example we modify data from the C++ array by dividing all elements by 10.

And as the ndarray passed as parameter is a pointer, so the original ndarray is also modified.

Checking with contains()

In bonus, we check with the Boost.Python contains() method if a float is present in the final array.

Let's code a bit

// badprog.com
#include "pch.h"

//
#include <boost/python/numpy.hpp>

//
#include <iostream>
#include <string>

//
namespace python  = boost::python;
namespace numpy   = boost::python::numpy;

// ----------------------------------------------------------------------------
//
// ----------------------------------------------------------------------------
void changeArrayOriginalFromPyArray(numpy::ndarray *py_array) {
  //
  float *arrayData = reinterpret_cast<float *>(py_array->get_data());
  int i = 0;

  //
  arrayData[2] = 1337.4;

  // we need shape(1) because this returns the number of elements
  while (i < py_array->shape(1)) {
    //
    arrayData[i] /= 10;
    ++i;
  }
}

// ----------------------------------------------------------------------------
//
// ----------------------------------------------------------------------------
void displayOriginalVector(std::vector<float&originalVector) {
  //
  for (auto &element : originalVector) {
    //
    std::cout << element << ' ';
  }

  //
  std::cout << std::endl;
}

// ----------------------------------------------------------------------------
//
// ----------------------------------------------------------------------------
void displayPyArray(numpy::ndarray &py_array) {
  //
  std::cout << "py_array: " << python::extract<char const *>(python::str(py_array)) << std::endl;
}

// ----------------------------------------------------------------------------
//
// ----------------------------------------------------------------------------
int main() {
  //
  Py_Initialize();
  numpy::initialize();

  //
  std::vector<float> originalVector = { 1.52.53.54.55.5 };
  int sizeOfVector = originalVector.size();
  float *originalArray = originalVector.data();

  //
  std::cout << "sizeOfArray = " << sizeOfVector << std::endl;

  //
  numpy::dtype arrayType = numpy::dtype::get_builtin<float>();

  //
  numpy::ndarray py_array = numpy::from_data(
    originalArray,
    arrayType,
    python::make_tuple(1, sizeOfVector),
    python::make_tuple(sizeof(float) * 0sizeof(float) * 1),
    python::object()
  );

  //
  // 1 -------------------------------------------------------------------------
  //
  std::cout << "C++ array: ";

  //
  displayOriginalVector(originalVector);

  //
  displayPyArray(py_array);

  //
  changeArrayOriginalFromPyArray(&py_array);

  //
  // 2 - Modifying py_array
  //
  std::cout << "C++ array: ";

  //
  displayOriginalVector(originalVector);

  //
  displayPyArray(py_array);

  //
  // 3 - Modifying originalVector
  //
  originalVector.at(3) = 911;

  //
  std::cout << "C++ array: ";

  //
  displayOriginalVector(originalVector);

  //
  displayPyArray(py_array);

  //
  // 4 - Containing
  //
  float valueToFind = 1.5 / 10;

  //
  std::cout << "py_array: " << python::extract<char const *>(python::str(py_array.contains(1337))) << std::endl;
  std::cout << "py_array: " << python::extract<char const *>(python::str(py_array.contains(valueToFind))) << std::endl;

  //
  return 0;
}

 

Build, link and run

As a result you'll have from the console:

sizeOfArray = 5

C++ array: 1.5 2.5 3.5 4.5 5.5

py_array: [[1.5 2.5 3.5 4.5 5.5]]

C++ array: 0.15 0.25 133.74 0.45 0.55

py_array: [[  0.15   0.25 133.74   0.45   0.55]]

C++ array: 0.15 0.25 133.74 911 0.55

py_array: [[1.5000e-01 2.5000e-01 1.3374e+02 9.1100e+02 5.5000e-01]]

py_array: False

py_array: True

Conclusion

You are now able to manipulate C++ vector, array and Numpy ndarray like if they were the same object.

Good job, you did it. laugh

 

Add new comment

Plain text

  • No HTML tags allowed.
  • Lines and paragraphs break automatically.