gtsam_points
Loading...
Searching...
No Matches
small_kdtree.hpp
1// SPDX-FileCopyrightText: Copyright 2024 Kenji Koide
2// SPDX-License-Identifier: MIT
3
4// KdTree code derived from small_gicp.
5// While the following KdTree code is written from scratch, it is heavily inspired by the nanoflann library.
6// Thus, the following original license of nanoflann is included to be sure.
7
8// https://github.com/jlblancoc/nanoflann/blob/master/include/nanoflann.hpp
9/***********************************************************************
10 * Software License Agreement (BSD License)
11 *
12 * Copyright 2008-2009 Marius Muja (mariusm@cs.ubc.ca). All rights reserved.
13 * Copyright 2008-2009 David G. Lowe (lowe@cs.ubc.ca). All rights reserved.
14 * Copyright 2011-2024 Jose Luis Blanco (joseluisblancoc@gmail.com).
15 * All rights reserved.
16 *
17 * THE BSD LICENSE
18 *
19 * Redistribution and use in source and binary forms, with or without
20 * modification, are permitted provided that the following conditions
21 * are met:
22 *
23 * 1. Redistributions of source code must retain the above copyright
24 * notice, this list of conditions and the following disclaimer.
25 * 2. Redistributions in binary form must reproduce the above copyright
26 * notice, this list of conditions and the following disclaimer in the
27 * documentation and/or other materials provided with the distribution.
28 *
29 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
30 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
31 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
32 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
33 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
34 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
35 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
36 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
37 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
38 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
39 *************************************************************************/
40#pragma once
41
42#include <atomic>
43#include <memory>
44#include <numeric>
45#include <Eigen/Core>
46
47#include <gtsam_points/config.hpp>
48#include <gtsam_points/ann/knn_result.hpp>
49#include <gtsam_points/types/frame_traits.hpp>
50
51#ifdef GTSAM_POINTS_USE_TBB
52#include <tbb/parallel_invoke.h>
53#endif
54
55namespace gtsam_points {
56
59 int max_scan_count = 128;
60};
61
64public:
68 double operator()(const Eigen::Vector4d& pt) const { return pt[axis]; }
69
76 template <typename PointCloud, typename IndexConstIterator>
78 find_axis(const PointCloud& points, IndexConstIterator first, IndexConstIterator last, const ProjectionSetting& setting) {
79 const size_t N = std::distance(first, last);
80 Eigen::Vector4d sum_pt = Eigen::Vector4d::Zero();
81 Eigen::Vector4d sum_sq = Eigen::Vector4d::Zero();
82
83 const size_t step = N < setting.max_scan_count ? 1 : N / setting.max_scan_count;
84 const size_t num_steps = N / step;
85 for (int i = 0; i < num_steps; i++) {
86 const auto itr = first + step * i;
87 const Eigen::Vector4d pt = frame::point(points, *itr);
88 sum_pt += pt;
89 sum_sq += pt.cwiseProduct(pt);
90 }
91
92 const Eigen::Vector4d mean = sum_pt / sum_pt.w();
93 const Eigen::Vector4d var = (sum_sq - mean.cwiseProduct(sum_pt));
94
95 return AxisAlignedProjection{var[0] > var[1] ? (var[0] > var[2] ? 0 : 2) : (var[1] > var[2] ? 1 : 2)};
96 }
97
98public:
99 int axis;
100};
101
102using NodeIndexType = std::uint32_t;
103static constexpr NodeIndexType INVALID_NODE = std::numeric_limits<NodeIndexType>::max();
104
106template <typename Projection>
108 union {
109 struct Leaf {
110 NodeIndexType first;
111 NodeIndexType last;
112 } lr;
113 struct NonLeaf {
114 Projection proj;
115 double thresh;
116 } sub;
117 } node_type;
118
119 NodeIndexType left = INVALID_NODE;
120 NodeIndexType right = INVALID_NODE;
121};
122
125public:
129 template <typename KdTree, typename PointCloud>
130 void build_tree(KdTree& kdtree, const PointCloud& points) const {
131 kdtree.indices.resize(frame::size(points));
132 std::iota(kdtree.indices.begin(), kdtree.indices.end(), 0);
133
134 size_t node_count = 0;
135 kdtree.nodes.resize(frame::size(points));
136 kdtree.root = create_node(kdtree, node_count, points, kdtree.indices.begin(), kdtree.indices.begin(), kdtree.indices.end());
137 kdtree.nodes.resize(node_count);
138 }
139
145 template <typename PointCloud, typename KdTree, typename IndexConstIterator>
146 NodeIndexType create_node(
147 KdTree& kdtree,
148 size_t& node_count,
149 const PointCloud& points,
150 IndexConstIterator global_first,
151 IndexConstIterator first,
152 IndexConstIterator last) const {
153 const size_t N = std::distance(first, last);
154 const NodeIndexType node_index = node_count++;
155 auto& node = kdtree.nodes[node_index];
156
157 // Create a leaf node.
158 if (N <= max_leaf_size) {
159 // std::sort(first, last);
160 node.node_type.lr.first = std::distance(global_first, first);
161 node.node_type.lr.last = std::distance(global_first, last);
162
163 return node_index;
164 }
165
166 // Find the best axis to split the input points.
167 using Projection = typename KdTree::Projection;
168 const auto proj = Projection::find_axis(points, first, last, projection_setting);
169 const auto median_itr = first + N / 2;
170 std::nth_element(first, median_itr, last, [&](size_t i, size_t j) { return proj(frame::point(points, i)) < proj(frame::point(points, j)); });
171
172 // Create a non-leaf node.
173 node.node_type.sub.proj = proj;
174 node.node_type.sub.thresh = proj(frame::point(points, *median_itr));
175
176 // Create left and right child nodes.
177 node.left = create_node(kdtree, node_count, points, global_first, first, median_itr);
178 node.right = create_node(kdtree, node_count, points, global_first, median_itr, last);
179
180 return node_index;
181 }
182
183public:
184 int max_leaf_size = 20;
186};
187
190public:
194
196 template <typename KdTree, typename PointCloud>
197 void build_tree(KdTree& kdtree, const PointCloud& points) const {
198 kdtree.indices.resize(frame::size(points));
199 std::iota(kdtree.indices.begin(), kdtree.indices.end(), 0);
200
201 std::atomic_uint64_t node_count = 0;
202 kdtree.nodes.resize(frame::size(points));
203
204#ifndef _MSC_VER
205#pragma omp parallel num_threads(num_threads)
206 {
207#pragma omp single nowait
208 {
209 kdtree.root = create_node(kdtree, node_count, points, kdtree.indices.begin(), kdtree.indices.begin(), kdtree.indices.end());
210 }
211 }
212#else
213 kdtree.root = create_node(kdtree, node_count, points, kdtree.indices.begin(), kdtree.indices.begin(), kdtree.indices.end());
214#endif
215
216 kdtree.nodes.resize(node_count);
217 }
218
224 template <typename PointCloud, typename KdTree, typename IndexConstIterator>
225 NodeIndexType create_node(
226 KdTree& kdtree,
227 std::atomic_uint64_t& node_count,
228 const PointCloud& points,
229 IndexConstIterator global_first,
230 IndexConstIterator first,
231 IndexConstIterator last) const {
232 const size_t N = std::distance(first, last);
233 const NodeIndexType node_index = node_count++;
234 auto& node = kdtree.nodes[node_index];
235
236 // Create a leaf node.
237 if (N <= max_leaf_size) {
238 // std::sort(first, last);
239 node.node_type.lr.first = std::distance(global_first, first);
240 node.node_type.lr.last = std::distance(global_first, last);
241
242 return node_index;
243 }
244
245 // Find the best axis to split the input points.
246 using Projection = typename KdTree::Projection;
247 const auto proj = Projection::find_axis(points, first, last, projection_setting);
248 const auto median_itr = first + N / 2;
249 std::nth_element(first, median_itr, last, [&](size_t i, size_t j) { return proj(frame::point(points, i)) < proj(frame::point(points, j)); });
250
251 // Create a non-leaf node.
252 node.node_type.sub.proj = proj;
253 node.node_type.sub.thresh = proj(frame::point(points, *median_itr));
254
255 // Create left and right child nodes.
256#ifndef _MSC_VER
257#pragma omp task default(shared) if (N > 512)
258 node.left = create_node(kdtree, node_count, points, global_first, first, median_itr);
259#pragma omp task default(shared) if (N > 512)
260 node.right = create_node(kdtree, node_count, points, global_first, median_itr, last);
261#pragma omp taskwait
262#else
263 node.left = create_node(kdtree, node_count, points, global_first, first, median_itr);
264 node.right = create_node(kdtree, node_count, points, global_first, median_itr, last);
265#endif
266
267 return node_index;
268 }
269
270public:
274};
275
276#ifdef GTSAM_POINTS_USE_TBB
278struct KdTreeBuilderTBB {
279public:
281 template <typename KdTree, typename PointCloud>
282 void build_tree(KdTree& kdtree, const PointCloud& points) const {
283 kdtree.indices.resize(frame::size(points));
284 std::iota(kdtree.indices.begin(), kdtree.indices.end(), 0);
285
286 std::atomic_uint64_t node_count = 0;
287 kdtree.nodes.resize(frame::size(points));
288 kdtree.root = create_node(kdtree, node_count, points, kdtree.indices.begin(), kdtree.indices.begin(), kdtree.indices.end());
289 kdtree.nodes.resize(node_count);
290 }
291
297 template <typename PointCloud, typename KdTree, typename IndexConstIterator>
298 NodeIndexType create_node(
299 KdTree& kdtree,
300 std::atomic_uint64_t& node_count,
301 const PointCloud& points,
302 IndexConstIterator global_first,
303 IndexConstIterator first,
304 IndexConstIterator last) const {
305 const size_t N = std::distance(first, last);
306 const NodeIndexType node_index = node_count++;
307 auto& node = kdtree.nodes[node_index];
308
309 // Create a leaf node.
310 if (N <= max_leaf_size) {
311 // std::sort(first, last);
312 node.node_type.lr.first = std::distance(global_first, first);
313 node.node_type.lr.last = std::distance(global_first, last);
314
315 return node_index;
316 }
317
318 // Find the best axis to split the input points.
319 using Projection = typename KdTree::Projection;
320 const auto proj = Projection::find_axis(points, first, last, projection_setting);
321 const auto median_itr = first + N / 2;
322 std::nth_element(first, median_itr, last, [&](size_t i, size_t j) { return proj(frame::point(points, i)) < proj(frame::point(points, j)); });
323
324 // Create a non-leaf node.
325 node.node_type.sub.proj = proj;
326 node.node_type.sub.thresh = proj(frame::point(points, *median_itr));
327
328 // Create left and right child nodes.
329 if (N > 512) {
330 tbb::parallel_invoke(
331 [&] { node.left = create_node(kdtree, node_count, points, global_first, first, median_itr); },
332 [&] { node.right = create_node(kdtree, node_count, points, global_first, median_itr, last); });
333 } else {
334 node.left = create_node(kdtree, node_count, points, global_first, first, median_itr);
335 node.right = create_node(kdtree, node_count, points, global_first, median_itr, last);
336 }
337
338 return node_index;
339 }
340
341public:
342 int max_leaf_size = 20;
343 ProjectionSetting projection_setting;
344};
345#endif
346
350template <typename PointCloud, typename Projection_ = AxisAlignedProjection>
352public:
353 using Projection = Projection_;
354 using Node = KdTreeNode<Projection>;
355
359 template <typename Builder = KdTreeBuilder>
360 explicit UnsafeKdTree(const PointCloud& points, const Builder& builder = KdTreeBuilder()) : points(points) {
361 if (frame::size(points) == 0) {
362 std::cerr << "warning: Empty point cloud" << std::endl;
363 return;
364 }
365
366 builder.build_tree(*this, points);
367 }
368
375 size_t nearest_neighbor_search(const Eigen::Vector3d& query_, size_t* k_indices, double* k_sq_dists, const KnnSetting& setting = KnnSetting())
376 const {
377 return knn_search<1>(query_, k_indices, k_sq_dists, setting);
378 }
379
387 size_t knn_search(const Eigen::Vector3d& query_, int k, size_t* k_indices, double* k_sq_dists, const KnnSetting& setting = KnnSetting()) const {
388 const Eigen::Vector4d query = (Eigen::Vector4d() << query_, 1.0).finished();
389 KnnResult<-1> result(k_indices, k_sq_dists, k, identity_transform(), setting.max_sq_dist);
390 knn_search(query, root, result, setting);
391 return result.num_found();
392 }
393
400 template <int N>
401 size_t knn_search(const Eigen::Vector3d& query_, size_t* k_indices, double* k_sq_dists, const KnnSetting& setting = KnnSetting()) const {
402 const Eigen::Vector4d query = (Eigen::Vector4d() << query_, 1.0).finished();
403 KnnResult<N> result(k_indices, k_sq_dists, -1, identity_transform(), setting.max_sq_dist);
404 knn_search(query, root, result, setting);
405 return result.num_found();
406 }
407
416 const Eigen::Vector3d& query_,
417 double radius,
418 std::vector<size_t>& indices,
419 std::vector<double>& sq_dists,
420 const KnnSetting& setting = KnnSetting()) const {
421 const Eigen::Vector4d query = (Eigen::Vector4d() << query_, 1.0).finished();
422 RadiusSearchResult result;
423 radius_search(query, root, result, radius * radius, setting);
424 result.sort();
425
426 indices.resize(result.num_found());
427 std::transform(result.neighbors.begin(), result.neighbors.end(), indices.begin(), [](const auto& p) { return p.first; });
428 sq_dists.resize(result.num_found());
429 std::transform(result.neighbors.begin(), result.neighbors.end(), sq_dists.begin(), [](const auto& p) { return p.second; });
430
431 return result.num_found();
432 }
433
434private:
436 template <typename Result>
437 bool knn_search(const Eigen::Vector4d& query, NodeIndexType node_index, Result& result, const KnnSetting& setting) const {
438 const auto& node = nodes[node_index];
439
440 // Check if it's a leaf node.
441 if (node.left == INVALID_NODE) {
442 // Compare the query point with all points in the leaf node.
443 for (size_t i = node.node_type.lr.first; i < node.node_type.lr.last; i++) {
444 const double sq_dist = (frame::point(points, indices[i]) - query).squaredNorm();
445 result.push(indices[i], sq_dist);
446 }
447 return !setting.fulfilled(result);
448 }
449
450 const double val = node.node_type.sub.proj(query);
451 const double diff = val - node.node_type.sub.thresh;
452 const double cut_sq_dist = diff * diff;
453
454 NodeIndexType best_child;
455 NodeIndexType other_child;
456
457 if (diff < 0.0) {
458 best_child = node.left;
459 other_child = node.right;
460 } else {
461 best_child = node.right;
462 other_child = node.left;
463 }
464
465 // Check the best child node first.
466 if (!knn_search(query, best_child, result, setting)) {
467 return false;
468 }
469
470 // Check if the other child node needs to be tested.
471 if (result.worst_distance() > cut_sq_dist) {
472 return knn_search(query, other_child, result, setting);
473 }
474
475 return true;
476 }
477
479 template <typename Result>
480 bool radius_search(const Eigen::Vector4d& query, NodeIndexType node_index, Result& result, double sq_radius, const KnnSetting& setting) const {
481 const auto& node = nodes[node_index];
482
483 // Check if it's a leaf node.
484 if (node.left == INVALID_NODE) {
485 // Compare the query point with all points in the leaf node.
486 for (size_t i = node.node_type.lr.first; i < node.node_type.lr.last; i++) {
487 if (setting.fulfilled(result)) {
488 return false;
489 }
490
491 const double sq_dist = (frame::point(points, indices[i]) - query).squaredNorm();
492 if (sq_dist < sq_radius) {
493 result.push(indices[i], sq_dist);
494 }
495 }
496 return !setting.fulfilled(result);
497 }
498
499 const double val = node.node_type.sub.proj(query);
500 const double diff = val - node.node_type.sub.thresh;
501 const double cut_sq_dist = diff * diff;
502
503 NodeIndexType best_child;
504 NodeIndexType other_child;
505
506 if (diff < 0.0) {
507 best_child = node.left;
508 other_child = node.right;
509 } else {
510 best_child = node.right;
511 other_child = node.left;
512 }
513
514 // Check the best child node first.
515 if (!radius_search(query, best_child, result, sq_radius, setting)) {
516 return false;
517 }
518
519 // Check if the other child node needs to be tested.
520 if (sq_radius > cut_sq_dist) {
521 return radius_search(query, other_child, result, sq_radius, setting);
522 }
523
524 return true;
525 }
526
527public:
529 std::vector<size_t> indices;
530
531 NodeIndexType root;
532 std::vector<Node> nodes;
533};
534
535} // namespace gtsam_points
Conventional axis-aligned projection (i.e., selecting any of XYZ axes with the largest variance).
Definition small_kdtree.hpp:63
static AxisAlignedProjection find_axis(const PointCloud &points, IndexConstIterator first, IndexConstIterator last, const ProjectionSetting &setting)
Find the axis with the largest variance.
Definition small_kdtree.hpp:78
double operator()(const Eigen::Vector4d &pt) const
Project the point to the selected axis.
Definition small_kdtree.hpp:68
int axis
Axis index (0: X, 1: Y, 2: Z)
Definition small_kdtree.hpp:99
Kd-tree builder with OpenMP.
Definition small_kdtree.hpp:189
int num_threads
Number of threads.
Definition small_kdtree.hpp:271
void build_tree(KdTree &kdtree, const PointCloud &points) const
Build KdTree.
Definition small_kdtree.hpp:197
NodeIndexType create_node(KdTree &kdtree, std::atomic_uint64_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 small_kdtree.hpp:225
ProjectionSetting projection_setting
Projection setting.
Definition small_kdtree.hpp:273
KdTreeBuilderOMP(int num_threads=4)
Constructor.
Definition small_kdtree.hpp:193
int max_leaf_size
Maximum number of points in a leaf node.
Definition small_kdtree.hpp:272
Single thread Kd-tree builder.
Definition small_kdtree.hpp:124
int max_leaf_size
Maximum number of points in a leaf node.
Definition small_kdtree.hpp:184
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 small_kdtree.hpp:146
ProjectionSetting projection_setting
Projection setting.
Definition small_kdtree.hpp:185
void build_tree(KdTree &kdtree, const PointCloud &points) const
Build KdTree.
Definition small_kdtree.hpp:130
KdTree node.
Definition small_kdtree.hpp:107
NodeIndexType right
Right child node index.
Definition small_kdtree.hpp:120
double thresh
Threshold value.
Definition small_kdtree.hpp:115
Projection proj
Projection axis.
Definition small_kdtree.hpp:114
NodeIndexType left
Left child node index.
Definition small_kdtree.hpp:119
struct gtsam_points::KdTreeNode::@0::Leaf lr
Leaf node.
struct gtsam_points::KdTreeNode::@0::NonLeaf sub
Non-leaf node.
NodeIndexType first
First point index in the leaf node.
Definition small_kdtree.hpp:110
NodeIndexType last
Last point index in the leaf node.
Definition small_kdtree.hpp:111
KdTree-based nearest neighbor search.
Definition kdtree.hpp:21
K-nearest neighbor search result container.
Definition knn_result.hpp:36
K-nearest neighbor search setting.
Definition knn_result.hpp:11
bool fulfilled(const Result &result) const
Check if the result satisfies the early termination condition.
Definition knn_result.hpp:15
Standard point cloud class that holds only pointers to point attributes.
Definition point_cloud.hpp:19
Parameters to control the projection axis search.
Definition small_kdtree.hpp:58
int max_scan_count
Maximum number of points to use for the axis search.
Definition small_kdtree.hpp:59
Radius neighbor search result container.
Definition knn_result.hpp:121
"Unsafe" KdTree.
Definition small_kdtree.hpp:351
NodeIndexType root
Root node index (should be zero)
Definition small_kdtree.hpp:531
size_t radius_search(const Eigen::Vector3d &query_, double radius, std::vector< size_t > &indices, std::vector< double > &sq_dists, const KnnSetting &setting=KnnSetting()) const
Find neighbors in a search radius.
Definition small_kdtree.hpp:415
size_t knn_search(const Eigen::Vector3d &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 small_kdtree.hpp:387
UnsafeKdTree(const PointCloud &points, const Builder &builder=KdTreeBuilder())
Constructor.
Definition small_kdtree.hpp:360
std::vector< size_t > indices
Point indices refered by nodes.
Definition small_kdtree.hpp:529
size_t nearest_neighbor_search(const Eigen::Vector3d &query_, size_t *k_indices, double *k_sq_dists, const KnnSetting &setting=KnnSetting()) const
Find the nearest neighbor.
Definition small_kdtree.hpp:375
std::vector< Node > nodes
Kd-tree nodes.
Definition small_kdtree.hpp:532
const PointCloud & points
Input points.
Definition small_kdtree.hpp:528
size_t knn_search(const Eigen::Vector3d &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 small_kdtree.hpp:401
Identity transformation function. (use std::identity instead in C++20)
Definition knn_result.hpp:26