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:
- Python:
- Boost:
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) * 0, sizeof(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
#include
//
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
//
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
int sizeOfVector = originalVector.size();
float *originalArray = originalVector.data();
//
std::cout « “sizeOfArray = " « sizeOfVector « std::endl;
//
numpy::dtype arrayType = numpy::dtype::get_builtin
//
numpy::ndarray py_array = numpy::from_data(
originalArray,
arrayType,
python::make_tuple(1, sizeOfVector),
python::make_tuple(sizeof(float) * 0, sizeof(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.