// ____ ______ __ // / __ \ / ____// / // / /_/ // / / / // / ____// /___ / /___ PixInsight Class Library // /_/ \____//_____/ PCL 2.4.23 // ---------------------------------------------------------------------------- // pcl/SurfacePolynomial.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_SurfacePolynomial_h #define __PCL_SurfacePolynomial_h /// \file pcl/SurfacePolynomial.h #include #include #include #include #include namespace pcl { // ---------------------------------------------------------------------------- /*! * \class SurfacePolynomial * \brief Two-dimensional interpolating/approximating surface polynomial. * * %SurfacePolynomial implements interpolating surface polynomials for * arbitrarily distributed input nodes in two dimensions. * * Compared to surface splines (also known as thin plates), surface * polynomials are much more rigid, that is, much less adaptable to local * function variations. This can be a desirable property in some scenarios. An * important drawback of polynomials is that they are prone to oscillations, * especially for relatively high interpolation orders greater than three. An * important advantage is that they are fast even for very large data sets. * * \sa SurfaceSpline */ template class PCL_CLASS SurfacePolynomial { public: /*! * Represents a vector of coordinates, function values or polynomial * coefficients. */ typedef GenericVector vector_type; /*! * The numeric type used to represent coordinates, function values and * polynomial coefficients. */ typedef typename vector_type::scalar scalar; /*! * Default constructor. Constructs an empty, two-dimensional interpolating * surface polynomial of third degree. */ SurfacePolynomial() = default; /*! * Copy constructor. */ SurfacePolynomial( const SurfacePolynomial& ) = default; /*! * Move constructor. */ SurfacePolynomial( SurfacePolynomial&& ) = default; /*! * Virtual destructor. */ virtual ~SurfacePolynomial() { } /*! * Copy assignment operator. Returns a reference to this object. */ SurfacePolynomial& operator =( const SurfacePolynomial& ) = default; /*! * Move assignment operator. Returns a reference to this object. */ SurfacePolynomial& operator =( SurfacePolynomial&& ) = default; /*! * Returns true iff this surface polynomial is valid. A valid surface * polynomial has been initialized with a sufficient number of nodes. */ bool IsValid() const { return !m_polynomial.IsEmpty(); } /*! * Returns the degree of this surface polynomial. */ int Degree() const { return m_degree; } /*! * Sets the degree of this surface polynomial. * * \param degree Polynomial degree. Must be ≥ 1. * * Calling this member function implicitly resets this %SurfacePolynomial * object and destroys all internal working structures. * * High-degree polynomials, especially for degrees higher than three, tend * to oscillate and may generate artifacts in the interpolated surface. * * The default degree is 3. Recommended values are 2, 3 and 4. */ void SetDegree( int degree ) { PCL_PRECONDITION( degree >= 1 ) Clear(); m_degree = pcl::Max( 1, degree ); } /*! * Generation of a two-dimensional surface polynomial. * * \param x X node coordinates. * * \param y Y node coordinates. * * \param z Node values. * * \param n Number of nodes. Must be ≥ 3 * (3 nodes * 2 coordinates = six degrees of freedom). * * The input nodes can be arbitrarily distributed, and they don't need to * follow any specific order. However, all nodes must be distinct with * respect to the machine epsilon for the floating point type T. */ void Initialize( const T* x, const T* y, const T* z, int n ) { PCL_PRECONDITION( x != nullptr && y != nullptr && z != nullptr ) PCL_PRECONDITION( n > 2 ) if ( n < 3 ) throw Error( "At least three input nodes are required in SurfacePolynomial::Initialize()" ); Clear(); // Find mean coordinate values m_x0 = m_y0 = 0; for ( int i = 0; i < n; ++i ) { m_x0 += x[i]; m_y0 += y[i]; } m_x0 /= n; m_y0 /= n; // Find radius of unit circle m_r0 = 0; for ( int i = 0; i < n; ++i ) { double dx = m_x0 - x[i]; double dy = m_y0 - y[i]; double r = Sqrt( dx*dx + dy*dy ); if ( r > m_r0 ) m_r0 = r; } if ( 1 + m_r0 == 1 ) throw Error( "SurfacePolynomial::Initialize(): Empty or insignificant interpolation space" ); m_r0 = 1/m_r0; const int size = (m_degree + 1)*(m_degree + 2) >> 1; DMatrix M( 0.0, size, size ); DVector R( 0.0, size ); { // Transform coordinates to unit circle DVector X( n ), Y( n ); for ( int i = 0; i < n; ++i ) { X[i] = m_r0*(x[i] - m_x0); Y[i] = m_r0*(y[i] - m_y0); } GenericVector Z( n ); for ( int k = 0; k < n; ++k ) { Z[k] = DVector( size ); for ( int i = 0, l = 0; i <= m_degree; ++i ) for ( int j = 0; j <= m_degree-i; ++j, ++l ) Z[k][l] = PowI( X[k], i ) * PowI( Y[k], j ); } int n2 = n*n; for ( int i = 0; i < size; ++i ) { for ( int j = 0; j < size; ++j ) { for ( int k = 0; k < n; ++k ) M[i][j] += Z[k][i] * Z[k][j]; M[i][j] /= n2; } for ( int k = 0; k < n; ++k ) R[i] += z[k] * Z[k][i]; R[i] /= n2; } } for ( int i = 0; i < size; ++i ) { double pMi = M[i][i]; if ( M[i][i] != 0 ) { for ( int j = i; j < size; ++j ) M[i][j] /= pMi; R[i] /= pMi; } for ( int k = 1; i+k < size; ++k ) { double pMk = M[i+k][i]; if ( M[i+k][i] != 0 ) { for ( int j = i; j < size; ++j ) { M[i+k][j] /= pMk; M[i+k][j] -= M[i][j]; } R[i+k] /= pMk; R[i+k] -= R[i]; } } } m_polynomial = vector_type( size ); for ( int i = size; --i >= 0; ) { m_polynomial[i] = scalar( R[i] ); for ( int j = i; --j >= 0; ) R[j] -= M[j][i]*R[i]; } } /*! * Two-dimensional surface polynomial interpolation. Returns an interpolated * value at the specified \a x and \a y coordinates. */ T operator ()( double x, double y ) const { PCL_PRECONDITION( !m_polynomial.IsEmpty() ) double dx = m_r0*(x - m_x0); double dy = m_r0*(y - m_y0); double z = 0; double px = 1; for ( int i = 0, l = 0; ; px *= dx ) { double py = 1; for ( int j = 0; j <= m_degree-i; py *= dy, ++j, ++l ) z += m_polynomial[l]*px*py; if ( ++i > m_degree ) break; } return T( z ); } /*! * Resets this surface polynomial interpolation, deallocating all internal * working structures. */ void Clear() { m_polynomial.Clear(); } protected: double m_r0 = 1; // scaling factor for normalization of node coordinates double m_x0 = 0; // zero offset for normalization of X node coordinates double m_y0 = 0; // zero offset for normalization of Y node coordinates int m_degree = 3; // polynomial degree > 0 vector_type m_polynomial; // coefficients of the 2-D surface polynomial }; // ---------------------------------------------------------------------------- /*! * \class PointSurfacePolynomial * \brief Vector polynomial interpolation/approximation in two dimensions * * The template parameter P represents an interpolation point in two * dimensions. The type P must implement P::x and P::y data members accessible * from the current %PointSurfacePolynomial template specialization. These * members must provide the values of the horizontal and vertical coordinates, * respectively, of an interpolation point. In addition, the scalar types of * the P::x and P::y point members must support conversion to double semantics. */ template class PCL_CLASS PointSurfacePolynomial { public: /*! * Represents an interpolation point in two dimensions. */ typedef P point; /*! * Represents a sequence of interpolation points. */ typedef Array point_list; /*! * Represents a coordinate interpolating/approximating surface. */ typedef SurfacePolynomial surface; /*! * Default constructor. Yields an empty instance that cannot be used without * initialization. */ PointSurfacePolynomial() = default; /*! * Copy constructor. */ PointSurfacePolynomial( const PointSurfacePolynomial& ) = default; /*! * Move constructor. */ PointSurfacePolynomial( PointSurfacePolynomial&& ) = default; /*! * Constructs a %PointSurfacePolynomial object initialized for the * specified input data and interpolation parameters. * * See the corresponding Initialize() member function for a detailed * description of parameters. */ PointSurfacePolynomial( const point_list& P1, const point_list& P2, int degree = 3 ) { Initialize( P1, P2, degree ); } /*! * Constructs a %PointSurfacePolynomial object initialized with prescribed * point surface interpolations. * * See the corresponding Initialize() member function for a more detailed * description of parameters and their required conditions. */ PointSurfacePolynomial( const surface& Sx, const surface& Sy ) { Initialize( Sx, Sy ); } /*! * Copy assignment operator. Returns a reference to this object. */ PointSurfacePolynomial& operator =( const PointSurfacePolynomial& ) = default; /*! * Move assignment operator. Returns a reference to this object. */ PointSurfacePolynomial& operator =( PointSurfacePolynomial&& ) = default; /*! * Initializes this %PointSurfacePolynomial object for the specified * input data and interpolation parameters. * * \param P1 A sequence of distinct interpolation node points. * * \param P2 A sequence of interpolation values. For each point in * \a P1, the coordinates of its counterpart point in * \a P2 will be used as the interpolation node values in * the X and Y directions. * * \param degree Polynomial degree. Must be ≥ 1. The default value is * 3. See SurfacePolynomial::SetDegree() for a complete * description of this parameter. * * The input nodes can be arbitrarily distributed and don't need to follow * any specific order. However, all node points should be distinct with * respect to the machine epsilon for the floating point type used to * represent coordinates. * * See the SurfacePolynomial::Initialize() member function for a complete * description of this initialization process. */ void Initialize( const point_list& P1, const point_list& P2, int degree = 3 ) { PCL_PRECONDITION( P1.Length() >= 3 ) PCL_PRECONDITION( P1.Length() <= P2.Length() ) PCL_PRECONDITION( degree > 0 ) m_Sx.Clear(); m_Sy.Clear(); m_Sx.SetDegree( degree ); m_Sy.SetDegree( degree ); if ( P1.Length() < 3 || P2.Length() < 3 ) throw Error( "PointSurfacePolynomial::Initialize(): At least three input nodes must be specified." ); if ( P2.Length() < P1.Length() ) throw Error( "PointSurfacePolynomial::Initialize(): Incompatible point array lengths." ); DVector X( int( P1.Length() ) ), Y( int( P1.Length() ) ), Zx( int( P1.Length() ) ), Zy( int( P1.Length() ) ); for ( int i = 0; i < X.Length(); ++i ) { X[i] = P1[i].x; Y[i] = P1[i].y; Zx[i] = P2[i].x; Zy[i] = P2[i].y; } m_Sx.Initialize( X.Begin(), Y.Begin(), Zx.Begin(), X.Length() ); m_Sy.Initialize( X.Begin(), Y.Begin(), Zy.Begin(), X.Length() ); } /*! * Deallocates internal structures, yielding an empty object that cannot be * used before a new call to Initialize(). */ void Clear() { m_Sx.Clear(); m_Sy.Clear(); } /*! * Returns true iff this is a valid, initialized object ready for * interpolation. */ bool IsValid() const { return m_Sx.IsValid() && m_Sy.IsValid(); } /*! * Returns a reference to the internal object used for interpolation in the * X plane direction. */ const surface& SurfaceX() const { return m_Sx; } /*! * Returns a reference to the internal object used for interpolation in the * Y plane direction. */ const surface& SurfaceY() const { return m_Sy; } /*! * Returns an interpolated point at the specified coordinates. */ template DPoint operator ()( T x, T y ) const { return DPoint( m_Sx( x, y ), m_Sy( x, y ) ); } /*! * Returns an interpolated point at the given \a p.x and \a p.y coordinates. */ template DPoint operator ()( const GenericPoint& p ) const { return operator ()( p.x, p.y ); } private: /* * The surface interpolations in the X and Y plane directions. */ surface m_Sx, m_Sy; }; // ---------------------------------------------------------------------------- } // pcl #endif // __PCL_SurfacePolynomial_h // ---------------------------------------------------------------------------- // EOF pcl/SurfacePolynomial.h - Released 2022-03-12T18:59:29Z