Files
tenmon/3rdparty/include/pcl/KDTree.h
T
2022-04-12 08:17:18 +02:00

608 lines
20 KiB
C++

// ____ ______ __
// / __ \ / ____// /
// / /_/ // / / /
// / ____// /___ / /___ PixInsight Class Library
// /_/ \____//_____/ PCL 2.4.23
// ----------------------------------------------------------------------------
// pcl/KDTree.h - Released 2022-03-12T18:59:29Z
// ----------------------------------------------------------------------------
// This file is part of the PixInsight Class Library (PCL).
// PCL is a multiplatform C++ framework for development of PixInsight modules.
//
// Copyright (c) 2003-2022 Pleiades Astrophoto S.L. All Rights Reserved.
//
// Redistribution and use in both source and binary forms, with or without
// modification, is permitted provided that the following conditions are met:
//
// 1. All redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
//
// 2. All 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.
//
// 3. Neither the names "PixInsight" and "Pleiades Astrophoto", nor the names
// of their contributors, may be used to endorse or promote products derived
// from this software without specific prior written permission. For written
// permission, please contact info@pixinsight.com.
//
// 4. All products derived from this software, in any form whatsoever, must
// reproduce the following acknowledgment in the end-user documentation
// and/or other materials provided with the product:
//
// "This product is based on software from the PixInsight project, developed
// by Pleiades Astrophoto and its contributors (https://pixinsight.com/)."
//
// Alternatively, if that is where third-party acknowledgments normally
// appear, this acknowledgment must be reproduced in the product itself.
//
// THIS SOFTWARE IS PROVIDED BY PLEIADES ASTROPHOTO AND ITS CONTRIBUTORS
// "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 PLEIADES ASTROPHOTO OR ITS
// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
// EXEMPLARY OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, BUSINESS
// INTERRUPTION; PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; AND LOSS OF USE,
// DATA OR PROFITS) 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.
// ----------------------------------------------------------------------------
#ifndef __PCL_KDTree_h
#define __PCL_KDTree_h
/// \file pcl/KDTree.h
#include <pcl/Defs.h>
#include <pcl/Array.h>
#include <pcl/Vector.h>
namespace pcl
{
// ----------------------------------------------------------------------------
/*!
* \class KDTree
* \brief Bucket PR K-d tree for point data in arbitrary dimensions.
*
* An n-dimensional K-d tree is a specialized binary tree for partitioning of
* a set of points in an n-dimensional space. K-d trees have important
* applications in computational geometry problems requiring efficient
* rectangular range searching.
*
* This class implements a <em>bucket point region K-d tree</em> structure
* (see Reference 2).
*
* The template type argument T represents the type of a \e point object stored
* in a %KDTree structure. The type T must have the following properties:
*
* \li The standard default and copy constructors are required:\n
* \n
* T::T() \n
* T::T( const T& )
*
* \li The \c T::component subtype must be defined. It represents a component
* of an object of type T. For example, if T is a vector type, T::component
* must be the type of a vector component.
*
* \li The array subscript operator must be defined as follows:\n
* \n
* component T::operator []( int i ) const \n
* \n
* This operator must return the value of the i-th component of an object being
* stored in the K-d tree, such that 0 <= i < N, where N > 0 is the dimension
* of the point space.
*
* \note We use this implementation of K-d trees in some essential PixInsight
* tools with success (e.g., StarAlignment), and hopefully it will be also
* useful for you, but we don't claim it to be complete. This is a practical
* and relatively simple implementation, where only the construction,
* destruction and range search operations are available. In particular, this
* implementation does not include point addition, deletion and iteration
* operations. Future versions of PCL will include more complete
* implementations of this fundamental data structure.
*
* \b References
*
* \li 1. Mark de Berg et al., <em>Computational Geometry: Algorithms and
* Applications Third Edition,</em> Springer, 2010, Section 5.2.
*
* \li 2. Hanan Samet, <em>Foundations of Multidimensional and Metric Data
* Structures,</em> Morgan Kaufmann, 2006, Section 1.5.
*
* \sa QuadTree
*/
template <class T>
class PCL_CLASS KDTree
{
public:
/*!
* Represents an N-dimensional point stored in this K-d tree.
*/
typedef T point;
/*!
* Represents a point component.
*/
typedef typename point::component component;
/*!
* A vector of point components. Used internally for tree build and range
* search operations.
*/
typedef GenericVector<component> component_vector;
/*!
* A list of points. Used for tree build and search operations.
*/
typedef Array<point> point_list;
/*!
* Constructs an empty K-d tree.
*/
KDTree() = default;
/*!
* Constructs a K-d tree and builds it for the specified list of \a points.
*
* \param points A list of points that will be stored in this
* K-d tree.
*
* \param bucketCapacity The maximum number of points in a leaf tree node.
* Must be >= 1. The default value is 16.
*
* The dimension of the point space is taken as the length of the first
* point in the list (by calling points[0].Length()), and must be > 0. All
* points in the \a points list must be able to provide at least
* \a dimension components through a zero-based array subscript operator.
*
* If the specified list of \a points is empty, this constructor yields an
* empty K-d tree. If the dimension of the point space is less than one, an
* Error exception is thrown.
*/
KDTree( const point_list& points, int bucketCapacity = 16 )
{
Build( points, bucketCapacity );
}
/*!
* Constructs a K-d tree of the specified \a dimension and builds it for a
* list of \a points.
*
* \param points A list of points that will be stored in this
* K-d tree.
*
* \param dimension The dimension of the point space. Must be > 0.
*
* \param bucketCapacity The maximum number of points in a leaf tree node.
* Must be >= 1.
*
* All points in the \a points list must be able to provide at least
* \a dimension components through a zero-based array subscript operator.
*
* If the specified list of \a points is empty, this constructor yields an
* empty K-d tree. If the dimension of the point space is less than one, an
* Error exception is thrown.
*/
KDTree( const point_list& points, int dimension, int bucketCapacity )
{
Build( points, dimension, bucketCapacity );
}
/*!
* Copy constructor. Copy construction is disabled because this class uses
* internal data structures that cannot be copy-constructed. However,
* %KDTree implements move construction and move assignment.
*/
KDTree( const KDTree& ) = delete;
/*!
* Copy assignment operator. Copy assignment is disabled because this class
* uses internal data structures that cannot be copy-assigned. However,
* %KDTree implements move assignment and move construction.
*/
KDTree& operator =( const KDTree& ) = delete;
/*!
* Move constructor.
*/
KDTree( KDTree&& x )
: m_root( x.m_root )
, m_dimension( x.m_dimension )
, m_bucketCapacity( x.m_bucketCapacity )
, m_length( x.m_length )
{
x.m_root = nullptr;
x.m_length = 0;
}
/*!
* Move assignment operator. Returns a reference to this object.
*/
KDTree& operator =( KDTree&& x )
{
if ( &x != this )
{
DestroyTree( m_root );
m_root = x.m_root;
m_dimension = x.m_dimension;
m_bucketCapacity = x.m_bucketCapacity;
m_length = x.m_length;
x.m_root = nullptr;
x.m_length = 0;
}
return *this;
}
/*!
* Destroys a K-d tree. All the stored point objects are deleted.
*/
~KDTree()
{
Clear();
}
/*!
* Removes all the stored point objects, yielding an empty K-d tree.
*/
void Clear()
{
DestroyTree( m_root );
m_root = nullptr;
m_length = 0;
}
/*!
* Builds a new K-d tree for the specified list of \a points.
*
* \param points A list of points that will be stored in this
* K-d tree.
*
* \param bucketCapacity The maximum number of points in a leaf tree node.
* Must be >= 1. The default value is 16.
*
* The dimension of the point space is taken as the length of the first
* point in the list (by calling points[0].Length()), and must be > 0. All
* points in the \a points list must be able to provide at least
* \a dimension components through a zero-based array subscript operator.
*
* If the tree stores point objects before calling this function, they are
* destroyed and removed before building a new tree.
*
* If the specified list of \a points is empty, this member function yields
* an empty K-d tree. If the dimension of the point space is less than one,
* an Error exception is thrown.
*/
void Build( const point_list& points, int bucketCapacity = 16 )
{
Clear();
m_bucketCapacity = Max( 1, bucketCapacity );
if ( !points.IsEmpty() )
{
m_dimension = points[0].Length();
if ( m_dimension < 1 )
throw Error( "KDTree::Build(): Invalid point space dimension." );
m_root = BuildTree( points, 0 );
}
}
/*!
* Builds a new K-d tree of the specified \a dimension for a list of
* \a points.
*
* \param points A list of points that will be stored in this
* K-d tree.
*
* \param dimension The dimension of the point space. Must be > 0.
*
* \param bucketCapacity The maximum number of points in a leaf tree node.
* Must be >= 1.
*
* All points in the \a points list must be able to provide at least
* \a dimension components through a zero-based array subscript operator.
*
* If the tree stores point objects before calling this function, they are
* destroyed and removed before building a new tree.
*
* If the specified list of \a points is empty, this member function yields
* an empty K-d tree. If the dimension of the point space is less than one,
* an Error exception is thrown.
*/
void Build( const point_list& points, int dimension, int bucketCapacity )
{
Clear();
m_bucketCapacity = Max( 1, bucketCapacity );
if ( (m_dimension = dimension) < 1 )
throw Error( "KDTree::Build(): Invalid point space dimension." );
m_root = BuildTree( points, 0 );
}
/*!
* Performs a range search in this K-d tree.
*
* \param pt Reference to the point being searched for. The coordinates
* of this point define the center of the hyper-rectangular
* search range in the N-dimensional point space.
*
* \param epsilon Search tolerance, or half-side of the search
* hyperrectangle.
*
* Returns a (possibly empty) list with all the points found in the tree
* within the search range. In two dimensions, the search range would be the
* rectangle defined by the points:
*
* (pt[0] - epsilon, pt[1] - epsilon) and \n
* (pt[0] + epsilon, pt[1] + epsilon)
*
* with an obvious extension to higher dimensions. If \a epsilon is zero,
* the search can only return the set of stored points that are identical to
* the specified search point.
*/
point_list Search( const point& pt, component epsilon ) const
{
component_vector p0( m_dimension );
component_vector p1( m_dimension );
for ( int i = 0; i < m_dimension; ++i )
{
p0[i] = pt[i] - epsilon;
p1[i] = pt[i] + epsilon;
}
point_list found;
SearchTree( found, p0, p1, m_root, 0 );
return found;
}
/*!
* Performs a range search in this K-d tree.
*
* \param pt Reference to the point being searched for. The
* coordinates of this point define the center of the
* hyper-rectangular search range in the N-dimensional
* point space.
*
* \param epsilon Search tolerance, or half-side of the search
* hyperrectangle.
*
* \param callback Callback functional.
*
* \param data Callback data.
*
* The callback function prototype should be:
*
* \code void callback( const point& pt, void* data ) \endcode
*
* The callback function will be called once for each point found in the
* tree within the specified search range. In two dimensions, the search
* range would be the rectangle defined by the points:
*
* (pt[0] - epsilon, pt[1] - epsilon) and \n
* (pt[0] + epsilon, pt[1] + epsilon)
*
* with an obvious extension to higher dimensions. If \a epsilon is zero,
* the search can only return the set of stored points that are identical to
* the specified search point.
*/
template <class F>
void Search( const point& pt, component epsilon, F callback, void* data ) const
{
component_vector p0( m_dimension );
component_vector p1( m_dimension );
for ( int i = 0; i < m_dimension; ++i )
{
p0[i] = pt[i] - epsilon;
p1[i] = pt[i] + epsilon;
}
SearchTree( p0, p1, callback, data, m_root, 0 );
}
/*!
* Returns the dimension of this K-d tree. This is the number of components
* in a point stored in the tree.
*/
int Dimension() const
{
return m_dimension;
}
/*!
* Returns the total number of points stored in this K-d tree.
*/
size_type Length() const
{
return m_length;
}
/*!
* Returns true iff this K-d tree is empty.
*/
bool IsEmpty()
{
return m_root == nullptr;
}
/*!
* Exchanges two %KDTree objects \a x1 and \a x2.
*/
friend void Swap( KDTree& x1, KDTree& x2 )
{
pcl::Swap( x1.m_root, x2.m_root );
pcl::Swap( x1.m_dimension, x2.m_dimension );
pcl::Swap( x1.m_bucketCapacity, x2.m_bucketCapacity );
pcl::Swap( x1.m_length, x2.m_length );
}
private:
struct Node
{
double split = 0; // position of this node's splitting hyperplane
Node* left = nullptr; // child points at coordinates <= split
Node* right = nullptr; // child points at coordinates > split
Node( double s = 0 )
: split( s )
{
}
bool IsLeaf() const
{
return left == nullptr && right == nullptr;
}
};
struct LeafNode : public Node
{
point_list points;
LeafNode( const point_list& p )
: points( p )
{
}
};
Node* m_root = nullptr;
int m_dimension = 0;
int m_bucketCapacity = 0;
size_type m_length = 0;
LeafNode* NewLeafNode( const point_list& points )
{
m_length += points.Length();
return new LeafNode( points );
}
Node* BuildTree( const point_list& points, int depth )
{
if ( points.IsEmpty() )
return nullptr;
if ( points.Length() <= size_type( m_bucketCapacity ) )
return NewLeafNode( points );
int index = depth % m_dimension;
Node* node = new Node( SplitValue( points, index ) );
point_list left, right;
for ( const point& p : points )
if ( p[index] <= node->split )
left.Add( p );
else
right.Add( p );
// If we are about to build a degenerate subtree, abort this branch of
// recursion right now.
if ( left.IsEmpty() || right.IsEmpty() )
{
delete node;
return NewLeafNode( points );
}
node->left = BuildTree( left, depth+1 );
node->right = BuildTree( right, depth+1 );
// Further degeneracies cannot happen in theory, but let's prevent them
// for extra safety.
if ( node->IsLeaf() )
{
delete node;
return NewLeafNode( points );
}
return node;
}
void SearchTree( point_list& found, const component_vector& p0, const component_vector& p1, const Node* node, int depth ) const
{
if ( node != nullptr )
if ( node->IsLeaf() )
{
const LeafNode* leaf = static_cast<const LeafNode*>( node );
for ( const point& p : leaf->points )
for ( int j = 0; ; )
{
component x = p[j];
if ( x < p0[j] || p1[j] < x )
break;
if ( ++j == m_dimension )
{
found.Add( p );
break;
}
}
}
else
{
int index = depth % m_dimension;
if ( p0[index] <= node->split )
SearchTree( found, p0, p1, node->left, depth+1 );
if ( p1[index] > node->split )
SearchTree( found, p0, p1, node->right, depth+1 );
}
}
template <class F>
void SearchTree( const component_vector& p0, const component_vector& p1, F callback, void* data, const Node* node, int depth ) const
{
if ( node != nullptr )
if ( node->IsLeaf() )
{
const LeafNode* leaf = static_cast<const LeafNode*>( node );
for ( const point& p : leaf->points )
for ( int j = 0; ; )
{
component x = p[j];
if ( x < p0[j] || p1[j] < x )
break;
if ( ++j == m_dimension )
{
callback( p, data );
break;
}
}
}
else
{
int index = depth % m_dimension;
if ( p0[index] <= node->split )
SearchTree( p0, p1, callback, data, node->left, depth+1 );
if ( p1[index] > node->split )
SearchTree( p0, p1, callback, data, node->right, depth+1 );
}
}
void DestroyTree( Node* node )
{
if ( node != nullptr )
if ( node->IsLeaf() )
delete static_cast<LeafNode*>( node );
else
{
DestroyTree( node->left );
DestroyTree( node->right );
delete node;
}
}
double SplitValue( const point_list& points, int index )
{
component_vector v( points.Length() );
for ( int i = 0; i < v.Length(); ++i )
v[i] = points[i][index];
return v.Median();
}
};
// ----------------------------------------------------------------------------
} // pcl
#endif // __PCL_KDTree_h
// ----------------------------------------------------------------------------
// EOF pcl/KDTree.h - Released 2022-03-12T18:59:29Z