ACloudViewer  3.9.4
A Modern Library for 3D Data Processing
trianglemesh.cpp
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 
9 
10 #include <stdint.h>
11 
12 #include <string>
13 #include <unordered_map>
14 
18 #include "pybind/docstring.h"
20 
21 namespace cloudViewer {
22 namespace t {
23 namespace geometry {
24 
25 void pybind_trianglemesh(py::module& m) {
26  py::class_<TriangleMesh, PyGeometry<TriangleMesh>,
27  std::shared_ptr<TriangleMesh>, Geometry, DrawableGeometry>
28  triangle_mesh(m, "TriangleMesh",
29  R"(
30 A triangle mesh contains vertices and triangles. The triangle mesh class stores
31 the attribute data in key-value maps. There are two maps: the vertex attributes
32 map, and the triangle attribute map.
33 
34 The attributes of the triangle mesh have different levels::
35 
36 import cloudViewer as cv3d
37 
38 device = cv3d.core.Device("CPU:0")
39 dtype_f = cv3d.core.float32
40 dtype_i = cv3d.core.int32
41 
42 # Create an empty triangle mesh
43 # Use mesh.vertex to access the vertices' attributes
44 # Use mesh.triangle to access the triangles' attributes
45 mesh = cv3d.t.geometry.TriangleMesh(device)
46 
47 # Default attribute: vertex.positions, triangle.indices
48 # These attributes is created by default and is required by all triangle
49 # meshes. The shape of both must be (N, 3). The device of "positions"
50 # determines the device of the triangle mesh.
51 mesh.vertex.positions = cv3d.core.Tensor([[0, 0, 0],
52  [0, 0, 1],
53  [0, 1, 0],
54  [0, 1, 1]], dtype_f, device)
55 mesh.triangle.indices = cv3d.core.Tensor([[0, 1, 2],
56  [0, 2, 3]]], dtype_i, device)
57 
58 # Common attributes: vertex.colors , vertex.normals
59 # triangle.colors, triangle.normals
60 # Common attributes are used in built-in triangle mesh operations. The
61 # spellings must be correct. For example, if "normal" is used instead of
62 # "normals", some internal operations that expects "normals" will not work.
63 # "normals" and "colors" must have shape (N, 3) and must be on the same
64 # device as the triangle mesh.
65 mesh.vertex.normals = cv3d.core.Tensor([[0, 0, 1],
66  [0, 1, 0],
67  [1, 0, 0],
68  [1, 1, 1]], dtype_f, device)
69 mesh.vertex.colors = cv3d.core.Tensor([[0.0, 0.0, 0.0],
70  [0.1, 0.1, 0.1],
71  [0.2, 0.2, 0.2],
72  [0.3, 0.3, 0.3]], dtype_f, device)
73 mesh.triangle.normals = cv3d.core.Tensor(...)
74 mesh.triangle.colors = cv3d.core.Tensor(...)
75 
76 # User-defined attributes
77 # You can also attach custom attributes. The value tensor must be on the
78 # same device as the triangle mesh. The are no restrictions on the shape and
79 # dtype, e.g.,
80 pcd.vertex.labels = cv3d.core.Tensor(...)
81 pcd.triangle.features = cv3d.core.Tensor(...)
82 )");
83 
84  // Constructors.
85  triangle_mesh
86  .def(py::init<const core::Device&>(),
87  "Construct an empty trianglemesh on the provided ``device`` "
88  "(default: 'CPU:0').",
89  "device"_a = core::Device("CPU:0"))
90  .def(py::init<const core::Tensor&, const core::Tensor&>(),
91  "vertex_positions"_a, "triangle_indices"_a)
92  .def("__repr__", &TriangleMesh::ToString);
93 
94  py::detail::bind_copy_functions<TriangleMesh>(triangle_mesh);
95  // Pickle support.
96  triangle_mesh.def(py::pickle(
97  [](const TriangleMesh& mesh) {
98  // __getstate__
99  return py::make_tuple(mesh.GetDevice(), mesh.GetVertexAttr(),
100  mesh.GetTriangleAttr());
101  },
102  [](py::tuple t) {
103  // __setstate__
104  if (t.size() != 3) {
106  "Cannot unpickle TriangleMesh! Expecting a tuple "
107  "of size 3.");
108  }
109 
110  const core::Device device = t[0].cast<core::Device>();
111  TriangleMesh mesh(device);
112  if (!device.IsAvailable()) {
114  "Device ({}) is not available. TriangleMesh will "
115  "be created on CPU.",
116  device.ToString());
117  mesh.To(core::Device("CPU:0"));
118  }
119 
120  const TensorMap vertex_attr = t[1].cast<TensorMap>();
121  const TensorMap triangle_attr = t[2].cast<TensorMap>();
122  for (auto& kv : vertex_attr) {
123  mesh.SetVertexAttr(kv.first, kv.second);
124  }
125  for (auto& kv : triangle_attr) {
126  mesh.SetTriangleAttr(kv.first, kv.second);
127  }
128 
129  return mesh;
130  }));
131 
132  // Triangle mesh's attributes: vertices, vertex_colors, vertex_normals, etc.
133  // def_property_readonly is sufficient, since the returned TensorMap can
134  // be editable in Python. We don't want the TensorMap to be replaced
135  // by another TensorMap in Python.
136  triangle_mesh.def_property_readonly(
137  "vertex",
138  py::overload_cast<>(&TriangleMesh::GetVertexAttr, py::const_));
139  triangle_mesh.def_property_readonly(
140  "triangle",
141  py::overload_cast<>(&TriangleMesh::GetTriangleAttr, py::const_));
142 
143  // Device transfers.
144  triangle_mesh.def("to", &TriangleMesh::To,
145  "Transfer the triangle mesh to a specified device.",
146  "device"_a, "copy"_a = false);
147  triangle_mesh.def("clone", &TriangleMesh::Clone,
148  "Returns copy of the triangle mesh on the same device.");
149 
150  triangle_mesh.def(
151  "cpu",
152  [](const TriangleMesh& triangle_mesh) {
153  return triangle_mesh.To(core::Device("CPU:0"));
154  },
155  "Transfer the triangle mesh to CPU. If the triangle mesh "
156  "is already on CPU, no copy will be performed.");
157  triangle_mesh.def(
158  "cuda",
159  [](const TriangleMesh& triangle_mesh, int device_id) {
160  return triangle_mesh.To(core::Device("CUDA", device_id));
161  },
162  "Transfer the triangle mesh to a CUDA device. If the triangle mesh "
163  "is already on the specified CUDA device, no copy will be "
164  "performed.",
165  "device_id"_a = 0);
166 
167  // Triangle Mesh's specific functions.
168  triangle_mesh.def("get_min_bound", &TriangleMesh::GetMinBound,
169  "Returns the min bound for point coordinates.");
170  triangle_mesh.def("get_max_bound", &TriangleMesh::GetMaxBound,
171  "Returns the max bound for point coordinates.");
172  triangle_mesh.def("get_center", &TriangleMesh::GetCenter,
173  "Returns the center for point coordinates.");
174  triangle_mesh.def("transform", &TriangleMesh::Transform, "transformation"_a,
175  "Transforms the points and normals (if exist).");
176  triangle_mesh.def("translate", &TriangleMesh::Translate, "translation"_a,
177  "relative"_a = true, "Translates points.");
178  triangle_mesh.def("scale", &TriangleMesh::Scale, "scale"_a, "center"_a,
179  "Scale points.");
180  triangle_mesh.def("rotate", &TriangleMesh::Rotate, "R"_a, "center"_a,
181  "Rotate points and normals (if exist).");
182 
183  triangle_mesh.def(
184  "normalize_normals", &TriangleMesh::NormalizeNormals,
185  "Normalize both triangle normals and vertex normals to length 1.");
186  triangle_mesh.def("compute_triangle_normals",
188  "Function to compute triangle normals, usually called "
189  "before rendering.",
190  "normalized"_a = true);
191  triangle_mesh.def("compute_vertex_normals",
193  "Function to compute vertex normals, usually called "
194  "before rendering.",
195  "normalized"_a = true);
196 
197  triangle_mesh.def(
198  "get_surface_area", &TriangleMesh::GetSurfaceArea,
199  R"(Computes the surface area of the mesh, i.e., the sum of the individual triangle surfaces.
200 
201 Example:
202 This computes the surface area of the Stanford Bunny::
203  bunny = cv3d.data.BunnyMesh()
204  mesh = cv3d.t.io.read_triangle_mesh(bunny.path)
205  print('The surface area is', mesh.get_surface_area())
206 
207 Returns:
208 A scalar describing the surface area of the mesh.
209 )");
210 
211  triangle_mesh.def(
212  "compute_convex_hull", &TriangleMesh::ComputeConvexHull,
213  "joggle_inputs"_a = false,
214  R"(Compute the convex hull of a point cloud using qhull. This runs on the CPU.
215 
216 Args:
217 joggle_inputs (bool, default=False): Handle precision problems by
218  randomly perturbing the input data. Set to True if perturbing the input
219  iis acceptable but you need convex simplicial output. If False,
220  neighboring facets may be merged in case of precision problems. See
221  `QHull docs <http://www.qhull.org/html/qh-impre.htm#joggle`__ for more
222  details.
223 
224 Returns:
225 TriangleMesh representing the convexh hull. This contains an
226 extra vertex property "point_indices" that contains the index of the
227 corresponding vertex in the original mesh.
228 
229 Example:
230 We will load the Stanford Bunny dataset, compute and display it's convex hull::
231 
232  bunny = cv3d.data.BunnyMesh()
233  mesh = cv3d.t.geometry.TriangleMesh.from_legacy(cv3d.io.read_triangle_mesh(bunny.path))
234  hull = mesh.compute_convex_hull()
235  cv3d.visualization.draw([{'name': 'bunny', 'geometry': mesh}, {'name': 'convex hull', 'geometry': hull}])
236 )");
237 
238  // creation
239  triangle_mesh.def_static(
240  "from_legacy", &TriangleMesh::FromLegacy, "mesh_legacy"_a,
241  "vertex_dtype"_a = core::Float32, "triangle_dtype"_a = core::Int64,
242  "device"_a = core::Device("CPU:0"),
243  "Create a TriangleMesh from a legacy CloudViewer TriangleMesh.");
244  triangle_mesh.def_static(
245  "from_triangle_mesh_model", &TriangleMesh::FromTriangleMeshModel,
246  "model"_a, "vertex_dtype"_a = core::Float32,
247  "triangle_dtype"_a = core::Int64,
248  "device"_a = core::Device("CPU:0"),
249  R"(Convert a TriangleMeshModel (e.g. as read from a file with
250 `cloudViewer.io.read_triangle_mesh_model()`) to a dictionary of mesh names to
251 triangle meshes with the specified vertex and triangle dtypes and moved to the
252 specified device. Only a single material per mesh is supported. Materials common
253 to multiple meshes will be duplicated. Textures (as t.geometry.Image) will use
254 shared storage on the CPU (GPU resident images for textures is not yet supported).
255 
256 Returns:
257 Dictionary of names to triangle meshes.
258 
259 Example:
260 Converting the FlightHelmetModel to a dictionary of triangle meshes::
261 
262  flight_helmet = cv3d.data.FlightHelmetModel()
263  model = cv3d.io.read_triangle_model(flight_helmet.path)
264  mesh_dict = cv3d.t.geometry.TriangleMesh.from_triangle_mesh_model(model)
265  cv3d.visualization.draw(list({"name": name, "geometry": tmesh} for
266  (name, tmesh) in mesh_dict.items()))
267 )");
268  // conversion
269  triangle_mesh.def("to_legacy", &TriangleMesh::ToLegacy,
270  "Convert to a legacy CloudViewer TriangleMesh.");
271 
272  triangle_mesh.def("clip_plane", &TriangleMesh::ClipPlane, "point"_a,
273  "normal"_a,
274  R"(Returns a new triangle mesh clipped with the plane.
275 
276 This method clips the triangle mesh with the specified plane.
277 Parts of the mesh on the positive side of the plane will be kept and triangles
278 intersected by the plane will be cut.
279 
280 Args:
281 point (cloudViewer.core.Tensor): A point on the plane.
282 
283 normal (cloudViewer.core.Tensor): The normal of the plane. The normal points to
284  the positive side of the plane for which the geometry will be kept.
285 
286 Returns:
287 New triangle mesh clipped with the plane.
288 
289 
290 This example shows how to create a hemisphere from a sphere::
291 
292 import cloudViewer as cv3d
293 
294 sphere = cv3d.t.geometry.TriangleMesh.from_legacy(cv3d.geometry.ccMesh.create_sphere())
295 hemisphere = sphere.clip_plane(point=[0,0,0], normal=[1,0,0])
296 
297 cv3d.visualization.draw(hemisphere)
298 )");
299 
300  triangle_mesh.def(
301  "slice_plane",
302  // Accept anything for contour_values that pybind can convert to
303  // std::list. This also avoids cv3d.utility.DoubleVector.
304  [](const TriangleMesh& self, const core::Tensor& point,
305  const core::Tensor& normal, std::list<double> contour_values) {
306  std::vector<double> cv(contour_values.begin(),
307  contour_values.end());
308  return self.SlicePlane(point, normal, cv);
309  },
310  "point"_a, "normal"_a, "contour_values"_a = std::list<double>{0.0},
311  R"(Returns a line set with the contour slices defined by the plane and values.
312 
313 This method generates slices as LineSet from the mesh at specific contour
314 values with respect to a plane.
315 
316 Args:
317 point (cloudViewer.core.Tensor): A point on the plane.
318 normal (cloudViewer.core.Tensor): The normal of the plane.
319 contour_values (list): A list of contour values at which slices will be
320  generated. The value describes the signed distance to the plane.
321 
322 Returns:
323 LineSet with the extracted contours.
324 
325 
326 This example shows how to create a hemisphere from a sphere::
327 
328 import cloudViewer as cv3d
329 import numpy as np
330 
331 bunny = cv3d.data.BunnyMesh()
332 mesh = cv3d.t.geometry.TriangleMesh.from_legacy(cv3d.io.read_triangle_mesh(bunny.path))
333 contours = mesh.slice_plane([0,0,0], [0,1,0], np.linspace(0,0.2))
334 cv3d.visualization.draw([{'name': 'bunny', 'geometry': contours}])
335 
336 )");
337 
338  // Triangle Mesh's creation APIs.
339  triangle_mesh
340  .def_static("create_box", &TriangleMesh::CreateBox,
341  "Create a box triangle mesh. One vertex of the box "
342  "will be placed at the origin and the box aligns "
343  "with the positive x, y, and z axes.",
344  "width"_a = 1.0, "height"_a = 1.0, "depth"_a = 1.0,
345  "float_dtype"_a = core::Float32,
346  "int_dtype"_a = core::Int64,
347  "device"_a = core::Device("CPU:0"))
348  .def_static("create_sphere", &TriangleMesh::CreateSphere,
349  "Create a sphere mesh centered at (0, 0, 0).",
350  "radius"_a = 1.0, "resolution"_a = 20,
351  "float_dtype"_a = core::Float32,
352  "int_dtype"_a = core::Int64,
353  "device"_a = core::Device("CPU:0"))
354  .def_static("create_tetrahedron", &TriangleMesh::CreateTetrahedron,
355  "Create a tetrahedron mesh centered at (0, 0, 0).",
356  "radius"_a = 1.0, "float_dtype"_a = core::Float32,
357  "int_dtype"_a = core::Int64,
358  "device"_a = core::Device("CPU:0"))
359  .def_static("create_octahedron", &TriangleMesh::CreateOctahedron,
360  "Create a octahedron mesh centered at (0, 0, 0).",
361  "radius"_a = 1.0, "float_dtype"_a = core::Float32,
362  "int_dtype"_a = core::Int64,
363  "device"_a = core::Device("CPU:0"))
364  .def_static("create_icosahedron", &TriangleMesh::CreateIcosahedron,
365  "Create a icosahedron mesh centered at (0, 0, 0).",
366  "radius"_a = 1.0, "float_dtype"_a = core::Float32,
367  "int_dtype"_a = core::Int64,
368  "device"_a = core::Device("CPU:0"))
369  .def_static("create_cylinder", &TriangleMesh::CreateCylinder,
370  "Create a cylinder mesh.", "radius"_a = 1.0,
371  "height"_a = 2.0, "resolution"_a = 20, "split"_a = 4,
372  "float_dtype"_a = core::Float32,
373  "int_dtype"_a = core::Int64,
374  "device"_a = core::Device("CPU:0"))
375  .def_static("create_cone", &TriangleMesh::CreateCone,
376  "Create a cone mesh.", "radius"_a = 1.0,
377  "height"_a = 2.0, "resolution"_a = 20, "split"_a = 1,
378  "float_dtype"_a = core::Float32,
379  "int_dtype"_a = core::Int64,
380  "device"_a = core::Device("CPU:0"))
381  .def_static("create_torus", &TriangleMesh::CreateTorus,
382  "Create a torus mesh.", "torus_radius"_a = 1.0,
383  "tube_radius"_a = 0.5, "radial_resolution"_a = 30,
384  "tubular_resolution"_a = 20,
385  "float_dtype"_a = core::Float32,
386  "int_dtype"_a = core::Int64,
387  "device"_a = core::Device("CPU:0"))
388  .def_static("create_arrow", &TriangleMesh::CreateArrow,
389  "Create a arrow mesh.", "cylinder_radius"_a = 1.0,
390  "cone_radius"_a = 1.5, "cylinder_height"_a = 5.0,
391  "cone_height"_a = 4.0, "resolution"_a = 20,
392  "cylinder_split"_a = 4, "cone_split"_a = 1,
393  "float_dtype"_a = core::Float32,
394  "int_dtype"_a = core::Int64,
395  "device"_a = core::Device("CPU:0"))
396  .def_static("create_coordinate_frame",
398  "Create a coordinate frame mesh.", "size"_a = 1.0,
399  "origin"_a = Eigen::Vector3d(0.0, 0.0, 0.0),
400  "float_dtype"_a = core::Float32,
401  "int_dtype"_a = core::Int64,
402  "device"_a = core::Device("CPU:0"))
403  .def_static("create_mobius", &TriangleMesh::CreateMobius,
404  "Create a Mobius strip.", "length_split"_a = 70,
405  "width_split"_a = 15, "twists"_a = 1, "radius"_a = 1,
406  "flatness"_a = 1, "width"_a = 1, "scale"_a = 1,
407  "float_dtype"_a = core::Float32,
408  "int_dtype"_a = core::Int64,
409  "device"_a = core::Device("CPU:0"));
410 
412  m, "TriangleMesh", "create_box",
413  {{"width", "x-directional length."},
414  {"height", "y-directional length."},
415  {"depth", "z-directional length."},
416  {"float_dtype", "Float_dtype, Float32 or Float64."},
417  {"int_dtype", "Int_dtype, Int32 or Int64."},
418  {"device", "Device of the create mesh."}});
420  m, "TriangleMesh", "create_sphere",
421  {{"radius", "The radius of the sphere."},
422  {"resolution", "The resolution of the sphere."},
423  {"float_dtype", "Float_dtype, Float32 or Float64."},
424  {"int_dtype", "Int_dtype, Int32 or Int64."},
425  {"device", "Device of the create sphere."}});
427  m, "TriangleMesh", "create_tetrahedron",
428  {{"radius", "Distance from centroid to mesh vertices."},
429  {"float_dtype", "Float_dtype, Float32 or Float64."},
430  {"int_dtype", "Int_dtype, Int32 or Int64."},
431  {"device", "Device of the create tetrahedron."}});
433  m, "TriangleMesh", "create_octahedron",
434  {{"radius", "Distance from centroid to mesh vertices."},
435  {"float_dtype", "Float_dtype, Float32 or Float64."},
436  {"int_dtype", "Int_dtype, Int32 or Int64."},
437  {"device", "Device of the create octahedron."}});
439  m, "TriangleMesh", "create_icosahedron",
440  {{"radius", "Distance from centroid to mesh vertices."},
441  {"float_dtype", "Float_dtype, Float32 or Float64."},
442  {"int_dtype", "Int_dtype, Int32 or Int64."},
443  {"device", "Device of the create octahedron."}});
445  m, "TriangleMesh", "create_cylinder",
446  {{"radius", "The radius of the cylinder."},
447  {"height",
448  "The height of the cylinder.The axis of the cylinder will be "
449  "from (0, 0, -height/2) to (0, 0, height/2)."},
450  {"resolution",
451  " The circle will be split into ``resolution`` segments"},
452  {"split", "The ``height`` will be split into ``split`` segments."},
453  {"float_dtype", "Float_dtype, Float32 or Float64."},
454  {"int_dtype", "Int_dtype, Int32 or Int64."},
455  {"device", "Device of the create octahedron."}});
457  m, "TriangleMesh", "create_cone",
458  {{"radius", "The radius of the cone."},
459  {"height",
460  "The height of the cone. The axis of the cone will be from (0, "
461  "0, 0) to (0, 0, height)."},
462  {"resolution",
463  "The circle will be split into ``resolution`` segments"},
464  {"split", "The ``height`` will be split into ``split`` segments."},
465  {"float_dtype", "Float_dtype, Float32 or Float64."},
466  {"int_dtype", "Int_dtype, Int32 or Int64."},
467  {"device", "Device of the create octahedron."}});
469  m, "TriangleMesh", "create_torus",
470  {{"torus_radius",
471  "The radius from the center of the torus to the center of the "
472  "tube."},
473  {"tube_radius", "The radius of the torus tube."},
474  {"radial_resolution",
475  "The number of segments along the radial direction."},
476  {"tubular_resolution",
477  "The number of segments along the tubular direction."},
478  {"float_dtype", "Float_dtype, Float32 or Float64."},
479  {"int_dtype", "Int_dtype, Int32 or Int64."},
480  {"device", "Device of the create octahedron."}});
482  m, "TriangleMesh", "create_arrow",
483  {{"cylinder_radius", "The radius of the cylinder."},
484  {"cone_radius", "The radius of the cone."},
485  {"cylinder_height",
486  "The height of the cylinder. The cylinder is from (0, 0, 0) to "
487  "(0, 0, cylinder_height)"},
488  {"cone_height",
489  "The height of the cone. The axis of the cone will be from (0, "
490  "0, cylinder_height) to (0, 0, cylinder_height + cone_height)"},
491  {"resolution",
492  "The cone will be split into ``resolution`` segments."},
493  {"cylinder_split",
494  "The ``cylinder_height`` will be split into ``cylinder_split`` "
495  "segments."},
496  {"cone_split",
497  "The ``cone_height`` will be split into ``cone_split`` "
498  "segments."},
499  {"float_dtype", "Float_dtype, Float32 or Float64."},
500  {"int_dtype", "Int_dtype, Int32 or Int64."},
501  {"device", "Device of the create octahedron."}});
503  m, "TriangleMesh", "create_coordinate_frame",
504  {{"size", "The size of the coordinate frame."},
505  {"origin", "The origin of the coordinate frame."},
506  {"float_dtype", "Float_dtype, Float32 or Float64."},
507  {"int_dtype", "Int_dtype, Int32 or Int64."},
508  {"device", "Device of the create octahedron."}});
510  m, "TriangleMesh", "create_mobius",
511  {{"length_split", "The number of segments along the Mobius strip."},
512  {"width_split",
513  "The number of segments along the width of the Mobius strip."},
514  {"twists", "Number of twists of the Mobius strip."},
515  {"radius", "The radius of the Mobius strip."},
516  {"flatness", "Controls the flatness/height of the Mobius strip."},
517  {"width", "Width of the Mobius strip."},
518  {"scale", "Scale the complete Mobius strip."},
519  {"float_dtype", "Float_dtype, Float32 or Float64."},
520  {"int_dtype", "Int_dtype, Int32 or Int64."},
521  {"device", "Device of the create octahedron."}});
522 
523  triangle_mesh.def_static("create_text", &TriangleMesh::CreateText, "text"_a,
524  "depth"_a = 0.0, "float_dtype"_a = core::Float32,
525  "int_dtype"_a = core::Int64,
526  "device"_a = core::Device("CPU:0"),
527  R"(Create a triangle mesh from a text string.
528 
529 Args:
530 text (str): The text for generating the mesh. ASCII characters 32-126 are
531  supported (includes alphanumeric characters and punctuation). In
532  addition the line feed '\n' is supported to start a new line.
533 depth (float): The depth of the generated mesh. If depth is 0 then a flat mesh will be generated.
534 float_dtype (cv3d.core.Dtype): Float type for the vertices. Either Float32 or Float64.
535 int_dtype (cv3d.core.Dtype): Int type for the triangle indices. Either Int32 or Int64.
536 device (cv3d.core.Device): The device for the returned mesh.
537 
538 Returns:
539 Text as triangle mesh.
540 
541 Example:
542 This shows how to simplify the Stanford Bunny mesh::
543 
544  import cloudViewer as cv3d
545 
546  mesh = cv3d.t.geometry.TriangleMesh.create_text('CloudViewer', depth=1)
547  cv3d.visualization.draw([{'name': 'text', 'geometry': mesh}])
548 )");
549 
550  triangle_mesh.def_static(
551  "create_isosurfaces",
552  // Accept anything for contour_values that pybind can convert to
553  // std::list. This also avoids cv3d.utility.DoubleVector.
554  [](const core::Tensor& volume, std::list<double> contour_values,
555  const core::Device& device) {
556  std::vector<double> cv(contour_values.begin(),
557  contour_values.end());
558  return TriangleMesh::CreateIsosurfaces(volume, cv, device);
559  },
560  "volume"_a, "contour_values"_a = std::list<double>{0.0},
561  "device"_a = core::Device("CPU:0"),
562  R"(Create a mesh from a 3D scalar field (volume) by computing the
563 isosurface.
564 
565 This method uses the Flying Edges dual contouring method that computes the
566 isosurface similar to Marching Cubes. The center of the first voxel of the
567 volume is at the origin (0,0,0). The center of the voxel at index [z,y,x]
568 will be at (x,y,z).
569 
570 Args:
571 volume (cloudViewer.core.Tensor): 3D tensor with the volume.
572 contour_values (list): A list of contour values at which isosurfaces will
573  be generated. The default value is 0.
574 device (cv3d.core.Device): The device for the returned mesh.
575 
576 Returns:
577 A TriangleMesh with the extracted isosurfaces.
578 
579 
580 This example shows how to create a sphere from a volume::
581 
582 import cloudViewer as cv3d
583 import numpy as np
584 
585 grid_coords = np.stack(np.meshgrid(*3*[np.linspace(-1,1,num=64)], indexing='ij'), axis=-1)
586 vol = 0.5 - np.linalg.norm(grid_coords, axis=-1)
587 mesh = cv3d.t.geometry.TriangleMesh.create_isosurfaces(vol)
588 cv3d.visualization.draw(mesh)
589 
590 
591 This example shows how to convert a mesh to a signed distance field (SDF) and back to a mesh::
592 
593 import cloudViewer as cv3d
594 import numpy as np
595 
596 mesh1 = cv3d.t.geometry.TriangleMesh.create_torus()
597 grid_coords = np.stack(np.meshgrid(*3*[np.linspace(-2,2,num=64, dtype=np.float32)], indexing='ij'), axis=-1)
598 
599 scene = cv3d.t.geometry.RaycastingScene()
600 scene.add_triangles(mesh1)
601 sdf = scene.compute_signed_distance(grid_coords)
602 mesh2 = cv3d.t.geometry.TriangleMesh.create_isosurfaces(sdf)
603 
604 # Flip the triangle orientation for SDFs with negative values as "inside" and positive values as "outside"
605 mesh2.triangle.indices = mesh2.triangle.indices[:,[2,1,0]]
606 
607 cv3d.visualization.draw(mesh2)
608 
609 )");
610 
611  triangle_mesh.def(
612  "simplify_quadric_decimation",
613  &TriangleMesh::SimplifyQuadricDecimation, "target_reduction"_a,
614  "preserve_volume"_a = true,
615  R"(Function to simplify mesh using Quadric Error Metric Decimation by Garland and Heckbert.
616 
617 This function always uses the CPU device.
618 
619 Args:
620 target_reduction (float): The factor of triangles to delete, i.e., setting
621  this to 0.9 will return a mesh with about 10% of the original triangle
622  count. It is not guaranteed that the target reduction factor will be
623  reached.
624 
625 preserve_volume (bool): If set to True this enables volume preservation
626  which reduces the error in triangle normal direction.
627 
628 Returns:
629 Simplified TriangleMesh.
630 
631 Example:
632 This shows how to simplify the Stanford Bunny mesh::
633 
634  bunny = cv3d.data.BunnyMesh()
635  mesh = cv3d.t.geometry.TriangleMesh.from_legacy(cv3d.io.read_triangle_mesh(bunny.path))
636  simplified = mesh.simplify_quadric_decimation(0.99)
637  cv3d.visualization.draw([{'name': 'bunny', 'geometry': simplified}])
638 )");
639 
640  triangle_mesh.def(
641  "boolean_union", &TriangleMesh::BooleanUnion, "mesh"_a,
642  "tolerance"_a = 1e-6,
643  R"(Computes the mesh that encompasses the union of the volumes of two meshes.
644 Both meshes should be manifold.
645 
646 This function always uses the CPU device.
647 
648 Args:
649 mesh (cloudViewer.t.geometry.TriangleMesh): This is the second operand for the
650  boolean operation.
651 
652 tolerance (float): Threshold which determines when point distances are
653  considered to be 0.
654 
655 Returns:
656 The mesh describing the union volume.
657 
658 Example:
659 This computes the union of a sphere and a cube::
660 
661  box = cv3d.geometry.ccMesh.create_box()
662  box = cv3d.t.geometry.TriangleMesh.from_legacy(box)
663  sphere = cv3d.geometry.ccMesh.create_sphere(0.8)
664  sphere = cv3d.t.geometry.TriangleMesh.from_legacy(sphere)
665 
666  ans = box.boolean_union(sphere)
667 
668  cv3d.visualization.draw([{'name': 'union', 'geometry': ans}])
669 )");
670 
671  triangle_mesh.def(
672  "boolean_intersection", &TriangleMesh::BooleanIntersection,
673  "mesh"_a, "tolerance"_a = 1e-6,
674  R"(Computes the mesh that encompasses the intersection of the volumes of two meshes.
675 Both meshes should be manifold.
676 
677 This function always uses the CPU device.
678 
679 Args:
680 mesh (cloudViewer.t.geometry.TriangleMesh): This is the second operand for the
681  boolean operation.
682 
683 tolerance (float): Threshold which determines when point distances are
684  considered to be 0.
685 
686 Returns:
687 The mesh describing the intersection volume.
688 
689 Example:
690 This computes the intersection of a sphere and a cube::
691 
692  box = cv3d.geometry.ccMesh.create_box()
693  box = cv3d.t.geometry.TriangleMesh.from_legacy(box)
694  sphere = cv3d.geometry.ccMesh.create_sphere(0.8)
695  sphere = cv3d.t.geometry.TriangleMesh.from_legacy(sphere)
696 
697  ans = box.boolean_intersection(sphere)
698 
699  cv3d.visualization.draw([{'name': 'intersection', 'geometry': ans}])
700 )");
701 
702  triangle_mesh.def(
703  "boolean_difference", &TriangleMesh::BooleanDifference, "mesh"_a,
704  "tolerance"_a = 1e-6,
705  R"(Computes the mesh that encompasses the volume after subtracting the volume of the second operand.
706 Both meshes should be manifold.
707 
708 This function always uses the CPU device.
709 
710 Args:
711 mesh (cloudViewer.t.geometry.TriangleMesh): This is the second operand for the
712  boolean operation.
713 
714 tolerance (float): Threshold which determines when point distances are
715  considered to be 0.
716 
717 Returns:
718 The mesh describing the difference volume.
719 
720 Example:
721 This subtracts the sphere from the cube volume::
722 
723  box = cv3d.geometry.ccMesh.create_box()
724  box = cv3d.t.geometry.TriangleMesh.from_legacy(box)
725  sphere = cv3d.geometry.ccMesh.create_sphere(0.8)
726  sphere = cv3d.t.geometry.TriangleMesh.from_legacy(sphere)
727 
728  ans = box.boolean_difference(sphere)
729 
730  cv3d.visualization.draw([{'name': 'difference', 'geometry': ans}])
731 )");
732 
733  triangle_mesh.def("get_axis_aligned_bounding_box",
735  "Create an axis-aligned bounding box from vertex "
736  "attribute 'positions'.");
737  triangle_mesh.def("get_oriented_bounding_box",
739  "Create an oriented bounding box from vertex attribute "
740  "'positions'.");
741 
742  triangle_mesh.def("fill_holes", &TriangleMesh::FillHoles,
743  "hole_size"_a = 1e6,
744  R"(Fill holes by triangulating boundary edges.
745 
746 This function always uses the CPU device.
747 
748 Args:
749 hole_size (float): This is the approximate threshold for filling holes.
750  The value describes the maximum radius of holes to be filled.
751 
752 Returns:
753 New mesh after filling holes.
754 
755 Example:
756 Fill holes at the bottom of the Stanford Bunny mesh::
757 
758  bunny = cv3d.data.BunnyMesh()
759  mesh = cv3d.t.geometry.TriangleMesh.from_legacy(cv3d.io.read_triangle_mesh(bunny.path))
760  filled = mesh.fill_holes()
761  cv3d.visualization.draw([{'name': 'filled', 'geometry': ans}])
762 )");
763 
764  triangle_mesh.def(
765  "compute_uvatlas", &TriangleMesh::ComputeUVAtlas, "size"_a = 512,
766  "gutter"_a = 1.f, "max_stretch"_a = 1.f / 6,
767  "parallel_partitions"_a = 1, "nthreads"_a = 0,
768  R"(Creates an UV atlas and adds it as triangle attr 'texture_uvs' to the mesh.
769 
770 Input meshes must be manifold for this method to work.
771 The algorithm is based on:
772 Zhou et al, "Iso-charts: Stretch-driven Mesh Parameterization using Spectral Analysis", Eurographics Symposium on Geometry Processing (2004)
773 Sander et al. "Signal-Specialized Parametrization" Europgraphics 2002
774 This function always uses the CPU device.
775 
776 Args:
777 size (int): The target size of the texture (size x size). The uv coordinates
778  will still be in the range [0..1] but parameters like gutter use pixels
779  as units.
780 gutter (float): This is the space around the uv islands in pixels.
781 max_stretch (float): The maximum amount of stretching allowed. The parameter
782  range is [0..1] with 0 meaning no stretch allowed.
783 
784 parallel_partitions (int): The approximate number of partitions created
785  before computing the UV atlas for parallelizing the computation.
786  Parallelization can be enabled with values > 1. Note that
787  parallelization increases the number of UV islands and can lead to results
788  with lower quality.
789 
790 nthreads (int): The number of threads used when parallel_partitions
791  is > 1. Set to 0 for automatic number of thread detection.
792 
793 Returns:
794 This function creates a face attribute "texture_uvs" and returns a tuple
795 with (max stretch, num_charts, num_partitions) storing the
796 actual amount of stretch, the number of created charts, and the number of
797 parallel partitions created.
798 
799 Example:
800 This code creates a uv map for the Stanford Bunny mesh::
801 
802  import cloudViewer as cv3d
803  bunny = cv3d.data.BunnyMesh()
804  mesh = cv3d.t.geometry.TriangleMesh.from_legacy(cv3d.io.read_triangle_mesh(bunny.path))
805  mesh.compute_uvatlas()
806 
807  # Add a wood texture and visualize
808  texture_data = cv3d.data.WoodTexture()
809  mesh.material.material_name = 'defaultLit'
810  mesh.material.texture_maps['albedo'] = cv3d.t.io.read_image(texture_data.albedo_texture_path)
811  cv3d.visualization.draw(mesh)
812 )");
813 
814  triangle_mesh.def("bake_vertex_attr_textures",
816  "vertex_attr"_a, "margin"_a = 2., "fill"_a = 0.,
817  "update_material"_a = true,
818  R"(Bake vertex attributes into textures.
819 
820 This function assumes a triangle attribute with name 'texture_uvs'.
821 Only float type attributes can be baked to textures.
822 
823 This function always uses the CPU device.
824 
825 Args:
826 size (int): The width and height of the texture in pixels. Only square
827  textures are supported.
828 
829 vertex_attr (set): The vertex attributes for which textures should be
830  generated.
831 
832 margin (float): The margin in pixels. The recommended value is 2. The margin
833  are additional pixels around the UV islands to avoid discontinuities.
834 
835 fill (float): The value used for filling texels outside the UV islands.
836 
837 update_material (bool): If true updates the material of the mesh.
838  Baking a vertex attribute with the name 'albedo' will become the albedo
839  texture in the material. Existing textures in the material will be
840  overwritten.
841 
842 Returns:
843 A dictionary of tensors that store the baked textures.
844 
845 Example:
846 We generate a texture storing the xyz coordinates for each texel::
847 
848  import cloudViewer as cv3d
849  from matplotlib import pyplot as plt
850 
851  box = cv3d.geometry.ccMesh.create_box(create_uv_map=True)
852  box = cv3d.t.geometry.TriangleMesh.from_legacy(box)
853  box.vertex['albedo'] = box.vertex.positions
854 
855  # Initialize material and bake the 'albedo' vertex attribute to a
856  # texture. The texture will be automatically added to the material of
857  # the object.
858  box.material.set_default_properties()
859  texture_tensors = box.bake_vertex_attr_textures(128, {'albedo'})
860 
861  # Shows the textured cube.
862  cv3d.visualization.draw([box])
863 
864  # Plot the tensor with the texture.
865  plt.imshow(texture_tensors['albedo'].numpy())
866 
867 )");
868 
869  triangle_mesh.def("bake_triangle_attr_textures",
871  "triangle_attr"_a, "margin"_a = 2., "fill"_a = 0.,
872  "update_material"_a = true,
873  R"(Bake triangle attributes into textures.
874 
875 This function assumes a triangle attribute with name 'texture_uvs'.
876 
877 This function always uses the CPU device.
878 
879 Args:
880 size (int): The width and height of the texture in pixels. Only square
881  textures are supported.
882 
883 triangle_attr (set): The vertex attributes for which textures should be
884  generated.
885 
886 margin (float): The margin in pixels. The recommended value is 2. The margin
887  are additional pixels around the UV islands to avoid discontinuities.
888 
889 fill (float): The value used for filling texels outside the UV islands.
890 
891 update_material (bool): If true updates the material of the mesh.
892  Baking a vertex attribute with the name 'albedo' will become the albedo
893  texture in the material. Existing textures in the material will be
894  overwritten.
895 
896 Returns:
897 A dictionary of tensors that store the baked textures.
898 
899 Example:
900 We generate a texture visualizing the index of the triangle to which the
901 texel belongs to::
902 
903  import cloudViewer as cv3d
904  from matplotlib import pyplot as plt
905 
906  box = cv3d.geometry.ccMesh.create_box(create_uv_map=True)
907  box = cv3d.t.geometry.TriangleMesh.from_legacy(box)
908  # Creates a triangle attribute 'albedo' which is the triangle index
909  # multiplied by (255//12).
910  box.triangle['albedo'] = (255//12)*np.arange(box.triangle.indices.shape[0], dtype=np.uint8)
911 
912  # Initialize material and bake the 'albedo' triangle attribute to a
913  # texture. The texture will be automatically added to the material of
914  # the object.
915  box.material.set_default_properties()
916  texture_tensors = box.bake_triangle_attr_textures(128, {'albedo'})
917 
918  # Shows the textured cube.
919  cv3d.visualization.draw([box])
920 
921  # Plot the tensor with the texture.
922  plt.imshow(texture_tensors['albedo'].numpy())
923 )");
924 
925  triangle_mesh.def("extrude_rotation", &TriangleMesh::ExtrudeRotation,
926  "angle"_a, "axis"_a, "resolution"_a = 16,
927  "translation"_a = 0.0, "capping"_a = true,
928  R"(Sweeps the triangle mesh rotationally about an axis.
929 Args:
930 angle (float): The rotation angle in degree.
931 axis (cloudViewer.core.Tensor): The rotation axis.
932 resolution (int): The resolution defines the number of intermediate sweeps
933  about the rotation axis.
934 translation (float): The translation along the rotation axis.
935 
936 Returns:
937 A triangle mesh with the result of the sweep operation.
938 
939 Example:
940 This code generates a spring with a triangle cross-section::
941 
942  import cloudViewer as cv3d
943 
944  mesh = cv3d.t.geometry.TriangleMesh([[1,1,0], [0.7,1,0], [1,0.7,0]], [[0,1,2]])
945  spring = mesh.extrude_rotation(3*360, [0,1,0], resolution=3*16, translation=2)
946  cv3d.visualization.draw([{'name': 'spring', 'geometry': spring}])
947 )");
948 
949  triangle_mesh.def("extrude_linear", &TriangleMesh::ExtrudeLinear,
950  "vector"_a, "scale"_a = 1.0, "capping"_a = true,
951  R"(Sweeps the line set along a direction vector.
952 Args:
953 vector (cloudViewer.core.Tensor): The direction vector.
954 scale (float): Scalar factor which essentially scales the direction vector.
955 
956 Returns:
957 A triangle mesh with the result of the sweep operation.
958 
959 Example:
960 This code generates a wedge from a triangle::
961 
962  import cloudViewer as cv3d
963  triangle = cv3d.t.geometry.TriangleMesh([[1.0,1.0,0.0], [0,1,0], [1,0,0]], [[0,1,2]])
964  wedge = triangle.extrude_linear([0,0,1])
965  cv3d.visualization.draw([{'name': 'wedge', 'geometry': wedge}])
966 )");
967 
968  triangle_mesh.def("pca_partition", &TriangleMesh::PCAPartition,
969  "max_faces"_a,
970  R"(Partition the mesh by recursively doing PCA.
971 
972 This function creates a new face attribute with the name "partition_ids" storing
973 the partition id for each face.
974 
975 Args:
976 max_faces (int): The maximum allowed number of faces in a partition.
977 
978 
979 Example:
980 
981 This code partitions a mesh such that each partition contains at most 20k
982 faces::
983 
984  import cloudViewer as cv3d
985  import numpy as np
986  bunny = cv3d.data.BunnyMesh()
987  mesh = cv3d.t.geometry.TriangleMesh.from_legacy(cv3d.io.read_triangle_mesh(bunny.path))
988  num_partitions = mesh.pca_partition(max_faces=20000)
989 
990  # print the partition ids and the number of faces for each of them.
991  print(np.unique(mesh.triangle.partition_ids.numpy(), return_counts=True))
992 
993 )");
994 
995  triangle_mesh.def(
996  "select_faces_by_mask", &TriangleMesh::SelectFacesByMask, "mask"_a,
997  R"(Returns a new mesh with the faces selected by a boolean mask.
998 
999 Args:
1000 mask (cloudViewer.core.Tensor): A boolean mask with the shape (N) with N as the
1001  number of faces in the mesh.
1002 
1003 Returns:
1004 A new mesh with the selected faces. If the original mesh is empty, return an empty mesh.
1005 
1006 Example:
1007 
1008 This code partitions the mesh using PCA and then visualized the individual
1009 parts::
1010 
1011  import cloudViewer as cv3d
1012  import numpy as np
1013  bunny = cv3d.data.BunnyMesh()
1014  mesh = cv3d.t.geometry.TriangleMesh.from_legacy(cv3d.io.read_triangle_mesh(bunny.path))
1015  num_partitions = mesh.pca_partition(max_faces=20000)
1016 
1017  parts = []
1018  for i in range(num_partitions):
1019  mask = mesh.triangle.partition_ids == i
1020  part = mesh.select_faces_by_mask(mask)
1021  part.vertex.colors = np.tile(np.random.rand(3), (part.vertex.positions.shape[0],1))
1022  parts.append(part)
1023 
1024  cv3d.visualization.draw(parts)
1025 
1026 )");
1027 
1028  triangle_mesh.def(
1029  "select_by_index", &TriangleMesh::SelectByIndex, "indices"_a,
1030  "copy_attributes"_a = true,
1031  R"(Returns a new mesh with the vertices selected according to the indices list.
1032 If an item from the indices list exceeds the max vertex number of the mesh
1033 or has a negative value, it is ignored.
1034 
1035 Args:
1036 indices (cloudViewer.core.Tensor): An integer list of indices. Duplicates are
1037 allowed, but ignored. Signed and unsigned integral types are accepted.
1038 copy_attributes (bool): Indicates if vertex attributes (other than
1039 positions) and triangle attributes (other than indices) should be copied to
1040 the returned mesh.
1041 
1042 Returns:
1043 A new mesh with the selected vertices and faces built from these vertices.
1044 If the original mesh is empty, return an empty mesh.
1045 
1046 Example:
1047 
1048 This code selects the top face of a box, which has indices [2, 3, 6, 7]::
1049 
1050  import cloudViewer as cv3d
1051  import numpy as np
1052  box = cv3d.t.geometry.TriangleMesh.create_box()
1053  top_face = box.select_by_index([2, 3, 6, 7])
1054 )");
1055 
1056  triangle_mesh.def("project_images_to_albedo",
1058  "intrinsic_matrices"_a, "extrinsic_matrices"_a,
1059  "tex_size"_a = 1024, "update_material"_a = true,
1060  py::call_guard<py::gil_scoped_release>(), R"(
1061 Create an albedo for the triangle mesh using calibrated images. The triangle
1062 mesh must have texture coordinates ("texture_uvs" triangle attribute). This works
1063 by back projecting the images onto the texture surface. Overlapping images are
1064 blended together in the resulting albedo. For best results, use images captured
1065 with exposure and white balance lock to reduce the chance of seams in the output
1066 texture.
1067 
1068 This function is only supported on the CPU.
1069 
1070 Args:
1071 images (List[cloudViewer.t.geometry.Image]): List of images.
1072 intrinsic_matrices (List[cloudViewer.core.Tensor]): List of (3,3) intrinsic matrices describing
1073  the pinhole camera.
1074 extrinsic_matrices (List[cloudViewer.core.Tensor]): List of (4,4) extrinsic matrices describing
1075  the position and orientation of the camera.
1076 tex_size (int): Output albedo texture size. This is a square image, so
1077  only one side is needed.
1078 update_material (bool): Whether to update the material of the triangle
1079  mesh, possibly overwriting an existing albedo texture.
1080 
1081 Returns:
1082 Image with albedo texture.)");
1083  triangle_mesh.def(
1084  "remove_unreferenced_vertices",
1086  R"(Removes unreferenced vertices from the mesh in-place.)");
1087 
1088  triangle_mesh.def(
1089  "compute_triangle_areas", &TriangleMesh::ComputeTriangleAreas,
1090  R"(Compute triangle areas and save it as \"areas\" triangle attribute.
1091 
1092 Returns:
1093 The mesh.
1094 
1095 Example:
1096 
1097 This code computes the overall surface area of a box::
1098 
1099  import cloudViewer as cv3d
1100  box = cv3d.t.geometry.TriangleMesh.create_box()
1101  surface_area = box.compute_triangle_areas().triangle.areas.sum()
1102 )");
1103 
1104  triangle_mesh.def("remove_non_manifold_edges",
1106  R"(Function that removes all non-manifold edges, by
1107 successively deleting triangles with the smallest surface
1108 area adjacent to the non-manifold edge until the number of
1109 adjacent triangles to the edge is `<= 2`.
1110 
1111 Returns:
1112 The mesh.
1113 )");
1114 
1115  triangle_mesh.def("get_non_manifold_edges",
1117  "allow_boundary_edges"_a = true,
1118  R"(Returns the list consisting of non-manifold edges.)");
1119 
1120  triangle_mesh.def(
1121  "sample_points_uniformly", &TriangleMesh::SamplePointsUniformly,
1122  "number_of_points"_a, "use_triangle_normal"_a = false,
1123  R"(Sample points uniformly from the triangle mesh surface and return as a PointCloud. Normals and colors are interpolated from the triangle mesh. If texture_uvs and albedo are present, these are used to estimate the sampled point color, otherwise vertex colors are used, if present. During sampling, triangle areas are computed and saved in the "areas" attribute.
1124 
1125 Args:
1126 number_of_points (int): The number of points to sample.
1127 use_triangle_normal (bool): If true, use the triangle normal as the normal of the sampled point. By default, the vertex normals are interpolated instead.
1128 
1129 Returns:
1130 Sampled point cloud, with colors and normals, if available.
1131 
1132 Example::
1133 
1134 mesh = cv3d.t.geometry.TriangleMesh.create_box()
1135 mesh.vertex.colors = mesh.vertex.positions.clone()
1136 pcd = mesh.sample_points_uniformly(100000)
1137 cv3d.visualization.draw([mesh, pcd], point_size=5, show_ui=True, show_skybox=False)
1138 
1139 )");
1140 
1141  triangle_mesh.def("compute_metrics", &TriangleMesh::ComputeMetrics,
1142  "mesh2"_a, "metrics"_a, "params"_a,
1143  R"(Compute various metrics between two triangle meshes.
1144 
1145 This uses ray casting for distance computations between a sampled point cloud
1146 and a triangle mesh. Currently, Chamfer distance, Hausdorff distance and
1147 F-Score `[Knapitsch2017] <../tutorial/reference.html#Knapitsch2017>`_ are supported.
1148 The Chamfer distance is the sum of the mean distance to the nearest neighbor from
1149 the sampled surface points of the first mesh to the second mesh and vice versa.
1150 The F-Score at the fixed threshold radius is the harmonic mean of the Precision
1151 and Recall. Recall is the percentage of surface points from the first mesh that
1152 have the second mesh within the threshold radius, while Precision is the
1153 percentage of sampled points from the second mesh that have the first mesh
1154 surface within the threshold radius.
1155 
1156 .. math::
1157 :nowrap:
1158 
1159 \begin{align}
1160  \text{Chamfer Distance: } d_{CD}(X,Y) &= \frac{1}{|X|}\sum_{i \in X} || x_i - n(x_i, Y) || + \frac{1}{|Y|}\sum_{i \in Y} || y_i - n(y_i, X) ||\\
1161  \text{Hausdorff distance: } d_H(X,Y) &= \max \left\{ \max_{i \in X} || x_i - n(x_i, Y) ||, \max_{i \in Y} || y_i - n(y_i, X) || \right\}\\
1162  \text{Precision: } P(X,Y|d) &= \frac{100}{|X|} \sum_{i \in X} || x_i - n(x_i, Y) || < d \\
1163  \text{Recall: } R(X,Y|d) &= \frac{100}{|Y|} \sum_{i \in Y} || y_i - n(y_i, X) || < d \\
1164  \text{F-Score: } F(X,Y|d) &= \frac{2 P(X,Y|d) R(X,Y|d)}{P(X,Y|d) + R(X,Y|d)} \\
1165 \end{align}
1166 
1167 As a side effect, the triangle areas are saved in the "areas" attribute.
1168 
1169 Args:
1170 mesh2 (t.geometry.TriangleMesh): Other triangle mesh to compare with.
1171 metrics (Sequence[t.geometry.Metric]): List of Metrics to compute. Multiple metrics can be computed at once for efficiency.
1172 params (t.geometry.MetricParameters): This holds parameters required by different metrics.
1173 
1174 Returns:
1175 Tensor containing the requested metrics.
1176 
1177 Example::
1178 
1179 from cloudViewer.t.geometry import TriangleMesh, Metric, MetricParameters
1180 # box is a cube with one vertex at the origin and a side length 1
1181 box1 = TriangleMesh.create_box()
1182 box2 = TriangleMesh.create_box()
1183 box2.vertex.positions *= 1.1
1184 
1185 # 3 faces of the cube are the same, and 3 are shifted up by 0.1
1186 metric_params = MetricParameters(fscore_radius=cv3d.utility.FloatVector(
1187  (0.05, 0.15)), n_sampled_points=100000)
1188 metrics = box1.compute_metrics(
1189  box2, (Metric.ChamferDistance, Metric.HausdorffDistance, Metric.FScore),
1190  metric_params)
1191 
1192 print(metrics)
1193 np.testing.assert_allclose(metrics.cpu().numpy(), (0.1, 0.17, 50, 100),
1194  rtol=0.05)
1195 )");
1196 }
1197 
1198 } // namespace geometry
1199 } // namespace t
1200 } // namespace cloudViewer
Common CUDA utilities.
double normal[3]
std::string ToString() const
Returns string representation of device, e.g. "CPU:0", "CUDA:0".
Definition: Device.cpp:89
bool IsAvailable() const
Returns true if the device is available.
Definition: Device.cpp:108
Mix-in class for geometry types that can be visualized.
The base geometry class.
Definition: Geometry.h:23
A triangle mesh contains vertices and triangles.
Definition: TriangleMesh.h:98
static TriangleMesh CreateBox(double width=1.0, double height=1.0, double depth=1.0, core::Dtype float_dtype=core::Float32, core::Dtype int_dtype=core::Int64, const core::Device &device=core::Device("CPU:0"))
TriangleMesh SimplifyQuadricDecimation(double target_reduction, bool preserve_volume=true) const
TriangleMesh & Translate(const core::Tensor &translation, bool relative=true)
Translates the VertexPositions of the TriangleMesh.
core::Tensor ComputeMetrics(const TriangleMesh &mesh2, std::vector< Metric > metrics={Metric::ChamferDistance}, MetricParameters params=MetricParameters()) const
TriangleMesh BooleanIntersection(const TriangleMesh &mesh, double tolerance=1e-6) const
static TriangleMesh CreateIsosurfaces(const core::Tensor &volume, const std::vector< double > contour_values={0.0}, const core::Device &device=core::Device("CPU:0"))
double GetSurfaceArea() const
Function that computes the surface area of the mesh, i.e. the sum of the individual triangle surfaces...
static TriangleMesh CreateCylinder(double radius=1.0, double height=2.0, int resolution=20, int split=4, core::Dtype float_dtype=core::Float32, core::Dtype int_dtype=core::Int64, const core::Device &device=core::Device("CPU:0"))
static TriangleMesh CreateTetrahedron(double radius=1.0, core::Dtype float_dtype=core::Float32, core::Dtype int_dtype=core::Int64, const core::Device &device=core::Device("CPU:0"))
static geometry::TriangleMesh FromLegacy(const ccMesh &mesh_legacy, core::Dtype float_dtype=core::Float32, core::Dtype int_dtype=core::Int64, const core::Device &device=core::Device("CPU:0"))
TriangleMesh SelectFacesByMask(const core::Tensor &mask) const
TriangleMesh Clone() const
Returns copy of the triangle mesh on the same device.
Definition: TriangleMesh.h:130
const TensorMap & GetVertexAttr() const
Getter for vertex_attr_ TensorMap. Used in Pybind.
Definition: TriangleMesh.h:133
static TriangleMesh CreateIcosahedron(double radius=1.0, core::Dtype float_dtype=core::Float32, core::Dtype int_dtype=core::Int64, const core::Device &device=core::Device("CPU:0"))
static TriangleMesh CreateText(const std::string &text, double depth=0.0, core::Dtype float_dtype=core::Float32, core::Dtype int_dtype=core::Int64, const core::Device &device=core::Device("CPU:0"))
Image ProjectImagesToAlbedo(const std::vector< Image > &images, const std::vector< core::Tensor > &intrinsic_matrices, const std::vector< core::Tensor > &extrinsic_matrices, int tex_size=1024, bool update_material=true)
static std::unordered_map< std::string, geometry::TriangleMesh > FromTriangleMeshModel(const cloudViewer::visualization::rendering::TriangleMeshModel &model, core::Dtype float_dtype=core::Float32, core::Dtype int_dtype=core::Int64, const core::Device &device=core::Device("CPU:0"))
std::unordered_map< std::string, core::Tensor > BakeTriangleAttrTextures(int size, const std::unordered_set< std::string > &triangle_attr={}, double margin=2., double fill=0., bool update_material=true)
std::string ToString() const
Text description.
static TriangleMesh CreateArrow(double cylinder_radius=1.0, double cone_radius=1.5, double cylinder_height=5.0, double cone_height=4.0, int resolution=20, int cylinder_split=4, int cone_split=1, core::Dtype float_dtype=core::Float32, core::Dtype int_dtype=core::Int64, const core::Device &device=core::Device("CPU:0"))
TriangleMesh ClipPlane(const core::Tensor &point, const core::Tensor &normal) const
Clip mesh with a plane. This method clips the triangle mesh with the specified plane....
static TriangleMesh CreateMobius(int length_split=70, int width_split=15, int twists=1, double radius=1, double flatness=1, double width=1, double scale=1, core::Dtype float_dtype=core::Float32, core::Dtype int_dtype=core::Int64, const core::Device &device=core::Device("CPU:0"))
TriangleMesh ExtrudeRotation(double angle, const core::Tensor &axis, int resolution=16, double translation=0.0, bool capping=true) const
AxisAlignedBoundingBox GetAxisAlignedBoundingBox() const
Create an axis-aligned bounding box from vertex attribute "positions".
core::Device GetDevice() const override
Returns the device of the geometry.
Definition: TriangleMesh.h:721
std::unordered_map< std::string, core::Tensor > BakeVertexAttrTextures(int size, const std::unordered_set< std::string > &vertex_attr={}, double margin=2., double fill=0., bool update_material=true)
TriangleMesh To(const core::Device &device, bool copy=false) const
core::Tensor GetNonManifoldEdges(bool allow_boundary_edges=true) const
TriangleMesh & NormalizeNormals()
Normalize both triangle normals and vertex normals to length 1.
TriangleMesh BooleanUnion(const TriangleMesh &mesh, double tolerance=1e-6) const
PointCloud SamplePointsUniformly(size_t number_of_points, bool use_triangle_normal=false)
static TriangleMesh CreateCoordinateFrame(double size=1.0, const Eigen::Vector3d &origin=Eigen::Vector3d(0.0, 0.0, 0.0), core::Dtype float_dtype=core::Float32, core::Dtype int_dtype=core::Int64, const core::Device &device=core::Device("CPU:0"))
static TriangleMesh CreateCone(double radius=1.0, double height=2.0, int resolution=20, int split=1, core::Dtype float_dtype=core::Float32, core::Dtype int_dtype=core::Int64, const core::Device &device=core::Device("CPU:0"))
ccMesh ToLegacy() const
Convert to a legacy CloudViewer TriangleMesh.
static TriangleMesh CreateTorus(double torus_radius=1.0, double tube_radius=0.5, int radial_resolution=30, int tubular_resolution=20, core::Dtype float_dtype=core::Float32, core::Dtype int_dtype=core::Int64, const core::Device &device=core::Device("CPU:0"))
TriangleMesh & Scale(double scale, const core::Tensor &center)
Scales the VertexPositions of the TriangleMesh.
TriangleMesh FillHoles(double hole_size=1e6) const
TriangleMesh & Rotate(const core::Tensor &R, const core::Tensor &center)
Rotates the VertexPositions, VertexNormals and TriangleNormals (if exists).
TriangleMesh & ComputeTriangleAreas()
Function to compute triangle areas and save it as a triangle attribute "areas". Prints a warning,...
TriangleMesh ExtrudeLinear(const core::Tensor &vector, double scale=1.0, bool capping=true) const
TriangleMesh BooleanDifference(const TriangleMesh &mesh, double tolerance=1e-6) const
static TriangleMesh CreateOctahedron(double radius=1.0, core::Dtype float_dtype=core::Float32, core::Dtype int_dtype=core::Int64, const core::Device &device=core::Device("CPU:0"))
std::tuple< float, int, int > ComputeUVAtlas(size_t size=512, float gutter=1.0f, float max_stretch=1.f/6, int parallel_partitions=1, int nthreads=0)
TriangleMesh ComputeConvexHull(bool joggle_inputs=false) const
TriangleMesh & ComputeVertexNormals(bool normalized=true)
Function to compute vertex normals, usually called before rendering.
TriangleMesh SelectByIndex(const core::Tensor &indices, bool copy_attributes=true) const
static TriangleMesh CreateSphere(double radius=1.0, int resolution=20, core::Dtype float_dtype=core::Float32, core::Dtype int_dtype=core::Int64, const core::Device &device=core::Device("CPU:0"))
TriangleMesh & Transform(const core::Tensor &transformation)
Transforms the VertexPositions, VertexNormals and TriangleNormals (if exist) of the TriangleMesh.
const TensorMap & GetTriangleAttr() const
Getter for triangle_attr_ TensorMap. Used in Pybind.
Definition: TriangleMesh.h:159
TriangleMesh & ComputeTriangleNormals(bool normalized=true)
Function to compute triangle normals, usually called before rendering.
OrientedBoundingBox GetOrientedBoundingBox() const
Create an oriented bounding box from vertex attribute "positions".
#define LogWarning(...)
Definition: Logging.h:72
#define LogError(...)
Definition: Logging.h:60
const Dtype Int64
Definition: Dtype.cpp:47
const Dtype Float32
Definition: Dtype.cpp:42
void ClassMethodDocInject(py::module &pybind_module, const std::string &class_name, const std::string &function_name, const std::unordered_map< std::string, std::string > &map_parameter_body_docs)
Definition: docstring.cpp:27
void pybind_trianglemesh(py::module &m)
Generic file read and write utility for python interface.
Definition: lsd.c:149