ACloudViewer  3.9.4
A Modern Library for 3D Data Processing
VisualizerForAlignment.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 
11 #include <tinyfiledialogs/tinyfiledialogs.h>
13 
14 namespace cloudViewer {
15 
18  // clang-format off
19  utility::LogInfo(" -- Alignment control --");
20  utility::LogInfo(" Ctrl + R : Reset source and target to initial state.");
21  utility::LogInfo(" Ctrl + S : Save current alignment session into a JSON file.");
22  utility::LogInfo(" Ctrl + O : Load current alignment session from a JSON file.");
23  utility::LogInfo(" Ctrl + A : Align point clouds based on manually annotations.");
24  utility::LogInfo(" Ctrl + I : Run ICP refinement.");
25  utility::LogInfo(" Ctrl + D : Run voxel downsample for both source and target.");
26  utility::LogInfo(" Ctrl + K : Load a polygon from a JSON file and crop source.");
27  utility::LogInfo(" Ctrl + E : Evaluate error and save to files.");
28  // clang-format on
29 }
30 
32  std::shared_ptr<ccPointCloud> source,
33  std::shared_ptr<ccPointCloud> target) {
37  source_copy_ptr_ = std::make_shared<ccPointCloud>();
38  target_copy_ptr_ = std::make_shared<ccPointCloud>();
39  *source_copy_ptr_ = *source;
40  *target_copy_ptr_ = *target;
42 }
43 
45  GLFWwindow *window, int key, int scancode, int action, int mods) {
46  if (action == GLFW_PRESS && (mods & GLFW_MOD_CONTROL)) {
47  const char *filename = nullptr;
48  const char *pattern[1] = {"*.json"};
49  switch (key) {
50  case GLFW_KEY_R: {
53  ResetViewPoint(true);
55  return;
56  }
57  case GLFW_KEY_S: {
58  std::string default_alignment =
59  default_directory_ + "alignment.json";
60  if (use_dialog_) {
61  filename = tinyfd_saveFileDialog(
62  "Alignment session", default_alignment.c_str(), 1,
63  pattern, "JSON file (*.json)");
64  } else {
65  filename = default_alignment.c_str();
66  }
67  if (filename) {
69  }
70  return;
71  }
72  case GLFW_KEY_O: {
73  std::string default_alignment =
74  default_directory_ + "alignment.json";
75  if (use_dialog_) {
76  filename = tinyfd_openFileDialog(
77  "Alignment session", default_alignment.c_str(), 1,
78  pattern, "JSON file (*.json)", 0);
79  } else {
80  filename = default_alignment.c_str();
81  }
82  if (filename) {
84  }
85  return;
86  }
87  case GLFW_KEY_A: {
89  ResetViewPoint(true);
91  }
92  return;
93  }
94  case GLFW_KEY_I: {
95  if (use_dialog_) {
96  std::string buffer =
98  const char *str = tinyfd_inputBox(
99  "Set voxel size",
100  "Set max correspondence distance for ICP (ignored "
101  "if it is non-positive)",
102  buffer.c_str());
103  if (!str) {
104  utility::LogWarning("Dialog closed.");
105  return;
106  } else {
107  char *end;
108  errno = 0;
109  double l = std::strtod(str, &end);
110  if (errno == ERANGE &&
111  (l == HUGE_VAL || l == -HUGE_VAL)) {
113  "Illegal input, use default max "
114  "correspondence distance.");
115  } else {
117  }
118  }
119  }
120  if (max_correspondence_distance_ > 0.0) {
122  "ICP with max correspondence distance {:.4f}.",
127  Eigen::Matrix4d::Identity(),
128  pipelines::registration::
129  TransformationEstimationPointToPoint(true),
131  1e-6, 1e-6, 30));
133  "Registration finished with fitness {:.4f} and "
134  "RMSE {:.4f}.",
135  result.fitness_, result.inlier_rmse_);
136  if (result.fitness_ > 0.0) {
138  result.transformation_ * transformation_;
140  source_copy_ptr_->Transform(result.transformation_);
141  UpdateGeometry();
142  }
143  } else {
145  "No ICP performed due to illegal max "
146  "correspondence distance.");
147  }
148  return;
149  }
150  case GLFW_KEY_D: {
151  if (use_dialog_) {
152  std::string buffer = fmt::format("{:.4f}", voxel_size_);
153  const char *str = tinyfd_inputBox(
154  "Set voxel size",
155  "Set voxel size (ignored if it is non-positive)",
156  buffer.c_str());
157  if (!str) {
158  utility::LogWarning("Dialog closed.");
159  return;
160  } else {
161  char *end;
162  errno = 0;
163  double l = std::strtod(str, &end);
164  if (errno == ERANGE &&
165  (l == HUGE_VAL || l == -HUGE_VAL)) {
167  "Illegal input, use default voxel size.");
168  } else {
169  voxel_size_ = l;
170  }
171  }
172  }
173  if (voxel_size_ > 0.0) {
174  utility::LogInfo("Voxel downsample with voxel size {:.4f}.",
175  voxel_size_);
177  *source_copy_ptr_->VoxelDownSample(voxel_size_);
178  UpdateGeometry();
179  } else {
181  "No voxel downsample performed due to illegal "
182  "voxel size.");
183  }
184  return;
185  }
186  case GLFW_KEY_K: {
188  if (use_dialog_) {
189  if (const char *polygon_filename_chars =
190  tinyfd_openFileDialog(
191  "Bounding polygon", "polygon.json",
192  0, nullptr, nullptr, 0)) {
194  std::string(polygon_filename_chars);
195  } else {
197  "Internal error: tinyfd_openFileDialog "
198  "returned nullptr.");
199  }
200  } else {
201  polygon_filename_ = "polygon.json";
202  }
203  }
204  auto polygon_volume = std::make_shared<
207  *polygon_volume)) {
209  *polygon_volume->CropPointCloud(*source_copy_ptr_);
210  ResetViewPoint(true);
211  UpdateGeometry();
212  }
213  return;
214  }
215  case GLFW_KEY_E: {
216  std::string default_alignment =
217  default_directory_ + "alignment.json";
218  if (use_dialog_) {
219  filename = tinyfd_saveFileDialog(
220  "Alignment session", default_alignment.c_str(), 1,
221  pattern, "JSON file (*.json)");
222  } else {
223  filename = default_alignment.c_str();
224  }
225  if (filename) {
228  }
229  return;
230  }
231  }
232  }
233  visualization::Visualizer::KeyPressCallback(window, key, scancode, action,
234  mods);
235 }
236 
246 }
247 
250  return false;
251  }
260  source_copy_ptr_->Transform(transformation_);
263  ResetViewPoint(true);
264  return UpdateGeometry();
265 }
266 
268  const auto &source_idx = source_visualizer_.GetPickedPoints();
269  const auto &target_idx = target_visualizer_.GetPickedPoints();
270  if (source_idx.empty() || target_idx.empty() ||
271  source_idx.size() != target_idx.size()) {
273  "# of picked points mismatch: {:d} in source, {:d} in "
274  "target.",
275  (int)source_idx.size(), (int)target_idx.size());
276  return false;
277  }
279  with_scaling_);
281  for (size_t i = 0; i < source_idx.size(); i++) {
282  corres.push_back(Eigen::Vector2i(source_idx[i], target_idx[i]));
283  }
284  utility::LogInfo("Error is {:.4f} before alignment.",
286  *alignment_session_.target_ptr_, corres));
292  source_copy_ptr_->Transform(transformation_);
293  utility::LogInfo("Error is {:.4f} before alignment.",
295  *alignment_session_.target_ptr_, corres));
296  return true;
297 }
298 
300  utility::LogInfo("Current transformation is:");
301  utility::LogInfo("\t{:.6f} {:.6f} {:.6f} {:.6f}", transformation_(0, 0),
302  transformation_(0, 1), transformation_(0, 2),
303  transformation_(0, 3));
304  utility::LogInfo("\t{:.6f} {:.6f} {:.6f} {:.6f}", transformation_(1, 0),
305  transformation_(1, 1), transformation_(1, 2),
306  transformation_(1, 3));
307  utility::LogInfo("\t{:.6f} {:.6f} {:.6f} {:.6f}", transformation_(2, 0),
308  transformation_(2, 1), transformation_(2, 2),
309  transformation_(2, 3));
310  utility::LogInfo("\t{:.6f} {:.6f} {:.6f} {:.6f}", transformation_(3, 0),
311  transformation_(3, 1), transformation_(3, 2),
312  transformation_(3, 3));
313 }
314 
316  const std::string &filename) {
317  // Evaluate source_copy_ptr_ and target_copy_ptr_
318  std::string source_filename =
320  ".source.ply";
321  std::string target_filename =
323  ".target.ply";
324  std::string source_binname =
326  ".source.bin";
327  std::string target_binname =
329  ".target.bin";
330  FILE *f;
331 
332  io::WritePointCloud(source_filename, *source_copy_ptr_);
333  auto source_dis =
334  source_copy_ptr_->ComputePointCloudDistance(*target_copy_ptr_);
335  f = utility::filesystem::FOpen(source_binname, "wb");
336  if (!f) {
337  utility::LogError("EvaluateAlignmentAndSave: Unable to open file {}.",
338  source_binname);
339  return;
340  }
341  fwrite(source_dis.data(), sizeof(double), source_dis.size(), f);
342  fclose(f);
343  io::WritePointCloud(target_filename, *target_copy_ptr_);
344  auto target_dis =
345  target_copy_ptr_->ComputePointCloudDistance(*source_copy_ptr_);
346  f = utility::filesystem::FOpen(target_binname, "wb");
347  if (!f) {
348  utility::LogError("EvaluateAlignmentAndSave: Unable to open file {}.",
349  target_binname);
350  return;
351  }
352  fwrite(target_dis.data(), sizeof(double), target_dis.size(), f);
353  fclose(f);
354 }
355 
356 } // namespace cloudViewer
std::string filename
filament::Texture::InternalFormat format
core::Tensor result
Definition: VtkUtils.cpp:76
std::shared_ptr< ccPointCloud > source_ptr_
std::shared_ptr< ccPointCloud > target_ptr_
std::vector< size_t > source_indices_
Eigen::Matrix4d_u transformation_
std::vector< size_t > target_indices_
std::shared_ptr< ccPointCloud > target_copy_ptr_
bool LoadSessionFromFile(const std::string &filename)
visualization::VisualizerWithEditing & target_visualizer_
visualization::VisualizerWithEditing & source_visualizer_
bool AddSourceAndTarget(std::shared_ptr< ccPointCloud > source, std::shared_ptr< ccPointCloud > target)
std::shared_ptr< ccPointCloud > source_copy_ptr_
void KeyPressCallback(GLFWwindow *window, int key, int scancode, int action, int mods) override
void EvaluateAlignmentAndSave(const std::string &filename)
bool SaveSessionToFile(const std::string &filename)
Class that defines the convergence criteria of ICP.
Definition: Registration.h:36
Eigen::Matrix4d ComputeTransformation(const ccPointCloud &source, const ccPointCloud &target, const CorrespondenceSet &corres) const override
double ComputeRMSE(const ccPointCloud &source, const ccPointCloud &target, const CorrespondenceSet &corres) const override
double point_size_
Point size for PointCloud.
Definition: RenderOption.h:178
virtual bool UpdateGeometry(std::shared_ptr< const ccHObject > geometry_ptr=nullptr)
Function to update geometry.
Definition: Visualizer.cpp:466
virtual void KeyPressCallback(GLFWwindow *window, int key, int scancode, int action, int mods)
RenderOption & GetRenderOption()
Function to retrieve the associated RenderOption.
Definition: Visualizer.h:177
virtual void UpdateRender()
Function to inform render needed to be updated.
Definition: Visualizer.cpp:479
void ResetViewPoint(bool reset_bounding_box=false)
Function to reset view point.
virtual bool AddGeometry(std::shared_ptr< const ccHObject > geometry_ptr, bool reset_bounding_box=true)
Function to add geometry to the scene and create corresponding shaders.
Definition: Visualizer.cpp:336
#define LogWarning(...)
Definition: Logging.h:72
#define LogInfo(...)
Definition: Logging.h:81
#define LogError(...)
Definition: Logging.h:60
bool WriteIJsonConvertible(const std::string &filename, const cloudViewer::utility::IJsonConvertible &object)
bool WritePointCloud(const std::string &filename, const ccPointCloud &pointcloud, const WritePointCloudOption &params)
bool ReadIJsonConvertible(const std::string &filename, cloudViewer::utility::IJsonConvertible &object)
std::vector< Eigen::Vector2i > CorrespondenceSet
RegistrationResult RegistrationICP(const ccPointCloud &source, const ccPointCloud &target, double max_correspondence_distance, const Eigen::Matrix4d &init, const TransformationEstimation &estimation, const ICPConvergenceCriteria &criteria)
Functions for ICP registration.
std::string GetFileNameWithoutExtension(const std::string &filename)
Definition: FileSystem.cpp:295
bool FileExists(const std::string &filename)
Definition: FileSystem.cpp:524
FILE * FOpen(const std::string &filename, const std::string &mode)
Definition: FileSystem.cpp:609
Generic file read and write utility for python interface.
Eigen::Matrix< Index, 2, 1 > Vector2i
Definition: knncpp.h:29