ACloudViewer  3.9.4
A Modern Library for 3D Data Processing
PointCloudFactory.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 
8 #include <Logging.h>
9 #include <Parallel.h>
10 
11 #include <Eigen/Dense>
12 #include <Eigen/Eigenvalues>
13 #include <limits>
14 #include <queue>
15 #include <tuple>
16 
17 #include "Image.h"
18 #include "RGBDImage.h"
19 #include "VoxelGrid.h"
21 #include "ecvKDTreeFlann.h"
22 #include "ecvMesh.h"
23 #include "ecvPointCloud.h"
24 #include "ecvQhull.h"
25 #include "ecvScalarField.h"
26 #include "ecvTetraMesh.h"
27 
28 using namespace cloudViewer;
29 
30 namespace cloudViewer {
31 
32 namespace {
33 using namespace geometry;
34 
35 int CountValidDepthPixels(const Image &depth, int stride) {
36  int num_valid_pixels = 0;
37  for (int i = 0; i < depth.height_; i += stride) {
38  for (int j = 0; j < depth.width_; j += stride) {
39  const float *p = depth.PointerAt<float>(j, i);
40  if (*p > 0) num_valid_pixels += 1;
41  }
42  }
43  return num_valid_pixels;
44 }
45 
46 std::shared_ptr<ccPointCloud> CreatePointCloudFromFloatDepthImage(
47  const Image &depth,
48  const camera::PinholeCameraIntrinsic &intrinsic,
49  const Eigen::Matrix4d &extrinsic,
50  int stride,
51  bool project_valid_depth_only) {
52  auto pointcloud = std::make_shared<ccPointCloud>();
53  Eigen::Matrix4d camera_pose = extrinsic.inverse();
54  auto focal_length = intrinsic.GetFocalLength();
55  auto principal_point = intrinsic.GetPrincipalPoint();
56  int num_valid_pixels;
57  if (!project_valid_depth_only) {
58  num_valid_pixels =
59  int(depth.height_ / stride) * int(depth.width_ / stride);
60  } else {
61  num_valid_pixels = CountValidDepthPixels(depth, stride);
62  }
63  pointcloud->resize(num_valid_pixels);
64  int cnt = 0;
65  for (int i = 0; i < depth.height_; i += stride) {
66  for (int j = 0; j < depth.width_; j += stride) {
67  const float *p = depth.PointerAt<float>(j, i);
68  if (*p > 0) {
69  double z = (double)(*p);
70  double x = (j - principal_point.first) * z / focal_length.first;
71  double y =
72  (i - principal_point.second) * z / focal_length.second;
73  Eigen::Vector4d point =
74  camera_pose * Eigen::Vector4d(x, y, z, 1.0);
75 
76  pointcloud->setEigenPoint(static_cast<size_t>(cnt++),
77  point.block<3, 1>(0, 0));
78  } else if (!project_valid_depth_only) {
79  double z = std::numeric_limits<float>::quiet_NaN();
80  double x = std::numeric_limits<float>::quiet_NaN();
81  double y = std::numeric_limits<float>::quiet_NaN();
82  pointcloud->setEigenPoint(static_cast<size_t>(cnt++),
83  Eigen::Vector3d(x, y, z));
84  }
85  }
86  }
87  return pointcloud;
88 }
89 
90 template <typename TC, int NC>
91 std::shared_ptr<ccPointCloud> CreatePointCloudFromRGBDImageT(
92  const RGBDImage &image,
93  const camera::PinholeCameraIntrinsic &intrinsic,
94  const Eigen::Matrix4d &extrinsic,
95  bool project_valid_depth_only) {
96  auto pointcloud = std::make_shared<ccPointCloud>();
97  Eigen::Matrix4d camera_pose = extrinsic.inverse();
98  auto focal_length = intrinsic.GetFocalLength();
99  auto principal_point = intrinsic.GetPrincipalPoint();
100  double scale = (sizeof(TC) == 1) ? 255.0 : 1.0;
101  int num_valid_pixels;
102  if (!project_valid_depth_only) {
103  num_valid_pixels = image.depth_.height_ * image.depth_.width_;
104  } else {
105  num_valid_pixels = CountValidDepthPixels(image.depth_, 1);
106  }
107  pointcloud->resize(num_valid_pixels);
108  pointcloud->resizeTheRGBTable();
109  int cnt = 0;
110  for (int i = 0; i < image.depth_.height_; i++) {
111  float *p = (float *)(image.depth_.data_.data() +
112  i * image.depth_.BytesPerLine());
113  TC *pc = (TC *)(image.color_.data_.data() +
114  i * image.color_.BytesPerLine());
115  for (int j = 0; j < image.depth_.width_; j++, p++, pc += NC) {
116  if (*p > 0) {
117  double z = (double)(*p);
118  double x = (j - principal_point.first) * z / focal_length.first;
119  double y =
120  (i - principal_point.second) * z / focal_length.second;
121  Eigen::Vector4d point =
122  camera_pose * Eigen::Vector4d(x, y, z, 1.0);
123  pointcloud->setEigenPoint(static_cast<size_t>(cnt),
124  point.block<3, 1>(0, 0));
125  pointcloud->setEigenColor(
126  static_cast<size_t>(cnt++),
127  Eigen::Vector3d(pc[0], pc[(NC - 1) / 2], pc[NC - 1]) /
128  scale);
129  } else if (!project_valid_depth_only) {
130  double z = std::numeric_limits<float>::quiet_NaN();
131  double x = std::numeric_limits<float>::quiet_NaN();
132  double y = std::numeric_limits<float>::quiet_NaN();
133  pointcloud->setEigenPoint(static_cast<size_t>(cnt),
134  Eigen::Vector3d(x, y, z));
135  pointcloud->setEigenColor(
136  static_cast<size_t>(cnt++),
137  Eigen::Vector3d(std::numeric_limits<TC>::quiet_NaN(),
138  std::numeric_limits<TC>::quiet_NaN(),
139  std::numeric_limits<TC>::quiet_NaN()));
140  }
141  }
142  }
143  return pointcloud;
144 }
145 
146 // Disjoint set data structure to find cycles in graphs
147 class DisjointSet {
148 public:
149  DisjointSet(size_t size) : parent_(size), size_(size) {
150  for (size_t idx = 0; idx < size; idx++) {
151  parent_[idx] = idx;
152  size_[idx] = 0;
153  }
154  }
155 
156  // find representative element for given x
157  // using path compression
158  size_t Find(size_t x) {
159  if (x != parent_[x]) {
160  parent_[x] = Find(parent_[x]);
161  }
162  return parent_[x];
163  }
164 
165  // combine two sets using size of sets
166  void Union(size_t x, size_t y) {
167  x = Find(x);
168  y = Find(y);
169  if (x != y) {
170  if (size_[x] < size_[y]) {
171  size_[y] += size_[x];
172  parent_[x] = y;
173  } else {
174  size_[x] += size_[y];
175  parent_[y] = x;
176  }
177  }
178  }
179 
180 private:
181  std::vector<size_t> parent_;
182  std::vector<size_t> size_;
183 };
184 
185 struct WeightedEdge {
186  WeightedEdge(size_t v0, size_t v1, double weight)
187  : v0_(v0), v1_(v1), weight_(weight) {}
188  size_t v0_;
189  size_t v1_;
190  double weight_;
191 };
192 
193 // Minimum Spanning Tree algorithm (Kruskal's algorithm)
194 std::vector<WeightedEdge> Kruskal(std::vector<WeightedEdge> &edges,
195  size_t n_vertices) {
196  std::sort(edges.begin(), edges.end(),
197  [](WeightedEdge &e0, WeightedEdge &e1) {
198  return e0.weight_ < e1.weight_;
199  });
200  DisjointSet disjoint_set(n_vertices);
201  std::vector<WeightedEdge> mst;
202  for (size_t eidx = 0; eidx < edges.size(); ++eidx) {
203  size_t set0 = disjoint_set.Find(edges[eidx].v0_);
204  size_t set1 = disjoint_set.Find(edges[eidx].v1_);
205  if (set0 != set1) {
206  mst.push_back(edges[eidx]);
207  disjoint_set.Union(set0, set1);
208  }
209  }
210  return mst;
211 }
212 
213 } // unnamed namespace
214 } // namespace cloudViewer
215 
216 std::shared_ptr<ccPointCloud> ccPointCloud::CreateFromDepthImage(
217  const cloudViewer::geometry::Image &depth,
219  const Eigen::Matrix4d &extrinsic /* = Eigen::Matrix4d::Identity()*/,
220  double depth_scale /* = 1000.0*/,
221  double depth_trunc /* = 1000.0*/,
222  int stride /* = 1*/,
223  bool project_valid_depth_only /* = true*/) {
224  if (depth.num_of_channels_ == 1) {
225  if (depth.bytes_per_channel_ == 2) {
226  auto float_depth =
227  depth.ConvertDepthToFloatImage(depth_scale, depth_trunc);
228  return cloudViewer::CreatePointCloudFromFloatDepthImage(
229  *float_depth, intrinsic, extrinsic, stride,
230  project_valid_depth_only);
231  } else if (depth.bytes_per_channel_ == 4) {
232  return cloudViewer::CreatePointCloudFromFloatDepthImage(
233  depth, intrinsic, extrinsic, stride,
234  project_valid_depth_only);
235  }
236  }
238  "[CreatePointCloudFromDepthImage] Unsupported image format.");
239  return std::make_shared<ccPointCloud>();
240 }
241 
242 std::shared_ptr<ccPointCloud> ccPointCloud::CreateFromRGBDImage(
245  const Eigen::Matrix4d &extrinsic /* = Eigen::Matrix4d::Identity()*/,
246  bool project_valid_depth_only /* = true*/) {
247  if (image.depth_.num_of_channels_ == 1 &&
248  image.depth_.bytes_per_channel_ == 4) {
249  if (image.color_.bytes_per_channel_ == 1 &&
250  image.color_.num_of_channels_ == 3) {
251  return cloudViewer::CreatePointCloudFromRGBDImageT<uint8_t, 3>(
252  image, intrinsic, extrinsic, project_valid_depth_only);
253  } else if (image.color_.bytes_per_channel_ == 1 &&
254  image.color_.num_of_channels_ == 4) {
255  return cloudViewer::CreatePointCloudFromRGBDImageT<uint8_t, 4>(
256  image, intrinsic, extrinsic, project_valid_depth_only);
257  } else if (image.color_.bytes_per_channel_ == 4 &&
258  image.color_.num_of_channels_ == 1) {
259  return cloudViewer::CreatePointCloudFromRGBDImageT<float, 1>(
260  image, intrinsic, extrinsic, project_valid_depth_only);
261  }
262  }
264  "[CreatePointCloudFromRGBDImage] Unsupported image format.");
265  return std::make_shared<ccPointCloud>();
266 }
267 
268 std::shared_ptr<ccPointCloud> ccPointCloud::CreateFromVoxelGrid(
269  const cloudViewer::geometry::VoxelGrid &voxel_grid) {
270  auto output = std::make_shared<ccPointCloud>();
271  output->resize(static_cast<unsigned int>(voxel_grid.voxels_.size()));
272  bool has_colors = voxel_grid.HasColors();
273  if (has_colors) {
274  output->resizeTheRGBTable();
275  }
276  size_t vidx = 0;
277  for (auto &it : voxel_grid.voxels_) {
278  const cloudViewer::geometry::Voxel voxel = it.second;
279  output->setPoint(
280  vidx, voxel_grid.GetVoxelCenterCoordinate(voxel.grid_index_));
281  if (has_colors) {
282  output->setPointColor(static_cast<unsigned int>(vidx),
284  }
285  vidx++;
286  }
287  return output;
288 }
289 
291  if (hasNormals()) {
292  for (size_t i = 0; i < m_normals->size(); ++i) {
293  ccNormalVectors::GetNormalPtr(m_normals->getValue(i)).normalize();
294  }
295  }
296  return *this;
297 }
298 
300  const ccPointCloud &target) {
301  std::vector<double> distances(size());
303  kdtree.SetGeometry(target);
304 
305 #ifdef _OPENMP
306 #pragma omp parallel for schedule(static)
307 #endif
308  for (int i = 0; i < static_cast<int>(size()); i++) {
309  std::vector<int> indices(1);
310  std::vector<double> dists(1);
311  if (kdtree.SearchKNN(CCVector3d::fromArray(
312  *getPoint(static_cast<unsigned int>(i))),
313  1, indices, dists) == 0) {
315  "[ccPointCloud::ComputePointCloudDistance] Found a point "
316  "without neighbors.");
317  distances[i] = 0.0;
318  } else {
319  distances[i] = std::sqrt(dists[0]);
320  }
321  }
322  return distances;
323 }
324 
326  bool remove_infinite) {
327  bool has_normal = hasNormals();
328  bool has_color = hasColors();
329  bool has_covariance = HasCovariances();
330  bool has_fwf = hasFWF();
331  // scalar fields
332  unsigned sfCount = getNumberOfScalarFields();
333 
334  size_t old_point_num = m_points.size();
335  size_t k = 0; // new index
336  for (size_t i = 0; i < old_point_num; i++) { // old index
337 
338  bool is_nan = remove_nan && (std::isnan(m_points[i][0]) ||
339  std::isnan(m_points[i][1]) ||
340  std::isnan(m_points[i][2]));
341 
342  bool is_infinite = remove_infinite && (std::isinf(m_points[i][0]) ||
343  std::isinf(m_points[i][1]) ||
344  std::isinf(m_points[i][2]));
345 
346  if (!is_nan && !is_infinite) {
347  m_points[k] = m_points[i];
348  if (has_normal) m_points[k] = m_points[i];
349  if (has_color) m_points[k] = m_points[i];
350  if (has_covariance) covariances_[k] = covariances_[i];
351  if (has_fwf) {
352  if (fwfDescriptors().contains(
353  m_fwfWaveforms[k].descriptorID())) {
354  // remove invalid descriptors
355  fwfDescriptors().remove(m_fwfWaveforms[k].descriptorID());
356  }
357  m_fwfWaveforms[k] = m_fwfWaveforms[i];
358  }
359 
360  if (sfCount != 0) {
361  bool error = false;
362  for (unsigned j = 0; j < sfCount; ++j) {
363  ccScalarField *currentScalarField =
364  static_cast<ccScalarField *>(getScalarField(j));
365 
366  if (currentScalarField) {
367  currentScalarField->setValue(
368  j, currentScalarField->getValue(i));
369  } else {
370  error = true;
372  "[RemoveNonFinitePoints] Not enough memory to "
373  "copy scalar field!");
374  }
375  }
376 
377  if (error) {
378  deleteAllScalarFields();
379  }
380  }
381 
382  k++;
383  }
384  }
385 
386  if (!resize(static_cast<unsigned int>(k))) {
387  utility::LogError("point cloud resize error!!!");
388  }
389 
390  sfCount = getNumberOfScalarFields();
391  if (sfCount != 0) {
392  for (unsigned j = 0; j < sfCount; ++j) {
393  ccScalarField *currentScalarField =
394  static_cast<ccScalarField *>(getScalarField(j));
395  currentScalarField->resizeSafe(k);
396  currentScalarField->computeMinAndMax();
397  }
398 
399  // we display the same scalar field as the source (if we managed to copy
400  // it!)
401  if (getCurrentDisplayedScalarField()) {
402  int sfIdx = getScalarFieldIndexByName(
403  getCurrentDisplayedScalarField()->getName());
404  if (sfIdx >= 0)
405  setCurrentDisplayedScalarField(sfIdx);
406  else
407  setCurrentDisplayedScalarField(static_cast<int>(sfCount) - 1);
408  }
409  }
410 
411  CVLog::Print("[RemoveNonFinitePoints] {:d} nan points have been removed.",
412  (int)(old_point_num - k));
414  "[RemoveNonFinitePoints] {:d} nan points have been removed.",
415  (int)(old_point_num - k));
416 
417  return *this;
418 }
419 
420 // helper classes for VoxelDownSample and VoxelDownSampleAndTrace
421 namespace {
422 class AccumulatedPoint {
423 public:
424  void AddPoint(const ccPointCloud &cloud, unsigned int index) {
425  point_ += cloud.getEigenPoint(index);
426  if (cloud.hasNormals()) {
427  if (!std::isnan(cloud.getPointNormal(index)[0]) &&
428  !std::isnan(cloud.getPointNormal(index)[1]) &&
429  !std::isnan(cloud.getPointNormal(index)[2])) {
430  normal_ += cloud.getEigenNormal(index);
431  }
432  }
433  if (cloud.hasColors()) {
434  color_ += cloud.getEigenColor(index);
435  }
436  if (cloud.HasCovariances()) {
437  covariance_ += cloud.covariances_[index];
438  }
439  num_of_points_++;
440  }
441 
442  Eigen::Vector3d GetAveragePoint() const {
443  return point_ / double(num_of_points_);
444  }
445 
446  Eigen::Vector3d GetAverageNormal() const { return normal_.normalized(); }
447 
448  Eigen::Vector3d GetAverageColor() const {
449  return color_ / double(num_of_points_);
450  }
451 
452  Eigen::Matrix3d GetAverageCovariance() const {
453  return covariance_ / double(num_of_points_);
454  }
455 
456 public:
457  int num_of_points_ = 0;
458  Eigen::Vector3d point_ = Eigen::Vector3d::Zero();
459  Eigen::Vector3d normal_ = Eigen::Vector3d::Zero();
460  Eigen::Vector3d color_ = Eigen::Vector3d::Zero();
461  Eigen::Matrix3d covariance_ = Eigen::Matrix3d::Zero();
462 };
463 
464 class point_cubic_id {
465 public:
466  size_t point_id;
467  int cubic_id;
468 };
469 
470 class AccumulatedPointForTrace : public AccumulatedPoint {
471 public:
472  void AddPoint(const ccPointCloud &cloud,
473  size_t index,
474  int cubic_index,
475  bool approximate_class) {
476  point_ += cloud.getEigenPoint(index);
477  if (cloud.hasNormals()) {
478  if (!std::isnan(cloud.getEigenNormal(index)(0)) &&
479  !std::isnan(cloud.getEigenNormal(index)(1)) &&
480  !std::isnan(cloud.getEigenNormal(index)(2))) {
481  normal_ += cloud.getEigenNormal(index);
482  }
483  }
484  if (cloud.hasColors()) {
485  Eigen::Vector3d fcolor = cloud.getEigenColor(index);
486 
487  if (approximate_class) {
488  auto got = classes.find(int(fcolor[0]));
489  if (got == classes.end())
490  classes[int(fcolor[0])] = 1;
491  else
492  classes[int(fcolor[0])] += 1;
493  } else {
494  color_ += fcolor;
495  }
496  }
497 
498  if (cloud.HasCovariances()) {
499  covariance_ += cloud.covariances_[index];
500  }
501 
502  point_cubic_id new_id;
503  new_id.point_id = index;
504  new_id.cubic_id = cubic_index;
505  original_id.push_back(new_id);
506  num_of_points_++;
507  }
508 
509  Eigen::Vector3d GetMaxClass() {
510  int max_class = -1;
511  int max_count = -1;
512  for (auto it = classes.begin(); it != classes.end(); it++) {
513  if (it->second > max_count) {
514  max_count = it->second;
515  max_class = it->first;
516  }
517  }
518  return Eigen::Vector3d(max_class, max_class, max_class);
519  }
520 
521  std::vector<point_cubic_id> GetOriginalID() { return original_id; }
522 
523 private:
524  // original point cloud id in higher resolution + its cubic id
525  std::vector<point_cubic_id> original_id;
526  std::unordered_map<int, int> classes;
527 };
528 } // namespace
529 
530 std::shared_ptr<ccPointCloud> ccPointCloud::VoxelDownSample(double voxel_size) {
531  auto output = std::make_shared<ccPointCloud>("pointCloud");
532  // visibility
533  output->setVisible(isVisible());
534  output->setEnabled(isEnabled());
535 
536  // other parameters
537  output->importParametersFrom(this);
538 
539  if (voxel_size <= 0.0) {
540  utility::LogError("[ccPointCloud::VoxelDownSample] voxel_size <= 0.");
541  }
542 
543  Eigen::Vector3d voxel_size3 =
544  Eigen::Vector3d(voxel_size, voxel_size, voxel_size);
545 
546  Eigen::Vector3d voxel_min_bound = GetMinBound() - voxel_size3 * 0.5;
547  Eigen::Vector3d voxel_max_bound = GetMaxBound() + voxel_size3 * 0.5;
548  if (voxel_size * std::numeric_limits<int>::max() <
549  (voxel_max_bound - voxel_min_bound).maxCoeff()) {
551  "[ccPointCloud::VoxelDownSample] voxel_size is too small.");
552  }
553  std::unordered_map<Eigen::Vector3i, AccumulatedPoint,
555  voxelindex_to_accpoint;
556 
557  Eigen::Vector3d ref_coord;
559  for (int i = 0; i < (int)size(); i++) {
560  Eigen::Vector3d p = getEigenPoint(i); // must reserve a temp variable
561  ref_coord = (p - voxel_min_bound) / voxel_size;
562  voxel_index << int(floor(ref_coord(0))), int(floor(ref_coord(1))),
563  int(floor(ref_coord(2)));
564  voxelindex_to_accpoint[voxel_index].AddPoint(*this, i);
565  }
566 
567  bool has_normals = hasNormals();
568  bool has_colors = hasColors();
569  bool has_covariances = HasCovariances();
570 
571  if (!output->reserveThePointsTable(
572  static_cast<unsigned int>(voxelindex_to_accpoint.size()))) {
574  "[ccPointCloud::VoxelDownSample] Not enough memory to "
575  "duplicate cloud!");
576  return nullptr;
577  }
578 
579  // RGB colors
580  if (has_colors) {
581  if (output->reserveTheRGBTable()) {
582  output->showColors(colorsShown());
583  } else {
585  "[ccPointCloud::VoxelDownSample] Not enough memory to copy "
586  "RGB colors!");
587  has_colors = false;
588  }
589  }
590 
591  // Normals
592  if (has_normals) {
593  if (output->reserveTheNormsTable()) {
594  output->showNormals(normalsShown());
595  } else {
597  "[ccPointCloud::VoxelDownSample] Not enough memory to copy "
598  "normals!");
599  has_normals = false;
600  }
601  }
602 
603  int j = 0;
604  for (auto accpoint : voxelindex_to_accpoint) {
605  // import points
606  output->addPoint(accpoint.second.GetAveragePoint());
607 
608  // import colors
609  if (has_colors) {
610  output->addEigenColor(accpoint.second.GetAverageColor());
611  }
612 
613  // import normals
614  if (has_normals) {
615  output->addEigenNorm(accpoint.second.GetAverageNormal());
616  }
617 
618  // import covariance
619  if (has_covariances) {
620  output->covariances_.emplace_back(
621  accpoint.second.GetAverageCovariance());
622  }
623  }
624 
626  "ccPointCloud down sampled from {:d} points to {:d} points.",
627  (int)size(), (int)output->size());
628  return output;
629 }
630 
631 std::tuple<std::shared_ptr<ccPointCloud>,
632  Eigen::MatrixXi,
633  std::vector<std::vector<int>>>
635  const Eigen::Vector3d &min_bound,
636  const Eigen::Vector3d &max_bound,
637  bool approximate_class) const {
638  if (voxel_size <= 0.0) {
639  utility::LogError("[VoxelDownSampleAndTrace] voxel_size <= 0.");
640  }
641 
642  // Note: this is different from VoxelDownSample.
643  // It is for fixing coordinate for multiscale voxel space
644  auto voxel_min_bound = min_bound;
645  auto voxel_max_bound = max_bound;
646  if (voxel_size * std::numeric_limits<int>::max() <
647  (voxel_max_bound - voxel_min_bound).maxCoeff()) {
648  utility::LogError("[VoxelDownSampleAndTrace] voxel_size is too small.");
649  }
650 
651  Eigen::MatrixXi cubic_id;
652  auto output = std::make_shared<ccPointCloud>("pointCloud");
653  // visibility
654  output->setVisible(isVisible());
655  output->setEnabled(isEnabled());
656 
657  // other parameters
658  output->importParametersFrom(this);
659 
660  std::unordered_map<Eigen::Vector3i, AccumulatedPointForTrace,
662  voxelindex_to_accpoint;
663 
664  int cid_temp[3] = {1, 2, 4};
665  for (size_t i = 0; i < this->size(); i++) {
666  Eigen::Vector3d p = getEigenPoint(i); // must reserve a temp variable
667  auto ref_coord = (p - voxel_min_bound) / voxel_size;
668  auto voxel_index = Eigen::Vector3i(int(floor(ref_coord(0))),
669  int(floor(ref_coord(1))),
670  int(floor(ref_coord(2))));
671  int cid = 0;
672  for (int c = 0; c < 3; c++) {
673  if ((ref_coord(c) - voxel_index(c)) >= 0.5) {
674  cid += cid_temp[c];
675  }
676  }
677  voxelindex_to_accpoint[voxel_index].AddPoint(*this, i, cid,
678  approximate_class);
679  }
680 
681  bool has_normals = hasNormals();
682  bool has_colors = hasColors();
683  bool has_covariances = HasCovariances();
684  int cnt = 0;
685  cubic_id.resize(voxelindex_to_accpoint.size(), 8);
686  cubic_id.setConstant(-1);
687  std::vector<std::vector<int>> original_indices(
688  voxelindex_to_accpoint.size());
689 
690  if (!output->reserveThePointsTable(
691  static_cast<unsigned int>(voxelindex_to_accpoint.size()))) {
693  "[ccPointCloud::VoxelDownSampleAndTrace] Not enough memory to "
694  "duplicate cloud!");
695  }
696 
697  // RGB colors
698  if (has_colors) {
699  if (output->reserveTheRGBTable()) {
700  output->showColors(colorsShown());
701  } else {
703  "[ccPointCloud::VoxelDownSampleAndTrace] Not enough memory "
704  "to copy RGB colors!");
705  has_colors = false;
706  }
707  }
708 
709  // Normals
710  if (has_normals) {
711  if (output->reserveTheNormsTable()) {
712  output->showNormals(normalsShown());
713  } else {
715  "[ccPointCloud::VoxelDownSampleAndTrace] Not enough memory "
716  "to copy normals!");
717  has_normals = false;
718  }
719  }
720 
721  for (auto accpoint : voxelindex_to_accpoint) {
722  // import points
723  output->addEigenPoint(accpoint.second.GetAveragePoint());
724 
725  // import colors
726  if (has_colors) {
727  if (approximate_class) {
728  output->addEigenColor(accpoint.second.GetMaxClass());
729  } else {
730  output->addEigenColor(accpoint.second.GetAverageColor());
731  }
732  }
733 
734  // import normals
735  if (has_normals) {
736  output->addEigenNorm(accpoint.second.GetAverageNormal());
737  }
738 
739  // import covariance
740  if (has_covariances) {
741  output->covariances_.emplace_back(
742  accpoint.second.GetAverageCovariance());
743  }
744 
745  auto original_id = accpoint.second.GetOriginalID();
746  for (int i = 0; i < (int)original_id.size(); i++) {
747  size_t pid = original_id[i].point_id;
748  int cid = original_id[i].cubic_id;
749  cubic_id(cnt, cid) = int(pid);
750  original_indices[cnt].push_back(int(pid));
751  }
752  cnt++;
753  }
754 
756  "ccPointCloud down sampled from {:d} points to {:d} points.",
757  (int)size(), (int)output->size());
758 
759  return std::make_tuple(output, cubic_id, original_indices);
760 }
761 
762 std::shared_ptr<ccPointCloud> ccPointCloud::UniformDownSample(
763  size_t every_k_points) const {
764  if (every_k_points == 0) {
766  "[ccPointCloud::UniformDownSample] Illegal sample rate.");
767  return nullptr;
768  }
769 
770  std::vector<size_t> indices;
771  for (size_t i = 0; i < size(); i += every_k_points) {
772  indices.push_back(i);
773  }
774 
775  return SelectByIndex(indices);
776 }
777 
778 std::shared_ptr<ccPointCloud> ccPointCloud::RandomDownSample(
779  double sampling_ratio) const {
780  if (sampling_ratio < 0 || sampling_ratio > 1) {
782  "[RandomDownSample] Illegal sampling_ratio {}, sampling_ratio "
783  "must be between 0 and 1.");
784  }
785  std::vector<size_t> indices(this->size());
786  std::iota(std::begin(indices), std::end(indices), (size_t)0);
787  std::random_device rd;
788  std::mt19937 prng(rd());
789  std::shuffle(indices.begin(), indices.end(), prng);
790  indices.resize((int)(sampling_ratio * this->size()));
791  return SelectByIndex(indices);
792 }
793 
794 std::shared_ptr<ccPointCloud> ccPointCloud::FarthestPointDownSample(
795  const size_t num_samples, const size_t start_index) const {
796  if (num_samples == 0) {
797  return std::make_shared<ccPointCloud>();
798  } else if (num_samples == this->size()) {
799  return std::make_shared<ccPointCloud>(*this);
800  } else if (num_samples > this->size()) {
802  "Illegal number of samples: {}, must <= point size: {}",
803  num_samples, this->size());
804  } else if (start_index >= this->size()) {
805  utility::LogError("Illegal start index: {}, must < point size: {}",
806  start_index, this->size());
807  }
808  // We can also keep track of the non-selected indices with unordered_set,
809  // but since typically num_samples << num_points, it may not be worth it.
810  std::vector<size_t> selected_indices;
811  selected_indices.reserve(num_samples);
812  const size_t num_points = this->size();
813  std::vector<double> distances(num_points,
814  std::numeric_limits<double>::infinity());
815  size_t farthest_index = start_index;
816  for (size_t i = 0; i < num_samples; i++) {
817  selected_indices.push_back(farthest_index);
818  const Eigen::Vector3d &selected = getEigenPoint(farthest_index);
819  double max_dist = 0;
820  for (size_t j = 0; j < num_points; j++) {
821  double dist = (getEigenPoint(j) - selected).squaredNorm();
822  distances[j] = std::min(distances[j], dist);
823  if (distances[j] > max_dist) {
824  max_dist = distances[j];
825  farthest_index = j;
826  }
827  }
828  }
829  return SelectByIndex(selected_indices);
830 }
831 
832 std::tuple<std::shared_ptr<ccPointCloud>, std::vector<size_t>>
834  double search_radius) const {
835  if (nb_points < 1 || search_radius <= 0) {
837  "[RemoveRadiusOutliers] Illegal input parameters,"
838  "number of points and radius must be positive");
839  return std::make_tuple(std::make_shared<ccPointCloud>("pointCloud"),
840  std::vector<size_t>());
841  }
843  kdtree.SetGeometry(*this);
844  std::vector<bool> mask = std::vector<bool>(size());
845 #ifdef _OPENMP
846 #pragma omp parallel for schedule(static)
847 #endif
848  for (int i = 0; i < int(size()); i++) {
849  std::vector<int> tmp_indices;
850  std::vector<double> dist;
851  size_t nb_neighbors = kdtree.SearchRadius(
852  CCVector3d::fromArray(*getPoint(static_cast<unsigned int>(i))),
853  search_radius, tmp_indices, dist);
854  mask[i] = (nb_neighbors > nb_points);
855  }
856  std::vector<size_t> indices;
857  for (size_t i = 0; i < mask.size(); i++) {
858  if (mask[i]) {
859  indices.push_back(i);
860  }
861  }
862  return std::make_tuple(SelectByIndex(indices), indices);
863 }
864 
865 std::tuple<std::shared_ptr<ccPointCloud>, std::vector<size_t>>
867  double std_ratio) const {
868  if (nb_neighbors < 1 || std_ratio <= 0) {
870  "[RemoveStatisticalOutliers] Illegal input parameters, number "
871  "of neighbors and standard deviation ratio must be positive");
872  return std::make_tuple(std::make_shared<ccPointCloud>("pointCloud"),
873  std::vector<size_t>());
874  }
875 
876  if (size() == 0) {
877  return std::make_tuple(std::make_shared<ccPointCloud>("pointCloud"),
878  std::vector<size_t>());
879  }
880 
882  kdtree.SetGeometry(*this);
883  std::vector<double> avg_distances = std::vector<double>(size());
884  std::vector<size_t> indices;
885  size_t valid_distances = 0;
886 #ifdef _OPENMP
887 #pragma omp parallel for schedule(static)
888 #endif
889  for (int i = 0; i < int(size()); i++) {
890  std::vector<int> tmp_indices;
891  std::vector<double> dist;
892  kdtree.SearchKNN(
893  CCVector3d::fromArray(*getPoint(static_cast<unsigned int>(i))),
894  int(nb_neighbors), tmp_indices, dist);
895  double mean = -1.0;
896  if (dist.size() > 0u) {
897  valid_distances++;
898  std::for_each(dist.begin(), dist.end(),
899  [](double &d) { d = std::sqrt(d); });
900  mean = std::accumulate(dist.begin(), dist.end(), 0.0) / dist.size();
901  }
902  avg_distances[i] = mean;
903  }
904  if (valid_distances == 0) {
905  return std::make_tuple(std::make_shared<ccPointCloud>("pointCloud"),
906  std::vector<size_t>());
907  }
908  double cloud_mean = std::accumulate(
909  avg_distances.begin(), avg_distances.end(), 0.0,
910  [](double const &x, double const &y) { return y > 0 ? x + y : x; });
911  cloud_mean /= valid_distances;
912  double sq_sum = std::inner_product(
913  avg_distances.begin(), avg_distances.end(), avg_distances.begin(),
914  0.0, [](double const &x, double const &y) { return x + y; },
915  [cloud_mean](double const &x, double const &y) {
916  return x > 0 ? (x - cloud_mean) * (y - cloud_mean) : 0;
917  });
918  // Bessel's correction
919  double std_dev = std::sqrt(sq_sum / (valid_distances - 1));
920  double distance_threshold = cloud_mean + std_ratio * std_dev;
921  for (size_t i = 0; i < avg_distances.size(); i++) {
922  if (avg_distances[i] > 0 && avg_distances[i] < distance_threshold) {
923  indices.push_back(i);
924  }
925  }
926  return std::make_tuple(SelectByIndex(indices), indices);
927 }
928 
929 namespace cloudViewer {
930 
931 namespace {
932 using namespace geometry;
933 
934 Eigen::Vector3d ComputeEigenvector0(const Eigen::Matrix3d &A, double eval0) {
935  Eigen::Vector3d row0(A(0, 0) - eval0, A(0, 1), A(0, 2));
936  Eigen::Vector3d row1(A(0, 1), A(1, 1) - eval0, A(1, 2));
937  Eigen::Vector3d row2(A(0, 2), A(1, 2), A(2, 2) - eval0);
938  Eigen::Vector3d r0xr1 = row0.cross(row1);
939  Eigen::Vector3d r0xr2 = row0.cross(row2);
940  Eigen::Vector3d r1xr2 = row1.cross(row2);
941  double d0 = r0xr1.dot(r0xr1);
942  double d1 = r0xr2.dot(r0xr2);
943  double d2 = r1xr2.dot(r1xr2);
944 
945  double dmax = d0;
946  int imax = 0;
947  if (d1 > dmax) {
948  dmax = d1;
949  imax = 1;
950  }
951  if (d2 > dmax) {
952  imax = 2;
953  }
954 
955  if (imax == 0) {
956  return r0xr1 / std::sqrt(d0);
957  } else if (imax == 1) {
958  return r0xr2 / std::sqrt(d1);
959  } else {
960  return r1xr2 / std::sqrt(d2);
961  }
962 }
963 
964 Eigen::Vector3d ComputeEigenvector1(const Eigen::Matrix3d &A,
965  const Eigen::Vector3d &evec0,
966  double eval1) {
967  Eigen::Vector3d U, V;
968  if (std::abs(evec0(0)) > std::abs(evec0(1))) {
969  double inv_length =
970  1 / std::sqrt(evec0(0) * evec0(0) + evec0(2) * evec0(2));
971  U << -evec0(2) * inv_length, 0, evec0(0) * inv_length;
972  } else {
973  double inv_length =
974  1 / std::sqrt(evec0(1) * evec0(1) + evec0(2) * evec0(2));
975  U << 0, evec0(2) * inv_length, -evec0(1) * inv_length;
976  }
977  V = evec0.cross(U);
978 
979  Eigen::Vector3d AU(A(0, 0) * U(0) + A(0, 1) * U(1) + A(0, 2) * U(2),
980  A(0, 1) * U(0) + A(1, 1) * U(1) + A(1, 2) * U(2),
981  A(0, 2) * U(0) + A(1, 2) * U(1) + A(2, 2) * U(2));
982 
983  Eigen::Vector3d AV = {A(0, 0) * V(0) + A(0, 1) * V(1) + A(0, 2) * V(2),
984  A(0, 1) * V(0) + A(1, 1) * V(1) + A(1, 2) * V(2),
985  A(0, 2) * V(0) + A(1, 2) * V(1) + A(2, 2) * V(2)};
986 
987  double m00 = U(0) * AU(0) + U(1) * AU(1) + U(2) * AU(2) - eval1;
988  double m01 = U(0) * AV(0) + U(1) * AV(1) + U(2) * AV(2);
989  double m11 = V(0) * AV(0) + V(1) * AV(1) + V(2) * AV(2) - eval1;
990 
991  double absM00 = std::abs(m00);
992  double absM01 = std::abs(m01);
993  double absM11 = std::abs(m11);
994  double max_abs_comp;
995  if (absM00 >= absM11) {
996  max_abs_comp = std::max(absM00, absM01);
997  if (max_abs_comp > 0) {
998  if (absM00 >= absM01) {
999  m01 /= m00;
1000  m00 = 1 / std::sqrt(1 + m01 * m01);
1001  m01 *= m00;
1002  } else {
1003  m00 /= m01;
1004  m01 = 1 / std::sqrt(1 + m00 * m00);
1005  m00 *= m01;
1006  }
1007  return m01 * U - m00 * V;
1008  } else {
1009  return U;
1010  }
1011  } else {
1012  max_abs_comp = std::max(absM11, absM01);
1013  if (max_abs_comp > 0) {
1014  if (absM11 >= absM01) {
1015  m01 /= m11;
1016  m11 = 1 / std::sqrt(1 + m01 * m01);
1017  m01 *= m11;
1018  } else {
1019  m11 /= m01;
1020  m01 = 1 / std::sqrt(1 + m11 * m11);
1021  m11 *= m01;
1022  }
1023  return m11 * U - m01 * V;
1024  } else {
1025  return U;
1026  }
1027  }
1028 }
1029 
1030 Eigen::Vector3d FastEigen3x3(const Eigen::Matrix3d &covariance) {
1031  // Previous version based on:
1032  // https://en.wikipedia.org/wiki/Eigenvalue_algorithm#3.C3.973_matrices
1033  // Current version based on
1034  // https://www.geometrictools.com/Documentation/RobustEigenSymmetric3x3.pdf
1035  // which handles edge cases like points on a plane
1036 
1037  Eigen::Matrix3d A = covariance;
1038  double max_coeff = A.maxCoeff();
1039  if (max_coeff == 0) {
1040  return Eigen::Vector3d::Zero();
1041  }
1042  A /= max_coeff;
1043 
1044  double norm = A(0, 1) * A(0, 1) + A(0, 2) * A(0, 2) + A(1, 2) * A(1, 2);
1045  if (norm > 0) {
1046  Eigen::Vector3d eval;
1047  Eigen::Vector3d evec0;
1048  Eigen::Vector3d evec1;
1049  Eigen::Vector3d evec2;
1050 
1051  double q = (A(0, 0) + A(1, 1) + A(2, 2)) / 3;
1052 
1053  double b00 = A(0, 0) - q;
1054  double b11 = A(1, 1) - q;
1055  double b22 = A(2, 2) - q;
1056 
1057  double p =
1058  std::sqrt((b00 * b00 + b11 * b11 + b22 * b22 + norm * 2) / 6);
1059 
1060  double c00 = b11 * b22 - A(1, 2) * A(1, 2);
1061  double c01 = A(0, 1) * b22 - A(1, 2) * A(0, 2);
1062  double c02 = A(0, 1) * A(1, 2) - b11 * A(0, 2);
1063  double det = (b00 * c00 - A(0, 1) * c01 + A(0, 2) * c02) / (p * p * p);
1064 
1065  double half_det = det * 0.5;
1066  half_det = std::min(std::max(half_det, -1.0), 1.0);
1067 
1068  double angle = std::acos(half_det) / (double)3;
1069  double const two_thirds_pi = 2.09439510239319549;
1070  double beta2 = std::cos(angle) * 2;
1071  double beta0 = std::cos(angle + two_thirds_pi) * 2;
1072  double beta1 = -(beta0 + beta2);
1073 
1074  eval(0) = q + p * beta0;
1075  eval(1) = q + p * beta1;
1076  eval(2) = q + p * beta2;
1077 
1078  if (half_det >= 0) {
1079  evec2 = ComputeEigenvector0(A, eval(2));
1080  if (eval(2) < eval(0) && eval(2) < eval(1)) {
1081  A *= max_coeff;
1082  return evec2;
1083  }
1084  evec1 = ComputeEigenvector1(A, evec2, eval(1));
1085  A *= max_coeff;
1086  if (eval(1) < eval(0) && eval(1) < eval(2)) {
1087  return evec1;
1088  }
1089  evec0 = evec1.cross(evec2);
1090  return evec0;
1091  } else {
1092  evec0 = ComputeEigenvector0(A, eval(0));
1093  if (eval(0) < eval(1) && eval(0) < eval(2)) {
1094  A *= max_coeff;
1095  return evec0;
1096  }
1097  evec1 = ComputeEigenvector1(A, evec0, eval(1));
1098  A *= max_coeff;
1099  if (eval(1) < eval(0) && eval(1) < eval(2)) {
1100  return evec1;
1101  }
1102  evec2 = evec0.cross(evec1);
1103  return evec2;
1104  }
1105  } else {
1106  A *= max_coeff;
1107  if (A(0, 0) < A(1, 1) && A(0, 0) < A(2, 2)) {
1108  return Eigen::Vector3d(1, 0, 0);
1109  } else if (A(1, 1) < A(0, 0) && A(1, 1) < A(2, 2)) {
1110  return Eigen::Vector3d(0, 1, 0);
1111  } else {
1112  return Eigen::Vector3d(0, 0, 1);
1113  }
1114  }
1115 }
1116 
1117 Eigen::Vector3d ComputeNormal(const Eigen::Matrix3d &covariance,
1118  bool fast_normal_computation) {
1119  if (fast_normal_computation) {
1120  return FastEigen3x3(covariance);
1121  }
1122  Eigen::SelfAdjointEigenSolver<Eigen::Matrix3d> solver;
1123  solver.compute(covariance, Eigen::ComputeEigenvectors);
1124  return solver.eigenvectors().col(0);
1125 }
1126 
1127 } // unnamed namespace
1128 } // namespace cloudViewer
1129 
1130 std::vector<Eigen::Matrix3d> ccPointCloud::EstimatePerPointCovariances(
1131  const ccPointCloud &input,
1133  &search_param /* = KDTreeSearchParamKNN()*/) {
1134  const auto points = input.getEigenPoints();
1135  std::vector<Eigen::Matrix3d> covariances;
1136  covariances.resize(points.size());
1137 
1138  KDTreeFlann kdtree;
1139  kdtree.SetGeometry(input);
1140 #ifdef _OPENMP
1141 #pragma omp parallel for schedule(static) \
1142  num_threads(utility::EstimateMaxThreads())
1143 #endif
1144  for (int i = 0; i < (int)points.size(); i++) {
1145  std::vector<int> indices;
1146  std::vector<double> distance2;
1147  if (kdtree.Search(points[i], search_param, indices, distance2) >= 3) {
1148  auto covariance = utility::ComputeCovariance(points, indices);
1149  if (input.HasCovariances() && covariance.isIdentity(1e-4)) {
1150  covariances[i] = input.covariances_[i];
1151  } else {
1152  covariances[i] = covariance;
1153  }
1154  } else {
1155  covariances[i] = Eigen::Matrix3d::Identity();
1156  }
1157  }
1158  return covariances;
1159 }
1162  &search_param /* = KDTreeSearchParamKNN()*/) {
1163  this->covariances_ = EstimatePerPointCovariances(*this, search_param);
1164 }
1165 
1168  &search_param /* = KDTreeSearchParamKNN()*/,
1169  bool fast_normal_computation /* = true */) {
1170  bool has_normal = hasNormals();
1171  if (!hasNormals()) {
1172  resizeTheNormsTable();
1173  }
1174 
1175  std::vector<Eigen::Matrix3d> covariances;
1176  if (!HasCovariances()) {
1177  covariances = EstimatePerPointCovariances(*this, search_param);
1178  } else {
1179  covariances = covariances_;
1180  }
1181 
1182 #ifdef _OPENMP
1183 #pragma omp parallel for schedule(static) \
1184  num_threads(utility::EstimateMaxThreads())
1185 #endif
1186  for (int i = 0; i < (int)covariances.size(); i++) {
1187  auto normal = ComputeNormal(covariances[i], fast_normal_computation);
1188  if (normal.norm() == 0.0) {
1189  if (has_normal) {
1191  getPointNormal(static_cast<unsigned int>(i)));
1192  } else {
1193  normal = Eigen::Vector3d(0.0, 0.0, 1.0);
1194  }
1195  }
1196  if (has_normal && normal.dot(CCVector3d::fromArray(getPointNormal(
1197  static_cast<unsigned int>(i)))) < 0.0) {
1198  normal *= -1.0;
1199  }
1200  setPointNormal(static_cast<unsigned int>(i), normal);
1201  }
1202 
1203  return true;
1204 }
1205 
1207  const Eigen::Vector3d &orientation_reference
1208  /* = Eigen::Vector3d(0.0, 0.0, 1.0)*/) {
1209  if (!hasNormals()) {
1211  "[OrientNormalsToAlignWithDirection] No normals in the "
1212  "ccPointCloud. Call EstimateNormals() first.");
1213  }
1214 #ifdef _OPENMP
1215 #pragma omp parallel for schedule(static) \
1216  num_threads(utility::EstimateMaxThreads())
1217 #endif
1218  for (int i = 0; i < (int)this->size(); i++) {
1219  auto &normal = getPointNormalPtr(static_cast<size_t>(i));
1220  if (normal.norm() == 0.0f) {
1221  normal = CCVector3::fromArray(orientation_reference);
1222  } else if (normal.dot(CCVector3::fromArray(orientation_reference)) <
1223  0.0f) {
1224  normal *= -1.0f;
1225  }
1226  }
1227  return true;
1228 }
1229 
1231  const Eigen::Vector3d &camera_location /* = Eigen::Vector3d::Zero()*/) {
1232  if (hasNormals() == false) {
1234  "[OrientNormalsTowardsCameraLocation] No normals in the "
1235  "ccPointCloud. Call EstimateNormals() first.");
1236  }
1237 #ifdef _OPENMP
1238 #pragma omp parallel for schedule(static) \
1239  num_threads(utility::EstimateMaxThreads())
1240 #endif
1241  for (int i = 0; i < (int)this->size(); i++) {
1242  Eigen::Vector3d orientation_reference =
1243  camera_location - getEigenPoint(static_cast<size_t>(i));
1244  auto &normal = getPointNormalPtr(static_cast<size_t>(i));
1245  if (normal.norm() == 0.0f) {
1246  normal = orientation_reference;
1247  if (normal.norm() == 0.0f) {
1248  normal = CCVector3(0.0f, 0.0f, 1.0f);
1249  } else {
1250  normal.normalize();
1251  }
1252  } else if (normal.dot(orientation_reference) < 0.0f) {
1253  normal *= -1.0f;
1254  }
1255  }
1256  return true;
1257 }
1258 
1260  if (!hasNormals()) {
1262  "[OrientNormalsConsistentTangentPlane] No normals in the "
1263  "ccPointCloud. Call EstimateNormals() first.");
1264  }
1265 
1266  // Create Riemannian graph (Euclidian MST + kNN)
1267  // Euclidian MST is subgraph of Delaunay triangulation
1268  std::shared_ptr<cloudViewer::geometry::TetraMesh> delaunay_mesh;
1269  std::vector<size_t> pt_map;
1270  std::tie(delaunay_mesh, pt_map) =
1272  std::vector<cloudViewer::WeightedEdge> delaunay_graph;
1273  std::unordered_set<size_t> graph_edges;
1274  auto EdgeIndex = [&](size_t v0, size_t v1) -> size_t {
1275  return std::min(v0, v1) * this->size() + std::max(v0, v1);
1276  };
1277  auto AddEdgeToDelaunayGraph = [&](size_t v0, size_t v1) {
1278  v0 = pt_map[v0];
1279  v1 = pt_map[v1];
1280  size_t edge = EdgeIndex(v0, v1);
1281  if (graph_edges.count(edge) == 0) {
1282  double dist = (getEigenPoint(v0) - getEigenPoint(v1)).squaredNorm();
1283  delaunay_graph.push_back(cloudViewer::WeightedEdge(v0, v1, dist));
1284  graph_edges.insert(edge);
1285  }
1286  };
1287  for (const Eigen::Vector4i &tetra : delaunay_mesh->tetras_) {
1288  AddEdgeToDelaunayGraph(tetra[0], tetra[1]);
1289  AddEdgeToDelaunayGraph(tetra[0], tetra[2]);
1290  AddEdgeToDelaunayGraph(tetra[0], tetra[3]);
1291  AddEdgeToDelaunayGraph(tetra[1], tetra[2]);
1292  AddEdgeToDelaunayGraph(tetra[1], tetra[3]);
1293  AddEdgeToDelaunayGraph(tetra[2], tetra[3]);
1294  }
1295 
1296  std::vector<cloudViewer::WeightedEdge> mst =
1297  cloudViewer::Kruskal(delaunay_graph, this->size());
1298 
1299  auto NormalWeight = [&](size_t v0, size_t v1) -> double {
1300  return 1.0 - std::abs(getEigenNormal(v0).dot(getEigenNormal(v1)));
1301  };
1302  for (auto &edge : mst) {
1303  edge.weight_ = NormalWeight(edge.v0_, edge.v1_);
1304  }
1305 
1306  // Add k nearest neighbors to Riemannian graph
1307  cloudViewer::geometry::KDTreeFlann kdtree(*this);
1308  for (size_t v0 = 0; v0 < this->size(); ++v0) {
1309  std::vector<int> neighbors;
1310  std::vector<double> dists2;
1311  kdtree.SearchKNN(getEigenPoint(v0), int(k), neighbors, dists2);
1312  for (size_t vidx1 = 0; vidx1 < neighbors.size(); ++vidx1) {
1313  size_t v1 = size_t(neighbors[vidx1]);
1314  if (v0 == v1) {
1315  continue;
1316  }
1317  size_t edge = EdgeIndex(v0, v1);
1318  if (graph_edges.count(edge) == 0) {
1319  double weight = NormalWeight(v0, v1);
1320  mst.push_back(cloudViewer::WeightedEdge(v0, v1, weight));
1321  graph_edges.insert(edge);
1322  }
1323  }
1324  }
1325 
1326  // extract MST from Riemannian graph
1327  mst = cloudViewer::Kruskal(mst, this->size());
1328 
1329  // convert list of edges to graph
1330  std::vector<std::unordered_set<size_t>> mst_graph(this->size());
1331  for (const auto &edge : mst) {
1332  size_t v0 = edge.v0_;
1333  size_t v1 = edge.v1_;
1334  mst_graph[v0].insert(v1);
1335  mst_graph[v1].insert(v0);
1336  }
1337 
1338  // find start node for tree traversal
1339  // init with node that maximizes z
1340  double max_z = std::numeric_limits<double>::lowest();
1341  size_t v0;
1342  for (size_t vidx = 0; vidx < this->size(); ++vidx) {
1343  const Eigen::Vector3d &v = getEigenPoint(vidx);
1344  if (v(2) > max_z) {
1345  max_z = v(2);
1346  v0 = vidx;
1347  }
1348  }
1349 
1350  // traverse MST and orient normals consistently
1351  std::queue<size_t> traversal_queue;
1352  std::vector<bool> visited(this->size(), false);
1353  traversal_queue.push(v0);
1354  auto TestAndOrientNormal = [&](const CCVector3 &n0, CCVector3 &n1) {
1355  if (n0.dot(n1) < 0) {
1356  n1 *= -1;
1357  }
1358  };
1359  TestAndOrientNormal(CCVector3(0.0f, 0.0f, 1.0f), getPointNormalPtr(v0));
1360  while (!traversal_queue.empty()) {
1361  v0 = traversal_queue.front();
1362  traversal_queue.pop();
1363  visited[v0] = true;
1364  for (size_t v1 : mst_graph[v0]) {
1365  if (!visited[v1]) {
1366  traversal_queue.push(v1);
1367  TestAndOrientNormal(getPointNormalPtr(v0),
1368  getPointNormalPtr(v1));
1369  }
1370  }
1371  }
1372 }
1373 
1374 std::vector<double> ccPointCloud::ComputeMahalanobisDistance() const {
1375  std::vector<double> mahalanobis(size());
1376  Eigen::Vector3d mean;
1377  Eigen::Matrix3d covariance;
1378  std::tie(mean, covariance) = computeMeanAndCovariance();
1379 
1380  Eigen::Matrix3d cov_inv = covariance.inverse();
1381 #ifdef _OPENMP
1382 #pragma omp parallel for schedule(static) \
1383  num_threads(utility::EstimateMaxThreads())
1384 #endif
1385  for (int i = 0; i < (int)size(); i++) {
1386  Eigen::Vector3d p =
1387  CCVector3d::fromArray(*getPoint(static_cast<unsigned int>(i))) -
1388  mean;
1389  mahalanobis[i] = std::sqrt(p.transpose() * cov_inv * p);
1390  }
1391  return mahalanobis;
1392 }
1393 
1395  std::vector<double> nn_dis(size());
1396  cloudViewer::geometry::KDTreeFlann kdtree(*this);
1397 #ifdef _OPENMP
1398 #pragma omp parallel for schedule(static) \
1399  num_threads(utility::EstimateMaxThreads())
1400 #endif
1401  for (int i = 0; i < (int)size(); i++) {
1402  std::vector<int> indices(2);
1403  std::vector<double> dists(2);
1404  if (kdtree.SearchKNN(CCVector3d::fromArray(
1405  *getPoint(static_cast<unsigned int>(i))),
1406  2, indices, dists) <= 1) {
1408  "[ComputePointCloudNearestNeighborDistance] Found a point "
1409  "without neighbors.");
1410  nn_dis[i] = 0.0;
1411  } else {
1412  nn_dis[i] = std::sqrt(dists[1]);
1413  }
1414  }
1415  return nn_dis;
1416 }
1417 
1419  std::vector<double> nn_dis = ComputeNearestNeighborDistance();
1420  return std::accumulate(std::begin(nn_dis), std::end(nn_dis), 0.0) /
1421  nn_dis.size();
1422 }
1423 
1424 std::tuple<std::shared_ptr<ccMesh>, std::vector<size_t>>
1427 }
1428 
1429 std::tuple<std::shared_ptr<ccMesh>, std::vector<size_t>>
1430 ccPointCloud::HiddenPointRemoval(const Eigen::Vector3d &camera_location,
1431  const double radius) const {
1432  if (radius <= 0) {
1434  "[ccPointCloud::HiddenPointRemoval] radius must be larger than "
1435  "zero.");
1436  return std::make_tuple(std::make_shared<ccMesh>(nullptr),
1437  std::vector<size_t>());
1438  }
1439 
1440  // perform spherical projection
1441  std::vector<CCVector3> spherical_projection;
1442  for (unsigned int pidx = 0; pidx < size(); ++pidx) {
1443  CCVector3 projected_point = *getPoint(pidx) - camera_location;
1444  double norm = projected_point.norm();
1445  spherical_projection.push_back(
1446  projected_point +
1447  2 * static_cast<PointCoordinateType>(radius - norm) *
1448  projected_point / norm);
1449  }
1450 
1451  // add origin
1452  size_t origin_pidx = spherical_projection.size();
1453  spherical_projection.push_back(CCVector3(0, 0, 0));
1454 
1455  // calculate convex hull of spherical projection
1456  std::shared_ptr<ccMesh> visible_mesh;
1457  std::vector<size_t> pt_map;
1458  std::tie(visible_mesh, pt_map) =
1460  spherical_projection);
1461 
1462  // reassign original points to mesh
1463  size_t origin_vidx = pt_map.size();
1464  for (size_t vidx = 0; vidx < pt_map.size(); vidx++) {
1465  size_t pidx = pt_map[vidx];
1466 
1467  if (pidx < size()) {
1468  visible_mesh->setVertice(vidx, getEigenPoint(pidx));
1469  }
1470 
1471  if (pidx == origin_pidx) {
1472  origin_vidx = vidx;
1473  visible_mesh->setVertice(vidx, camera_location);
1474  }
1475  }
1476 
1477  // erase origin if part of mesh
1478  if (origin_vidx < visible_mesh->getVerticeSize()) {
1479  visible_mesh->getAssociatedCloud()->removePoints(origin_vidx);
1480  pt_map.erase(pt_map.begin() + origin_vidx);
1481  for (size_t tidx = visible_mesh->size(); tidx-- > 0;) {
1482  auto &tsi = visible_mesh->getTrianglesPtr()->getValue(tidx);
1483  if (tsi.i1 == (unsigned int)origin_vidx ||
1484  tsi.i2 == (unsigned int)origin_vidx ||
1485  tsi.i3 == (unsigned int)origin_vidx) {
1486  visible_mesh->removeTriangles(tidx);
1487  } else {
1488  if (tsi.i1 > (int)origin_vidx) tsi.i1 -= 1;
1489  if (tsi.i2 > (int)origin_vidx) tsi.i2 -= 1;
1490  if (tsi.i3 > (int)origin_vidx) tsi.i3 -= 1;
1491  }
1492  }
1493  }
1494  return std::make_tuple(visible_mesh, pt_map);
1495 }
1496 
1498  setRGBColor(ecvColor::Rgb::FromEigen(color));
1499  return (*this);
1500 }
Vector3Tpl< PointCoordinateType > CCVector3
Default 3D Vector.
Definition: CVGeom.h:798
float PointCoordinateType
Type of the coordinates of a (N-D) point.
Definition: CVTypes.h:16
std::shared_ptr< core::Tensor > image
double normal[3]
int size
bool has_colors
bool has_normals
int points
int64_t size_
Definition: FilePLY.cpp:37
math::float4 color
size_t v0_
size_t v1_
double weight_
size_t stride
long voxel_index
Definition: VoxelGridIO.cpp:29
double voxel_size
Definition: VoxelGridIO.cpp:28
static bool Print(const char *format,...)
Prints out a formatted message in console.
Definition: CVLog.cpp:113
void normalize()
Sets vector norm to unity.
Definition: CVGeom.h:428
Type dot(const Vector3Tpl &v) const
Dot product.
Definition: CVGeom.h:408
Type norm() const
Returns vector norm.
Definition: CVGeom.h:424
static Vector3Tpl fromArray(const int a[3])
Constructor from an int array.
Definition: CVGeom.h:268
static CCVector3 & GetNormalPtr(unsigned normIndex)
A 3D cloud and its associated features (color, normals, scalar fields, etc.)
std::tuple< std::shared_ptr< ccMesh >, std::vector< size_t > > HiddenPointRemoval(const Eigen::Vector3d &camera_location, const double radius) const
This is an implementation of the Hidden Point Removal operator described in Katz et....
std::vector< double > ComputePointCloudDistance(const ccPointCloud &target)
Function to compute the point to point distances between point clouds.
bool EstimateNormals(const cloudViewer::geometry::KDTreeSearchParam &search_param=cloudViewer::geometry::KDTreeSearchParamKNN(), bool fast_normal_computation=true)
Function to compute the normals of a point cloud.
std::tuple< std::shared_ptr< ccPointCloud >, std::vector< size_t > > RemoveStatisticalOutliers(size_t nb_neighbors, double std_ratio) const
Function to remove points that are further away from their nb_neighbor neighbors in average.
static std::vector< Eigen::Matrix3d > EstimatePerPointCovariances(const ccPointCloud &input, const cloudViewer::geometry::KDTreeSearchParam &search_param=cloudViewer::geometry::KDTreeSearchParamKNN())
Static function to compute the covariance matrix for each point of a point cloud. Doesn't change the ...
std::tuple< std::shared_ptr< ccPointCloud >, Eigen::MatrixXi, std::vector< std::vector< int > > > VoxelDownSampleAndTrace(double voxel_size, const Eigen::Vector3d &min_bound, const Eigen::Vector3d &max_bound, bool approximate_class=false) const
Function to downsample using geometry.ccPointCloud.VoxelDownSample.
bool HasCovariances() const
Returns 'true' if the point cloud contains per-point covariance matrix.
bool OrientNormalsTowardsCameraLocation(const Eigen::Vector3d &camera_location=Eigen::Vector3d::Zero())
Function to orient the normals of a point cloud.
std::vector< double > ComputeMahalanobisDistance() const
Function to compute the Mahalanobis distance for points in an input point cloud.
Eigen::Vector3d getEigenNormal(size_t index) const
double ComputeResolution() const
std::shared_ptr< ccPointCloud > RandomDownSample(double sampling_ratio) const
Function to downsample input pointcloud into output pointcloud randomly.
std::tuple< std::shared_ptr< ccPointCloud >, std::vector< size_t > > RemoveRadiusOutliers(size_t nb_points, double search_radius) const
Function to remove points that have less than nb_points in a sphere of a given radius.
bool hasNormals() const override
Returns whether normals are enabled or not.
std::shared_ptr< ccPointCloud > FarthestPointDownSample(const size_t num_samples, const size_t start_index=0) const
Function to downsample input pointcloud into output pointcloud with a set of points has farthest dist...
void OrientNormalsConsistentTangentPlane(size_t k)
Function to consistently orient estimated normals based on consistent tangent planes as described in ...
bool OrientNormalsToAlignWithDirection(const Eigen::Vector3d &orientation_reference=Eigen::Vector3d(0.0, 0.0, 1.0))
Function to orient the normals of a point cloud.
std::tuple< std::shared_ptr< ccMesh >, std::vector< size_t > > ComputeConvexHull() const
Function that computes the convex hull of the point cloud using qhull.
std::shared_ptr< ccPointCloud > CreateFromVoxelGrid(const cloudViewer::geometry::VoxelGrid &voxel_grid)
Function to create a PointCloud from a VoxelGrid.
std::vector< double > ComputeNearestNeighborDistance() const
bool hasColors() const override
Returns whether colors are enabled or not.
const CCVector3 & getPointNormal(unsigned pointIndex) const override
Returns normal corresponding to a given point.
std::vector< Eigen::Matrix3d > covariances_
Covariance Matrix for each point.
Eigen::Vector3d getEigenColor(size_t index) const
std::shared_ptr< ccPointCloud > UniformDownSample(size_t every_k_points) const
Function to downsample input ccPointCloud into output ccPointCloud uniformly.
ccPointCloud & PaintUniformColor(const Eigen::Vector3d &color)
Assigns each vertex in the ccMesh the same color.
ccPointCloud & NormalizeNormals()
Normalize point normals to length 1.`.
std::shared_ptr< ccPointCloud > VoxelDownSample(double voxel_size)
Function to downsample input ccPointCloud into output ccPointCloud with a voxel.
void EstimateCovariances(const cloudViewer::geometry::KDTreeSearchParam &search_param=cloudViewer::geometry::KDTreeSearchParamKNN())
Function to compute the covariance matrix for each point of a point cloud.
ccPointCloud & RemoveNonFinitePoints(bool remove_nan=true, bool remove_infinite=true)
Remove all points fromt he point cloud that have a nan entry, or infinite entries.
static std::shared_ptr< ccPointCloud > CreateFromDepthImage(const cloudViewer::geometry::Image &depth, const cloudViewer::camera::PinholeCameraIntrinsic &intrinsic, const Eigen::Matrix4d &extrinsic=Eigen::Matrix4d::Identity(), double depth_scale=1000.0, double depth_trunc=1000.0, int stride=1, bool project_valid_depth_only=true)
Factory function to create a pointcloud from a depth image and a camera model.
static std::shared_ptr< ccPointCloud > CreateFromRGBDImage(const cloudViewer::geometry::RGBDImage &image, const cloudViewer::camera::PinholeCameraIntrinsic &intrinsic, const Eigen::Matrix4d &extrinsic=Eigen::Matrix4d::Identity(), bool project_valid_depth_only=true)
Factory function to create a pointcloud from an RGB-D image and a camera model.
A scalar field associated to display-related parameters.
void computeMinAndMax() override
Determines the min and max values.
std::vector< Eigen::Vector3d > getEigenPoints() const
Eigen::Vector3d getEigenPoint(size_t index) const
ScalarType & getValue(std::size_t index)
Definition: ScalarField.h:92
void setValue(std::size_t index, ScalarType value)
Definition: ScalarField.h:96
bool resizeSafe(std::size_t count, bool initNewElements=false, ScalarType valueForNewElements=0)
Resizes memory (no exception thrown)
Definition: ScalarField.cpp:81
Contains the pinhole camera intrinsic parameters.
std::pair< double, double > GetFocalLength() const
Returns the focal length in a tuple of X-axis and Y-axis focal lengths.
std::pair< double, double > GetPrincipalPoint() const
The Image class stores image with customizable width, height, num of channels and bytes per channel.
Definition: Image.h:33
int num_of_channels_
Number of chanels in the image.
Definition: Image.h:223
int height_
Height of the image.
Definition: Image.h:221
int bytes_per_channel_
Number of bytes per channel.
Definition: Image.h:225
std::shared_ptr< Image > ConvertDepthToFloatImage(double depth_scale=1000.0, double depth_trunc=3.0) const
Definition: Image.cpp:97
int width_
Width of the image.
Definition: Image.h:219
T * PointerAt(int u, int v) const
Function to access the raw data of a single-channel Image.
Definition: Image.cpp:77
KDTree with FLANN for nearest neighbor search.
int SearchRadius(const T &query, double radius, std::vector< int > &indices, std::vector< double > &distance2) const
int SearchKNN(const T &query, int knn, std::vector< int > &indices, std::vector< double > &distance2) const
int Search(const T &query, const KDTreeSearchParam &param, std::vector< int > &indices, std::vector< double > &distance2) const
bool SetGeometry(const ccHObject &geometry)
Base class for KDTree search parameters.
static std::tuple< std::shared_ptr< ccMesh >, std::vector< size_t > > ComputeConvexHull(const std::vector< Eigen::Vector3d > &points)
Definition: ecvQhull.cpp:34
RGBDImage is for a pair of registered color and depth images,.
Definition: RGBDImage.h:27
static std::tuple< std::shared_ptr< TetraMesh >, std::vector< size_t > > CreateFromPointCloud(const ccPointCloud &point_cloud)
Function that creates a tetrahedral mesh (TetraMeshFactory.cpp). from a point cloud.
VoxelGrid is a collection of voxels which are aligned in grid.
Definition: VoxelGrid.h:64
std::unordered_map< Eigen::Vector3i, Voxel, cloudViewer::utility::hash_eigen< Eigen::Vector3i > > voxels_
Voxels contained in voxel grid.
Definition: VoxelGrid.h:274
Eigen::Vector3d GetVoxelCenterCoordinate(const Eigen::Vector3i &idx) const
Function that returns the 3d coordinates of the queried voxel center.
Definition: VoxelGrid.h:115
bool HasColors() const
Returns true if the voxel grid contains voxel colors.
Definition: VoxelGrid.h:108
Base Voxel class, containing grid id and color.
Definition: VoxelGrid.h:38
Eigen::Vector3i grid_index_
Grid coordinate index of the voxel.
Definition: VoxelGrid.h:56
Eigen::Vector3d color_
Color of the voxel.
Definition: VoxelGrid.h:58
constexpr static RgbTpl FromEigen(const Eigen::Vector3d &t)
Definition: ecvColorTypes.h:86
#define LogWarning(...)
Definition: Logging.h:72
#define LogError(...)
Definition: Logging.h:60
#define LogDebug(...)
Definition: Logging.h:90
const double * e
normal_z y
normal_z x
normal_z z
typename boost::graph_traits< CP::Graph< T > >::edges_size_type EdgeIndex
Definition: Graph.h:80
double std_dev(const T &vec)
CLOUDVIEWER_HOST_DEVICE void ComputeEigenvector0(const scalar_t *A, const scalar_t eval0, scalar_t *eigen_vector0)
CLOUDVIEWER_HOST_DEVICE void ComputeEigenvector1(const scalar_t *A, const scalar_t *evec0, const scalar_t eval1, scalar_t *eigen_vector1)
void SelectByIndex(benchmark::State &state, bool remove_duplicates, const core::Device &device)
Definition: PointCloud.cpp:149
Eigen::Matrix3d ComputeCovariance(const std::vector< Eigen::Vector3d > &points, const std::vector< IdxType > &indices)
Function to compute the covariance matrix of a set of points.
Definition: Eigen.cpp:287
MiniVec< float, N > floor(const MiniVec< float, N > &a)
Definition: MiniVec.h:75
Generic file read and write utility for python interface.
Eigen::Matrix< Index, 3, 1 > Vector3i
Definition: knncpp.h:30
Eigen::Matrix< Index, 4, 1 > Vector4i
Definition: knncpp.h:31