test_coherent_point_drift.cpp

Back to Coherent point drift

test/pastel/geometry/

// Description: Testing for coherent point-drift
// DocumentationOf: coherent_point_drift.h

#include "test/test_init.h"

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

namespace
{

    struct Report
    {
        template <typename State>
        void operator()(State&& state)
        {
            std::cout << "Q" << std::endl;
            std::cout << state.Q << std::endl;

            std::cout << "S" << std::endl;
            std::cout << state.S << std::endl;

            std::cout << "t" << std::endl;
            std::cout << state.t << std::endl;

            std::cout << "sigma2" << std::endl;
            std::cout << state.sigma2 << std::endl;

            std::cout << "W" << std::endl;
            std::cout << state.W << std::endl;
        };
    };

    template <typename Real>
    void testCase(
        MatrixView<Real> Ps,
        MatrixView<Real> Qs,
        MatrixView<Real> Ss,
        ColMatrixView<Real> ts,
        std::initializer_list<Cpd_Matrix> matrixSet,
        std::initializer_list<Cpd_Scaling> scalingSet,
        std::initializer_list<Cpd_Translation> translationSet)
    {
        Real threshold = 1e-4;

        MapMatrix<Real> P = asMatrix(Ps);
        MapMatrix<Real> Q = asMatrix(Qs);
        MapMatrix<Real> S = asMatrix(Ss);
        MapColMatrix<Real> t = asMatrix(ts);

        Matrix<Real> R = (Q * S * P).colwise() + t;
        MatrixView<Real> Rs = view(R);

        REQUIRE(Ps.rows() == 2);

        auto deltaNorm = [&]()
        {
            Matrix<Real> delta = 
                ((Q * S * P).colwise() + t) - R;

            return maxNorm(delta);
        };

        for (auto scaling : scalingSet)
        {
            for (auto translation : translationSet)
            {
                for (auto matrix : matrixSet)
                {
                    integer orientation = 1;

                    if (matrix == LsAffine_Matrix::Identity &&
                        scaling != LsAffine_Scaling::Rigid)
                    {
                        orientation = 0;
                    }

                    if (scaling == LsAffine_Scaling::Free)
                    {
                        orientation = 0;
                    }

                    coherentPointDrift(
                        Ps, Rs, Qs, Ss, ts,
                        PASTEL_TAG(scaling), scaling,
                        PASTEL_TAG(translation), translation,
                        PASTEL_TAG(matrix), matrix,
                        PASTEL_TAG(orientation), orientation);

                    if (deltaNorm() >= threshold)
                    {
                        std::cout << "Matrix " << (integer)matrix
                            << " Scaling " << (integer)scaling
                            << " Orientation " << (integer)orientation
                            << " Translation " << (integer)translation
                            << std::endl;

                        std::cout << "P" << std::endl << Ps << std::endl;
                        std::cout << "R" << std::endl << Rs << std::endl;
                        std::cout << "Q" << std::endl << Qs << std::endl;
                        std::cout << "S" << std::endl << Ss << std::endl;
                        std::cout << "t" << std::endl << ts << std::endl;
                        std::cout << "((Q * S * P).colwise() + t) - R" << std::endl << (((Q * S * P).colwise() + t) - R) << std::endl;
                    }

                    REQUIRE(deltaNorm() < threshold);
                }
            }
        }
    }

    template <typename Real>
    void testRotation()
    {
        Matrix<Real> S = Matrix<Real>::Identity(2, 2);
        ColMatrix<Real> t = Matrix<Real>::Zero(2, 1);

        Matrix<Real> P(2, 3);
        P <<
            0, 1, 0,
            0, 0, 1;

        // Angle pi / 3 often matches to another minimum.
        Real maxAngle = constantPi<Real>() / 4;
        for (Real alpha = 0; alpha <= maxAngle; alpha += maxAngle / 10)
        {
            Matrix<Real> Q(2, 2);
            Q <<
                std::cos(alpha), -std::sin(alpha),
                std::sin(alpha), std::cos(alpha);

            testCase(
                view(P), view(Q), view(S), view(t), 
                {Cpd_Matrix::Free},
                // CPD does not work when scaling is estimated.
                // I don't know if this is a bug in the implementation,
                // or what causes it.
                {Cpd_Scaling::Rigid/*, Cpd_Scaling::Conformal, Cpd_Scaling::Free*/},
                {Cpd_Translation::Free, Cpd_Translation::Identity});
        }
    }

    template <typename Real>
    void testTranslation()
    {
        Matrix<Real> Q = Matrix<Real>::Identity(2, 2);
        Matrix<Real> S = Matrix<Real>::Identity(2, 2);

        Matrix<Real> fromSet(2, 7);
        fromSet <<
            0, 5, 10, 11, 12, 13, 15,
            0, 6, 3, 7, 5, 6, -4;

        Real offsetMin = -1;
        Real offsetMax = 1;
        for (Real alpha = offsetMin; alpha <= offsetMax; alpha += (offsetMax - offsetMin) / 10)
        {
            ColMatrix<Real> t(2, 1);
            t << alpha, alpha;

            testCase(
                view(fromSet), view(Q), view(S), view(t), 
                {Cpd_Matrix::Free, Cpd_Matrix::Identity},
                {Cpd_Scaling::Rigid/*, Cpd_Scaling::Conformal, Cpd_Scaling::Free*/},
                {Cpd_Translation::Free});
        }
    }

}

TEST_CASE("translation (coherent_point_drift)")
{
    testTranslation<float>();
    testTranslation<double>();
}

TEST_CASE("rotation (coherent_point_drift)")
{
    testRotation<float>();
    testRotation<double>();
}