Commit 176970cb authored by Alexander Dielen's avatar Alexander Dielen

new property interface

parent 042a26d1
Pipeline #6148 passed with stage
in 1 minute and 12 seconds
#ifndef OPENMESH_PYTHON_MESH_HH
#define OPENMESH_PYTHON_MESH_HH
#include "Utilities.hh"
#include "MeshTypes.hh"
#include "Iterator.hh"
#include "Circulator.hh"
......@@ -188,14 +189,6 @@ OM::FaceHandle add_face(Mesh& _self, const py::list& _vhandles) {
return _self.add_face(vector);
}
template<class dtype>
py::capsule free_when_done(dtype *data) {
return py::capsule(data, [](void *f) {
dtype *ptr = reinterpret_cast<dtype *>(f);
delete[] ptr;
});
}
/**
* Converts OpenMesh vectors to numpy arrays.
*
......@@ -358,87 +351,6 @@ py::array_t<int> halfedge_vertex_indices(Mesh& _self) {
return py::array_t<int>(shape, strides, indices, base);
}
/**
* Attempts to return a custom property for all mesh items at once using a
* numpy array. Returns an empty array if the property contains elements that
* are not numpy arrays or at least one of the arrays has a non-uniform shape.
*
* @note The returned array is constructed on the fly and contains copies
* of the actual property values.
*/
template <class Mesh, class PropHandle, class IndexHandle>
py::array_t<double> property_array(Mesh& _self, PropHandle _ph, size_t _n) {
// assume that all arrays have the same size and
// retrieve the size of the first array
const py::object tmp_obj = _self.property(_ph, IndexHandle(0));
py::array_t<double> tmp_arr;
try {
tmp_arr = tmp_obj.cast<py::array_t<double> >();
}
catch (py::error_already_set& e) {
return py::array_t<double>();
}
const size_t size = tmp_arr.size();
// better check this now
if (size == 0) {
return py::array_t<double>();
}
// allocate memory
double *data = new double[size * _n];
// copy one array at a time
for (size_t i = 0; i < _n; ++i) {
const IndexHandle ih(i);
const py::object obj = _self.property(_ph, ih);
try {
const auto arr = obj.cast<py::array_t<double> >();
if (arr.size() != size) {
throw py::error_already_set();
}
std::copy(arr.data(0), arr.data(0) + size, &data[size * i]);
}
catch (py::error_already_set& e) {
delete[] data;
return py::array_t<double>();
}
}
// make numpy array
const auto shape = {_n, size};
const auto strides = {size * sizeof(double), sizeof(double)};
py::capsule base = free_when_done(data);
return py::array_t<double>(shape, strides, data, base);
}
/**
* Attempts to set a custom property for all mesh items at once using a
* numpy array.
*
* @note The property is set to copies of slices of _arr.
*/
template <class Mesh, class PropHandle, class IndexHandle>
void set_property_array(Mesh& _self, PropHandle _ph, py::array_t<double> _arr, size_t _n) {
// array cannot be empty and its shape has to be (_n, m,...)
if (_arr.size() == 0 || _arr.ndim() < 2 || _arr.shape(0) != _n) {
return;
}
// copy one array at a time
const size_t size = _arr.strides(0) / sizeof(double);
for (size_t i = 0; i < _n; ++i) {
double *data = new double[size];
std::copy(_arr.data(i), _arr.data(i) + size, data);
const auto shape = {size};
const auto strides = {sizeof(double)};
py::capsule base = free_when_done(data);
py::array_t<double> tmp(shape, strides, data, base);
_self.property(_ph, IndexHandle(i)) = tmp;
}
}
/**
* This function template is used to expose mesh member functions that are only
* available for a specific type of mesh (i.e. they are available for polygon
......@@ -1398,43 +1310,65 @@ void expose_mesh(py::module& m, const char *_name) {
})
//======================================================================
// property_array
// numpy indices
//======================================================================
.def("property_array", [] (Mesh& _self, OM::VPropHandleT<py::none> _ph) {
return property_array<Mesh, OM::VPropHandleT<py::none>, OM::VertexHandle>(_self, _ph, _self.n_vertices());
})
.def("property_array", [] (Mesh& _self, OM::HPropHandleT<py::none> _ph) {
return property_array<Mesh, OM::HPropHandleT<py::none>, OM::HalfedgeHandle>(_self, _ph, _self.n_halfedges());
})
.def("property_array", [] (Mesh& _self, OM::EPropHandleT<py::none> _ph) {
return property_array<Mesh, OM::EPropHandleT<py::none>, OM::EdgeHandle>(_self, _ph, _self.n_edges());
})
.def("property_array", [] (Mesh& _self, OM::FPropHandleT<py::none> _ph) {
return property_array<Mesh, OM::FPropHandleT<py::none>, OM::FaceHandle>(_self, _ph, _self.n_faces());
})
.def("edge_vertex_indices", &edge_vertex_indices<Mesh>)
.def("ev_indices", &edge_vertex_indices<Mesh>)
.def("halfedge_vertex_indices", &halfedge_vertex_indices<Mesh>)
.def("hv_indices", &halfedge_vertex_indices<Mesh>)
//======================================================================
// set_property_array
// new property interface: single item
//======================================================================
.def("set_property_array", [] (Mesh& _self, OM::VPropHandleT<py::none> _ph, py::array_t<double, py::array::c_style | py::array::forcecast> _arr) {
return set_property_array<Mesh, OM::VPropHandleT<py::none>, OM::VertexHandle>(_self, _ph, _arr, _self.n_vertices());
})
.def("set_property_array", [] (Mesh& _self, OM::HPropHandleT<py::none> _ph, py::array_t<double, py::array::c_style | py::array::forcecast> _arr) {
return set_property_array<Mesh, OM::HPropHandleT<py::none>, OM::HalfedgeHandle>(_self, _ph, _arr, _self.n_halfedges());
})
.def("set_property_array", [] (Mesh& _self, OM::EPropHandleT<py::none> _ph, py::array_t<double, py::array::c_style | py::array::forcecast> _arr) {
return set_property_array<Mesh, OM::EPropHandleT<py::none>, OM::EdgeHandle>(_self, _ph, _arr, _self.n_edges());
})
.def("set_property_array", [] (Mesh& _self, OM::FPropHandleT<py::none> _ph, py::array_t<double, py::array::c_style | py::array::forcecast> _arr) {
return set_property_array<Mesh, OM::FPropHandleT<py::none>, OM::FaceHandle>(_self, _ph, _arr, _self.n_faces());
})
.def("vertex_property", &Mesh::template py_property<OM::VertexHandle, typename Mesh::VPropHandle>)
.def("halfedge_property", &Mesh::template py_property<OM::HalfedgeHandle, typename Mesh::HPropHandle>)
.def("edge_property", &Mesh::template py_property<OM::EdgeHandle, typename Mesh::EPropHandle>)
.def("face_property", &Mesh::template py_property<OM::FaceHandle, typename Mesh::FPropHandle>)
.def("edge_vertex_indices", &edge_vertex_indices<Mesh>)
.def("ev_indices", &edge_vertex_indices<Mesh>)
.def("halfedge_vertex_indices", &halfedge_vertex_indices<Mesh>)
.def("hv_indices", &halfedge_vertex_indices<Mesh>)
.def("set_vertex_property", &Mesh::template py_set_property<OM::VertexHandle, typename Mesh::VPropHandle>)
.def("set_halfedge_property", &Mesh::template py_set_property<OM::HalfedgeHandle, typename Mesh::HPropHandle>)
.def("set_edge_property", &Mesh::template py_set_property<OM::EdgeHandle, typename Mesh::EPropHandle>)
.def("set_face_property", &Mesh::template py_set_property<OM::FaceHandle, typename Mesh::FPropHandle>)
.def("has_vertex_property", &Mesh::template py_has_property<OM::VertexHandle>)
.def("has_halfedge_property", &Mesh::template py_has_property<OM::HalfedgeHandle>)
.def("has_edge_property", &Mesh::template py_has_property<OM::EdgeHandle>)
.def("has_face_property", &Mesh::template py_has_property<OM::FaceHandle>)
.def("remove_vertex_property", &Mesh::template py_remove_property<OM::VertexHandle>)
.def("remove_halfedge_property", &Mesh::template py_remove_property<OM::HalfedgeHandle>)
.def("remove_edge_property", &Mesh::template py_remove_property<OM::EdgeHandle>)
.def("remove_face_property", &Mesh::template py_remove_property<OM::FaceHandle>)
//======================================================================
// new property interface: generic
//======================================================================
.def("vertex_property", &Mesh::template py_property_generic<OM::VertexHandle, typename Mesh::VPropHandle>)
.def("halfedge_property", &Mesh::template py_property_generic<OM::HalfedgeHandle, typename Mesh::HPropHandle>)
.def("edge_property", &Mesh::template py_property_generic<OM::EdgeHandle, typename Mesh::EPropHandle>)
.def("face_property", &Mesh::template py_property_generic<OM::FaceHandle, typename Mesh::FPropHandle>)
.def("set_vertex_property", &Mesh::template py_set_property_generic<OM::VertexHandle, typename Mesh::VPropHandle>)
.def("set_halfedge_property", &Mesh::template py_set_property_generic<OM::HalfedgeHandle, typename Mesh::HPropHandle>)
.def("set_edge_property", &Mesh::template py_set_property_generic<OM::EdgeHandle, typename Mesh::EPropHandle>)
.def("set_face_property", &Mesh::template py_set_property_generic<OM::FaceHandle, typename Mesh::FPropHandle>)
//======================================================================
// new property interface: array
//======================================================================
.def("vertex_property_array", &Mesh::template py_property_array<OM::VertexHandle, typename Mesh::VPropHandle>)
.def("halfedge_property_array", &Mesh::template py_property_array<OM::HalfedgeHandle, typename Mesh::HPropHandle>)
.def("edge_property_array", &Mesh::template py_property_array<OM::EdgeHandle, typename Mesh::EPropHandle>)
.def("face_property_array", &Mesh::template py_property_array<OM::FaceHandle, typename Mesh::FPropHandle>)
.def("set_vertex_property_array", &Mesh::template py_set_property_array<OM::VertexHandle, typename Mesh::VPropHandle>)
.def("set_halfedge_property_array", &Mesh::template py_set_property_array<OM::HalfedgeHandle, typename Mesh::HPropHandle>)
.def("set_edge_property_array", &Mesh::template py_set_property_array<OM::EdgeHandle, typename Mesh::EPropHandle>)
.def("set_face_property_array", &Mesh::template py_set_property_array<OM::FaceHandle, typename Mesh::FPropHandle>)
;
expose_type_specific_functions(class_mesh);
......
......@@ -5,10 +5,17 @@
#define OM_STATIC_BUILD
#include "Utilities.hh"
#include <OpenMesh/Core/IO/MeshIO.hh>
#include <OpenMesh/Core/Mesh/TriMesh_ArrayKernelT.hh>
#include <OpenMesh/Core/Mesh/PolyMesh_ArrayKernelT.hh>
#include <pybind11/pybind11.h>
#include <pybind11/numpy.h>
namespace py = pybind11;
struct MeshTraits : public OpenMesh::DefaultTraits {
/** Use double precision points */
......@@ -26,7 +33,171 @@ struct MeshTraits : public OpenMesh::DefaultTraits {
typedef OpenMesh::Vec3d TexCoord3D;
};
typedef OpenMesh::TriMesh_ArrayKernelT<MeshTraits> TriMesh;
typedef OpenMesh::PolyMesh_ArrayKernelT<MeshTraits> PolyMesh;
template <class Mesh>
class MeshWrapperT : public Mesh {
public:
typedef OpenMesh::VPropHandleT<py::none> VPropHandle;
typedef OpenMesh::HPropHandleT<py::none> HPropHandle;
typedef OpenMesh::EPropHandleT<py::none> EPropHandle;
typedef OpenMesh::FPropHandleT<py::none> FPropHandle;
template <class Handle, class PropHandle>
py::none py_property(const std::string& _name, Handle _h) {
const auto prop = py_prop_on_demand<Handle, PropHandle>(_name);
return Mesh::property(prop, _h);
}
template <class Handle, class PropHandle>
void py_set_property(const std::string& _name, Handle _h, py::object _val) {
const auto prop = py_prop_on_demand<Handle, PropHandle>(_name);
Mesh::property(prop, _h) = _val;
}
template <class Handle>
bool py_has_property(const std::string& _name) {
auto& prop_map = py_prop_map(Handle());
return prop_map.count(_name);
}
template <class Handle>
void py_remove_property(const std::string& _name) {
auto& prop_map = py_prop_map(Handle());
if (prop_map.count(_name) != 0) {
Mesh::remove_property(prop_map.at(_name));
prop_map.erase(_name);
}
}
template <class Handle, class PropHandle>
py::list py_property_generic(const std::string& _name) {
const size_t n = py_n_items(Handle());
const auto prop = py_prop_on_demand<Handle, PropHandle>(_name);
py::list res;
for (size_t i = 0; i < n; ++i) {
res.append(Mesh::property(prop, Handle(i)));
}
return res;
}
template <class Handle, class PropHandle>
void py_set_property_generic(const std::string& _name, py::list _list) {
const size_t n = py_n_items(Handle());
const auto prop = py_prop_on_demand<Handle, PropHandle>(_name);
if (_list.size() != n) {
return;
}
for (size_t i = 0; i < n; ++i) {
Mesh::property(prop, Handle(i)) = py::object(_list[i]);
}
}
template <class Handle, class PropHandle>
py::array_t<double> py_property_array(const std::string& _name) {
const size_t n = py_n_items(Handle());
const auto prop = py_prop_on_demand<Handle, PropHandle>(_name);
// assume that all arrays have the same size and
// retrieve the size of the first array
const py::object tmp_obj = Mesh::property(prop, Handle(0));
py::array_t<double> tmp_arr;
try {
tmp_arr = tmp_obj.cast<py::array_t<double> >();
}
catch (py::error_already_set& e) {
return py::array_t<double>();
}
const size_t size = tmp_arr.size();
// better check this now
if (size == 0) {
return py::array_t<double>();
}
// allocate memory
double *data = new double[size * n];
// copy one array at a time
for (size_t i = 0; i < n; ++i) {
const Handle hnd(i);
const py::object obj = Mesh::property(prop, hnd);
try {
const auto arr = obj.cast<py::array_t<double> >();
if (arr.size() != size) {
throw py::error_already_set();
}
std::copy(arr.data(0), arr.data(0) + size, &data[size * i]);
}
catch (py::error_already_set& e) {
delete[] data;
return py::array_t<double>();
}
}
// make numpy array
const auto shape = {n, size};
const auto strides = {size * sizeof(double), sizeof(double)};
py::capsule base = free_when_done(data);
return py::array_t<double>(shape, strides, data, base);
}
template <class Handle, class PropHandle>
void py_set_property_array(const std::string& _name, py::array_t<double, py::array::c_style | py::array::forcecast> _arr) {
const size_t n = py_n_items(Handle());
const auto prop = py_prop_on_demand<Handle, PropHandle>(_name);
// array cannot be empty and its shape has to be (_n, m,...)
if (_arr.size() == 0 || _arr.ndim() < 2 || _arr.shape(0) != n) {
return;
}
// copy one array at a time
const size_t size = _arr.strides(0) / sizeof(double);
for (size_t i = 0; i < n; ++i) {
double *data = new double[size];
std::copy(_arr.data(i), _arr.data(i) + size, data);
const auto shape = {size};
const auto strides = {sizeof(double)};
py::capsule base = free_when_done(data);
py::array_t<double> tmp(shape, strides, data, base);
Mesh::property(prop, Handle(i)) = tmp;
}
}
private:
template <class Handle, class PropHandle>
PropHandle py_prop_on_demand(const std::string& _name) {
auto& prop_map = py_prop_map(Handle());
if (prop_map.count(_name) == 0) {
PropHandle prop;
Mesh::add_property(prop, _name);
prop_map[_name] = prop;
}
return prop_map.at(_name);
}
std::map<std::string, VPropHandle>& py_prop_map(OpenMesh::VertexHandle) { return vprop_map; }
std::map<std::string, HPropHandle>& py_prop_map(OpenMesh::HalfedgeHandle) { return hprop_map; }
std::map<std::string, EPropHandle>& py_prop_map(OpenMesh::EdgeHandle) { return eprop_map; }
std::map<std::string, FPropHandle>& py_prop_map(OpenMesh::FaceHandle) { return fprop_map; }
size_t py_n_items(OpenMesh::VertexHandle) const { return Mesh::n_vertices(); }
size_t py_n_items(OpenMesh::HalfedgeHandle) const { return Mesh::n_halfedges(); }
size_t py_n_items(OpenMesh::EdgeHandle) const { return Mesh::n_edges(); }
size_t py_n_items(OpenMesh::FaceHandle) const { return Mesh::n_faces(); }
std::map<std::string, VPropHandle> vprop_map;
std::map<std::string, HPropHandle> hprop_map;
std::map<std::string, EPropHandle> eprop_map;
std::map<std::string, FPropHandle> fprop_map;
};
typedef MeshWrapperT<OpenMesh::TriMesh_ArrayKernelT<MeshTraits> > TriMesh;
typedef MeshWrapperT<OpenMesh::PolyMesh_ArrayKernelT<MeshTraits> > PolyMesh;
#endif
#ifndef OPENMESH_PYTHON_UTILITIES_HH
#define OPENMESH_PYTHON_UTILITIES_HH
#include <pybind11/pybind11.h>
namespace py = pybind11;
template<class dtype>
py::capsule free_when_done(dtype *data) {
return py::capsule(data, [](void *f) {
dtype *ptr = reinterpret_cast<dtype *>(f);
delete[] ptr;
});
}
#endif
......@@ -10,83 +10,75 @@ class Python(unittest.TestCase):
openmesh.read_mesh(self.mesh, 'TestFiles/cube-minimal.obj')
def test_vertex_property_array(self):
prop = openmesh.VPropHandle()
self.mesh.add_property(prop)
# c_contiguous
arr1 = np.random.rand(self.mesh.n_vertices(), 10)
self.mesh.set_property_array(prop, arr1)
arr2 = self.mesh.property_array(prop)
self.mesh.set_vertex_property_array('random', arr1)
arr2 = self.mesh.vertex_property_array('random')
self.assertTrue(np.allclose(arr1, arr2))
for vh in self.mesh.vertices():
arr3 = self.mesh.property(prop, vh)
arr3 = self.mesh.vertex_property('random', vh)
self.assertTrue(np.allclose(arr1[vh.idx()], arr3))
# f_contiguous
arr1 = np.random.rand(10, self.mesh.n_vertices())
self.mesh.set_property_array(prop, arr1.T)
arr2 = self.mesh.property_array(prop)
self.mesh.set_vertex_property_array('random', arr1.T)
arr2 = self.mesh.vertex_property_array('random')
self.assertTrue(np.allclose(arr1.T, arr2))
for vh in self.mesh.vertices():
arr3 = self.mesh.property(prop, vh)
arr3 = self.mesh.vertex_property('random', vh)
self.assertTrue(np.allclose(arr1.T[vh.idx()], arr3))
def test_halfedge_property_array(self):
prop = openmesh.HPropHandle()
self.mesh.add_property(prop)
# c_contiguous
arr1 = np.random.rand(self.mesh.n_halfedges(), 10)
self.mesh.set_property_array(prop, arr1)
arr2 = self.mesh.property_array(prop)
self.mesh.set_halfedge_property_array('random', arr1)
arr2 = self.mesh.halfedge_property_array('random')
self.assertTrue(np.allclose(arr1, arr2))
for hh in self.mesh.halfedges():
arr3 = self.mesh.property(prop, hh)
arr3 = self.mesh.halfedge_property('random', hh)
self.assertTrue(np.allclose(arr1[hh.idx()], arr3))
# f_contiguous
arr1 = np.random.rand(10, self.mesh.n_halfedges())
self.mesh.set_property_array(prop, arr1.T)
arr2 = self.mesh.property_array(prop)
self.mesh.set_halfedge_property_array('random', arr1.T)
arr2 = self.mesh.halfedge_property_array('random')
self.assertTrue(np.allclose(arr1.T, arr2))
for hh in self.mesh.halfedges():
arr3 = self.mesh.property(prop, hh)
arr3 = self.mesh.halfedge_property('random', hh)
self.assertTrue(np.allclose(arr1.T[hh.idx()], arr3))
def test_edge_property_array(self):
prop = openmesh.EPropHandle()
self.mesh.add_property(prop)
# c_contiguous
arr1 = np.random.rand(self.mesh.n_edges(), 10)
self.mesh.set_property_array(prop, arr1)
arr2 = self.mesh.property_array(prop)
self.mesh.set_edge_property_array('random', arr1)
arr2 = self.mesh.edge_property_array('random')
self.assertTrue(np.allclose(arr1, arr2))
for eh in self.mesh.edges():
arr3 = self.mesh.property(prop, eh)
arr3 = self.mesh.edge_property('random', eh)
self.assertTrue(np.allclose(arr1[eh.idx()], arr3))
# f_contiguous
arr1 = np.random.rand(10, self.mesh.n_edges())
self.mesh.set_property_array(prop, arr1.T)
arr2 = self.mesh.property_array(prop)
self.mesh.set_edge_property_array('random', arr1.T)
arr2 = self.mesh.edge_property_array('random')
self.assertTrue(np.allclose(arr1.T, arr2))
for eh in self.mesh.edges():
arr3 = self.mesh.property(prop, eh)
arr3 = self.mesh.edge_property('random', eh)
self.assertTrue(np.allclose(arr1.T[eh.idx()], arr3))
def test_face_property_array(self):
prop = openmesh.FPropHandle()
self.mesh.add_property(prop)
# c_contiguous
arr1 = np.random.rand(self.mesh.n_faces(), 10)
self.mesh.set_property_array(prop, arr1)
arr2 = self.mesh.property_array(prop)
self.mesh.set_face_property_array('random', arr1)
arr2 = self.mesh.face_property_array('random')
self.assertTrue(np.allclose(arr1, arr2))
for fh in self.mesh.faces():
arr3 = self.mesh.property(prop, fh)
arr3 = self.mesh.face_property('random', fh)
self.assertTrue(np.allclose(arr1[fh.idx()], arr3))
# f_contiguous
arr1 = np.random.rand(10, self.mesh.n_faces())
self.mesh.set_property_array(prop, arr1.T)
arr2 = self.mesh.property_array(prop)
self.mesh.set_face_property_array('random', arr1.T)
arr2 = self.mesh.face_property_array('random')
self.assertTrue(np.allclose(arr1.T, arr2))
for fh in self.mesh.faces():
arr3 = self.mesh.property(prop, fh)
arr3 = self.mesh.face_property('random', fh)
self.assertTrue(np.allclose(arr1.T[fh.idx()], arr3))
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment