small_gicp
kdtree.hpp
Go to the documentation of this file.
1 // SPDX-FileCopyrightText: Copyright 2024 Kenji Koide
2 // SPDX-License-Identifier: MIT
3 
4 // While the following KdTree code is written from scratch, it is heavily inspired by the nanoflann library.
5 // Thus, the following original license of nanoflann is included to be sure.
6 
7 // https://github.com/jlblancoc/nanoflann/blob/master/include/nanoflann.hpp
8 /***********************************************************************
9  * Software License Agreement (BSD License)
10  *
11  * Copyright 2008-2009 Marius Muja (mariusm@cs.ubc.ca). All rights reserved.
12  * Copyright 2008-2009 David G. Lowe (lowe@cs.ubc.ca). All rights reserved.
13  * Copyright 2011-2024 Jose Luis Blanco (joseluisblancoc@gmail.com).
14  * All rights reserved.
15  *
16  * THE BSD LICENSE
17  *
18  * Redistribution and use in source and binary forms, with or without
19  * modification, are permitted provided that the following conditions
20  * are met:
21  *
22  * 1. Redistributions of source code must retain the above copyright
23  * notice, this list of conditions and the following disclaimer.
24  * 2. Redistributions in binary form must reproduce the above copyright
25  * notice, this list of conditions and the following disclaimer in the
26  * documentation and/or other materials provided with the distribution.
27  *
28  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
29  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
30  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
31  * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
32  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
33  * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
34  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
35  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
36  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
37  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
38  *************************************************************************/
39 #pragma once
40 
41 #include <memory>
42 #include <numeric>
43 #include <Eigen/Core>
44 
49 
50 namespace small_gicp {
51 
52 using NodeIndexType = std::uint32_t;
53 static constexpr NodeIndexType INVALID_NODE = std::numeric_limits<NodeIndexType>::max();
54 
56 template <typename Projection>
57 struct KdTreeNode {
58  union {
59  struct Leaf {
62  } lr;
63  struct NonLeaf {
64  Projection proj;
65  double thresh;
66  } sub;
68 
69  NodeIndexType left = INVALID_NODE;
70  NodeIndexType right = INVALID_NODE;
71 };
72 
74 struct KdTreeBuilder {
75 public:
79  template <typename KdTree, typename PointCloud>
80  void build_tree(KdTree& kdtree, const PointCloud& points) const {
81  kdtree.indices.resize(traits::size(points));
82  std::iota(kdtree.indices.begin(), kdtree.indices.end(), 0);
83 
84  size_t node_count = 0;
85  kdtree.nodes.resize(traits::size(points));
86  kdtree.root = create_node(kdtree, node_count, points, kdtree.indices.begin(), kdtree.indices.begin(), kdtree.indices.end());
87  kdtree.nodes.resize(node_count);
88  }
89 
95  template <typename PointCloud, typename KdTree, typename IndexConstIterator>
96  NodeIndexType create_node(KdTree& kdtree, size_t& node_count, const PointCloud& points, IndexConstIterator global_first, IndexConstIterator first, IndexConstIterator last)
97  const {
98  const size_t N = std::distance(first, last);
99  const NodeIndexType node_index = node_count++;
100  auto& node = kdtree.nodes[node_index];
101 
102  // Create a leaf node.
103  if (N <= max_leaf_size) {
104  // std::sort(first, last);
105  node.node_type.lr.first = std::distance(global_first, first);
106  node.node_type.lr.last = std::distance(global_first, last);
107 
108  return node_index;
109  }
110 
111  // Find the best axis to split the input points.
112  using Projection = typename KdTree::Projection;
113  const auto proj = Projection::find_axis(points, first, last, projection_setting);
114  const auto median_itr = first + N / 2;
115  std::nth_element(first, median_itr, last, [&](size_t i, size_t j) { return proj(traits::point(points, i)) < proj(traits::point(points, j)); });
116 
117  // Create a non-leaf node.
118  node.node_type.sub.proj = proj;
119  node.node_type.sub.thresh = proj(traits::point(points, *median_itr));
120 
121  // Create left and right child nodes.
122  node.left = create_node(kdtree, node_count, points, global_first, first, median_itr);
123  node.right = create_node(kdtree, node_count, points, global_first, median_itr, last);
124 
125  return node_index;
126  }
127 
128 public:
129  int max_leaf_size = 20;
131 };
132 
136 template <typename PointCloud, typename Projection_ = AxisAlignedProjection>
137 struct UnsafeKdTree {
138 public:
139  using Projection = Projection_;
141 
145  template <typename Builder = KdTreeBuilder>
146  explicit UnsafeKdTree(const PointCloud& points, const Builder& builder = KdTreeBuilder()) : points(points) {
147  if (traits::size(points) == 0) {
148  std::cerr << "warning: Empty point cloud" << std::endl;
149  return;
150  }
151 
152  builder.build_tree(*this, points);
153  }
154 
161  size_t nearest_neighbor_search(const Eigen::Vector4d& query, size_t* k_indices, double* k_sq_dists, const KnnSetting& setting = KnnSetting()) const {
162  return knn_search<1>(query, k_indices, k_sq_dists, setting);
163  }
164 
172  size_t knn_search(const Eigen::Vector4d& query, int k, size_t* k_indices, double* k_sq_dists, const KnnSetting& setting = KnnSetting()) const {
173  KnnResult<-1> result(k_indices, k_sq_dists, k);
174  knn_search(query, root, result, setting);
175  return result.num_found();
176  }
177 
184  template <int N>
185  size_t knn_search(const Eigen::Vector4d& query, size_t* k_indices, double* k_sq_dists, const KnnSetting& setting = KnnSetting()) const {
186  KnnResult<N> result(k_indices, k_sq_dists);
187  knn_search(query, root, result, setting);
188  return result.num_found();
189  }
190 
191 private:
193  template <typename Result>
194  bool knn_search(const Eigen::Vector4d& query, NodeIndexType node_index, Result& result, const KnnSetting& setting) const {
195  const auto& node = nodes[node_index];
196 
197  // Check if it's a leaf node.
198  if (node.left == INVALID_NODE) {
199  // Compare the query point with all points in the leaf node.
200  for (size_t i = node.node_type.lr.first; i < node.node_type.lr.last; i++) {
201  const double sq_dist = (traits::point(points, indices[i]) - query).squaredNorm();
202  result.push(indices[i], sq_dist);
203  }
204  return !setting.fulfilled(result);
205  }
206 
207  const double val = node.node_type.sub.proj(query);
208  const double diff = val - node.node_type.sub.thresh;
209  const double cut_sq_dist = diff * diff;
210 
211  NodeIndexType best_child;
212  NodeIndexType other_child;
213 
214  if (diff < 0.0) {
215  best_child = node.left;
216  other_child = node.right;
217  } else {
218  best_child = node.right;
219  other_child = node.left;
220  }
221 
222  // Check the best child node first.
223  if (!knn_search(query, best_child, result, setting)) {
224  return false;
225  }
226 
227  // Check if the other child node needs to be tested.
228  if (result.worst_distance() > cut_sq_dist) {
229  return knn_search(query, other_child, result, setting);
230  }
231 
232  return true;
233  }
234 
235 public:
237  std::vector<size_t> indices;
238 
240  std::vector<Node> nodes;
241 };
242 
244 template <typename PointCloud, typename Projection = AxisAlignedProjection>
245 struct KdTree {
246 public:
247  using Ptr = std::shared_ptr<KdTree<PointCloud, Projection>>;
248  using ConstPtr = std::shared_ptr<const KdTree<PointCloud, Projection>>;
249 
250  template <typename Builder = KdTreeBuilder>
251  explicit KdTree(std::shared_ptr<const PointCloud> points, const Builder& builder = Builder()) : points(points),
252  kdtree(*points, builder) {}
253 
261  size_t nearest_neighbor_search(const Eigen::Vector4d& query, size_t* k_indices, double* k_sq_dists, const KnnSetting& setting = KnnSetting()) const {
262  return kdtree.nearest_neighbor_search(query, k_indices, k_sq_dists, setting);
263  }
264 
272  size_t knn_search(const Eigen::Vector4d& query, size_t k, size_t* k_indices, double* k_sq_dists, const KnnSetting& setting = KnnSetting()) const {
273  return kdtree.knn_search(query, k, k_indices, k_sq_dists, setting);
274  }
275 
276 public:
277  const std::shared_ptr<const PointCloud> points;
279 };
280 
281 namespace traits {
282 
283 template <typename PointCloud, typename Projection>
284 struct Traits<UnsafeKdTree<PointCloud, Projection>> {
285  static size_t nearest_neighbor_search(const UnsafeKdTree<PointCloud, Projection>& tree, const Eigen::Vector4d& point, size_t* k_indices, double* k_sq_dists) {
286  return tree.nearest_neighbor_search(point, k_indices, k_sq_dists);
287  }
288 
289  static size_t knn_search(const UnsafeKdTree<PointCloud, Projection>& tree, const Eigen::Vector4d& point, size_t k, size_t* k_indices, double* k_sq_dists) {
290  return tree.knn_search(point, k, k_indices, k_sq_dists);
291  }
292 };
293 
294 template <typename PointCloud, typename Projection>
295 struct Traits<KdTree<PointCloud, Projection>> {
296  static size_t nearest_neighbor_search(const KdTree<PointCloud, Projection>& tree, const Eigen::Vector4d& point, size_t* k_indices, double* k_sq_dists) {
297  return tree.nearest_neighbor_search(point, k_indices, k_sq_dists);
298  }
299 
300  static size_t knn_search(const KdTree<PointCloud, Projection>& tree, const Eigen::Vector4d& point, size_t k, size_t* k_indices, double* k_sq_dists) {
301  return tree.knn_search(point, k, k_indices, k_sq_dists);
302  }
303 };
304 
305 } // namespace traits
306 
307 } // namespace small_gicp
size_t size(const T &points)
Get the number of points.
Definition: traits.hpp:16
auto point(const T &points, size_t i)
Get i-th point. 4D vector is used to take advantage of SIMD intrinsics. The last element must be fill...
Definition: traits.hpp:40
Definition: flat_container.hpp:12
std::uint32_t NodeIndexType
Definition: kdtree.hpp:52
Single thread Kd-tree builder.
Definition: kdtree.hpp:74
NodeIndexType create_node(KdTree &kdtree, size_t &node_count, const PointCloud &points, IndexConstIterator global_first, IndexConstIterator first, IndexConstIterator last) const
Create a Kd-tree node from the given point indices.
Definition: kdtree.hpp:96
int max_leaf_size
Maximum number of points in a leaf node.
Definition: kdtree.hpp:129
ProjectionSetting projection_setting
Projection setting.
Definition: kdtree.hpp:130
void build_tree(KdTree &kdtree, const PointCloud &points) const
Build KdTree.
Definition: kdtree.hpp:80
KdTree node.
Definition: kdtree.hpp:57
union small_gicp::KdTreeNode::@0 node_type
NodeIndexType right
Right child node index.
Definition: kdtree.hpp:70
Projection proj
Projection axis.
Definition: kdtree.hpp:64
NodeIndexType first
First point index in the leaf node.
Definition: kdtree.hpp:60
NodeIndexType last
Last point index in the leaf node.
Definition: kdtree.hpp:61
double thresh
Threshold value.
Definition: kdtree.hpp:65
struct small_gicp::KdTreeNode::@0::Leaf lr
Leaf node.
NodeIndexType left
Left child node index.
Definition: kdtree.hpp:69
struct small_gicp::KdTreeNode::@0::NonLeaf sub
Non-leaf node.
"Safe" KdTree that holds the ownership of the input points.
Definition: kdtree.hpp:245
const std::shared_ptr< const PointCloud > points
Points.
Definition: kdtree.hpp:277
const UnsafeKdTree< PointCloud, Projection > kdtree
KdTree.
Definition: kdtree.hpp:278
size_t knn_search(const Eigen::Vector4d &query, size_t k, size_t *k_indices, double *k_sq_dists, const KnnSetting &setting=KnnSetting()) const
Find k-nearest neighbors. This method uses dynamic memory allocation.
Definition: kdtree.hpp:272
std::shared_ptr< const KdTree< PointCloud, Projection > > ConstPtr
Definition: kdtree.hpp:248
std::shared_ptr< KdTree< PointCloud, Projection > > Ptr
Definition: kdtree.hpp:247
KdTree(std::shared_ptr< const PointCloud > points, const Builder &builder=Builder())
Definition: kdtree.hpp:251
size_t nearest_neighbor_search(const Eigen::Vector4d &query, size_t *k_indices, double *k_sq_dists, const KnnSetting &setting=KnnSetting()) const
Find k-nearest neighbors. This method uses dynamic memory allocation.
Definition: kdtree.hpp:261
K-nearest neighbor search result container.
Definition: knn_result.hpp:33
size_t num_found() const
Number of found neighbors.
Definition: knn_result.hpp:73
K-nearest neighbor search setting.
Definition: knn_result.hpp:13
bool fulfilled(const Result &result) const
Check if the result satisfies the early termination condition.
Definition: knn_result.hpp:17
Point cloud.
Definition: point_cloud.hpp:15
Parameters to control the projection axis search.
Definition: projection.hpp:12
"Unsafe" KdTree.
Definition: kdtree.hpp:137
UnsafeKdTree(const PointCloud &points, const Builder &builder=KdTreeBuilder())
Constructor.
Definition: kdtree.hpp:146
NodeIndexType root
Root node index (should be zero)
Definition: kdtree.hpp:239
std::vector< size_t > indices
Point indices refered by nodes.
Definition: kdtree.hpp:237
Projection_ Projection
Definition: kdtree.hpp:139
const PointCloud & points
Input points.
Definition: kdtree.hpp:236
size_t knn_search(const Eigen::Vector4d &query, size_t *k_indices, double *k_sq_dists, const KnnSetting &setting=KnnSetting()) const
Find k-nearest neighbors. This method uses fixed and static memory allocation. Might be faster for sm...
Definition: kdtree.hpp:185
size_t nearest_neighbor_search(const Eigen::Vector4d &query, size_t *k_indices, double *k_sq_dists, const KnnSetting &setting=KnnSetting()) const
Find the nearest neighbor.
Definition: kdtree.hpp:161
std::vector< Node > nodes
Kd-tree nodes.
Definition: kdtree.hpp:240
size_t knn_search(const Eigen::Vector4d &query, int k, size_t *k_indices, double *k_sq_dists, const KnnSetting &setting=KnnSetting()) const
Find k-nearest neighbors. This method uses dynamic memory allocation.
Definition: kdtree.hpp:172
static size_t knn_search(const KdTree< PointCloud, Projection > &tree, const Eigen::Vector4d &point, size_t k, size_t *k_indices, double *k_sq_dists)
Definition: kdtree.hpp:300
static size_t nearest_neighbor_search(const KdTree< PointCloud, Projection > &tree, const Eigen::Vector4d &point, size_t *k_indices, double *k_sq_dists)
Definition: kdtree.hpp:296
static size_t nearest_neighbor_search(const UnsafeKdTree< PointCloud, Projection > &tree, const Eigen::Vector4d &point, size_t *k_indices, double *k_sq_dists)
Definition: kdtree.hpp:285
static size_t knn_search(const UnsafeKdTree< PointCloud, Projection > &tree, const Eigen::Vector4d &point, size_t k, size_t *k_indices, double *k_sq_dists)
Definition: kdtree.hpp:289
Definition: traits.hpp:13