diff --git a/CHANGELOG b/CHANGELOG index 5994db921..add95a281 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -6,7 +6,11 @@ Ponca changelog Current head (v.1.3 RC) - API - - [SpatialPartitioning] Change part of the kdtree API (#123) + - [spatialPartitioning] Change part of the kdtree API (#123) + - [spatialPartitioning] Refactor KdTree into KdTreeDense + KdTreeSparse (#129) + +- Docs + - [spatialPartitioning] Update KdTree docs to reflect the kdtree API refactor (#129) -------------------------------------------------------------------------------- v.1.2 diff --git a/Ponca/src/SpatialPartitioning/KdTree/Query/kdTreeKNearestQueries.h b/Ponca/src/SpatialPartitioning/KdTree/Query/kdTreeKNearestQueries.h index dfb03a7c4..7396cfdc3 100644 --- a/Ponca/src/SpatialPartitioning/KdTree/Query/kdTreeKNearestQueries.h +++ b/Ponca/src/SpatialPartitioning/KdTree/Query/kdTreeKNearestQueries.h @@ -25,7 +25,7 @@ class KdTreeKNearestQueryBase : public KdTreeQuery, public QueryType using QueryAccelType = KdTreeQuery; using Iterator = IteratorType; - inline KdTreeKNearestQueryBase(const KdTreeBase* kdtree, IndexType k, typename QueryType::InputType input) : + inline KdTreeKNearestQueryBase(const KdTreeImplBase* kdtree, IndexType k, typename QueryType::InputType input) : KdTreeQuery(kdtree), QueryType(k, input) { } public: diff --git a/Ponca/src/SpatialPartitioning/KdTree/Query/kdTreeNearestQueries.h b/Ponca/src/SpatialPartitioning/KdTree/Query/kdTreeNearestQueries.h index e6f6c3d53..b68a4186f 100644 --- a/Ponca/src/SpatialPartitioning/KdTree/Query/kdTreeNearestQueries.h +++ b/Ponca/src/SpatialPartitioning/KdTree/Query/kdTreeNearestQueries.h @@ -25,7 +25,7 @@ class KdTreeNearestQueryBase : public KdTreeQuery, public QueryType using QueryAccelType = KdTreeQuery; using Iterator = IteratorType; - KdTreeNearestQueryBase(const KdTreeBase* kdtree, typename QueryType::InputType input) : + KdTreeNearestQueryBase(const KdTreeImplBase* kdtree, typename QueryType::InputType input) : KdTreeQuery(kdtree), QueryType(input){} public: diff --git a/Ponca/src/SpatialPartitioning/KdTree/Query/kdTreeQuery.h b/Ponca/src/SpatialPartitioning/KdTree/Query/kdTreeQuery.h index 8580c409d..db82e7e2b 100644 --- a/Ponca/src/SpatialPartitioning/KdTree/Query/kdTreeQuery.h +++ b/Ponca/src/SpatialPartitioning/KdTree/Query/kdTreeQuery.h @@ -10,7 +10,7 @@ #include "../../../Common/Containers/stack.h" namespace Ponca { -template class KdTreeBase; +template class KdTreeImplBase; template class KdTreeQuery @@ -21,7 +21,7 @@ class KdTreeQuery using Scalar = typename DataPoint::Scalar; using VectorType = typename DataPoint::VectorType; - explicit inline KdTreeQuery(const KdTreeBase* kdtree) : m_kdtree( kdtree ), m_stack() {} + explicit inline KdTreeQuery(const KdTreeImplBase* kdtree) : m_kdtree( kdtree ), m_stack() {} protected: /// \brief Init stack for a new search @@ -30,7 +30,7 @@ class KdTreeQuery m_stack.push({0,0}); } - const KdTreeBase* m_kdtree { nullptr }; + const KdTreeImplBase* m_kdtree { nullptr }; Stack, 2 * Traits::MAX_DEPTH> m_stack; templatenodes(); - const auto& points = m_kdtree->points(); - const auto& indices = m_kdtree->samples(); + const auto& nodes = m_kdtree->nodes(); + const auto& points = m_kdtree->points(); - if (nodes.empty() || points.empty() || indices.empty()) + if (nodes.empty() || points.empty() || m_kdtree->sample_count() == 0) throw std::invalid_argument("Empty KdTree"); while(!m_stack.empty()) @@ -66,7 +65,7 @@ class KdTreeQuery prepareLeafTraversal(start, end); for(IndexType i=start; ipointFromSample(i); if(skipFunctor(idx)) continue; Scalar d = (point - points[idx].pos()).squaredNorm(); diff --git a/Ponca/src/SpatialPartitioning/KdTree/Query/kdTreeRangeQueries.h b/Ponca/src/SpatialPartitioning/KdTree/Query/kdTreeRangeQueries.h index 127077ab0..d05f62fba 100644 --- a/Ponca/src/SpatialPartitioning/KdTree/Query/kdTreeRangeQueries.h +++ b/Ponca/src/SpatialPartitioning/KdTree/Query/kdTreeRangeQueries.h @@ -30,7 +30,7 @@ class KdTreeRangeQueryBase : public KdTreeQuery, public QueryType friend Iterator; public: - KdTreeRangeQueryBase(const KdTreeBase* kdtree, Scalar radius, typename QueryType::InputType input) : + KdTreeRangeQueryBase(const KdTreeImplBase* kdtree, Scalar radius, typename QueryType::InputType input) : KdTreeQuery(kdtree), QueryType(radius, input){} public: diff --git a/Ponca/src/SpatialPartitioning/KdTree/kdTree.h b/Ponca/src/SpatialPartitioning/KdTree/kdTree.h index c791bc999..be04da413 100644 --- a/Ponca/src/SpatialPartitioning/KdTree/kdTree.h +++ b/Ponca/src/SpatialPartitioning/KdTree/kdTree.h @@ -11,6 +11,7 @@ #include #include #include +#include #include #include #include @@ -24,23 +25,46 @@ #include "Query/kdTreeRangeQueries.h" namespace Ponca { -template class KdTreeBase; +template class KdTreeImplBase; +template class KdTreeDenseBase; +template class KdTreeSparseBase; /*! - * \brief Public interface for KdTree datastructure. + * \brief Base type for default KdTree implementations * - * Provides default implementation of the KdTree + * \see KdTreeDefaultTraits for the default trait interface documentation. + * \see KdTreeImplBase for complete API + */ +template +using KdTreeImpl = KdTreeImplBase>; + +/*! + * \brief Public interface for dense KdTree datastructure. + * + * Provides default implementation of the dense KdTree * * \see KdTreeDefaultTraits for the default trait interface documentation. - * \see KdTreeBase for complete API + * \see KdTreeDenseBase for complete API */ template -using KdTree = KdTreeBase>; +using KdTreeDense = KdTreeDenseBase>; /*! - * \brief Customizable base class for KdTree datastructure + * \brief Public interface for sparse KdTree datastructure. * - * \see Ponca::KdTree + * Provides default implementation of the sparse KdTree + * + * \see KdTreeDefaultTraits for the default trait interface documentation. + * \see KdTreeSparseBase for complete API + */ +template +using KdTreeSparse = KdTreeSparseBase>; + +/*! + * \brief Customizable base class for KdTree datastructure implementations + * + * \see Ponca::KdTreeDense + * \see Ponca::KdTreeSparse * * \tparam Traits Traits type providing the types and constants used by the kd-tree. Must have the * same interface as the default traits type. @@ -50,16 +74,14 @@ using KdTree = KdTreeBase>; * \todo Better handle sampling: do not store non-selected points (requires to store original indices) */ template -class KdTreeBase +class KdTreeImplBase { public: - using DataPoint = typename Traits::DataPoint; ///< DataPoint given by user via Traits - using IndexType = typename Traits::IndexType; ///< Type used to index points into the PointContainer - using LeafSizeType = typename Traits::LeafSizeType; ///< Type used to store the size of leaf nodes - + using DataPoint = typename Traits::DataPoint; ///< DataPoint given by user via Traits + using IndexType = typename Traits::IndexType; ///< Type used to index points into the PointContainer + using LeafSizeType = typename Traits::LeafSizeType; ///< Type used to store the size of leaf nodes using PointContainer = typename Traits::PointContainer; ///< Container for DataPoint used inside the KdTree using IndexContainer = typename Traits::IndexContainer; ///< Container for indices used inside the KdTree - using NodeIndexType = typename Traits::NodeIndexType; ///< Type used to index nodes into the NodeContainer using NodeType = typename Traits::NodeType; ///< Type of nodes used inside the KdTree using NodeContainer = typename Traits::NodeContainer; ///< Container for nodes used inside the KdTree @@ -68,18 +90,15 @@ class KdTreeBase using VectorType = typename DataPoint::VectorType; ///< VectorType given by user via DataPoint using AabbType = typename NodeType::AabbType; ///< Bounding box type given by user via NodeType - enum - { - /*! - * \brief The maximum number of nodes that the kd-tree can have. - */ - MAX_NODE_COUNT = NodeType::MAX_COUNT, - - /*! - * \brief The maximum number of points that can be stored in the kd-tree. - */ - MAX_POINT_COUNT = std::size_t(2) << sizeof(IndexType)*8, - }; + /// \brief The maximum number of nodes that the kd-tree can have. + static constexpr std::size_t MAX_NODE_COUNT = NodeType::MAX_COUNT; + /// \brief The maximum number of points that can be stored in the kd-tree. + static constexpr std::size_t MAX_POINT_COUNT = std::size_t(2) << sizeof(IndexType)*8; + + /// \brief The maximum depth of the kd-tree. + static constexpr int MAX_DEPTH = Traits::MAX_DEPTH; + + static constexpr bool SUPPORTS_SUBSAMPLING = false; static_assert(std::is_same::value, "PointContainer must contain DataPoints"); @@ -90,47 +109,19 @@ class KdTreeBase static_assert(std::is_same::value, "Index type mismatch"); static_assert(std::is_same::value, "Node type mismatch"); - static_assert(Traits::MAX_DEPTH > 0, "Max depth must be strictly positive"); + static_assert(MAX_DEPTH > 0, "Max depth must be strictly positive"); - /// Default constructor creating an empty tree. \see build \see buildWithSampling - inline KdTreeBase(): - m_points(PointContainer()), - m_nodes(NodeContainer()), - m_indices(IndexContainer()), - m_min_cell_size(64), - m_leaf_count(0) - { - }; - - /// Constructor generating a tree from a custom contained type converted using DefaultConverter - template - inline KdTreeBase(PointUserContainer&& points): - m_points(PointContainer()), - m_nodes(NodeContainer()), - m_indices(IndexContainer()), - m_min_cell_size(64), - m_leaf_count(0) - { - this->build(std::forward(points)); - }; - - /// Constructor generating a tree sampled from a custom contained type converted using DefaultConverter - template - inline KdTreeBase(PointUserContainer&& points, IndexUserContainer sampling): // PointUserContainer => Given by user, transformed to PointContainer - // IndexUserContainer => Given by user, transformed to IndexContainer - m_points(), - m_nodes(), - m_indices(), - m_min_cell_size(64), - m_leaf_count(0) - { - buildWithSampling(std::forward(points), std::move(sampling)); - }; - - /// Clear tree data - inline void clear(); + // Construction ------------------------------------------------------------ +public: + /// Generate a tree from a custom contained type converted using the specified converter + /// \tparam PointUserContainer Input point container, transformed to PointContainer + /// \tparam IndexUserContainer Input sampling container, transformed to IndexContainer + /// \param points Input points + /// \param c Cast/Convert input point type to DataType + template + inline void build(PointUserContainer&& points, Converter c); - /// Convert a CustomPointContainer to the KdTree PointContainer using DataPoint default constructor + /// Convert a custom point container to the KdTree \ref PointContainer using \ref DataPoint default constructor struct DefaultConverter { template @@ -154,40 +145,8 @@ class KdTreeBase build(std::forward(points), DefaultConverter()); } - /// Generate a tree from a custom contained type converted using DefaultConverter - /// \tparam PointUserContainer Input point container, transformed to PointContainer - /// \tparam IndexUserContainer Input sampling container, transformed to IndexContainer - /// \param points Input points - /// \param c Cast/Convert input point type to DataType - template - inline void build(PointUserContainer&& points, Converter c); - - /// Generate a tree sampled from a custom contained type converted using DefaultConverter - /// \tparam PointUserContainer Input point, transformed to PointContainer - /// \tparam IndexUserContainer Input sampling, transformed to IndexContainer - /// \param points Input points - /// \param sampling Indices of points used in the tree - template - inline void buildWithSampling(PointUserContainer&& points, - IndexUserContainer sampling) - { - buildWithSampling(std::forward(points), std::move(sampling), DefaultConverter()); - } - - /// Generate a tree sampled from a custom contained type converted using DefaultConverter - /// \tparam PointUserContainer Input point, transformed to PointContainer - /// \tparam IndexUserContainer Input sampling, transformed to IndexContainer - /// \tparam Converter - /// \param points Input points - /// \param sampling Indices of points used in the tree - /// \param c Cast/Convert input point type to DataType - template - inline void buildWithSampling(PointUserContainer&& points, - IndexUserContainer sampling, - Converter c); - - inline bool valid() const; - inline void print(std::ostream& os, bool verbose = false) const; + /// Clear tree data + inline void clear(); // Accessors --------------------------------------------------------------- public: @@ -246,10 +205,29 @@ class KdTreeBase m_min_cell_size = min_cell_size; } - // Internal ---------------------------------------------------------------- -protected: - inline void build_rec(NodeIndexType node_id, IndexType start, IndexType end, int level); - inline IndexType partition(IndexType start, IndexType end, int dim, Scalar value); + // Index mapping ----------------------------------------------------------- +public: + /// Return the point index associated with the specified sample index + inline IndexType pointFromSample(IndexType sample_index) const + { + return m_indices[sample_index]; + } + + /// Return the \ref DataPoint associated with the specified sample index + /// \note Convenience function, equivalent to + /// `point_data()[pointFromSample(sample_index)]` + inline DataPoint& pointDataFromSample(IndexType sample_index) + { + return m_points[pointFromSample(sample_index)]; + } + + /// Return the \ref DataPoint associated with the specified sample index + /// \note Convenience function, equivalent to + /// `point_data()[pointFromSample(sample_index)]` + inline const DataPoint& pointDataFromSample(IndexType sample_index) const + { + return m_points[pointFromSample(sample_index)]; + } // Query ------------------------------------------------------------------- public : @@ -283,6 +261,10 @@ public : return KdTreeRangeIndexQuery(this, r, index); } + // Utilities --------------------------------------------------------------- +public: + inline bool valid() const; + inline void print(std::ostream& os, bool verbose = false) const; // Data -------------------------------------------------------------------- protected: @@ -292,13 +274,136 @@ public : LeafSizeType m_min_cell_size; ///< Minimal number of points per leaf NodeIndexType m_leaf_count; ///< Number of leaves in the Kdtree (computed during construction) + + // Internal ---------------------------------------------------------------- +protected: + inline KdTreeImplBase(): + m_points(), + m_nodes(), + m_indices(), + m_min_cell_size(64), + m_leaf_count(0) + { + } + + /// Generate a tree sampled from a custom contained type converted using a `Converter` + /// \tparam PointUserContainer Input point, transformed to PointContainer + /// \tparam IndexUserContainer Input sampling, transformed to IndexContainer + /// \tparam Converter + /// \param points Input points + /// \param sampling Indices of points used in the tree + /// \param c Cast/Convert input point type to DataType + template + inline void buildWithSampling(PointUserContainer&& points, + IndexUserContainer sampling, + Converter c); + + /// Generate a tree sampled from a custom contained type converted using a \ref KdTreeImplBase::DefaultConverter + /// \tparam PointUserContainer Input points, transformed to PointContainer + /// \tparam IndexUserContainer Input sampling, transformed to IndexContainer + /// \param points Input points + /// \param sampling Samples used in the tree + template + inline void buildWithSampling(PointUserContainer&& points, + IndexUserContainer sampling) + { + buildWithSampling(std::forward(points), std::move(sampling), DefaultConverter()); + } + +private: + inline void build_rec(NodeIndexType node_id, IndexType start, IndexType end, int level); + inline IndexType partition(IndexType start, IndexType end, int dim, Scalar value); +}; + +/*! + * \brief Customizable base class for dense KdTree datastructure + * + * This version of the KdTree does not support subsampling. For an + * implementation that supports subsampling, see \ref KdTreeSparseBase. + * + * \see Ponca::KdTreeDense + * \see Ponca::KdTreeSparse + * + * \tparam Traits Traits type providing the types and constants used by the kd-tree. Must have the + * same interface as the default traits type. + * + * \see KdTreeDefaultTraits for the trait interface documentation. + */ +template +class KdTreeDenseBase : public KdTreeImplBase +{ +private: + using Base = KdTreeImplBase; + +public: + /// Default constructor creating an empty tree + /// \see build + KdTreeDenseBase() = default; + + /// Constructor generating a tree from a custom contained type converted using a \ref KdTreeImplBase::DefaultConverter + template + inline explicit KdTreeDenseBase(PointUserContainer&& points) + : Base() + { + this->build(std::forward(points)); + } +}; + +/*! + * \brief Customizable base class for KdTreeSparse datastructure + * + * This version of the KdTree supports construction using a subset of samples. + * + * \see buildWithSampling + * \see Ponca::KdTreeDense + * \see Ponca::KdTreeSparse + * + * \tparam Traits Traits type providing the types and constants used by the kd-tree. Must have the + * same interface as the default traits type. + * + * \see KdTreeDefaultTraits for the trait interface documentation. + */ +template +class KdTreeSparseBase : public KdTreeImplBase +{ +private: + using Base = KdTreeImplBase; + +public: + static constexpr bool SUPPORTS_SUBSAMPLING = false; + + /// Default constructor creating an empty tree + /// \see build + KdTreeSparseBase() = default; + + /// Constructor generating a tree from a custom contained type converted using a \ref KdTreeImplBase::DefaultConverter + template + inline explicit KdTreeSparseBase(PointUserContainer&& points) + : Base() + { + this->build(std::forward(points)); + } + + /// Constructor generating a tree sampled from a custom contained type converted using a \ref KdTreeImplBase::DefaultConverter + /// \tparam PointUserContainer Input points, transformed to PointContainer + /// \tparam IndexUserContainer Input sampling, transformed to IndexContainer + /// \param point Input points + /// \param sampling Samples used in the tree + template + inline KdTreeSparseBase(PointUserContainer&& points, IndexUserContainer sampling) + : Base() + { + this->buildWithSampling(std::forward(points), std::move(sampling)); + } + + using Base::buildWithSampling; }; #include "./kdTree.hpp" } // namespace Ponca template -std::ostream& operator<<(std::ostream& os, const Ponca::KdTreeBase& kdtree) +std::ostream& operator<<(std::ostream& os, const Ponca::KdTreeImplBase& kdtree) { kdtree.print(os); return os; diff --git a/Ponca/src/SpatialPartitioning/KdTree/kdTree.hpp b/Ponca/src/SpatialPartitioning/KdTree/kdTree.hpp index bf6706764..6dd7f7f91 100644 --- a/Ponca/src/SpatialPartitioning/KdTree/kdTree.hpp +++ b/Ponca/src/SpatialPartitioning/KdTree/kdTree.hpp @@ -6,18 +6,9 @@ // KdTree ---------------------------------------------------------------------- -template -void KdTreeBase::clear() -{ - m_points.clear(); - m_nodes.clear(); - m_indices.clear(); - m_leaf_count = 0; -} - template template -inline void KdTreeBase::build(PointUserContainer&& points, Converter c) +inline void KdTreeImplBase::build(PointUserContainer&& points, Converter c) { IndexContainer ids(points.size()); std::iota(ids.begin(), ids.end(), 0); @@ -25,30 +16,16 @@ inline void KdTreeBase::build(PointUserContainer&& points, Converter c) } template -template -inline void KdTreeBase::buildWithSampling(PointUserContainer&& points, - IndexUserContainer sampling, - Converter c) +void KdTreeImplBase::clear() { - PONCA_DEBUG_ASSERT(points.size() <= MAX_POINT_COUNT); - this->clear(); - - // Move, copy or convert input samples - c(std::forward(points), m_points); - - m_nodes = NodeContainer(); - m_nodes.reserve(4 * point_count() / m_min_cell_size); - m_nodes.emplace_back(); - - m_indices = std::move(sampling); - - this->build_rec(0, 0, sample_count(), 1); - - PONCA_DEBUG_ASSERT(this->valid()); + m_points.clear(); + m_nodes.clear(); + m_indices.clear(); + m_leaf_count = 0; } template -bool KdTreeBase::valid() const +bool KdTreeImplBase::valid() const { if (m_points.empty()) return m_nodes.empty() && m_indices.empty(); @@ -95,12 +72,12 @@ bool KdTreeBase::valid() const } template -void KdTreeBase::print(std::ostream& os, bool verbose) const +void KdTreeImplBase::print(std::ostream& os, bool verbose) const { os << "KdTree:"; os << "\n MaxNodes: " << MAX_NODE_COUNT; os << "\n MaxPoints: " << MAX_POINT_COUNT; - os << "\n MaxDepth: " << Traits::MAX_DEPTH; + os << "\n MaxDepth: " << MAX_DEPTH; os << "\n PointCount: " << point_count(); os << "\n SampleCount: " << sample_count(); os << "\n NodeCount: " << node_count(); @@ -140,7 +117,30 @@ void KdTreeBase::print(std::ostream& os, bool verbose) const } template -void KdTreeBase::build_rec(NodeIndexType node_id, IndexType start, IndexType end, int level) +template +inline void KdTreeImplBase::buildWithSampling(PointUserContainer&& points, + IndexUserContainer sampling, + Converter c) +{ + PONCA_DEBUG_ASSERT(points.size() <= MAX_POINT_COUNT); + this->clear(); + + // Move, copy or convert input samples + c(std::forward(points), m_points); + + m_nodes = NodeContainer(); + m_nodes.reserve(4 * point_count() / m_min_cell_size); + m_nodes.emplace_back(); + + m_indices = std::move(sampling); + + this->build_rec(0, 0, sample_count(), 1); + + PONCA_DEBUG_ASSERT(this->valid()); +} + +template +void KdTreeImplBase::build_rec(NodeIndexType node_id, IndexType start, IndexType end, int level) { NodeType& node = m_nodes[node_id]; AabbType aabb; @@ -174,7 +174,7 @@ void KdTreeBase::build_rec(NodeIndexType node_id, IndexType start, Index } template -auto KdTreeBase::partition(IndexType start, IndexType end, int dim, Scalar value) +auto KdTreeImplBase::partition(IndexType start, IndexType end, int dim, Scalar value) -> IndexType { const auto& points = m_points; diff --git a/Ponca/src/SpatialPartitioning/KnnGraph/knnGraph.h b/Ponca/src/SpatialPartitioning/KnnGraph/knnGraph.h index a10066283..203a661c3 100644 --- a/Ponca/src/SpatialPartitioning/KnnGraph/knnGraph.h +++ b/Ponca/src/SpatialPartitioning/KnnGraph/knnGraph.h @@ -60,16 +60,15 @@ template class KnnGraphBase // knnGraph ---------------------------------------------------------------- public: - /// \brief Build a KnnGraph from a KdTree + /// \brief Build a KnnGraph from a KdTreeDense /// - /// \warning In the current version, the graph does not support kdtree with subsampling /// \param k Number of requested neighbors. Might be reduced if k is larger than the kdtree size - 1 /// (query point is not included in query output, thus -1) /// /// \warning Stores a const reference to kdtree.point_data() /// \warning KdTreeTraits compatibility is checked with static assertion template - inline KnnGraphBase(const KdTreeBase& kdtree, int k = 6) + inline KnnGraphBase(const KdTreeImplBase& kdtree, int k = 6) : m_k(std::min(k,kdtree.sample_count()-1)), m_kdTreePoints(kdtree.points()) { diff --git a/doc/src/ponca_module_spatialpartitioning.mdoc b/doc/src/ponca_module_spatialpartitioning.mdoc index 75ed60389..be8efcfb5 100644 --- a/doc/src/ponca_module_spatialpartitioning.mdoc +++ b/doc/src/ponca_module_spatialpartitioning.mdoc @@ -12,9 +12,9 @@ namespace Ponca \subsection spatialpartitioning_intro_structures Datastructures - - Ponca::KdTree : a binary search tree (https://en.wikipedia.org/wiki/K-d_tree) + - Ponca::KdTreeDense and Ponca::KdTreeSparse : a binary search tree (https://en.wikipedia.org/wiki/K-d_tree) - Ponca::KnnGraph : a nearest neighbor graph (https://en.wikipedia.org/wiki/Nearest_neighbor_graph). Constructed from - a Ponca::KdTree. + a KdTree. All datastructures are available in arbitrary dimensions. @@ -57,7 +57,9 @@ fit.computeWithIds( myDataStructure.range_neighbors(fitInitPos, scale), vectorPo \section spatialpartitioning_kdtree KdTree \subsection spatialpartitioning_kdtree_usage Basic usage - The class Ponca::KdTree provides methods to construct a tree and query points neighborhoods. + The class Ponca::KdTreeDense provides methods to construct a tree and query points neighborhoods. The class + Ponca::KdTreeSparse provides the same functionalities as its dense counterpart, but allows construction from a subset + of points. \subsubsection spatialpartitioning_kdtree_usage_construction Construction As for the other modules, the point type is defined by a template parameter `DataPoint`. @@ -67,13 +69,13 @@ fit.computeWithIds( myDataStructure.range_neighbors(fitInitPos, scale), vectorPo For convenience, it is possible to convert the custom input points to `DataPoint` using a converter. - Also, it is possible to provide a set of indices to sample a subset of the input point during the construction. + When using a Ponca::KdTreeSparse, it is possible to provide a set of indices to sample a subset of the input point + during the construction. Here, to randomly select half of the points: \snippet tests/src/queries_range.cpp Kdtree sampling construction - \subsubsection spatialpartitioning_kdtree_usage_queries Queries - As for other datastructures, queries are objects generated by Ponca::KdTree, and are designed as `Range`: accessing + As for other datastructures, queries are objects generated by the KdTree, and are designed as `Range`: accessing the neighbors requires to iterate over the query. Here an example from the test suite, where the indices returned from the queries are compared with an explicit search \snippet tests/src/queries_knearest.cpp Kdtree construction and query @@ -82,6 +84,20 @@ fit.computeWithIds( myDataStructure.range_neighbors(fitInitPos, scale), vectorPo - KdTreeKNearestQueryBase, specialized by KdTreeKNearestIndexQuery and KdTreeKNearestPointQuery - KdTreeNearestQueryBase, specialized by KdTreeNearestIndexQuery and KdTreeNearestPointQuery - KdTreeRangeQueryBase, specialized by KdTreeRangeIndexQuery and KdTreeRangePointQuery + + \subsubsection spatialpartitioning_kdtree_usage_samples_and_indexing Samples and indexing + There are two main ways of iterating over a KdTree: over points or over samples. Points are the underlying data + storage, which the KdTree does not modify, while samples are a set of indices into the point storage that the KdTree + reorders internally when it is constructed to keep track of spatial partitions. + + We call indices into the underlying point storage *point indices* and indices into the sample storage *sample + indices*. Note that the sample storage stores point indices: sample number 0 may, for example, refer to point number 25. + The point storage can be accessed with KdTreeImplBase::points, while the array of samples can be accessed with + KdTreeImplBase::samples. + + While most of the KdTree API is built on using point indices, it can still be useful to iterate over samples instead + (e.g. when using a Ponca::KdTreeSparse). If you ever need to convert sample indices to point indices, see + KdTreeImplBase::pointFromSample (see also KdTreeImplBase::pointDataFromSample). \subsection spatialpartitioning_kdtree_examples Examples @@ -94,7 +110,8 @@ fit.computeWithIds( myDataStructure.range_neighbors(fitInitPos, scale), vectorPo - `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`. + Ponca::KdTreeDenseBase and Ponca::KdTreeSparseBase are customizable versions of Ponca::KdTreeDense and + Ponca::KdTreeSparse, which can be controlled using `Traits`. See KdTreeDefaultTraits and KdTreeCustomizableNode for customization APIs. See also: - `examples/cpp/ponca_customize_kdtree.cpp` @@ -103,8 +120,8 @@ fit.computeWithIds( myDataStructure.range_neighbors(fitInitPos, scale), vectorPo The kd-tree is a binary search tree that - is balanced (cuts at the median at each node) - cuts along the dimension that extends the most at each node - - has a maximal depth (KdTreeDefaultTraits::MAX_DEPTH) - - has a minimal number of points per leaf (KdTreeBase::m_min_cell_size) + - has a maximal depth (KdTreeImplBase::MAX_DEPTH) + - has a minimal number of points per leaf (KdTreeImplBase::m_min_cell_size) - only stores points in the leafs - uses depth-first search with a static stack for queries - keeps the initial order of points @@ -114,14 +131,15 @@ fit.computeWithIds( myDataStructure.range_neighbors(fitInitPos, scale), vectorPo The class Ponca::KnnGraph provides methods to construct a neighbor graph and query points neighborhoods. \subsubsection spatialpartitioning_knngraph_usage_construction Construction - The graph is constructed from an existing Ponca::KdTree, and the point collection is accessed through it (with no - copy). Here an example from the test-suite where a graph is constructed, where only closest neighbors are connected: + The graph is constructed from an existing KdTree, and the point collection is accessed through it (with no copy). Here + an example from the test-suite where a graph is constructed, where only closest neighbors are connected: \snippet tests/src/queries_nearest.cpp KnnGraph construction - \warning KnnGraph can only be constructed from KdTree without sub-sampling. + \warning At the moment, a KnnGraph can only be constructed from a Ponca::KdTreeDense. This will change in a future + release. \subsubsection spatialpartitioning_knngraph_usage_queries Queries - As for other datastructures, queries are objects generated by Ponca::KdTree, and are designed as `Range`: accessing + As for other datastructures, queries are objects generated by KdTrees, and are designed as `Range`: accessing the neighbors requires to iterate over the query. \snippet tests/src/queries_knearest.cpp KnnGraph construction and query @@ -140,4 +158,4 @@ fit.computeWithIds( myDataStructure.range_neighbors(fitInitPos, scale), vectorPo
[\ref user_manual_page "Go back to user manual"]
*/ -} \ No newline at end of file +} diff --git a/examples/cpp/nanoflann/ponca_nanoflann.cpp b/examples/cpp/nanoflann/ponca_nanoflann.cpp index ff8f1bc50..1cf56248c 100644 --- a/examples/cpp/nanoflann/ponca_nanoflann.cpp +++ b/examples/cpp/nanoflann/ponca_nanoflann.cpp @@ -98,7 +98,7 @@ int test_raw(FitType& f, const std::vector& _vecs, VectorType _p) return f.getNumNeighbors(); } -int test_ponca_kdtree(FitType& f, const std::vector& _vecs, VectorType _p, const KdTree& tree, Scalar tmax){ +int test_ponca_kdtree(FitType& f, const std::vector& _vecs, VectorType _p, const KdTreeImpl& tree, Scalar tmax){ f.init(_p); if(! ( //! [Use Ponca KdTree] @@ -147,7 +147,7 @@ int main() std::generate(vecs.begin(), vecs.end(), []() {return MyPoint::Random(); }); //! [Create Ponca KdTree] -KdTree ponca_tree(vecs); +KdTreeDense ponca_tree(vecs); //! [Create Ponca KdTree] //! [Create NanoFlann KdTree] @@ -191,4 +191,4 @@ my_kd_tree_t mat_index(3, nfcloud); << "Raw : " << neiRaw << "\n" << "Ponca : " << neiPonca << "\n" << "Nanoflann : " << neiFlann << "\n"; -} \ No newline at end of file +} diff --git a/examples/cpp/ponca_basic_cpu.cpp b/examples/cpp/ponca_basic_cpu.cpp index 76ade052c..8a3f539e2 100644 --- a/examples/cpp/ponca_basic_cpu.cpp +++ b/examples/cpp/ponca_basic_cpu.cpp @@ -76,7 +76,7 @@ using Fit3 = BasketDiff< Fit1, FitSpaceDer, OrientedSphereDer, GLSDer, Curvature using Fit4 = Basket; template -void test_fit(Fit& _fit, const KdTree& tree, const VectorType& _p) +void test_fit(Fit& _fit, const KdTreeImpl& tree, const VectorType& _p) { Scalar tmax = 100.0; @@ -136,7 +136,7 @@ int main() p = vecs.at(0).pos(); - KdTree tree {vecs}; + KdTreeDense tree {vecs}; std::cout << "====================\nOrientedSphereFit:\n"; Fit1 fit1; diff --git a/examples/cpp/ponca_customize_kdtree.cpp b/examples/cpp/ponca_customize_kdtree.cpp index e8f430d8d..fea9193c3 100644 --- a/examples/cpp/ponca_customize_kdtree.cpp +++ b/examples/cpp/ponca_customize_kdtree.cpp @@ -61,7 +61,7 @@ int main() return DataPoint{100 * DataPoint::VectorType::Random()};}); //! [KdTreeTypeWithCustomNode] - using CustomKdTree = Ponca::KdTreeBase>; + using CustomKdTree = Ponca::KdTreeDenseBase>; //! [KdTreeTypeWithCustomNode] // build the k-d tree diff --git a/examples/cpp/ponca_neighbor_search.cpp b/examples/cpp/ponca_neighbor_search.cpp index d871689bd..1460f0f3d 100644 --- a/examples/cpp/ponca_neighbor_search.cpp +++ b/examples/cpp/ponca_neighbor_search.cpp @@ -26,7 +26,7 @@ int main() return DataPoint{100 * DataPoint::VectorType::Random()};}); // build the k-d tree - const Ponca::KdTree kdtree(points); + const Ponca::KdTreeDense kdtree(points); // neighbor searches are done below from these arbitrary index and point const int query_idx = 10; diff --git a/tests/src/basket.cpp b/tests/src/basket.cpp index 9f56e79c6..e7dc37e42 100644 --- a/tests/src/basket.cpp +++ b/tests/src/basket.cpp @@ -26,7 +26,7 @@ using namespace std; using namespace Ponca; template -typename DataPoint::Scalar generateData(KdTree& tree) +typename DataPoint::Scalar generateData(KdTreeImpl& tree) { typedef typename DataPoint::Scalar Scalar; typedef typename DataPoint::VectorType VectorType; @@ -61,7 +61,7 @@ typename DataPoint::Scalar generateData(KdTree& tree) } template -void testBasicFunctionalities(const KdTree& tree, typename Fit::Scalar analysisScale) +void testBasicFunctionalities(const KdTreeImpl& tree, typename Fit::Scalar analysisScale) { using DataPoint = typename Fit::DataPoint; @@ -124,7 +124,7 @@ void testBasicFunctionalities(const KdTree& tree, typen } template -void testIsSame(const KdTree& tree, +void testIsSame(const KdTreeImpl& tree, typename Fit1::Scalar analysisScale, Functor f) { @@ -230,7 +230,7 @@ void callSubTests() using HybridSpaceDiff = BasketDiff; using HybridScaleSpaceDiff = BasketDiff; - KdTreetree; + KdTreeDense tree; Scalar scale = generateData(tree); for(int i = 0; i < g_repeat; ++i) diff --git a/tests/src/queries_knearest.cpp b/tests/src/queries_knearest.cpp index fae8eb7fb..c8d8a12fc 100644 --- a/tests/src/queries_knearest.cpp +++ b/tests/src/queries_knearest.cpp @@ -18,7 +18,7 @@ template void testKdTreeKNearestIndex(bool quick = true) { using Scalar = typename DataPoint::Scalar; - using VectorContainer = typename KdTree::PointContainer; + using VectorContainer = typename KdTreeImpl::PointContainer; using VectorType = typename DataPoint::VectorType; const int N = quick ? 100 : 10000; @@ -28,7 +28,7 @@ void testKdTreeKNearestIndex(bool quick = true) auto kdStart = std::chrono::system_clock::now(); /// [Kdtree construction and query] - Ponca::KdTree kdTree(points); + Ponca::KdTreeDense kdTree(points); #pragma omp parallel for for (int i = 0; i < N; ++i) @@ -77,7 +77,7 @@ template void testKdTreeKNearestPoint(bool quick = true) { using Scalar = typename DataPoint::Scalar; - using VectorContainer = typename KdTree::PointContainer; + using VectorContainer = typename KdTreeImpl::PointContainer; using VectorType = typename DataPoint::VectorType; const int N = quick ? 100 : 10000; @@ -86,7 +86,7 @@ void testKdTreeKNearestPoint(bool quick = true) auto points = VectorContainer(N); std::generate(points.begin(), points.end(), []() {return DataPoint(VectorType::Random()); }); - KdTree structure(points); + KdTreeDense structure(points); /// [Kdtree construction] #pragma omp parallel for diff --git a/tests/src/queries_nearest.cpp b/tests/src/queries_nearest.cpp index c7e6fba53..5862a3bab 100644 --- a/tests/src/queries_nearest.cpp +++ b/tests/src/queries_nearest.cpp @@ -18,14 +18,14 @@ template void testKdTreeNearestIndex(bool quick = true) { using Scalar = typename DataPoint::Scalar; - using VectorContainer = typename KdTree::PointContainer; + using VectorContainer = typename KdTreeImpl::PointContainer; using VectorType = typename DataPoint::VectorType; const int N = quick ? 100 : 10000; auto points = VectorContainer(N); std::generate(points.begin(), points.end(), []() {return DataPoint(VectorType::Random()); }); - KdTree kdTree(points); + KdTreeDense kdTree(points); #pragma omp parallel for for (int i = 0; i < N; ++i) @@ -61,14 +61,14 @@ template void testKdTreeNearestPoint(bool quick = true) { using Scalar = typename DataPoint::Scalar; - using VectorContainer = typename KdTree::PointContainer; + using VectorContainer = typename KdTreeImpl::PointContainer; using VectorType = typename DataPoint::VectorType; const int N = quick ? 100 : 10000; auto points = VectorContainer(N); std::generate(points.begin(), points.end(), []() {return DataPoint(VectorType::Random()); }); - KdTree structure(points); + KdTreeDense structure(points); #pragma omp parallel for for (int i = 0; i < N; ++i) diff --git a/tests/src/queries_range.cpp b/tests/src/queries_range.cpp index 4926d8a29..eaec3af96 100644 --- a/tests/src/queries_range.cpp +++ b/tests/src/queries_range.cpp @@ -18,14 +18,14 @@ template void testKdTreeRangeIndex(bool quick = true) { using Scalar = typename DataPoint::Scalar; - using VectorContainer = typename KdTree::PointContainer; + using VectorContainer = typename KdTreeImpl::PointContainer; using VectorType = typename DataPoint::VectorType; const int N = quick ? 100 : 5000; auto points = VectorContainer(N); std::generate(points.begin(), points.end(), []() {return DataPoint(VectorType::Random()); }); - KdTree *kdtree {nullptr}; + KdTreeImpl *kdtree {nullptr}; std::vector sampling; // we need sampling for GT computation if(SampleKdTree){ @@ -37,15 +37,14 @@ void testKdTreeRangeIndex(bool quick = true) int seed = 0; std::sample(indices.begin(), indices.end(), sampling.begin(), N / 2, std::mt19937(seed)); - kdtree = new KdTree (points, sampling); + kdtree = new KdTreeSparse (points, sampling); } else { sampling.resize(N); std::iota(sampling.begin(), sampling.end(), 0); - kdtree = new KdTree (points); + kdtree = new KdTreeDense (points); } - #pragma omp parallel for for (int i = 0; i < N; ++i) { @@ -90,7 +89,7 @@ template void testKdTreeRangePoint(bool quick = true) { using Scalar = typename DataPoint::Scalar; - using VectorContainer = typename KdTree::PointContainer; + using VectorContainer = typename KdTreeImpl::PointContainer; using VectorType = typename DataPoint::VectorType; const int N = quick ? 100 : 10000; @@ -105,7 +104,7 @@ void testKdTreeRangePoint(bool quick = true) std::iota(indices.begin(), indices.end(), 0); std::sample(indices.begin(), indices.end(), sampling.begin(), N / 2, std::mt19937(seed)); - KdTree structure(points, sampling); + KdTreeSparse structure(points, sampling); /// [Kdtree sampling construction] #pragma omp parallel for