Skip to content

Commit 6f73ef5

Browse files
committed
Added metric_lninf supporting negative infinity.
Renamed metric_linf to metric_lpinf and LInf to LPInf (positive infinity). Renamed dynamic_size to dynamic_extent to avoid name collisions. Simplified both Euclidean and topological metrics. Added an example showing how to create custom metrics. Updated the benchmark table in the README.md file. Added the metric_box_map class to remove code duplication. Bug fix: toplogical_box.contains(topological_box). Removed various warnings.
1 parent 79372b8 commit 6f73ef5

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

49 files changed

+770
-461
lines changed

README.md

Lines changed: 20 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,14 @@ PicoTree is a C++ header only library with [Python bindings](https://github.com/
66

77
| | Build C++ | Build Python | Knn C++ | Knn Python |
88
| ----------------------------------- | --------- | ------------- | ---------- | ----------- |
9-
| [nanoflann][nano] v1.5.0 | 2.9s | ... | 3.2s | ... |
10-
| [SciPy KDTree][spkd] 1.11.0 | ... | 4.5s | ... | 563.6s |
11-
| [Scikit-learn KDTree][skkd] 1.2.2 | ... | 6.2s | ... | 42.2s |
12-
| [pykdtree][pykd] 1.3.7 | ... | 1.0s | ... | 6.6s |
13-
| [OpenCV FLANN][cvfn] 4.6.0 | 1.9s | ... | 4.7s | ... |
14-
| PicoTree KdTree v0.8.3 | 0.9s | 1.0s | 2.8s | 3.1s |
9+
| [nanoflann][nano] v1.7.1 | 1.42s | ... | 1.40s | ... |
10+
| [SciPy KDTree][spkd] 1.15.2 | ... | 1.37s | ... | 212.04s |
11+
| [Scikit-learn KDTree][skkd] 1.6.1 | ... | 3.60s | ... | 11.90s |
12+
| [pykdtree][pykd] 1.3.7 | ... | 0.43s | ... | 2.95s |
13+
| [OpenCV FLANN][cvfn] 4.11.0 | 0.99s | ... | 2.30s | ... |
14+
| PicoTree KdTree v1.0.0 | 0.54s | 0.54s | 1.13s | 1.64s |
1515

16-
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].
16+
These numbers were generated on an Intel Core i9-14900HX using two [LiDAR](./docs/benchmark.md) based point clouds of sizes 7733372 and 7200863. 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

1818
[nano]: https://github.com/jlblancoc/nanoflann
1919
[spkd]: https://docs.scipy.org/doc/scipy/reference/generated/scipy.spatial.KDTree.html
@@ -26,12 +26,18 @@ Available under the [MIT](https://en.wikipedia.org/wiki/MIT_License) license.
2626
# Capabilities
2727

2828
KdTree:
29-
* Nearest neighbor, approximate nearest neighbor, radius, box, and customizable nearest neighbor searches.
30-
* Different [metric spaces](https://en.wikipedia.org/wiki/Metric_space):
31-
* Support for topological spaces with identifications. E.g., points on the circle `[-pi, pi]`.
32-
* Available distance functions: `metric_l1`, `metric_l2_squared`, `metric_linf`, `metric_so2`, and `metric_se2_squared`.
33-
* Metrics can be customized.
34-
* Multiple tree splitting rules: `median_max_side_t`, `midpoint_max_side_t` and `sliding_midpoint_max_side_t`.
29+
* Search functions:
30+
* Exact nearest neighbor, radius and box searches.
31+
* Approximate nearest neighbor and radius searches.
32+
* Custom nearest neighbor searches.
33+
* [Metric spaces](https://en.wikipedia.org/wiki/Metric_space):
34+
* Support for spaces with identifications. E.g., points on the circle S<sup>1</sup> `[0, 1]`.
35+
* Available distance functions: `metric_l1`, `metric_l2_squared`, `metric_lpinf`, `metric_lninf`, `metric_so2`, and `metric_se2_squared`.
36+
* Custom metrics.
37+
* Multiple build options:
38+
* Tree splitting rules: `median_max_side_t`, `midpoint_max_side_t` and `sliding_midpoint_max_side_t`.
39+
* Tree split stop conditions: `max_leaf_size_t` and `max_leaf_depth_t`.
40+
* Custom starting bounding box.
3541
* Compile time and run time known dimensions.
3642
* Static tree builds.
3743
* Thread safe queries.
@@ -59,6 +65,7 @@ PicoTree can interface with different types of points and point sets through tra
5965
* Supporting a [custom point type](./examples/kd_tree/kd_tree_custom_point_type.cpp).
6066
* Supporting a [custom space type](./examples/kd_tree/kd_tree_custom_space_type.cpp).
6167
* Creating a [custom search visitor](./examples/kd_tree/kd_tree_custom_search_visitor.cpp).
68+
* Creating a [custom metric](./examples/kd_tree/kd_tree_custom_metric.cpp).
6269
* [Saving and loading](./examples/kd_tree/kd_tree_save_and_load.cpp) a KdTree to and from a file.
6370
* Support for [Eigen](./examples/eigen/eigen.cpp) and [OpenCV](./examples/opencv/opencv.cpp) data types.
6471
* [Running the KdTree and KdForest](./examples/kd_forest/kd_forest.cpp) on the [MNIST](http://yann.lecun.com/exdb/mnist/) and [SIFT](http://corpus-texmex.irisa.fr/) datasets.

examples/benchmark/bm_opencv_flann.cpp

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@ BENCHMARK_REGISTER_F(BmOpenCvFlann, BuildRt)
9999

100100
BENCHMARK_DEFINE_F(BmOpenCvFlann, KnnCt)(benchmark::State& state) {
101101
int max_leaf_size = static_cast<int>(state.range(0));
102-
int knn_count = static_cast<int>(state.range(1));
102+
std::size_t knn_count = static_cast<std::size_t>(state.range(1));
103103

104104
// Reorder will change the order of the input to fit the generated indices,
105105
// but it will replace (and delete) the original input. The reorder option
@@ -126,7 +126,12 @@ BENCHMARK_DEFINE_F(BmOpenCvFlann, KnnCt)(benchmark::State& state) {
126126

127127
for (auto& p : points_test_) {
128128
fl::Matrix<scalar_type> query(p.data(), 1, 3);
129-
tree.knnSearch(query, mat_indices, mat_distances, knn_count, psearch);
129+
tree.knnSearch(
130+
query,
131+
mat_indices,
132+
mat_distances,
133+
static_cast<int>(knn_count),
134+
psearch);
130135
}
131136
}
132137
}

examples/benchmark/bm_pico_kd_tree.cpp

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -27,15 +27,17 @@ using pico_kd_tree_rt_sld_mid = pico_tree::kd_tree<pico_rt_space<Point_>>;
2727
// ****************************************************************************
2828

2929
BENCHMARK_DEFINE_F(BmPicoKdTree, BuildCtSldMid)(benchmark::State& state) {
30-
pico_tree::max_leaf_size_t max_leaf_size = state.range(0);
30+
pico_tree::max_leaf_size_t max_leaf_size =
31+
static_cast<pico_tree::size_t>(state.range(0));
3132

3233
for (auto _ : state) {
3334
pico_kd_tree_ct_sld_mid<point_type> tree(points_tree_, max_leaf_size);
3435
}
3536
}
3637

3738
BENCHMARK_DEFINE_F(BmPicoKdTree, BuildRtSldMid)(benchmark::State& state) {
38-
pico_tree::max_leaf_size_t max_leaf_size = state.range(0);
39+
pico_tree::max_leaf_size_t max_leaf_size =
40+
static_cast<pico_tree::size_t>(state.range(0));
3941

4042
for (auto _ : state) {
4143
pico_kd_tree_rt_sld_mid<point_type> tree(
@@ -59,8 +61,9 @@ BENCHMARK_REGISTER_F(BmPicoKdTree, BuildRtSldMid)
5961
// ****************************************************************************
6062

6163
BENCHMARK_DEFINE_F(BmPicoKdTree, KnnCtSldMid)(benchmark::State& state) {
62-
pico_tree::max_leaf_size_t max_leaf_size = state.range(0);
63-
std::size_t knn_count = state.range(1);
64+
pico_tree::max_leaf_size_t max_leaf_size =
65+
static_cast<pico_tree::size_t>(state.range(0));
66+
pico_tree::size_t knn_count = static_cast<pico_tree::size_t>(state.range(1));
6467

6568
pico_kd_tree_ct_sld_mid<point_type> tree(points_tree_, max_leaf_size);
6669

@@ -106,7 +109,8 @@ BENCHMARK_REGISTER_F(BmPicoKdTree, KnnCtSldMid)
106109
// ****************************************************************************
107110

108111
BENCHMARK_DEFINE_F(BmPicoKdTree, RadiusCtSldMid)(benchmark::State& state) {
109-
pico_tree::max_leaf_size_t max_leaf_size = state.range(0);
112+
pico_tree::max_leaf_size_t max_leaf_size =
113+
static_cast<pico_tree::size_t>(state.range(0));
110114
scalar_type radius =
111115
static_cast<scalar_type>(state.range(1)) / scalar_type(10.0);
112116
scalar_type squared = radius * radius;
@@ -145,7 +149,8 @@ BENCHMARK_REGISTER_F(BmPicoKdTree, RadiusCtSldMid)
145149
// ****************************************************************************
146150

147151
BENCHMARK_DEFINE_F(BmPicoKdTree, BoxCtSldMid)(benchmark::State& state) {
148-
pico_tree::max_leaf_size_t max_leaf_size = state.range(0);
152+
pico_tree::max_leaf_size_t max_leaf_size =
153+
static_cast<pico_tree::size_t>(state.range(0));
149154
scalar_type radius =
150155
static_cast<scalar_type>(state.range(1)) / scalar_type(10.0);
151156

@@ -175,7 +180,8 @@ BENCHMARK_REGISTER_F(BmPicoKdTree, BoxCtSldMid)
175180
->Args({14, 15});
176181

177182
BENCHMARK_DEFINE_F(BmPicoKdTree, BoxRtSldMid)(benchmark::State& state) {
178-
pico_tree::max_leaf_size_t max_leaf_size = state.range(0);
183+
pico_tree::max_leaf_size_t max_leaf_size =
184+
static_cast<pico_tree::size_t>(state.range(0));
179185
scalar_type radius =
180186
static_cast<scalar_type>(state.range(1)) / scalar_type(10.0);
181187

examples/benchmark/nano_adaptor.hpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,8 @@ class nano_adaptor {
2121

2222
//! \brief Returns the dim'th component of the idx'th point in the class:
2323
inline scalar_type kdtree_get_pt(Index_ const idx, Index_ const dim) const {
24-
return points_[idx].data()[dim];
24+
return points_[static_cast<typename std::vector<Point_>::size_type>(idx)]
25+
.data()[dim];
2526
}
2627

2728
// Optional bounding-box computation: return false to default to a standard

examples/kd_tree/CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ add_demo_executable(kd_tree_search)
1212

1313
add_demo_executable(kd_tree_dynamic_arrays)
1414

15+
add_demo_executable(kd_tree_custom_metric)
16+
1517
add_demo_executable(kd_tree_custom_point_type)
1618

1719
add_demo_executable(kd_tree_custom_space_type)

examples/kd_tree/kd_tree_creation.cpp

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -109,11 +109,11 @@ void space_type_deduction() {
109109
// space type when we want to change any of the other template arguments, such
110110
// as the metric type. In this case we can use the make_kd_tree method to make
111111
// life a bit easier.
112-
auto tree2 = pico_tree::make_kd_tree<pico_tree::metric_linf>(
112+
auto tree2 = pico_tree::make_kd_tree<pico_tree::metric_lpinf>(
113113
std::ref(points), max_leaf_size);
114114

115-
using kd_tree2_type =
116-
pico_tree::kd_tree<std::reference_wrapper<space>, pico_tree::metric_linf>;
115+
using kd_tree2_type = pico_tree::
116+
kd_tree<std::reference_wrapper<space>, pico_tree::metric_lpinf>;
117117

118118
static_assert(std::is_same_v<decltype(tree2), kd_tree2_type>);
119119

Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
#include <pico_toolshed/point.hpp>
2+
#include <pico_tree/kd_tree.hpp>
3+
#include <pico_tree/vector_traits.hpp>
4+
5+
// This example shows how to create a custom metric for the kd_tree. The kd_tree
6+
// does not support metrics that "normalize" the distance between two points for
7+
// performance reasons. This means that, for example, the L2 metric is not
8+
// supported, but there is support for the L1 or L2^2 metric.
9+
//
10+
// There are two different categories of metrics: Euclidean and topological. A
11+
// Euclidean metric supports any R^n space where all axes are orthogonal with
12+
// respect to each other. A topological space is the same, but it allows
13+
// wrapping of coordinate values along any of the axes. An implementation for
14+
// both is provided below.
15+
16+
// The LP^P metric is a generalization of the L2^2 metric.
17+
template <std::size_t P_>
18+
struct metric_lp_p {
19+
static_assert(P_ > 0, "P_CANNOT_BE_ZERO");
20+
21+
// Indicate that this metric is a Euclidean one.
22+
using space_category = pico_tree::euclidean_space_tag;
23+
24+
// Calculates the distance between two points.
25+
template <typename InputIterator_>
26+
auto operator()(
27+
InputIterator_ begin1, InputIterator_ end1, InputIterator_ begin2) const {
28+
using scalar_type =
29+
typename std::iterator_traits<InputIterator_>::value_type;
30+
31+
scalar_type d{};
32+
for (; begin1 != end1; ++begin1, ++begin2) {
33+
d += operator()(*begin1 - *begin2);
34+
}
35+
return d;
36+
}
37+
38+
// Returns the absolute value of x to the power of p.
39+
template <typename Scalar_>
40+
Scalar_ operator()(Scalar_ x) const {
41+
return std::pow(std::abs(x), static_cast<Scalar_>(P_));
42+
}
43+
};
44+
45+
// This metric measures distances on the two dimensional ring torus T2. The
46+
// torus is the Cartesian product of two circles S1 x S1. The values of each of
47+
// the point coordinates should be within the range of [0...1].
48+
struct metric_t2_squared {
49+
// Indicate that this metric is defined on a topological space. The
50+
// topological_space_tag is required because the torus wraps around in both
51+
// dimensions.
52+
using space_category = pico_tree::topological_space_tag;
53+
54+
// Calculates the distance between two points.
55+
template <typename InputIterator_>
56+
auto operator()(
57+
InputIterator_ begin1, InputIterator_ end1, InputIterator_ begin2) const {
58+
using scalar_type =
59+
typename std::iterator_traits<InputIterator_>::value_type;
60+
61+
scalar_type d{};
62+
for (; begin1 != end1; ++begin1, ++begin2) {
63+
d += pico_tree::squared_s1_distance(*begin1, *begin2);
64+
}
65+
return d;
66+
}
67+
68+
// Distances are squared values.
69+
template <typename Scalar_>
70+
Scalar_ operator()(Scalar_ x) const {
71+
return x * x;
72+
}
73+
74+
template <typename UnaryPredicate_>
75+
void apply_one_space([[maybe_unused]] int dim, UnaryPredicate_ p) const {
76+
p(pico_tree::one_space_s1{});
77+
}
78+
};
79+
80+
void search_lp3_3() {
81+
using point = pico_tree::point_2f;
82+
using scalar = typename point::scalar_type;
83+
84+
pico_tree::max_leaf_size_t max_leaf_size = 12;
85+
std::size_t point_count = 1024 * 1024;
86+
scalar area_size = 10;
87+
88+
using kd_tree = pico_tree::kd_tree<std::vector<point>, metric_lp_p<3>>;
89+
using neighbor = typename kd_tree::neighbor_type;
90+
91+
kd_tree tree(
92+
pico_tree::generate_random_n<point>(point_count, area_size),
93+
max_leaf_size);
94+
95+
neighbor nn;
96+
tree.search_nn(point{area_size / scalar(2), area_size / scalar(2)}, nn);
97+
98+
std::cout << "Index closest point: " << nn.index << std::endl;
99+
}
100+
101+
void search_t2() {
102+
using point = pico_tree::point_2f;
103+
using scalar = typename point::scalar_type;
104+
105+
pico_tree::max_leaf_size_t max_leaf_size = 12;
106+
std::size_t point_count = 1024 * 1024;
107+
scalar area_size = 1;
108+
109+
using kd_tree = pico_tree::kd_tree<std::vector<point>, metric_t2_squared>;
110+
using neighbor = typename kd_tree::neighbor_type;
111+
112+
kd_tree tree(
113+
pico_tree::generate_random_n<point>(point_count, area_size),
114+
max_leaf_size);
115+
116+
std::array<neighbor, 8> knn;
117+
tree.search_knn(point{area_size, area_size}, knn.begin(), knn.end());
118+
119+
// These prints show that wrapping near values 0 ~ 1 is supported.
120+
std::cout << "Closest points (index, distance, point): " << std::endl;
121+
for (auto const& nn : knn) {
122+
std::cout << " " << nn.index << ", " << nn.distance << ", ["
123+
<< tree.space()[static_cast<std::size_t>(nn.index)] << "]"
124+
<< std::endl;
125+
}
126+
}
127+
128+
int main() {
129+
search_lp3_3();
130+
search_t2();
131+
return 0;
132+
}

examples/kd_tree/kd_tree_custom_point_type.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ template <>
1717
struct point_traits<PointXYZ> {
1818
using point_type = PointXYZ;
1919
using scalar_type = float;
20-
// Spatial dimension. Set to pico_tree::dynamic_size when the dimension is
20+
// Spatial dimension. Set to pico_tree::dynamic_extent when the dimension is
2121
// only known at run-time.
2222
static constexpr pico_tree::size_t dim = 3;
2323

examples/kd_tree/kd_tree_custom_search_visitor.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
#include <limits>
12
#include <pico_toolshed/point.hpp>
23
#include <pico_toolshed/scoped_timer.hpp>
34
#include <pico_tree/kd_tree.hpp>

examples/kd_tree/kd_tree_custom_space_type.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ struct space_traits<std::deque<std::array<Scalar_, Dim_>>> {
1414
using space_type = std::deque<std::array<Scalar_, Dim_>>;
1515
using point_type = std::array<Scalar_, Dim_>;
1616
using scalar_type = Scalar_;
17-
// Spatial dimension. Set to pico_tree::dynamic_size when the dimension is
17+
// Spatial dimension. Set to pico_tree::dynamic_extent when the dimension is
1818
// only known at run-time.
1919
static constexpr pico_tree::size_t dim = Dim_;
2020

0 commit comments

Comments
 (0)