Skip to content

Commit f6f65d0

Browse files
authored
Merge pull request #35 from Jaybro/ctad
Ctad
2 parents 46699e6 + 35b5fbd commit f6f65d0

File tree

17 files changed

+193
-70
lines changed

17 files changed

+193
-70
lines changed

CMakeLists.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ include(${CMAKE_CURRENT_SOURCE_DIR}/cmake/utils.cmake)
66

77
project(pico_tree
88
LANGUAGES CXX
9-
VERSION 0.8.2
9+
VERSION 0.8.3
1010
DESCRIPTION "PicoTree is a C++ header only library for fast nearest neighbor searches and range searches using a KdTree."
1111
HOMEPAGE_URL "https://github.com/Jaybro/pico_tree")
1212

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ PicoTree is a C++ header only library with [Python bindings](https://github.com/
1111
| [Scikit-learn KDTree][skkd] 1.2.2 | ... | 6.2s | ... | 42.2s |
1212
| [pykdtree][pykd] 1.3.7 | ... | 1.0s | ... | 6.6s |
1313
| [OpenCV FLANN][cvfn] 4.6.0 | 1.9s | ... | 4.7s | ... |
14-
| PicoTree KdTree v0.8.2 | 0.9s | 1.0s | 2.8s | 3.1s |
14+
| PicoTree KdTree v0.8.3 | 0.9s | 1.0s | 2.8s | 3.1s |
1515

1616
Two [LiDAR](./docs/benchmark.md) based point clouds of sizes 7733372 and 7200863 were used to generate these numbers. The first point cloud was the input to the build algorithm and the second to the query algorithm. All benchmarks were run on a single thread with the following parameters: `max_leaf_size=10` and `knn=1`. A more detailed [C++ comparison](./docs/benchmark.md) of PicoTree is available with respect to [nanoflann][nano].
1717

docs/benchmark.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ One of the PicoTree examples contains [benchmarks](../examples/benchmark/) of di
44

55
The results described in this document were generated on 29-08-2021 using MinGW GCC 10.3, PicoTree v0.7.4 and Nanoflann v1.3.2.
66

7-
Note: The performance of PicoTree v0.8.2 released on 07-09-2023 is identical to that of v0.7.4. However, the build algorithm of nanoflann v1.5.0 regressed and has become 90% slower.
7+
Note: The performance of PicoTree v0.8.3 released on 26-09-2023 is identical to that of v0.7.4. However, the build algorithm of nanoflann v1.5.0 regressed and has become 90% slower.
88

99
# Data sets
1010

examples/eigen/eigen.cpp

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -40,13 +40,12 @@ std::vector<PointX> GenerateRandomEigenN(
4040
// neighbors.
4141
void BasicVector() {
4242
using PointX = Eigen::Vector2f;
43-
using KdTree = pico_tree::KdTree<std::vector<PointX>>;
44-
using Scalar = typename KdTree::ScalarType;
45-
using Index = typename KdTree::IndexType;
43+
using Scalar = typename PointX::Scalar;
44+
using Index = int;
4645

4746
// Including <pico_tree/eigen3_traits.hpp> provides support for Eigen types
4847
// with std::vector.
49-
pico_tree::KdTree<std::vector<PointX>> tree(
48+
pico_tree::KdTree tree(
5049
GenerateRandomEigenN<PointX>(kNumPoints, kArea), kMaxLeafCount);
5150

5251
PointX p = PointX::Random() * kArea / Scalar(2.0);

examples/kd_tree/kd_tree_creation.cpp

Lines changed: 67 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,28 @@
33
#include <pico_tree/kd_tree.hpp>
44
#include <pico_tree/vector_traits.hpp>
55

6-
// This application demonstrates how a KdTree can take its input point set.
7-
// Although all of the examples use an std::vector<> as the input for building a
8-
// KdTree, they will work with any of the inputs supported by this library
9-
// (e.g., Eigen::Matrix<>).
6+
// The examples in this application demonstrate the different ways in which a
7+
// KdTree can be constructed from a point set. The following is covered:
8+
//
9+
// Value-move idiom:
10+
// A KdTree takes the input by value. This means that the KdTree takes ownership
11+
// of a copy of the input. When a copy is not desired, the point set can either
12+
// be moved into the KdTree or it can take the point set by reference by
13+
// wrapping it in an std::reference_wrapper<>. In the latter case, the KdTree
14+
// will only have shallow ownership of the input. This allows it to be used for
15+
// other purposes as well.
16+
//
17+
// Class template argument deduction:
18+
// The class template argument that defines the space type (the input point set
19+
// type) does not always have to be specified and can be deduced by the
20+
// compiler. In case another class template argument needs to be specified, such
21+
// as the metric type, then the space type may still be deduced using the
22+
// MakeKdTree<> convenience method.
23+
24+
// Although all of the examples use an std::vector<std::array<>> as the input
25+
// for building a KdTree, they will work with any of the inputs supported by
26+
// this library (e.g., Eigen::Matrix<>).
27+
using Space = std::vector<std::array<float, 3>>;
1028

1129
template <typename Tree>
1230
void QueryTree(Tree const& tree) {
@@ -17,61 +35,83 @@ void QueryTree(Tree const& tree) {
1735
std::cout << "Index closest point: " << nn.index << std::endl;
1836
}
1937

20-
auto MakePointSet() {
21-
std::vector<std::array<float, 3>> points{
22-
{0.0f, 1.0f, 2.0f}, {3.0f, 4.0f, 5.0f}};
38+
auto MakePointSet() { return Space{{0.0f, 1.0f, 2.0f}, {3.0f, 4.0f, 5.0f}}; }
2339

24-
return points;
25-
}
26-
27-
// The KdTree takes the input by value. In this example, creating a KdTree
28-
// results in a copy of the input point set.
40+
// In this example, the creation of a KdTree results in a copy of the input
41+
// point set. The KdTree has full ownership of the copy.
2942
void BuildKdTreeWithCopy() {
3043
int max_leaf_size = 12;
3144
auto points = MakePointSet();
3245

33-
pico_tree::KdTree<std::vector<std::array<float, 3>>> tree(
34-
points, max_leaf_size);
46+
pico_tree::KdTree<Space> tree(points, max_leaf_size);
3547

3648
QueryTree(tree);
3749
}
3850

39-
// The KdTree takes the input by value. In this example, the point sets are not
40-
// copied but moved into the KdTree. This prevents a copy.
51+
// In this example, the point sets are not copied but moved into the KdTrees
52+
// when they are created. Each tree has full ownership of the moved point set.
4153
void BuildKdTreeWithMove() {
4254
int max_leaf_size = 12;
4355
auto points = MakePointSet();
4456

45-
pico_tree::KdTree<std::vector<std::array<float, 3>>> tree1(
46-
std::move(points), max_leaf_size);
57+
pico_tree::KdTree<Space> tree1(std::move(points), max_leaf_size);
4758

48-
pico_tree::KdTree<std::vector<std::array<float, 3>>> tree2(
49-
MakePointSet(), max_leaf_size);
59+
pico_tree::KdTree<Space> tree2(MakePointSet(), max_leaf_size);
5060

5161
QueryTree(tree1);
5262
QueryTree(tree2);
5363
}
5464

55-
// The KdTree takes the input by value. In this example, the input is taken by
56-
// reference. This prevents a copy.
65+
// In this example, the input is wrapped in an std::reference_wrapper<>. Thus,
66+
// only a reference is copied by a KdTree. Each tree only has shallow ownership
67+
// of the input point set.
5768
void BuildKdTreeWithReference() {
5869
int max_leaf_size = 12;
5970
auto points = MakePointSet();
6071

6172
// By reference.
62-
pico_tree::KdTree<std::reference_wrapper<std::vector<std::array<float, 3>>>>
63-
tree1(points, max_leaf_size);
73+
pico_tree::KdTree<std::reference_wrapper<Space>> tree1(points, max_leaf_size);
6474

6575
// By const reference.
66-
pico_tree::KdTree<
67-
std::reference_wrapper<std::vector<std::array<float, 3>> const>>
68-
tree2(points, max_leaf_size);
76+
pico_tree::KdTree<std::reference_wrapper<Space const>> tree2(
77+
points, max_leaf_size);
78+
79+
QueryTree(tree1);
80+
QueryTree(tree2);
81+
}
82+
83+
// This example shows that the type of the input point set may be deduced by the
84+
// compiler.
85+
void SpaceTypeDeduction() {
86+
int max_leaf_size = 12;
87+
auto points = MakePointSet();
88+
89+
// The type of the first class template argument, the space type, is
90+
// determined by the compiler.
91+
pico_tree::KdTree tree1(std::ref(points), max_leaf_size);
92+
93+
using KdTree1Type = pico_tree::KdTree<std::reference_wrapper<Space>>;
94+
95+
static_assert(std::is_same_v<decltype(tree1), KdTree1Type>);
96+
97+
// Using the previous auto deduction method, we still have to specify the
98+
// space type when we want to change any of the other template arguments, such
99+
// as the metric type. In this case we can use the MakeKdTree method to make
100+
// life a bit easier.
101+
auto tree2 =
102+
pico_tree::MakeKdTree<pico_tree::LInf>(std::ref(points), max_leaf_size);
103+
104+
using KdTree2Type =
105+
pico_tree::KdTree<std::reference_wrapper<Space>, pico_tree::LInf>;
106+
107+
static_assert(std::is_same_v<decltype(tree2), KdTree2Type>);
69108

70109
QueryTree(tree1);
71110
QueryTree(tree2);
72111
}
73112

74113
int main() {
114+
SpaceTypeDeduction();
75115
BuildKdTreeWithReference();
76116
BuildKdTreeWithMove();
77117
BuildKdTreeWithCopy();

examples/kd_tree/kd_tree_minimal.cpp

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
#include <iostream>
2+
// Provides support for fixed size arrays and std::array.
23
#include <pico_tree/array_traits.hpp>
34
#include <pico_tree/kd_tree.hpp>
5+
// Provides support for std::vector.
46
#include <pico_tree/vector_traits.hpp>
57

68
// This example shows how to create and query a KdTree. An std::vector can be
@@ -11,10 +13,11 @@ int main() {
1113
std::vector<std::array<float, 3>> points{
1214
{0.0f, 1.0f, 2.0f}, {3.0f, 4.0f, 5.0f}};
1315

14-
// The KdTree takes the input by value. To prevent a copy, we can either use
15-
// an std::reference_wrapper or move the point set into the tree.
16-
pico_tree::KdTree<std::reference_wrapper<std::vector<std::array<float, 3>>>>
17-
tree(points, max_leaf_size);
16+
// The KdTree takes the input by value. To prevent a copy, we can either move
17+
// the point set into the tree or the point set can be taken by reference by
18+
// wrapping it in an std::reference_wrapper. Below we take the input by
19+
// reference:
20+
pico_tree::KdTree tree(std::ref(points), max_leaf_size);
1821

1922
float query[3] = {4.0f, 4.0f, 4.0f};
2023
pico_tree::Neighbor<int, float> nn;

examples/opencv/opencv.cpp

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,8 +33,7 @@ void BasicVector() {
3333
using PointX = cv::Vec<Scalar, 3>;
3434
std::vector<PointX> random = GenerateRandomVecN<PointX>(kNumPoints, kArea);
3535

36-
pico_tree::KdTree<std::reference_wrapper<std::vector<PointX>>> tree(
37-
random, 10);
36+
pico_tree::KdTree tree(std::cref(random), 10);
3837

3938
auto p = random[random.size() / 2];
4039

examples/pico_understory/CMakeLists.txt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,10 @@ target_sources(pico_understory
99
${CMAKE_CURRENT_LIST_DIR}/pico_understory/internal/cover_tree_node.hpp
1010
${CMAKE_CURRENT_LIST_DIR}/pico_understory/internal/cover_tree_search.hpp
1111
${CMAKE_CURRENT_LIST_DIR}/pico_understory/internal/kd_tree_priority_search.hpp
12+
${CMAKE_CURRENT_LIST_DIR}/pico_understory/internal/matrix_space_traits.hpp
13+
${CMAKE_CURRENT_LIST_DIR}/pico_understory/internal/matrix_space.hpp
14+
${CMAKE_CURRENT_LIST_DIR}/pico_understory/internal/point_traits.hpp
15+
${CMAKE_CURRENT_LIST_DIR}/pico_understory/internal/priority_queue.hpp
1216
${CMAKE_CURRENT_LIST_DIR}/pico_understory/internal/rkd_tree_builder.hpp
1317
${CMAKE_CURRENT_LIST_DIR}/pico_understory/internal/rkd_tree_hh_data.hpp
1418
${CMAKE_CURRENT_LIST_DIR}/pico_understory/internal/static_buffer.hpp

examples/pico_understory/pico_understory/internal/kd_tree_priority_search.hpp

Lines changed: 7 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
#include "pico_tree/internal/kd_tree_node.hpp"
77
#include "pico_tree/internal/point.hpp"
88
#include "pico_tree/metric.hpp"
9+
#include "pico_understory/internal/priority_queue.hpp"
910

1011
namespace pico_tree::internal {
1112

@@ -48,40 +49,24 @@ class PrioritySearchNearestEuclidean {
4849
inline void operator()(NodeType const* const root_node) {
4950
std::size_t leaves_visited = 0;
5051
queue_.reserve(max_leaves_visited_);
51-
queue_.push_back({ScalarType(0.0), root_node});
52+
queue_.PushBack(ScalarType(0.0), root_node);
5253
while (!queue_.empty()) {
53-
auto const [node_box_distance, node] = queue_.front();
54+
auto const [node_box_distance, node] = queue_.Front();
5455

5556
if (leaves_visited >= max_leaves_visited_ ||
5657
visitor_.max() < node_box_distance) {
5758
break;
5859
}
5960

60-
// TODO For small queues it's probably not worth it to use a deque.
61-
queue_.erase(queue_.begin());
61+
queue_.PopFront();
6262

6363
SearchNearest(node, node_box_distance);
6464
++leaves_visited;
65+
queue_.reserve(max_leaves_visited_ - leaves_visited);
6566
}
6667
}
6768

6869
private:
69-
inline void Push(NodeType const* node, ScalarType node_box_distance) {
70-
if (queue_.size() < max_leaves_visited_) {
71-
if (queue_.empty()) {
72-
queue_.push_back({node_box_distance, node});
73-
} else if (queue_.back().first < node_box_distance) {
74-
queue_.push_back({node_box_distance, node});
75-
} else {
76-
queue_.push_back(queue_.back());
77-
InsertSorted(
78-
queue_.begin(), std::prev(queue_.end()), {node_box_distance, node});
79-
}
80-
} else if (queue_.back().first > node_box_distance) {
81-
InsertSorted(queue_.begin(), queue_.end(), {node_box_distance, node});
82-
}
83-
}
84-
8570
// Add nodes to the priority queue until a leaf node is reached.
8671
inline void SearchNearest(
8772
NodeType const* const node, ScalarType node_box_distance) {
@@ -139,7 +124,7 @@ class PrioritySearchNearestEuclidean {
139124

140125
// Add to priority queue to be searched later.
141126
if (visitor_.max() > node_box_distance) {
142-
Push(node_2nd, node_box_distance);
127+
queue_.PushBack(node_box_distance, node_2nd);
143128
}
144129
}
145130
}
@@ -151,7 +136,7 @@ class PrioritySearchNearestEuclidean {
151136
Size max_leaves_visited_;
152137
// TODO This gets created every query. Solving this would require a different
153138
// PicoTree interface. The queue probably shouldn't be too big.
154-
std::vector<QueuePairType> queue_;
139+
PriorityQueue<ScalarType, NodeType const*> queue_;
155140
Visitor_& visitor_;
156141
};
157142

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
#pragma once
2+
3+
#include <vector>
4+
5+
#include "pico_tree/internal/search_visitor.hpp"
6+
7+
namespace pico_tree::internal {
8+
9+
template <typename P_, typename T_>
10+
class PriorityQueue {
11+
public:
12+
inline explicit PriorityQueue() = default;
13+
14+
inline std::pair<P_, T_>& Front() { return buffer_.front(); }
15+
16+
inline std::pair<P_, T_> const& Front() const { return buffer_.front(); }
17+
18+
inline void PopFront() {
19+
// TODO For small queues it's probably not worth it to use a deque.
20+
buffer_.erase(buffer_.begin());
21+
}
22+
23+
inline void PushBack(P_ priority, T_ item) {
24+
if (buffer_.size() < buffer_.capacity()) {
25+
if (buffer_.empty()) {
26+
buffer_.push_back({priority, item});
27+
} else if (buffer_.back().first < priority) {
28+
buffer_.push_back({priority, item});
29+
} else {
30+
buffer_.push_back(buffer_.back());
31+
InsertSorted(
32+
buffer_.begin(), std::prev(buffer_.end()), {priority, item});
33+
}
34+
} else if (buffer_.back().first > priority) {
35+
InsertSorted(buffer_.begin(), buffer_.end(), {priority, item});
36+
}
37+
}
38+
39+
void reserve(std::size_t capacity) { buffer_.reserve(capacity); }
40+
41+
std::size_t capacity() const { return buffer_.capacity(); }
42+
43+
std::size_t size() const { return buffer_.size(); }
44+
45+
bool empty() const { return buffer_.empty(); }
46+
47+
private:
48+
std::vector<std::pair<P_, T_>> buffer_;
49+
};
50+
51+
} // namespace pico_tree::internal

0 commit comments

Comments
 (0)