test_ls_affine.cpp

Back to Least-squares transformations

test/pastel/geometry/

// Description: Testing for least-squares transformations
// DocumentationOf: ls_affine.h

#include "test/test_init.h"

#include <pastel/geometry/pattern_matching/ls_affine.h>
#include <pastel/math/sampling/random_orthogonal.h>
#include <pastel/sys/random.h>

namespace
{

    template <typename Real>
    void testRandom()
    {
        integer trials = 400;
        Real threshold = 
            std::is_same<Real, float>::value ? 1e-4 : 1e-11;

        // Randomly chosen cases.

        integer fails = 0;
        for (integer k = 0; k < trials;++k)
        {
            integer d = randomInteger(10) + 1;
            integer n = randomInteger(100) + 10;
            arma::Mat<Real> W(n, n, arma::fill::eye);
            W *= random<Real>() * 10;

            LsAffine_Matrix matrix = randomElement({
                LsAffine_Matrix::Free, 
                LsAffine_Matrix::Identity});

            LsAffine_Scaling scaling = randomElement({
                LsAffine_Scaling::Free, 
                LsAffine_Scaling::Diagonal,
                LsAffine_Scaling::Conformal, 
                LsAffine_Scaling::Rigid});

            LsAffine_Translation translation = randomElement({
                LsAffine_Translation::Free, 
                LsAffine_Translation::Identity});

            integer orientation = randomElement({-1, 0, 1});

            if (scaling == LsAffine_Scaling::Free || 
                (matrix == LsAffine_Matrix::Identity &&
                scaling != LsAffine_Scaling::Rigid))
            {
                // Orientation can not be forced when scaling is free or
                // the matrix Q is identity and scaling is not rigid.
                orientation = 0;
            }

            if (matrix == LsAffine_Matrix::Free &&
                scaling == LsAffine_Scaling::Diagonal)
            {
                // This is not implemented.
                continue;
            }

            if (orientation < 0 &&
                matrix == LsAffine_Matrix::Identity &&
                scaling == LsAffine_Scaling::Rigid &&
                even(d))
            {
                // When Q = I and S = +/- I, a negative
                // det(QS) is only possible in odd dimensions.
                orientation = randomElement({0, 1});
            }

            arma::Mat<Real> Q(d, d, arma::fill::eye);

            if (matrix == LsAffine_Matrix::Free)
            {
                Q = randomOrthogonal<Real>(d, 
                    PASTEL_TAG(orientation), orientation);
            }

            arma::Mat<Real> S(d, d, arma::fill::eye);
            if (scaling == LsAffine_Scaling::Free)
            {
                S = arma::randn<arma::Mat<Real>>(d, d);
                S = S + S.t();
                if (matrix == LsAffine_Matrix::Free)
                {
                    arma::Mat<Real> U, V;
                    arma::Col<Real> D;
                    arma::svd(U, D, V, Q * S);
                    Q = U * V.t();
                    S = V * arma::diagmat(D) * V.t();
                }
            }

            if (scaling == LsAffine_Scaling::Conformal)
            {
                S *= random<Real>() * 10 + 1;
            }

            if (scaling == LsAffine_Scaling::Diagonal)
            {
                for (integer i = 0; i < d; ++i)
                {
                    S(i, i) *= random<Real>() * 10 + 1;
                }    
            }

            arma::Col<Real> t(d, arma::fill::zeros);
            if (translation == LsAffine_Translation::Free)
            {
                t = arma::randn<arma::Col<Real>>(d, 1) * 10;
            }

            if (orientation < 0)
            {
                if (matrix == LsAffine_Matrix::Identity)
                {
                    ASSERT(scaling != LsAffine_Scaling::Diagonal);
                    ASSERT(scaling != LsAffine_Scaling::Free);

                    if (scaling == LsAffine_Scaling::Rigid)
                    {
                        S = -S;
                    }
                }
                else
                {
                    // The orientation has already been
                    // handled for the free case in the
                    // generation of Q.
                }
            }

            // Generate test point-sets.
            arma::Mat<Real> P = arma::randn<arma::Mat<Real>>(d, n);
            arma::Mat<Real> R = Q * S * P + t * arma::ones<arma::Mat<Real>>(1, n);

            arma::Mat<Real> QE(d, d);
            arma::Mat<Real> SE(d, d);
            arma::Col<Real> tE(d);

            const Real* qePointer = QE.memptr();
            const Real* sePointer = SE.memptr();
            const Real* tePointer = tE.memptr();

            // Compute the transformation back by least-squares.
            auto lsMatch = lsAffine(
                P, R,
                PASTEL_TAG(orientation), orientation,
                matrix,
                scaling,
                translation,
                PASTEL_TAG(W), W,
                PASTEL_TAG(Q0), std::move(QE),
                PASTEL_TAG(S0), std::move(SE),
                PASTEL_TAG(t0), std::move(tE));

            QE = std::move(lsMatch.Q);
            SE = std::move(lsMatch.S);
            tE = std::move(lsMatch.t);

            REQUIRE(qePointer == QE.memptr());
            REQUIRE(sePointer == SE.memptr());
            REQUIRE(tePointer == tE.memptr());

            // Check that the errors are small.
            Real qError = arma::norm(QE - Q, "inf");
            Real sError = arma::norm(SE - S, "inf");
            Real tError = arma::norm(tE - t, "inf");

            if (std::max(std::max(qError, sError), tError) > threshold ||
                (orientation != 0 && sign(arma::det(QE * SE)) != sign(orientation)))
            {
                /*
               std::cout << orientation << " " 
                   << (integer)matrix << " "
                   << (integer)scaling << " " 
                   << (integer)translation << std::endl;

               std::cout << qError << " " 
                   << sError << " "
                   << tError << std::endl;

               std::cout << "Q" << Q << std::endl;
               std::cout << "S" << S << std::endl;
               std::cout << "t" << t << std::endl;

               std::cout << "QE" << QE << std::endl;
               std::cout << "SE" << SE << std::endl;
               std::cout << "tE" << tE << std::endl;
               */

                fails = fails + 1;
            }
        }

        REQUIRE(fails == 0);
    }

}

TEST_CASE("lsAffine (lsAffine)")
{
    testRandom<float>();
    testRandom<double>();
}