Skip to content

Commit

Permalink
Merge pull request #217 from jlblancoc/feature/rknn2
Browse files Browse the repository at this point in the history
Add RKNN searches, and more code coverage and tests
  • Loading branch information
jlblancoc authored Nov 27, 2023
2 parents 5db886b + 7c7b9e1 commit 73c75ce
Show file tree
Hide file tree
Showing 5 changed files with 286 additions and 37 deletions.
6 changes: 5 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
nanoflann 1.5.1: UNRELEASED
* **API changes:**
- Add new search method `rknnSearch()` for knn searches with a maximum radius.
- Add missing `SearchParameters` argument to `KDTreeSingleIndexDynamicAdaptor_::knnSearch()` ([PR#213](https://github.com/jlblancoc/nanoflann/pull/213) by [ManosPapadakis95](https://github.com/ManosPapadakis95)).
- Add missing method `KNNResultSet::empty()` for consistency with the other result sets.
* **Other changes**:
- Add examples: `nanoflann_gui_example_R3_knn` and `nanoflann_gui_example_R3_radius`
- Add GUI examples for each search type:
- `nanoflann_gui_example_R3_knn`
- `nanoflann_gui_example_R3_radius`
- `nanoflann_gui_example_R3_rknn`


nanoflann 1.5.0: Released Jun 16, 2023
Expand Down
5 changes: 5 additions & 0 deletions examples/examples_gui/nanoflann_gui_example_R3/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -6,20 +6,25 @@ find_package(mrpt-gui REQUIRED)

add_executable(${PROJECT_NAME}_radius ${PROJECT_NAME}.cpp)
add_executable(${PROJECT_NAME}_knn ${PROJECT_NAME}.cpp)
add_executable(${PROJECT_NAME}_rknn ${PROJECT_NAME}.cpp)

target_compile_definitions(${PROJECT_NAME}_radius PRIVATE USE_RADIUS_SEARCH)
target_compile_definitions(${PROJECT_NAME}_knn PRIVATE USE_KNN_SEARCH)
target_compile_definitions(${PROJECT_NAME}_rknn PRIVATE USE_RKNN_SEARCH)

# optimized build:
if (CMAKE_COMPILER_IS_GNUCXX)
target_compile_options(${PROJECT_NAME}_radius PRIVATE -O2 -mtune=native)
target_compile_options(${PROJECT_NAME}_knn PRIVATE -O2 -mtune=native)
target_compile_options(${PROJECT_NAME}_rknn PRIVATE -O2 -mtune=native)
endif()

# Make sure the include path is used:
target_link_libraries(${PROJECT_NAME}_radius nanoflann::nanoflann mrpt::gui)
target_link_libraries(${PROJECT_NAME}_knn nanoflann::nanoflann mrpt::gui)
target_link_libraries(${PROJECT_NAME}_rknn nanoflann::nanoflann mrpt::gui)

# for this example to find "../utils.h"
target_include_directories(${PROJECT_NAME}_radius PRIVATE ".")
target_include_directories(${PROJECT_NAME}_knn PRIVATE ".")
target_include_directories(${PROJECT_NAME}_rknn PRIVATE ".")
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ void kdtree_demo(const size_t N)
glFoundPts->setColor_u8(0x00, 0x00, 0xff, 0x80);
scene->insert(glFoundPts);

#if defined(USE_RADIUS_SEARCH)
#if defined(USE_RADIUS_SEARCH) || defined(USE_RKNN_SEARCH)
glQuerySphere->setColor_u8(0xe0, 0xe0, 0xe0, 0x30);
glQuerySphere->enableDrawSolid3D(true);
#else
Expand Down Expand Up @@ -108,21 +108,22 @@ void kdtree_demo(const size_t N)
// Declare here to avoid reallocations:
#if defined(USE_RADIUS_SEARCH)
std::vector<nanoflann::ResultItem<size_t, double>> indicesDists;
#elif defined(USE_KNN_SEARCH)
#elif defined(USE_KNN_SEARCH) || defined(USE_RKNN_SEARCH)
std::vector<size_t> indices;
std::vector<double> distances;
#else
#error Expected either KNN or radius search build flag!
#error Expected either KNN, radius, or RKNN search build flag!
#endif

// Loop: different searches until the window is closed:
while (win.isOpen())
{
// Unsorted radius search:
#if defined(USE_RADIUS_SEARCH)
#if defined(USE_RADIUS_SEARCH) || defined(USE_RKNN_SEARCH)
const double radius = rng.drawUniform(0.1, maxRangeXY * 0.5);
const double sqRadius = radius * radius;
#else
#endif
#if defined(USE_KNN_SEARCH) || defined(USE_RKNN_SEARCH)
const size_t nnToSearch = (rng.drawUniform32bit() % 10) + 1;
#endif
const double queryPt[3] = {
Expand All @@ -138,7 +139,16 @@ void kdtree_demo(const size_t N)
sqRadius, indicesDists);
index.findNeighbors(resultSet, queryPt);
#else

#if defined(USE_RKNN_SEARCH)
nanoflann::RKNNResultSet<double, size_t> resultSet(
nnToSearch, sqRadius);
#elif defined(USE_KNN_SEARCH)
nanoflann::KNNResultSet<double, size_t> resultSet(nnToSearch);
#else
#error Should not reach here!
#endif

indices.resize(nnToSearch);
distances.resize(nnToSearch);
resultSet.init(indices.data(), distances.data());
Expand Down Expand Up @@ -178,7 +188,7 @@ void kdtree_demo(const size_t N)
glQueryPt->insertPoint(queryPt[0], queryPt[1], queryPt[2]);

glQuerySphere->setLocation(queryPt[0], queryPt[1], queryPt[2]);
#if defined(USE_RADIUS_SEARCH)
#if defined(USE_RADIUS_SEARCH) || defined(USE_RKNN_SEARCH)
glQuerySphere->setRadius(radius);
#else
glQuerySphere->setRadius(worstDist);
Expand Down
122 changes: 119 additions & 3 deletions include/nanoflann.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
*
* Copyright 2008-2009 Marius Muja ([email protected]). All rights reserved.
* Copyright 2008-2009 David G. Lowe ([email protected]). All rights reserved.
* Copyright 2011-2022 Jose Luis Blanco ([email protected]).
* Copyright 2011-2023 Jose Luis Blanco ([email protected]).
* All rights reserved.
*
* THE BSD LICENSE
Expand Down Expand Up @@ -60,7 +60,7 @@
#include <vector>

/** Library version: 0xMmP (M=Major,m=minor,P=patch) */
#define NANOFLANN_VERSION 0x150
#define NANOFLANN_VERSION 0x151

// Avoid conflicting declaration of min/max macros in Windows headers
#if !defined(NOMINMAX) && \
Expand Down Expand Up @@ -158,6 +158,8 @@ inline typename std::enable_if<!has_assign<Container>::value, void>::type

/** @addtogroup result_sets_grp Result set classes
* @{ */

/** Result set for KNN searches (N-closest neighbors) */
template <
typename _DistanceType, typename _IndexType = size_t,
typename _CountType = size_t>
Expand Down Expand Up @@ -191,8 +193,92 @@ class KNNResultSet

CountType size() const { return count; }
bool empty() const { return count == 0; }
bool full() const { return count == capacity; }

/**
* Called during search to add an element matching the criteria.
* @return true if the search should be continued, false if the results are
* sufficient
*/
bool addPoint(DistanceType dist, IndexType index)
{
CountType i;
for (i = count; i > 0; --i)
{
/** If defined and two points have the same distance, the one with
* the lowest-index will be returned first. */
#ifdef NANOFLANN_FIRST_MATCH
if ((dists[i - 1] > dist) ||
((dist == dists[i - 1]) && (indices[i - 1] > index)))
{
#else
if (dists[i - 1] > dist)
{
#endif
if (i < capacity)
{
dists[i] = dists[i - 1];
indices[i] = indices[i - 1];
}
}
else
break;
}
if (i < capacity)
{
dists[i] = dist;
indices[i] = index;
}
if (count < capacity) count++;

// tell caller that the search shall continue
return true;
}

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

/** Result set for RKNN searches (N-closest neighbors with a maximum radius) */
template <
typename _DistanceType, typename _IndexType = size_t,
typename _CountType = size_t>
class RKNNResultSet
{
public:
using DistanceType = _DistanceType;
using IndexType = _IndexType;
using CountType = _CountType;

bool full() const { return count == capacity; }
private:
IndexType* indices;
DistanceType* dists;
CountType capacity;
CountType count;
DistanceType maximumSearchDistanceSquared;

public:
explicit RKNNResultSet(
CountType capacity_, DistanceType maximumSearchDistanceSquared_)
: indices(nullptr),
dists(nullptr),
capacity(capacity_),
count(0),
maximumSearchDistanceSquared(maximumSearchDistanceSquared_)
{
}

void init(IndexType* indices_, DistanceType* dists_)
{
indices = indices_;
dists = dists_;
count = 0;
if (capacity)
dists[capacity - 1] = (std::numeric_limits<DistanceType>::max)();
}

CountType size() const { return count; }
bool empty() const { return count == 0; }
bool full() const { return count == capacity; }

/**
* Called during search to add an element matching the criteria.
Expand All @@ -201,6 +287,8 @@ class KNNResultSet
*/
bool addPoint(DistanceType dist, IndexType index)
{
if (dist > maximumSearchDistanceSquared) return true;

CountType i;
for (i = count; i > 0; --i)
{
Expand Down Expand Up @@ -1681,6 +1769,34 @@ class KDTreeSingleIndexAdaptor
return resultSet.size();
}

/**
* Find the first N neighbors to \a query_point[0:dim-1] within a maximum
* radius. The output is given as a vector of pairs, of which the first
* element is a point index and the second the corresponding distance.
* Previous contents of \a IndicesDists are cleared.
*
* \sa radiusSearch, findNeighbors
* \return Number `N` of valid points in the result set.
*
* \note If L2 norms are used, all returned distances are actually squared
* distances.
*
* \note Only the first `N` entries in `out_indices` and `out_distances`
* will be valid. Return is less than `num_closest` only if the
* number of elements in the tree is less than `num_closest`.
*/
Size rknnSearch(
const ElementType* query_point, const Size num_closest,
IndexType* out_indices, DistanceType* out_distances,
const DistanceType& radius) const
{
nanoflann::RKNNResultSet<DistanceType, IndexType> resultSet(
num_closest, radius);
resultSet.init(out_indices, out_distances);
findNeighbors(resultSet, query_point);
return resultSet.size();
}

/** @} */

public:
Expand Down
Loading

0 comments on commit 73c75ce

Please sign in to comment.