Skip to main content

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.

Comments

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.
coder_wannabe said…
This was really helpful! Thank you!

Popular posts from this blog

Why A^{T}A is invertible? (2) Linear Algebra

Why A^{T}A has the inverse Let me explain why A^{T}A has the inverse, if the columns of A are independent. First, if a matrix is n by n, and all the columns are independent, then this is a square full rank matrix. Therefore, there is the inverse. So, the problem is when A is a m by n, rectangle matrix.  Strang's explanation is based on null space. Null space and column space are the fundamental of the linear algebra. This explanation is simple and clear. However, when I was a University student, I did not recall the explanation of the null space in my linear algebra class. Maybe I was careless. I regret that... Explanation based on null space This explanation is based on Strang's book. Column space and null space are the main characters. Let's start with this explanation. Assume  x  where x is in the null space of A .  The matrices ( A^{T} A ) and A share the null space as the following: This means, if x is in the null space of A , x is also in the null spa

Gauss's quote for positive, negative, and imaginary number

Recently I watched the following great videos about imaginary numbers by Welch Labs. https://youtu.be/T647CGsuOVU?list=PLiaHhY2iBX9g6KIvZ_703G3KJXapKkNaF I like this article about naming of math by Kalid Azad. https://betterexplained.com/articles/learning-tip-idea-name/ Both articles mentioned about Gauss, who suggested to use other names of positive, negative, and imaginary numbers. Gauss wrote these names are wrong and that is one of the reason people didn't get why negative times negative is positive, or, pure positive imaginary times pure positive imaginary is negative real number. I made a few videos about explaining why -1 * -1 = +1, too. Explanation: why -1 * -1 = +1 by pattern https://youtu.be/uD7JRdAzKP8 Explanation: why -1 * -1 = +1 by climbing a mountain https://youtu.be/uD7JRdAzKP8 But actually Gauss's insight is much powerful. The original is in the Gauß, Werke, Bd. 2, S. 178 . Hätte man +1, -1, √-1) nicht positiv, negative, imaginäre (oder gar um

Why parallelogram area is |ad-bc|?

Here is my question. The area of parallelogram is the difference of these two rectangles (red rectangle - blue rectangle). This is not intuitive for me. If you also think it is not so intuitive, you might interested in my slides. I try to explain this for hight school students. Slides:  A bit intuitive (for me) explanation of area of parallelogram  (to my site, external link) .