ACloudViewer  3.9.4
A Modern Library for 3D Data Processing
SLACOptimizer.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 <FileSystem.h>
11 
17 
18 namespace cloudViewer {
19 namespace t {
20 namespace pipelines {
21 namespace slac {
23 
24 // Write point clouds to disk after preprocessing (remove outliers,
25 // estimate normals, etc).
26 static std::vector<std::string> PreprocessPointClouds(
27  const std::vector<std::string>& fnames,
28  const SLACOptimizerParams& params) {
29  std::string subdir_name = params.GetSubfolderName();
30  if (!subdir_name.empty()) {
32  }
33 
34  std::vector<std::string> fnames_processed;
35 
36  for (auto& fname : fnames) {
37  std::string fname_processed = fmt::format(
38  "{}/{}", subdir_name,
40  fnames_processed.emplace_back(fname_processed);
41  if (utility::filesystem::FileExists(fname_processed)) continue;
42 
43  auto pcd = io::CreatePointCloudFromFile(fname);
44  if (pcd == nullptr) {
45  utility::LogError("Internal error: pcd is nullptr.");
46  }
47 
48  // Pre-processing input pointcloud.
49  if (params.voxel_size_ > 0) {
50  pcd = pcd->VoxelDownSample(params.voxel_size_);
51  pcd->RemoveStatisticalOutliers(20, 2.0);
52  pcd->EstimateNormals();
53  } else {
54  pcd->RemoveStatisticalOutliers(20, 2.0);
55  if (!pcd->hasNormals()) {
56  pcd->EstimateNormals();
57  }
58  }
59 
60  io::WritePointCloud(fname_processed, *pcd);
61  utility::LogInfo("Saving processed point cloud {}", fname_processed);
62  }
63 
64  return fnames_processed;
65 }
66 
67 // The correspondences from NNS has the value as the target index
68 // correspondening the source point which is index of the value itself.
69 // Therefore, for target_indexed_correspondences = {2, 3, -1 , 4}.
70 // (source, target) correspondences are: {{0, 2}, {1, 3}, {3, 4}}.
71 //
72 // For convenience to access
73 // source and target pointcloud indexed by their correspondences, this
74 // function converts {N, 1} shaped target_indices correspondences to {C, 2}
75 // shaped CorrespondenceSet, where C is the number of correspondences such that
76 //
77 // For getting correspondence indexed pointclouds:
78 // source_indexed_pcd = source.GetPointPositions()
79 // .IndexGet({correspondence_set.T()[0]});
80 // target_indexed_pcd = target.GetPointPositions()
81 // .IndexGet({correspondence_set.T()[1]});
82 //
83 // For getting the i-th correspondence pair:
84 // correspondence_pair_i = make_pair(correspondence[i][0],
85 // correspondence[i][1]);
87  const core::Tensor& target_correspondences) {
88  core::Device device = target_correspondences.GetDevice();
89  int64_t N = target_correspondences.GetLength();
90 
91  core::Tensor valid_correspondences =
92  target_correspondences.Ne(-1).Reshape({-1});
93 
94  // Only take valid indices.
95  core::Tensor target_indices =
96  target_correspondences.IndexGet({valid_correspondences});
97 
98  // Number of good correspondences (C).
99  int64_t C = target_indices.GetLength();
100 
101  // correpondence_set : (i, corres[i]).
102  // source[i] and target[corres[i]] is a correspondence.
103  core::Tensor source_indices =
104  core::Tensor::Arange(0, N, 1, core::Int64, device)
105  .IndexGet({valid_correspondences})
106  .Reshape({C, 1});
107 
108  // Creating {C, 2} shaped tensor by horizontal stacking {source_indices,
109  // target_indices}.
110  core::Tensor correspondence_set({C, 2}, core::Int64, device);
111  correspondence_set.SetItem(
112  {core::TensorKey::Slice(0, C, 1), core::TensorKey::Slice(0, 1, 1)},
113  source_indices);
114  correspondence_set.SetItem(
115  {core::TensorKey::Slice(0, C, 1), core::TensorKey::Slice(1, 2, 1)},
116  target_indices);
117 
118  return correspondence_set;
119 }
120 
121 // Aggressive pruning -- reject any suspicious pair
122 //
123 // tpcd_i is the source pointcloud, tpcd_j is the target pointcloud,
124 // T_i is the transformation_source_to_world,
125 // T_j is the transformation_target_to_world,
126 // T_ij is the transformation_source_to_target.
127 // distance_threshold is the search_distance for NNS.
128 // i and j are the indices of source and target pointcloud respectively.
130  int i,
131  int j,
132  PointCloud& tpcd_i,
133  PointCloud& tpcd_j,
134  const core::Tensor& T_i,
135  const core::Tensor& T_j,
136  const core::Tensor& T_ij,
137  float distance_threshold,
138  float fitness_threshold,
139  bool debug) {
140  core::Device device = tpcd_i.GetDevice();
141  core::Dtype dtype = tpcd_i.GetPointPositions().GetDtype();
142 
145 
146  core::AssertTensorShape(T_i, {4, 4});
147  core::AssertTensorShape(T_j, {4, 4});
148  core::AssertTensorShape(T_ij, {4, 4});
149 
150  PointCloud tpcd_i_transformed_Tij = tpcd_i.Clone();
151  tpcd_i_transformed_Tij.Transform(T_ij);
152 
153  // Obtain correspondence via nns, between tpcd_i_transformed_Tij and tpcd_j.
155  bool check = tpcd_j_nns.HybridIndex(distance_threshold);
156  if (!check) {
157  utility::LogError("Index is not set.");
158  }
159  core::Tensor target_indices, residual_distances_Tij, neighbour_counts;
160  std::tie(target_indices, residual_distances_Tij, neighbour_counts) =
161  tpcd_j_nns.HybridSearch(tpcd_i_transformed_Tij.GetPointPositions(),
162  distance_threshold, 1);
163 
164  target_indices = target_indices.To(core::Int64);
165 
166  // Get the correspondence_set Transformed of shape {C, 2}.
167  core::Tensor correspondence_set =
169 
170  // Get correspondence indexed pointcloud.
171  PointCloud tpcd_i_indexed(
172  tpcd_i.GetPointPositions().IndexGet({correspondence_set.T()[0]}));
173  PointCloud tpcd_j_indexed(
174  tpcd_j.GetPointPositions().IndexGet({correspondence_set.T()[1]}));
175 
176  // Inlier Ratio is calculated on pointclouds transformed by their pose in
177  // model frame, to reject any suspicious pair.
178  tpcd_i_indexed.Transform(T_i);
179  tpcd_j_indexed.Transform(T_j);
180 
181  core::Tensor residual = (tpcd_i_indexed.GetPointPositions() -
182  tpcd_j_indexed.GetPointPositions());
183  core::Tensor square_residual = (residual * residual).Sum({1});
184  core::Tensor inliers =
185  square_residual.Le(distance_threshold * distance_threshold);
186 
187  int64_t num_inliers = inliers.To(core::Int64).Sum({0}).Item<int64_t>();
188 
189  float inlier_ratio = static_cast<float>(num_inliers) /
190  static_cast<float>(inliers.GetLength());
191 
192  utility::LogDebug("Tij and (Ti, Tj) compatibility ratio = {}.",
193  inlier_ratio);
194 
195  if (j != i + 1 && inlier_ratio < fitness_threshold) {
196  if (debug) {
198  tpcd_i, tpcd_j, correspondence_set,
199  T_j.Inverse().Matmul(T_i).To(device, dtype));
200  }
201  return core::Tensor();
202  }
203 
204  return correspondence_set;
205 }
206 
207 // Read pose graph containing loop closures and odometry to compute
208 // correspondences.
210  const std::vector<std::string>& fnames_processed,
211  const PoseGraph& pose_graph,
213  const SLACDebugOption& debug_option) {
214  // Enumerate pose graph edges.
215  for (auto& edge : pose_graph.edges_) {
216  int i = edge.source_node_id_;
217  int j = edge.target_node_id_;
218 
219  utility::LogInfo("Processing {:02d} -> {:02d}", i, j);
220 
221  std::string correspondences_fname = fmt::format(
222  "{}/{:03d}_{:03d}.npy", params.GetSubfolderName(), i, j);
223  if (utility::filesystem::FileExists(correspondences_fname)) continue;
224 
225  PointCloud tpcd_i =
226  CreateTPCDFromFile(fnames_processed[i], params.device_);
227  PointCloud tpcd_j =
228  CreateTPCDFromFile(fnames_processed[j], params.device_);
229 
230  // pose of i in model frame.
232  pose_graph.nodes_[i].pose_);
233  // pose of j in model frame.
235  pose_graph.nodes_[j].pose_);
236  // transformation of i to j.
238  edge.transformation_);
239 
240  // Get correspondences.
242  i, j, tpcd_i, tpcd_j, T_i, T_j, T_ij,
243  params.distance_threshold_, params.fitness_threshold_,
244  debug_option.debug_);
245 
246  if (correspondence_set.GetLength() > 0) {
247  correspondence_set.Save(correspondences_fname);
248  utility::LogInfo("Saving {} corres for {:02d} -> {:02d}",
249  correspondence_set.GetLength(), i, j);
250  }
251  }
252 }
253 
254 static void InitializeControlGrid(ControlGrid& ctr_grid,
255  const std::vector<std::string>& fnames) {
256  core::Device device(ctr_grid.GetDevice());
257  for (auto& fname : fnames) {
258  utility::LogInfo("Initializing grid for {}", fname);
259 
260  auto tpcd = CreateTPCDFromFile(fname, device);
261  ctr_grid.Touch(tpcd);
262  }
263  utility::LogInfo("Initialization finished.");
264 }
265 
266 static void UpdatePoses(PoseGraph& fragment_pose_graph, core::Tensor& delta) {
267  core::Tensor delta_poses = delta.View({-1, 6}).To(core::Device("CPU:0"));
268 
269  if (delta_poses.GetLength() != int64_t(fragment_pose_graph.nodes_.size())) {
270  utility::LogError("Dimension Mismatch");
271  }
272  for (int64_t i = 0; i < delta_poses.GetLength(); ++i) {
273  core::Tensor pose_delta = kernel::PoseToTransformation(delta_poses[i]);
274  core::Tensor pose_tensor =
276  fragment_pose_graph.nodes_[i].pose_)
277  .To(core::Float32));
278 
279  Eigen::Matrix<float, -1, -1, Eigen::RowMajor> pose_eigen =
281  Eigen::Matrix<double, -1, -1, Eigen::RowMajor> pose_eigen_d =
282  pose_eigen.cast<double>().eval();
283  Eigen::Ref<Eigen::Matrix<double, 4, 4, Eigen::RowMajor>> pose_eigen_ref(
284  pose_eigen_d);
285  fragment_pose_graph.nodes_[i].pose_ = pose_eigen_ref;
286  }
287 }
288 
289 static void UpdateControlGrid(ControlGrid& ctr_grid, core::Tensor& delta) {
290  core::Tensor delta_cgrids = delta.View({-1, 3});
291  if (delta_cgrids.GetLength() != int64_t(ctr_grid.Size())) {
292  utility::LogError("Dimension Mismatch");
293  }
294 
295  ctr_grid.GetCurrPositions().Slice(0, 0, ctr_grid.Size()) += delta_cgrids;
296 }
297 
298 std::pair<PoseGraph, ControlGrid> RunSLACOptimizerForFragments(
299  const std::vector<std::string>& fnames,
300  const PoseGraph& pose_graph,
302  const SLACDebugOption& debug_option) {
303  core::Device device(params.device_);
304  if (!params.slac_folder_.empty()) {
306  }
307 
308  // First preprocess the point cloud with downsampling and normal
309  // estimation.
310  auto fnames_down = PreprocessPointClouds(fnames, params);
311  // Then obtain the correspondences given the pose graph
312  SaveCorrespondencesForPointClouds(fnames_down, pose_graph, params,
313  debug_option);
314 
315  // First initialize the ctr_grid.
316  // grid size = 3.0 / 8: recommended by the original implementation
317  // https://github.com/qianyizh/ElasticReconstruction
318  // grid count = 8000: empirical value, will be increased dynamically if
319  // exceeded.
320  ControlGrid ctr_grid(3.0 / 8, 8000, device);
321  InitializeControlGrid(ctr_grid, fnames_down);
322  ctr_grid.Compactify();
323 
324  // Fill-in
325  // fragments x 6 (se3) + control_grids x 3 (R^3)
326  int64_t num_params = fnames_down.size() * 6 + ctr_grid.Size() * 3;
327  utility::LogInfo("Initializing the {}^2 Hessian matrix", num_params);
328 
329  PoseGraph pose_graph_update(pose_graph);
330  for (int itr = 0; itr < params.max_iterations_; ++itr) {
331  utility::LogInfo("Iteration {}", itr);
332  core::Tensor AtA = core::Tensor::Zeros({num_params, num_params},
333  core::Float32, device);
334  core::Tensor Atb =
335  core::Tensor::Zeros({num_params, 1}, core::Float32, device);
336 
337  core::Tensor indices_eye0 =
338  core::Tensor::Arange(0, 6, 1, core::Int64, device);
339  AtA.IndexSet({indices_eye0, indices_eye0},
340  core::Tensor::Ones({}, core::Float32, device));
341 
342  core::Tensor residual_data =
343  core::Tensor::Zeros({1}, core::Float32, device);
344  FillInSLACAlignmentTerm(AtA, Atb, residual_data, ctr_grid, fnames_down,
345  pose_graph_update, params, debug_option);
346 
347  utility::LogInfo("Alignment loss = {}", residual_data[0].Item<float>());
348 
349  core::Tensor residual_reg =
350  core::Tensor::Zeros({1}, core::Float32, device);
351  FillInSLACRegularizerTerm(AtA, Atb, residual_reg, ctr_grid,
352  pose_graph_update.nodes_.size(), params,
353  debug_option);
354  utility::LogInfo("Regularizer loss = {}",
355  residual_reg[0].Item<float>());
356 
357  core::Tensor delta = AtA.Solve(Atb.Neg());
358 
359  core::Tensor delta_poses =
360  delta.Slice(0, 0, 6 * pose_graph_update.nodes_.size());
361  core::Tensor delta_cgrids = delta.Slice(
362  0, 6 * pose_graph_update.nodes_.size(), delta.GetLength());
363 
364  UpdatePoses(pose_graph_update, delta_poses);
365  UpdateControlGrid(ctr_grid, delta_cgrids);
366  }
367  return std::make_pair(pose_graph_update, ctr_grid);
368 }
369 
370 PoseGraph RunRigidOptimizerForFragments(const std::vector<std::string>& fnames,
371  const PoseGraph& pose_graph,
373  const SLACDebugOption& debug_option) {
374  core::Device device(params.device_);
375  if (!params.slac_folder_.empty()) {
377  }
378 
379  // First preprocess the point cloud with downsampling and normal
380  // estimation.
381  std::vector<std::string> fnames_down =
382  PreprocessPointClouds(fnames, params);
383  // Then obtain the correspondences given the pose graph
384  SaveCorrespondencesForPointClouds(fnames_down, pose_graph, params,
385  debug_option);
386 
387  // Fill-in
388  // fragments x 6 (se3)
389  int64_t num_params = fnames_down.size() * 6;
390  utility::LogInfo("Initializing the {}^2 Hessian matrix.", num_params);
391 
392  PoseGraph pose_graph_update(pose_graph);
393  for (int itr = 0; itr < params.max_iterations_; ++itr) {
394  utility::LogInfo("Iteration {}", itr);
395  core::Tensor AtA = core::Tensor::Zeros({num_params, num_params},
396  core::Float32, device);
397  core::Tensor Atb =
398  core::Tensor::Zeros({num_params, 1}, core::Float32, device);
399  core::Tensor residual = core::Tensor::Zeros({1}, core::Float32, device);
400 
401  // Fix pose 0
402  core::Tensor indices_eye0 = core::Tensor::Arange(0, 6, 1);
403  AtA.IndexSet({indices_eye0, indices_eye0},
404  1e5 * core::Tensor::Ones({}, core::Float32, device));
405 
406  FillInRigidAlignmentTerm(AtA, Atb, residual, fnames_down,
407  pose_graph_update, params, debug_option);
408  utility::LogInfo("Loss = {}", residual[0].Item<float>());
409 
410  core::Tensor delta = AtA.Solve(Atb.Neg());
411  UpdatePoses(pose_graph_update, delta);
412  }
413 
414  return pose_graph_update;
415 }
416 
417 } // namespace slac
418 } // namespace pipelines
419 } // namespace t
420 } // namespace cloudViewer
filament::Texture::InternalFormat format
cmdLineReadable * params[]
#define AssertTensorDevice(tensor,...)
Definition: TensorCheck.h:45
#define AssertTensorDtype(tensor,...)
Definition: TensorCheck.h:21
#define AssertTensorShape(tensor,...)
Definition: TensorCheck.h:61
static TensorKey Slice(utility::optional< int64_t > start, utility::optional< int64_t > stop, utility::optional< int64_t > step)
Definition: TensorKey.cpp:138
Tensor Solve(const Tensor &rhs) const
Definition: Tensor.cpp:1928
Tensor Matmul(const Tensor &rhs) const
Definition: Tensor.cpp:1919
void IndexSet(const std::vector< Tensor > &index_tensors, const Tensor &src_tensor)
Advanced indexing getter.
Definition: Tensor.cpp:936
Tensor Sum(const SizeVector &dims, bool keepdim=false) const
Definition: Tensor.cpp:1240
void Save(const std::string &file_name) const
Save tensor to numpy's npy format.
Definition: Tensor.cpp:1877
static Tensor Arange(const Scalar start, const Scalar stop, const Scalar step=1, const Dtype dtype=core::Int64, const Device &device=core::Device("CPU:0"))
Create a 1D tensor with evenly spaced values in the given interval.
Definition: Tensor.cpp:436
int64_t GetLength() const
Definition: Tensor.h:1125
Tensor Ne(const Tensor &value) const
Element-wise not-equals-to of tensors, returning a new boolean tensor.
Definition: Tensor.cpp:1714
Dtype GetDtype() const
Definition: Tensor.h:1164
Tensor Inverse() const
Definition: Tensor.cpp:1982
Tensor SetItem(const Tensor &value)
Set all items. Equivalent to tensor[:] = value in Python.
Definition: Tensor.cpp:564
Tensor IndexGet(const std::vector< Tensor > &index_tensors) const
Advanced indexing getter. This will always allocate a new Tensor.
Definition: Tensor.cpp:905
Tensor Le(const Tensor &value) const
Definition: Tensor.cpp:1650
static Tensor Zeros(const SizeVector &shape, Dtype dtype, const Device &device=Device("CPU:0"))
Create a tensor fill with zeros.
Definition: Tensor.cpp:406
Tensor View(const SizeVector &dst_shape) const
Definition: Tensor.cpp:721
Device GetDevice() const override
Definition: Tensor.cpp:1435
Tensor Reshape(const SizeVector &dst_shape) const
Definition: Tensor.cpp:671
static Tensor Ones(const SizeVector &shape, Dtype dtype, const Device &device=Device("CPU:0"))
Create a tensor fill with ones.
Definition: Tensor.cpp:412
Tensor Slice(int64_t dim, int64_t start, int64_t stop, int64_t step=1) const
Definition: Tensor.cpp:857
Tensor To(Dtype dtype, bool copy=false) const
Definition: Tensor.cpp:739
A Class for nearest neighbor search.
bool HybridIndex(utility::optional< double > radius={})
std::tuple< Tensor, Tensor, Tensor > HybridSearch(const Tensor &query_points, const double radius, const int max_knn) const
Data structure defining the pose graph.
Definition: PoseGraph.h:96
std::vector< PoseGraphNode > nodes_
List of PoseGraphNode.
Definition: PoseGraph.h:108
std::vector< PoseGraphEdge > edges_
List of PoseGraphEdge.
Definition: PoseGraph.h:111
A point cloud contains a list of 3D points.
Definition: PointCloud.h:82
core::Tensor & GetPointPositions()
Get the value of the "positions" attribute. Convenience function.
Definition: PointCloud.h:124
core::Device GetDevice() const override
Returns the device attribute of this PointCloud.
Definition: PointCloud.h:421
PointCloud Clone() const
Returns copy of the point cloud on the same device.
Definition: PointCloud.cpp:123
PointCloud & Transform(const core::Tensor &transformation)
Transforms the PointPositions and PointNormals (if exist) of the PointCloud.
Definition: PointCloud.cpp:171
void Touch(const geometry::PointCloud &pcd)
Allocate control grids in the shared camera space.
Definition: ControlGrid.cpp:46
#define LogInfo(...)
Definition: Logging.h:81
#define LogError(...)
Definition: Logging.h:60
#define LogDebug(...)
Definition: Logging.h:90
core::Tensor EigenMatrixToTensor(const Eigen::MatrixBase< Derived > &matrix)
Converts a eigen matrix of shape (M, N) with alignment A and type T to a tensor.
Eigen::Matrix< float, Eigen::Dynamic, Eigen::Dynamic, Eigen::RowMajor > TensorToEigenMatrixXf(const core::Tensor &tensor)
Converts a 2D tensor to Eigen::MatrixXf of same shape. Regardless of the tensor dtype,...
const Dtype Int64
Definition: Dtype.cpp:47
CLOUDVIEWER_HOST_DEVICE Pair< First, Second > make_pair(const First &_first, const Second &_second)
Definition: SlabTraits.h:49
const Dtype Float32
Definition: Dtype.cpp:42
::ccPointCloud PointCloud
Definition: PointCloud.h:19
void To(const core::Tensor &src, core::Tensor &dst, double scale, double offset)
Definition: Image.cpp:17
bool WritePointCloud(const std::string &filename, const geometry::PointCloud &pointcloud, const cloudViewer::io::WritePointCloudOption &params)
std::shared_ptr< geometry::PointCloud > CreatePointCloudFromFile(const std::string &filename, const std::string &format, bool print_progress)
core::Tensor PoseToTransformation(const core::Tensor &pose)
Convert pose to the transformation matrix.
void SaveCorrespondencesForPointClouds(const std::vector< std::string > &fnames_processed, const PoseGraph &pose_graph, const SLACOptimizerParams &params, const SLACDebugOption &debug_option)
Read pose graph containing loop closures and odometry to compute putative correspondences between pai...
static void InitializeControlGrid(ControlGrid &ctr_grid, const std::vector< std::string > &fnames)
PoseGraph RunRigidOptimizerForFragments(const std::vector< std::string > &fnames, const PoseGraph &pose_graph, const SLACOptimizerParams &params, const SLACDebugOption &debug_option)
Extended ICP to simultaneously align multiple point clouds with dense pairwise point-to-plane distanc...
static PointCloud CreateTPCDFromFile(const std::string &fname, const core::Device &device=core::Device("CPU:0"))
std::pair< PoseGraph, ControlGrid > RunSLACOptimizerForFragments(const std::vector< std::string > &fnames, const PoseGraph &pose_graph, const SLACOptimizerParams &params, const SLACDebugOption &debug_option)
Simultaneous Localization and Calibration: Self-Calibration of Consumer Depth Cameras,...
void VisualizePointCloudCorrespondences(const t::geometry::PointCloud &tpcd_i, const t::geometry::PointCloud &tpcd_j, const core::Tensor correspondences, const core::Tensor &T_ij)
Visualize pairs with correspondences.
static void UpdateControlGrid(ControlGrid &ctr_grid, core::Tensor &delta)
static void FillInSLACAlignmentTerm(Tensor &AtA, Tensor &Atb, Tensor &residual, ControlGrid &ctr_grid, const PointCloud &tpcd_param_i, const PointCloud &tpcd_param_j, const Tensor &Ti, const Tensor &Tj, const int i, const int j, const int n_fragments, const float threshold)
static std::vector< std::string > PreprocessPointClouds(const std::vector< std::string > &fnames, const SLACOptimizerParams &params)
static core::Tensor GetCorrespondenceSetForPointCloudPair(int i, int j, PointCloud &tpcd_i, PointCloud &tpcd_j, const core::Tensor &T_i, const core::Tensor &T_j, const core::Tensor &T_ij, float distance_threshold, float fitness_threshold, bool debug)
static core::Tensor ConvertCorrespondencesTargetIndexedToCx2Form(const core::Tensor &target_correspondences)
void FillInSLACRegularizerTerm(Tensor &AtA, Tensor &Atb, Tensor &residual, ControlGrid &ctr_grid, int n_frags, const SLACOptimizerParams &params, const SLACDebugOption &debug_option)
static void FillInRigidAlignmentTerm(Tensor &AtA, Tensor &Atb, Tensor &residual, PointCloud &tpcd_i, PointCloud &tpcd_j, const Tensor &Ti, const Tensor &Tj, const int i, const int j, const float threshold)
static void UpdatePoses(PoseGraph &fragment_pose_graph, core::Tensor &delta)
bool FileExists(const std::string &filename)
Definition: FileSystem.cpp:524
bool MakeDirectory(const std::string &directory)
Definition: FileSystem.cpp:491
std::string GetFileNameWithoutDirectory(const std::string &filename)
Definition: FileSystem.cpp:301
Generic file read and write utility for python interface.