335 lines
9.5 KiB
C++
Executable File
335 lines
9.5 KiB
C++
Executable File
/**
|
|
* @file NumpyEigenConverter.hpp
|
|
* @author Paul Furgale <paul.furgale@utoronto.ca>
|
|
* @date Fri Feb 4 11:17:25 2011
|
|
*
|
|
* @brief Classes to support conversion from numpy arrays in Python
|
|
* to Eigen3 matrices in c++
|
|
*
|
|
*
|
|
*/
|
|
|
|
#ifndef NUMPY_EIGEN_CONVERTER_HPP
|
|
#define NUMPY_EIGEN_CONVERTER_HPP
|
|
|
|
#include <numpy_eigen/boost_python_headers.hpp>
|
|
//#include <iostream>
|
|
|
|
#include "numpy/numpyconfig.h"
|
|
#ifdef NPY_1_7_API_VERSION
|
|
#define NPY_NO_DEPRECATED_API NPY_1_7_API_VERSION
|
|
#define NPE_PY_ARRAY_OBJECT PyArrayObject
|
|
#else
|
|
//TODO Remove this as soon as support for Numpy version before 1.7 is dropped
|
|
#define NPE_PY_ARRAY_OBJECT PyObject
|
|
#endif
|
|
|
|
#define PY_ARRAY_UNIQUE_SYMBOL NP_Eigen_AS
|
|
#include <numpy/arrayobject.h>
|
|
|
|
#include "type_traits.hpp"
|
|
#include <boost/lexical_cast.hpp>
|
|
#include "copy_routines.hpp"
|
|
|
|
|
|
|
|
/**
|
|
* @class NumpyEigenConverter
|
|
* @tparam the Eigen3 matrix type this class is specialized for
|
|
*
|
|
* adapted from http://misspent.wordpress.com/2009/09/27/how-to-write-boost-python-converters/
|
|
* General help available http://docs.scipy.org/doc/numpy/reference/c-api.array.html
|
|
*
|
|
* To use:
|
|
*
|
|
* #include <NumpyEigenConverter.hpp>
|
|
*
|
|
*
|
|
* BOOST_PYTHON_MODULE(libmy_module_python)
|
|
* {
|
|
* // The converters will cause a segfault unless import_array() is called before the first one
|
|
* import_array();
|
|
* NumpyEigenConverter<Eigen::Matrix< double, 1, 1 > >::register_converter();
|
|
* NumpyEigenConverter<Eigen::Matrix< double, 2, 1 > >::register_converter();
|
|
* }
|
|
*
|
|
*/
|
|
template<typename EIGEN_MATRIX_T>
|
|
struct NumpyEigenConverter
|
|
{
|
|
|
|
typedef EIGEN_MATRIX_T matrix_t;
|
|
typedef typename matrix_t::Scalar scalar_t;
|
|
|
|
enum {
|
|
RowsAtCompileTime = matrix_t::RowsAtCompileTime,
|
|
ColsAtCompileTime = matrix_t::ColsAtCompileTime,
|
|
MaxRowsAtCompileTime = matrix_t::MaxRowsAtCompileTime,
|
|
MaxColsAtCompileTime = matrix_t::MaxColsAtCompileTime,
|
|
NpyType = TypeToNumPy<scalar_t>::NpyType,
|
|
//Flags = ei_compute_matrix_flags<_Scalar, _Rows, _Cols, _Options, _MaxRows, _MaxCols>::ret,
|
|
//CoeffReadCost = NumTraits<Scalar>::ReadCost,
|
|
Options = matrix_t::Options
|
|
//InnerStrideAtCompileTime = 1,
|
|
//OuterStrideAtCompileTime = (Options&RowMajor) ? ColsAtCompileTime : RowsAtCompileTime
|
|
};
|
|
|
|
static std::string castSizeOption(int option)
|
|
{
|
|
if(option == Eigen::Dynamic)
|
|
return "Dynamic";
|
|
else
|
|
return boost::lexical_cast<std::string>(option);
|
|
}
|
|
|
|
static std::string toString()
|
|
{
|
|
return std::string() + "Eigen::Matrix<" + TypeToNumPy<scalar_t>::typeString() + ", " +
|
|
castSizeOption(RowsAtCompileTime) + ", " +
|
|
castSizeOption(ColsAtCompileTime) + ", " +
|
|
boost::lexical_cast<std::string>((int)Options) + ", " +
|
|
castSizeOption(MaxRowsAtCompileTime) + ", " +
|
|
castSizeOption(MaxColsAtCompileTime) + ">";
|
|
}
|
|
|
|
// The "Convert from C to Python" API
|
|
static PyObject * convert(const matrix_t & M)
|
|
{
|
|
PyObject * P = NULL;
|
|
if(RowsAtCompileTime == 1 || ColsAtCompileTime == 1)
|
|
{
|
|
// Create a 1D array
|
|
npy_intp dimensions[1];
|
|
dimensions[0] = M.size();
|
|
P = PyArray_SimpleNew(1, dimensions, TypeToNumPy<scalar_t>::NpyType);
|
|
numpyTypeDemuxer< CopyEigenToNumpyVector<const matrix_t> >(&M, reinterpret_cast<NPE_PY_ARRAY_OBJECT*>(P));
|
|
}
|
|
else
|
|
{
|
|
// create a 2D array.
|
|
npy_intp dimensions[2];
|
|
dimensions[0] = M.rows();
|
|
dimensions[1] = M.cols();
|
|
P = PyArray_SimpleNew(2, dimensions, TypeToNumPy<scalar_t>::NpyType);
|
|
numpyTypeDemuxer< CopyEigenToNumpyMatrix<const matrix_t> >(&M, reinterpret_cast<NPE_PY_ARRAY_OBJECT*>(P));
|
|
}
|
|
|
|
// incrementing the reference seems to cause a memory leak.
|
|
// boost::python::incref(P);
|
|
// This agrees with the sample code found here:
|
|
// http://mail.python.org/pipermail/cplusplus-sig/2008-October/013825.html
|
|
return P;
|
|
}
|
|
|
|
static bool isDimensionValid(int requestedSize, int sizeAtCompileTime, int maxSizeAtCompileTime)
|
|
{
|
|
bool valid = true;
|
|
if(sizeAtCompileTime == Eigen::Dynamic)
|
|
{
|
|
// Check for dynamic fixed size
|
|
// http://eigen.tuxfamily.org/dox-devel/TutorialMatrixClass.html#TutorialMatrixOptTemplParams
|
|
if(!(maxSizeAtCompileTime == Eigen::Dynamic || requestedSize <= maxSizeAtCompileTime))
|
|
{
|
|
valid = false;
|
|
}
|
|
}
|
|
else if(sizeAtCompileTime != requestedSize)
|
|
{
|
|
valid = false;
|
|
}
|
|
return valid;
|
|
}
|
|
|
|
static void checkMatrixSizes(NPE_PY_ARRAY_OBJECT * obj_ptr)
|
|
{
|
|
int rows = PyArray_DIM(obj_ptr, 0);
|
|
int cols = PyArray_DIM(obj_ptr, 1);
|
|
|
|
bool rowsValid = isDimensionValid(rows, RowsAtCompileTime, MaxRowsAtCompileTime);
|
|
bool colsValid = isDimensionValid(cols, ColsAtCompileTime, MaxColsAtCompileTime);
|
|
if(!rowsValid || !colsValid)
|
|
{
|
|
THROW_TYPE_ERROR("Can not convert " << npyArrayTypeString(obj_ptr) << " to " << toString()
|
|
<< ". Mismatched sizes.");
|
|
}
|
|
}
|
|
|
|
static void checkRowVectorSizes(NPE_PY_ARRAY_OBJECT * obj_ptr, int cols)
|
|
{
|
|
if(!isDimensionValid(cols, ColsAtCompileTime, MaxColsAtCompileTime))
|
|
{
|
|
THROW_TYPE_ERROR("Can not convert " << npyArrayTypeString(obj_ptr) << " to " << toString()
|
|
<< ". Mismatched sizes.");
|
|
}
|
|
}
|
|
|
|
static void checkColumnVectorSizes(NPE_PY_ARRAY_OBJECT * obj_ptr, int rows)
|
|
{
|
|
// Check if the type can accomidate one column.
|
|
if(ColsAtCompileTime == Eigen::Dynamic || ColsAtCompileTime == 1)
|
|
{
|
|
if(!isDimensionValid(rows, RowsAtCompileTime, MaxRowsAtCompileTime))
|
|
{
|
|
THROW_TYPE_ERROR("Can not convert " << npyArrayTypeString(obj_ptr) << " to " << toString()
|
|
<< ". Mismatched sizes.");
|
|
}
|
|
}
|
|
else
|
|
{
|
|
THROW_TYPE_ERROR("Can not convert " << npyArrayTypeString(obj_ptr) << " to " << toString()
|
|
<< ". Mismatched sizes.");
|
|
}
|
|
|
|
}
|
|
|
|
static void checkVectorSizes(NPE_PY_ARRAY_OBJECT * obj_ptr)
|
|
{
|
|
int size = PyArray_DIM(obj_ptr, 0);
|
|
|
|
// If the number of rows is fixed at 1, assume that is the sense of the vector.
|
|
// Otherwise, assume it is a column.
|
|
if(RowsAtCompileTime == 1)
|
|
{
|
|
checkRowVectorSizes(obj_ptr, size);
|
|
}
|
|
else
|
|
{
|
|
checkColumnVectorSizes(obj_ptr, size);
|
|
}
|
|
}
|
|
|
|
|
|
static void* convertible(PyObject *obj_ptr)
|
|
{
|
|
// Check for a null pointer.
|
|
if(!obj_ptr)
|
|
{
|
|
//THROW_TYPE_ERROR("PyObject pointer was null");
|
|
return 0;
|
|
}
|
|
|
|
// Make sure this is a numpy array.
|
|
if (!PyArray_Check(obj_ptr))
|
|
{
|
|
//THROW_TYPE_ERROR("Conversion is only defined for numpy array and matrix types");
|
|
return 0;
|
|
}
|
|
|
|
NPE_PY_ARRAY_OBJECT * array_ptr = reinterpret_cast<NPE_PY_ARRAY_OBJECT*>(obj_ptr);
|
|
|
|
// Check the type of the array.
|
|
int npyType = getNpyType(array_ptr);
|
|
|
|
if(!TypeToNumPy<scalar_t>::canConvert(npyType))
|
|
{
|
|
//THROW_TYPE_ERROR("Can not convert " << npyArrayTypeString(obj_ptr) << " to " << toString()
|
|
// << ". Mismatched types.");
|
|
return 0;
|
|
}
|
|
|
|
|
|
|
|
// Check the array dimensions.
|
|
int nd = PyArray_NDIM(array_ptr);
|
|
|
|
if(nd != 1 && nd != 2)
|
|
{
|
|
THROW_TYPE_ERROR("Conversion is only valid for arrays with 1 or 2 dimensions. Argument has " << nd << " dimensions");
|
|
}
|
|
|
|
if(nd == 1)
|
|
{
|
|
checkVectorSizes(array_ptr);
|
|
}
|
|
else
|
|
{
|
|
// Two-dimensional matrix type.
|
|
checkMatrixSizes(array_ptr);
|
|
}
|
|
|
|
|
|
return obj_ptr;
|
|
}
|
|
|
|
|
|
static void construct(PyObject *obj_ptr, boost::python::converter::rvalue_from_python_stage1_data *data)
|
|
{
|
|
boost::python::converter::rvalue_from_python_storage<matrix_t> * matData = reinterpret_cast<boost::python::converter::rvalue_from_python_storage<matrix_t> * >(data);
|
|
void* storage = matData->storage.bytes;
|
|
|
|
// Make sure storage is 16byte aligned. With help from code from Memory.h
|
|
void * aligned = reinterpret_cast<void*>((reinterpret_cast<size_t>(storage) & ~(size_t(15))) + 16);
|
|
|
|
matrix_t * Mp = new (aligned) matrix_t();
|
|
// Stash the memory chunk pointer for later use by boost.python
|
|
// This signals boost::python that the new value must be deleted eventually
|
|
data->convertible = storage;
|
|
|
|
|
|
// std::cout << "Creating aligned pointer " << aligned << " from storage " << storage << std::endl;
|
|
// std::cout << "matrix size: " << sizeof(matrix_t) << std::endl;
|
|
// std::cout << "referent size: " << boost::python::detail::referent_size< matrix_t & >::value << std::endl;
|
|
// std::cout << "sizeof(storage): " << sizeof(matData->storage) << std::endl;
|
|
// std::cout << "sizeof(bytes): " << sizeof(matData->storage.bytes) << std::endl;
|
|
|
|
|
|
|
|
matrix_t & M = *Mp;
|
|
|
|
if (!PyArray_Check(obj_ptr))
|
|
{
|
|
THROW_TYPE_ERROR("construct is only defined for numpy array and matrix types");
|
|
}
|
|
|
|
NPE_PY_ARRAY_OBJECT * array_ptr = reinterpret_cast<NPE_PY_ARRAY_OBJECT*>(obj_ptr);
|
|
|
|
int nd = PyArray_NDIM(array_ptr);
|
|
if(nd == 1)
|
|
{
|
|
int size = PyArray_DIM(array_ptr, 0);
|
|
// This is a vector type
|
|
if(RowsAtCompileTime == 1)
|
|
{
|
|
// Row Vector
|
|
M.resize(1,size);
|
|
}
|
|
else
|
|
{
|
|
// Column Vector
|
|
M.resize(size,1);
|
|
}
|
|
numpyTypeDemuxer< CopyNumpyToEigenVector<matrix_t> >(&M, array_ptr);
|
|
}
|
|
else
|
|
{
|
|
int rows = PyArray_DIM(array_ptr, 0);
|
|
int cols = PyArray_DIM(array_ptr, 1);
|
|
|
|
M.resize(rows,cols);
|
|
numpyTypeDemuxer< CopyNumpyToEigenMatrix<matrix_t> >(&M, array_ptr);
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
// The registration function.
|
|
static void register_converter()
|
|
{
|
|
boost::python::to_python_converter<matrix_t,NumpyEigenConverter>();
|
|
boost::python::converter::registry::push_back(
|
|
&NumpyEigenConverter::convertible,
|
|
&NumpyEigenConverter::construct,
|
|
boost::python::type_id<matrix_t>());
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
#endif /* NUMPY_EIGEN_CONVERTER_HPP */
|