Commit 9621b829 authored by Laurent Belcour's avatar Laurent Belcour

[Python] Changing the python interface to pybind11

               Right now the change work on OSX, and is feature complete w/r/t the test suite.
parent 477c9b24
......@@ -4,3 +4,6 @@
[submodule "external/Catch"]
path = external/Catch
url = https://github.com/philsquared/Catch.git
[submodule "external/pybind11"]
path = external/pybind11
url = https://github.com/pybind/pybind11.git
Subproject commit f117a48ea2fd446d2865826a58d08027d4579cb3
......@@ -5,29 +5,32 @@ Import('env', 'library_available')
env = env.Clone()
env.Prepend(LIBS = ['core'])
## Add pybind path
env.AppendUnique(CPPPATH = '#external/pybind11/include/')
## Building a test function for boost::python
##
bp_test_source = """
// STL and Boost includes
#include <memory>
#include <boost/python.hpp>
#include <pybind11/pybind11.h>
// ALTA includes
#include <core/common.h>
#include <core/ptr.h>
#include <core/function.h>
int main(int argc, char** argv) {
boost::python::class_<alta::function, alta::ptr<alta::function>, boost::noncopyable>("function", boost::python::no_init);
boost::python::register_ptr_to_python<alta::ptr<alta::function>>();
namespace py = pybind11;
return 0;
PYBIND11_MODULE(example, m) {
m.doc() = "pybind11 example plugin"; // optional module docstring
m.def("norm", &norm, "Compute the norm of vector 'v'", py::arg("v"));
}
"""
def CheckBoostPython(context):
context.Message('Checking boost::python using small example...')
result = context.TryLink(bp_test_source, '.cpp')
result = context.TryCompile(bp_test_source, '.cpp')
context.Result(result)
return result
......@@ -41,16 +44,6 @@ if library_available(env, pkgspec='python-2.7',
lib='PYTHON_LIB', header='Python.h'):
conf = Configure(env, custom_tests = {'CheckBoostPython' : CheckBoostPython})
# On GNU/Linux the '-mt' suffix is no longer used, but it is still
# used on some other platforms (see
# <http://stackoverflow.com/questions/2293962/boost-libraries-in-multithreading-aware-mode>.)
build_lib = conf.CheckLibWithHeader('boost_python-mt',
'boost/python.hpp', 'c++')
if not build_lib:
build_lib = conf.CheckLibWithHeader('boost_python',
'boost/python.hpp', 'c++')
build_lib = conf.CheckBoostPython();
env = conf.Finish()
......
......@@ -9,8 +9,9 @@
License, v. 2.0. If a copy of the MPL was not distributed with this
file, You can obtain one at http://mozilla.org/MPL/2.0/. */
// Boost includes
#include <boost/python.hpp>
// Pybind11 includes
#include <pybind11/pybind11.h>
namespace py = pybind11;
// ALTA include
#include <core/common.h>
......@@ -25,57 +26,13 @@
#include <iostream>
// Local includes
#include "wrapper_vec.hpp"
#define bp boost::python
#include "wrapper_args.h"
#include "wrapper_vec.h"
#include "wrapper_data.h"
using namespace alta;
/* This class is a wrapper to ALTA's arguments class to add Python specific
* behaviour such as dictionnary initialization.
*
* Right now it does not handle automatic conversion in function call for
* example. The following code is not possible:
*
* import alta
* alta.get_data('data_merl', {'params' : 'STARK_2D'})
*
* Instead, one has to construct the arguments object from the ALTA library
* to use it afterwards:
*
* import alta
* args = alta.arguments({'params' : 'STARK_2D'})
* alta.get_data('data_merl', args)
*/
struct python_arguments : public arguments {
python_arguments() : arguments() {}
python_arguments(bp::dict d) : arguments() {
bp::list keys = d.keys();
for(int i=0; i<bp::len(keys); ++i) {
const std::string s_key = bp::extract<std::string>(keys[i]);
const std::string s_val = bp::extract<std::string>(d[keys[i]]);
this->update(s_key, s_val);
}
}
};
/* Create a data object from a plugin's name and the data filename. This
* function is here to accelerate the loading of data file.
*/
static ptr<data> load_data(const std::string& plugin_name, const std::string& filename) {
ptr<data> d = plugins_manager::load_data(filename, plugin_name);
return d;
}
static ptr<data> get_data_with_args(const std::string& plugin_name,
size_t size,
const parameters& params,
const python_arguments& args) {
return plugins_manager::get_data(plugin_name, size, params, args);
}
static ptr<data> get_data(const std::string& plugin_name, size_t size,
const parameters& params) {
return plugins_manager::get_data(plugin_name, size, params);
}
/* Creating functions for the plugins_manager calls
*
......@@ -335,137 +292,110 @@ static void brdf2data(const ptr<function>& f, ptr<data>& d) {
/* Compute distance metric between 'in' and 'ref'.
*/
static bp::dict data2stats(const ptr<data>& in, const ptr<data>& ref) {
static py::dict data2stats(const ptr<data>& in, const ptr<data>& ref) {
// Compute the metrics
errors::metrics res;
errors::compute(in.get(), ref.get(), nullptr, res);
// Fill the resulting Python vector
bp::dict py_res;
py::dict py_res;
for(auto rpair : res) {
py_res.setdefault<std::string, vec>(rpair.first, rpair.second);
py_res[py::str(rpair.first)] = rpair.second;
}
return py_res;
}
inline void register_function(py::module& m) {
py::class_<function, ptr<function>>(m, "function")
.def("__add__", &add_function)
.def("__mul__", &mult_function)
.def("__rmul__", &mult_function)
.def("value", &function::value)
.def("load", &load_from_file)
.def("load", &load_from_file_with_args)
.def("save", &function::save)
.def("save", &save_function_without_args)
.def("set", &set_function_params)
.def("get", &get_function_params);
m.def("get_function", get_function, py::arg("name") = "nonlinear_diffuse",
py::arg("param") = parameters(6, 3, params::CARTESIAN, params::RGB_COLOR));
m.def("get_function", get_function_from_args);
m.def("load_function", load_function);
m.def("load_function", load_function_with_args);
}
inline void register_fitter(py::module& m) {
py::class_<fitter, ptr<fitter>>(m, "fitter")
.def("fit_data", &fit_data_with_args)
.def("fit_data", &fit_data_without_args);
m.def("get_fitter", plugins_manager::get_fitter);
}
#define STRINGIFY_(x) #x
#define STRINGIFY(x) STRINGIFY_(x)
/* Exporting the ALTA module
*/
BOOST_PYTHON_MODULE(alta)
{
// Argument class
//
bp::class_<arguments>("_arguments");
bp::class_<python_arguments, bp::bases<arguments>>("arguments")
.def(bp::init<>())
.def(bp::init<bp::dict>())
.def("__getitem__", &arguments::operator[])
.def("update", &arguments::update);
PYBIND11_MODULE(alta, m) {
m.doc() = "ALTA python bindinds";
m.def("norm", &norm, "Compute the norm of vector 'v'", py::arg("v"));
// Vec class
//
register_wrapper_vec();
// 'parameters' class.
bp::class_<parameters>("parameters")
.def(bp::init<unsigned int, unsigned int, params::input, params::output>());
// Parameterization enums.
/* Register the 'parameters' class and the enums */
py::class_<parameters>(m, "parameters")
.def(py::init<unsigned int, unsigned int, params::input, params::output>());
#define PARAM_VALUE(name) \
.value(STRINGIFY(name), params:: name)
bp::enum_<params::input>("input_parametrization")
PARAM_VALUE(RUSIN_TH_PH_TD_PD)
PARAM_VALUE(RUSIN_TH_PH_TD)
PARAM_VALUE(RUSIN_TH_TD_PD)
PARAM_VALUE(RUSIN_TH_TD)
PARAM_VALUE(RUSIN_VH_VD)
PARAM_VALUE(RUSIN_VH)
PARAM_VALUE(COS_TH_TD)
PARAM_VALUE(COS_TH)
PARAM_VALUE(SCHLICK_TK_PK)
PARAM_VALUE(SCHLICK_VK)
PARAM_VALUE(SCHLICK_TL_TK_PROJ_DPHI)
PARAM_VALUE(COS_TK)
PARAM_VALUE(RETRO_TL_TVL_PROJ_DPHI)
PARAM_VALUE(STEREOGRAPHIC)
PARAM_VALUE(SPHERICAL_TL_PL_TV_PV)
PARAM_VALUE(COS_TLV)
PARAM_VALUE(COS_TLR)
PARAM_VALUE(ISOTROPIC_TV_TL)
PARAM_VALUE(ISOTROPIC_TV_TL_DPHI)
PARAM_VALUE(ISOTROPIC_TV_PROJ_DPHI)
PARAM_VALUE(ISOTROPIC_TL_TV_PROJ_DPHI)
PARAM_VALUE(ISOTROPIC_TD_PD)
PARAM_VALUE(STARK_2D)
PARAM_VALUE(STARK_3D)
PARAM_VALUE(NEUMANN_2D)
PARAM_VALUE(NEUMANN_3D)
PARAM_VALUE(CARTESIAN)
PARAM_VALUE(UNKNOWN_INPUT);
bp::enum_<params::output>("output_parametrization")
PARAM_VALUE(INV_STERADIAN)
PARAM_VALUE(INV_STERADIAN_COSINE_FACTOR)
PARAM_VALUE(ENERGY)
PARAM_VALUE(RGB_COLOR)
PARAM_VALUE(XYZ_COLOR)
PARAM_VALUE(UNKNOWN_OUTPUT);
py::enum_<params::input>(m, "input_parametrization")
PARAM_VALUE(RUSIN_TH_PH_TD_PD)
PARAM_VALUE(RUSIN_TH_PH_TD)
PARAM_VALUE(RUSIN_TH_TD_PD)
PARAM_VALUE(RUSIN_TH_TD)
PARAM_VALUE(RUSIN_VH_VD)
PARAM_VALUE(RUSIN_VH)
PARAM_VALUE(COS_TH_TD)
PARAM_VALUE(COS_TH)
PARAM_VALUE(SCHLICK_TK_PK)
PARAM_VALUE(SCHLICK_VK)
PARAM_VALUE(SCHLICK_TL_TK_PROJ_DPHI)
PARAM_VALUE(COS_TK)
PARAM_VALUE(RETRO_TL_TVL_PROJ_DPHI)
PARAM_VALUE(STEREOGRAPHIC)
PARAM_VALUE(SPHERICAL_TL_PL_TV_PV)
PARAM_VALUE(COS_TLV)
PARAM_VALUE(COS_TLR)
PARAM_VALUE(ISOTROPIC_TV_TL)
PARAM_VALUE(ISOTROPIC_TV_TL_DPHI)
PARAM_VALUE(ISOTROPIC_TV_PROJ_DPHI)
PARAM_VALUE(ISOTROPIC_TL_TV_PROJ_DPHI)
PARAM_VALUE(ISOTROPIC_TD_PD)
PARAM_VALUE(STARK_2D)
PARAM_VALUE(STARK_3D)
PARAM_VALUE(NEUMANN_2D)
PARAM_VALUE(NEUMANN_3D)
PARAM_VALUE(CARTESIAN)
PARAM_VALUE(UNKNOWN_INPUT);
py::enum_<params::output>(m, "output_parametrization")
PARAM_VALUE(INV_STERADIAN)
PARAM_VALUE(INV_STERADIAN_COSINE_FACTOR)
PARAM_VALUE(ENERGY)
PARAM_VALUE(RGB_COLOR)
PARAM_VALUE(XYZ_COLOR)
PARAM_VALUE(UNKNOWN_OUTPUT);
#undef PARAM_VALUE
bp::register_ptr_to_python<ptr<parameters>>();
/* Register the `args` and `vec` types */
register_args(m);
register_vec(m);
// Function interface
//
bp::class_<function, ptr<function>, boost::noncopyable>("function", bp::no_init)
.def("__add__", &add_function)
.def("__mul__", &mult_function)
.def("__rmul__", &mult_function)
.def("value", &function::value)
.def("load", &load_from_file)
.def("load", &load_from_file_with_args)
.def("save", &function::save)
.def("save", &save_function_without_args)
.def("set", &set_function_params)
.def("get", &get_function_params);
bp::def("get_function", get_function);
bp::def("get_function", get_function_from_args);
bp::def("load_function", load_function);
bp::def("load_function", load_function_with_args);
bp::register_ptr_to_python<ptr<function>>();
// Data interface
//
bp::class_<data, ptr<data>, boost::noncopyable>("data", bp::no_init)
.def("size", &data::size)
.def("get", &data::get)
//.def("set", &data::set)
.def("save", &data::save);
bp::def("get_data", get_data);
bp::def("get_data", get_data_with_args);
bp::def("load_data", load_data);
bp::register_ptr_to_python<ptr<data>>();
// Fitter interface
//
bp::class_<fitter, ptr<fitter>, boost::noncopyable>("fitter", bp::no_init)
.def("fit_data", &fit_data_with_args)
.def("fit_data", &fit_data_without_args);
bp::def("get_fitter", plugins_manager::get_fitter);
bp::register_ptr_to_python<ptr<fitter>>();
// Softs
//
bp::def("data2data", data2data);
bp::def("data2stats", data2stats);
bp::def("brdf2data", brdf2data);
}
/* register the `data`, `function` and `fitter` objects */
register_data(m);
register_function(m);
register_fitter(m);
/* register `softs` */
m.def("data2data", data2data);
m.def("data2stats", data2stats);
m.def("brdf2data", brdf2data);
}
\ No newline at end of file
/* ALTA --- Analysis of Bidirectional Reflectance Distribution Functions
Copyright (C) 2018 Unity
This file is part of ALTA.
This Source Code Form is subject to the terms of the Mozilla Public
License, v. 2.0. If a copy of the MPL was not distributed with this
file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#pragma once
// Pybind11 includes
#include <pybind11/pybind11.h>
namespace py = pybind11;
// ALTA include
#include <core/args.h>
#include <core/ptr.h>
#include <core/data.h>
using namespace alta;
/* This class is a wrapper to ALTA's arguments class to add Python specific
* behaviour such as dictionnary initialization.
*
* Right now it does not handle automatic conversion in function call for
* example. The following code is not possible:
*
* import alta
* alta.get_data('data_merl', {'params' : 'STARK_2D'})
*
* Instead, one has to construct the arguments object from the ALTA library
* to use it afterwards:
*
* import alta
* args = alta.arguments({'params' : 'STARK_2D'})
* alta.get_data('data_merl', args)
*/
struct python_arguments : public arguments {
python_arguments() : arguments() {}
python_arguments(py::dict d) : arguments() {
for(auto item : d) {
const std::string s_key = std::string(py::str(item.first));
const std::string s_val = std::string(py::str(item.second));
this->update(s_key, s_val);
}
}
};
/* Register the `arguments` class to python */
inline void register_args(py::module& m) {
py::class_<python_arguments>(m, "arguments")
.def(py::init<>())
.def(py::init<py::dict>())
.def("__getitem__", &arguments::operator[])
.def("update", &arguments::update);
}
\ No newline at end of file
/* ALTA --- Analysis of Bidirectional Reflectance Distribution Functions
Copyright (C) 2018 Unity
This file is part of ALTA.
This Source Code Form is subject to the terms of the Mozilla Public
License, v. 2.0. If a copy of the MPL was not distributed with this
file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#pragma once
// Pybind11 includes
#include <pybind11/pybind11.h>
namespace py = pybind11;
// ALTA include
#include <core/common.h>
#include <core/ptr.h>
#include <core/data.h>
using namespace alta;
/* Create a data object from a plugin's name and the data filename. This
* function is here to accelerate the loading of data file.
*/
static ptr<data> load_data(const std::string& plugin_name,
const std::string& filename)
{
return plugins_manager::load_data(filename, plugin_name);
}
static ptr<data> get_data_with_args(const std::string& plugin_name,
size_t size,
const parameters& params,
const arguments& args)
{
return plugins_manager::get_data(plugin_name, size, params, args);
}
static ptr<data> get_data(const std::string& plugin_name, size_t size,
const parameters& params)
{
return plugins_manager::get_data(plugin_name, size, params);
}
/* Register the `data` class to python
*/
inline void register_data(py::module& m) {
py::class_<data, ptr<data>>(m, "data")
.def("size", &data::size)
.def("get", &data::get)
.def("set", &data::set)
.def("save", &data::save);
m.def("get_data", get_data);
m.def("get_data", get_data_with_args);
m.def("load_data", load_data);
}
\ No newline at end of file
/* ALTA --- Analysis of Bidirectional Reflectance Distribution Functions
Copyright (C) 2015 Université de Montréal
This file is part of ALTA.
This Source Code Form is subject to the terms of the Mozilla Public
License, v. 2.0. If a copy of the MPL was not distributed with this
file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#pragma once
// Pybind11 includes
#include <pybind11/pybind11.h>
#include <pybind11/stl.h>
namespace py = pybind11;
// ALTA include
#include <core/common.h>
// STL include
#include <iostream>
/* Specific function call to set a vector's element
*/
static void vec_set_item(vec& x, int i, double a) {
x[i] = a;
}
/* Operators on vec
*/
static vec vec_add(const vec& a, const vec& b) {
return a + b;
}
static vec vec_sub(const vec& a, const vec& b) {
return a - b;
}
/* Specific convert a vec to a string
*/
static std::string vec_str(const vec& x) {
std::stringstream stream;
stream << "[";
for(int i=0; i<x.size(); ++i) {
if(i > 0) { stream << ", "; }
stream << x[i];
}
stream << "]";
return stream.str();
}
/* Register the vector class to python
*/
inline void register_vec(py::module& m) {
py::class_<vec>(m, "vec")
.def(py::init<vec>())
.def(py::init([]( const std::vector<double>& a) {
vec v(a.size());
memcpy((void*)v.data(), (const void*)a.data(), a.size()*sizeof(double));
return v;
}))
.def("__add__", &vec_add)
.def("__sub__", &vec_sub)
.def("__len__", &vec::size)
.def("__getitem__", [](const vec &s, size_t i) {
if (i >= s.size()) throw py::index_error();
return s[i];
})
.def("__setitem__", &vec_set_item)
.def("__str__", &vec_str);
}
/* ALTA --- Analysis of Bidirectional Reflectance Distribution Functions
Copyright (C) 2015 Université de Montréal
This file is part of ALTA.
This Source Code Form is subject to the terms of the Mozilla Public
License, v. 2.0. If a copy of the MPL was not distributed with this
file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#pragma once
// Boost includes
#include <boost/python.hpp>
// ALTA include
#include <core/common.h>
// STL include
#include <iostream>
#define bp boost::python
/* Create special converter from and to list/vec
*
* This allow to create a 'vec' object using a python 'list' with the following
* interface:
*
* v = alta.vec([1,1,1])
*
*/
struct converter_vec_list {
// Constructor add the converter to the registry
static void register_converter() {
bp::converter::registry::push_back(
&convertible,
&construct,
bp::type_id<vec>());
}
// Is the Python object convertible to a vec ?
static void* convertible(PyObject* obj_ptr) {
if (!PyList_Check(obj_ptr)) return nullptr;
return obj_ptr;
}
// From a PyObject, construct a vector object
static void construct(
PyObject* obj_ptr,
bp::converter::rvalue_from_python_stage1_data* data) {
auto size = PyList_Size(obj_ptr);
vec* _vec = new vec(size);
for(auto i=0; i<size; ++i) {
auto pyitem = PyList_GetItem(obj_ptr, i);
(*_vec)[i] = PyFloat_AsDouble(pyitem);
}
data->convertible = (void*) _vec;;
}
};
/* Iterator over the 'vec' class to enable construction of lists and arrays in
* python using the __iter__ interface. To build a 'list' or a numpy 'array'
* from an object (like 'vec'), Python requires another class that enable to
* iterate over the structure. This class is created using 'vec::__iter__'
* method. Then, elements of 'vec' are extracted using the 'next' method of the
* iterator. To enable use with numpy.array, the iterator needs to be nested
* (have a method '__iter__'). This is how numpy handles multidmensional arrays.
*
* With this class, it is possible to use the following code:
*
* v = alta.vec([1,1,1])
* a = np.array(v)
*/
struct iterator_vec {
const double* cur;
const double* end;
iterator_vec(const vec& x) : cur(x.data()), end(x.data()+x.size()) { }
// Iterator over the 'vec' structure.
//
static double next(iterator_vec& iter) {
if(iter.cur == iter.end) {
PyErr_SetString(PyExc_StopIteration, "No more data.");
bp::throw_error_already_set();
}
double r = *(iter.cur);
iter.cur++;
return r;
}
// Create an iterator from the 'vec' object.
//
static iterator_vec iter(const vec& x) {
return iterator_vec(x);
}
// Create a nested iterator.
//
static iterator_vec self(const iterator_vec& x) {
return x;
}
};
/* Specific function call to acces a vector's element
*/
static double vec_get_item(const vec& x, int i) {
return x[i];
}
/* Specific function call to set a vector's element
*/
static void vec_set_item(vec& x, int i, double a) {
x[i] = a;
}
/* Operators on vec
*/
static vec vec_add(const vec& a, const vec& b) {
return a + b;
}
static vec vec_sub(const vec& a, const vec& b) {
return a - b;
}
/* Specific convert a vec to a string
*/
static std::string vec_str(const vec& x) {
std::stringstream stream;
stream << "[";
for(int i=0; i<x.size(); ++i) {
if(i > 0) { stream << ", "; }
stream << x[i];
}
stream << "]";
return stream.str();
}
/* Register the vector class to boost python
*/
static void register_wrapper_vec() {
converter_vec_list::register_converter();
bp::class_<iterator_vec>("iterator_vec", bp::no_init)
.def("next", &iterator_vec::next)
.def("__iter__", &iterator_vec::self);
bp::class_<vec>("vec")
.def(bp::init<vec>())
.def(bp::self_ns::str(bp::self_ns::self))
.def("__add__", &vec_add)
.def("__sub__", &vec_sub)
.def("__len__", &vec::size)
.def("__getitem__", &vec_get_item)
.def("__setitem__", &vec_set_item)
.def("__str__", &vec_str)
.def("__iter__", &iterator_vec::iter);
}
import alta
import numpy
fail = 0
# Init the vector using the
# Init the vector using an input array
# Also test if the _getitem_ function works
x = alta.vec([1.0, 1.0, 1.0])
if x[0] != 1.0:
fail += 1