Skip to content

Commit a74fc3b

Browse files
committed
BUGFIX: sorted was ignored in some resultsets
1 parent 3900193 commit a74fc3b

File tree

4 files changed

+110
-54
lines changed

4 files changed

+110
-54
lines changed

CHANGELOG.md

+5
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
1+
# nanoflann 1.6.0: Released Jul 11, 2024
2+
- BUG FIX: nanoflann::SearchParameters::sorted was ignored for RadiusResultSet.
3+
- ResultSet classes now must implement a sort() method.
4+
- Added type IndexType to nanoflann:KDTreeBaseClass
5+
16
# nanoflann 1.5.5: Released Mar 12, 2024
27
- Potentially more efficient scheduling of multi-thread index building ([PR #236](https://github.com/jlblancoc/nanoflann/pull/236))
38
- Bump minimum required cmake version to 3.5 ([PR #230](https://github.com/jlblancoc/nanoflann/pull/230/))

examples/pointcloud_custom_resultset.cpp

+7
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,13 @@ class MyCustomResultSet
8282
}
8383

8484
DistanceType worstDist() const { return radius; }
85+
86+
void sort()
87+
{
88+
std::sort(
89+
m_indices_dists.begin(), m_indices_dists.end(),
90+
nanoflann::IndexDist_Sorter());
91+
}
8592
};
8693

8794
void kdtree_demo(const size_t N)

include/nanoflann.hpp

+60-47
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,39 @@ inline typename std::enable_if<!has_assign<Container>::value, void>::type
157157
for (size_t i = 0; i < nElements; i++) c[i] = value;
158158
}
159159

160+
161+
/** operator "<" for std::sort() */
162+
struct IndexDist_Sorter
163+
{
164+
/** PairType will be typically: ResultItem<IndexType,DistanceType> */
165+
template <typename PairType>
166+
bool operator()(const PairType& p1, const PairType& p2) const
167+
{
168+
return p1.second < p2.second;
169+
}
170+
};
171+
172+
/**
173+
* Each result element in RadiusResultSet. Note that distances and indices
174+
* are named `first` and `second` to keep backward-compatibility with the
175+
* `std::pair<>` type used in the past. In contrast, this structure is ensured
176+
* to be `std::is_standard_layout` so it can be used in wrappers to other
177+
* languages.
178+
* See: https://github.com/jlblancoc/nanoflann/issues/166
179+
*/
180+
template <typename IndexType = size_t, typename DistanceType = double>
181+
struct ResultItem
182+
{
183+
ResultItem() = default;
184+
ResultItem(const IndexType index, const DistanceType distance)
185+
: first(index), second(distance)
186+
{
187+
}
188+
189+
IndexType first; //!< Index of the sample in the dataset
190+
DistanceType second; //!< Distance from sample to query point
191+
};
192+
160193
/** @addtogroup result_sets_grp Result set classes
161194
* @{ */
162195

@@ -237,6 +270,11 @@ class KNNResultSet
237270
}
238271

239272
DistanceType worstDist() const { return dists[capacity - 1]; }
273+
274+
void sort()
275+
{
276+
// already sorted
277+
}
240278
};
241279

242280
/** Result set for RKNN searches (N-closest neighbors with a maximum radius) */
@@ -321,38 +359,11 @@ class RKNNResultSet
321359
}
322360

323361
DistanceType worstDist() const { return dists[capacity - 1]; }
324-
};
325362

326-
/** operator "<" for std::sort() */
327-
struct IndexDist_Sorter
328-
{
329-
/** PairType will be typically: ResultItem<IndexType,DistanceType> */
330-
template <typename PairType>
331-
bool operator()(const PairType& p1, const PairType& p2) const
332-
{
333-
return p1.second < p2.second;
334-
}
335-
};
336-
337-
/**
338-
* Each result element in RadiusResultSet. Note that distances and indices
339-
* are named `first` and `second` to keep backward-compatibility with the
340-
* `std::pair<>` type used in the past. In contrast, this structure is ensured
341-
* to be `std::is_standard_layout` so it can be used in wrappers to other
342-
* languages.
343-
* See: https://github.com/jlblancoc/nanoflann/issues/166
344-
*/
345-
template <typename IndexType = size_t, typename DistanceType = double>
346-
struct ResultItem
347-
{
348-
ResultItem() = default;
349-
ResultItem(const IndexType index, const DistanceType distance)
350-
: first(index), second(distance)
363+
void sort()
351364
{
365+
// already sorted
352366
}
353-
354-
IndexType first; //!< Index of the sample in the dataset
355-
DistanceType second; //!< Distance from sample to query point
356367
};
357368

358369
/**
@@ -413,6 +424,12 @@ class RadiusResultSet
413424
m_indices_dists.begin(), m_indices_dists.end(), IndexDist_Sorter());
414425
return *it;
415426
}
427+
428+
void sort()
429+
{
430+
std::sort(
431+
m_indices_dists.begin(), m_indices_dists.end(), IndexDist_Sorter());
432+
}
416433
};
417434

418435
/** @} */
@@ -980,7 +997,7 @@ struct array_or_vector<-1, T>
980997
*/
981998
template <
982999
class Derived, typename Distance, class DatasetAdaptor, int32_t DIM = -1,
983-
typename IndexType = uint32_t>
1000+
typename index_t = uint32_t>
9841001
class KDTreeBaseClass
9851002
{
9861003
public:
@@ -995,6 +1012,7 @@ class KDTreeBaseClass
9951012

9961013
using ElementType = typename Distance::ElementType;
9971014
using DistanceType = typename Distance::DistanceType;
1015+
using IndexType = index_t;
9981016

9991017
/**
10001018
* Array of indices to vectors in the dataset_.
@@ -1239,10 +1257,7 @@ class KDTreeBaseClass
12391257
std::ref(right_bbox), std::ref(thread_count),
12401258
std::ref(mutex));
12411259
}
1242-
else
1243-
{
1244-
--thread_count;
1245-
}
1260+
else { --thread_count; }
12461261

12471262
BoundingBox left_bbox(bbox);
12481263
left_bbox[cutfeat].high = cutval;
@@ -1495,17 +1510,17 @@ class KDTreeBaseClass
14951510
*/
14961511
template <
14971512
typename Distance, class DatasetAdaptor, int32_t DIM = -1,
1498-
typename IndexType = uint32_t>
1513+
typename index_t = uint32_t>
14991514
class KDTreeSingleIndexAdaptor
15001515
: public KDTreeBaseClass<
1501-
KDTreeSingleIndexAdaptor<Distance, DatasetAdaptor, DIM, IndexType>,
1502-
Distance, DatasetAdaptor, DIM, IndexType>
1516+
KDTreeSingleIndexAdaptor<Distance, DatasetAdaptor, DIM, index_t>,
1517+
Distance, DatasetAdaptor, DIM, index_t>
15031518
{
15041519
public:
15051520
/** Deleted copy constructor*/
15061521
explicit KDTreeSingleIndexAdaptor(
15071522
const KDTreeSingleIndexAdaptor<
1508-
Distance, DatasetAdaptor, DIM, IndexType>&) = delete;
1523+
Distance, DatasetAdaptor, DIM, index_t>&) = delete;
15091524

15101525
/** The data source used by this index */
15111526
const DatasetAdaptor& dataset_;
@@ -1516,15 +1531,16 @@ class KDTreeSingleIndexAdaptor
15161531

15171532
using Base = typename nanoflann::KDTreeBaseClass<
15181533
nanoflann::KDTreeSingleIndexAdaptor<
1519-
Distance, DatasetAdaptor, DIM, IndexType>,
1520-
Distance, DatasetAdaptor, DIM, IndexType>;
1534+
Distance, DatasetAdaptor, DIM, index_t>,
1535+
Distance, DatasetAdaptor, DIM, index_t>;
15211536

15221537
using Offset = typename Base::Offset;
15231538
using Size = typename Base::Size;
15241539
using Dimension = typename Base::Dimension;
15251540

15261541
using ElementType = typename Base::ElementType;
15271542
using DistanceType = typename Base::DistanceType;
1543+
using IndexType = typename Base::IndexType;
15281544

15291545
using Node = typename Base::Node;
15301546
using NodePtr = Node*;
@@ -1677,6 +1693,9 @@ class KDTreeSingleIndexAdaptor
16771693
assign(dists, (DIM > 0 ? DIM : Base::dim_), zero);
16781694
DistanceType dist = this->computeInitialDistances(*this, vec, dists);
16791695
searchLevel(result, vec, Base::root_node_, dist, dists, epsError);
1696+
1697+
if (searchParams.sorted) result.sort();
1698+
16801699
return result.full();
16811700
}
16821701

@@ -1733,9 +1752,6 @@ class KDTreeSingleIndexAdaptor
17331752
radius, IndicesDists);
17341753
const Size nFound =
17351754
radiusSearchCustomCallback(query_point, resultSet, searchParams);
1736-
if (searchParams.sorted)
1737-
std::sort(
1738-
IndicesDists.begin(), IndicesDists.end(), IndexDist_Sorter());
17391755
return nFound;
17401756
}
17411757

@@ -1848,7 +1864,7 @@ class KDTreeSingleIndexAdaptor
18481864
{
18491865
const IndexType accessor = Base::vAcc_[i]; // reorder... : i;
18501866
DistanceType dist = distance_.evalMetric(
1851-
vec, accessor, (DIM > 0 ? DIM : Base::dim_));
1867+
vec, accessor, (DIM > 0 ? DIM : Base::dim_));
18521868
if (dist < worst_dist)
18531869
{
18541870
if (!result_set.addPoint(dist, Base::vAcc_[i]))
@@ -2202,9 +2218,6 @@ class KDTreeSingleIndexDynamicAdaptor_
22022218
radius, IndicesDists);
22032219
const size_t nFound =
22042220
radiusSearchCustomCallback(query_point, resultSet, searchParams);
2205-
if (searchParams.sorted)
2206-
std::sort(
2207-
IndicesDists.begin(), IndicesDists.end(), IndexDist_Sorter());
22082221
return nFound;
22092222
}
22102223

tests/test_main.cpp

+38-7
Original file line numberDiff line numberDiff line change
@@ -59,16 +59,14 @@ void L2_vs_L2_simple_test(const size_t N, const size_t num_results)
5959
num_t query_pt[3] = {0.5, 0.5, 0.5};
6060

6161
// construct a kd-tree index:
62-
typedef KDTreeSingleIndexAdaptor<
62+
using my_kd_tree_simple_t = KDTreeSingleIndexAdaptor<
6363
L2_Simple_Adaptor<num_t, PointCloud<num_t>>, PointCloud<num_t>,
6464
3 /* dim */
65-
>
66-
my_kd_tree_simple_t;
65+
>;
6766

68-
typedef KDTreeSingleIndexAdaptor<
67+
using my_kd_tree_t = KDTreeSingleIndexAdaptor<
6968
L2_Adaptor<num_t, PointCloud<num_t>>, PointCloud<num_t>, 3 /* dim */
70-
>
71-
my_kd_tree_t;
69+
>;
7270

7371
my_kd_tree_simple_t index1(
7472
3 /*dim*/, cloud, KDTreeSingleIndexAdaptorParams(10 /* max leaf */));
@@ -95,6 +93,38 @@ void L2_vs_L2_simple_test(const size_t N, const size_t num_results)
9593
EXPECT_EQ(ret_index1[i], ret_index[i]);
9694
EXPECT_DOUBLE_EQ(out_dist_sqr1[i], out_dist_sqr[i]);
9795
}
96+
// Ensure results are sorted:
97+
num_t lastDist = -1;
98+
for (size_t i = 0; i < out_dist_sqr.size(); i++)
99+
{
100+
const num_t newDist = out_dist_sqr[i];
101+
EXPECT_GE(newDist, lastDist);
102+
lastDist = newDist;
103+
}
104+
105+
// Test "RadiusResultSet" too:
106+
const num_t maxRadiusSqrSearch = 10.0 * 10.0;
107+
108+
std::vector<nanoflann::ResultItem<
109+
typename my_kd_tree_simple_t::IndexType,
110+
typename my_kd_tree_simple_t::DistanceType>>
111+
radiusIdxs;
112+
113+
nanoflann::RadiusResultSet<num_t, typename my_kd_tree_simple_t::IndexType>
114+
radiusResults(maxRadiusSqrSearch, radiusIdxs);
115+
radiusResults.init();
116+
nanoflann::SearchParameters searchParams;
117+
searchParams.sorted = true;
118+
index1.findNeighbors(radiusResults, &query_pt[0], searchParams);
119+
120+
// Ensure results are sorted:
121+
lastDist = -1;
122+
for (const auto& r : radiusIdxs)
123+
{
124+
const num_t newDist = r.second;
125+
EXPECT_GE(newDist, lastDist);
126+
lastDist = newDist;
127+
}
98128
}
99129

100130
using namespace nanoflann;
@@ -725,7 +755,8 @@ TEST(kdtree, add_and_remove_points)
725755
my_kd_tree_simple_t index(
726756
3 /*dim*/, cloud, KDTreeSingleIndexAdaptorParams(10 /* max leaf */));
727757

728-
const auto query = [&index]() -> size_t {
758+
const auto query = [&index]() -> size_t
759+
{
729760
const double query_pt[3] = {0.5, 0.5, 0.5};
730761
const size_t num_results = 1;
731762
std::vector<size_t> ret_index(num_results);

0 commit comments

Comments
 (0)