I have a queue of objects that I am dequeueing in order to get an object and process it in python before returning the result. I'm a bit unsure as to how it all fits together but from what I've gathered from various places I think I am pretty close.
I have a class that looks like this:
class PyData
{
public:
PyData(
const btVector3 &TORSO_LV,
std::vector<std::tuple<float, float, float>> DsOsAVs,
std::vector<btVector3> RF_FORCES,
std::vector<btVector3> LF_FORCES,
float slope,
float compliance
);
std::tuple<float, float, float> m_TORSO_LV;
std::vector<std::tuple<float, float, float>> m_DsOsAVS;
std::vector<std::tuple<float, float, float>> m_RF_FORCES;
std::vector<std::tuple<float, float, float>> m_LF_FORCES;
float m_slope;
float m_compliance;
~PyData();
};
and then I create a boost python module that looks like this:
BOOST_PYTHON_MODULE(pydata) {
bp::class_<PyData>("PyData",
bp::init<
const btVector3,
std::vector<std::tuple<float, float, float>>,
std::vector<btVector3>,
std::vector<btVector3>,
float,
float
>())
.def_readonly("Torso_LV", &PyData::m_TORSO_LV)
.def_readonly("DsOsAVs", &PyData::m_DsOsAVS)
.def_readonly("RF_FORCES", &PyData::m_RF_FORCES)
.def_readonly("LF_FORCES", &PyData::m_LF_FORCES);
};
After every 33 ms I create a PyData object and place it into the queue. Something like this:
// Check the sample clock for sampling
if (m_sampleClock.getTimeMilliseconds() > 33) {
if (ContactManager::GetInstance().m_beingUsed) {
PyData dat = BuildPyData();
if (dat.m_compliance != 0.0f) {
std::unique_lock <std::mutex> l(m_mutex);
m_data.push_front(dat);
m_NotEmptyCV.notify_one();
l.unlock();
}
}
m_sampleClock.reset();
}
I then have a separate worker thread that dequeues the queue to obtain an object and send it off to a python function which looks like:
void ContactLearningApp::PythonWorkerThread() {
printf("Start Python thread. \n");
bp::object f = m_interface.attr("predict_on_data");
while (true) {
//printf("Inside while loop and waiting. \n");
std::unique_lock<std::mutex> ul(m_mutex);
while (m_data.size() <= 0) {
m_NotEmptyCV.wait(ul);
}
PyData dat = m_data.back();
m_data.pop_back();
f(boost::python::ptr(&dat));
ul.unlock();
//m_ProcessedCV.notify_one();
//bp::exec("print ('Hello from boost')", m_main_namespace);
}
}
Basically, I am trying to pass in an object instantiated in c++ as a python argument but I am not sure how to piece it together. The python interpreter doesn't need a copy of the object so I am using boost::python::ptr. The python file is simple and I just want to print out the object received onto the console like this:
def predict_on_data(data):
print("In Predict on Data")
print(data)
I'm not sure how this integrates with the boost module. What would be the correct way to do this?
I've written some sample code based on your PyData data object; this code uses the boost::python data structures (tuple and list) for exchanging data to/from Python, as this is their intended use, but these can be populated by copying data into them from std::tuple and std::vector as needed.
This works with Python 2.7 and boost 1.53. Hopefully you can use this to help; NB the call to initpydata() (generated function) is required after Py_Initialze().
C++ code:
#include <iostream>
#include <vector>
#include <tuple>
#include <boost/python.hpp>
#include <boost/python/list.hpp>
class PyData
{
public:
PyData() {}
float m_slope;
float m_compliance;
boost::python::tuple m_TORSO_LV;
boost::python::list m_DsOsAVS;
boost::python::list m_RF_FORCES;
boost::python::list m_LF_FORCES;
void InitData()
{
// simulate setting up data
m_slope = 1.0;
m_compliance = 2.0;
m_TORSO_LV = boost::python::make_tuple(3.0, 4.0, 5.0);
m_DsOsAVS.append(boost::python::make_tuple(10.0, 11.0, 12.0));
m_DsOsAVS.append(boost::python::make_tuple(20.0, 21.0, 22.0));
// etc.
}
~PyData() {}
};
BOOST_PYTHON_MODULE(pydata) {
boost::python::class_<PyData>("PyData")
.def_readwrite("Torso_LV", &PyData::m_TORSO_LV)
.def_readwrite("DsOsAVs", &PyData::m_DsOsAVS)
.def_readwrite("RF_FORCES", &PyData::m_RF_FORCES)
.def_readwrite("LF_FORCES", &PyData::m_LF_FORCES)
.def_readwrite("slope", &PyData::m_slope)
.def_readwrite("compliance", &PyData::m_compliance)
;
};
int main (int argc, char * argv[])
{
Py_Initialize();
initpydata();
boost::python::object main=boost::python::import("__main__");
boost::python::object global(main.attr("__dict__"));
boost::python::object result = boost::python::exec_file("/home/andy/Python2.py", global, global);
boost::python::object predict_on_data = global["predict_on_data"];
if (!predict_on_data.is_none())
{
boost::shared_ptr<PyData> o(new PyData);
o->InitData();
predict_on_data(boost::python::ptr(o.get()));
std::cout << "values in c++ object are now: " << o->m_slope << " and " << o->m_compliance << std::endl;
}
return 0;
}
Python code (Python2.py file in this example):
def predict_on_data(o):
print "In Python:"
print repr(o)
# print the data members in o
print "o.slope is " + repr(o.slope)
print "o.compliance is " + repr(o.compliance)
print "o.Torso_LV is " + repr(o.Torso_LV)
print "o.m_DsOsAVs is " + repr(o.DsOsAVs)
# modify some data
o.slope = -1.0
o.compliance = -2.0
Once running this should give output like this:
In Python:
<pydata.PyData object at 0x7f41200956e0>
o.slope is 1.0
o.compliance is 2.0
o.Torso_LV is (3.0, 4.0, 5.0)
o.m_DsOsAVs is [(10.0, 11.0, 12.0), (20.0, 21.0, 22.0)]
values in c++ object are now: -1 and -2
Hope this is useful.