335 lines
		
	
	
		
			9.5 KiB
		
	
	
	
		
			C++
		
	
	
		
		
			
		
	
	
			335 lines
		
	
	
		
			9.5 KiB
		
	
	
	
		
			C++
		
	
	
|  | /**
 | ||
|  |  * @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 */
 |