2012-01-13

Passing a user defined python object to C++ code(1) Source code


Once I explained how to pass a python build-in object (dict, list, and
string) to C++ code using python.boost.
http://shitohichiumaya.blogspot.com/2010/08/boostpython-how-to-pass-python-object.html

This time, I will explain about how to pass a user defined python object
to C++ via python.boost. I will also explain why numpy.array's
numpy.int64 object casts an error when I do extract< int >. This casts a
TypeError (TypeError: No registered converter was able to produce a C++
rvalue of type int from this Python object of type numpy.int64()). Then
I will show you how to solve this problem.

Here is an python example code.
-----
#
# test pass object 2 module, python side implementation
# Copyright (C) 2012 Hitoshi
#
import numpy
import passobj2_mod

class TriMesh(object):
    """TriMesh: simplified triangle mesh primitive for python.boost
    demo"""

    def __init__(self, _mat_name):
        """default constructor."""
        self.__material_name = _mat_name
        self.vertex_list       = []
        self.face_idx_list     = []

    def get_material_name(self):
        """get material name
        \return material name"""
        return self.__material_name

    def info_summary(self):
        """summary information
        \return summary information string
        """
        ret_str =\
            '# materialname = ' + self.__material_name         + '\n' +\
            '# vertices     = ' + str(len(self.vertex_list))   + '\n' +\
            '# faces        = ' + str(len(self.face_idx_list)) + '\n'
        return ret_str


tmesh = TriMesh('diffuse_material')

# vertices
vlist = []
vlist.append(numpy.array([552.8, 0.0,   0.0]))
vlist.append(numpy.array([0.0,   0.0,   0.0]))
vlist.append(numpy.array([0.0,   0.0, 559.2]))
vlist.append(numpy.array([549.6, 0.0, 559.2]))
# faces (obj file like)
flist = []
flist.append(numpy.array([1, 2, 3]))
flist.append(numpy.array([1, 3, 4]))

tmesh.vertex_list   = vlist
tmesh.face_idx_list = flist
print 'triangle mesh info\n' + tmesh.info_summary()

pobj2 = passobj2_mod.passobj2()
pobj2.pass_trimesh(tmesh)
-----

This code defines a simple triangle mesh class. Each triangle vertex is
represented by numpy.array that have three floats (a three-dimensional
vector). Each triangle face is represented by three vertex indices.  You
may notice the index started with 1. Because I often use the wavefront
obj file format for a geometry file. In that file format, the index
started with 1. This TriMesh has a material name as a private member.

The C++ python.boost code that accesses to python TriMesh object is the
following.

-----
/// How to pass the python dict to the C++ function/method.
///
/// Copyright (C) 2010-2012 Hitoshi
///
/// test for using C++ class from python: pass an object that contains
/// list of numpy object
///
/// trimesh is an object, that has vertex list and face list.

#include <boost/python.hpp>
#include <boost/python/object.hpp>
#include <boost/python/extract.hpp>
#include <boost/python/list.hpp>
#include <boost/python/dict.hpp>
#include <boost/python/str.hpp>

#include <stdexcept>
#include <iostream>

/// using namespace only for example
using namespace boost::python;

//----------------------------------------------------------------------
/// print one numpy.array (actually length 3 sequence), type float
///
/// \param[in] float32_3_obj float[3] object
void print_float32_3(boost::python::object const & float32_3_obj)
{
    // check sequence length is 3 or not.
    if(boost::python::len(float32_3_obj) != 3){
        std::string const objstr =
            boost::python::extract< std::string >(
                boost::python::str(float32_3_obj));
        std::cerr << "print_float32_3: arg is not a float[3] obj ["
                  << objstr << "]" << std::endl;
        return;
    }

    float vec[] = {0.0f, 0.0f, 0.0f};
    for(int i = 0; i < 3; ++i){
        vec[i] = boost::python::extract< float >(
            float32_3_obj.attr("__getitem__")(i));
    }
    std::cout << "float[3] = " << vec[0] << " " << vec[1] << " "
              << vec[2] << std::endl;
}

//----------------------------------------------------------------------
/// print one numpy.array (actually length 3 sequence), type int
///
/// \param[in] int32_3_obj float[3] object
void print_int32_3(boost::python::object const & int32_3_obj)
{
    // check sequence length is 3 or not.
    if(boost::python::len(int32_3_obj) != 3){
        std::string const objstr =
            boost::python::extract< std::string >(
                boost::python::str(int32_3_obj));
        std::cerr << "print_int32_3: arg is not a int[3] obj ["
                  << objstr << "]" << std::endl;
        return;
    }

    int vec[] = {0, 0, 0};
    for(int i = 0; i < 3; ++i){
        // MARK_03
        object int64_obj = int32_3_obj.attr("__getitem__")(i);
        vec[i] = boost::python::extract< int >(
            int64_obj.attr("__int__")());
    }
    std::cout << "int[3] = " << vec[0] << " " << vec[1] << " "
              << vec[2] << std::endl;
}

//----------------------------------------------------------------------
/// accessing numpy.array list
///
/// \param[in] numpy_list_obj list of numpy.array.
/// \param[in] is_float       true when float array
void access_numpy_array_list(object const & numpy_list_obj,
                             bool is_float)
{
    int const len = boost::python::len(numpy_list_obj);
    std::cout << "len(numpy_list_obj) = " << len << std::endl;
    list cpp_list = extract< list >(numpy_list_obj);
    for(int i = 0; i < len; ++i){
        // operator[] returns python::boost::object.
        object one_vec = cpp_list[i];
        if(is_float){
            print_float32_3(one_vec);
        }
        else{
            print_int32_3(one_vec);
        }
    }
}

//----------------------------------------------------------------------
/// C++ object which python can instantiate
class PassObj2 {
public:
    /// constructor
    PassObj2()
    {
        // empty
    }

    /// pass a python object
    /// \param[in] pytrimesh a trimesh object
    void pass_trimesh(object const & pytrimesh) const {
        // access to material name via the access method. MARK_01
        std::string const matname =
            extract< std::string >(pytrimesh.attr("get_material_name")());
        std::cout << "trimesh.get_material_name() = " << matname << std::endl;

        // access to python list
        object const vlist_obj = pytrimesh.attr("vertex_list");
        object const flist_obj = pytrimesh.attr("face_idx_list");

        std::cout << "--- vertex_list ---"   << std::endl;
        access_numpy_array_list(vlist_obj, true);
        std::cout << "--- face_idx_list ---" << std::endl;
        access_numpy_array_list(flist_obj, false);
    }

private:
};

/// importing module name is 'passobj_mod'
BOOST_PYTHON_MODULE(passobj2_mod)
{
    class_("passobj2")
        .def("pass_trimesh",
             &PassObj2::pass_trimesh,
             "pass python trimesh object to c++ method")
        ;
}
\end{verbatim}

The following code is a build script. Of course, you need python.boost
installed in your system.
\begin{verbatim}
#! /bin/sh -x
# Copyright (C) 2012  Hitoshi
#
PYTHON_INCLUDE=`python-config --includes`
MOD_CPP_SOURCE_BASE=passobj2_mod
g++ ${PYTHON_INCLUDE} -DPIC -shared -fPIC ${MOD_CPP_SOURCE_BASE}.cpp -o ${MOD_CPP_SOURCE_BASE}.so -lboost_python
\end{verbatim}

If you run this program, you will see the following result.
\begin{verbatim}
@[34]bash % python test_passobj2_mod.py
triangle mesh info
# materialname = diffuse_material
# vertices     = 4
# faces        = 2

trimesh.get_material_name() = diffuse_material
--- vertex_list ---
len(numpy_list_obj) = 4
float[3] = 552.8 0 0
float[3] = 0 0 0
float[3] = 0 0 559.2
float[3] = 549.6 0 559.2
--- face_idx_list ---
len(numpy_list_obj) = 2
int[3] = 1 2 3
int[3] = 1 3 4
---

The C++ output shows the same as in the python code.

This time I showed the code. Let's talk about the details in the
following blog entries.

1 comment:

Eric said...

Thank you for the post! This has actually be really helpful for me. One typo though you have:
class_("passobj2") should be class_("passobj2") as class_ is a class template.