diff --git a/CHANGELOG b/CHANGELOG
index 8c584d1e4..c850008cf 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -4,6 +4,20 @@ Ponca changelog
--------------------------------------------------------------------------------
Current head (v.1.1 RC)
+This release improves the Spatial Partitioning Package with new features and
+improved doc.
+
+- API
+ - [spatialPartitioning] Add kd-tree traits type (#80)
+ - [fitting] Add Primitive::getNumNeighbors (#86)
+
+- Bug-fixes and code improvements
+ - [spatialPartitioning] Fix unwanted function hiding with DryFit::setWeightFunc (#86)
+ - [fitting] Remove deadcode in Basket (#86)
+
+-Docs
+ - [spatialPartitioning] Update module page with a minimal doc and examples (#86)
+ - [spatialPartitioning] Add NanoFlann example (#86)
--------------------------------------------------------------------------------
v.1.0
@@ -28,7 +42,6 @@ The changes introduced in this version mostly are introduced by PR #56.
- [fitting] Rename normal() as primitiveGradient in MlsSphereFit (#72)
- [fitting] Add new weight kernels (#71,#74)
- [fitting] Add mechanism to check weight derivatives validity (#74)
- - [spatialPartitioning] Add kd-tree traits type (#80)
- Bug-fixes and code improvements
- [fitting] Reduce objets sizes by removing unnecessary attributes (#56)
diff --git a/Ponca/src/Fitting/basket.h b/Ponca/src/Fitting/basket.h
index 224b775d3..4e2fcaf4b 100644
--- a/Ponca/src/Fitting/basket.h
+++ b/Ponca/src/Fitting/basket.h
@@ -64,13 +64,13 @@ namespace internal
PONCA_MULTIARCH inline \
FIT_RESULT computeWithIds(IndexRange ids, const PointContainer& points){ \
FIT_RESULT res = UNDEFINED; \
- do { \
- for (const auto& i : ids){ \
- this->addNeighbor(points[i]); \
- } \
- res = this->finalize(); \
- } while ( res == NEED_OTHER_PASS ); \
- return res;return Self::computeWithIds(ids, points); \
+ do { \
+ for (const auto& i : ids){ \
+ this->addNeighbor(points[i]); \
+ } \
+ res = this->finalize(); \
+ } while ( res == NEED_OTHER_PASS ); \
+ return res; \
} \
WRITE_BASKET_SINGLE_HOST_FUNCTIONS
diff --git a/Ponca/src/Fitting/dryFit.h b/Ponca/src/Fitting/dryFit.h
index af56a6ab5..0fe885d77 100644
--- a/Ponca/src/Fitting/dryFit.h
+++ b/Ponca/src/Fitting/dryFit.h
@@ -37,9 +37,6 @@ namespace Ponca
PONCA_FITTING_APIDOC_FINALIZE
PONCA_MULTIARCH inline FIT_RESULT finalize() { return Base::finalize(); }
- PONCA_FITTING_APIDOC_SETWFUNC
- PONCA_MULTIARCH inline void setWeightFunc (const WFunctor& /*_w*/) { }
-
//! \brief Simulate Scalar field computation
PONCA_MULTIARCH inline Scalar potential ( ) const { return Scalar(0); }
diff --git a/Ponca/src/Fitting/primitive.h b/Ponca/src/Fitting/primitive.h
index bbd4c6d8c..72c322eaa 100644
--- a/Ponca/src/Fitting/primitive.h
+++ b/Ponca/src/Fitting/primitive.h
@@ -83,6 +83,9 @@ class PrimitiveBase
and the result is stable, eq. having more than 6 neighbors) */
PONCA_MULTIARCH inline bool isStable() const { return m_eCurrentState == STABLE; }
+ /*! \brief Get number of points added in the neighborhood (with non negative weight) */
+ PONCA_MULTIARCH inline int getNumNeighbors() const { return m_nbNeighbors; }
+
/*! \return the current test of the fit */
PONCA_MULTIARCH inline FIT_RESULT getCurrentState() const
{
diff --git a/doc/src/example.mdoc b/doc/src/example.mdoc
index dcb88e1c8..02b225bb9 100644
--- a/doc/src/example.mdoc
+++ b/doc/src/example.mdoc
@@ -17,5 +17,7 @@
- \subpage example_cxx_pcl_page : How to use Ponca with Point cloud library (PCL). A basic example to compute and visualize surface curvature.
- \subpage example_cu_ssc_page : Calculate Screen Space Curvature using CUDA/C++.
- \subpage example_python_ssc_page : Calculate Screen Space Curvature using CUDA/Python.
+ - @ref spatialpartitioning "Spatial Partitioning Module"
+ - \subpage example_cxx_nanoflann_page : Comparison between Nanoflann and Ponca KdTree APIs.
*/
diff --git a/doc/src/example_cxx_nanoflann.mdoc b/doc/src/example_cxx_nanoflann.mdoc
new file mode 100644
index 000000000..242d9e86f
--- /dev/null
+++ b/doc/src/example_cxx_nanoflann.mdoc
@@ -0,0 +1,53 @@
+/*
+ This Source Code Form is subject to the terms of the Mozilla Public
+ License, v. 2.0. If a copy of the MPL was not distributed with this
+ file, You can obtain one at http://mozilla.org/MPL/2.0/.
+*/
+
+
+/*!
+ \page example_cxx_nanoflann_page Comparison between Nanoflann and Ponca KdTree APIs
+
+ \section nanoflann_intro_sec Introduction
+
+ This example demonstrates how to use Ponca and
+ Nanoflann (v1.5.0) KdTrees on the same data, and compare runtime performances.
+
+ \subsection nanoflann_compilation_sec Compilation
+
+ As for other examples, you need to enable the compilation of the examples with `cmake`, and build the examples:
+
+ \code
+ cmake [..] -DPONCA_CONFIGURE_EXAMPLES=ON
+ make ponca-examples // or make ponca_nanoflann
+ \endcode
+
+ \section nanoflann_comparison_sec API comparisons
+
+ Task | Ponca | Nanoflann
+ ---------------- | ----------------------------------------------------- | -------------
+ KdTree Creation | \snippet ponca_nanoflann.cpp Create Ponca KdTree | \snippet ponca_nanoflann.cpp Create NanoFlann KdTree
+ Range Query | \snippet ponca_nanoflann.cpp Use Ponca KdTree | \snippet ponca_nanoflann.cpp Use NanoFlann KdTree
+
+ The fitting object is a Ponca::DryFit, an object that filter the neighbors according to their weight (out of scale points
+ are not counted), but do not perform any other computation:
+ \snippet ponca_nanoflann.cpp Define Fit Type
+
+ \section nanoflann_timings_sec Timings
+ The example output the time required to collect the neighbors, and the number of neighbors collected:
+ \code
+Timings:
+Raw : 0.168698
+Ponca : 0.025475
+Nanoflann : 0.027746
+Number of neighbors:
+Raw : 950618
+Ponca : 950618
+Nanoflann : 950618
+ \endcode
+
+ \section nanoflann_sourcecode_sec Example source code
+ Source file: `ponca_nanoflann.cpp`
+ \include ponca_nanoflann.cpp
+
+ */
diff --git a/doc/src/ponca_module_spatialpartitioning.mdoc b/doc/src/ponca_module_spatialpartitioning.mdoc
index a897d51ef..ea77c9cd1 100644
--- a/doc/src/ponca_module_spatialpartitioning.mdoc
+++ b/doc/src/ponca_module_spatialpartitioning.mdoc
@@ -42,7 +42,13 @@ namespace Ponca
\note As queries are objets that are independent from the KdTree, they can be created and used in parallel from
multiple threads.
-
+ \subsection spatialpartitioning_kdtree_examples Examples
+ KdTree usage is demonstrated both in tests and examples:
+ - `tests/src/basket.cpp`
+ - `tests/src/kdtree_knearest.cpp`
+ - `tests/src/kdtree_nearest.cpp`
+ - `tests/src/kdtree_range.cpp`
+ - `examples/cpp/nanoflann/ponca_nanoflann.cpp`
\subsection spatialpartitioning_kdtree_extending Extending KdTree
Ponca::KdTreeBase is a customizable version of Ponca::KdTree, which can be controlled using `Traits`.
diff --git a/examples/cpp/CMakeLists.txt b/examples/cpp/CMakeLists.txt
index 51d34a14c..53070e5a2 100644
--- a/examples/cpp/CMakeLists.txt
+++ b/examples/cpp/CMakeLists.txt
@@ -35,3 +35,4 @@ add_dependencies(ponca-examples ponca_fit_line)
ponca_handle_eigen_dependency(ponca_fit_line)
add_subdirectory(pcl)
+add_subdirectory(nanoflann)
diff --git a/examples/cpp/nanoflann/CMakeLists.txt b/examples/cpp/nanoflann/CMakeLists.txt
new file mode 100644
index 000000000..c2ee5d572
--- /dev/null
+++ b/examples/cpp/nanoflann/CMakeLists.txt
@@ -0,0 +1,8 @@
+project(Ponca_Example_Nanoflann LANGUAGES CXX)
+
+set(CMAKE_INCLUDE_CURRENT_DIR ON)
+
+add_executable(ponca_nanoflann ponca_nanoflann.cpp)
+target_include_directories(ponca_nanoflann PRIVATE ${PONCA_src_ROOT})
+add_dependencies(ponca-examples ponca_nanoflann)
+ponca_handle_eigen_dependency(ponca_nanoflann)
diff --git a/examples/cpp/nanoflann/nanoflann.hpp b/examples/cpp/nanoflann/nanoflann.hpp
new file mode 100644
index 000000000..305b25a4d
--- /dev/null
+++ b/examples/cpp/nanoflann/nanoflann.hpp
@@ -0,0 +1,2585 @@
+/***********************************************************************
+ * Software License Agreement (BSD License)
+ *
+ * Copyright 2008-2009 Marius Muja (mariusm@cs.ubc.ca). All rights reserved.
+ * Copyright 2008-2009 David G. Lowe (lowe@cs.ubc.ca). All rights reserved.
+ * Copyright 2011-2022 Jose Luis Blanco (joseluisblancoc@gmail.com).
+ * All rights reserved.
+ *
+ * THE BSD LICENSE
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *************************************************************************/
+
+/** \mainpage nanoflann C++ API documentation
+ * nanoflann is a C++ header-only library for building KD-Trees, mostly
+ * optimized for 2D or 3D point clouds.
+ *
+ * nanoflann does not require compiling or installing, just an
+ * #include in your code.
+ *
+ * See:
+ * - [Online README](https://github.com/jlblancoc/nanoflann)
+ * - [C++ API documentation](https://jlblancoc.github.io/nanoflann/)
+ */
+
+#pragma once
+
+#include
+#include
+#include
+#include
+#include // for abs()
+#include // for abs()
+#include // std::reference_wrapper
+#include
+#include
+#include // std::numeric_limits
+#include
+#include
+#include
+#include
+
+/** Library version: 0xMmP (M=Major,m=minor,P=patch) */
+#define NANOFLANN_VERSION 0x150
+
+// Avoid conflicting declaration of min/max macros in Windows headers
+#if !defined(NOMINMAX) && \
+ (defined(_WIN32) || defined(_WIN32_) || defined(WIN32) || defined(_WIN64))
+#define NOMINMAX
+#ifdef max
+#undef max
+#undef min
+#endif
+#endif
+// Avoid conflicts with X11 headers
+#ifdef None
+#undef None
+#endif
+
+namespace nanoflann
+{
+/** @addtogroup nanoflann_grp nanoflann C++ library for KD-trees
+ * @{ */
+
+/** the PI constant (required to avoid MSVC missing symbols) */
+template
+T pi_const()
+{
+ return static_cast(3.14159265358979323846);
+}
+
+/**
+ * Traits if object is resizable and assignable (typically has a resize | assign
+ * method)
+ */
+template
+struct has_resize : std::false_type
+{
+};
+
+template
+struct has_resize().resize(1), 0)>
+ : std::true_type
+{
+};
+
+template
+struct has_assign : std::false_type
+{
+};
+
+template
+struct has_assign().assign(1, 0), 0)>
+ : std::true_type
+{
+};
+
+/**
+ * Free function to resize a resizable object
+ */
+template
+inline typename std::enable_if::value, void>::type resize(
+ Container& c, const size_t nElements)
+{
+ c.resize(nElements);
+}
+
+/**
+ * Free function that has no effects on non resizable containers (e.g.
+ * std::array) It raises an exception if the expected size does not match
+ */
+template
+inline typename std::enable_if::value, void>::type
+ resize(Container& c, const size_t nElements)
+{
+ if (nElements != c.size())
+ throw std::logic_error("Try to change the size of a std::array.");
+}
+
+/**
+ * Free function to assign to a container
+ */
+template
+inline typename std::enable_if::value, void>::type assign(
+ Container& c, const size_t nElements, const T& value)
+{
+ c.assign(nElements, value);
+}
+
+/**
+ * Free function to assign to a std::array
+ */
+template
+inline typename std::enable_if::value, void>::type
+ assign(Container& c, const size_t nElements, const T& value)
+{
+ for (size_t i = 0; i < nElements; i++) c[i] = value;
+}
+
+/** @addtogroup result_sets_grp Result set classes
+ * @{ */
+template <
+ typename _DistanceType, typename _IndexType = size_t,
+ typename _CountType = size_t>
+class KNNResultSet
+{
+ public:
+ using DistanceType = _DistanceType;
+ using IndexType = _IndexType;
+ using CountType = _CountType;
+
+ private:
+ IndexType* indices;
+ DistanceType* dists;
+ CountType capacity;
+ CountType count;
+
+ public:
+ explicit KNNResultSet(CountType capacity_)
+ : indices(nullptr), dists(nullptr), capacity(capacity_), count(0)
+ {
+ }
+
+ void init(IndexType* indices_, DistanceType* dists_)
+ {
+ indices = indices_;
+ dists = dists_;
+ count = 0;
+ if (capacity)
+ dists[capacity - 1] = (std::numeric_limits::max)();
+ }
+
+ CountType size() const { return count; }
+
+ 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]; }
+};
+
+/** operator "<" for std::sort() */
+struct IndexDist_Sorter
+{
+ /** PairType will be typically: ResultItem */
+ template
+ bool operator()(const PairType& p1, const PairType& p2) const
+ {
+ return p1.second < p2.second;
+ }
+};
+
+/**
+ * Each result element in RadiusResultSet. Note that distances and indices
+ * are named `first` and `second` to keep backward-compatibility with the
+ * `std::pair<>` type used in the past. In contrast, this structure is ensured
+ * to be `std::is_standard_layout` so it can be used in wrappers to other
+ * languages.
+ * See: https://github.com/jlblancoc/nanoflann/issues/166
+ */
+template
+struct ResultItem
+{
+ ResultItem() = default;
+ ResultItem(const IndexType index, const DistanceType distance)
+ : first(index), second(distance)
+ {
+ }
+
+ IndexType first; //!< Index of the sample in the dataset
+ DistanceType second; //!< Distance from sample to query point
+};
+
+/**
+ * A result-set class used when performing a radius based search.
+ */
+template
+class RadiusResultSet
+{
+ public:
+ using DistanceType = _DistanceType;
+ using IndexType = _IndexType;
+
+ public:
+ const DistanceType radius;
+
+ std::vector>& m_indices_dists;
+
+ explicit RadiusResultSet(
+ DistanceType radius_,
+ std::vector>& indices_dists)
+ : radius(radius_), m_indices_dists(indices_dists)
+ {
+ init();
+ }
+
+ void init() { clear(); }
+ void clear() { m_indices_dists.clear(); }
+
+ size_t size() const { return m_indices_dists.size(); }
+ size_t empty() const { return m_indices_dists.empty(); }
+
+ bool full() const { return true; }
+
+ /**
+ * 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)
+ {
+ if (dist < radius) m_indices_dists.emplace_back(index, dist);
+ return true;
+ }
+
+ DistanceType worstDist() const { return radius; }
+
+ /**
+ * Find the worst result (farthest neighbor) without copying or sorting
+ * Pre-conditions: size() > 0
+ */
+ ResultItem worst_item() const
+ {
+ if (m_indices_dists.empty())
+ throw std::runtime_error(
+ "Cannot invoke RadiusResultSet::worst_item() on "
+ "an empty list of results.");
+ auto it = std::max_element(
+ m_indices_dists.begin(), m_indices_dists.end(), IndexDist_Sorter());
+ return *it;
+ }
+};
+
+/** @} */
+
+/** @addtogroup loadsave_grp Load/save auxiliary functions
+ * @{ */
+template
+void save_value(std::ostream& stream, const T& value)
+{
+ stream.write(reinterpret_cast(&value), sizeof(T));
+}
+
+template
+void save_value(std::ostream& stream, const std::vector& value)
+{
+ size_t size = value.size();
+ stream.write(reinterpret_cast(&size), sizeof(size_t));
+ stream.write(reinterpret_cast(value.data()), sizeof(T) * size);
+}
+
+template
+void load_value(std::istream& stream, T& value)
+{
+ stream.read(reinterpret_cast(&value), sizeof(T));
+}
+
+template
+void load_value(std::istream& stream, std::vector& value)
+{
+ size_t size;
+ stream.read(reinterpret_cast(&size), sizeof(size_t));
+ value.resize(size);
+ stream.read(reinterpret_cast(value.data()), sizeof(T) * size);
+}
+/** @} */
+
+/** @addtogroup metric_grp Metric (distance) classes
+ * @{ */
+
+struct Metric
+{
+};
+
+/** Manhattan distance functor (generic version, optimized for
+ * high-dimensionality data sets). Corresponding distance traits:
+ * nanoflann::metric_L1
+ *
+ * \tparam T Type of the elements (e.g. double, float, uint8_t)
+ * \tparam DataSource Source of the data, i.e. where the vectors are stored
+ * \tparam _DistanceType Type of distance variables (must be signed)
+ * \tparam IndexType Type of the arguments with which the data can be
+ * accessed (e.g. float, double, int64_t, T*)
+ */
+template <
+ class T, class DataSource, typename _DistanceType = T,
+ typename IndexType = uint32_t>
+struct L1_Adaptor
+{
+ using ElementType = T;
+ using DistanceType = _DistanceType;
+
+ const DataSource& data_source;
+
+ L1_Adaptor(const DataSource& _data_source) : data_source(_data_source) {}
+
+ DistanceType evalMetric(
+ const T* a, const IndexType b_idx, size_t size,
+ DistanceType worst_dist = -1) const
+ {
+ DistanceType result = DistanceType();
+ const T* last = a + size;
+ const T* lastgroup = last - 3;
+ size_t d = 0;
+
+ /* Process 4 items with each loop for efficiency. */
+ while (a < lastgroup)
+ {
+ const DistanceType diff0 =
+ std::abs(a[0] - data_source.kdtree_get_pt(b_idx, d++));
+ const DistanceType diff1 =
+ std::abs(a[1] - data_source.kdtree_get_pt(b_idx, d++));
+ const DistanceType diff2 =
+ std::abs(a[2] - data_source.kdtree_get_pt(b_idx, d++));
+ const DistanceType diff3 =
+ std::abs(a[3] - data_source.kdtree_get_pt(b_idx, d++));
+ result += diff0 + diff1 + diff2 + diff3;
+ a += 4;
+ if ((worst_dist > 0) && (result > worst_dist)) { return result; }
+ }
+ /* Process last 0-3 components. Not needed for standard vector lengths.
+ */
+ while (a < last)
+ { result += std::abs(*a++ - data_source.kdtree_get_pt(b_idx, d++)); }
+ return result;
+ }
+
+ template
+ DistanceType accum_dist(const U a, const V b, const size_t) const
+ {
+ return std::abs(a - b);
+ }
+};
+
+/** **Squared** Euclidean distance functor (generic version, optimized for
+ * high-dimensionality data sets). Corresponding distance traits:
+ * nanoflann::metric_L2
+ *
+ * \tparam T Type of the elements (e.g. double, float, uint8_t)
+ * \tparam DataSource Source of the data, i.e. where the vectors are stored
+ * \tparam _DistanceType Type of distance variables (must be signed)
+ * \tparam IndexType Type of the arguments with which the data can be
+ * accessed (e.g. float, double, int64_t, T*)
+ */
+template <
+ class T, class DataSource, typename _DistanceType = T,
+ typename IndexType = uint32_t>
+struct L2_Adaptor
+{
+ using ElementType = T;
+ using DistanceType = _DistanceType;
+
+ const DataSource& data_source;
+
+ L2_Adaptor(const DataSource& _data_source) : data_source(_data_source) {}
+
+ DistanceType evalMetric(
+ const T* a, const IndexType b_idx, size_t size,
+ DistanceType worst_dist = -1) const
+ {
+ DistanceType result = DistanceType();
+ const T* last = a + size;
+ const T* lastgroup = last - 3;
+ size_t d = 0;
+
+ /* Process 4 items with each loop for efficiency. */
+ while (a < lastgroup)
+ {
+ const DistanceType diff0 =
+ a[0] - data_source.kdtree_get_pt(b_idx, d++);
+ const DistanceType diff1 =
+ a[1] - data_source.kdtree_get_pt(b_idx, d++);
+ const DistanceType diff2 =
+ a[2] - data_source.kdtree_get_pt(b_idx, d++);
+ const DistanceType diff3 =
+ a[3] - data_source.kdtree_get_pt(b_idx, d++);
+ result +=
+ diff0 * diff0 + diff1 * diff1 + diff2 * diff2 + diff3 * diff3;
+ a += 4;
+ if ((worst_dist > 0) && (result > worst_dist)) { return result; }
+ }
+ /* Process last 0-3 components. Not needed for standard vector lengths.
+ */
+ while (a < last)
+ {
+ const DistanceType diff0 =
+ *a++ - data_source.kdtree_get_pt(b_idx, d++);
+ result += diff0 * diff0;
+ }
+ return result;
+ }
+
+ template
+ DistanceType accum_dist(const U a, const V b, const size_t) const
+ {
+ return (a - b) * (a - b);
+ }
+};
+
+/** **Squared** Euclidean (L2) distance functor (suitable for low-dimensionality
+ * datasets, like 2D or 3D point clouds) Corresponding distance traits:
+ * nanoflann::metric_L2_Simple
+ *
+ * \tparam T Type of the elements (e.g. double, float, uint8_t)
+ * \tparam DataSource Source of the data, i.e. where the vectors are stored
+ * \tparam _DistanceType Type of distance variables (must be signed)
+ * \tparam IndexType Type of the arguments with which the data can be
+ * accessed (e.g. float, double, int64_t, T*)
+ */
+template <
+ class T, class DataSource, typename _DistanceType = T,
+ typename IndexType = uint32_t>
+struct L2_Simple_Adaptor
+{
+ using ElementType = T;
+ using DistanceType = _DistanceType;
+
+ const DataSource& data_source;
+
+ L2_Simple_Adaptor(const DataSource& _data_source)
+ : data_source(_data_source)
+ {
+ }
+
+ DistanceType evalMetric(
+ const T* a, const IndexType b_idx, size_t size) const
+ {
+ DistanceType result = DistanceType();
+ for (size_t i = 0; i < size; ++i)
+ {
+ const DistanceType diff =
+ a[i] - data_source.kdtree_get_pt(b_idx, i);
+ result += diff * diff;
+ }
+ return result;
+ }
+
+ template
+ DistanceType accum_dist(const U a, const V b, const size_t) const
+ {
+ return (a - b) * (a - b);
+ }
+};
+
+/** SO2 distance functor
+ * Corresponding distance traits: nanoflann::metric_SO2
+ *
+ * \tparam T Type of the elements (e.g. double, float, uint8_t)
+ * \tparam DataSource Source of the data, i.e. where the vectors are stored
+ * \tparam _DistanceType Type of distance variables (must be signed) (e.g.
+ * float, double) orientation is constrained to be in [-pi, pi]
+ * \tparam IndexType Type of the arguments with which the data can be
+ * accessed (e.g. float, double, int64_t, T*)
+ */
+template <
+ class T, class DataSource, typename _DistanceType = T,
+ typename IndexType = uint32_t>
+struct SO2_Adaptor
+{
+ using ElementType = T;
+ using DistanceType = _DistanceType;
+
+ const DataSource& data_source;
+
+ SO2_Adaptor(const DataSource& _data_source) : data_source(_data_source) {}
+
+ DistanceType evalMetric(
+ const T* a, const IndexType b_idx, size_t size) const
+ {
+ return accum_dist(
+ a[size - 1], data_source.kdtree_get_pt(b_idx, size - 1), size - 1);
+ }
+
+ /** Note: this assumes that input angles are already in the range [-pi,pi]
+ */
+ template
+ DistanceType accum_dist(const U a, const V b, const size_t) const
+ {
+ DistanceType result = DistanceType();
+ DistanceType PI = pi_const();
+ result = b - a;
+ if (result > PI)
+ result -= 2 * PI;
+ else if (result < -PI)
+ result += 2 * PI;
+ return result;
+ }
+};
+
+/** SO3 distance functor (Uses L2_Simple)
+ * Corresponding distance traits: nanoflann::metric_SO3
+ *
+ * \tparam T Type of the elements (e.g. double, float, uint8_t)
+ * \tparam DataSource Source of the data, i.e. where the vectors are stored
+ * \tparam _DistanceType Type of distance variables (must be signed) (e.g.
+ * float, double)
+ * \tparam IndexType Type of the arguments with which the data can be
+ * accessed (e.g. float, double, int64_t, T*)
+ */
+template <
+ class T, class DataSource, typename _DistanceType = T,
+ typename IndexType = uint32_t>
+struct SO3_Adaptor
+{
+ using ElementType = T;
+ using DistanceType = _DistanceType;
+
+ L2_Simple_Adaptor
+ distance_L2_Simple;
+
+ SO3_Adaptor(const DataSource& _data_source)
+ : distance_L2_Simple(_data_source)
+ {
+ }
+
+ DistanceType evalMetric(
+ const T* a, const IndexType b_idx, size_t size) const
+ {
+ return distance_L2_Simple.evalMetric(a, b_idx, size);
+ }
+
+ template
+ DistanceType accum_dist(const U a, const V b, const size_t idx) const
+ {
+ return distance_L2_Simple.accum_dist(a, b, idx);
+ }
+};
+
+/** Metaprogramming helper traits class for the L1 (Manhattan) metric */
+struct metric_L1 : public Metric
+{
+ template
+ struct traits
+ {
+ using distance_t = L1_Adaptor;
+ };
+};
+/** Metaprogramming helper traits class for the L2 (Euclidean) **squared**
+ * distance metric */
+struct metric_L2 : public Metric
+{
+ template
+ struct traits
+ {
+ using distance_t = L2_Adaptor;
+ };
+};
+/** Metaprogramming helper traits class for the L2_simple (Euclidean)
+ * **squared** distance metric */
+struct metric_L2_Simple : public Metric
+{
+ template
+ struct traits
+ {
+ using distance_t = L2_Simple_Adaptor;
+ };
+};
+/** Metaprogramming helper traits class for the SO3_InnerProdQuat metric */
+struct metric_SO2 : public Metric
+{
+ template
+ struct traits
+ {
+ using distance_t = SO2_Adaptor;
+ };
+};
+/** Metaprogramming helper traits class for the SO3_InnerProdQuat metric */
+struct metric_SO3 : public Metric
+{
+ template
+ struct traits
+ {
+ using distance_t = SO3_Adaptor;
+ };
+};
+
+/** @} */
+
+/** @addtogroup param_grp Parameter structs
+ * @{ */
+
+enum class KDTreeSingleIndexAdaptorFlags
+{
+ None = 0,
+ SkipInitialBuildIndex = 1
+};
+
+inline std::underlying_type::type operator&(
+ KDTreeSingleIndexAdaptorFlags lhs, KDTreeSingleIndexAdaptorFlags rhs)
+{
+ using underlying =
+ typename std::underlying_type::type;
+ return static_cast(lhs) & static_cast(rhs);
+}
+
+/** Parameters (see README.md) */
+struct KDTreeSingleIndexAdaptorParams
+{
+ KDTreeSingleIndexAdaptorParams(
+ size_t _leaf_max_size = 10,
+ KDTreeSingleIndexAdaptorFlags _flags =
+ KDTreeSingleIndexAdaptorFlags::None,
+ unsigned int _n_thread_build = 1)
+ : leaf_max_size(_leaf_max_size),
+ flags(_flags),
+ n_thread_build(_n_thread_build)
+ {
+ }
+
+ size_t leaf_max_size;
+ KDTreeSingleIndexAdaptorFlags flags;
+ unsigned int n_thread_build;
+};
+
+/** Search options for KDTreeSingleIndexAdaptor::findNeighbors() */
+struct SearchParameters
+{
+ SearchParameters(float eps_ = 0, bool sorted_ = true)
+ : eps(eps_), sorted(sorted_)
+ {
+ }
+
+ float eps; //!< search for eps-approximate neighbours (default: 0)
+ bool sorted; //!< only for radius search, require neighbours sorted by
+ //!< distance (default: true)
+};
+/** @} */
+
+/** @addtogroup memalloc_grp Memory allocation
+ * @{ */
+
+/**
+ * Pooled storage allocator
+ *
+ * The following routines allow for the efficient allocation of storage in
+ * small chunks from a specified pool. Rather than allowing each structure
+ * to be freed individually, an entire pool of storage is freed at once.
+ * This method has two advantages over just using malloc() and free(). First,
+ * it is far more efficient for allocating small objects, as there is
+ * no overhead for remembering all the information needed to free each
+ * object or consolidating fragmented memory. Second, the decision about
+ * how long to keep an object is made at the time of allocation, and there
+ * is no need to track down all the objects to free them.
+ *
+ */
+class PooledAllocator
+{
+ static constexpr size_t WORDSIZE = 16;
+ static constexpr size_t BLOCKSIZE = 8192;
+
+ /* We maintain memory alignment to word boundaries by requiring that all
+ allocations be in multiples of the machine wordsize. */
+ /* Size of machine word in bytes. Must be power of 2. */
+ /* Minimum number of bytes requested at a time from the system. Must be
+ * multiple of WORDSIZE. */
+
+ using Offset = uint32_t;
+ using Size = uint32_t;
+ using Dimension = int32_t;
+
+ Size remaining_ = 0; //!< Number of bytes left in current block of storage
+ void* base_ = nullptr; //!< Pointer to base of current block of storage
+ void* loc_ = nullptr; //!< Current location in block to next allocate
+
+ void internal_init()
+ {
+ remaining_ = 0;
+ base_ = nullptr;
+ usedMemory = 0;
+ wastedMemory = 0;
+ }
+
+ public:
+ Size usedMemory = 0;
+ Size wastedMemory = 0;
+
+ /**
+ Default constructor. Initializes a new pool.
+ */
+ PooledAllocator() { internal_init(); }
+
+ /**
+ * Destructor. Frees all the memory allocated in this pool.
+ */
+ ~PooledAllocator() { free_all(); }
+
+ /** Frees all allocated memory chunks */
+ void free_all()
+ {
+ while (base_ != nullptr)
+ {
+ // Get pointer to prev block
+ void* prev = *(static_cast(base_));
+ ::free(base_);
+ base_ = prev;
+ }
+ internal_init();
+ }
+
+ /**
+ * Returns a pointer to a piece of new memory of the given size in bytes
+ * allocated from the pool.
+ */
+ void* malloc(const size_t req_size)
+ {
+ /* Round size up to a multiple of wordsize. The following expression
+ only works for WORDSIZE that is a power of 2, by masking last bits
+ of incremented size to zero.
+ */
+ const Size size = (req_size + (WORDSIZE - 1)) & ~(WORDSIZE - 1);
+
+ /* Check whether a new block must be allocated. Note that the first
+ word of a block is reserved for a pointer to the previous block.
+ */
+ if (size > remaining_)
+ {
+ wastedMemory += remaining_;
+
+ /* Allocate new storage. */
+ const Size blocksize =
+ (size + sizeof(void*) + (WORDSIZE - 1) > BLOCKSIZE)
+ ? size + sizeof(void*) + (WORDSIZE - 1)
+ : BLOCKSIZE;
+
+ // use the standard C malloc to allocate memory
+ void* m = ::malloc(blocksize);
+ if (!m)
+ {
+ fprintf(stderr, "Failed to allocate memory.\n");
+ throw std::bad_alloc();
+ }
+
+ /* Fill first word of new block with pointer to previous block. */
+ static_cast(m)[0] = base_;
+ base_ = m;
+
+ Size shift = 0;
+ // int size_t = (WORDSIZE - ( (((size_t)m) + sizeof(void*)) &
+ // (WORDSIZE-1))) & (WORDSIZE-1);
+
+ remaining_ = blocksize - sizeof(void*) - shift;
+ loc_ = (static_cast(m) + sizeof(void*) + shift);
+ }
+ void* rloc = loc_;
+ loc_ = static_cast(loc_) + size;
+ remaining_ -= size;
+
+ usedMemory += size;
+
+ return rloc;
+ }
+
+ /**
+ * Allocates (using this pool) a generic type T.
+ *
+ * Params:
+ * count = number of instances to allocate.
+ * Returns: pointer (of type T*) to memory buffer
+ */
+ template
+ T* allocate(const size_t count = 1)
+ {
+ T* mem = static_cast(this->malloc(sizeof(T) * count));
+ return mem;
+ }
+};
+/** @} */
+
+/** @addtogroup nanoflann_metaprog_grp Auxiliary metaprogramming stuff
+ * @{ */
+
+/** Used to declare fixed-size arrays when DIM>0, dynamically-allocated vectors
+ * when DIM=-1. Fixed size version for a generic DIM:
+ */
+template
+struct array_or_vector
+{
+ using type = std::array;
+};
+/** Dynamic size version */
+template
+struct array_or_vector<-1, T>
+{
+ using type = std::vector;
+};
+
+/** @} */
+
+/** kd-tree base-class
+ *
+ * Contains the member functions common to the classes KDTreeSingleIndexAdaptor
+ * and KDTreeSingleIndexDynamicAdaptor_.
+ *
+ * \tparam Derived The name of the class which inherits this class.
+ * \tparam DatasetAdaptor The user-provided adaptor, which must be ensured to
+ * have a lifetime equal or longer than the instance of this class.
+ * \tparam Distance The distance metric to use, these are all classes derived
+ * from nanoflann::Metric
+ * \tparam DIM Dimensionality of data points (e.g. 3 for 3D points)
+ * \tparam IndexType Type of the arguments with which the data can be
+ * accessed (e.g. float, double, int64_t, T*)
+ */
+template <
+ class Derived, typename Distance, class DatasetAdaptor, int32_t DIM = -1,
+ typename IndexType = uint32_t>
+class KDTreeBaseClass
+{
+ public:
+ /** Frees the previously-built index. Automatically called within
+ * buildIndex(). */
+ void freeIndex(Derived& obj)
+ {
+ obj.pool_.free_all();
+ obj.root_node_ = nullptr;
+ obj.size_at_index_build_ = 0;
+ }
+
+ using ElementType = typename Distance::ElementType;
+ using DistanceType = typename Distance::DistanceType;
+
+ /**
+ * Array of indices to vectors in the dataset_.
+ */
+ std::vector vAcc_;
+
+ using Offset = typename decltype(vAcc_)::size_type;
+ using Size = typename decltype(vAcc_)::size_type;
+ using Dimension = int32_t;
+
+ /*---------------------------
+ * Internal Data Structures
+ * --------------------------*/
+ struct Node
+ {
+ /** Union used because a node can be either a LEAF node or a non-leaf
+ * node, so both data fields are never used simultaneously */
+ union
+ {
+ struct leaf
+ {
+ Offset left, right; //!< Indices of points in leaf node
+ } lr;
+ struct nonleaf
+ {
+ Dimension divfeat; //!< Dimension used for subdivision.
+ /// The values used for subdivision.
+ DistanceType divlow, divhigh;
+ } sub;
+ } node_type;
+
+ /** Child nodes (both=nullptr mean its a leaf node) */
+ Node *child1 = nullptr, *child2 = nullptr;
+ };
+
+ using NodePtr = Node*;
+ using NodeConstPtr = const Node*;
+
+ struct Interval
+ {
+ ElementType low, high;
+ };
+
+ NodePtr root_node_ = nullptr;
+
+ Size leaf_max_size_ = 0;
+
+ /// Number of thread for concurrent tree build
+ Size n_thread_build_ = 1;
+ /// Number of current points in the dataset
+ Size size_ = 0;
+ /// Number of points in the dataset when the index was built
+ Size size_at_index_build_ = 0;
+ Dimension dim_ = 0; //!< Dimensionality of each data point
+
+ /** Define "BoundingBox" as a fixed-size or variable-size container
+ * depending on "DIM" */
+ using BoundingBox = typename array_or_vector::type;
+
+ /** Define "distance_vector_t" as a fixed-size or variable-size container
+ * depending on "DIM" */
+ using distance_vector_t = typename array_or_vector::type;
+
+ /** The KD-tree used to find neighbours */
+ BoundingBox root_bbox_;
+
+ /**
+ * Pooled memory allocator.
+ *
+ * Using a pooled memory allocator is more efficient
+ * than allocating memory directly when there is a large
+ * number small of memory allocations.
+ */
+ PooledAllocator pool_;
+
+ /** Returns number of points in dataset */
+ Size size(const Derived& obj) const { return obj.size_; }
+
+ /** Returns the length of each point in the dataset */
+ Size veclen(const Derived& obj) { return DIM > 0 ? DIM : obj.dim; }
+
+ /// Helper accessor to the dataset points:
+ ElementType dataset_get(
+ const Derived& obj, IndexType element, Dimension component) const
+ {
+ return obj.dataset_.kdtree_get_pt(element, component);
+ }
+
+ /**
+ * Computes the inde memory usage
+ * Returns: memory used by the index
+ */
+ Size usedMemory(Derived& obj)
+ {
+ return obj.pool_.usedMemory + obj.pool_.wastedMemory +
+ obj.dataset_.kdtree_get_point_count() *
+ sizeof(IndexType); // pool memory and vind array memory
+ }
+
+ void computeMinMax(
+ const Derived& obj, Offset ind, Size count, Dimension element,
+ ElementType& min_elem, ElementType& max_elem)
+ {
+ min_elem = dataset_get(obj, vAcc_[ind], element);
+ max_elem = min_elem;
+ for (Offset i = 1; i < count; ++i)
+ {
+ ElementType val = dataset_get(obj, vAcc_[ind + i], element);
+ if (val < min_elem) min_elem = val;
+ if (val > max_elem) max_elem = val;
+ }
+ }
+
+ /**
+ * Create a tree node that subdivides the list of vecs from vind[first]
+ * to vind[last]. The routine is called recursively on each sublist.
+ *
+ * @param left index of the first vector
+ * @param right index of the last vector
+ */
+ NodePtr divideTree(
+ Derived& obj, const Offset left, const Offset right, BoundingBox& bbox)
+ {
+ NodePtr node = obj.pool_.template allocate(); // allocate memory
+ const auto dims = (DIM > 0 ? DIM : obj.dim_);
+
+ /* If too few exemplars remain, then make this a leaf node. */
+ if ((right - left) <= static_cast(obj.leaf_max_size_))
+ {
+ node->child1 = node->child2 = nullptr; /* Mark as leaf node. */
+ node->node_type.lr.left = left;
+ node->node_type.lr.right = right;
+
+ // compute bounding-box of leaf points
+ for (Dimension i = 0; i < dims; ++i)
+ {
+ bbox[i].low = dataset_get(obj, obj.vAcc_[left], i);
+ bbox[i].high = dataset_get(obj, obj.vAcc_[left], i);
+ }
+ for (Offset k = left + 1; k < right; ++k)
+ {
+ for (Dimension i = 0; i < dims; ++i)
+ {
+ const auto val = dataset_get(obj, obj.vAcc_[k], i);
+ if (bbox[i].low > val) bbox[i].low = val;
+ if (bbox[i].high < val) bbox[i].high = val;
+ }
+ }
+ }
+ else
+ {
+ Offset idx;
+ Dimension cutfeat;
+ DistanceType cutval;
+ middleSplit_(obj, left, right - left, idx, cutfeat, cutval, bbox);
+
+ node->node_type.sub.divfeat = cutfeat;
+
+ BoundingBox left_bbox(bbox);
+ left_bbox[cutfeat].high = cutval;
+ node->child1 = this->divideTree(obj, left, left + idx, left_bbox);
+
+ BoundingBox right_bbox(bbox);
+ right_bbox[cutfeat].low = cutval;
+ node->child2 = this->divideTree(obj, left + idx, right, right_bbox);
+
+ node->node_type.sub.divlow = left_bbox[cutfeat].high;
+ node->node_type.sub.divhigh = right_bbox[cutfeat].low;
+
+ for (Dimension i = 0; i < dims; ++i)
+ {
+ bbox[i].low = std::min(left_bbox[i].low, right_bbox[i].low);
+ bbox[i].high = std::max(left_bbox[i].high, right_bbox[i].high);
+ }
+ }
+
+ return node;
+ }
+
+ /**
+ * Create a tree node that subdivides the list of vecs from vind[first] to
+ * vind[last] concurrently. The routine is called recursively on each
+ * sublist.
+ *
+ * @param left index of the first vector
+ * @param right index of the last vector
+ * @param thread_count count of std::async threads
+ * @param mutex mutex for mempool allocation
+ */
+ NodePtr divideTreeConcurrent(
+ Derived& obj, const Offset left, const Offset right, BoundingBox& bbox,
+ std::atomic& thread_count, std::mutex& mutex)
+ {
+ std::unique_lock lock(mutex);
+ NodePtr node = obj.pool_.template allocate(); // allocate memory
+ lock.unlock();
+
+ const auto dims = (DIM > 0 ? DIM : obj.dim_);
+
+ /* If too few exemplars remain, then make this a leaf node. */
+ if ((right - left) <= static_cast(obj.leaf_max_size_))
+ {
+ node->child1 = node->child2 = nullptr; /* Mark as leaf node. */
+ node->node_type.lr.left = left;
+ node->node_type.lr.right = right;
+
+ // compute bounding-box of leaf points
+ for (Dimension i = 0; i < dims; ++i)
+ {
+ bbox[i].low = dataset_get(obj, obj.vAcc_[left], i);
+ bbox[i].high = dataset_get(obj, obj.vAcc_[left], i);
+ }
+ for (Offset k = left + 1; k < right; ++k)
+ {
+ for (Dimension i = 0; i < dims; ++i)
+ {
+ const auto val = dataset_get(obj, obj.vAcc_[k], i);
+ if (bbox[i].low > val) bbox[i].low = val;
+ if (bbox[i].high < val) bbox[i].high = val;
+ }
+ }
+ }
+ else
+ {
+ Offset idx;
+ Dimension cutfeat;
+ DistanceType cutval;
+ middleSplit_(obj, left, right - left, idx, cutfeat, cutval, bbox);
+
+ node->node_type.sub.divfeat = cutfeat;
+
+ std::future left_future, right_future;
+
+ BoundingBox left_bbox(bbox);
+ left_bbox[cutfeat].high = cutval;
+ if (++thread_count < n_thread_build_)
+ {
+ left_future = std::async(
+ std::launch::async, &KDTreeBaseClass::divideTreeConcurrent,
+ this, std::ref(obj), left, left + idx, std::ref(left_bbox),
+ std::ref(thread_count), std::ref(mutex));
+ }
+ else
+ {
+ --thread_count;
+ node->child1 = this->divideTreeConcurrent(
+ obj, left, left + idx, left_bbox, thread_count, mutex);
+ }
+
+ BoundingBox right_bbox(bbox);
+ right_bbox[cutfeat].low = cutval;
+ if (++thread_count < n_thread_build_)
+ {
+ right_future = std::async(
+ std::launch::async, &KDTreeBaseClass::divideTreeConcurrent,
+ this, std::ref(obj), left + idx, right,
+ std::ref(right_bbox), std::ref(thread_count),
+ std::ref(mutex));
+ }
+ else
+ {
+ --thread_count;
+ node->child2 = this->divideTreeConcurrent(
+ obj, left + idx, right, right_bbox, thread_count, mutex);
+ }
+
+ if (left_future.valid())
+ {
+ node->child1 = left_future.get();
+ --thread_count;
+ }
+ if (right_future.valid())
+ {
+ node->child2 = right_future.get();
+ --thread_count;
+ }
+
+ node->node_type.sub.divlow = left_bbox[cutfeat].high;
+ node->node_type.sub.divhigh = right_bbox[cutfeat].low;
+
+ for (Dimension i = 0; i < dims; ++i)
+ {
+ bbox[i].low = std::min(left_bbox[i].low, right_bbox[i].low);
+ bbox[i].high = std::max(left_bbox[i].high, right_bbox[i].high);
+ }
+ }
+
+ return node;
+ }
+
+ void middleSplit_(
+ const Derived& obj, const Offset ind, const Size count, Offset& index,
+ Dimension& cutfeat, DistanceType& cutval, const BoundingBox& bbox)
+ {
+ const auto dims = (DIM > 0 ? DIM : obj.dim_);
+ const auto EPS = static_cast(0.00001);
+ ElementType max_span = bbox[0].high - bbox[0].low;
+ for (Dimension i = 1; i < dims; ++i)
+ {
+ ElementType span = bbox[i].high - bbox[i].low;
+ if (span > max_span) { max_span = span; }
+ }
+ ElementType max_spread = -1;
+ cutfeat = 0;
+ for (Dimension i = 0; i < dims; ++i)
+ {
+ ElementType span = bbox[i].high - bbox[i].low;
+ if (span > (1 - EPS) * max_span)
+ {
+ ElementType min_elem, max_elem;
+ computeMinMax(obj, ind, count, i, min_elem, max_elem);
+ ElementType spread = max_elem - min_elem;
+ if (spread > max_spread)
+ {
+ cutfeat = i;
+ max_spread = spread;
+ }
+ }
+ }
+ // split in the middle
+ DistanceType split_val = (bbox[cutfeat].low + bbox[cutfeat].high) / 2;
+ ElementType min_elem, max_elem;
+ computeMinMax(obj, ind, count, cutfeat, min_elem, max_elem);
+
+ if (split_val < min_elem)
+ cutval = min_elem;
+ else if (split_val > max_elem)
+ cutval = max_elem;
+ else
+ cutval = split_val;
+
+ Offset lim1, lim2;
+ planeSplit(obj, ind, count, cutfeat, cutval, lim1, lim2);
+
+ if (lim1 > count / 2)
+ index = lim1;
+ else if (lim2 < count / 2)
+ index = lim2;
+ else
+ index = count / 2;
+ }
+
+ /**
+ * Subdivide the list of points by a plane perpendicular on the axis
+ * corresponding to the 'cutfeat' dimension at 'cutval' position.
+ *
+ * On return:
+ * dataset[ind[0..lim1-1]][cutfeat]cutval
+ */
+ void planeSplit(
+ const Derived& obj, const Offset ind, const Size count,
+ const Dimension cutfeat, const DistanceType& cutval, Offset& lim1,
+ Offset& lim2)
+ {
+ /* Move vector indices for left subtree to front of list. */
+ Offset left = 0;
+ Offset right = count - 1;
+ for (;;)
+ {
+ while (left <= right &&
+ dataset_get(obj, vAcc_[ind + left], cutfeat) < cutval)
+ ++left;
+ while (right && left <= right &&
+ dataset_get(obj, vAcc_[ind + right], cutfeat) >= cutval)
+ --right;
+ if (left > right || !right)
+ break; // "!right" was added to support unsigned Index types
+ std::swap(vAcc_[ind + left], vAcc_[ind + right]);
+ ++left;
+ --right;
+ }
+ /* If either list is empty, it means that all remaining features
+ * are identical. Split in the middle to maintain a balanced tree.
+ */
+ lim1 = left;
+ right = count - 1;
+ for (;;)
+ {
+ while (left <= right &&
+ dataset_get(obj, vAcc_[ind + left], cutfeat) <= cutval)
+ ++left;
+ while (right && left <= right &&
+ dataset_get(obj, vAcc_[ind + right], cutfeat) > cutval)
+ --right;
+ if (left > right || !right)
+ break; // "!right" was added to support unsigned Index types
+ std::swap(vAcc_[ind + left], vAcc_[ind + right]);
+ ++left;
+ --right;
+ }
+ lim2 = left;
+ }
+
+ DistanceType computeInitialDistances(
+ const Derived& obj, const ElementType* vec,
+ distance_vector_t& dists) const
+ {
+ assert(vec);
+ DistanceType dist = DistanceType();
+
+ for (Dimension i = 0; i < (DIM > 0 ? DIM : obj.dim_); ++i)
+ {
+ if (vec[i] < obj.root_bbox_[i].low)
+ {
+ dists[i] =
+ obj.distance_.accum_dist(vec[i], obj.root_bbox_[i].low, i);
+ dist += dists[i];
+ }
+ if (vec[i] > obj.root_bbox_[i].high)
+ {
+ dists[i] =
+ obj.distance_.accum_dist(vec[i], obj.root_bbox_[i].high, i);
+ dist += dists[i];
+ }
+ }
+ return dist;
+ }
+
+ static void save_tree(
+ const Derived& obj, std::ostream& stream, const NodeConstPtr tree)
+ {
+ save_value(stream, *tree);
+ if (tree->child1 != nullptr) { save_tree(obj, stream, tree->child1); }
+ if (tree->child2 != nullptr) { save_tree(obj, stream, tree->child2); }
+ }
+
+ static void load_tree(Derived& obj, std::istream& stream, NodePtr& tree)
+ {
+ tree = obj.pool_.template allocate();
+ load_value(stream, *tree);
+ if (tree->child1 != nullptr) { load_tree(obj, stream, tree->child1); }
+ if (tree->child2 != nullptr) { load_tree(obj, stream, tree->child2); }
+ }
+
+ /** Stores the index in a binary file.
+ * IMPORTANT NOTE: The set of data points is NOT stored in the file, so
+ * when loading the index object it must be constructed associated to the
+ * same source of data points used while building it. See the example:
+ * examples/saveload_example.cpp \sa loadIndex */
+ void saveIndex(const Derived& obj, std::ostream& stream) const
+ {
+ save_value(stream, obj.size_);
+ save_value(stream, obj.dim_);
+ save_value(stream, obj.root_bbox_);
+ save_value(stream, obj.leaf_max_size_);
+ save_value(stream, obj.vAcc_);
+ save_tree(obj, stream, obj.root_node_);
+ }
+
+ /** Loads a previous index from a binary file.
+ * IMPORTANT NOTE: The set of data points is NOT stored in the file, so
+ * the index object must be constructed associated to the same source of
+ * data points used while building the index. See the example:
+ * examples/saveload_example.cpp \sa loadIndex */
+ void loadIndex(Derived& obj, std::istream& stream)
+ {
+ load_value(stream, obj.size_);
+ load_value(stream, obj.dim_);
+ load_value(stream, obj.root_bbox_);
+ load_value(stream, obj.leaf_max_size_);
+ load_value(stream, obj.vAcc_);
+ load_tree(obj, stream, obj.root_node_);
+ }
+};
+
+/** @addtogroup kdtrees_grp KD-tree classes and adaptors
+ * @{ */
+
+/** kd-tree static index
+ *
+ * Contains the k-d trees and other information for indexing a set of points
+ * for nearest-neighbor matching.
+ *
+ * The class "DatasetAdaptor" must provide the following interface (can be
+ * non-virtual, inlined methods):
+ *
+ * \code
+ * // Must return the number of data poins
+ * size_t kdtree_get_point_count() const { ... }
+ *
+ *
+ * // Must return the dim'th component of the idx'th point in the class:
+ * T kdtree_get_pt(const size_t idx, const size_t dim) const { ... }
+ *
+ * // Optional bounding-box computation: return false to default to a standard
+ * bbox computation loop.
+ * // Return true if the BBOX was already computed by the class and returned
+ * in "bb" so it can be avoided to redo it again.
+ * // Look at bb.size() to find out the expected dimensionality (e.g. 2 or 3
+ * for point clouds) template bool kdtree_get_bbox(BBOX &bb) const
+ * {
+ * bb[0].low = ...; bb[0].high = ...; // 0th dimension limits
+ * bb[1].low = ...; bb[1].high = ...; // 1st dimension limits
+ * ...
+ * return true;
+ * }
+ *
+ * \endcode
+ *
+ * \tparam DatasetAdaptor The user-provided adaptor, which must be ensured to
+ * have a lifetime equal or longer than the instance of this class.
+ * \tparam Distance The distance metric to use: nanoflann::metric_L1,
+ * nanoflann::metric_L2, nanoflann::metric_L2_Simple, etc. \tparam DIM
+ * Dimensionality of data points (e.g. 3 for 3D points) \tparam IndexType Will
+ * be typically size_t or int
+ */
+template <
+ typename Distance, class DatasetAdaptor, int32_t DIM = -1,
+ typename IndexType = uint32_t>
+class KDTreeSingleIndexAdaptor
+ : public KDTreeBaseClass<
+ KDTreeSingleIndexAdaptor,
+ Distance, DatasetAdaptor, DIM, IndexType>
+{
+ public:
+ /** Deleted copy constructor*/
+ explicit KDTreeSingleIndexAdaptor(
+ const KDTreeSingleIndexAdaptor<
+ Distance, DatasetAdaptor, DIM, IndexType>&) = delete;
+
+ /** The data source used by this index */
+ const DatasetAdaptor& dataset_;
+
+ const KDTreeSingleIndexAdaptorParams indexParams;
+
+ Distance distance_;
+
+ using Base = typename nanoflann::KDTreeBaseClass<
+ nanoflann::KDTreeSingleIndexAdaptor<
+ Distance, DatasetAdaptor, DIM, IndexType>,
+ Distance, DatasetAdaptor, DIM, IndexType>;
+
+ using Offset = typename Base::Offset;
+ using Size = typename Base::Size;
+ using Dimension = typename Base::Dimension;
+
+ using ElementType = typename Base::ElementType;
+ using DistanceType = typename Base::DistanceType;
+
+ using Node = typename Base::Node;
+ using NodePtr = Node*;
+
+ using Interval = typename Base::Interval;
+
+ /** Define "BoundingBox" as a fixed-size or variable-size container
+ * depending on "DIM" */
+ using BoundingBox = typename Base::BoundingBox;
+
+ /** Define "distance_vector_t" as a fixed-size or variable-size container
+ * depending on "DIM" */
+ using distance_vector_t = typename Base::distance_vector_t;
+
+ /**
+ * KDTree constructor
+ *
+ * Refer to docs in README.md or online in
+ * https://github.com/jlblancoc/nanoflann
+ *
+ * The KD-Tree point dimension (the length of each point in the datase, e.g.
+ * 3 for 3D points) is determined by means of:
+ * - The \a DIM template parameter if >0 (highest priority)
+ * - Otherwise, the \a dimensionality parameter of this constructor.
+ *
+ * @param inputData Dataset with the input features. Its lifetime must be
+ * equal or longer than that of the instance of this class.
+ * @param params Basically, the maximum leaf node size
+ *
+ * Note that there is a variable number of optional additional parameters
+ * which will be forwarded to the metric class constructor. Refer to example
+ * `examples/pointcloud_custom_metric.cpp` for a use case.
+ *
+ */
+ template
+ explicit KDTreeSingleIndexAdaptor(
+ const Dimension dimensionality, const DatasetAdaptor& inputData,
+ const KDTreeSingleIndexAdaptorParams& params, Args&&... args)
+ : dataset_(inputData),
+ indexParams(params),
+ distance_(inputData, std::forward(args)...)
+ {
+ init(dimensionality, params);
+ }
+
+ explicit KDTreeSingleIndexAdaptor(
+ const Dimension dimensionality, const DatasetAdaptor& inputData,
+ const KDTreeSingleIndexAdaptorParams& params = {})
+ : dataset_(inputData), indexParams(params), distance_(inputData)
+ {
+ init(dimensionality, params);
+ }
+
+ private:
+ void init(
+ const Dimension dimensionality,
+ const KDTreeSingleIndexAdaptorParams& params)
+ {
+ Base::size_ = dataset_.kdtree_get_point_count();
+ Base::size_at_index_build_ = Base::size_;
+ Base::dim_ = dimensionality;
+ if (DIM > 0) Base::dim_ = DIM;
+ Base::leaf_max_size_ = params.leaf_max_size;
+ if (params.n_thread_build > 0)
+ {
+ Base::n_thread_build_ = params.n_thread_build;
+ }
+ else
+ {
+ Base::n_thread_build_ =
+ std::max(std::thread::hardware_concurrency(), 1u);
+ }
+
+ if (!(params.flags &
+ KDTreeSingleIndexAdaptorFlags::SkipInitialBuildIndex))
+ {
+ // Build KD-tree:
+ buildIndex();
+ }
+ }
+
+ public:
+ /**
+ * Builds the index
+ */
+ void buildIndex()
+ {
+ Base::size_ = dataset_.kdtree_get_point_count();
+ Base::size_at_index_build_ = Base::size_;
+ init_vind();
+ this->freeIndex(*this);
+ Base::size_at_index_build_ = Base::size_;
+ if (Base::size_ == 0) return;
+ computeBoundingBox(Base::root_bbox_);
+ // construct the tree
+ if (Base::n_thread_build_ == 1)
+ {
+ Base::root_node_ =
+ this->divideTree(*this, 0, Base::size_, Base::root_bbox_);
+ }
+ else
+ {
+ std::atomic thread_count(0u);
+ std::mutex mutex;
+ Base::root_node_ = this->divideTreeConcurrent(
+ *this, 0, Base::size_, Base::root_bbox_, thread_count, mutex);
+ }
+ }
+
+ /** \name Query methods
+ * @{ */
+
+ /**
+ * Find set of nearest neighbors to vec[0:dim-1]. Their indices are stored
+ * inside the result object.
+ *
+ * Params:
+ * result = the result object in which the indices of the
+ * nearest-neighbors are stored vec = the vector for which to search the
+ * nearest neighbors
+ *
+ * \tparam RESULTSET Should be any ResultSet
+ * \return True if the requested neighbors could be found.
+ * \sa knnSearch, radiusSearch
+ *
+ * \note If L2 norms are used, all returned distances are actually squared
+ * distances.
+ */
+ template
+ bool findNeighbors(
+ RESULTSET& result, const ElementType* vec,
+ const SearchParameters& searchParams = {}) const
+ {
+ assert(vec);
+ if (this->size(*this) == 0) return false;
+ if (!Base::root_node_)
+ throw std::runtime_error(
+ "[nanoflann] findNeighbors() called before building the "
+ "index.");
+ float epsError = 1 + searchParams.eps;
+
+ // fixed or variable-sized container (depending on DIM)
+ distance_vector_t dists;
+ // Fill it with zeros.
+ auto zero = static_cast(0);
+ assign(dists, (DIM > 0 ? DIM : Base::dim_), zero);
+ DistanceType dist = this->computeInitialDistances(*this, vec, dists);
+ searchLevel(result, vec, Base::root_node_, dist, dists, epsError);
+ return result.full();
+ }
+
+ /**
+ * Find the "num_closest" nearest neighbors to the \a query_point[0:dim-1].
+ * Their indices and distances are stored in the provided pointers to
+ * array/vector.
+ *
+ * \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 knnSearch(
+ const ElementType* query_point, const Size num_closest,
+ IndexType* out_indices, DistanceType* out_distances) const
+ {
+ nanoflann::KNNResultSet resultSet(num_closest);
+ resultSet.init(out_indices, out_distances);
+ findNeighbors(resultSet, query_point);
+ return resultSet.size();
+ }
+
+ /**
+ * Find all the 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.
+ *
+ * If searchParams.sorted==true, the output list is sorted by ascending
+ * distances.
+ *
+ * For a better performance, it is advisable to do a .reserve() on the
+ * vector if you have any wild guess about the number of expected matches.
+ *
+ * \sa knnSearch, findNeighbors, radiusSearchCustomCallback
+ * \return The number of points within the given radius (i.e. indices.size()
+ * or dists.size() )
+ *
+ * \note If L2 norms are used, search radius and all returned distances
+ * are actually squared distances.
+ */
+ Size radiusSearch(
+ const ElementType* query_point, const DistanceType& radius,
+ std::vector>& IndicesDists,
+ const SearchParameters& searchParams = {}) const
+ {
+ RadiusResultSet resultSet(
+ radius, IndicesDists);
+ const Size nFound =
+ radiusSearchCustomCallback(query_point, resultSet, searchParams);
+ if (searchParams.sorted)
+ std::sort(
+ IndicesDists.begin(), IndicesDists.end(), IndexDist_Sorter());
+ return nFound;
+ }
+
+ /**
+ * Just like radiusSearch() but with a custom callback class for each point
+ * found in the radius of the query. See the source of RadiusResultSet<> as
+ * a start point for your own classes. \sa radiusSearch
+ */
+ template
+ Size radiusSearchCustomCallback(
+ const ElementType* query_point, SEARCH_CALLBACK& resultSet,
+ const SearchParameters& searchParams = {}) const
+ {
+ findNeighbors(resultSet, query_point, searchParams);
+ return resultSet.size();
+ }
+
+ /** @} */
+
+ public:
+ /** Make sure the auxiliary list \a vind has the same size than the current
+ * dataset, and re-generate if size has changed. */
+ void init_vind()
+ {
+ // Create a permutable array of indices to the input vectors.
+ Base::size_ = dataset_.kdtree_get_point_count();
+ if (Base::vAcc_.size() != Base::size_) Base::vAcc_.resize(Base::size_);
+ for (Size i = 0; i < Base::size_; i++) Base::vAcc_[i] = i;
+ }
+
+ void computeBoundingBox(BoundingBox& bbox)
+ {
+ const auto dims = (DIM > 0 ? DIM : Base::dim_);
+ resize(bbox, dims);
+ if (dataset_.kdtree_get_bbox(bbox))
+ {
+ // Done! It was implemented in derived class
+ }
+ else
+ {
+ const Size N = dataset_.kdtree_get_point_count();
+ if (!N)
+ throw std::runtime_error(
+ "[nanoflann] computeBoundingBox() called but "
+ "no data points found.");
+ for (Dimension i = 0; i < dims; ++i)
+ {
+ bbox[i].low = bbox[i].high =
+ this->dataset_get(*this, Base::vAcc_[0], i);
+ }
+ for (Offset k = 1; k < N; ++k)
+ {
+ for (Dimension i = 0; i < dims; ++i)
+ {
+ const auto val =
+ this->dataset_get(*this, Base::vAcc_[k], i);
+ if (val < bbox[i].low) bbox[i].low = val;
+ if (val > bbox[i].high) bbox[i].high = val;
+ }
+ }
+ }
+ }
+
+ /**
+ * Performs an exact search in the tree starting from a node.
+ * \tparam RESULTSET Should be any ResultSet
+ * \return true if the search should be continued, false if the results are
+ * sufficient
+ */
+ template
+ bool searchLevel(
+ RESULTSET& result_set, const ElementType* vec, const NodePtr node,
+ DistanceType mindist, distance_vector_t& dists,
+ const float epsError) const
+ {
+ /* If this is a leaf node, then do check and return. */
+ if ((node->child1 == nullptr) && (node->child2 == nullptr))
+ {
+ DistanceType worst_dist = result_set.worstDist();
+ for (Offset i = node->node_type.lr.left;
+ i < node->node_type.lr.right; ++i)
+ {
+ const IndexType accessor = Base::vAcc_[i]; // reorder... : i;
+ DistanceType dist = distance_.evalMetric(
+ vec, accessor, (DIM > 0 ? DIM : Base::dim_));
+ if (dist < worst_dist)
+ {
+ if (!result_set.addPoint(dist, Base::vAcc_[i]))
+ {
+ // the resultset doesn't want to receive any more
+ // points, we're done searching!
+ return false;
+ }
+ }
+ }
+ return true;
+ }
+
+ /* Which child branch should be taken first? */
+ Dimension idx = node->node_type.sub.divfeat;
+ ElementType val = vec[idx];
+ DistanceType diff1 = val - node->node_type.sub.divlow;
+ DistanceType diff2 = val - node->node_type.sub.divhigh;
+
+ NodePtr bestChild;
+ NodePtr otherChild;
+ DistanceType cut_dist;
+ if ((diff1 + diff2) < 0)
+ {
+ bestChild = node->child1;
+ otherChild = node->child2;
+ cut_dist =
+ distance_.accum_dist(val, node->node_type.sub.divhigh, idx);
+ }
+ else
+ {
+ bestChild = node->child2;
+ otherChild = node->child1;
+ cut_dist =
+ distance_.accum_dist(val, node->node_type.sub.divlow, idx);
+ }
+
+ /* Call recursively to search next level down. */
+ if (!searchLevel(
+ result_set, vec, bestChild, mindist, dists, epsError))
+ {
+ // the resultset doesn't want to receive any more points, we're done
+ // searching!
+ return false;
+ }
+
+ DistanceType dst = dists[idx];
+ mindist = mindist + cut_dist - dst;
+ dists[idx] = cut_dist;
+ if (mindist * epsError <= result_set.worstDist())
+ {
+ if (!searchLevel(
+ result_set, vec, otherChild, mindist, dists, epsError))
+ {
+ // the resultset doesn't want to receive any more points, we're
+ // done searching!
+ return false;
+ }
+ }
+ dists[idx] = dst;
+ return true;
+ }
+
+ public:
+ /** Stores the index in a binary file.
+ * IMPORTANT NOTE: The set of data points is NOT stored in the file, so
+ * when loading the index object it must be constructed associated to the
+ * same source of data points used while building it. See the example:
+ * examples/saveload_example.cpp \sa loadIndex */
+ void saveIndex(std::ostream& stream) const
+ {
+ Base::saveIndex(*this, stream);
+ }
+
+ /** Loads a previous index from a binary file.
+ * IMPORTANT NOTE: The set of data points is NOT stored in the file, so
+ * the index object must be constructed associated to the same source of
+ * data points used while building the index. See the example:
+ * examples/saveload_example.cpp \sa loadIndex */
+ void loadIndex(std::istream& stream) { Base::loadIndex(*this, stream); }
+
+}; // class KDTree
+
+/** kd-tree dynamic index
+ *
+ * Contains the k-d trees and other information for indexing a set of points
+ * for nearest-neighbor matching.
+ *
+ * The class "DatasetAdaptor" must provide the following interface (can be
+ * non-virtual, inlined methods):
+ *
+ * \code
+ * // Must return the number of data poins
+ * size_t kdtree_get_point_count() const { ... }
+ *
+ * // Must return the dim'th component of the idx'th point in the class:
+ * T kdtree_get_pt(const size_t idx, const size_t dim) const { ... }
+ *
+ * // Optional bounding-box computation: return false to default to a standard
+ * bbox computation loop.
+ * // Return true if the BBOX was already computed by the class and returned
+ * in "bb" so it can be avoided to redo it again.
+ * // Look at bb.size() to find out the expected dimensionality (e.g. 2 or 3
+ * for point clouds) template bool kdtree_get_bbox(BBOX &bb) const
+ * {
+ * bb[0].low = ...; bb[0].high = ...; // 0th dimension limits
+ * bb[1].low = ...; bb[1].high = ...; // 1st dimension limits
+ * ...
+ * return true;
+ * }
+ *
+ * \endcode
+ *
+ * \tparam DatasetAdaptor The user-provided adaptor (see comments above).
+ * \tparam Distance The distance metric to use: nanoflann::metric_L1,
+ * nanoflann::metric_L2, nanoflann::metric_L2_Simple, etc.
+ * \tparam DIM Dimensionality of data points (e.g. 3 for 3D points)
+ * \tparam IndexType Type of the arguments with which the data can be
+ * accessed (e.g. float, double, int64_t, T*)
+ */
+template <
+ typename Distance, class DatasetAdaptor, int32_t DIM = -1,
+ typename IndexType = uint32_t>
+class KDTreeSingleIndexDynamicAdaptor_
+ : public KDTreeBaseClass<
+ KDTreeSingleIndexDynamicAdaptor_<
+ Distance, DatasetAdaptor, DIM, IndexType>,
+ Distance, DatasetAdaptor, DIM, IndexType>
+{
+ public:
+ /**
+ * The dataset used by this index
+ */
+ const DatasetAdaptor& dataset_; //!< The source of our data
+
+ KDTreeSingleIndexAdaptorParams index_params_;
+
+ std::vector& treeIndex_;
+
+ Distance distance_;
+
+ using Base = typename nanoflann::KDTreeBaseClass<
+ nanoflann::KDTreeSingleIndexDynamicAdaptor_<
+ Distance, DatasetAdaptor, DIM, IndexType>,
+ Distance, DatasetAdaptor, DIM, IndexType>;
+
+ using ElementType = typename Base::ElementType;
+ using DistanceType = typename Base::DistanceType;
+
+ using Offset = typename Base::Offset;
+ using Size = typename Base::Size;
+ using Dimension = typename Base::Dimension;
+
+ using Node = typename Base::Node;
+ using NodePtr = Node*;
+
+ using Interval = typename Base::Interval;
+ /** Define "BoundingBox" as a fixed-size or variable-size container
+ * depending on "DIM" */
+ using BoundingBox = typename Base::BoundingBox;
+
+ /** Define "distance_vector_t" as a fixed-size or variable-size container
+ * depending on "DIM" */
+ using distance_vector_t = typename Base::distance_vector_t;
+
+ /**
+ * KDTree constructor
+ *
+ * Refer to docs in README.md or online in
+ * https://github.com/jlblancoc/nanoflann
+ *
+ * The KD-Tree point dimension (the length of each point in the datase, e.g.
+ * 3 for 3D points) is determined by means of:
+ * - The \a DIM template parameter if >0 (highest priority)
+ * - Otherwise, the \a dimensionality parameter of this constructor.
+ *
+ * @param inputData Dataset with the input features. Its lifetime must be
+ * equal or longer than that of the instance of this class.
+ * @param params Basically, the maximum leaf node size
+ */
+ KDTreeSingleIndexDynamicAdaptor_(
+ const Dimension dimensionality, const DatasetAdaptor& inputData,
+ std::vector& treeIndex,
+ const KDTreeSingleIndexAdaptorParams& params =
+ KDTreeSingleIndexAdaptorParams())
+ : dataset_(inputData),
+ index_params_(params),
+ treeIndex_(treeIndex),
+ distance_(inputData)
+ {
+ Base::size_ = 0;
+ Base::size_at_index_build_ = 0;
+ for (auto& v : Base::root_bbox_) v = {};
+ Base::dim_ = dimensionality;
+ if (DIM > 0) Base::dim_ = DIM;
+ Base::leaf_max_size_ = params.leaf_max_size;
+ if (params.n_thread_build > 0)
+ {
+ Base::n_thread_build_ = params.n_thread_build;
+ }
+ else
+ {
+ Base::n_thread_build_ =
+ std::max(std::thread::hardware_concurrency(), 1u);
+ }
+ }
+
+ /** Explicitly default the copy constructor */
+ KDTreeSingleIndexDynamicAdaptor_(
+ const KDTreeSingleIndexDynamicAdaptor_& rhs) = default;
+
+ /** Assignment operator definiton */
+ KDTreeSingleIndexDynamicAdaptor_ operator=(
+ const KDTreeSingleIndexDynamicAdaptor_& rhs)
+ {
+ KDTreeSingleIndexDynamicAdaptor_ tmp(rhs);
+ std::swap(Base::vAcc_, tmp.Base::vAcc_);
+ std::swap(Base::leaf_max_size_, tmp.Base::leaf_max_size_);
+ std::swap(index_params_, tmp.index_params_);
+ std::swap(treeIndex_, tmp.treeIndex_);
+ std::swap(Base::size_, tmp.Base::size_);
+ std::swap(Base::size_at_index_build_, tmp.Base::size_at_index_build_);
+ std::swap(Base::root_node_, tmp.Base::root_node_);
+ std::swap(Base::root_bbox_, tmp.Base::root_bbox_);
+ std::swap(Base::pool_, tmp.Base::pool_);
+ return *this;
+ }
+
+ /**
+ * Builds the index
+ */
+ void buildIndex()
+ {
+ Base::size_ = Base::vAcc_.size();
+ this->freeIndex(*this);
+ Base::size_at_index_build_ = Base::size_;
+ if (Base::size_ == 0) return;
+ computeBoundingBox(Base::root_bbox_);
+ // construct the tree
+ if (Base::n_thread_build_ == 1)
+ {
+ Base::root_node_ =
+ this->divideTree(*this, 0, Base::size_, Base::root_bbox_);
+ }
+ else
+ {
+ std::atomic thread_count(0u);
+ std::mutex mutex;
+ Base::root_node_ = this->divideTreeConcurrent(
+ *this, 0, Base::size_, Base::root_bbox_, thread_count, mutex);
+ }
+ }
+
+ /** \name Query methods
+ * @{ */
+
+ /**
+ * Find set of nearest neighbors to vec[0:dim-1]. Their indices are stored
+ * inside the result object.
+ * This is the core search function, all others are wrappers around this
+ * one.
+ *
+ * \param result The result object in which the indices of the
+ * nearest-neighbors are stored.
+ * \param vec The vector of the query point for which to search the
+ * nearest neighbors.
+ * \param searchParams Optional parameters for the search.
+ *
+ * \tparam RESULTSET Should be any ResultSet
+ * \return True if the requested neighbors could be found.
+ *
+ * \sa knnSearch(), radiusSearch(), radiusSearchCustomCallback()
+ *
+ * \note If L2 norms are used, all returned distances are actually squared
+ * distances.
+ */
+ template
+ bool findNeighbors(
+ RESULTSET& result, const ElementType* vec,
+ const SearchParameters& searchParams = {}) const
+ {
+ assert(vec);
+ if (this->size(*this) == 0) return false;
+ if (!Base::root_node_) return false;
+ float epsError = 1 + searchParams.eps;
+
+ // fixed or variable-sized container (depending on DIM)
+ distance_vector_t dists;
+ // Fill it with zeros.
+ assign(
+ dists, (DIM > 0 ? DIM : Base::dim_),
+ static_cast(0));
+ DistanceType dist = this->computeInitialDistances(*this, vec, dists);
+ searchLevel(result, vec, Base::root_node_, dist, dists, epsError);
+ return result.full();
+ }
+
+ /**
+ * Find the "num_closest" nearest neighbors to the \a query_point[0:dim-1].
+ * Their indices are stored inside the result object. \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 may be less than `num_closest` only if the
+ * number of elements in the tree is less than `num_closest`.
+ */
+ Size knnSearch(
+ const ElementType* query_point, const Size num_closest,
+ IndexType* out_indices, DistanceType* out_distances) const
+ {
+ nanoflann::KNNResultSet resultSet(num_closest);
+ resultSet.init(out_indices, out_distances);
+ findNeighbors(resultSet, query_point);
+ return resultSet.size();
+ }
+
+ /**
+ * Find all the 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.
+ *
+ * If searchParams.sorted==true, the output list is sorted by ascending
+ * distances.
+ *
+ * For a better performance, it is advisable to do a .reserve() on the
+ * vector if you have any wild guess about the number of expected matches.
+ *
+ * \sa knnSearch, findNeighbors, radiusSearchCustomCallback
+ * \return The number of points within the given radius (i.e. indices.size()
+ * or dists.size() )
+ *
+ * \note If L2 norms are used, search radius and all returned distances
+ * are actually squared distances.
+ */
+ Size radiusSearch(
+ const ElementType* query_point, const DistanceType& radius,
+ std::vector>& IndicesDists,
+ const SearchParameters& searchParams = {}) const
+ {
+ RadiusResultSet resultSet(
+ radius, IndicesDists);
+ const size_t nFound =
+ radiusSearchCustomCallback(query_point, resultSet, searchParams);
+ if (searchParams.sorted)
+ std::sort(
+ IndicesDists.begin(), IndicesDists.end(), IndexDist_Sorter());
+ return nFound;
+ }
+
+ /**
+ * Just like radiusSearch() but with a custom callback class for each point
+ * found in the radius of the query. See the source of RadiusResultSet<> as
+ * a start point for your own classes. \sa radiusSearch
+ */
+ template
+ Size radiusSearchCustomCallback(
+ const ElementType* query_point, SEARCH_CALLBACK& resultSet,
+ const SearchParameters& searchParams = {}) const
+ {
+ findNeighbors(resultSet, query_point, searchParams);
+ return resultSet.size();
+ }
+
+ /** @} */
+
+ public:
+ void computeBoundingBox(BoundingBox& bbox)
+ {
+ const auto dims = (DIM > 0 ? DIM : Base::dim_);
+ resize(bbox, dims);
+
+ if (dataset_.kdtree_get_bbox(bbox))
+ {
+ // Done! It was implemented in derived class
+ }
+ else
+ {
+ const Size N = Base::size_;
+ if (!N)
+ throw std::runtime_error(
+ "[nanoflann] computeBoundingBox() called but "
+ "no data points found.");
+ for (Dimension i = 0; i < dims; ++i)
+ {
+ bbox[i].low = bbox[i].high =
+ this->dataset_get(*this, Base::vAcc_[0], i);
+ }
+ for (Offset k = 1; k < N; ++k)
+ {
+ for (Dimension i = 0; i < dims; ++i)
+ {
+ const auto val =
+ this->dataset_get(*this, Base::vAcc_[k], i);
+ if (val < bbox[i].low) bbox[i].low = val;
+ if (val > bbox[i].high) bbox[i].high = val;
+ }
+ }
+ }
+ }
+
+ /**
+ * Performs an exact search in the tree starting from a node.
+ * \tparam RESULTSET Should be any ResultSet
+ */
+ template
+ void searchLevel(
+ RESULTSET& result_set, const ElementType* vec, const NodePtr node,
+ DistanceType mindist, distance_vector_t& dists,
+ const float epsError) const
+ {
+ /* If this is a leaf node, then do check and return. */
+ if ((node->child1 == nullptr) && (node->child2 == nullptr))
+ {
+ DistanceType worst_dist = result_set.worstDist();
+ for (Offset i = node->node_type.lr.left;
+ i < node->node_type.lr.right; ++i)
+ {
+ const IndexType index = Base::vAcc_[i]; // reorder... : i;
+ if (treeIndex_[index] == -1) continue;
+ DistanceType dist = distance_.evalMetric(
+ vec, index, (DIM > 0 ? DIM : Base::dim_));
+ if (dist < worst_dist)
+ {
+ if (!result_set.addPoint(
+ static_cast(dist),
+ static_cast(
+ Base::vAcc_[i])))
+ {
+ // the resultset doesn't want to receive any more
+ // points, we're done searching!
+ return; // false;
+ }
+ }
+ }
+ return;
+ }
+
+ /* Which child branch should be taken first? */
+ Dimension idx = node->node_type.sub.divfeat;
+ ElementType val = vec[idx];
+ DistanceType diff1 = val - node->node_type.sub.divlow;
+ DistanceType diff2 = val - node->node_type.sub.divhigh;
+
+ NodePtr bestChild;
+ NodePtr otherChild;
+ DistanceType cut_dist;
+ if ((diff1 + diff2) < 0)
+ {
+ bestChild = node->child1;
+ otherChild = node->child2;
+ cut_dist =
+ distance_.accum_dist(val, node->node_type.sub.divhigh, idx);
+ }
+ else
+ {
+ bestChild = node->child2;
+ otherChild = node->child1;
+ cut_dist =
+ distance_.accum_dist(val, node->node_type.sub.divlow, idx);
+ }
+
+ /* Call recursively to search next level down. */
+ searchLevel(result_set, vec, bestChild, mindist, dists, epsError);
+
+ DistanceType dst = dists[idx];
+ mindist = mindist + cut_dist - dst;
+ dists[idx] = cut_dist;
+ if (mindist * epsError <= result_set.worstDist())
+ {
+ searchLevel(
+ result_set, vec, otherChild, mindist, dists, epsError);
+ }
+ dists[idx] = dst;
+ }
+
+ public:
+ /** Stores the index in a binary file.
+ * IMPORTANT NOTE: The set of data points is NOT stored in the file, so
+ * when loading the index object it must be constructed associated to the
+ * same source of data points used while building it. See the example:
+ * examples/saveload_example.cpp \sa loadIndex */
+ void saveIndex(std::ostream& stream) { saveIndex(*this, stream); }
+
+ /** Loads a previous index from a binary file.
+ * IMPORTANT NOTE: The set of data points is NOT stored in the file, so
+ * the index object must be constructed associated to the same source of
+ * data points used while building the index. See the example:
+ * examples/saveload_example.cpp \sa loadIndex */
+ void loadIndex(std::istream& stream) { loadIndex(*this, stream); }
+};
+
+/** kd-tree dynaimic index
+ *
+ * class to create multiple static index and merge their results to behave as
+ * single dynamic index as proposed in Logarithmic Approach.
+ *
+ * Example of usage:
+ * examples/dynamic_pointcloud_example.cpp
+ *
+ * \tparam DatasetAdaptor The user-provided adaptor (see comments above).
+ * \tparam Distance The distance metric to use: nanoflann::metric_L1,
+ * nanoflann::metric_L2, nanoflann::metric_L2_Simple, etc. \tparam DIM
+ * Dimensionality of data points (e.g. 3 for 3D points) \tparam IndexType
+ * Will be typically size_t or int
+ */
+template <
+ typename Distance, class DatasetAdaptor, int32_t DIM = -1,
+ typename IndexType = uint32_t>
+class KDTreeSingleIndexDynamicAdaptor
+{
+ public:
+ using ElementType = typename Distance::ElementType;
+ using DistanceType = typename Distance::DistanceType;
+
+ using Offset = typename KDTreeSingleIndexDynamicAdaptor_<
+ Distance, DatasetAdaptor, DIM>::Offset;
+ using Size = typename KDTreeSingleIndexDynamicAdaptor_<
+ Distance, DatasetAdaptor, DIM>::Size;
+ using Dimension = typename KDTreeSingleIndexDynamicAdaptor_<
+ Distance, DatasetAdaptor, DIM>::Dimension;
+
+ protected:
+ Size leaf_max_size_;
+ Size treeCount_;
+ Size pointCount_;
+
+ /**
+ * The dataset used by this index
+ */
+ const DatasetAdaptor& dataset_; //!< The source of our data
+
+ /** treeIndex[idx] is the index of tree in which point at idx is stored.
+ * treeIndex[idx]=-1 means that point has been removed. */
+ std::vector treeIndex_;
+ std::unordered_set removedPoints_;
+
+ KDTreeSingleIndexAdaptorParams index_params_;
+
+ Dimension dim_; //!< Dimensionality of each data point
+
+ using index_container_t = KDTreeSingleIndexDynamicAdaptor_<
+ Distance, DatasetAdaptor, DIM, IndexType>;
+ std::vector index_;
+
+ public:
+ /** Get a const ref to the internal list of indices; the number of indices
+ * is adapted dynamically as the dataset grows in size. */
+ const std::vector& getAllIndices() const
+ {
+ return index_;
+ }
+
+ private:
+ /** finds position of least significant unset bit */
+ int First0Bit(IndexType num)
+ {
+ int pos = 0;
+ while (num & 1)
+ {
+ num = num >> 1;
+ pos++;
+ }
+ return pos;
+ }
+
+ /** Creates multiple empty trees to handle dynamic support */
+ void init()
+ {
+ using my_kd_tree_t = KDTreeSingleIndexDynamicAdaptor_<
+ Distance, DatasetAdaptor, DIM, IndexType>;
+ std::vector index(
+ treeCount_,
+ my_kd_tree_t(dim_ /*dim*/, dataset_, treeIndex_, index_params_));
+ index_ = index;
+ }
+
+ public:
+ Distance distance_;
+
+ /**
+ * KDTree constructor
+ *
+ * Refer to docs in README.md or online in
+ * https://github.com/jlblancoc/nanoflann
+ *
+ * The KD-Tree point dimension (the length of each point in the datase, e.g.
+ * 3 for 3D points) is determined by means of:
+ * - The \a DIM template parameter if >0 (highest priority)
+ * - Otherwise, the \a dimensionality parameter of this constructor.
+ *
+ * @param inputData Dataset with the input features. Its lifetime must be
+ * equal or longer than that of the instance of this class.
+ * @param params Basically, the maximum leaf node size
+ */
+ explicit KDTreeSingleIndexDynamicAdaptor(
+ const int dimensionality, const DatasetAdaptor& inputData,
+ const KDTreeSingleIndexAdaptorParams& params =
+ KDTreeSingleIndexAdaptorParams(),
+ const size_t maximumPointCount = 1000000000U)
+ : dataset_(inputData), index_params_(params), distance_(inputData)
+ {
+ treeCount_ = static_cast(std::log2(maximumPointCount)) + 1;
+ pointCount_ = 0U;
+ dim_ = dimensionality;
+ treeIndex_.clear();
+ if (DIM > 0) dim_ = DIM;
+ leaf_max_size_ = params.leaf_max_size;
+ init();
+ const size_t num_initial_points = dataset_.kdtree_get_point_count();
+ if (num_initial_points > 0) { addPoints(0, num_initial_points - 1); }
+ }
+
+ /** Deleted copy constructor*/
+ explicit KDTreeSingleIndexDynamicAdaptor(
+ const KDTreeSingleIndexDynamicAdaptor<
+ Distance, DatasetAdaptor, DIM, IndexType>&) = delete;
+
+ /** Add points to the set, Inserts all points from [start, end] */
+ void addPoints(IndexType start, IndexType end)
+ {
+ const Size count = end - start + 1;
+ int maxIndex = 0;
+ treeIndex_.resize(treeIndex_.size() + count);
+ for (IndexType idx = start; idx <= end; idx++)
+ {
+ const int pos = First0Bit(pointCount_);
+ maxIndex = std::max(pos, maxIndex);
+ treeIndex_[pointCount_] = pos;
+
+ const auto it = removedPoints_.find(idx);
+ if (it != removedPoints_.end())
+ {
+ removedPoints_.erase(it);
+ treeIndex_[idx] = pos;
+ }
+
+ for (int i = 0; i < pos; i++)
+ {
+ for (int j = 0; j < static_cast(index_[i].vAcc_.size());
+ j++)
+ {
+ index_[pos].vAcc_.push_back(index_[i].vAcc_[j]);
+ if (treeIndex_[index_[i].vAcc_[j]] != -1)
+ treeIndex_[index_[i].vAcc_[j]] = pos;
+ }
+ index_[i].vAcc_.clear();
+ }
+ index_[pos].vAcc_.push_back(idx);
+ pointCount_++;
+ }
+
+ for (int i = 0; i <= maxIndex; ++i)
+ {
+ index_[i].freeIndex(index_[i]);
+ if (!index_[i].vAcc_.empty()) index_[i].buildIndex();
+ }
+ }
+
+ /** Remove a point from the set (Lazy Deletion) */
+ void removePoint(size_t idx)
+ {
+ if (idx >= pointCount_) return;
+ removedPoints_.insert(idx);
+ treeIndex_[idx] = -1;
+ }
+
+ /**
+ * Find set of nearest neighbors to vec[0:dim-1]. Their indices are stored
+ * inside the result object.
+ *
+ * Params:
+ * result = the result object in which the indices of the
+ * nearest-neighbors are stored vec = the vector for which to search the
+ * nearest neighbors
+ *
+ * \tparam RESULTSET Should be any ResultSet
+ * \return True if the requested neighbors could be found.
+ * \sa knnSearch, radiusSearch
+ *
+ * \note If L2 norms are used, all returned distances are actually squared
+ * distances.
+ */
+ template
+ bool findNeighbors(
+ RESULTSET& result, const ElementType* vec,
+ const SearchParameters& searchParams = {}) const
+ {
+ for (size_t i = 0; i < treeCount_; i++)
+ { index_[i].findNeighbors(result, &vec[0], searchParams); }
+ return result.full();
+ }
+};
+
+/** An L2-metric KD-tree adaptor for working with data directly stored in an
+ * Eigen Matrix, without duplicating the data storage. You can select whether a
+ * row or column in the matrix represents a point in the state space.
+ *
+ * Example of usage:
+ * \code
+ * Eigen::Matrix mat;
+ *
+ * // Fill out "mat"...
+ * using my_kd_tree_t = nanoflann::KDTreeEigenMatrixAdaptor<
+ * Eigen::Matrix>;
+ *
+ * const int max_leaf = 10;
+ * my_kd_tree_t mat_index(mat, max_leaf);
+ * mat_index.index->...
+ * \endcode
+ *
+ * \tparam DIM If set to >0, it specifies a compile-time fixed dimensionality
+ * for the points in the data set, allowing more compiler optimizations.
+ * \tparam Distance The distance metric to use: nanoflann::metric_L1,
+ * nanoflann::metric_L2, nanoflann::metric_L2_Simple, etc.
+ * \tparam row_major If set to true the rows of the matrix are used as the
+ * points, if set to false the columns of the matrix are used as the
+ * points.
+ */
+template <
+ class MatrixType, int32_t DIM = -1, class Distance = nanoflann::metric_L2,
+ bool row_major = true>
+struct KDTreeEigenMatrixAdaptor
+{
+ using self_t =
+ KDTreeEigenMatrixAdaptor;
+ using num_t = typename MatrixType::Scalar;
+ using IndexType = typename MatrixType::Index;
+ using metric_t = typename Distance::template traits<
+ num_t, self_t, IndexType>::distance_t;
+
+ using index_t = KDTreeSingleIndexAdaptor<
+ metric_t, self_t,
+ row_major ? MatrixType::ColsAtCompileTime
+ : MatrixType::RowsAtCompileTime,
+ IndexType>;
+
+ index_t* index_; //! The kd-tree index for the user to call its methods as
+ //! usual with any other FLANN index.
+
+ using Offset = typename index_t::Offset;
+ using Size = typename index_t::Size;
+ using Dimension = typename index_t::Dimension;
+
+ /// Constructor: takes a const ref to the matrix object with the data points
+ explicit KDTreeEigenMatrixAdaptor(
+ const Dimension dimensionality,
+ const std::reference_wrapper& mat,
+ const int leaf_max_size = 10)
+ : m_data_matrix(mat)
+ {
+ const auto dims = row_major ? mat.get().cols() : mat.get().rows();
+ if (static_cast(dims) != dimensionality)
+ throw std::runtime_error(
+ "Error: 'dimensionality' must match column count in data "
+ "matrix");
+ if (DIM > 0 && static_cast(dims) != DIM)
+ throw std::runtime_error(
+ "Data set dimensionality does not match the 'DIM' template "
+ "argument");
+ index_ = new index_t(
+ dims, *this /* adaptor */,
+ nanoflann::KDTreeSingleIndexAdaptorParams(leaf_max_size));
+ }
+
+ public:
+ /** Deleted copy constructor */
+ KDTreeEigenMatrixAdaptor(const self_t&) = delete;
+
+ ~KDTreeEigenMatrixAdaptor() { delete index_; }
+
+ const std::reference_wrapper m_data_matrix;
+
+ /** Query for the \a num_closest closest points to a given point (entered as
+ * query_point[0:dim-1]). Note that this is a short-cut method for
+ * index->findNeighbors(). The user can also call index->... methods as
+ * desired.
+ *
+ * \note If L2 norms are used, all returned distances are actually squared
+ * distances.
+ */
+ void query(
+ const num_t* query_point, const Size num_closest,
+ IndexType* out_indices, num_t* out_distances) const
+ {
+ nanoflann::KNNResultSet resultSet(num_closest);
+ resultSet.init(out_indices, out_distances);
+ index_->findNeighbors(resultSet, query_point);
+ }
+
+ /** @name Interface expected by KDTreeSingleIndexAdaptor
+ * @{ */
+
+ const self_t& derived() const { return *this; }
+ self_t& derived() { return *this; }
+
+ // Must return the number of data points
+ Size kdtree_get_point_count() const
+ {
+ if (row_major)
+ return m_data_matrix.get().rows();
+ else
+ return m_data_matrix.get().cols();
+ }
+
+ // Returns the dim'th component of the idx'th point in the class:
+ num_t kdtree_get_pt(const IndexType idx, size_t dim) const
+ {
+ if (row_major)
+ return m_data_matrix.get().coeff(idx, IndexType(dim));
+ else
+ return m_data_matrix.get().coeff(IndexType(dim), idx);
+ }
+
+ // Optional bounding-box computation: return false to default to a standard
+ // bbox computation loop.
+ // Return true if the BBOX was already computed by the class and returned
+ // in "bb" so it can be avoided to redo it again. Look at bb.size() to
+ // find out the expected dimensionality (e.g. 2 or 3 for point clouds)
+ template
+ bool kdtree_get_bbox(BBOX& /*bb*/) const
+ {
+ return false;
+ }
+
+ /** @} */
+
+}; // end of KDTreeEigenMatrixAdaptor
+/** @} */
+
+/** @} */ // end of grouping
+} // namespace nanoflann
diff --git a/examples/cpp/nanoflann/ponca_nanoflann.cpp b/examples/cpp/nanoflann/ponca_nanoflann.cpp
new file mode 100644
index 000000000..4e14df1d5
--- /dev/null
+++ b/examples/cpp/nanoflann/ponca_nanoflann.cpp
@@ -0,0 +1,193 @@
+
+
+/*!
+\file examples/cpp/nanoflann/ponca_nanoflann.cpp
+\brief Demonstrate how to use Ponca with Nanoflann, and compare performances with built-in kdtree
+
+
+\author: Nicolas Mellado
+*/
+
+#include
+#include
+#include "./nanoflann.hpp"
+
+#include
+
+using namespace Ponca;
+
+
+// This class defines the input data format
+class MyPoint
+{
+public:
+ enum {Dim = 3};
+ typedef double Scalar;
+ typedef Eigen::Matrix VectorType;
+ typedef Eigen::Matrix MatrixType;
+
+ PONCA_MULTIARCH inline MyPoint(const VectorType& _pos = VectorType::Zero()) : m_pos(_pos) {}
+ PONCA_MULTIARCH inline const VectorType& pos() const { return m_pos; }
+ PONCA_MULTIARCH inline VectorType& pos() { return m_pos; }
+
+ // Generate points on a sphere of radius 1
+ static inline MyPoint Random()
+ {
+ return { VectorType::Random().normalized() * Eigen::internal::random(0.9,1.1) };
+ }
+
+private:
+ VectorType m_pos;
+};
+
+typedef MyPoint::Scalar Scalar;
+typedef MyPoint::VectorType VectorType;
+
+//! [Define Fit Type]
+using WeightFunc = DistWeightFunc > ;
+using FitType = Basket; // build a fitting object that does nothing
+//! [Define Fit Type]
+
+
+///// Nanoflann stuff
+struct NFPointCloud
+{
+ using coord_t = typename MyPoint::Scalar; //!< The type of each coordinate
+
+ const std::vector& pts;
+
+ inline NFPointCloud(const std::vector& _pts) : pts(_pts) {}
+
+ // Must return the number of data points
+ inline size_t kdtree_get_point_count() const { return pts.size(); }
+
+ // Returns the dim'th component of the idx'th point in the class:
+ // Since this is inlined and the "dim" argument is typically an immediate
+ // value, the
+ // "if/else's" are actually solved at compile time.
+ inline coord_t kdtree_get_pt(const size_t idx, const size_t dim) const
+ {
+ if (dim == 0)
+ return pts[idx].pos()[0];
+ else if (dim == 1)
+ return pts[idx].pos()[1];
+ else
+ return pts[idx].pos()[2];
+ }
+
+ // Optional bounding-box computation: return false to default to a standard
+ // bbox computation loop.
+ // Return true if the BBOX was already computed by the class and returned
+ // in "bb" so it can be avoided to redo it again. Look at bb.size() to
+ // find out the expected dimensionality (e.g. 2 or 3 for point clouds)
+ template
+ bool kdtree_get_bbox(BBOX& /* bb */) const
+ {
+ return false;
+ }
+};
+using my_kd_tree_t = nanoflann::KDTreeSingleIndexAdaptor<
+ nanoflann::L2_Simple_Adaptor< Scalar, NFPointCloud>, NFPointCloud, 3>;
+///// Enf of nanoflann stuff
+
+int test_raw(FitType& f, const std::vector& _vecs, VectorType _p)
+{
+ f.init(_p);
+ if(! (f.compute( _vecs ) == STABLE) )
+ std::cerr << "[raw] Something weird happened" << std::endl;
+ return f.getNumNeighbors();
+}
+
+int test_ponca_kdtree(FitType& f, const std::vector& _vecs, VectorType _p, const KdTree& tree, Scalar tmax){
+ f.init(_p);
+ if(! (
+ //! [Use Ponca KdTree]
+f.computeWithIds( tree.range_neighbors(_p, tmax), _vecs )
+ //! [Use Ponca KdTree]
+ == STABLE) )
+ std::cerr << "[ponca_kdtree] Something weird happened" << std::endl;
+ return f.getNumNeighbors();
+}
+
+int test_nanflann_kdtree(FitType& f, const std::vector& _vecs, VectorType _p, const my_kd_tree_t& tree, Scalar tmax)
+{
+ // radius search:
+ const Scalar squaredRadius = 1;
+ std::vector> indices_dists;
+ nanoflann::RadiusResultSet resultSet(
+ tmax*tmax, indices_dists);
+
+ tree.findNeighbors(resultSet, _p.data());
+
+ //! [Use NanoFlann KdTree]
+f.init(_p);
+// Compute
+auto res = Ponca::UNDEFINED;
+do {
+ for (const auto& r : resultSet.m_indices_dists){
+ f.addNeighbor(_vecs[r.first]);
+ }
+ res = f.finalize();
+} while ( res == NEED_OTHER_PASS );
+ //! [Use NanoFlann KdTree]
+
+ if(res != STABLE)
+ std::cerr << "[nanoflann_kdtree] Something weird happened" << std::endl;
+ return f.getNumNeighbors();
+}
+
+
+int main()
+{
+ using Scalar = typename MyPoint::Scalar;
+ // init random point cloud
+ int n = 100000;
+ std::vector vecs (n);
+ std::generate(vecs.begin(), vecs.end(), []() {return MyPoint::Random(); });
+
+ //! [Create Ponca KdTree]
+KdTree ponca_tree(vecs);
+ //! [Create Ponca KdTree]
+
+ //! [Create NanoFlann KdTree]
+NFPointCloud nfcloud(vecs);
+my_kd_tree_t mat_index(3, nfcloud);
+ //! [Create NanoFlann KdTree]
+
+ Scalar tmax = 0.2;
+
+ FitType fit;
+ fit.setWeightFunc(WeightFunc(tmax));
+
+ int nbrun = 1000;
+ std::vector queries (nbrun);
+ std::generate(queries.begin(), queries.end(), []() {return MyPoint::Random().pos(); });
+
+ int neiRaw {0}, neiPonca {0}, neiFlann {0};
+ auto start = std::chrono::system_clock::now();
+ for(int i = 0; i != nbrun; ++i)
+ neiRaw += test_raw(fit, vecs, queries[i]);
+ auto end = std::chrono::system_clock::now();
+ std::chrono::duration rawDiff = (end-start);
+
+ start = std::chrono::system_clock::now();
+ for(int i = 0; i != nbrun; ++i)
+ neiPonca += test_ponca_kdtree(fit, vecs, queries[i], ponca_tree, tmax);
+ end = std::chrono::system_clock::now();
+ std::chrono::duration poncaDiff = (end-start);
+
+ start = std::chrono::system_clock::now();
+ for(int i = 0; i != nbrun; ++i)
+ neiFlann += test_nanflann_kdtree(fit, vecs, queries[i], mat_index, tmax);
+ end = std::chrono::system_clock::now();
+ std::chrono::duration nanoflannDiff = (end-start);
+
+ std::cout << "Timings: " << "\n"
+ << "Raw : " << rawDiff.count() << "\n"
+ << "Ponca : " << poncaDiff.count() << "\n"
+ << "Nanoflann : " << nanoflannDiff.count() << "\n";
+ std::cout << "Number of neighbors: " << "\n"
+ << "Raw : " << neiRaw << "\n"
+ << "Ponca : " << neiPonca << "\n"
+ << "Nanoflann : " << neiFlann << "\n";
+}
\ No newline at end of file