// ____ ______ __ // / __ \ / ____// / // / /_/ // / / / // / ____// /___ / /___ PixInsight Class Library // /_/ \____//_____/ PCL 2.4.23 // ---------------------------------------------------------------------------- // pcl/SurfaceSimplifier.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_SurfaceSimplifier_h #define __PCL_SurfaceSimplifier_h /// \file pcl/SurfaceSimplifier.h #include #include #include #include namespace pcl { // ---------------------------------------------------------------------------- /* * Default tolerance for surface simplification. * * The tolerance parameter determines the maximum absolute deviation of * function values from a plane allowed to simplify the subset of points within * a subregion of the input point space. This parameter is specified in * bivariate function values, i.e. in Z-axis units. */ #define __PCL_SURFACE_SIMPLIFIER_DEFAULT_TOLERANCE 0.01 /* * Default state of the surface simplification outlier rejection feature. * * When enabled, a prescribed fraction of outlier points will be rejected on * each subregion for estimation of local curvature. */ #define __PCL_SURFACE_SIMPLIFIER_DEFAULT_REJECTION_ENABLED true /* * Default rejection fraction for surface simplification. * * This parameter specifies the maximum fraction of outliers allowed for * simplification of a subset of points in a region of the input point space. * Point rejection makes the surface simplification algorithm robust to outlier * function values. */ #define __PCL_SURFACE_SIMPLIFIER_DEFAULT_REJECT_FRACTION 0.2F /* * Whether to include subregion centroids in simplified point sets. * * When enabled, the centroid of each simplified subregion will also be * included in the corresponding list of simplified points. This can improve * the shape preservation behavior of the surface simplification algorithm, at * the cost of a reduced amount of additional points. */ #define __PCL_SURFACE_SIMPLIFIER_DEFAULT_INCLUDE_CENTROIDS false // ---------------------------------------------------------------------------- /*! * \class SurfaceSimplifier * \brief Shape-preserving simplification of 2-D surfaces * * Given a finite set of three dimensional points representing sampled values * of a real bivariate function of the form * * z = f(x,y), * * the shape-preserving surface simplification algorithm attempts to generate a * reduced set of points with equivalent geometric properties to within a * prescribed maximum error parameter. * * The implemented algorithm divides the input point space recursively on the * XY plane into rectangular regions using custom quadtree structures. For each * region, the algorithm finds the orientation of the dominant plane through * principal component analysis. The deviation of function values from the * dominant plane is evaluated for the points in the region, and if the region * is considered flat to within a tolerance parameter, its points are replaced * with a simplified (reduced) set of points that tends to preserve the local * shape of the original function over the region. If the region is tagged as * curve, it is further divided using a new quadtree recursion, until no * additional simplification can be achieved. * * Surface simplification is an important auxiliary tool to improve the * practical application of surface interpolation and approximation devices. * These algorithms allow us to work with large-scale data sets by selecting a * subset of essential data points, usually much smaller than the original set, * adapted to solve a particular problem. Surface simplification is * particularly useful for the application of computationally expensive * approximation algorithms, such as surface splines or thin plates. A good * example is computation of high accuracy astrometric solutions, where surface * simplification allows us to use large sets of thousands of stars to generate * thin plate models of local distortions. Since generation of thin plates has * roughly O(N^3) time complexity, the efficient reduction of input point sets * is crucial for this application. */ class PCL_CLASS SurfaceSimplifier { private: /*! * \struct SurfaceSimplifier::point * \brief Working 3-D point structure for quadtree storage used by the * recursive surface simplification algorithm. * \internal */ struct point { typedef double component; component x, y, z; point() = default; point( const point& ) = default; template point( T a_x, T a_y, T a_z ) : x( double( a_x ) ) , y( double( a_y ) ) , z( double( a_z ) ) { } template point( T k ) { x = y = z = double( k ); } component operator[]( int i ) const { return i ? y : x; } point& operator +=( const point& p ) { x += p.x; y += p.y; z += p.z; return *this; } template point& operator /=( T k ) { x /= k; y /= k; z /= k; return *this; } double SquaredDistanceTo( const point& p ) const { double dx = p.x - x, dy = p.y - y; return dx*dx + dy*dy; } }; typedef QuadTree tree; typedef typename tree::rectangle rectangle; typedef typename tree::point_list point_list; public: /*! * Constructs a new %SurfaceSimplifier object with default parameters: * * \li Tolerance = 0.01 in function value units (Z-axis values). * \li Outlier rejection enabled * \li Outlier rejection fraction = 0.2 * \li Inclusion of centroid points disabled */ SurfaceSimplifier() = default; /*! * Constructs a new %SurfaceSimplifier instance with the specified * \a tolerance. */ SurfaceSimplifier( double tolerance ) : m_tolerance( Abs( tolerance ) ) { PCL_PRECONDITION( tolerance >= 0 ) } /*! * Copy constructor. */ SurfaceSimplifier( const SurfaceSimplifier& ) = default; /*! * Returns the current tolerance of this surface simplifier. * * See the SetTolerance() member function for a description of the * tolerance parameter. */ double Tolerance() const { return m_tolerance; } /*! * Sets the \a tolerance of this surface simplifier. * * The tolerance parameter determines the maximum absolute deviation of * function values from a plane allowed to simplify the subset of points * within a subregion of the input point space. * * The value of this parameter is specified in bivariate function value * units, i.e. in Z-axis units. Higher tolerances tend to allow for more * simplification, and hence for shorter simplified point lists. However, an * excessive tolerance value may degrade the accuracy of the simplified * surface in terms of preservation of the original function's structure and * shape. This parameter must be tailored carefully to the requirements of * the function being simplified and the tasks where the simplified version * is going to be applied. */ void SetTolerance( double tolerance ) { PCL_PRECONDITION( tolerance > 0 ) m_tolerance = Abs( tolerance ); } /*! * Returns true iff outlier rejection is enabled for this object. * * See the EnableRejection() and RejectFraction() member functions for a * description of outlier rejection in the surface simplification algorithm. */ bool IsRejectionEnabled() const { return m_enableRejection; } /*! * Enables outlier rejection for this surface simplifier. * * When enabled, a prescribed fraction of outlier points (see the * RejectFraction() member function) will be rejected on each subregion for * estimation of local curvature. An adequate amount of rejection is * important to achieve a robust result, especially for simplification of * noisy data where outliers may generate false curvatures that prevent * efficient simplification. */ void EnableRejection( bool enabled = true ) { m_enableRejection = enabled; } /*! * Disables outlier rejection for this surface simplifier. See * EnableRejection() for more information. */ void DisableRejection( bool disable = true ) { EnableRejection( !disable ); } /*! * Returns the fraction of outlier points rejected for estimation of local * curvature. * * See the EnableRejection() and RejectFraction() member functions for a * description of outlier rejection in the surface simplification algorithm. */ float RejectFraction() const { return m_rejectFraction; } /*! * Sets the fraction of outlier points rejected by this surface simplifier. * * This parameter defines a fraction of outlier points that will be rejected * on each subregion of the point space being simplified, for estimation of * local curvature. * * Rejecting an adequate fraction of points makes the surface simplification * algorithm more immune to noise in the input data, including erroneous * points that may deviate considerably from the true surface represented by * the sampled function. The result is a more robust and accurate simplified * surface. However, too high of a rejection fraction may remove significant * data and lead to an inaccurate result. * * The specified \a rejectFraction value must be in the (0,1) range. The * default value upon construction is 0.2, which is quite appropriate in * most cases. */ void SetRejectFraction( float rejectFraction ) { PCL_PRECONDITION( rejectFraction > 0 && rejectFraction < 1 ) m_rejectFraction = Range( rejectFraction, 0.0F, 1.0F ); } /*! * Returns true iff inclusion of centroid points is enabled for this object. * * See the EnableCentroidInclusion() member function for a description of * the centroid inclusion feature of the surface simplification algorithm. */ bool IsCentroidInclusionEnabled() const { return m_includeCentroids; } /*! * Enables inclusion of centroid points for this surface simplifier. * * When a subregion of the input space is simplified, the surface * simplification algorithm replaces the subset of points in the subregion * with a simplified, reduced set. If this option is enabled, the average * point of the subset, also known as \e centroid, is also included in the * simplified point list. This usually improves the shape preservation * behavior of the algorithm, at the cost of a small amount of additional * points in the simplified point list. */ void EnableCentroidInclusion( bool enable = true ) { m_includeCentroids = enable; } /*! * Disables inclusion of centroid points for this surface simplifier. See * EnableCentroidInclusion() for more information. */ void DisableCentroidInclusion( bool disable = true ) { EnableCentroidInclusion( !disable ); } /*! * Attempts to simplify a set of points given by its separate coordinates * and function values. * * \param[out] xs The X coordinates of the simplified point set. * * \param[out] ys The Y coordinates of the simplified point set. * * \param[out] zs The function values for the simplified point set. * * \param x The X coordinates of the input point set. * * \param y The Y coordinates of the input point set. * * \param z The function values for the input point set. * * The template argument C must be a direct container of numeric scalars * with random access semantics, such as Array or GenericVector. * * If the specified surface can be simplified with the current working * parameters defined for this object, the output containers will have less * elements (usually \e much less) than the input containers. Otherwise an * exact copy of the input containers will be obtained in \a xs, \a ys and * \a zs. This will happen also if the input containers have less than four * coordinates, since a triangular facet cannot be simplified. */ template void Simplify( C& xs, C& ys, C& zs, const C& x, const C& y, const C& z ) const { int n = int( Min( Min( x.Length(), y.Length() ), z.Length() ) ); if ( n < 4 ) { xs = x; ys = y; zs = z; return; } point_list P; for ( int i = 0; i < n; ++i ) P << point( x[i], y[i], z[i] ); tree R( P, n ); P = Simplify( R ); if ( int( P.Length() ) >= n ) { xs = x; ys = y; zs = z; return; } R.Build( P, n ); P = Simplify( R ); n = int( P.Length() ); xs = C( n ); ys = C( n ); zs = C( n ); for ( int i = 0; i < n; ++i ) { xs[i] = typename C::item_type( P[i].x ); ys[i] = typename C::item_type( P[i].y ); zs[i] = typename C::item_type( P[i].z ); } } private: double m_tolerance = __PCL_SURFACE_SIMPLIFIER_DEFAULT_TOLERANCE; float m_rejectFraction = __PCL_SURFACE_SIMPLIFIER_DEFAULT_REJECT_FRACTION; bool m_enableRejection = __PCL_SURFACE_SIMPLIFIER_DEFAULT_REJECTION_ENABLED; bool m_includeCentroids = __PCL_SURFACE_SIMPLIFIER_DEFAULT_INCLUDE_CENTROIDS; /*! * Recursive part of the shape-preserving surface simplification algorithm. * Returns the simplified point list for the set of points stored in the * local quadtree \a R. * \internal */ point_list Simplify( tree& R ) const { point_list Q; R.Traverse( [&Q,this]( const rectangle& rect, point_list& points, void*& ) { int n = int( points.Length() ); if ( n < 4 ) // cannot simplify triangles { Q << points; return; } /* * Compute local centroid coordinates. */ point p0( 0 ); for ( const point& p : points ) p0 += p; p0 /= n; /* * Form the covariance matrix. */ double xx = 0, xy = 0, xz = 0, yy = 0, yz = 0, zz = 0; for ( const point& p : points ) { double dx = p.x - p0.x; double dy = p.y - p0.y; double dz = p.z - p0.z; xx += dx*dx; xy += dx*dy; xz += dx*dz; yy += dy*dy; yz += dy*dz; zz += dz*dz; } int n1 = n - 1; xx /= n1; xy /= n1; xz /= n1; yy /= n1; yz /= n1; zz /= n1; Matrix M( xx, xy, xz, xy, yy, yz, xz, yz, zz ); /* * Test all local function values against the plane fitted at the * centroid, with optional outlier rejection. The plane normal * vector is the least eigenvector, from which we can form the * plane equation: ax + by + cz = 0. */ ComputeEigenvectors( M ); double a = M[0][0]; double b = M[1][0]; double c = M[2][0]; int mr = m_enableRejection ? Max( 1, TruncInt( m_rejectFraction*n ) ) : 1; // max. number of outliers int nr = 0; point_list P = points; for ( point& p : P ) { double z = p0.z - (a*(p.x - p0.x) + b*(p.y - p0.y))/c; if ( Abs( p.z - z ) > m_tolerance ) { if ( ++nr == mr ) { /* * If the current region deviates from the fitted plane * more than allowed by the tolerance parameter after * outlier rejection, try to simplify it further with a * deeper quadtree subdivision. */ tree R( points, TruncInt( 0.75*n ) ); Q << Simplify( R ); return; } // Winsorize outlier function values. p.z = (p.z > z) ? z + m_tolerance : z - m_tolerance; } } /* * If the current region is flat to within the tolerance parameter, * take the convex hull as its simplified point set. This is what * makes our simplification algorithm shape-preserving. */ if ( m_includeCentroids ) { // If one or more points have been rejected, calculate a new // centroid function value from Winsorized z coordinates. if ( nr > 0 ) { p0.z = 0; for ( const point& p : P ) p0.z += p.z; p0.z /= n; } Q << p0; } Q << ConvexHull( P ); } ); return Q; } /*! * \internal * Compute the eigenvectors of a 3x3 symmetric matrix. */ static void ComputeEigenvectors( Matrix& ); /*! * \internal * Compute the convex hull of a point set. */ static point_list ConvexHull( point_list& ); }; // ---------------------------------------------------------------------------- } // pcl #endif // __PCL_SurfaceSimplifier_h // ---------------------------------------------------------------------------- // EOF pcl/SurfaceSimplifier.h - Released 2022-03-12T18:59:29Z