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 */
 |