ACloudViewer  3.9.4
A Modern Library for 3D Data Processing
wrappers.h
Go to the documentation of this file.
1 // ----------------------------------------------------------------------------
2 // - CloudViewer: www.cloudViewer.org -
3 // ----------------------------------------------------------------------------
4 // Copyright (c) 2018-2024 www.cloudViewer.org
5 // SPDX-License-Identifier: MIT
6 // ----------------------------------------------------------------------------
7 
8 #pragma once
9 
10 #include <CVShareable.h>
11 #include <CVTypes.h>
12 #include <PointCloudTpl.h>
13 
14 #include <pybind11/numpy.h>
15 #include <pybind11/pybind11.h>
16 
17 #include <stdexcept>
18 #include <type_traits>
19 
20 namespace py = pybind11;
21 using namespace pybind11::literals;
22 
24 template <class T> using observer_ptr = std::unique_ptr<T, py::nodelete>;
25 
30 template <class T> class CCShareableHolder
31 {
32  public:
33  static_assert(std::is_base_of<CCShareable, T>::value == true, "T must be a subclass of CCShareable");
34 
35  CCShareableHolder() = default;
36 
37  explicit CCShareableHolder(T *obj) : m_ptr(obj)
38  {
39  if (m_ptr)
40  {
41  m_ptr->link();
42  }
43  }
44 
45  T *get()
46  {
47  return m_ptr;
48  }
49 
50  const T *get() const
51  {
52  return m_ptr;
53  }
54 
56  {
57  if (m_ptr)
58  {
59  m_ptr->release();
60  m_ptr = nullptr;
61  }
62  }
63 
64  private:
65  T *m_ptr = nullptr;
66 };
67 
69 
70 namespace PyCC
71 {
72 inline void NoOpDelete(void *) {}
73 
74 template <class T> py::array_t<T> SpanAsNumpyArray(T *data, py::array::ShapeContainer shape)
75 {
76  auto capsule = py::capsule(data, NoOpDelete);
77  return py::array(shape, data, capsule);
78 }
79 
80 template <class T> py::array_t<T> SpanAsNumpyArray(T *data, size_t len)
81 {
82  auto capsule = py::capsule(data, NoOpDelete);
83  return py::array(len, data, capsule);
84 }
85 
86 template <class T> py::array_t<T> VectorAsNumpyArray(std::vector<T> &vector)
87 {
88  // https://stackoverflow.com/questions/44659924/returning-numpy-arrays-via-pybind11
89  // https://github.com/pybind/pybind11/issues/1042
90  return SpanAsNumpyArray(vector.data(), vector.size());
91 }
92 
93 template <class PointCloudType>
94 void addPointsFromArrays(PointCloudType &self,
95  py::array_t<PointCoordinateType> &xs,
96  py::array_t<PointCoordinateType> &ys,
97  py::array_t<PointCoordinateType> &zs)
98 {
99  if (xs.size() != ys.size() || xs.size() != zs.size())
100  {
101  throw py::value_error("xs, ys, zs must have the same size");
102  }
103 
104  const py::ssize_t numToReserve = self.size() + xs.size();
105  if (numToReserve > std::numeric_limits<unsigned int>::max())
106  {
107  throw std::out_of_range(std::to_string(numToReserve) + " cannot be casted to unsigned int");
108  }
109  self.reserve(static_cast<unsigned int>(numToReserve));
110 
111  auto xs_it = xs.begin();
112  auto ys_it = ys.begin();
113  auto zs_it = zs.begin();
114 
115  for (; xs_it != xs.end();)
116  {
117  self.addPoint({xs_it->cast<PointCoordinateType>(),
118  ys_it->cast<PointCoordinateType>(),
119  zs_it->cast<PointCoordinateType>()});
120  ++xs_it;
121  ++ys_it;
122  ++zs_it;
123  }
124 }
125 } // namespace PyCC
126 
127 static const constexpr char ADD_SCALAR_FIELD_DOCSTRING[] = R"doc(
128  Adds a scalar field with the given name to the point cloud.
129 
130  Parameters
131  ----------
132  name: str
133  name of the scalar field that will be added.
134  values: optional, numpy.array, list of float
135  values to use when initializing the new scalar field
136 
137  Raises
138  ------
139  RuntimeError if the point cloud already has a scalar field with the given ``name``
140  ValueError if values are provided don't have the same length (size) as the point cloud
141 
142 )doc";
143 
144 static const constexpr char SIZE_SCALAR_FIELD_DOCSTRING[] = R"doc(
145  Returns the size (number of points) in the point cloud.
146 
147  ``len`` also works as an alias to size.
148 
149  .. code:: Python
150 
151  pc = pycc.ccPointCloud("name")
152  assert len(pc) == pc.size()
153 )doc";
154 
155 #define DEFINE_POINTCLOUDTPL(T, module, name) \
156  py::class_<cloudViewer::PointCloudTpl<T>, T>(module, name) \
157  .def("size", &cloudViewer::PointCloudTpl<T>::size, SIZE_SCALAR_FIELD_DOCSTRING) \
158  .def("forEach", &cloudViewer::PointCloudTpl<T>::forEach, "action"_a) \
159  .def("getBoundingBox", &cloudViewer::PointCloudTpl<T>::getBoundingBox, "bbMin"_a, "bbMax"_a) \
160  .def("getNextPoint", \
161  &cloudViewer::PointCloudTpl<T>::getNextPoint, \
162  py::return_value_policy::reference) \
163  .def("enableScalarField", &cloudViewer::PointCloudTpl<T>::enableScalarField) \
164  .def("isScalarFieldEnabled", &cloudViewer::PointCloudTpl<T>::isScalarFieldEnabled) \
165  .def("setPointScalarValue", \
166  &cloudViewer::PointCloudTpl<T>::setPointScalarValue, \
167  "pointIndex"_a, \
168  "value"_a) \
169  .def("getPointScalarValue", &cloudViewer::PointCloudTpl<T>::getPointScalarValue, "pointIndex"_a) \
170  .def("resize", &cloudViewer::PointCloudTpl<T>::resize, "newCount"_a) \
171  .def("reserve", &cloudViewer::PointCloudTpl<T>::reserve, "newCapacity"_a) \
172  .def("reset", &cloudViewer::PointCloudTpl<T>::reset) \
173  .def("invalidateBoundingBox", &cloudViewer::PointCloudTpl<T>::invalidateBoundingBox) \
174  .def("getNumberOfScalarFields", \
175  &cloudViewer::PointCloudTpl<T>::getNumberOfScalarFields, \
176  "Returns the number of scalar field of the point cloud") \
177  .def( \
178  "getScalarField", \
179  &cloudViewer::PointCloudTpl<T>::getScalarField, \
180  "index"_a, \
181  R"doc( \
182  Returns the scalar field identified by its index. \
183  \
184  If index is invalid, None is returned \
185 )doc") \
186  .def( \
187  "getScalarFieldName", \
188  &cloudViewer::PointCloudTpl<T>::getScalarFieldName, \
189  "index"_a, \
190  R"doc( \
191  Returns the name of the scalar field identified by the index. \
192  \
193  If index is invalid, -1 is returned \
194 )doc") \
195  .def( \
196  "getScalarFieldIndexByName", \
197  &cloudViewer::PointCloudTpl<T>::getScalarFieldIndexByName, \
198  "name"_a, \
199  R"doc( \
200  Returns the scalar field identified by its name. \
201  \
202  If no scalar field has the given name, None is returned \
203 )doc") \
204  .def("getCurrentInScalarField", &cloudViewer::PointCloudTpl<T>::getCurrentInScalarField) \
205  .def("getCurrentOutScalarField", &cloudViewer::PointCloudTpl<T>::getCurrentOutScalarField) \
206  .def("setCurrentInScalarField", &cloudViewer::PointCloudTpl<T>::setCurrentInScalarField, "index"_a) \
207  .def("getCurrentInScalarFieldIndex", &cloudViewer::PointCloudTpl<T>::getCurrentInScalarFieldIndex) \
208  .def( \
209  "setCurrentOutScalarField", &cloudViewer::PointCloudTpl<T>::setCurrentOutScalarField, "index"_a) \
210  .def("getCurrentOutScalarFieldIndex", &cloudViewer::PointCloudTpl<T>::getCurrentOutScalarFieldIndex) \
211  .def("setCurrentScalarField", &cloudViewer::PointCloudTpl<T>::setCurrentScalarField, "index"_a) \
212  .def("renameScalarField", &cloudViewer::PointCloudTpl<T>::renameScalarField, "index"_a, "newName"_a) \
213  .def( \
214  "addScalarField", \
215  [](cloudViewer::PointCloudTpl<T> &self, \
216  const char *sfName, \
217  const py::object &maybe_values = py::none()) \
218  { \
219  int idx = self.addScalarField(sfName); \
220  if (idx == -1) \
221  { \
222  throw std::runtime_error("Failed to add scalar field"); \
223  } \
224  if (!maybe_values.is_none()) \
225  { \
226  py::array_t<ScalarType> values(maybe_values); \
227  if (values.size() != self.size()) \
228  { \
229  throw py::value_error("value must have the same len as the poinc cloud size"); \
230  } \
231  auto values_u = values.unchecked<1>(); \
232  cloudViewer::ScalarField *sf = self.getScalarField(idx); \
233  for (py::ssize_t i{0}; i < values.size(); ++i) \
234  { \
235  sf->setValue(i, values_u(i)); \
236  } \
237  } \
238  return idx; \
239  }, \
240  "name"_a, \
241  "values"_a = py::none(), \
242  ADD_SCALAR_FIELD_DOCSTRING) \
243  .def( \
244  "deleteScalarField", \
245  &cloudViewer::PointCloudTpl<T>::deleteScalarField, \
246  "index"_a, \
247  R"doc( \
248  Removes the scalar field identified by the index . \
249  \
250  .. warning:: \
251  This operation may modify the scalar fields order \
252  (especially if the deleted SF is not the last one). \
253  However current IN & OUT scalar fields will stay up-to-date \
254  (while their index may change). \
255  \
256  Does nothing if index is invalid \
257 )doc") \
258  .def("deleteAllScalarFields", \
259  &cloudViewer::PointCloudTpl<T>::deleteAllScalarFields, \
260  "Deletes all scalar fields associated to this cloud") \
261  .def( \
262  "addPoint", \
263  [](cloudViewer::PointCloudTpl<T> &self, const CCVector3 &P) { self.addPoint(P); }, \
264  "P"_a, \
265  R"doc( \
266  Adds a 3D point to the point cloud \
267  \
268  .. note:: \
269  For better performances it is better to use :meth:`.addPoints`. \
270 )doc") \
271  .def( \
272  "addPoints", \
273  &PyCC::addPointsFromArrays<cloudViewer::PointCloudTpl<T>>, \
274  "xs"_a, \
275  "ys"_a, \
276  "zs"_a, \
277  R"doc( \
278  Takes values from xs, yz, zs array and add them as points of the point cloud. \
279  \
280  Raises \
281  ------ \
282  Value error if xs,ys,zs do not have the same length \
283 )doc") \
284  .def("__len__", &cloudViewer::PointCloudTpl<T>::size);
float PointCoordinateType
Type of the coordinates of a (N-D) point.
Definition: CVTypes.h:16
CCShareableHolder(T *obj)
Definition: wrappers.h:37
CCShareableHolder()=default
const T * get() const
Definition: wrappers.h:50
int max(int a, int b)
Definition: cutil_math.h:48
Definition: wrappers.h:71
py::array_t< T > SpanAsNumpyArray(T *data, size_t len)
Definition: wrappers.h:80
void NoOpDelete(void *)
Definition: wrappers.h:72
py::array_t< T > VectorAsNumpyArray(std::vector< T > &vector)
Definition: wrappers.h:86
void addPointsFromArrays(PointCloudType &self, py::array_t< PointCoordinateType > &xs, py::array_t< PointCoordinateType > &ys, py::array_t< PointCoordinateType > &zs)
Definition: wrappers.h:94
std::string to_string(const T &n)
Definition: Common.h:20
PYBIND11_DECLARE_HOLDER_TYPE(T, CCShareableHolder< T >)
static constexpr const char SIZE_SCALAR_FIELD_DOCSTRING[]
Definition: wrappers.h:144
static constexpr const char ADD_SCALAR_FIELD_DOCSTRING[]
Definition: wrappers.h:127
std::unique_ptr< T, py::nodelete > observer_ptr
A unique_ptr that never free its ptr.
Definition: wrappers.h:24