Skip to content

Commit 62e457b

Browse files
authored
v0.8.0 Patch (#29)
Added array traits to support default point types. Documentation and examples updated. Map traits return type fix. Added SplitterMidpoint unit tests.
1 parent 2d02756 commit 62e457b

15 files changed

+525
-254
lines changed

README.md

Lines changed: 27 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -27,28 +27,38 @@ Available under the [MIT](https://en.wikipedia.org/wiki/MIT_License) license.
2727

2828
# Capabilities
2929

30-
* KdTree:
31-
* Nearest neighbor, approximate nearest neighbor, radius, box, and customizable nearest neighbor searches.
32-
* Multiple tree splitting rules: `kLongestMedian`, `kMidpoint` and `kSlidingMidpoint`.
33-
* [Metrics](https://en.wikipedia.org/wiki/Metric_(mathematics)):
34-
* Support for topological spaces with identifications. E.g., points on the circle `[-pi, pi]`.
35-
* Available metrics: `L1`, `L2Squared`, `SO2`, and `SE2Squared`. Metrics can be customized.
36-
* Compile time and run time known dimensions.
37-
* Static tree builds.
38-
* Thread safe queries.
39-
* PicoTree can interface with different types of points or point sets through traits classes. These can be custom implementations or one of the `pico_tree::PointTraits<>` and `pico_tree::SpaceTraits<>` classes provided by this library. There is default support for the following data types:
30+
KdTree:
31+
* Nearest neighbor, approximate nearest neighbor, radius, box, and customizable nearest neighbor searches.
32+
* [Metrics](https://en.wikipedia.org/wiki/Metric_(mathematics)):
33+
* Support for topological spaces with identifications. E.g., points on the circle `[-pi, pi]`.
34+
* Available metrics: `L1`, `L2Squared`, `SO2`, and `SE2Squared`. Metrics can be customized.
35+
* Multiple tree splitting rules: `kLongestMedian`, `kMidpoint` and `kSlidingMidpoint`.
36+
* Compile time and run time known dimensions.
37+
* Static tree builds.
38+
* Thread safe queries.
39+
40+
PicoTree can interface with different types of points and point sets through traits classes. These can be custom implementations or one of the `pico_tree::SpaceTraits<>` and `pico_tree::PointTraits<>` classes provided by this library.
41+
* Space type support:
4042
* `std::vector<PointType>`.
41-
* A specialization of `pico_tree::PointTraits<>` is required for each `PointType`. There are traits available for Eigen and OpenCV point types.
42-
* `pico_tree::SpaceMap<PointType>` and `pico_tree::PointMap<>`.
43-
* These classes allow interfacing with raw pointers. It is assumed that points and their coordinates are laid out contiguously in memory.
44-
* `Eigen::Matrix` and `Eigen::Map<Eigen::Matrix>`.
43+
* `pico_tree::SpaceMap<PointType>`.
44+
* `Eigen::Matrix<>` and `Eigen::Map<Eigen::Matrix<>>`.
4545
* `cv::Mat`.
46+
* Point type support:
47+
* Fixed size arrays and `std::array<>`.
48+
* `pico_tree::PointMap<>`.
49+
* `Eigen::Vector<>` and `Eigen::Map<Eigen::Vector<>>`.
50+
* `cv::Vec<>`.
51+
* `pico_tree::SpaceMap<PointType>` and `pico_tree::PointMap<>` allow interfacing with dynamic size arrays. It is assumed that points and their coordinates are laid out contiguously in memory.
4652

4753
# Examples
4854

49-
* [Minimal working example](./examples/kd_tree/kd_tree_minimal.cpp) using an std::vector of points.
50-
* Creating [traits](./examples/kd_tree/kd_tree_traits.cpp) classes for a custom point and point set.
51-
* Using the KdTree's [search](./examples/kd_tree/kd_tree_search.cpp) options and creating a custom search visitor.
55+
* [Minimal working example](./examples/kd_tree/kd_tree_minimal.cpp) using an `std::vector<>` of points.
56+
* [Creating a KdTree](./examples/kd_tree/kd_tree_creation.cpp) and taking the input by value or reference.
57+
* Using the KdTree's [search](./examples/kd_tree/kd_tree_search.cpp) capabilities.
58+
* Working with [dynamic size arrays](./examples/kd_tree/kd_tree_dynamic_arrays.cpp).
59+
* Supporting a [custom point type](./examples/kd_tree/kd_tree_custom_point_type.cpp).
60+
* Supporting a [custom space type](./examples/kd_tree/kd_tree_custom_space_type.cpp).
61+
* Creating a [custom search visitor](./examples/kd_tree/kd_tree_custom_search_visitor.cpp).
5262
* Support for [Eigen](./examples/eigen/eigen.cpp) and [OpenCV](./examples/opencv/opencv.cpp) data types.
5363
* How to use the [KdTree with Python](./examples/python/kd_tree.py).
5464

examples/eigen/eigen.cpp

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

6-
// NOTES:
7-
86
// Because we use C++17 there is no need to take care of memory alignment:
97
// https://eigen.tuxfamily.org/dox-devel/group__TopicUnalignedArrayAssert.html
108
// https://eigen.tuxfamily.org/dox-devel/group__TopicStlContainers.html
119

12-
// The Eigen example is not a performance benchmark. So don't take the "elapsed
13-
// time" numbers too seriously.
14-
15-
using Index = int;
16-
1710
template <typename Point>
18-
using PointsMapCm = Eigen::Map<Eigen::Matrix<
11+
using PointsMapColMajor = Eigen::Map<Eigen::Matrix<
1912
typename Point::Scalar,
2013
Point::RowsAtCompileTime,
2114
Eigen::Dynamic>>;
2215

2316
template <typename Point>
24-
using PointsMapRm = Eigen::Map<Eigen::Matrix<
17+
using PointsMapRowMajor = Eigen::Map<Eigen::Matrix<
2518
typename Point::Scalar,
2619
Eigen::Dynamic,
2720
Point::ColsAtCompileTime,
2821
Eigen::RowMajor>>;
2922

3023
std::size_t const kRunCount = 1024 * 1024;
31-
int const kNumPoints = 1024 * 1024 * 2;
24+
std::size_t const kNumPoints = 1024 * 1024 * 2;
3225
float const kArea = 1000.0;
33-
Index const kMaxLeafCount = 16;
26+
std::size_t const kMaxLeafCount = 16;
3427

3528
template <typename PointX>
36-
std::vector<PointX> GenerateRandomEigenN(int n, typename PointX::Scalar size) {
29+
std::vector<PointX> GenerateRandomEigenN(
30+
std::size_t n, typename PointX::Scalar size) {
3731
std::vector<PointX> random(n);
3832
for (auto& p : random) {
3933
p = PointX::Random() * size / typename PointX::Scalar(2.0);
@@ -46,10 +40,12 @@ std::vector<PointX> GenerateRandomEigenN(int n, typename PointX::Scalar size) {
4640
// neighbors.
4741
void BasicVector() {
4842
using PointX = Eigen::Vector2f;
49-
using Scalar = typename PointX::Scalar;
43+
using KdTree = pico_tree::KdTree<std::vector<PointX>>;
44+
using Scalar = typename KdTree::ScalarType;
45+
using Index = typename KdTree::IndexType;
5046

51-
// Including <pico_tree/eigen.hpp> provides support for Eigen types with
52-
// std::vector.
47+
// Including <pico_tree/eigen3_traits.hpp> provides support for Eigen types
48+
// with std::vector.
5349
pico_tree::KdTree<std::vector<PointX>> tree(
5450
GenerateRandomEigenN<PointX>(kNumPoints, kArea), kMaxLeafCount);
5551

@@ -64,95 +60,75 @@ void BasicVector() {
6460

6561
// Creates a KdTree from an Eigen::Matrix<> and searches for nearest neighbors.
6662
void BasicMatrix() {
63+
using KdTree = pico_tree::KdTree<Eigen::Matrix3Xf>;
64+
using Neighbor = typename KdTree::NeighborType;
6765
using Scalar = typename Eigen::Matrix3Xf::Scalar;
6866
constexpr int Dim = Eigen::Matrix3Xf::RowsAtCompileTime;
6967

70-
Eigen::Vector3f p = Eigen::Vector3f::Random() * kArea / Scalar(2.0);
71-
72-
// The KdTree takes the matrix by value. Prevent a copy by:
73-
// * Using a move.
74-
// * Creating an Eigen::Map<>.
75-
// * Wrap with an std::reference_wrapper<>.
76-
{
77-
pico_tree::KdTree<Eigen::Matrix3Xf> tree(
78-
Eigen::Matrix3Xf::Random(Dim, kNumPoints) * kArea / Scalar(2.0),
79-
kMaxLeafCount);
80-
81-
pico_tree::Neighbor<Index, Scalar> nn;
82-
ScopedTimer t("pico_tree eigen val", kRunCount);
83-
for (std::size_t i = 0; i < kRunCount; ++i) {
84-
tree.SearchNn(p, nn);
85-
}
86-
}
87-
88-
{
89-
Eigen::Matrix3Xf matrix =
90-
Eigen::Matrix3Xf::Random(Dim, kNumPoints) * kArea / Scalar(2.0);
68+
KdTree tree(
69+
Eigen::Matrix3Xf::Random(Dim, kNumPoints) * kArea / Scalar(2.0),
70+
kMaxLeafCount);
9171

92-
pico_tree::KdTree<std::reference_wrapper<Eigen::Matrix3Xf>> tree(
93-
matrix, kMaxLeafCount);
94-
95-
pico_tree::Neighbor<Index, Scalar> nn;
96-
ScopedTimer t("pico_tree eigen ref", kRunCount);
97-
for (std::size_t i = 0; i < kRunCount; ++i) {
98-
tree.SearchNn(p, nn);
99-
}
72+
Eigen::Vector3f p = Eigen::Vector3f::Random() * kArea / Scalar(2.0);
73+
Neighbor nn;
74+
ScopedTimer t("pico_tree eigen matrix", kRunCount);
75+
for (std::size_t i = 0; i < kRunCount; ++i) {
76+
tree.SearchNn(p, nn);
10077
}
10178
}
10279

10380
// Creates a KdTree from a col-major matrix. The matrix maps an
10481
// std::vector<Eigen::Vector3f>.
105-
void VectorMapColMajor() {
82+
void ColMajorSupport() {
10683
using PointX = Eigen::Vector3f;
84+
using Map = PointsMapColMajor<PointX>;
85+
using KdTree = pico_tree::KdTree<Map>;
86+
using Neighbor = typename KdTree::NeighborType;
10787
using Scalar = typename PointX::Scalar;
10888
constexpr int Dim = PointX::RowsAtCompileTime;
109-
using Map = PointsMapCm<PointX>;
11089

11190
auto points = GenerateRandomEigenN<PointX>(kNumPoints, kArea);
11291
PointX p = PointX::Random() * kArea / Scalar(2.0);
11392

11493
std::cout << "Eigen RowMajor: " << Map::IsRowMajor << std::endl;
115-
{
116-
pico_tree::KdTree<Map> tree(
117-
Map(points.data()->data(), Dim, points.size()), kMaxLeafCount);
118-
119-
std::vector<pico_tree::Neighbor<Index, Scalar>> knn;
120-
ScopedTimer t("pico_tree deflt l2", kRunCount);
121-
for (std::size_t i = 0; i < kRunCount; ++i) {
122-
tree.SearchKnn(p, 1, knn);
123-
}
94+
95+
KdTree tree(Map(points.data()->data(), Dim, points.size()), kMaxLeafCount);
96+
97+
std::vector<Neighbor> knn;
98+
ScopedTimer t("pico_tree col major", kRunCount);
99+
for (std::size_t i = 0; i < kRunCount; ++i) {
100+
tree.SearchKnn(p, 1, knn);
124101
}
125102
}
126103

127104
// Creates a KdTree from a row-major matrix. The matrix maps an
128105
// std::vector<Eigen::RowVector3f>.
129-
void VectorMapRowMajor() {
106+
void RowMajorSupport() {
130107
using PointX = Eigen::RowVector3f;
108+
using Map = PointsMapRowMajor<PointX>;
109+
using KdTree = pico_tree::KdTree<Map>;
110+
using Neighbor = typename KdTree::NeighborType;
131111
using Scalar = typename PointX::Scalar;
132112
constexpr int Dim = PointX::ColsAtCompileTime;
133-
using Map = PointsMapRm<PointX>;
134113

135114
auto points = GenerateRandomEigenN<PointX>(kNumPoints, kArea);
136115
PointX p = PointX::Random() * kArea / Scalar(2.0);
137116

138117
std::cout << "Eigen RowMajor: " << PointX::IsRowMajor << std::endl;
139118

140-
{
141-
pico_tree::KdTree<Map> tree(
142-
Map(points.data()->data(), points.size(), Dim), kMaxLeafCount);
119+
KdTree tree(Map(points.data()->data(), points.size(), Dim), kMaxLeafCount);
143120

144-
std::vector<pico_tree::Neighbor<Index, Scalar>> knn;
145-
ScopedTimer t("pico_tree deflt l2", kRunCount);
146-
for (std::size_t i = 0; i < kRunCount; ++i) {
147-
tree.SearchKnn(p, 1, knn);
148-
}
121+
std::vector<Neighbor> knn;
122+
ScopedTimer t("pico_tree row major", kRunCount);
123+
for (std::size_t i = 0; i < kRunCount; ++i) {
124+
tree.SearchKnn(p, 1, knn);
149125
}
150126
}
151127

152128
int main() {
153129
BasicVector();
154130
BasicMatrix();
155-
VectorMapColMajor();
156-
VectorMapRowMajor();
131+
ColMajorSupport();
132+
RowMajorSupport();
157133
return 0;
158134
}

examples/kd_tree/CMakeLists.txt

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,19 @@
1-
add_executable(kd_tree_minimal kd_tree_minimal.cpp)
2-
set_default_target_properties(kd_tree_minimal)
3-
target_link_libraries(kd_tree_minimal PUBLIC PicoTree::PicoTree)
1+
function(add_demo_executable TARGET_NAME)
2+
add_executable(${TARGET_NAME} ${TARGET_NAME}.cpp)
3+
set_default_target_properties(${TARGET_NAME})
4+
target_link_libraries(${TARGET_NAME} PRIVATE pico_toolshed)
5+
endfunction()
46

5-
add_executable(kd_tree_traits kd_tree_traits.cpp)
6-
set_default_target_properties(kd_tree_traits)
7-
target_link_libraries(kd_tree_traits PUBLIC PicoTree::PicoTree)
7+
add_demo_executable(kd_tree_minimal)
88

9-
add_executable(kd_tree_search kd_tree_search.cpp)
10-
set_default_target_properties(kd_tree_search)
11-
target_link_libraries(kd_tree_search PUBLIC pico_toolshed)
9+
add_demo_executable(kd_tree_creation)
10+
11+
add_demo_executable(kd_tree_search)
12+
13+
add_demo_executable(kd_tree_dynamic_arrays)
14+
15+
add_demo_executable(kd_tree_custom_point_type)
16+
17+
add_demo_executable(kd_tree_custom_space_type)
18+
19+
add_demo_executable(kd_tree_custom_search_visitor)

examples/kd_tree/kd_tree_creation.cpp

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
#include <iostream>
2+
#include <pico_tree/array_traits.hpp>
3+
#include <pico_tree/kd_tree.hpp>
4+
#include <pico_tree/vector_traits.hpp>
5+
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<>).
10+
11+
template <typename Tree>
12+
void QueryTree(Tree const& tree) {
13+
float query[3] = {4.0f, 4.0f, 4.0f};
14+
pico_tree::Neighbor<int, float> nn;
15+
tree.SearchNn(query, nn);
16+
17+
std::cout << "Index closest point: " << nn.index << std::endl;
18+
}
19+
20+
auto MakePointSet() {
21+
std::vector<std::array<float, 3>> points{
22+
{0.0f, 1.0f, 2.0f}, {3.0f, 4.0f, 5.0f}};
23+
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.
29+
void BuildKdTreeWithCopy() {
30+
int max_leaf_size = 12;
31+
auto points = MakePointSet();
32+
33+
pico_tree::KdTree<std::vector<std::array<float, 3>>> tree(
34+
points, max_leaf_size);
35+
36+
QueryTree(tree);
37+
}
38+
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.
41+
void BuildKdTreeWithMove() {
42+
int max_leaf_size = 12;
43+
auto points = MakePointSet();
44+
45+
pico_tree::KdTree<std::vector<std::array<float, 3>>> tree1(
46+
std::move(points), max_leaf_size);
47+
48+
pico_tree::KdTree<std::vector<std::array<float, 3>>> tree2(
49+
MakePointSet(), max_leaf_size);
50+
51+
QueryTree(tree1);
52+
QueryTree(tree2);
53+
}
54+
55+
// The KdTree takes the input by value. In this example, the input is taken by
56+
// reference. This prevents a copy.
57+
void BuildKdTreeWithReference() {
58+
int max_leaf_size = 12;
59+
auto points = MakePointSet();
60+
61+
// By reference.
62+
pico_tree::KdTree<std::reference_wrapper<std::vector<std::array<float, 3>>>>
63+
tree1(points, max_leaf_size);
64+
65+
// By const reference.
66+
pico_tree::KdTree<
67+
std::reference_wrapper<std::vector<std::array<float, 3>> const>>
68+
tree2(points, max_leaf_size);
69+
70+
QueryTree(tree1);
71+
QueryTree(tree2);
72+
}
73+
74+
int main() {
75+
BuildKdTreeWithReference();
76+
BuildKdTreeWithMove();
77+
BuildKdTreeWithCopy();
78+
return 0;
79+
}

0 commit comments

Comments
 (0)