This commit is contained in:
2021-10-14 13:47:35 +02:00
commit 6625a8dfaa
4026 changed files with 844291 additions and 0 deletions
@@ -0,0 +1,54 @@
PROJECT(openvibe-module-geometry)
SET(PROJECT_VERSION_MAJOR ${OV_GLOBAL_VERSION_MAJOR})
SET(PROJECT_VERSION_MINOR ${OV_GLOBAL_VERSION_MINOR})
SET(PROJECT_VERSION_PATCH ${OV_GLOBAL_VERSION_PATCH})
SET(PROJECT_VERSION ${OV_GLOBAL_VERSION_STRING})
FILE(GLOB_RECURSE SRC_FILES src/*.cpp include/*.hpp include/*.h)
INCLUDE_DIRECTORIES(include)
# We use static library to allow template functions and stl object in class
ADD_LIBRARY(${PROJECT_NAME} STATIC ${SRC_FILES})
SET_TARGET_PROPERTIES(${PROJECT_NAME} PROPERTIES
VERSION ${PROJECT_VERSION}
SOVERSION ${PROJECT_VERSION_MAJOR}
FOLDER ${MODULES_FOLDER})
if(UNIX)
SET_TARGET_PROPERTIES(${PROJECT_NAME} PROPERTIES COMPILE_FLAGS "-fPIC")
ENDIF(UNIX)
if(WIN32)
ADD_DEFINITIONS(/bigobj) # Definition for big obj file in debug mode with visual studio
ENDIF(WIN32)
ADD_DEFINITIONS(-D_USE_MATH_DEFINES) # Definition for constant math as M_PI
# OpenViBE Third Party
INCLUDE("FindThirdPartyEigen")
INCLUDE("FindThirdPartyBoost")
# ---------------------------------
# Target macros
# Defines target operating system, architecture and compiler
# ---------------------------------
SET_BUILD_PLATFORM()
# -----------------------------
# Install files
# -----------------------------
INSTALL(TARGETS ${PROJECT_NAME}
RUNTIME DESTINATION ${DIST_BINDIR}
LIBRARY DESTINATION ${DIST_LIBDIR}
ARCHIVE DESTINATION ${DIST_LIBDIR})
INSTALL(DIRECTORY include/ DESTINATION ${DIST_INCLUDEDIR} FILES_MATCHING PATTERN "*.hpp" PATTERN "*.h")
# ---------------------------------
# Test applications
# ---------------------------------
IF(OV_COMPILE_TESTS)
ADD_SUBDIRECTORY(test)
ENDIF()
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,223 @@
///-------------------------------------------------------------------------------------------------
///
/// \file Basics.hpp
/// \brief Basic functions of Eigen matrix manipulation and verification.
/// \author Thibaut Monseigne (Inria).
/// \version 1.0.
/// \date 26/10/2018.
/// \copyright <a href="https://choosealicense.com/licenses/agpl-3.0/">GNU Affero General Public License v3.0</a>.
///
///-------------------------------------------------------------------------------------------------
#pragma once
#include <Eigen/Dense>
#include <vector>
#include <cmath> // Ceil
#include <type_traits> // Template type
namespace Geometry {
/// <summary> Enumeration of Standardization method for features matrix data. </summary>
enum class EStandardization
{
None, ///< No change.
Center, ///< Standardize data by removing the mean (on each feature separately).
StandardScale ///< Standardize data by removing the mean and scaling to unit variance (on each feature separately).
};
//************************************************
//******************** Matrix ********************
//************************************************
/// <summary> Apply an affine transformation and return the result (The last transpose is useless if matrix is SPD).
/// \f[
/// B = R^{-1/2} * A * {R^{-1/2}}^{\mathsf{T}}
/// \f]
/// </summary>
/// <param name="ref"> The reference matrix which transforms. </param>
/// <param name="matrix"> the matrix to transform. </param>
/// <returns> The transformed matrix </returns>
Eigen::MatrixXd AffineTransformation(const Eigen::MatrixXd& ref, const Eigen::MatrixXd& matrix);
/// <summary> Standardize data row by row with selected method (destructive operation). </summary>
/// <param name="matrix"> The matrix to standardize. </param>
/// <param name="standard"> Standard method. </param>
/// <returns> <c>True</c> if it succeeds, <c>False</c> otherwise. </returns>
bool MatrixStandardization(Eigen::MatrixXd& matrix, EStandardization standard = EStandardization::None);
/// <summary> Standardize data row by row with selected method (non destructive operation). </summary>
/// <param name="in"> The matrix to standardize. </param>
/// <param name="out"> The matrix standardized. </param>
/// <param name="standard"> Standard method. </param>
/// <returns> <c>True</c> if it succeeds, <c>False</c> otherwise. </returns>
bool MatrixStandardization(const Eigen::MatrixXd& in, Eigen::MatrixXd& out, EStandardization standard = EStandardization::None);
/// <summary> Removes the mean of each row at the matrix (destructive operation).\n
/// So \f$\mu=0\f$. </summary>
/// <param name="matrix"> The Matrix to center. </param>
/// <returns> <c>True</c> if it succeeds, <c>False</c> otherwise. </returns>
bool MatrixCenter(Eigen::MatrixXd& matrix);
/// <summary> Removes the mean of each row at the matrix (non destructive operation).\n
/// So \f$\mu=0\f$. </summary>
/// <param name="in"> The Matrix to center. </param>
/// <param name="out"> The Matrix centered. </param>
/// <returns> <c>True</c> if it succeeds, <c>False</c> otherwise. </returns>
bool MatrixCenter(const Eigen::MatrixXd& in, Eigen::MatrixXd& out);
/// <summary> Removes the mean of each row at the matrix and divide by the variance (destructive operation with scale return).\n
/// So \f$\mu=0\f$ and \f$\sigma=1\f$. </summary>
/// <param name="matrix"> The Matrix to standardize. </param>
/// <param name="scale"> The scale vector. </param>
/// <returns> <c>True</c> if it succeeds, <c>False</c> otherwise. </returns>
/// <remarks> Adaptation of <a href="http://scikit-learn.org">sklearn</a> <a href="https://scikit-learn.org/stable/modules/generated/sklearn.preprocessing.StandardScaler.html">StandardScaler</a> (<a href="https://github.com/scikit-learn/scikit-learn/blob/master/COPYING">License</a>). </remarks>
bool MatrixStandardScaler(Eigen::MatrixXd& matrix, Eigen::RowVectorXd& scale);
/// <summary> Removes the mean of each row at the matrix and divide by the variance (destructive operation).\n
/// So \f$\mu=0\f$ and \f$\sigma=1\f$. </summary>
/// <param name="matrix"> The Matrix to standardize. </param>
/// <returns> <c>True</c> if it succeeds, <c>False</c> otherwise. </returns>
/// <remarks> Adaptation of <a href="http://scikit-learn.org">sklearn</a> <a href="https://scikit-learn.org/stable/modules/generated/sklearn.preprocessing.StandardScaler.html">StandardScaler</a> (<a href="https://github.com/scikit-learn/scikit-learn/blob/master/COPYING">License</a>). </remarks>
bool MatrixStandardScaler(Eigen::MatrixXd& matrix);
/// <summary> Removes the mean of each row at the matrix and divide by the variance (non destructive operation with scale return).\n
/// So \f$\mu=0\f$ and \f$\sigma=1\f$. </summary>
/// <param name="in"> The Matrix to standardize. </param>
/// <param name="out"> The Matrix standardized. </param>
/// <param name="scale"> The scale vector. </param>
/// <returns> <c>True</c> if it succeeds, <c>False</c> otherwise. </returns>
/// <remarks> Adaptation of <a href="http://scikit-learn.org">sklearn</a> <a href="https://scikit-learn.org/stable/modules/generated/sklearn.preprocessing.StandardScaler.html">StandardScaler</a> (<a href="https://github.com/scikit-learn/scikit-learn/blob/master/COPYING">License</a>). </remarks>
bool MatrixStandardScaler(const Eigen::MatrixXd& in, Eigen::MatrixXd& out, Eigen::RowVectorXd& scale);
/// <summary> Removes the mean of each row at the matrix and divide by the variance (non destructive operation).\n
/// So \f$\mu=0\f$ and \f$\sigma=1\f$. </summary>
/// <param name="in"> The Matrix to standardize. </param>
/// <param name="out"> The Matrix standardized. </param>
/// <returns> <c>True</c> if it succeeds, <c>False</c> otherwise. </returns>
/// <remarks> Adaptation of <a href="http://scikit-learn.org">sklearn</a> <a href="https://scikit-learn.org/stable/modules/generated/sklearn.preprocessing.StandardScaler.html">StandardScaler</a> (<a href="https://github.com/scikit-learn/scikit-learn/blob/master/COPYING">License</a>). </remarks>
bool MatrixStandardScaler(const Eigen::MatrixXd& in, Eigen::MatrixXd& out);
/// <summary> Give the string format of Matrix. </summary>
/// <param name="matrix"> The Matrix to display. </param>
/// <returns> The string format. </returns>
std::string MatrixPrint(const Eigen::MatrixXd& matrix);
/// <summary> Check first the size, then if not empty matrix and then if they are almost equal. </summary>
/// <param name="matrix1"> First Matrix. </param>
/// <param name="matrix2"> Second Matrix. </param>
/// <param name="precision"> Precision for matrix comparison. </param>
/// <returns> <c>True</c> if the two elements are equals (with a precision tolerance), <c>False</c> otherwise. </returns>
bool AreEquals(const Eigen::MatrixXd& matrix1, const Eigen::MatrixXd& matrix2, double precision = 1e-6);
//*************************************************************
//******************** Index Manipulations ********************
//*************************************************************
/// <summary> Gets the items selected by the index. </summary>
/// <param name="row"> the original row. </param>
/// <param name="index"> Elements to select. </param>
/// <returns> Row with selected elements. </returns>
Eigen::RowVectorXd GetElements(const Eigen::RowVectorXd& row, const std::vector<size_t>& index);
/// <summary> <a href="https://numpy.org/doc/stable/reference/generated/numpy.arange.html">Numpy arange</a> implementation in C++. </summary>
/// <typeparam name="T"> Generic numeric type parameter. </typeparam>
/// <param name="start"> The start. </param>
/// <param name="stop"> The stop. </param>
/// <param name="step"> (Optional) Amount to increment by. </param>
/// <returns> vector&lt;T&gt; </returns>
template <typename T, typename = typename std::enable_if<std::is_arithmetic<T>::value, T>::type>
std::vector<T> ARange(const T start, const T stop, const T step = 1)
{
std::vector<T> result;
result.reserve(size_t(ceil(1.0 * (stop - start) / step)));
for (T i = start; i < stop; i += step) { result.push_back(i); }
return result;
}
/// <summary> Turn vector of vector into vector. </summary>
/// <typeparam name="T"> Generic type parameter. </typeparam>
/// <param name="in"> vector of vector. </param>
/// <returns> <c>vector&lt;T&gt;</c> </returns>
template <typename T>
std::vector<T> Vector2DTo1D(const std::vector<std::vector<T>>& in)
{
std::vector<T> result;
size_t sum = 0;
for (const auto& v : in) { sum += v.size(); }
result.reserve(sum);
for (const auto& v : in) { for (const auto& e : v) { result.push_back(e); } }
return result;
}
/// <summary> Turn vector into vector of vector with position repartition. </summary>
/// <typeparam name="T"> Generic type parameter. </typeparam>
/// <param name="in"> vector of vector. </param>
/// <param name="position"> position of element (size of position is the number of row the values are the number of element on each row). </param>
/// <returns> <c>vector&lt;T&gt;</c> </returns>
template <typename T>
std::vector<std::vector<T>> Vector1DTo2D(const std::vector<T>& in, const std::vector<size_t>& position)
{
const size_t n = position.size();
std::vector<std::vector<T>> result(n);
size_t idx = 0;
for (size_t i = 0; i < n; ++i)
{
const size_t nbSample = position[i];
result[i].resize(nbSample);
for (size_t j = 0; j < nbSample; ++j) { result[i][j] = in[idx++]; }
}
return result;
}
//***************************************************
//******************** Validates ********************
//***************************************************
/// <summary> Validate if value is in [min;max]. </summary>
/// <param name="value"> The value. </param>
/// <param name="min"> The minimum. </param>
/// <param name="max"> The maximum. </param>
/// <returns> <c>True</c> if it succeeds, <c>False</c> otherwise. </returns>
bool InRange(const double value, const double min, const double max);
/// <summary> Validate if the vector is not empty and the matrices are validate. </summary>
/// <param name="matrices"> Vector of Matrix. </param>
/// <returns> <c>True</c> if it succeeds, <c>False</c> otherwise. </returns>
bool AreNotEmpty(const std::vector<Eigen::MatrixXd>& matrices);
/// <summary> Validates if matrix is not empty. </summary>
/// <param name="matrix"> Matrix. </param>
/// <returns> <c>True</c> if it succeeds, <c>False</c> otherwise. </returns>
bool IsNotEmpty(const Eigen::MatrixXd& matrix);
/// <summary> Validates if two matrix have same size. </summary>
/// <param name="a"> Matrix A. </param>
/// <param name="b"> Matrix B. </param>
/// <returns> <c>True</c> if it succeeds, <c>False</c> otherwise. </returns>
bool HaveSameSize(const Eigen::MatrixXd& a, const Eigen::MatrixXd& b);
/// <summary> Validate if the vector is not empty and the matrices have same size. </summary>
/// <param name="matrices"> Vector of Matrix. </param>
/// <returns> <c>True</c> if it succeeds, <c>False</c> otherwise. </returns>
bool HaveSameSize(const std::vector<Eigen::MatrixXd>& matrices);
/// <summary> Validates if matrix is square matrix and not empty. </summary>
/// <param name="matrix"> Matrix. </param>
/// <returns> <c>True</c> if it succeeds, <c>False</c> otherwise. </returns>
bool IsSquare(const Eigen::MatrixXd& matrix);
/// <summary> Validate if the vector is not empty and the matrices are square matrix and not empty. </summary>
/// <param name="matrices"> Vector of Matrix. </param>
/// <returns> <c>True</c> if it succeeds, <c>False</c> otherwise. </returns>
bool AreSquare(const std::vector<Eigen::MatrixXd>& matrices);
//********************************************************
//******************** CSV MANAGEMENT ********************
//********************************************************
/// <summary>Return the string split by the \p sep parameter. </summary>
/// <param name="s"> The string to split. </param>
/// <param name="sep"> the separator string which splits. </param>
/// <returns> Vector of string part. </returns>
std::vector<std::string> Split(const std::string& s, const std::string& sep);
} // namespace Geometry
@@ -0,0 +1,46 @@
///-------------------------------------------------------------------------------------------------
///
/// \file Classification.hpp
/// \brief All functions to help Matrix Classifiers.
/// \author Thibaut Monseigne (Inria).
/// \version 1.0.
/// \date 26/10/2018.
/// \copyright <a href="https://choosealicense.com/licenses/agpl-3.0/">GNU Affero General Public License v3.0</a>.
/// \remarks
/// - LSQR inspired by <a href="http://scikit-learn.org">sklearn</a> <a href="https://scikit-learn.org/stable/modules/generated/sklearn.discriminant_analysis.LinearDiscriminantAnalysis.html">LinearDiscriminantAnalysis</a> (<a href="https://github.com/scikit-learn/scikit-learn/blob/master/COPYING">License</a>).
/// - FgDA inspired by the work of Alexandre Barachant : <a href="https://github.com/alexandrebarachant/pyRiemann">pyRiemann</a> (<a href="https://github.com/alexandrebarachant/pyRiemann/blob/master/LICENSE">License</a>).
///
///-------------------------------------------------------------------------------------------------
#pragma once
#include <vector>
#include <Eigen/Dense>
namespace Geometry {
/// <summary> Compute the weight of Linear Discriminant Analysis with Least squares (LSQR) Solver. </summary>
/// <param name="dataset"> The dataset (first dimension is the class, second dimension the trial as Feature Vector). </param>
/// <param name="weight"> The weight to apply. </param>
/// <returns> <c>True</c> if it succeeds, <c>False</c> otherwise. </returns>
/// <remarks> Inspired by <a href="http://scikit-learn.org">sklearn</a> <a href="https://scikit-learn.org/stable/modules/generated/sklearn.discriminant_analysis.LinearDiscriminantAnalysis.html">LinearDiscriminantAnalysis</a> (<a href="https://github.com/scikit-learn/scikit-learn/blob/master/COPYING">License</a>). </remarks>
bool LSQR(const std::vector<std::vector<Eigen::RowVectorXd>>& dataset, Eigen::MatrixXd& weight);
/// <summary> Compute Least squares (LSQR) Weight and transform to FgDA Weight. \n
/// \f[ W_{\text{FgDA}} = W^{\mathsf{T}} \times (W \times W^{\mathsf{T}})^{-1} \times W \f]
/// </summary>
/// <param name="dataset"> The dataset (first dimension is the class, second dimension is the trial as Feature Vector). </param>
/// <param name="weight"> The Weight to apply. </param>
/// <returns> <c>True</c> if it succeeds, <c>False</c> otherwise. </returns>
/// <remarks> Method inspired by the work of Alexandre Barachant : <a href="https://github.com/alexandrebarachant/pyRiemann">pyRiemann</a> (<a href="https://github.com/alexandrebarachant/pyRiemann/blob/master/LICENSE">License</a>). </remarks>
bool FgDACompute(const std::vector<std::vector<Eigen::RowVectorXd>>& dataset, Eigen::MatrixXd& weight);
/// <summary> Apply the weight on the vector. (just a matrix product) </summary>
/// <param name="in"> Sample to transform. </param>
/// <param name="out"> Transformed Sample. </param>
/// <param name="weight"> The Weight to apply. </param>
/// <returns> <c>True</c> if it succeeds, <c>False</c> otherwise. </returns>
/// <remarks> Method inspired by the work of Alexandre Barachant : <a href="https://github.com/alexandrebarachant/pyRiemann">pyRiemann</a> (<a href="https://github.com/alexandrebarachant/pyRiemann/blob/master/LICENSE">License</a>). </remarks>
bool FgDAApply(const Eigen::RowVectorXd& in, Eigen::RowVectorXd& out, const Eigen::MatrixXd& weight);
} // namespace Geometry
@@ -0,0 +1,224 @@
///-------------------------------------------------------------------------------------------------
///
/// \file Covariance.hpp
/// \brief All functions to estimate the Covariance Matrix.
/// \author Thibaut Monseigne (Inria).
/// \version 1.0.
/// \date 26/10/2018.
/// \copyright <a href="https://choosealicense.com/licenses/agpl-3.0/">GNU Affero General Public License v3.0</a>.
/// \remarks
/// - List of Estimator inspired by the work of Alexandre Barachant : <a href="https://github.com/alexandrebarachant/pyRiemann">pyRiemann</a> (<a href="https://github.com/alexandrebarachant/pyRiemann/blob/master/LICENSE">License</a>).
/// - <a href="http://scikit-learn.org/stable/modules/generated/sklearn.covariance.LedoitWolf.html">Ledoit and Wolf Estimator</a> inspired by <a href="http://scikit-learn.org">sklearn</a> (<a href="https://github.com/scikit-learn/scikit-learn/blob/master/COPYING">License</a>).
/// - <a href="http://scikit-learn.org/stable/modules/generated/sklearn.covariance.OAS.html">Oracle Approximating Shrinkage (OAS) Estimator</a> Inspired by <a href="http://scikit-learn.org">sklearn</a> (<a href="https://github.com/scikit-learn/scikit-learn/blob/master/COPYING">License</a>).
/// - <b>Minimum Covariance Determinant (MCD) Estimator isn't implemented. </b>
///
///-------------------------------------------------------------------------------------------------
#pragma once
#include "geometry/Basics.hpp"
#include <Eigen/Dense>
namespace Geometry {
//***************************************************
//******************** CONSTANTS ********************
//***************************************************
/// <summary> Enumeration of the covariance matrix estimator. Inspired by the work of Alexandre Barachant : <a href="https://github.com/alexandrebarachant/pyRiemann">pyRiemann</a>. </summary>
enum class EEstimator
{
COV, ///< The Simple Covariance Estimator.
SCM, ///< The Normalized Spatial Covariance Matrix (SCM) Estimator.
LWF, ///< The Ledoit and Wolf Estimator.
OAS, ///< The Oracle Approximating Shrinkage (OAS) Estimator.
MCD, ///< The Minimum Covariance Determinant (MCD) Estimator.
COR, ///< The Pearson Correlation Estimator.
IDE ///< The Identity Matrix.
};
/// <summary> Convert estimators to string. </summary>
/// <param name="estimator"> The estimator. </param>
/// <returns> <c>std::string</c> </returns>
inline std::string toString(const EEstimator estimator)
{
switch (estimator)
{
case EEstimator::COV: return "Covariance";
case EEstimator::SCM: return "Normalized Spatial Covariance Matrix (SCM)";
case EEstimator::LWF: return "Ledoit and Wolf";
case EEstimator::OAS: return "Oracle Approximating Shrinkage (OAS)";
case EEstimator::MCD: return "Minimum Covariance Determinant (MCD)";
case EEstimator::COR: return "Pearson Correlation";
case EEstimator::IDE: return "Identity";
}
return "Invalid";
}
/// <summary> Convert string to estimators. </summary>
/// <param name="estimator"> The estimator. </param>
/// <returns> <see cref="EEstimator"/> </returns>
inline EEstimator StringToEstimator(const std::string& estimator)
{
if (estimator == "Covariance") { return EEstimator::COV; }
if (estimator == "Normalized Spatial Covariance Matrix (SCM)") { return EEstimator::SCM; }
if (estimator == "Ledoit and Wolf") { return EEstimator::LWF; }
if (estimator == "Oracle Approximating Shrinkage (OAS)") { return EEstimator::OAS; }
if (estimator == "Minimum Covariance Determinant (MCD)") { return EEstimator::MCD; }
if (estimator == "Pearson Correlation") { return EEstimator::COR; }
return EEstimator::IDE;
}
//***********************************************************
//******************** COVARIANCES BASES ********************
//***********************************************************
/// <summary> Calculation of the Variance of a double dataset \f$\vec{X}\f$.\n
/// \f[ V(X) = \left(\frac{1}{n} \sum_{i=1}^{N}x_{i}^{2}\right) - \left(\frac{1}{n} \sum_{i=1}^{N}x_{i}\right)^{2} \f]
/// </summary>
/// <param name="x"> The dataset \f$\vec{X}\f$. With \f$ N \f$ Samples. </param>
/// <returns> The Variance. </returns>
double Variance(const Eigen::RowVectorXd& x);
/// <summary> Calculation of the Covariance between two double dataset \f$\vec{X}, \vec{Y}\f$.\n
/// \f[ \operatorname{Cov}\left(x,y\right) = \frac{\sum_{i=1}^{N}{x_{i}y_{i}} - \left(\sum_{i=1}^{N}{x_{i}}\sum_{i=1}^{N}{y_{i}}\right)/N}{N}\f]
/// </summary>
/// <param name="x"> The dataset \f$\vec{X}\f$. With \f$ N \f$ Samples. </param>
/// <param name="y"> The dataset \f$\vec{Y}\f$. With \f$ N \f$ Samples. </param>
/// <returns> The Covariance. </returns>
double Covariance(const Eigen::RowVectorXd& x, const Eigen::RowVectorXd& y);
/// <summary> Shrunks the Covariance Matrix \f$ M \f$ (destructive operation).\n
/// \f[ (1 - \text{shrinkage}) \times M_{\operatorname{Cov}} + \frac{\text{shrinkage} \times \operatorname{trace}(M_{Cov})}{N} \times I_N \f]
/// </summary>
/// <param name="cov"> The Covariance Matrix to shrink. </param>
/// <param name="shrinkage"> (Optional) The shrinkage coefficient : \f$ 0\leq \text{shrinkage} \leq 1\f$. </param>
/// <returns> <c>True</c> if it succeeds, <c>False</c> otherwise. </returns>
bool ShrunkCovariance(Eigen::MatrixXd& cov, double shrinkage = 0.1);
/// <summary> Shrunks the Covariance Matrix \f$ M \f$ (non destructive operation).\n
/// \f[ (1 - \text{shrinkage}) \times M_{\operatorname{Cov}} + \frac{\text{shrinkage} \times \operatorname{trace}(M_{Cov})}{N} \times I_N \f]
/// </summary>
/// <param name="in"> The covariance matrix to shrink. </param>
/// <param name="out"> The shrunk covariance matrix. </param>
/// <param name="shrinkage"> (Optional) The shrinkage coefficient : \f$ 0\leq \text{shrinkage} \leq 1\f$. </param>
/// <returns> <c>True</c> if it succeeds, <c>False</c> otherwise. </returns>
bool ShrunkCovariance(const Eigen::MatrixXd& in, Eigen::MatrixXd& out, double shrinkage = 0.1);
/// <summary> Select the function to call for the covariance matrix.\n
/// - centralizing the data is useless for <c><see cref="EEstimator::COV"/></c> and <c><see cref="EEstimator::COR"/></c>.\n
/// - centralizing the data is not usual for <c><see cref="EEstimator::SCM"/></c>.
/// </summary>
/// <param name="in"> The dataset \f$\vec{X}\f$. With \f$ N \f$ Rows (features) and \f$ S \f$ columns (samples). </param>
/// <param name="out"> The Covariance Matrix. </param>
/// <param name="estimator"> (Optional) The selected estimator (see <see cref="EEstimator"/>). </param>
/// <param name="standard"> (Optional) Standardize the data (see <see cref="EStandardization"/>). </param>
/// <returns> <c>True</c> if it succeeds, <c>False</c> otherwise. </returns>
bool CovarianceMatrix(const Eigen::MatrixXd& in, Eigen::MatrixXd& out, EEstimator estimator = EEstimator::COV,
EStandardization standard = EStandardization::Center);
//***********************************************************
//******************** COVARIANCES TYPES ********************
//***********************************************************
/// <summary> Calculation of the covariance matrix.\n
/// \f[ M_{\operatorname{Cov}} =
/// \begin{pmatrix}
/// V\left(x_1\right) & \operatorname{Cov}\left(x_1,x_2\right) &\cdots & \operatorname{Cov}\left(x_1,x_N\right)\\
/// \operatorname{Cov}\left(x_2,x_1\right) &\ddots & \ddots & \vdots \\
/// \vdots & \ddots & \ddots & \vdots \\
/// \operatorname{Cov}\left(x_N,x_1\right) &\cdots & \cdots & V\left(x_N\right)
/// \end{pmatrix}
/// \quad\quad \text{with } x_i \text{ the feature } i
/// \f]\n
/// With the <see cref="Variance"/> and <see cref="Covariance"/> function.
/// </summary>
/// <param name="samples"> The dataset \f$\vec{X}\f$. With \f$ N \f$ Rows (features) and \f$ S \f$ columns (samples). </param>
/// <param name="cov"> The Covariance Matrix. </param>
/// <returns> <c>True</c> if it succeeds, <c>False</c> otherwise. </returns>
bool CovarianceMatrixCOV(const Eigen::MatrixXd& samples, Eigen::MatrixXd& cov);
/// <summary> Calculation of the covariance matrix by the method : Normalized Spatial Covariance Matrix (SCM).\n
/// \f[ M_{\operatorname{Cov_{SCM}}} = \frac{XX^{\mathsf{T}}}{\operatorname{trace}{\left(XX^{\mathsf{T}}\right)}} \f]
/// </summary>
/// <param name="samples"> The dataset \f$\vec{X}\f$. With \f$ N \f$ Rows (features) and \f$ S \f$ columns (samples). </param>
/// <param name="cov"> The Covariance Matrix. </param>
/// <returns> <c>True</c> if it succeeds, <c>False</c> otherwise. </returns>
bool CovarianceMatrixSCM(const Eigen::MatrixXd& samples, Eigen::MatrixXd& cov);
/// <summary> Calculation of the covariance matrix and shrinkage by the method : Ledoit and Wolf.\n
/// -# Compute the Covariance Matrix (see <see cref="CovarianceMatrixCOV"/>) \f$ M_{\operatorname{Cov}} \f$
/// -# Compute the Ledoit and Wolf Shrinkage
/// -# Shrunk the Matrix (see <see cref="ShrunkCovariance"/>)
///
/// Ledoit and Wolf Shrinkage (from <a href="http://scikit-learn.org/stable/modules/generated/sklearn.covariance.LedoitWolf.html">Sklearn LedoitWolf Estimator</a>)
/// described in "A Well-Conditioned Estimator for Large-Dimensional Covariance Matrices", Ledoit and Wolf, Journal of Multivariate Analysis, Volume 88, Issue 2, February 2004, pages 365-411. : \n
/// \f[
/// \begin{aligned}
/// \vec{X}^2 &= \begin{pmatrix}x_{0,0}^2 & \cdots & x_{0,S}^2 \\ \vdots & \ddots &\vdots \\ x_{N,0}^2 & \cdots & x_{N,S}^2\end{pmatrix} \quad \text{with } x_{i,j} \in \vec{X}\\
/// M_{\mu} &= \mu\times I_N = \begin{pmatrix} \mu & 0 & \cdots & 0 \\ 0 & \ddots &\ddots & \vdots \\ \vdots & \ddots &\ddots & 0 \\ 0 & \cdots & 0 & \mu\end{pmatrix}
/// \quad \text{with } \mu = \frac{\operatorname{trace}(M_{\operatorname{Cov}})}{N}\\
/// M_{\delta} &= M_{\operatorname{Cov}}-M_{\mu}\\
/// M_{\delta}^2 &= M_{\delta} * M_{\delta}\\
/// M_{\beta} &= \frac{1}{S} \times \left(\vec{X}^2 * \vec{X}^{2\mathsf{T}}\right) - M_{Cov} * M_{Cov}\\
/// \Sigma\left( M \right) &=\text{ the sum of the elements of the matrix } M\\
/// \end{aligned}
/// \f]
/// \f[ \text{Shrinkage}_\text{LWF} = \frac{\beta}{\delta} \quad \text{with } \delta = \frac{\Sigma\left( M_{\delta}^2 \right)}{N} \quad\text{and}\quad
/// \beta = \operatorname{min}\left(\frac{\Sigma\left( M_{\beta}^2 \right)}{N \times S},~ \delta\right)\f]
/// </summary>
/// <param name="samples"> The dataset \f$\vec{X}\f$. With \f$ N \f$ Rows (features) and \f$ S \f$ columns (samples). </param>
/// <param name="cov"> The Covariance Matrix. </param>
/// <returns> <c>True</c> if it succeeds, <c>False</c> otherwise. </returns>
bool CovarianceMatrixLWF(const Eigen::MatrixXd& samples, Eigen::MatrixXd& cov);
/// <summary> Calculation of the covariance matrix and shrinkage by the method : Oracle Approximating Shrinkage (OAS).\n
/// -# Compute the Covariance Matrix (see <see cref="CovarianceMatrixCOV"/>) \f$ M_{\operatorname{Cov}} \f$
/// -# Compute the Oracle Approximating Shrinkage
/// -# Shrunk the Matrix (see <see cref="ShrunkCovariance"/>)
///
/// Oracle Approximating Shrinkage (from <a href="http://scikit-learn.org/stable/modules/generated/sklearn.covariance.OAS.html">Sklearn Oracle Approximating Shrinkage Estimator</a>)
/// describe in "Shrinkage Algorithms for MMSE Covariance Estimation" Chen et al., IEEE Trans. on Sign. Proc., Volume 58, Issue 10, October 2010. : \n
/// \f[
/// \begin{aligned}
/// \mu &= \frac{\operatorname{trace}(M_{\operatorname{Cov}})}{N}\\
/// \mu \left( M \right) &=\text{ the mean of the elements of the matrix } M\\
/// \alpha &= \mu \left( M_{\operatorname{Cov}} * M_{\operatorname{Cov}} \right)\\
/// \text{num} &= \alpha + \mu^2\\
/// \text{den} &= (S + 1) \times \frac{\alpha - \mu^2}{N}\\
/// \end{aligned}
/// \f]
/// \f[
/// \text{Shrinkage}_\text{OAS} = \begin{cases}
/// 1, & \text{if}\ \text{den} = 0 \text{ or num} > \text{den} \\
/// \frac{\text{num}}{\text{den}}, & \text{otherwise}
/// \end{cases}
/// \f]
/// </summary>
/// <param name="samples"> The dataset \f$\vec{X}\f$. With \f$ N \f$ Rows (features) and \f$ S \f$ columns (samples). </param>
/// <param name="cov"> The Covariance Matrix. </param>
/// <returns> <c>True</c> if it succeeds, <c>False</c> otherwise. </returns>
bool CovarianceMatrixOAS(const Eigen::MatrixXd& samples, Eigen::MatrixXd& cov);
/// <summary>Calculation of the covariance matrix and shrinkage by the method : Minimum Covariance Determinant (MCD). </summary>
/// <param name="samples"> The dataset \f$\vec{X}\f$. With \f$ N \f$ Rows (features) and \f$ S \f$ columns (samples). </param>
/// <param name="cov"> The Covariance Matrix. </param>
/// <returns> <c>True</c> if it succeeds, <c>False</c> otherwise. </returns>
/// \todo Not implemented.
bool CovarianceMatrixMCD(const Eigen::MatrixXd& samples, Eigen::MatrixXd& cov);
/// <summary> Calculation of the covariance matrix by the method : Pearson Correlation.\n
/// \f[
/// M_{\operatorname{Cov_{COR}}}\left(i,j\right)
/// = \frac{ M_{\operatorname{Cov}}\left(i,j\right) } { \sqrt{ M_{\operatorname{Cov}}\left(i,i\right) * M_{\operatorname{Cov}}\left(j,j\right) } }
/// \f]
/// </summary>
/// <param name="samples"> The dataset \f$\vec{X}\f$. With \f$ N \f$ Rows (features) and \f$ S \f$ columns (samples). </param>
/// <param name="cov"> The Covariance Matrix. </param>
/// <returns> <c>True</c> if it succeeds, <c>False</c> otherwise. </returns>
bool CovarianceMatrixCOR(const Eigen::MatrixXd& samples, Eigen::MatrixXd& cov);
/// <summary> Return the Identity matrix \f$ I_N \f$. </summary>
/// <param name="samples"> The dataset \f$\vec{X}\f$. With \f$ N \f$ Rows (features) and \f$ S \f$ columns (samples). </param>
/// <param name="cov"> The Covariance Matrix. </param>
/// <returns> <c>True</c> if it succeeds, <c>False</c> otherwise. </returns>
bool CovarianceMatrixIDE(const Eigen::MatrixXd& samples, Eigen::MatrixXd& cov);
} // namespace Geometry
@@ -0,0 +1,87 @@
///-------------------------------------------------------------------------------------------------
///
/// \file Distance.hpp
/// \brief All functions to estimate the Distance between two Covariance Matrix.
/// \author Thibaut Monseigne (Inria).
/// \version 1.0.
/// \date 26/10/2018.
/// \copyright <a href="https://choosealicense.com/licenses/agpl-3.0/">GNU Affero General Public License v3.0</a>.
/// \remarks
/// - List of Metrics inspired by the work of Alexandre Barachant : <a href="https://github.com/alexandrebarachant/pyRiemann">pyRiemann</a> (<a href="https://github.com/alexandrebarachant/pyRiemann/blob/master/LICENSE">License</a>).
///
///-------------------------------------------------------------------------------------------------
#pragma once
#include "geometry/Metrics.hpp"
#include <Eigen/Dense>
namespace Geometry {
/// <summary> Compute the distance between two matrix with the selected \p metric. </summary>
/// <param name="a"> The First Covariance matrix. </param>
/// <param name="b"> The Second Covariance matrix. </param>
/// <param name="metric"> (Optional) The metric (see <see cref="EMetric"/>). </param>
/// <returns> The Distance between A and B. </returns>
double Distance(const Eigen::MatrixXd& a, const Eigen::MatrixXd& b, EMetric metric = EMetric::Riemann);
/// <summary> Compute the Riemannian Distance between two covariance matrices A and B.\n
/// \f[ d_{\text{R}}(A,B) = \sqrt{\left( \sum_i \log\left(\lambda_i\right)^2 \right)} \f]
/// with : \f$\lambda_i\f$ the joint eigenvalues of \f$A\f$ and \f$B\f$.
/// </summary>
/// <param name="a"> The First Covariance matrix. </param>
/// <param name="b"> The Second Covariance matrix. </param>
/// <returns> The Riemannian Distance between A and B. </returns>
double DistanceRiemann(const Eigen::MatrixXd& a, const Eigen::MatrixXd& b);
/// <summary> Compute the Euclidian Distance between two covariance matrices A and B.\n
/// \f[ d_{\text{E}}(A,B) = \left\lVert B - A \right\rVert\f]
/// </summary>
/// <param name="a"> The First Covariance matrix. </param>
/// <param name="b"> The Second Covariance matrix. </param>
/// <returns> The Eclidean Distance between A and B. </returns>
double DistanceEuclidian(const Eigen::MatrixXd& a, const Eigen::MatrixXd& b);
/// <summary> Compute the Log Euclidian Distance between two covariance matrices A and B.\n
/// \f[ d_{\text{lE}}(A,B) = \left\lVert \log\left(B\right) - \log\left(A\right) \right\rVert\f]
/// </summary>
/// <param name="a"> The First Covariance matrix. </param>
/// <param name="b"> The Second Covariance matrix. </param>
/// <returns> The Log Eclidean Distance between A and B. </returns>
double DistanceLogEuclidian(const Eigen::MatrixXd& a, const Eigen::MatrixXd& b);
/// <summary> Compute the Log-det Distance between two covariance matrices A and B.\n
/// \f[ d_{\text{lD}}(A,B) = \sqrt{\log\left(\left\lvert\frac{A + B}{2}\right\rvert\right) - 0.5 \times \log\left( \left\lvert A \right\rvert \times \left\lvert B \right\rvert \right)} \f]
/// with : \f$\left\lvert A \right\rvert\f$ the determinant of \f$A\f$.
/// </summary>
/// <param name="a"> The First Covariance matrix. </param>
/// <param name="b"> The Second Covariance matrix. </param>
/// <returns> The Log-det Distance between A and B. </returns>
double DistanceLogDet(const Eigen::MatrixXd& a, const Eigen::MatrixXd& b);
/// <summary> Compute the Kullback Leibler Divergence between two covariance matrices A and B.\n
/// \f[ d_{\text{K}}(A,B) = 0.5 \times \left( \operatorname{trace}\left(B^{-1} ~ A\right) - N + \log\left( \frac{\left\lvert B \right\rvert }{\left\lvert A \right\rvert} \right) \right) \f]
/// with : \f$\left\lvert A \right\rvert\f$ the determinant of \f$A\f$.
/// </summary>
/// <param name="a"> The First Covariance matrix. </param>
/// <param name="b"> The Second Covariance matrix. </param>
/// <returns> The Kullback Leibler Divergence Distance between A and B. </returns>
double DistanceKullback(const Eigen::MatrixXd& a, const Eigen::MatrixXd& b);
/// <summary> Compute the Symetric Kullback Leibler Divergence between two covariance matrices A and B.\n
/// \f[ d_{\text{sK}}(A,B) = d_\text{K}(A, B) + d_\text{K}(B, A) \f]
/// </summary>
/// <param name="a"> The First Covariance matrix. </param>
/// <param name="b"> The Second Covariance matrix. </param>
/// <returns> The Symetric Kullback Leibler Divergence Distance between A and B. </returns>
double DistanceKullbackSym(const Eigen::MatrixXd& a, const Eigen::MatrixXd& b);
/// <summary> Compute the Wasserstein Distance between two covariance matrices A and B.\n
/// \f[ d_{\text{W}}(A,B) = \sqrt{ \operatorname{trace}\left(A + B - 2 \times \left(A^{1/2} ~ B ~ A^{1/2}\right)^{1/2}\right) } \f]
/// </summary>
/// <param name="a"> The First Covariance matrix. </param>
/// <param name="b"> The Second Covariance matrix. </param>
/// <returns> The Wasserstein Distance between A and B. </returns>
double DistanceWasserstein(const Eigen::MatrixXd& a, const Eigen::MatrixXd& b);
} // namespace Geometry
@@ -0,0 +1,109 @@
///-------------------------------------------------------------------------------------------------
///
/// \file Featurization.hpp
/// \brief All functions to transform Covariance matrix to feature vector.
/// \author Thibaut Monseigne (Inria).
/// \version 1.0.
/// \date 26/10/2018.
/// \copyright <a href="https://choosealicense.com/licenses/agpl-3.0/">GNU Affero General Public License v3.0</a>.
///
///-------------------------------------------------------------------------------------------------
#pragma once
#include <Eigen/Dense>
namespace Geometry {
/// <summary> Compute the features vector of covariance matrix with the selected method. </summary>
/// <param name="in"> The covariance in. </param>
/// <param name="out"> The Feature Vector. </param>
/// <param name="tangent"> (Optional) True to use tangent space featurization, Upper Triangle Squeeze if false. </param>
/// <param name="ref"> The reference Matrix (usefull for Tangent Space Featurization). </param>
/// <returns> <c>True</c> if it succeeds, <c>False</c> otherwise. </returns>
bool Featurization(const Eigen::MatrixXd& in, Eigen::RowVectorXd& out, bool tangent = true, const Eigen::MatrixXd& ref = Eigen::MatrixXd());
/// <summary> Compute the covariance matrix of features vector with the selected method. </summary>
/// <param name="in"> The Feature Vector. </param>
/// <param name="out"> The covariance out. </param>
/// <param name="tangent"> (Optional) True to use tangent space featurization, Upper Triangle Squeeze if false. </param>
/// <param name="ref"> The reference Matrix (usefull for Tangent Space Featurization). </param>
/// <returns> <c>True</c> if it succeeds, <c>False</c> otherwise. </returns>
bool UnFeaturization(const Eigen::RowVectorXd& in, Eigen::MatrixXd& out, bool tangent = true, const Eigen::MatrixXd& ref = Eigen::MatrixXd());
/// <summary> Squeeze the upper triangle of \f$N \times N\f$ square matrix to a \f$\frac{N\left(N+1\right)}{2}\f$ Vector.
/// <table align="center" border="0">
/// <tr><th>Upper Triangle Matrix</th><th></th><th>Row Major Upper Triangle Squeeze</th> <th></th> <th>Diagonal Major Upper Triangle Squeeze</th></tr>
/// <tr><td>\f[ \begin{pmatrix} a&b&c\\d&e&f\\g&h&i \end{pmatrix} \Rightarrow \begin{pmatrix} a&b&c\\0&e&f\\0&0&i \end{pmatrix} \f]</td>
/// <td><pre> </pre></td>
/// <td>\f[ \begin{pmatrix} a&b&c\\d&e&f\\g&h&i \end{pmatrix} \Rightarrow \begin{pmatrix} a&b&c&e&f&i \end{pmatrix} \f]</td>
/// <td><pre> </pre></td>
/// <td>\f[\begin{pmatrix} a&b&c\\d&e&f\\g&h&i \end{pmatrix} \Rightarrow \begin{pmatrix} a&e&i&b&f&c \end{pmatrix} \f]</td></tr>
/// </table>
/// </summary>
/// <param name="in"> The \f$N \times N\f$ square matrix. </param>
/// <param name="out"> The \f$\frac{N\left(N+1\right)}{2}\f$ Vector. </param>
/// <param name="rowMajor"> Get the values row by row if true, diagonal by diagonal if false. </param>
/// <returns> <c>True</c> if it succeeds, <c>False</c> otherwise. </returns>
bool SqueezeUpperTriangle(const Eigen::MatrixXd& in, Eigen::RowVectorXd& out, bool rowMajor = true);
/// <summary> Compute the upper triangle of \f$\frac{N\left(N+1\right)}{2}\f$ Vector to a \f$N \times N\f$ square matrix.
/// <table align="center" border="0">
/// <tr><th>Row Major Method</th> <th></th> <th>Diagonal Major Method</th></tr>
/// <tr><td>\f[ \begin{pmatrix} a&b&c&d&e&f \end{pmatrix} \Rightarrow \begin{pmatrix} a&b&c\\0&d&e\\0&0&f \end{pmatrix} \f]</td>
/// <td><pre> </pre></td>
/// <td>\f[ \begin{pmatrix} a&b&c&d&e&f \end{pmatrix} \Rightarrow \begin{pmatrix} a&d&f\\0&b&e\\0&0&c \end{pmatrix} \f]</td></tr>
/// </table>
/// </summary>
/// <param name="in"> The \f$\frac{N\left(N+1\right)}{2}\f$ Vector. </param>
/// <param name="out"> The \f$N \times N\f$ square matrix. </param>
/// <param name="rowMajor"> Get the values row by row if true, diagonal by diagonal if false. </param>
/// <returns> <c>True</c> if it succeeds, <c>False</c> otherwise. </returns>
bool UnSqueezeUpperTriangle(const Eigen::RowVectorXd& in, Eigen::MatrixXd& out, bool rowMajor = true);
/// <summary> Project a covariance matrices (\f$M\f$) in the tangent space (\f$\mathcal{T}\f$) according to the given reference point (\f$M_\text{Ref}\f$). <br/>
///
/// - Compute the transformation matrix for the covariance matrix \f$M\f$ with the reference matrix \f$M_\text{Ref}\f$ and squeeze ths matrix (see <see cref="SqueezeUpperTriangle"/>).
/// \f[
/// \begin{aligned}
/// J &= \log{\left(M_\text{Ref}^{-1/2} \times M \times M_\text{Ref}^{-1/2}\right)}\\
/// V_J &= \operatorname{SqueezeUpperTriangle}(J)
/// \end{aligned}
/// \f]
/// - Compute a coefficient Vector to apply to transformation vector.
/// \f[
/// \begin{aligned}
/// M_\text{Coeffs} &= \begin{pmatrix}
/// 1 & \sqrt{2} & \cdots & \sqrt{2} \\
/// 0 & 1 & \ddots & \sqrt{2} \\
/// \vdots & \ddots & \ddots & \vdots\\
/// 0 & \cdots & \cdots & 1
/// \end{pmatrix}\\
/// V_\text{Coeffs} &= \operatorname{SqueezeUpperTriangle}(M_\text{Coeffs})\\
/// \end{aligned}
/// \f]
/// - Compute the element wise product of the two vectors to have the tangent space Projection \f$\zeta_M\f$
/// \f[ \zeta_M = V_J \odot V_\text{Coeffs} \f]
/// </summary>
/// <param name="in"> The \f$N \times N\f$ covariance matrix. </param>
/// <param name="out"> The \f$\frac{N\left(N+1\right)}{2}\f$ row. </param>
/// <param name="ref"> (Optional) The \f$N \times N\f$ reference in (use the identity Matrix if empty). </param>
/// <returns> <c>True</c> if it succeeds, <c>False</c> otherwise. </returns>
bool TangentSpace(const Eigen::MatrixXd& in, Eigen::RowVectorXd& out, const Eigen::MatrixXd& ref = Eigen::MatrixXd());
/// <summary> Project a Tangent space vectors in the manifold according to the given reference point. <br/>
/// \f[
/// \begin{aligned}
/// \text{With : } M_\text{Ts} &= \operatorname{UnSqueezeUpperTriangle}(V_\text{Ts}) \quad \text{ and } \quad \mathsf{U}_{M}\text{ the upper triangular out.}\\
/// M_\text{Coeffs} &= \operatorname{diag}\left(M_\text{Ts}\right) + \frac{\mathsf{U}_{M_\text{Ts}} + \mathsf{U}_{M_\text{Ts}}^{\mathsf{T}}}{\sqrt{2}}\\
/// \Rightarrow M &= M_\text{Ref}^{1/2} ~ \exp{\left(M_\text{Coeffs}\right)} ~ M_\text{Ref}^{1/2}
/// \end{aligned}
/// \f]
/// </summary>
/// <param name="in"> The \f$\frac{N\left(N+1\right)}{2}\f$ row. </param>
/// <param name="out"> The \f$N \times N\f$ covariance matrix. </param>
/// <param name="ref"> (Optional) The \f$N \times N\f$ reference out (use the identity Matrix if empty). </param>
/// <returns> <c>True</c> if it succeeds, <c>False</c> otherwise. </returns>
bool UnTangentSpace(const Eigen::RowVectorXd& in, Eigen::MatrixXd& out, const Eigen::MatrixXd& ref = Eigen::MatrixXd());
} // namespace Geometry
@@ -0,0 +1,72 @@
///-------------------------------------------------------------------------------------------------
///
/// \file Geodesic.hpp
/// \brief All functions to estimate the Geodesic position of two Covariance Matrix.
/// \author Thibaut Monseigne (Inria).
/// \version 1.0.
/// \date 26/10/2018.
/// \copyright <a href="https://choosealicense.com/licenses/agpl-3.0/">GNU Affero General Public License v3.0</a>.
/// \remarks
/// - List of Metrics inspired by the work of Alexandre Barachant : <a href="https://github.com/alexandrebarachant/pyRiemann">pyRiemann</a> (<a href="https://github.com/alexandrebarachant/pyRiemann/blob/master/LICENSE">License</a>).
///
///-------------------------------------------------------------------------------------------------
#pragma once
#include "geometry/Metrics.hpp"
#include <Eigen/Dense>
namespace Geometry {
/// <summary> Compute the matrix at the position alpha on the geodesic between A and B with the selected \p metric.\n
/// - Allowed Metrics : <c>Riemann</c>, <c>Euclidian</c>, <c>LogEuclidian</c>, <c>Identity</c>
/// </summary>
/// <param name="a"> The First Covariance matrix. </param>
/// <param name="b"> The Second Covariance matrix. </param>
/// <param name="g"> The Geodesic. </param>
/// <param name="metric"> (Optional) The metric (see <see cref="EMetric"/>). </param>
/// <param name="alpha"> (Optional) Position on the Geodesic : \f$ 0\leq \text{alpha} \leq 1\f$. </param>
/// <returns> <c>True</c> if it succeeds, <c>False</c> otherwise. </returns>
bool Geodesic(const Eigen::MatrixXd& a, const Eigen::MatrixXd& b, Eigen::MatrixXd& g, EMetric metric = EMetric::Riemann, double alpha = 0.5);
/// <summary> Compute the matrix at the position alpha on the Riemannian geodesic between A and B. \n
/// \f[ \gamma_\text{R} = A^{1/2} ~ \left( A^{-1/2} ~ B ~ A^{-1/2} \right)^\alpha ~ A^{1/2} \f]
/// </summary>
/// <param name="a"> The First Covariance matrix. </param>
/// <param name="b"> The Second Covariance matrix. </param>
/// <param name="g"> The Geodesic. </param>
/// <param name="alpha"> (Optional) Position on the Geodesic : \f$ 0\leq \text{alpha} \leq 1\f$. </param>
/// <returns> <c>True</c> if it succeeds, <c>False</c> otherwise. </returns>
bool GeodesicRiemann(const Eigen::MatrixXd& a, const Eigen::MatrixXd& b, Eigen::MatrixXd& g, double alpha = 0.5);
/// <summary> Compute the matrix at the position alpha on the Euclidean geodesic between A and B.\n
/// \f[ \gamma_\text{E} = \left(1 - \alpha \right) \times A + \alpha \times B \f]
/// </summary>
/// <param name="a"> The First Covariance matrix. </param>
/// <param name="b"> The Second Covariance matrix. </param>
/// <param name="g"> The Geodesic. </param>
/// <param name="alpha"> (Optional) Position on the Geodesic : \f$ 0\leq \text{alpha} \leq 1\f$. </param>
/// <returns> <c>True</c> if it succeeds, <c>False</c> otherwise. </returns>
bool GeodesicEuclidian(const Eigen::MatrixXd& a, const Eigen::MatrixXd& b, Eigen::MatrixXd& g, double alpha = 0.5);
/// <summary> Compute the matrix at the position alpha on the Log Euclidean geodesic between A and B. \n
/// \f[ \gamma_\text{LogE} = \exp\left(\left(1 - \alpha \right) \times \log\left(A\right) + \alpha \times \log\left(B\right) \right)\f]
/// </summary>
/// <param name="a"> The First Covariance matrix. </param>
/// <param name="b"> The Second Covariance matrix. </param>
/// <param name="g"> The Geodesic. </param>
/// <param name="alpha"> (Optional) Position on the Geodesic : \f$ 0\leq \text{alpha} \leq 1\f$. </param>
/// <returns> <c>True</c> if it succeeds, <c>False</c> otherwise. </returns>
bool GeodesicLogEuclidian(const Eigen::MatrixXd& a, const Eigen::MatrixXd& b, Eigen::MatrixXd& g, double alpha = 0.5);
/// <summary> Compute the matrix at the position alpha on the Identity geodesic. \n
/// \f[ \gamma_\text{I} = I_N \f]
/// </summary>
/// <param name="a"> The First Covariance matrix. </param>
/// <param name="b"> The Second Covariance matrix. </param>
/// <param name="g"> The Geodesic. </param>
/// <param name="alpha"> (Optional) Position on the Geodesic : \f$ 0\leq \text{alpha} \leq 1\f$. </param>
/// <returns> <c>True</c> if it succeeds, <c>False</c> otherwise. </returns>
bool GeodesicIdentity(const Eigen::MatrixXd& a, const Eigen::MatrixXd& b, Eigen::MatrixXd& g, double alpha = 0.5);
} // namespace Geometry
@@ -0,0 +1,163 @@
///-------------------------------------------------------------------------------------------------
///
/// \file Mean.hpp
/// \brief All functions to estimate the mean of Vector of Covariance Matrix.
/// \author Thibaut Monseigne (Inria).
/// \version 1.0.
/// \date 26/10/2018.
/// \copyright <a href="https://choosealicense.com/licenses/agpl-3.0/">GNU Affero General Public License v3.0</a>.
/// \remarks
/// - List of Metrics inspired by the work of Alexandre Barachant : <a href="https://github.com/alexandrebarachant/pyRiemann">pyRiemann</a> (<a href="https://github.com/alexandrebarachant/pyRiemann/blob/master/LICENSE">License</a>).
/// - The Approximate joint diagonalization based on pham's algorithm is not implemented.
/// - The Approximate joint diagonalization based log-Euclidean (ALE) Mean doesn't work => Need to implement <see cref="AJDPham"/> and check if it works next.
/// - The Wasserstein Mean Doesn't work so good (after \f$10^{-3}\f$ precision with the pyriemann library).
///
///-------------------------------------------------------------------------------------------------
#pragma once
#include "geometry/Metrics.hpp"
#include <Eigen/Dense>
#include <vector>
namespace Geometry {
/// <summary> Compute the mean of vector of covariance matrix with the selected \p metric. </summary>
/// <param name="covs"> Vector of Covariance Matrix. </param>
/// <param name="mean"> The computed mean. </param>
/// <param name="metric"> (Optional) The metric (see <see cref="EMetric"/>). </param>
/// <returns> <c>True</c> if it succeeds, <c>False</c> otherwise. </returns>
bool Mean(const std::vector<Eigen::MatrixXd>& covs, Eigen::MatrixXd& mean, EMetric metric = EMetric::Riemann);
/// <summary> Approximate Joint Diagonalization based on pham's algorithm.\n
/// \f[ C_\text{AJD} = \cdots \f]
/// </summary>
/// <param name="covs"> Vector of Covariance Matrix. </param>
/// <param name="ajd"> The computed Approximate Joint Diagonalization. </param>
/// <param name="epsilon"> (Optional) The epsilon. </param>
/// <param name="maxIter"> (Optional) The maximum iterator. </param>
/// <returns> <c>True</c> if it succeeds, <c>False</c> otherwise. </returns>
/// \todo Not implemented.
bool AJDPham(const std::vector<Eigen::MatrixXd>& covs, Eigen::MatrixXd& ajd, double epsilon = 0.0001, int maxIter = 15);
/// <summary> Compute the Mean with the Riemannian Mean.\n
/// -# Compute the Classical Mean \f$ C_{\mu_\text{E}} \f$ (see <see cref="MeanEuclidian"/>)
/// -# Update with an iterative procedure that stops after 50 iterations or when one of two criterions is under \f$ 10^{-4}\f$
///
/// \f[ C_{\mu_\text{R}} = C_{\mu_\text{E}} \\ \nu=1.0 \\ \tau=+\infty \f]
/// Iterative process with \f$J\f$ while \f$ \text{iteration} < 50 \f$ and \f$ 10^{-4} < \left\lVert J \right\rVert \f$ and \f$ 10^{-4} < \nu \f$
/// \f[ \begin{aligned}
/// J &= \frac{1}{N} \sum_i \log\left(C_{\mu_\text{R}}^{-1/2} + C_i ~ C_{\mu_\text{R}}^{-1/2}\right)\\
/// C_{\mu_\text{R}} &= C_{\mu_\text{R}}^{1/2} ~ \exp(\nu \times J) ~ C_{\mu_\text{R}}^{1/2}\\
/// \end{aligned}
/// \f]
/// \f[ \begin{cases}
/// \text{if } \nu \times \left\lVert J \right\rVert < \tau & \nu = 0.95 \times \nu,~\tau = \nu \times \left\lVert J \right\rVert\\
/// \text{otherwise } & \nu = 0.5 \times \nu
/// \end{cases}
/// \f]
/// </summary>
/// <param name="covs"> Vector of Covariance Matrix. </param>
/// <param name="mean"> The mean. </param>
/// <returns> <c>True</c> if it succeeds, <c>False</c> otherwise. </returns>
bool MeanRiemann(const std::vector<Eigen::MatrixXd>& covs, Eigen::MatrixXd& mean);
/// <summary> Compute the Euclidian Mean.\n
/// \f[ C_{\mu_\text{E}} =\frac{1}{N} \sum_i{C_i}\f]
/// </summary>
/// <param name="covs"> Vector of Covariance Matrix. </param>
/// <param name="mean"> The mean. </param>
/// <returns> <c>True</c> if it succeeds, <c>False</c> otherwise. </returns>
bool MeanEuclidian(const std::vector<Eigen::MatrixXd>& covs, Eigen::MatrixXd& mean);
/// <summary> Compute the Log Euclidiean Mean.\n
/// \f[ C_{\mu_\text{lE}} =\exp\left(\frac{1}{N} \sum_i{\log\left(C_i\right)}\right)\f]
/// </summary>
/// <param name="covs"> Vector of Covariance Matrix. </param>
/// <param name="mean"> The mean. </param>
/// <returns> <c>True</c> if it succeeds, <c>False</c> otherwise. </returns>
bool MeanLogEuclidian(const std::vector<Eigen::MatrixXd>& covs, Eigen::MatrixXd& mean);
/// <summary> Compute the Log Determinant Mean.\n
/// -# Compute the Classical Mean \f$ C_{\mu_\text{E}} \f$ (see <see cref="MeanEuclidian"/>)
/// -# Update with an iterative procedure that stops after 50 iterations or when criterion is under \f$ 10^{-4}\f$
///
/// \f[ C_{\mu_\text{lD}} = C_{\mu_\text{E}}\f]
/// Iterative process with \f$J\f$ while \f$ \text{iteration} < 50 \f$ and \f$ 10^{-4} < \left\lVert J-C_\mu \right\rVert \f$
/// \f[ \begin{aligned}
/// J &= \left(\frac{1}{N} \sum_i \left( 0.5 \times\left(C_{\mu_\text{lD}} + C_i \right)\right)^{-1} \right)^{-1}\\
/// C_{\mu_\text{lD}} &= J
/// \end{aligned}\f]
/// </summary>
/// <param name="covs"> Vector of Covariance Matrix. </param>
/// <param name="mean"> The mean. </param>
/// <returns> <c>True</c> if it succeeds, <c>False</c> otherwise. </returns>
bool MeanLogDet(const std::vector<Eigen::MatrixXd>& covs, Eigen::MatrixXd& mean);
/// <summary> Compute the Kullback Mean.\n
/// The mean is the Geodesic center between the Euclidian and the Harmonic Mean.\n
/// \f[ C_{\mu_\text{K}} = \gamma \left( C_{\mu_{\text{E}}}, C_{\mu_{\text{H}}} \right) \f]
/// </summary>
/// <param name="covs"> Vector of Covariance Matrix. </param>
/// <param name="mean"> The mean. </param>
/// <returns> <c>True</c> if it succeeds, <c>False</c> otherwise. </returns>
bool MeanKullback(const std::vector<Eigen::MatrixXd>& covs, Eigen::MatrixXd& mean);
/// <summary> Compute the Wasserstein Mean.\n
/// -# Compute the Classical Mean \f$ C_{\mu_\text{E}} \f$ (see <see cref="MeanEuclidian"/>)
/// -# Update with an iterative procedure that stops after 50 iterations or when criterion is under \f$ 10^{-4}\f$
///
/// \f[ C_{\mu_\text{W}} = C_{\mu_{\text{E}}}\f]
/// Iterative process with \f$J\f$ while \f$ \text{iteration} < 50 \f$ and \f$ 10^{-4} < \left\lVert J-J_{-1} \right\rVert \f$
/// \f[ \begin{aligned}
/// J &= C_{\mu_\text{W}}^{1/2}\\
/// J &= \left(\frac{1}{N} \sum_i \left( J C_i J \right)^{1/2} \right)^{1/2}\\
/// \end{aligned}\f]
/// After the Iterative process : \f$ C_{\mu_\text{W}} = J*J \f$
/// </summary>
/// <param name="covs"> Vector of Covariance Matrix. </param>
/// <param name="mean"> The mean. </param>
/// <returns> <c>True</c> if it succeeds, <c>False</c> otherwise. </returns>
/// \todo Doesn't work so good (after \f$10^{-3}\f$ precision with the pyriemann library).
bool MeanWasserstein(const std::vector<Eigen::MatrixXd>& covs, Eigen::MatrixXd& mean);
/// <summary> Compute the Approximate joint diagonalization based log-Euclidean (ALE) Mean. \n
/// -# Compute the Approximate Joint Diagonalization \f$ C_\text{AJD} \f$ (see <see cref="AJDPham"/>)
/// -# Update with an iterative procedure that stops after 50 iterations or when criterion is under \f$ 10^{-4}\f$
///
/// \f[ C_{\mu_\text{ALE}} = C_\text{AJD}\f]
/// Iterative process with \f$J\f$ (and \f$U = \operatorname{diag}(\operatorname{diag}(\exp(J))\f$) while \f$ \text{iteration} < 50 \f$ and \f$ 10^{-4} < d_\text{R}(I_N,U) \f$
/// \f[ \begin{aligned}
/// J &= \frac{1}{N} \log\left(\sum_i \left( C_{\mu_\text{ALE}}^{\mathsf{T}} C_i C_{\mu_\text{ALE}} \right) \right)\\
/// U &= \operatorname{diag}(\operatorname{diag}(\exp(J))\\
/// C_{\mu_\text{ALE}} &= C_{\mu_\text{ALE}} * U^{-1/2}\\
/// \end{aligned}\f]
/// After the Iterative process :
/// \f[ \begin{aligned}
/// J &= \frac{1}{N} \log\left(\sum_i \left( C_{\mu_\text{ALE}}^{\mathsf{T}} C_i C_{\mu_\text{ALE}} \right) \right)\\
/// C_{\mu_\text{ALE}} &= \left(C_{\mu_\text{ALE}}^{-1}\right)^{\mathsf{T}} ~ \exp(J) ~ C_{\mu_\text{ALE}}^{-1}
/// \end{aligned}\f]
/// </summary>
/// <param name="covs"> Vector of Covariance Matrix. </param>
/// <param name="mean"> The mean. </param>
/// <returns> <c>True</c> if it succeeds, <c>False</c> otherwise. </returns>
/// \todo Doesn't work => Need to implement <see cref="AJDPham"/> and check if it works next.
bool MeanALE(const std::vector<Eigen::MatrixXd>& covs, Eigen::MatrixXd& mean);
/// <summary> Compute the Harmonic Mean.\n
/// \f[ C_{\mu_\text{H}} = (\frac{1}{N} \sum_i{C_i}^{-1})^{-1} \f]
/// </summary>
/// <param name="covs"> Vector of Covariance Matrix. </param>
/// <param name="mean"> The mean. </param>
/// <returns> <c>True</c> if it succeeds, <c>False</c> otherwise. </returns>
bool MeanHarmonic(const std::vector<Eigen::MatrixXd>& covs, Eigen::MatrixXd& mean);
/// <summary> Give the Identity Matrix.\n
/// \f[ C_{\mu_\text{I}} = I_N \f]
/// </summary>
/// <param name="covs"> Vector of Covariance Matrix. </param>
/// <param name="mean"> The mean. </param>
/// <returns> <c>True</c> if it succeeds, <c>False</c> otherwise. </returns>
bool MeanIdentity(const std::vector<Eigen::MatrixXd>& covs, Eigen::MatrixXd& mean);
} // namespace Geometry
@@ -0,0 +1,108 @@
///-------------------------------------------------------------------------------------------------
///
/// \file Median.hpp
/// \brief All Median functions for array or matrix.
/// \author Thibaut Monseigne (Inria).
/// \version 1.0.
/// \date 29/07/2020.
/// \copyright <a href="https://choosealicense.com/licenses/agpl-3.0/">GNU Affero General Public License v3.0</a>.
/// \remarks This algortihms is inspired by the plugin clean_rawdata in <a href="https://sccn.ucsd.edu/eeglab/index.php">EEGLAB</a> (<a href="https://github.com/sccn/clean_rawdata/blob/master/LICENSE">License</a>).
///
///-------------------------------------------------------------------------------------------------
#pragma once
#include <Eigen/Dense>
#include <vector>
#include "geometry/Metrics.hpp"
namespace Geometry {
//---------------------------------------------------------------------------
//------------------------------ Matrix Median ------------------------------
//---------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
/// <summary> Find the median of stl vector. </summary>
/// <typeparam name="T"> The type of the values (only arithmetic type). </typeparam>
/// <param name="v"> the vector of values. </param>
/// <returns> The median of vector. </returns>
template <typename T, typename = typename std::enable_if<std::is_arithmetic<T>::value, T>::type>
T Median(const std::vector<T>& v)
{
std::vector<T> tmp = v;
const size_t n = tmp.size() / 2; // Where is the middle (if odd number of value the decimal part is floor by cast)
std::stable_sort(tmp.begin(), tmp.end()); // We sort all because nth_element doesn't have same behaviour in Windows and Unix
return (tmp.size() % 2 == 0) ? (tmp[n] + tmp[n - 1]) / 2 : tmp[n]; // For Even number of value we take the mean of the two middle value
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
/// <summary> Find the median of values of the Eigen Matrix. </summary>
/// <param name="m"> the matrix. </param>
/// <returns> The median of matrix. </returns>
double Median(const Eigen::MatrixXd& m);
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
/// <summary> Compute the median of vector of matrix with the Weiszfeld's algorithm. <br/>
/// To compute this median, we start by computing the initial median of the dataset by taking each element of the matrices independently.
/// That is to say that for the element at position i, j(a_i, j) of the matrices, we computes the median of the elements a_i, j of all the matrices of the dataset.
/// We thus have an initial median for our dataset. <br/>
/// Then, we refine our median by the iterative algorithm of Weiszfeld:
/// - We remove the median in our dataset.
/// - For each new matrices, we compute the norm.
/// - We sum the the matrices in initial dataset (divided by their own norm) and we normalize the result by the sum of inverse norms.
/// - We iterate this previous step until we have a difference between the old and new median is under an epsilon or that the number of iterations is above the limit.
/// </summary>
/// <param name="matrices"> Vector of Matrix. </param>
/// <param name="median"> The computed median. </param>
/// <param name="epsilon"> (Optional) The epsilon value to stop algorithm. </param>
/// <param name="maxIter"> (Optional) The maximum iteration allowed to find best Median. </param>
/// <returns> <c>True</c> if it succeeds, <c>False</c> otherwise. </returns>
/// <remarks> it's an iterative algorithm, so we have a limit of iterations and an epsilon value to consider the calculation as satisfactory. </remarks>
bool MedianEuclidian(const std::vector<Eigen::MatrixXd>& matrices, Eigen::MatrixXd& median, const double epsilon = 0.0001, const size_t maxIter = 50);
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
/// <summary> Compute the median of vector of matrix with the Riemman Barycentre. <br/>
/// - Initialize the median with the euclidian mean of matrices.
/// - Iterate until the stop criterion (<c>iteration</c> over <c>maxIter</c> or \f$\text{gain}\f$ under <c>epsilon</c>).
/// - Compute the tangent space projection of each matrices with median as reference.
/// - Compute the sum (\f$\mathcal{S}\f$) of euclidian distance of each tangent space projection. <br/>
/// \f[ \delta_E=\sqrt{\sum_{i \in N}{x_i^2}} \quad \text{with } x_i \text{ the feature } i \text{ of the tangent space projection}\f]
/// - Compare with previous sum and stop if \f$\text{gain} < \varepsilon\f$. <br/>
/// \f[ \text{gain} = \left|\frac{\mathcal{S} - \mathcal{S}_\text{prev}}{\mathcal{S}_\text{prev}}\right| \f]
/// - Compute Median of each feature \f$i\f$ of tangent space projection.
/// - Transform this tangent space projection median to riemann space with previous median as reference and update the median by this new matrix.
/// </summary>
/// <param name="matrices"> Vector of Matrix. </param>
/// <param name="median"> The computed median. </param>
/// <param name="epsilon"> (Optional) The epsilon value to stop algorithm. </param>
/// <param name="maxIter"> (Optional) The maximum iteration allowed to find best Median. </param>
/// <returns> <c>True</c> if it succeeds, <c>False</c> otherwise. </returns>
bool MedianRiemann(const std::vector<Eigen::MatrixXd>& matrices, Eigen::MatrixXd& median, const double epsilon = 0.0001, const size_t maxIter = 50);
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
/// <summary> Give the identity matrix has median. </summary>
/// <param name="matrices"> Vector of Matrix. </param>
/// <param name="median"> The computed median. </param>
/// <returns> <c>True</c> if it succeeds, <c>False</c> otherwise. </returns>
bool MedianIdentity(const std::vector<Eigen::MatrixXd>& matrices, Eigen::MatrixXd& median);
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
/// <summary> Compute the median of vector of matrix with the Weiszfeld's algorithm for Euclidian Metric and Riemman Barycentre. </summary>
/// <param name="matrices"> Vector of Matrix. </param>
/// <param name="median"> The computed median. </param>
/// <param name="epsilon"> (Optional) The epsilon value to stop algorithm. </param>
/// <param name="maxIter"> (Optional) The maximum iteration allowed to find best Median. </param>
/// <param name="metric"> (Optional) THe metric to use. </param>
/// <returns> <c>True</c> if it succeeds, <c>False</c> otherwise. </returns>
/// <remarks> it's an iterative algorithm, so we have a limit of iterations and an epsilon value to consider the calculation as satisfactory. </remarks>
bool Median(const std::vector<Eigen::MatrixXd>& matrices, Eigen::MatrixXd& median,
const double epsilon = 0.0001, const size_t maxIter = 50, const EMetric& metric = EMetric::Euclidian);
//-------------------------------------------------------------------------------------------------
} // namespace Geometry
@@ -0,0 +1,69 @@
///-------------------------------------------------------------------------------------------------
///
/// \file Metrics.hpp
/// \brief All Metrics.
/// \author Thibaut Monseigne (Inria).
/// \version 1.0.
/// \date 26/10/2018.
/// \copyright <a href="https://choosealicense.com/licenses/agpl-3.0/">GNU Affero General Public License v3.0</a>.
/// \remarks
/// - List of Metrics inspired by the work of Alexandre Barachant : <a href="https://github.com/alexandrebarachant/pyRiemann">pyRiemann</a> (<a href="https://github.com/alexandrebarachant/pyRiemann/blob/master/LICENSE">License</a>).
///
///-------------------------------------------------------------------------------------------------
#pragma once
#include <string>
namespace Geometry {
/// <summary> Enumeration of metrics. Inspired by the work of Alexandre Barachant : <a href="https://github.com/alexandrebarachant/pyRiemann">pyRiemann</a>. </summary>
enum class EMetric
{
Riemann, ///< The Riemannian Metric.
Euclidian, ///< The Euclidian Metric.
LogEuclidian, ///< The Log Euclidian Metric.
LogDet, ///< The Log Determinant Metric.
Kullback, ///< The Kullback Metric.
ALE, ///< The AJD-based log-Euclidean (ALE) Metric.
Harmonic, ///< The Harmonic Metric.
Wasserstein, ///< The Wasserstein Metric.
Identity ///< The Identity Metric.
};
/// <summary> Convert metric to string. </summary>
/// <param name="metric"> The metric. </param>
/// <returns> <c>std::string</c> </returns>
inline std::string toString(const EMetric metric)
{
switch (metric)
{
case EMetric::Riemann: return "Riemann";
case EMetric::Euclidian: return "Euclidian";
case EMetric::LogEuclidian: return "Log Euclidian";
case EMetric::LogDet: return "Log Determinant";
case EMetric::Kullback: return "Kullback";
case EMetric::ALE: return "AJD-based log-Euclidean";
case EMetric::Harmonic: return "Harmonic";
case EMetric::Wasserstein: return "Wasserstein";
case EMetric::Identity: return "Identity";
}
return "Invalid Metric";
}
/// <summary> Convert string to metric. </summary>
/// <param name="metric"> The metric. </param>
/// <returns> <see cref="EMetric"/> </returns>
inline EMetric StringToMetric(const std::string& metric)
{
if (metric == "Riemann") { return EMetric::Riemann; }
if (metric == "Euclidian") { return EMetric::Euclidian; }
if (metric == "Log Euclidian") { return EMetric::LogEuclidian; }
if (metric == "Log Determinant") { return EMetric::LogDet; }
if (metric == "Kullback") { return EMetric::Kullback; }
if (metric == "AJD-based log-Euclidean") { return EMetric::ALE; }
if (metric == "Harmonic") { return EMetric::Harmonic; }
if (metric == "Wasserstein") { return EMetric::Wasserstein; }
return EMetric::Identity;
}
} // namespace Geometry
@@ -0,0 +1,104 @@
///-------------------------------------------------------------------------------------------------
///
/// \file Misc.hpp
/// \brief All misc functions.
/// \author Thibaut Monseigne (Inria).
/// \version 1.0.
/// \date 29/07/2020.
/// \copyright <a href="https://choosealicense.com/licenses/agpl-3.0/">GNU Affero General Public License v3.0</a>.
/// \remarks This algortihms is inspired by the plugin clean_rawdata in <a href="https://sccn.ucsd.edu/eeglab/index.php">EEGLAB</a> (<a href="https://github.com/sccn/clean_rawdata/blob/master/LICENSE">License</a>).
///
///-------------------------------------------------------------------------------------------------
#pragma once
#include <Eigen/Dense>
#include <vector>
#include "geometry/Metrics.hpp"
namespace Geometry {
//-------------------------------------------------------------------
//------------------------------ Range ------------------------------
//-------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
/// <summary> Create a range of double value. </summary>
/// <param name="begin"> Beginning of the range. </param>
/// <param name="end"> End of the range. </param>
/// <param name="step"> Step of the range. </param>
/// <param name="closed"> Authorize the end in range if <c>True</c>. </param>
/// <returns> The range vector. </returns>
/// <remarks>Use [std::iota](https://en.cppreference.com/w/cpp/algorithm/iota) function and a struct for this specific used. </remarks>
std::vector<double> doubleRange(const double begin, const double end, const double step = 1.0, const bool closed = true);
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
/// <summary> Create a range of index with double value rounded. </summary>
/// <param name="begin"> Beginning of the range. </param>
/// <param name="end"> End of the range. </param>
/// <param name="step"> Step of the range. </param>
/// <param name="closed"> Authorize the end in range if <c>True</c>. </param>
/// <param name="unique"> Remove duplicate value if <c>True</c>. </param>
/// <returns> The range vector. </returns>
/// <remarks>Use [std::iota](https://en.cppreference.com/w/cpp/algorithm/iota) function and a struct for this specific used. </remarks>
std::vector<size_t> RoundIndexRange(const double begin, const double end, const double step, const bool closed = true, const bool unique = true);
//-------------------------------------------------------------------------------------------------
//------------------------------------------------------------------------------
//------------------------------ Fit Distribution ------------------------------
//------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
/// <summary> Commputes histogram of dataset extended in <c>n</c> bins, bins are computed from \f$[0;max]\f$ (values) to \f$[0;n]\f$ (bins). </summary>
/// <param name="dataset"> Input vector (all datas are positive). </param>
/// <param name="n"> Number of bin of the final histogram. </param>
std::vector<size_t> BinHist(const std::vector<double>& dataset, const size_t n);
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
/// <summary> Get a Fit distribution. </summary>
/// <param name="values"> The values. </param>
/// <param name="mu"> The mu. </param>
/// <param name="sigma"> The sigma. </param>
/// <param name="betas"> List of wanted \f$\beta\f$ shapes. </param>
/// <param name="minQuant"> Minimum of wanted quantile (in range [0, 1]). </param>
/// <param name="maxQuant"> Maximum of wanted quantile (in range [0, 1]). </param>
/// <param name="minClean"> Minimum of estimated clean datas (only positive value). </param>
/// <param name="maxDropout"> Maximum of estimated artifact datas (only positive value). </param>
/// <param name="stepBound"> Step used to select beginning of datas subset (in range [0.0001, 0.1]). </param>
/// <param name="stepScale"> Step used to select size of datas subset (in range [0.0001, 0.1]). </param>
/// <returns> <c>True</c> if it succeeds, <c>False</c> otherwise. </returns>
bool FitDistribution(const std::vector<double>& values, double& mu, double& sigma,
const std::vector<double>& betas = doubleRange(1.7, 3.5, 0.15),
const double minQuant = 0.022, const double maxQuant = 0.60,
const double minClean = 0.250, const double maxDropout = 0.10,
const double stepBound = 0.010, const double stepScale = 0.01);
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------
//------------------------------ Riemannian Eigen Values ------------------------------
//-------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
/// <summary> Compute sorted eigen vector of the matrix. </summary>
/// <param name="matrix"> the input matrix. </param>
/// <param name="vectors"> Sorted eigen vectors. </param>
/// <param name="values"> Sorted eigen values. </param>
/// <param name="metric"> metric used for vectors. </param>
/// <remarks> Actually only euclidian method is implemented. <br/>
/// For Riemmanian metric, we must have some optimisation algorithm. </remarks>
void sortedEigenVector(const Eigen::MatrixXd& matrix, Eigen::MatrixXd& vectors, std::vector<double>& values, const EMetric metric = EMetric::Euclidian);
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
/// <summary> Compute the eigen vector of the input matrix. </summary>
/// <param name="matrix"> input Matrix. </param>
/// <param name="vectors"> Sorted eigen vectors. </param>
/// <param name="values"> Sorted eigen values. </param>
/// <returns> <c>True</c> if it succeeds, <c>False</c> otherwise. </returns>
/// <remarks> This algorithm is in <a href="https://sccn.ucsd.edu/eeglab/index.php">EEGLAB</a> plugin and inspired by the paper "A Riemannian Newton Algorithm for Nonlinear Eigenvalue Problems", Zhi Zhao, Zheng - Jian Bai, and Xiao - Qing Jin, SIAM Journal on Matrix Analysisand Applications, 36(2), 752 - 774, 2015. </remarks>
//bool RiemannianNonLinearEigenVector(const Eigen::MatrixXd& matrix, Eigen::MatrixXd& vectors, std::vector<double>& values);
//-------------------------------------------------------------------------------------------------
} // namespace Geometry
@@ -0,0 +1,161 @@
///-------------------------------------------------------------------------------------------------
///
/// \file CASR.hpp
/// \brief Class used to use Artifact Subspace Reconstruction Algorithm.
/// \author Thibaut Monseigne (Inria).
/// \version 1.0.
/// \date 27/08/2020.
/// \copyright <a href="https://choosealicense.com/licenses/agpl-3.0/">GNU Affero General Public License v3.0</a>.
///
///-------------------------------------------------------------------------------------------------
#pragma once
#include <string>
#include <vector>
#include <Eigen/Dense>
#include "geometry/Basics.hpp"
#include "geometry/Metrics.hpp"
namespace Geometry {
/// <summary> Class For Artifact Subspace Reconstruction (ASR) Algorithm. </summary>
class CASR
{
public:
CASR() = default; ///< Initializes a new instance of the <see cref="CASR"/> class.
/// <summary> Initializes a new instance of the <see cref="CASR"/> class with specified <c>metric</c>. </summary>
/// <remarks> Only Euclidian and Riemmann metrics are implemented If other is selected, Euclidian is used. </remarks>
explicit CASR(const EMetric& metric) { setMetric(metric); }
/// <summary> Initializes a new instance of the <see cref="CASR"/> class with specified <c>metric</c> and train with the specified <c>dataset</c>. </summary>
/// <remarks> Only Euclidian and Riemmann metrics are implemented If other is selected, Euclidian is used. </remarks>
explicit CASR(const EMetric& metric, const std::vector<Eigen::MatrixXd>& dataset)
{
setMetric(metric);
train(dataset);
}
~CASR() = default; ///< Finalizes an instance of the <see cref="CASR"/> class.
/// <summary> Trains the specified dataset. </summary>
/// <param name="dataset"> The dataset (Vector of signal window). </param>
/// <param name="rejectionLimit"> The rejection limit. </param>
/// <returns> <c>True</c> if it succeeds, <c>False</c> otherwise. </returns>
bool train(const std::vector<Eigen::MatrixXd>& dataset, const double rejectionLimit = 5);
/// <summary> Apply the ASR algorithm to the input signal. </summary>
/// <param name="in"> The input signal. </param>
/// <param name="out"> The corrected signal. </param>
/// <returns> <c>True</c> if it succeeds, <c>False</c> otherwise. </returns>
bool process(const Eigen::MatrixXd& in, Eigen::MatrixXd& out);
//***************************
//***** Getter / Setter *****
//***************************
/// <summary> Set the metric to use (only Riemann and euclidian is used). </summary>
/// <param name="metric">The metric. </param>
/// <remarks> If invalid metric is used Euclidian is selected. </remarks>
void setMetric(const EMetric& metric) { m_metric = (metric == EMetric::Riemann) ? EMetric::Riemann : EMetric::Euclidian; }
/// <summary> Sets the number of channel (dimension) to reconstruct in fraction, 0 for nothing 1 for all. </summary>
/// <param name="max"> The maximum ratio. </param>
/// <remarks> If value isn't in [0;1], this function does nothing. </remarks>
void setMaxChannel(const double max) { if (InRange(max, 0.0, 1.0)) { m_maxChannel = max; } }
/// <summary> Sets the differents matrices : median matrix, trheshold matrix, reconstruction matrix and covariance matrix. </summary>
/// <param name="median"> The median matrix. </param>
/// <param name="threshold"> The threshold matrix. </param>
/// <param name="reconstruct"> (Optional) The reconstruct matrix. </param>
/// <param name="covariance"> (Optional) The covariance matrix. </param>
/// <returns> <c>True</c> if it succeeds, <c>False</c> otherwise. </returns>
/// <remarks> All matrices must be square with same size (or empty for reconstruct and covariance matrix).
/// Trivial trigger is set to true. </remarks>
bool setMatrices(const Eigen::MatrixXd& median, const Eigen::MatrixXd& threshold,
const Eigen::MatrixXd& reconstruct = Eigen::MatrixXd(), const Eigen::MatrixXd& covariance = Eigen::MatrixXd());
EMetric getMetric() const { return m_metric; } ///< Get the metric.
size_t getChannelNumber() const { return m_nChannel; } ///< Get the matrices number of channel.
double getMaxChannel() const { return m_maxChannel; } ///< Get the number of channel (dimension) to reconstruct in fraction.
bool getTrivial() const { return m_trivial; } ///< Get is last reconstruct was trivial (first time or if previous doesn't need reconstruct).
Eigen::MatrixXd getMedian() const { return m_median; } ///< Get the median matrix.
Eigen::MatrixXd getThresholdMatrix() const { return m_threshold; } ///< Get the threshold matrix.
//***********************
//***** XML Manager *****
//***********************
/// <summary> Saves the ASR information in an XML file. </summary>
/// <param name="filename"> Filename. </param>
/// <returns> <c>True</c> if it succeeds, <c>False</c> otherwise. </returns>
bool saveXML(const std::string& filename) const;
/// <summary> Loads the ASR information from an XML file. </summary>
/// <param name="filename"> Filename. </param>
/// <returns> <c>True</c> if it succeeds, <c>False</c> otherwise. </returns>
bool loadXML(const std::string& filename);
//*****************************
//***** Override Operator *****
//*****************************
/// <summary> Check if object are equals (with a precision tolerance). </summary>
/// <param name="obj"> The second object. </param>
/// <param name="precision"> Precision for matrix comparison. </param>
/// <returns> <c>True</c> if the two elements are equals (with a precision tolerance), <c>False</c> otherwise. </returns>
bool isEqual(const CASR& obj, const double precision = 1e-6) const;
/// <summary> Copy object value. </summary>
/// <param name="obj"> The object to copy. </param>
void copy(const CASR& obj);
/// <summary> Get the ASR information for output. </summary>
/// <returns> The ASR print in stringstream. </returns>
std::stringstream print() const;
/// <summary> Override the affectation operator. </summary>
/// <param name="obj"> The second object. </param>
/// <returns> The copied object. </returns>
CASR& operator=(const CASR& obj)
{
copy(obj);
return *this;
}
/// <summary> Override the equal operator. </summary>
/// <param name="obj"> The second object. </param>
/// <returns> <c>True</c> if the two <see cref="CASR"/> are equals. </returns>
bool operator==(const CASR& obj) const { return isEqual(obj); }
/// <summary> Override the not equal operator. </summary>
/// <param name="obj"> The second object. </param>
/// <returns> <c>True</c> if the two <see cref="CASR"/> are diffrents. </returns>
bool operator!=(const CASR& obj) const { return !isEqual(obj); }
/// <summary> Override the ostream operator. </summary>
/// <param name="os"> The ostream. </param>
/// <param name="obj"> The object. </param>
/// <returns> Return the modified ostream. </returns>
friend std::ostream& operator <<(std::ostream& os, const CASR& obj)
{
os << obj.print().str();
return os;
}
protected:
//*********************
//***** Variables *****
//*********************
EMetric m_metric = EMetric::Euclidian; ///< Metric Used to compute (only euclidian and Riemann are implemented
size_t m_nChannel = 0; ///< Number of channels (dimension)
double m_maxChannel = 1; ///< Maximum number of channels (dimension) to reconstruct if needed (in fraction, 0 for nothing 1 for all).
bool m_trivial = true; ///< Define if previous sample was trivial to reconstruct
Eigen::MatrixXd m_median; ///< Median computed with train dataset
Eigen::MatrixXd m_threshold; ///< Threshold matrix computed with train dataset
Eigen::MatrixXd m_r; ///< Last Reconstruction matrix
Eigen::MatrixXd m_cov; ///< Last Covariance matrix
};
} // namespace Geometry
@@ -0,0 +1,141 @@
///-------------------------------------------------------------------------------------------------
///
/// \file CBias.hpp
/// \brief Class used to add Rebias to Other Classifier.
/// \author Thibaut Monseigne (Inria).
/// \version 1.0.
/// \date 27/08/2019.
/// \copyright <a href="https://choosealicense.com/licenses/agpl-3.0/">GNU Affero General Public License v3.0</a>.
///
///-------------------------------------------------------------------------------------------------
#pragma once
#include <Eigen/Dense>
#include <vector>
#include "geometry/Metrics.hpp"
#include "geometry/3rd-party/tinyxml2.h"
namespace Geometry {
/// <summary> Class For Bias Algorithm for covariance matrices. </summary>
class CBias
{
public:
/// <summary> Initializes a new instance of the <see cref="CBias"/> class. </summary>
CBias() = default;
/// <summary> Finalizes an instance of the <see cref="CBias"/> class. </summary>
~CBias() = default;
/// <summary> Computes the Bias matrix and reset the number of classification. </summary>
/// <param name="dataset"> The dataset (first dimension is the classes, second dimension is the trials as matrix). </param>
/// <param name="metric"> The metric. </param>
/// <returns> <c>True</c> if it succeeds, <c>False</c> otherwise. </returns>
bool computeBias(const std::vector<std::vector<Eigen::MatrixXd>>& dataset, const EMetric metric = EMetric::Riemann);
/// <summary> Computes the Bias matrix and reset the number of classification. </summary>
/// <param name="dataset"> The dataset is a vector of trial as matrix. </param>
/// <param name="metric"> The metric. </param>
/// <returns> <c>True</c> if it succeeds, <c>False</c> otherwise. </returns>
bool computeBias(const std::vector<Eigen::MatrixXd>& dataset, const EMetric metric = EMetric::Riemann);
/// <summary> Applies the Bias on 2D vector of Matrix. </summary>
/// <param name="in"> The input 2D vector of matrix. </param>
/// <param name="out"> The output 2D vector of matrix. </param>
void applyBias(const std::vector<std::vector<Eigen::MatrixXd>>& in, std::vector<std::vector<Eigen::MatrixXd>>& out);
/// <summary> Applies the Bias on vector of Matrix. </summary>
/// <param name="in"> The input vector of matrix. </param>
/// <param name="out"> The output vector of matrix. </param>
void applyBias(const std::vector<Eigen::MatrixXd>& in, std::vector<Eigen::MatrixXd>& out);
/// <summary> Applies the Bias on Matrix. </summary>
/// <param name="in"> The input matrix. </param>
/// <param name="out"> The output matrix. </param>
void applyBias(const Eigen::MatrixXd& in, Eigen::MatrixXd& out);
/// <summary> Updates the Bias. </summary>
/// <param name="sample"> The sample. </param>
/// <param name="metric"> The metric. </param>
void updateBias(const Eigen::MatrixXd& sample, const EMetric metric = EMetric::Riemann);
const Eigen::MatrixXd& getBias() const { return m_bias; } ///< Get the bias matrix.
void setBias(const Eigen::MatrixXd& bias); ///< Set the bias matrix and the inverse square root of biais.
size_t getClassificationNumber() const { return m_n; } ///< Get the Number of classification (used for update).
void setClassificationNumber(const size_t& n) { m_n = n; } ///< Set the Number of classification (used for update).
//***********************
//***** XML Manager *****
//***********************
/// <summary> Saves the Bias information in an XML file. </summary>
/// <param name="filename"> Filename. </param>
/// <returns> <c>True</c> if it succeeds, <c>False</c> otherwise. </returns>
bool saveXML(const std::string& filename) const;
/// <summary> Loads the Bias information from an XML file. </summary>
/// <param name="filename"> Filename. </param>
/// <returns> <c>True</c> if it succeeds, <c>False</c> otherwise. </returns>
bool loadXML(const std::string& filename);
/// <summary> Save informations in xml element (Bias and number of classification). </summary>
/// <returns> <c>True</c> if it succeeds, <c>False</c> otherwise. </returns>
bool saveAdditional(tinyxml2::XMLDocument& doc, tinyxml2::XMLElement* data) const;
/// <summary> Load informations in xml element (Bias and number of classification). </summary>
/// <returns> <c>True</c> if it succeeds, <c>False</c> otherwise. </returns>
bool loadAdditional(tinyxml2::XMLElement* data);
//*****************************
//***** Override Operator *****
//*****************************
/// <summary> Check if object are equals (with a precision tolerance). </summary>
/// <param name="obj"> The second object. </param>
/// <param name="precision"> Precision for matrix comparison. </param>
/// <returns> <c>True</c> if the two elements are equals (with a precision tolerance), <c>False</c> otherwise. </returns>
bool isEqual(const CBias& obj, const double precision = 1e-6) const;
/// <summary> Copy object value. </summary>
/// <param name="obj"> The object to copy. </param>
void copy(const CBias& obj);
/// <summary> Get the Classifier information for output. </summary>
/// <returns> The Classifier print in stringstream. </returns>
std::stringstream print() const;
/// <summary> Override the affectation operator. </summary>
/// <param name="obj"> The second object. </param>
/// <returns> The copied object. </returns>
CBias& operator=(const CBias& obj)
{
copy(obj);
return *this;
}
/// <summary> Override the equal operator. </summary>
/// <param name="obj"> The second object. </param>
/// <returns> <c>True</c> if the two <see cref="CBias"/> are equals. </returns>
bool operator==(const CBias& obj) const { return isEqual(obj); }
/// <summary> Override the not equal operator. </summary>
/// <param name="obj"> The second object. </param>
/// <returns> <c>True</c> if the two <see cref="CBias"/> are diffrents. </returns>
bool operator!=(const CBias& obj) const { return !isEqual(obj); }
/// <summary> Override the ostream operator. </summary>
/// <param name="os"> The ostream. </param>
/// <param name="obj"> The object. </param>
/// <returns> Return the modified ostream. </returns>
friend std::ostream& operator <<(std::ostream& os, const CBias& obj)
{
os << obj.print().str();
return os;
}
protected:
//*********************
//***** Variables *****
//*********************
size_t m_n = 0; ///< Number of classification launched (used for update).
Eigen::MatrixXd m_bias; ///< Bias Matrix.
Eigen::MatrixXd m_biasIS; ///< Inverse squared root bias matrix (stored and pre-computed for application of bias).
};
} // namespace Geometry
@@ -0,0 +1,117 @@
///-------------------------------------------------------------------------------------------------
///
/// \file CMatrixClassifierFgMDM.hpp
/// \brief Class of Minimum Distance to Mean with geodesic filtering (FgMDM) Classifier.
/// \author Thibaut Monseigne (Inria).
/// \version 1.0.
/// \date 10/12/2018.
/// \copyright <a href="https://choosealicense.com/licenses/agpl-3.0/">GNU Affero General Public License v3.0</a>.
///
///-------------------------------------------------------------------------------------------------
#pragma once
#include "geometry/classifier/CMatrixClassifierFgMDMRT.hpp"
namespace Geometry {
/// <summary> Class of Minimum Distance to Mean with geodesic filtering (FgMDM) Classifier. </summary>
/// <seealso cref="CMatrixClassifierMDM" />
class CMatrixClassifierFgMDM final : public CMatrixClassifierFgMDMRT
{
public:
//***********************
//***** Constructor *****
//***********************
/// <summary> Initializes a new instance of the <see cref="CMatrixClassifierFgMDM"/> class. </summary>
CMatrixClassifierFgMDM() = default;
/// <summary> Default Copy constructor. Initializes a new instance of the <see cref="CMatrixClassifierFgMDM"/> class. </summary>
/// <param name="obj"> Initial object. </param>
CMatrixClassifierFgMDM(const CMatrixClassifierFgMDM& obj) { *this = obj; }
/// <summary> Copy constructor with parent class. Initializes a new instance of the <see cref="CMatrixClassifierFgMDM"/> class. </summary>
/// <param name="obj"> Initial object. </param>
explicit CMatrixClassifierFgMDM(const CMatrixClassifierFgMDMRT& obj) { copy(obj); }
/// <summary> Initializes a new instance of the <see cref="CMatrixClassifierFgMDM"/> class and set base members. </summary>
/// <param name="nbClass"> The number of classes. </param>
/// <param name="metric"> Metric to use to calculate means (see also <see cref="EMetric" />). </param>
explicit CMatrixClassifierFgMDM(const size_t nbClass, const EMetric metric) : CMatrixClassifierFgMDMRT(nbClass, metric) { }
/// <summary> Finalizes an instance of the <see cref="CMatrixClassifierFgMDM"/> class. </summary>
/// <remarks> clear the <see cref="m_means"/> vector of Matrix and the <see cref="m_dataset"/> member. </remarks>
~CMatrixClassifierFgMDM() override;
//***************************
//***** Getter / Setter *****
//***************************
void setDataset(const std::vector<std::vector<Eigen::MatrixXd>>& dataset) { m_dataset = dataset; } ///< Set dataset.
const std::vector<std::vector<Eigen::MatrixXd>>& getDataset() const { return m_dataset; } ///< Get dataset.
//**********************
//***** Classifier *****
//**********************
/// <summary> Train the classifier with the dataset.
/// -# Compute the Riemann mean of all trials as reference and store this in <see cref="m_ref"/> member.
/// -# Set the good number of classes
/// -# Trasnform data to the Tangent Space with the reference
/// -# Compute the FgDA Weight (<see cref="FgDACompute" />).
/// -# Apply the FgDA Weight and return to Original Manifold.
/// -# Apply the train function of MDM Classifier (see <see cref="CMatrixClassifierMDM::train"/>)
/// </summary>
/// <param name="dataset"> The dataset (first dimension is the classes, second dimension is the trials as covariance matrix). </param>
/// <returns> <c>True</c> if it succeeds, <c>False</c> otherwise. </returns>
/// <remarks> the dataset is saved. </remarks>
bool train(const std::vector<std::vector<Eigen::MatrixXd>>& dataset) override;
/// <summary> Classify the matrix and return the class id, the distance and the probability of each class.\n
/// -# Transform the sample to the Tangent Space.\n
/// -# Apply the FgDA weight.\n
/// -# Return to the original Manifold.\n
/// -# Apply the classify function of MDM Classifier (see <see cref="CMatrixClassifierMDM::classify"/>)
/// </summary>
/// <remarks> The classifier is train with the new sample. </remarks>
/// \copydetails IMatrixClassifier::classify(const Eigen::MatrixXd&, size_t&, std::vector<double>&, std::vector<double>&, const EAdaptations, const size_t&)
bool classify(const Eigen::MatrixXd& sample, size_t& classId, std::vector<double>& distance, std::vector<double>& probability,
EAdaptations adaptation = EAdaptations::None, const size_t& realClassId = std::numeric_limits<size_t>::max()) override;
//*****************************
//***** Override Operator *****
//*****************************
/// <summary> Get the type of the classifier. </summary>
/// <returns> Minimum Distance to Mean with geodesic filtering (FgMDM). </returns>
std::string getType() const override { return toString(EMatrixClassifiers::FgMDM); }
/// <summary> Override the affectation operator. </summary>
/// <param name="obj"> The second object. </param>
/// <returns> The copied object. </returns>
CMatrixClassifierFgMDM& operator=(const CMatrixClassifierFgMDM& obj)
{
copy(obj);
return *this;
}
/// <summary> Override the ostream operator. </summary>
/// <param name="os"> The ostream. </param>
/// <param name="obj"> The object. </param>
/// <returns> Return the modified ostream. </returns>
friend std::ostream& operator <<(std::ostream& os, const CMatrixClassifierFgMDM& obj)
{
os << obj.print().str();
return os;
}
protected:
///<summary> train with the actual dataset (<see cref="m_dataset"/>). </summary>
bool train() { return CMatrixClassifierFgMDMRT::train(m_dataset); }
//*********************
//***** Variables *****
//*********************
std::vector<std::vector<Eigen::MatrixXd>> m_dataset; ///< Data set for train and adaptation (it can quickly rise).
};
} // namespace Geometry
@@ -0,0 +1,151 @@
///-------------------------------------------------------------------------------------------------
///
/// \file CMatrixClassifierFgMDMRT.hpp
/// \brief Class of Minimum Distance to Mean with geodesic filtering (FgMDM) Classifier RT (adaptation is Real Time Assumed)
/// \author Thibaut Monseigne (Inria).
/// \version 1.0.
/// \date 10/12/2018.
/// \copyright <a href="https://choosealicense.com/licenses/agpl-3.0/">GNU Affero General Public License v3.0</a>.
///
///-------------------------------------------------------------------------------------------------
#pragma once
#include "geometry/classifier/CMatrixClassifierMDM.hpp"
namespace Geometry {
/// <summary> Class of Minimum Distance to Mean with geodesic filtering (FgMDM) Classifier RT (adaptation is Real Time Assumed). </summary>
/// <seealso cref="CMatrixClassifierMDM" />
class CMatrixClassifierFgMDMRT : public CMatrixClassifierMDM
{
public:
//***********************
//***** Constructor *****
//***********************
/// <summary> Initializes a new instance of the <see cref="CMatrixClassifierFgMDMRT"/> class. </summary>
CMatrixClassifierFgMDMRT() = default;
/// <summary> Default Copy constructor. Initializes a new instance of the <see cref="CMatrixClassifierFgMDMRT"/> class. </summary>
/// <param name="obj"> Initial object. </param>
CMatrixClassifierFgMDMRT(const CMatrixClassifierFgMDMRT& obj) { *this = obj; }
/// <summary> Initializes a new instance of the <see cref="CMatrixClassifierFgMDMRT"/> class and set base members. </summary>
/// <param name="nbClass"> The number of classes. </param>
/// <param name="metric"> Metric to use to calculate means (see also <see cref="EMetric" />). </param>
explicit CMatrixClassifierFgMDMRT(const size_t nbClass, const EMetric metric) : CMatrixClassifierMDM(nbClass, metric) { }
/// <summary> Finalizes an instance of the <see cref="CMatrixClassifierFgMDMRT"/> class. </summary>
/// <remarks> clear the <see cref="m_means"/> vector of Matrix. </remarks>
~CMatrixClassifierFgMDMRT() override = default;
//***************************
//***** Getter / Setter *****
//***************************
const Eigen::MatrixXd& getRef() const { return m_ref; } ///< Get reference of tangent space.
void setRef(const Eigen::MatrixXd& ref) { m_ref = ref; } ///< Set reference of tangent space.
const Eigen::MatrixXd& getWeight() const { return m_weight; } ///< Get weight matrix of geodesic filter.
void setWeight(const Eigen::MatrixXd& weight) { m_weight = weight; } ///< Set weight matrix of geodesic filter.
//**********************
//***** Classifier *****
//**********************
/// <summary> Train the classifier with the dataset.
/// -# Compute the Riemann mean of all trials as reference and store this in <see cref="m_ref"/> member.
/// -# Set the good number of classes
/// -# Trasnform data to the Tangent Space with the reference
/// -# Compute the FgDA Weight (<see cref="FgDACompute" />).
/// -# Apply the FgDA Weight and return to Original Manifold.
/// -# Apply the train function of MDM Classifier (see <see cref="CMatrixClassifierMDM::train"/>)
/// </summary>
/// <param name="dataset"> The dataset (first dimension is the classes, second dimension is the trials as covariance matrix). </param>
/// <returns> <c>True</c> if it succeeds, <c>False</c> otherwise. </returns>
bool train(const std::vector<std::vector<Eigen::MatrixXd>>& dataset) override;
/// <summary> Classify the matrix and return the class id, the distance and the probability of each class.\n
/// -# Transform the sample to the Tangent Space.\n
/// -# Apply the FgDA weight.\n
/// -# Return to the original Manifold.\n
/// -# Apply the classify function of MDM Classifier (see <see cref="CMatrixClassifierMDM::classify"/>)
/// </summary>
/// <remarks>
/// <b>Remark</b> : We use the MDM classification whatever the adaptation method chosen.
/// Thus the MDM part evolves but the geodesic filtering does not evolve to keep an execution online.
/// A version allowing the adaptation of the Filter will be implemented for offline execution.
/// </remarks>
/// \copydetails IMatrixClassifier::classify(const Eigen::MatrixXd&, size_t&, std::vector<double>&, std::vector<double>&, const EAdaptations, const size_t&)
bool classify(const Eigen::MatrixXd& sample, size_t& classId, std::vector<double>& distance, std::vector<double>& probability,
EAdaptations adaptation = EAdaptations::None, const size_t& realClassId = std::numeric_limits<size_t>::max()) override;
//*****************************
//***** Override Operator *****
//*****************************
/// <summary> Check if object are equals (with a precision tolerance). </summary>
/// <param name="obj"> The second object. </param>
/// <param name="precision"> Precision for matrix comparison. </param>
/// <returns> <c>True</c> if the two elements are equals (with a precision tolerance). </returns>
bool isEqual(const CMatrixClassifierFgMDMRT& obj, double precision = 1e-6) const;
/// <summary> Copy object value. </summary>
/// <param name="obj"> The object to copy. </param>
void copy(const CMatrixClassifierFgMDMRT& obj);
/// <summary> Get the type of the classifier. </summary>
/// <returns> Minimum Distance to Mean with geodesic filtering (FgMDM). </returns>
std::string getType() const override { return toString(EMatrixClassifiers::FgMDM_RT); }
/// <summary> Override the affectation operator. </summary>
/// <param name="obj"> The second object. </param>
/// <returns> The copied object. </returns>
CMatrixClassifierFgMDMRT& operator=(const CMatrixClassifierFgMDMRT& obj)
{
copy(obj);
return *this;
}
/// <summary> Override the equal operator. </summary>
/// <param name="obj"> The second object. </param>
/// <returns> <c>True</c> if the two <see cref="CMatrixClassifierFgMDMRT"/> are equals. </returns>
bool operator==(const CMatrixClassifierFgMDMRT& obj) const { return isEqual(obj); }
/// <summary> Override the not equal operator. </summary>
/// <param name="obj"> The second object. </param>
/// <returns> <c>True</c> if the two <see cref="CMatrixClassifierFgMDMRT"/> are diffrents. </returns>
bool operator!=(const CMatrixClassifierFgMDMRT& obj) const { return !isEqual(obj); }
/// <summary> Override the ostream operator. </summary>
/// <param name="os"> The ostream. </param>
/// <param name="obj"> The object. </param>
/// <returns> Return the modified ostream. </returns>
friend std::ostream& operator <<(std::ostream& os, const CMatrixClassifierFgMDMRT& obj)
{
os << obj.print().str();
return os;
}
protected:
//***********************
//***** XML Manager *****
//***********************
/// <summary> Save Additionnal informations (Reference and LDA Weight). </summary>
/// <returns> <c>True</c> if it succeeds, <c>False</c> otherwise. </returns>
bool saveAdditional(tinyxml2::XMLDocument& doc, tinyxml2::XMLElement* data) const override;
/// <summary> Load Additionnal informations (Reference and LDA Weight). </summary>
/// <returns> <c>True</c> if it succeeds, <c>False</c> otherwise. </returns>
bool loadAdditional(tinyxml2::XMLElement* data) override;
/// <summary> Prints the Additional informations (Reference and LDA Weight). </summary>
/// <returns> Additional informations in stringstream. </returns>
std::stringstream printAdditional() const override;
//*********************
//***** Variables *****
//*********************
Eigen::MatrixXd m_ref; ///< Reference matrix of tanget space.
Eigen::MatrixXd m_weight; ///< Weght matrix of Filter Geodesic Discriminant Analysis.
};
} // namespace Geometry
@@ -0,0 +1,148 @@
///-------------------------------------------------------------------------------------------------
///
/// \file CMatrixClassifierFgMDMRTRebias.hpp
/// \brief Class of Minimum Distance to Mean with geodesic filtering (FgMDM) Classifier RT (adaptation is Real Time Assumed)
/// \author Thibaut Monseigne (Inria).
/// \version 1.0.
/// \date 10/12/2018.
/// \copyright <a href="https://choosealicense.com/licenses/agpl-3.0/">GNU Affero General Public License v3.0</a>.
///
///-------------------------------------------------------------------------------------------------
#pragma once
#include "geometry/classifier/CMatrixClassifierFgMDMRT.hpp"
#include "geometry/classifier/CBias.hpp"
namespace Geometry {
/// <summary> Class of Minimum Distance to Mean with geodesic filtering (FgMDM) Classifier RT (adaptation is Real Time Assumed). </summary>
/// <seealso cref="CMatrixClassifierFgMDMRT" />
class CMatrixClassifierFgMDMRTRebias final : public CMatrixClassifierFgMDMRT
{
public:
//***********************
//***** Constructor *****
//***********************
/// <summary> Initializes a new instance of the <see cref="CMatrixClassifierFgMDMRTRebias"/> class. </summary>
CMatrixClassifierFgMDMRTRebias() = default;
/// <summary> Default Copy constructor. Initializes a new instance of the <see cref="CMatrixClassifierFgMDMRTRebias"/> class. </summary>
/// <param name="obj"> Initial object. </param>
CMatrixClassifierFgMDMRTRebias(const CMatrixClassifierFgMDMRTRebias& obj) { *this = obj; }
/// <summary> Initializes a new instance of the <see cref="CMatrixClassifierFgMDMRTRebias"/> class and set base members. </summary>
/// <param name="nbClass"> The number of classes. </param>
/// <param name="metric"> Metric to use to calculate means (see also <see cref="EMetric" />). </param>
explicit CMatrixClassifierFgMDMRTRebias(const size_t nbClass, const EMetric metric) : CMatrixClassifierFgMDMRT(nbClass, metric) { }
/// <summary> Finalizes an instance of the <see cref="CMatrixClassifierFgMDMRTRebias"/> class. </summary>
/// <remarks> clear the <see cref="m_means"/> vector of Matrix. </remarks>
~CMatrixClassifierFgMDMRTRebias() override = default;
//***************************
//***** Getter / Setter *****
//***************************
const CBias& getBias() const { return m_bias; } ///< Get Rebias Method.
void setBias(const CBias& bias) { m_bias = bias; } ///< Set Rebias Method.
//**********************
//***** Classifier *****
//**********************
/// <summary> Train the classifier with the dataset.
/// -# Compute the Riemann mean of all trials as reference and store this in <see cref="m_ref"/> member.
/// -# Set the good number of classes
/// -# Trasnform data to the Tangent Space with the reference
/// -# Compute the FgDA Weight (<see cref="FgDACompute" />).
/// -# Apply the FgDA Weight and return to Original Manifold.
/// -# Apply the train function of MDM Classifier (see <see cref="CMatrixClassifierMDM::train"/>)
/// </summary>
/// <param name="dataset"> The dataset (first dimension is the classes, second dimension is the trials as covariance matrix). </param>
/// <returns> <c>True</c> if it succeeds, <c>False</c> otherwise. </returns>
bool train(const std::vector<std::vector<Eigen::MatrixXd>>& dataset) override;
/// <summary> Classify the matrix and return the class id, the distance and the probability of each class.\n
/// -# Transform the sample to the Tangent Space.\n
/// -# Apply the FgDA weight.\n
/// -# Return to the original Manifold.\n
/// -# Apply the classify function of MDM Classifier (see <see cref="CMatrixClassifierMDM::classify"/>)
/// </summary>
/// <remarks>
/// <b>Remark</b> : We use the MDM classification whatever the adaptation method chosen.
/// Thus the MDM part evolves but the geodesic filtering does not evolve to keep an execution online.
/// A version allowing the adaptation of the Filter will be implemented for offline execution.
/// </remarks>
/// \copydetails IMatrixClassifier::classify(const Eigen::MatrixXd&, size_t&, std::vector<double>&, std::vector<double>&, const EAdaptations, const size_t&)
bool classify(const Eigen::MatrixXd& sample, size_t& classId, std::vector<double>& distance, std::vector<double>& probability,
EAdaptations adaptation = EAdaptations::None, const size_t& realClassId = std::numeric_limits<size_t>::max()) override;
//*****************************
//***** Override Operator *****
//*****************************
/// <summary> Check if object are equals (with a precision tolerance). </summary>
/// <param name="obj"> The second object. </param>
/// <param name="precision"> Precision for matrix comparison. </param>
/// <returns> <c>True</c> if the two elements are equals (with a precision tolerance). </returns>
bool isEqual(const CMatrixClassifierFgMDMRTRebias& obj, double precision = 1e-6) const;
/// <summary> Copy object value. </summary>
/// <param name="obj"> The object to copy. </param>
void copy(const CMatrixClassifierFgMDMRTRebias& obj);
/// <summary> Get the type of the classifier. </summary>
/// <returns> Minimum Distance to Mean with geodesic filtering (FgMDM). </returns>
std::string getType() const override { return toString(EMatrixClassifiers::FgMDM_RT_Rebias); }
/// <summary> Override the affectation operator. </summary>
/// <param name="obj"> The second object. </param>
/// <returns> The copied object. </returns>
CMatrixClassifierFgMDMRTRebias& operator=(const CMatrixClassifierFgMDMRTRebias& obj)
{
copy(obj);
return *this;
}
/// <summary> Override the equal operator. </summary>
/// <param name="obj"> The second object. </param>
/// <returns> <c>True</c> if the two <see cref="CMatrixClassifierFgMDMRTRebias"/> are equals. </returns>
bool operator==(const CMatrixClassifierFgMDMRTRebias& obj) const { return isEqual(obj); }
/// <summary> Override the not equal operator. </summary>
/// <param name="obj"> The second object. </param>
/// <returns> <c>True</c> if the two <see cref="CMatrixClassifierFgMDMRTRebias"/> are diffrents. </returns>
bool operator!=(const CMatrixClassifierFgMDMRTRebias& obj) const { return !isEqual(obj); }
/// <summary> Override the ostream operator. </summary>
/// <param name="os"> The ostream. </param>
/// <param name="obj"> The object. </param>
/// <returns> Return the modified ostream. </returns>
friend std::ostream& operator <<(std::ostream& os, const CMatrixClassifierFgMDMRTRebias& obj)
{
os << obj.print().str();
return os;
}
protected:
//***********************
//***** XML Manager *****
//***********************
/// <summary> Save Additionnal informations (Reference and LDA Weight). </summary>
/// <returns> <c>True</c> if it succeeds, <c>False</c> otherwise. </returns>
bool saveAdditional(tinyxml2::XMLDocument& doc, tinyxml2::XMLElement* data) const override;
/// <summary> Load Additionnal informations (Reference and LDA Weight). </summary>
/// <returns> <c>True</c> if it succeeds, <c>False</c> otherwise. </returns>
bool loadAdditional(tinyxml2::XMLElement* data) override;
/// <summary> Prints the Additional informations (Reference and LDA Weight). </summary>
/// <returns> Additional informations in stringstream. </returns>
std::stringstream printAdditional() const override;
//*********************
//***** Variables *****
//*********************
CBias m_bias; ///< Rebias Method.
};
} // namespace Geometry
@@ -0,0 +1,164 @@
///-------------------------------------------------------------------------------------------------
///
/// \file CMatrixClassifierMDM.hpp
/// \brief Class of Minimum Distance to Mean (MDM) Classifier
/// \author Thibaut Monseigne (Inria).
/// \version 1.0.
/// \date 10/12/2018.
/// \copyright <a href="https://choosealicense.com/licenses/agpl-3.0/">GNU Affero General Public License v3.0</a>.
///
///-------------------------------------------------------------------------------------------------
#pragma once
#include "geometry/classifier/IMatrixClassifier.hpp"
#include "geometry/Metrics.hpp"
namespace Geometry {
/// <summary> Class of Minimum Distance to Mean (MDM) Classifier. </summary>
/// <seealso cref="IMatrixClassifier" />
class CMatrixClassifierMDM : public IMatrixClassifier
{
public:
//***********************
//***** Constructor *****
//***********************
/// <summary> Default constructor. Initializes a new instance of the <see cref="CMatrixClassifierMDM"/> class. </summary>
CMatrixClassifierMDM() { CMatrixClassifierMDM::setClassCount(m_nbClass); }
/// <summary> Default Copy constructor. Initializes a new instance of the <see cref="CMatrixClassifierMDM"/> class. </summary>
/// <param name="obj"> Initial object. </param>
CMatrixClassifierMDM(const CMatrixClassifierMDM& obj) { *this = obj; }
/// <summary> Initializes a new instance of the <see cref="CMatrixClassifierMDM"/> class and set base members. </summary>
/// <param name="nbClass"> The number of classes. </param>
/// <param name="metric"> Metric to use to calculate means (see also <see cref="EMetric" />). </param>
explicit CMatrixClassifierMDM(size_t nbClass, EMetric metric);
/// <summary> Finalizes an instance of the <see cref="CMatrixClassifierMDM"/> class. </summary>
/// <remarks> clear the <see cref="m_means"/> vector of Matrix. </remarks>
~CMatrixClassifierMDM() override;
//***************************
//***** Getter / Setter *****
//***************************
const std::vector<Eigen::MatrixXd>& getMeans() const { return m_means; } ///< Get Means of classes.
void setMeans(const std::vector<Eigen::MatrixXd>& means) { m_means = means; } ///< Set Means of classes.
const std::vector<size_t>& getTrialNumbers() const { return m_nbTrials; } ///< Get the number of trial used for train.
void setTrialNumbers(const std::vector<size_t>& nbTrials) { m_nbTrials = nbTrials; } ///< Set the number of trial used for train.
//**********************
//***** Classifier *****
//**********************
/// <summary> Set the class count. </summary>
/// <remarks> resize the <see cref="m_means"/> vector of Matrix. </remarks>
void setClassCount(size_t nbClass) override;
/// <summary> Train the classifier with the dataset.
/// -# Set the good number of classes
/// -# Compute the mean of each class (row) with the metric (<see cref="EMetric" />) in <see cref="m_metric"/> member.
/// -# Set the number of trials for each class.
/// </summary>
/// <param name="dataset"> The dataset (first dimension is the classes, second dimension is the trials as covariance matrix). </param>
/// <returns> <c>True</c> if it succeeds, <c>False</c> otherwise. </returns>
bool train(const std::vector<std::vector<Eigen::MatrixXd>>& dataset) override;
/// <summary> Classify the matrix and return the class id, the distance and the probability of each class.\n
/// - Compute the distance between the sample and each mean matrix.\n
/// - The class with the closest mean is the predicted class.\n
/// - The distances are returned.\n
/// - The probability \f$ \mathcal{P}_i \f$ to be the class \f$ i \f$ is compute as :
/// \f[
/// \begin{aligned}
/// p_i &= \frac{d_{\text{min}}}{d_i}\\
/// \mathcal{P}_i &= \frac{p_i}{\sum{\left(p_i\right)}}
/// \end{aligned}
/// \f]
/// </summary>
/// <remarks>
/// <b>Remark</b> : The probability is normalized \f$ \sum{\left(\mathcal{P}_i\right)} = 1 \f$\n
/// If the classfier is adapted, launch adaptation method (expected class if supervised, predicted class if unsupervised).\n
/// With \f$ C_k \f$ the prototype (mean) of the Class \f$ k \f$, \f$ \gamma_m \f$ the Geodesic (<see cref="Geodesic" />) with the metric \f$ m \f$ (<see cref="EMetric" />),
/// \f$ S \f$ the current trial (sample) and \f$ N_k \f$ the number of trials for the class \f$ k \f$ (with the current trial).
/// \f[
/// C_k = \gamma_m\left( C_k,S,\frac{1}{N_k}\right)
/// \f]
/// </remarks>
/// \copydetails IMatrixClassifier::classify(const Eigen::MatrixXd&, size_t&, std::vector<double>&, std::vector<double>&, const EAdaptations, const size_t&)
bool classify(const Eigen::MatrixXd& sample, size_t& classId, std::vector<double>& distance, std::vector<double>& probability,
EAdaptations adaptation = EAdaptations::None, const size_t& realClassId = std::numeric_limits<size_t>::max()) override;
//*****************************
//***** Override Operator *****
//*****************************
/// <summary> Check if object are equals (with a precision tolerance). </summary>
/// <param name="obj"> The second object. </param>
/// <param name="precision"> Precision for matrix comparison. </param>
/// <returns> <c>True</c> if the two elements are equals (with a precision tolerance). </returns>
bool isEqual(const CMatrixClassifierMDM& obj, double precision = 1e-6) const;
/// <summary> Copy object value. </summary>
/// <param name="obj"> The object to copy. </param>
void copy(const CMatrixClassifierMDM& obj);
/// <summary> Get the type of the classifier. </summary>
/// <returns> Minimum Distance to Mean. </returns>
std::string getType() const override { return toString(EMatrixClassifiers::MDM); }
/// <summary> Override the affectation operator. </summary>
/// <param name="obj"> The second object. </param>
/// <returns> The copied object. </returns>
CMatrixClassifierMDM& operator=(const CMatrixClassifierMDM& obj)
{
copy(obj);
return *this;
}
/// <summary> Override the equal operator. </summary>
/// <param name="obj"> The second object. </param>
/// <returns> <c>True</c> if the two <see cref="CMatrixClassifierMDM"/> are equals. </returns>
bool operator==(const CMatrixClassifierMDM& obj) const { return isEqual(obj); }
/// <summary> Override the not equal operator. </summary>
/// <param name="obj"> The second object. </param>
/// <returns> <c>True</c> if the two <see cref="CMatrixClassifierMDM"/> are diffrents. </returns>
bool operator!=(const CMatrixClassifierMDM& obj) const { return !isEqual(obj); }
/// <summary> Override the ostream operator. </summary>
/// <param name="os"> The ostream. </param>
/// <param name="obj"> The object. </param>
/// <returns> Return the modified ostream. </returns>
friend std::ostream& operator <<(std::ostream& os, const CMatrixClassifierMDM& obj)
{
os << obj.print().str();
return os;
}
protected:
//***********************
//***** XML Manager *****
//***********************
/// <summary> Save Classes informations (Mean and number of trials of each class). </summary>
/// <returns> <c>True</c> if it succeeds, <c>False</c> otherwise. </returns>
bool saveClasses(tinyxml2::XMLDocument& doc, tinyxml2::XMLElement* data) const override;
/// <summary> Load Classes informations (Mean and number of trials of each class). </summary>
/// <returns> <c>True</c> if it succeeds, <c>False</c> otherwise. </returns>
bool loadClasses(tinyxml2::XMLElement* data) override;
//*****************************
//***** Override Operator *****
//*****************************
std::stringstream printClasses() const override;
//*********************
//***** Variables *****
//*********************
std::vector<Eigen::MatrixXd> m_means; ///< Mean Matrix of each class.
std::vector<size_t> m_nbTrials; ///< Number of trials of each class.
};
} // namespace Geometry
@@ -0,0 +1,141 @@
///-------------------------------------------------------------------------------------------------
///
/// \file CMatrixClassifierMDMRebias.hpp
/// \brief Class of Minimum Distance to Mean (MDM) Classifier with Rebias.
/// \author Thibaut Monseigne (Inria).
/// \version 1.0.
/// \date 10/12/2018.
/// \copyright <a href="https://choosealicense.com/licenses/agpl-3.0/">GNU Affero General Public License v3.0</a>.
///
///-------------------------------------------------------------------------------------------------
#pragma once
#include "geometry/classifier/CMatrixClassifierMDM.hpp"
#include "geometry/Metrics.hpp"
#include "geometry/classifier/CBias.hpp"
namespace Geometry {
/// <summary> Class of Minimum Distance to Mean (MDM) Classifier with Rebias. </summary>
/// <seealso cref="IMatrixClassifier" />
class CMatrixClassifierMDMRebias final : public CMatrixClassifierMDM
{
public:
//***********************
//***** Constructor *****
//***********************
/// <summary> Default constructor. Initializes a new instance of the <see cref="CMatrixClassifierMDMRebias"/> class. </summary>
CMatrixClassifierMDMRebias() = default;
/// <summary> Default Copy constructor. Initializes a new instance of the <see cref="CMatrixClassifierMDMRebias"/> class. </summary>
/// <param name="obj"> Initial object. </param>
CMatrixClassifierMDMRebias(const CMatrixClassifierMDMRebias& obj) { *this = obj; }
/// <summary> Initializes a new instance of the <see cref="CMatrixClassifierMDMRebias"/> class and set base members. </summary>
/// <param name="nbClass"> The number of classes. </param>
/// <param name="metric"> Metric to use to calculate means (see also <see cref="EMetric" />). </param>
explicit CMatrixClassifierMDMRebias(const size_t nbClass, const EMetric metric) : CMatrixClassifierMDM(nbClass, metric) { }
/// <summary> Finalizes an instance of the <see cref="CMatrixClassifierMDMRebias"/> class. </summary>
~CMatrixClassifierMDMRebias() override = default;
//***************************
//***** Getter / Setter *****
//***************************
const CBias& getBias() const { return m_bias; } ///< Get Rebias Method.
void setBias(const CBias& bias) { m_bias = bias; } ///< Set Rebias Method.
//**********************
//***** Classifier *****
//**********************
/// <summary> Train the classifier with the dataset.
/// -# Compute the mean of all trials with the metric (<see cref="EMetric" />) in <see cref="m_metric"/> member as reference and store this in <see cref="m_bias"/> member.
/// -# Set the good number of classes
/// -# Apply an affine transformation on each trials with the reference : \f$ S_\text{new} = R^{-1/2} * S * {R^{-1/2}}^{\mathsf{T}} \f$
/// -# Compute the mean of each class (row), on transformed trials, with the metric (<see cref="EMetric" />) in <see cref="m_metric"/> member.
/// -# Set the number of trials for each class.
/// </summary>
/// <param name="dataset"> The dataset (first dimension is the classes, second dimension is the trials as covariance matrix). </param>
/// <returns> <c>True</c> if it succeeds, <c>False</c> otherwise. </returns>
bool train(const std::vector<std::vector<Eigen::MatrixXd>>& dataset) override;
/// <summary> Classify the matrix and return the class id, the distance and the probability of each class.
/// -# Apply an affine transformation on the trial (sample) with the reference : \f$ S_\text{new} = R^{-1/2} * S * {R^{-1/2}}^{\mathsf{T}} \f$
/// -# Update the reference with the current sample the first time and next with the Geodesic between the reference and the current sample.\n
/// With \f$ \gamma_m \f$ the Geodesic (<see cref="Geodesic" />) with the metric \f$ m \f$ (<see cref="EMetric" />) and \f$ N_c \f$ the number of classification : \f$ R = \gamma_\text{m}\left( R,S,\frac{1}{N_c} \right) \f$
/// -# Apply the classify function of MDM Classifier (see <see cref="CMatrixClassifierMDM::classify"/>)
/// </summary>
/// \copydetails IMatrixClassifier::classify(const Eigen::MatrixXd&, size_t&, std::vector<double>&, std::vector<double>&, const EAdaptations, const size_t&)
bool classify(const Eigen::MatrixXd& sample, size_t& classId, std::vector<double>& distance, std::vector<double>& probability,
EAdaptations adaptation = EAdaptations::None, const size_t& realClassId = std::numeric_limits<size_t>::max()) override;
//*****************************
//***** Override Operator *****
//*****************************
/// <summary> Check if object are equals (with a precision tolerance). </summary>
/// <param name="obj"> The second object. </param>
/// <param name="precision"> Precision for matrix comparison. </param>
/// <returns> <c>True</c> if the two elements are equals (with a precision tolerance). </returns>
bool isEqual(const CMatrixClassifierMDMRebias& obj, double precision = 1e-6) const;
/// <summary> Copy object value. </summary>
/// <param name="obj"> The object to copy. </param>
void copy(const CMatrixClassifierMDMRebias& obj);
/// <summary> Get the type of the classifier. </summary>
/// <returns> Minimum Distance to Mean REBIAS. </returns>
std::string getType() const override { return toString(EMatrixClassifiers::MDM_Rebias); }
/// <summary> Override the affectation operator. </summary>
/// <param name="obj"> The second object. </param>
/// <returns> The copied object. </returns>
CMatrixClassifierMDMRebias& operator=(const CMatrixClassifierMDMRebias& obj)
{
copy(obj);
return *this;
}
/// <summary> Override the equal operator. </summary>
/// <param name="obj"> The second object. </param>
/// <returns> <c>True</c> if the two <see cref="CMatrixClassifierMDMRebias"/> are equals. </returns>
bool operator==(const CMatrixClassifierMDMRebias& obj) const { return isEqual(obj); }
/// <summary> Override the not equal operator. </summary>
/// <param name="obj"> The second object. </param>
/// <returns> <c>True</c> if the two <see cref="CMatrixClassifierMDMRebias"/> are diffrents. </returns>
bool operator!=(const CMatrixClassifierMDMRebias& obj) const { return !isEqual(obj); }
/// <summary> Override the ostream operator. </summary>
/// <param name="os"> The ostream. </param>
/// <param name="obj"> The object. </param>
/// <returns> Return the modified ostream. </returns>
friend std::ostream& operator <<(std::ostream& os, const CMatrixClassifierMDMRebias& obj)
{
os << obj.print().str();
return os;
}
protected:
/// <summary> Save Additionnal informations (reference and number of classification). </summary>
/// <returns> <c>True</c> if it succeeds, <c>False</c> otherwise. </returns>
bool saveAdditional(tinyxml2::XMLDocument& doc, tinyxml2::XMLElement* data) const override;
/// <summary> Load Additionnal informations (reference and number of classification). </summary>
/// <returns> <c>True</c> if it succeeds, <c>False</c> otherwise. </returns>
bool loadAdditional(tinyxml2::XMLElement* data) override;
/// <summary> Prints the Additional informations (reference and number of classification). </summary>
/// <returns> Additional informations in stringstream. </returns>
std::stringstream printAdditional() const override;
//*********************
//***** Variables *****
//*********************
CBias m_bias; ///< Rebias Method.
};
} // namespace Geometry
@@ -0,0 +1,315 @@
///-------------------------------------------------------------------------------------------------
///
/// \file IMatrixClassifier.hpp
/// \brief Abstract class of Matrix Classifier
/// \author Thibaut Monseigne (Inria).
/// \version 1.0.
/// \date 10/12/2018.
/// \copyright <a href="https://choosealicense.com/licenses/agpl-3.0/">GNU Affero General Public License v3.0</a>.
///
///-------------------------------------------------------------------------------------------------
#pragma once
#include <Eigen/Dense>
#include <vector>
#include <limits>
#include "geometry/Metrics.hpp"
#include "geometry/3rd-party/tinyxml2.h"
namespace Geometry {
///-------------------------------------------------------------------------------------------------
/// <summary> Enumeration of Adaptation Methods for classifier. </summary>
enum class EAdaptations
{
None, ///< No Adaptation.
Supervised, ///< Supervised Adaptation.
Unsupervised ///< Unsupervised Adaptation.
};
/// <summary> Convert adaptations to string. </summary>
/// <param name="type"> The type of adaptation. </param>
/// <returns> <c>std::string</c> </returns>
inline std::string toString(const EAdaptations type)
{
switch (type)
{
case EAdaptations::None: return "No";
case EAdaptations::Supervised: return "Supervised";
case EAdaptations::Unsupervised: return "Unsupervised";
}
return "Invalid";
}
/// <summary> Convert string to adaptations. </summary>
/// <param name="type"> The type of adaptation. </param>
/// <returns> <see cref="EAdaptations"/> </returns>
inline EAdaptations StringToAdaptation(const std::string& type)
{
if (type == "No") { return EAdaptations::None; }
if (type == "Supervised") { return EAdaptations::Supervised; }
return EAdaptations::Unsupervised;
}
///-------------------------------------------------------------------------------------------------
///-------------------------------------------------------------------------------------------------
/// <summary> Enumeration of Matrix Classifiers. </summary>
enum class EMatrixClassifiers
{
MDM, ///< Minimum Distance To Mean (MDM) Classifier.
MDM_Rebias, ///< Minimum Distance To Mean Rebias (MDM Rebias) Classifier.
FgMDM_RT, ///< Minimum Distance to Mean with geodesic filtering (FgMDM) (Real Time adaptation assumed).
FgMDM, ///< Minimum Distance to Mean with geodesic filtering (FgMDM).
FgMDM_RT_Rebias, ///< Minimum Distance to Mean with geodesic filtering & Rebias adaptation (FgMDM Rebias) (Real Time adaptation assumed).
FgMDM_Rebias ///< Minimum Distance to Mean with geodesic filtering & Rebias adaptation (FgMDM Rebias).
};
/// <summary> Convert Matrix Classifiers to string. </summary>
/// <param name="type"> The type of classifier. </param>
/// <returns> <c>std::string</c> </returns>
inline std::string toString(const EMatrixClassifiers type)
{
switch (type)
{
case EMatrixClassifiers::MDM: return "Minimum Distance to Mean (MDM)";
case EMatrixClassifiers::MDM_Rebias: return "Minimum Distance to Mean Rebias (MDM Rebias)";
case EMatrixClassifiers::FgMDM_RT: return "Minimum Distance to Mean with geodesic filtering (FgMDM) (Real Time adaptation assumed)";
case EMatrixClassifiers::FgMDM: return "Minimum Distance to Mean with geodesic filtering (FgMDM)";
case EMatrixClassifiers::FgMDM_RT_Rebias: return "Minimum Distance to Mean with geodesic filtering Rebias (FgMDM Rebias) (Real Time adaptation assumed)";
case EMatrixClassifiers::FgMDM_Rebias: return "Minimum Distance to Mean with geodesic filtering Rebias (FgMDM Rebias)";
}
return "Invalid";
}
/// <summary> Convert string to Matrix Classifiers. </summary>
/// <param name="type"> The type of classifier. </param>
/// <returns> <see cref="EMatrixClassifiers"/> </returns>
inline EMatrixClassifiers StringToMatrixClassifier(const std::string& type)
{
if (type == "Minimum Distance to Mean (MDM)") { return EMatrixClassifiers::MDM; }
if (type == "Minimum Distance to Mean Rebias (MDM Rebias)") { return EMatrixClassifiers::MDM_Rebias; }
if (type == "Minimum Distance to Mean with geodesic filtering (FgMDM) (Real Time adaptation assumed)") { return EMatrixClassifiers::FgMDM_RT; }
if (type == "Minimum Distance to Mean with geodesic filtering (FgMDM)") { return EMatrixClassifiers::FgMDM; }
if (type == "Minimum Distance to Mean with geodesic filtering Rebias (FgMDM Rebias) (Real Time adaptation assumed)")
{
return EMatrixClassifiers::FgMDM_RT_Rebias;
}
return EMatrixClassifiers::FgMDM_Rebias;
}
///-------------------------------------------------------------------------------------------------
/// <summary> Format the Eigen matrix with Full Precision. </summary>
#define MATRIX_FORMAT Eigen::IOFormat(-2, 0, " ", "\n", "", "", "", "")
///-------------------------------------------------------------------------------------------------
/// <summary> Abstract class of Matrix Classifier. </summary>
class IMatrixClassifier
{
public:
//****************************
//***** Static Functions *****
//****************************
/// <summary> Format the Matrix for XML Saving. </summary>
/// <param name="in"> Matrix. </param>
/// <param name="out"> Stringstream. </param>
/// <returns> <c>True</c> if it succeeds, <c>False</c> otherwise. </returns>
static bool convertMatrixToXMLFormat(const Eigen::MatrixXd& in, std::stringstream& out);
/// <summary> Fill the Matrix From XML Format. </summary>
/// <param name="in"> Stringstream. </param>
/// <param name="out"> Matrix. </param>
/// <param name="rows"> Number of rows. </param>
/// <param name="cols"> Number of cols. </param>
/// <returns> <c>True</c> if it succeeds, <c>False</c> otherwise. </returns>
static bool convertXMLFormatToMatrix(std::stringstream& in, Eigen::MatrixXd& out, size_t rows, size_t cols);
/// <summary> Saves matrix. </summary>
/// <param name="element"> Matrix Node. </param>
/// <param name="matrix"> Matrix to save. </param>
/// <returns> <c>True</c> if it succeeds, <c>False</c> otherwise. </returns>
static bool saveMatrix(tinyxml2::XMLElement* element, const Eigen::MatrixXd& matrix);
/// <summary> Load matrix. </summary>
/// <param name="element"> Matrix Node. </param>
/// <param name="matrix"> Matrix to load. </param>
/// <returns> <c>True</c> if it succeeds, <c>False</c> otherwise. </returns>
static bool loadMatrix(tinyxml2::XMLElement* element, Eigen::MatrixXd& matrix);
//***********************
//***** Constructor *****
//***********************
/// <summary> Default constructor. Initializes a new instance of the <see cref="IMatrixClassifier"/> class. </summary>
IMatrixClassifier() = default;
/// <summary> Default Copy constructor. Initializes a new instance of the <see cref="IMatrixClassifier"/> class. </summary>
/// <param name="obj"> Initial object. </param>
IMatrixClassifier(const IMatrixClassifier& obj) { *this = obj; }
/// <summary> Initializes a new instance of the <see cref="IMatrixClassifier"/> class and set members. </summary>
/// <param name="nbClass"> The number of classes. </param>
/// <param name="metric"> Metric to use to calculate means (see also <see cref="EMetric" />). </param>
explicit IMatrixClassifier(size_t nbClass, EMetric metric);
/// <summary> Finalizes an instance of the <see cref="IMatrixClassifier"/> class. </summary>
virtual ~IMatrixClassifier() = default;
//**********************
//***** Classifier *****
//**********************
virtual size_t getClassCount() const { return m_nbClass; } ///< Get the class count.
virtual void setClassCount(size_t nbClass); ///< Set the class count.
/// <summary> Train the classifier with the dataset. </summary>
/// <param name="dataset"> The dataset (first dimension is the classes, second dimension is the trials as covariance matrix). </param>
/// <returns> <c>True</c> if it succeeds, <c>False</c> otherwise. </returns>
virtual bool train(const std::vector<std::vector<Eigen::MatrixXd>>& dataset) = 0;
/// <summary> Classify the matrix and return the class id (override of same function with all argument). </summary>
/// <param name="sample"> The sample to classify. </param>
/// <param name="classId"> The predicted class. </param>
/// <param name="adaptation"> Adaptation method for the classfier <see cref="EAdaptations" />. </param>
/// <param name="realClassId"> The expected class id if supervised adaptation. </param>
/// <returns> <c>True</c> if it succeeds, <c>False</c> otherwise. </returns>
/// <seealso cref="classify(const Eigen::MatrixXd&, size_t&, std::vector<double>&, std::vector<double>&, EAdaptations, const size_t&)"/>
virtual bool classify(const Eigen::MatrixXd& sample, size_t& classId,
EAdaptations adaptation = EAdaptations::None, const size_t& realClassId = std::numeric_limits<size_t>::max());
/// <summary> Classify the matrix and return the class id, the distance and the probability of each class. </summary>
/// <param name="sample"> The sample to classify. </param>
/// <param name="classId"> The predicted class. </param>
/// <param name="distance"> The distance of the sample with each class. </param>
/// <param name="probability"> The probability of the sample with each class. </param>
/// <param name="adaptation"> Adaptation method for the classfier <see cref="EAdaptations" />. </param>
/// <param name="realClassId"> The expected class id if supervised adaptation. </param>
/// <returns> <c>True</c> if it succeeds, <c>False</c> otherwise. </returns>
virtual bool classify(const Eigen::MatrixXd& sample, size_t& classId, std::vector<double>& distance, std::vector<double>& probability,
EAdaptations adaptation = EAdaptations::None, const size_t& realClassId = std::numeric_limits<size_t>::max()) = 0;
//***********************
//***** XML Manager *****
//***********************
/// <summary> Saves the classifier information in an XML file. </summary>
/// <param name="filename"> Filename. </param>
/// <returns> <c>True</c> if it succeeds, <c>False</c> otherwise. </returns>
virtual bool saveXML(const std::string& filename) const;
/// <summary> Loads the classifier information from an XML file. </summary>
/// <param name="filename"> Filename. </param>
/// <returns> <c>True</c> if it succeeds, <c>False</c> otherwise. </returns>
virtual bool loadXML(const std::string& filename);
//*****************************
//***** Override Operator *****
//*****************************
/// <summary> Check if object are equals (with a precision tolerance). </summary>
/// <param name="obj"> The second object. </param>
/// <param name="precision"> Precision for matrix comparison. </param>
/// <returns> <c>True</c> if the two elements are equals (with a precision tolerance). </returns>
bool isEqual(const IMatrixClassifier& obj, double precision = 1e-6) const;
/// <summary> Copy object value. </summary>
/// <param name="obj"> The object to copy. </param>
void copy(const IMatrixClassifier& obj);
/// <summary> Get the type of the classifier. </summary>
/// <returns> The type in string. </returns>
virtual std::string getType() const = 0;
/// <summary> Get the Classifier information for output. </summary>
/// <returns> The Classifier print in stringstream. </returns>
virtual std::stringstream print() const;
/// <summary> Override the affectation operator. </summary>
/// <param name="obj"> The second object. </param>
/// <returns> The copied object. </returns>
IMatrixClassifier& operator=(const IMatrixClassifier& obj)
{
copy(obj);
return *this;
}
/// <summary> Override the equal operator. </summary>
/// <param name="obj"> The second object. </param>
/// <returns> <c>True</c> if the two <see cref="IMatrixClassifier"/> are equals. </returns>
bool operator==(const IMatrixClassifier& obj) const { return isEqual(obj); }
/// <summary> Override the not equal operator. </summary>
/// <param name="obj"> The second object. </param>
/// <returns> <c>True</c> if the two objects are diffrents. </returns>
bool operator!=(const IMatrixClassifier& obj) const { return !isEqual(obj); }
/// <summary> Override the ostream operator. </summary>
/// <param name="os"> The ostream. </param>
/// <param name="obj"> The object. </param>
/// <returns> Return the modified ostream. </returns>
friend std::ostream& operator <<(std::ostream& os, const IMatrixClassifier& obj)
{
os << obj.print().str();
return os;
}
protected:
/// <summary> Prints the header informations. </summary>
/// <returns> Header informations in stringstream. </returns>
virtual std::stringstream printHeader() const;
/// <summary> Prints the Additional informations. </summary>
/// <returns> Additional informations in stringstream. </returns>
virtual std::stringstream printAdditional() const { return std::stringstream(); }
/// <summary> Prints the Classes informations. </summary>
/// <returns> Classes informations in stringstream. </returns>
virtual std::stringstream printClasses() const { return std::stringstream(); }
//***********************
//***** XML Manager *****
//***********************
/// <summary> Add the attribute on the first node (general informations as classifier type, number of class...).
///
/// -# The type of the classifier : <see cref="getType"/>
/// -# The number of classes : <see cref="m_nbClass"/>
/// -# The metric to use : <see cref="m_metric"/>
/// </summary>
/// <param name="data"> Node to modify. </param>
/// <returns> <c>True</c> if it succeeds, <c>False</c> otherwise. </returns>
virtual bool saveHeader(tinyxml2::XMLElement* data) const;
/// <summary> Loads the attribute on the first node (general informations as classifier type, number of class...).
///
/// -# Check the type : <see cref="getType"/>
/// -# The number of classes : <see cref="m_nbClass"/>
/// -# The metric to use : <see cref="m_metric"/>
/// </summary>
/// <param name="data"> Node to read. </param>
/// <returns> <c>True</c> if it succeeds, <c>False</c> otherwise. </returns>
virtual bool loadHeader(tinyxml2::XMLElement* data);
/// <summary> Save Additionnal informations (none at this level). </summary>
/// <returns> <c>True</c>. </returns>
virtual bool saveAdditional(tinyxml2::XMLDocument& /*doc*/, tinyxml2::XMLElement* /*data*/) const { return true; }
/// <summary> Load Additionnal informations (none at this level). </summary>
/// <returns> <c>True</c>. </returns>
virtual bool loadAdditional(tinyxml2::XMLElement* /*data*/) { return true; }
/// <summary> Save Classes informations (none at this level). </summary>
/// <returns> <c>True</c>. </returns>
virtual bool saveClasses(tinyxml2::XMLDocument& /*doc*/, tinyxml2::XMLElement* /*data*/) const { return true; }
/// <summary> Load Classes informations (none at this level). </summary>
/// <returns> <c>True</c>. </returns>
virtual bool loadClasses(tinyxml2::XMLElement* /*data*/) { return true; }
//*********************
//***** Variables *****
//*********************
size_t m_nbClass = 2; ///< Number of classes to classify.
EMetric m_metric = EMetric::Riemann; ///< Metric to use to calculate means and distances (see also <see cref="EMetric" />).
};
} // namespace Geometry
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,212 @@
#include "geometry/Basics.hpp"
#include <unsupported/Eigen/MatrixFunctions> // SQRT of Matrix
namespace Geometry {
//************************************************
//******************** Matrix ********************
//************************************************
//---------------------------------------------------------------------------------------------------
Eigen::MatrixXd AffineTransformation(const Eigen::MatrixXd& ref, const Eigen::MatrixXd& matrix)
{
const Eigen::MatrixXd isR = ref.sqrt().inverse(); // Inverse Square root of Reference matrix => isR
return isR * matrix * isR.transpose(); // Affine transformation : isR * sample * isR^T
}
//---------------------------------------------------------------------------------------------------
//---------------------------------------------------------------------------------------------------
bool MatrixStandardization(Eigen::MatrixXd& matrix, const EStandardization standard)
{
if (standard == EStandardization::Center) { return MatrixCenter(matrix); }
if (standard == EStandardization::StandardScale) { return MatrixStandardization(matrix); }
return true;
}
//---------------------------------------------------------------------------------------------------
//---------------------------------------------------------------------------------------------------
bool MatrixStandardization(const Eigen::MatrixXd& in, Eigen::MatrixXd& out, const EStandardization standard)
{
out = in;
return MatrixStandardization(out, standard);
}
//---------------------------------------------------------------------------------------------------
//---------------------------------------------------------------------------------------------------
bool MatrixCenter(Eigen::MatrixXd& matrix)
{
for (size_t i = 0, r = matrix.rows(), c = matrix.cols(); i < r; ++i)
{
const double mu = matrix.row(i).mean();
for (size_t j = 0; j < c; ++j) { matrix(i, j) -= mu; }
}
return true;
}
//---------------------------------------------------------------------------------------------------
//---------------------------------------------------------------------------------------------------
bool MatrixCenter(const Eigen::MatrixXd& in, Eigen::MatrixXd& out)
{
out = in;
return MatrixCenter(out);
}
//---------------------------------------------------------------------------------------------------
//---------------------------------------------------------------------------------------------------
bool MatrixStandardScaler(Eigen::MatrixXd& matrix)
{
Eigen::RowVectorXd dummyScale;
return MatrixStandardScaler(matrix, dummyScale);
}
//---------------------------------------------------------------------------------------------------
//---------------------------------------------------------------------------------------------------
bool MatrixStandardScaler(Eigen::MatrixXd& matrix, Eigen::RowVectorXd& scale)
{
const size_t r = matrix.rows(), c = matrix.cols();
std::vector<double> mu(r, 0), sigma(r, 0);
scale.resize(r);
for (size_t i = 0; i < r; ++i)
{
for (size_t j = 0; j < c; ++j)
{
const double value = matrix(i, j);
mu[i] += value;
sigma[i] += value * value;
}
mu[i] /= double(c);
sigma[i] = sigma[i] / double(c) - mu[i] * mu[i];
scale[i] = sigma[i] == 0 ? 1 : sqrt(sigma[i]);
for (size_t j = 0; j < c; ++j) { matrix(i, j) = (matrix(i, j) - mu[i]) / scale[i]; }
}
return true;
}
//---------------------------------------------------------------------------------------------------
//---------------------------------------------------------------------------------------------------
bool MatrixStandardScaler(const Eigen::MatrixXd& in, Eigen::MatrixXd& out, Eigen::RowVectorXd& scale)
{
out = in;
return MatrixStandardScaler(out, scale);
}
//---------------------------------------------------------------------------------------------------
//---------------------------------------------------------------------------------------------------
bool MatrixStandardScaler(const Eigen::MatrixXd& in, Eigen::MatrixXd& out)
{
Eigen::RowVectorXd dummyScale;
return MatrixStandardScaler(in, out, dummyScale);
}
//---------------------------------------------------------------------------------------------------
//---------------------------------------------------------------------------------------------------
std::string MatrixPrint(const Eigen::MatrixXd& matrix)
{
std::stringstream sstream;
sstream << matrix;
return sstream.str();
}
//---------------------------------------------------------------------------------------------------
//---------------------------------------------------------------------------------------------------
bool AreEquals(const Eigen::MatrixXd& matrix1, const Eigen::MatrixXd& matrix2, const double precision)
{
return matrix1.size() == matrix2.size() && (matrix1.size() == 0 || matrix1.isApprox(matrix2, precision));
}
//---------------------------------------------------------------------------------------------------
//************************************************
//************************************************
//************************************************
//*************************************************************
//******************** Index Manipulations ********************
//*************************************************************
//---------------------------------------------------------------------------------------------------
Eigen::RowVectorXd GetElements(const Eigen::RowVectorXd& row, const std::vector<size_t>& index)
{
const size_t k = index.size();
Eigen::RowVectorXd result(k);
for (size_t i = 0; i < k; ++i) { result[i] = row[index[i]]; }
return result;
}
//---------------------------------------------------------------------------------------------------
//*************************************************************
//*************************************************************
//*************************************************************
//***************************************************
//******************** Validates ********************
//***************************************************
//---------------------------------------------------------------------------------------------------
bool InRange(const double value, const double min, const double max) { return (min <= value && value <= max); }
//---------------------------------------------------------------------------------------------------
//---------------------------------------------------------------------------------------------------
bool AreNotEmpty(const std::vector<Eigen::MatrixXd>& matrices)
{
if (matrices.empty()) { return false; }
for (const auto& m : matrices) { if (!IsNotEmpty(m)) { return false; } }
return true;
}
//---------------------------------------------------------------------------------------------------
//---------------------------------------------------------------------------------------------------
bool IsNotEmpty(const Eigen::MatrixXd& matrix) { return (matrix.size() != 0); }
//---------------------------------------------------------------------------------------------------
//---------------------------------------------------------------------------------------------------
bool HaveSameSize(const Eigen::MatrixXd& a, const Eigen::MatrixXd& b) { return (IsNotEmpty(a) && a.rows() == b.rows() && a.cols() == b.cols()); }
//---------------------------------------------------------------------------------------------------
bool IsSquare(const Eigen::MatrixXd& matrix) { return (IsNotEmpty(matrix) && matrix.rows() == matrix.cols()); }
//---------------------------------------------------------------------------------------------------
//---------------------------------------------------------------------------------------------------
bool AreSquare(const std::vector<Eigen::MatrixXd>& matrices)
{
if (matrices.empty()) { return false; }
for (const auto& m : matrices) { if (!IsSquare(m)) { return false; } }
return true;
}
//---------------------------------------------------------------------------------------------------
//---------------------------------------------------------------------------------------------------
bool HaveSameSize(const std::vector<Eigen::MatrixXd>& matrices)
{
if (matrices.empty()) { return false; }
const size_t r = matrices[0].rows(), c = matrices[0].cols();
for (const auto& m : matrices) { if (size_t(m.rows()) != r || size_t(m.cols()) != c) { return false; } }
return true;
}
//---------------------------------------------------------------------------------------------------
//***************************************************
//***************************************************
//***************************************************
//********************************************************
//******************** CSV MANAGEMENT ********************
//********************************************************
//---------------------------------------------------------------------------------------------------
std::vector<std::string> Split(const std::string& s, const std::string& sep)
{
std::vector<std::string> result;
std::string::size_type i = 0, j;
const std::string::size_type n = sep.size();
while ((j = s.find(sep, i)) != std::string::npos)
{
result.emplace_back(s, i, j - i); // Add part
i = j + n; // Update pos
}
result.emplace_back(s, i, s.size() - 1 - i); // Last without \n
return result;
}
//---------------------------------------------------------------------------------------------------
//********************************************************
//********************************************************
//********************************************************
} // namespace Geometry
@@ -0,0 +1,93 @@
#include "geometry/Classification.hpp"
#include "geometry/Covariance.hpp"
#include "geometry/Basics.hpp"
namespace Geometry {
///-------------------------------------------------------------------------------------------------
bool LSQR(const std::vector<std::vector<Eigen::RowVectorXd>>& dataset, Eigen::MatrixXd& weight)
{
// Precomputation
if (dataset.empty()) { return false; }
const size_t nbClass = dataset.size(), nbFeatures = dataset[0][0].size();
std::vector<size_t> nbSample(nbClass);
size_t totalSample = 0;
for (size_t k = 0; k < nbClass; ++k)
{
if (dataset[k].empty()) { return false; }
nbSample[k] = dataset[k].size();
totalSample += nbSample[k];
}
// Compute Class Euclidian mean
Eigen::MatrixXd mean = Eigen::MatrixXd::Zero(nbClass, nbFeatures);
for (size_t k = 0; k < nbClass; ++k)
{
for (size_t i = 0; i < nbSample[k]; ++i) { mean.row(k) += dataset[k][i]; }
mean.row(k) /= double(nbSample[k]);
}
// Compute Class Covariance
Eigen::MatrixXd cov = Eigen::MatrixXd::Zero(nbFeatures, nbFeatures);
for (size_t k = 0; k < nbClass; ++k)
{
//Fit Data to existing covariance matrix method
Eigen::MatrixXd classData(nbFeatures, nbSample[k]);
for (size_t i = 0; i < nbSample[k]; ++i) { classData.col(i) = dataset[k][i]; }
// Standardize Features
Eigen::RowVectorXd scale;
MatrixStandardScaler(classData, scale);
//Compute Covariance of this class
Eigen::MatrixXd classCov;
if (!CovarianceMatrix(classData, classCov, EEstimator::LWF)) { return false; }
// Rescale
for (size_t i = 0; i < nbFeatures; ++i) { for (size_t j = 0; j < nbFeatures; ++j) { classCov(i, j) *= scale[i] * scale[j]; } }
//Add to cov with good weight
cov += (double(nbSample[k]) / double(totalSample)) * classCov;
}
// linear least squares systems solver
// Chosen solver with the performance table of this page : https://eigen.tuxfamily.org/dox/group__TutorialLinearAlgebra.html
weight = cov.colPivHouseholderQr().solve(mean.transpose()).transpose();
//weight = cov.completeOrthogonalDecomposition().solve(mean.transpose()).transpose();
//weight = cov.bdcSvd(ComputeThinU | ComputeThinV).solve(mean.transpose()).transpose();
// Treat binary case as a special case
if (nbClass == 2)
{
const Eigen::MatrixXd tmp = weight.row(1) - weight.row(0); // Need to use a tmp variable otherwise sometimes error
weight = tmp;
}
return true;
}
///-------------------------------------------------------------------------------------------------
///-------------------------------------------------------------------------------------------------
bool FgDACompute(const std::vector<std::vector<Eigen::RowVectorXd>>& dataset, Eigen::MatrixXd& weight)
{
// Compute LSQR Weight
Eigen::MatrixXd w;
if (!LSQR(dataset, w)) { return false; }
const size_t nbClass = w.rows();
// Transform to FgDA Weight
const Eigen::MatrixXd wT = w.transpose();
weight = (wT * (w * wT).colPivHouseholderQr().solve(Eigen::MatrixXd::Identity(nbClass, nbClass))) * w;
return true;
}
///-------------------------------------------------------------------------------------------------
///-------------------------------------------------------------------------------------------------
bool FgDAApply(const Eigen::RowVectorXd& in, Eigen::RowVectorXd& out, const Eigen::MatrixXd& weight)
{
if (in.cols() != weight.rows()) { return false; }
out = in * weight;
return true;
}
///-------------------------------------------------------------------------------------------------
} // namespace Geometry
@@ -0,0 +1,172 @@
# include "geometry/Covariance.hpp"
#include <algorithm> // std::min/max
namespace Geometry {
//***********************************************************
//******************** COVARIANCES BASES ********************
//***********************************************************
//---------------------------------------------------------------------------------------------------
double Variance(const Eigen::RowVectorXd& x)
{
const size_t S = x.cols(); // Number of Samples => S
if (S == 0) { return 0; } // If false input
const double mu = x.mean();
return x.cwiseProduct(x).sum() / S - mu * mu;
}
//---------------------------------------------------------------------------------------------------
//---------------------------------------------------------------------------------------------------
double Covariance(const Eigen::RowVectorXd& x, const Eigen::RowVectorXd& y)
{
const size_t xS = x.cols(), yS = y.cols(); // Number of Samples => S
if (xS == 0 || xS != yS) { return 0; } // If false input
return (x.cwiseProduct(y).sum() - x.sum() * y.sum() / xS) / xS;
}
//---------------------------------------------------------------------------------------------------
//---------------------------------------------------------------------------------------------------
bool ShrunkCovariance(Eigen::MatrixXd& cov, const double shrinkage)
{
if (!InRange(shrinkage, 0, 1)) { return false; } // Verification
const size_t n = cov.rows(); // Number of Features => N
const double coef = shrinkage * cov.trace() / n; // Diagonal Coefficient
cov = (1 - shrinkage) * cov; // Shrinkage
for (size_t i = 0; i < n; ++i) { cov(i, i) += coef; } // Add Diagonal Coefficient
return true;
}
//---------------------------------------------------------------------------------------------------
//---------------------------------------------------------------------------------------------------
bool ShrunkCovariance(const Eigen::MatrixXd& in, Eigen::MatrixXd& out, const double shrinkage)
{
out = in;
return ShrunkCovariance(out, shrinkage);
}
//---------------------------------------------------------------------------------------------------
//---------------------------------------------------------------------------------------------------
bool CovarianceMatrix(const Eigen::MatrixXd& in, Eigen::MatrixXd& out, const EEstimator estimator, const EStandardization standard)
{
if (!IsNotEmpty(in)) { return false; } // Verification
Eigen::MatrixXd sample;
MatrixStandardization(in, sample, standard); // Standardization
switch (estimator) // Switch Method
{
case EEstimator::COV: return CovarianceMatrixCOV(sample, out);
case EEstimator::SCM: return CovarianceMatrixSCM(sample, out);
case EEstimator::LWF: return CovarianceMatrixLWF(sample, out);
case EEstimator::OAS: return CovarianceMatrixOAS(sample, out);
case EEstimator::MCD: return CovarianceMatrixMCD(sample, out);
case EEstimator::COR: return CovarianceMatrixCOR(sample, out);
default: return CovarianceMatrixIDE(sample, out);
}
}
//---------------------------------------------------------------------------------------------------
//***********************************************************
//***********************************************************
//***********************************************************
//***********************************************************
//******************** COVARIANCES TYPES ********************
//***********************************************************
//---------------------------------------------------------------------------------------------------
bool CovarianceMatrixCOV(const Eigen::MatrixXd& samples, Eigen::MatrixXd& cov)
{
const size_t n = samples.rows(); // Number of Features => N
cov.resize(n, n); // Init size of matrix
for (size_t i = 0; i < n; ++i)
{
const Eigen::RowVectorXd ri = samples.row(i);
cov(i, i) = Variance(ri); // Diagonal Value
for (size_t j = i + 1; j < n; ++j)
{
const Eigen::RowVectorXd rj = samples.row(j);
cov(i, j) = cov(j, i) = Covariance(ri, rj); // Symetric covariance
}
}
return true;
}
//---------------------------------------------------------------------------------------------------
//---------------------------------------------------------------------------------------------------
bool CovarianceMatrixSCM(const Eigen::MatrixXd& samples, Eigen::MatrixXd& cov)
{
cov = samples * samples.transpose(); // X*X^T
cov /= cov.trace(); // X*X^T / trace(X*X^T)
return true;
}
//---------------------------------------------------------------------------------------------------
//---------------------------------------------------------------------------------------------------
bool CovarianceMatrixLWF(const Eigen::MatrixXd& samples, Eigen::MatrixXd& cov)
{
const size_t n = samples.rows(), S = samples.cols(); // Number of Features & Samples => N & S
CovarianceMatrixCOV(samples, cov); // Initial Covariance Matrix => Cov
const double mu = cov.trace() / n;
Eigen::MatrixXd mDelta = cov; // mDelta = cov - mu * I_n
for (size_t i = 0; i < n; ++i) { mDelta(i, i) -= mu; }
const Eigen::MatrixXd x2 = samples.cwiseProduct(samples), // Squared each sample => X^2
cov2 = cov.cwiseProduct(cov); // Squared each element of Cov => Cov^2
const double delta = mDelta.cwiseProduct(mDelta).sum() / n,
beta = 1. / double(n * S) * (x2 * x2.transpose() / double(S) - cov2).sum(),
shrinkage = std::min(beta, delta) / delta; // Assure shrinkage <= 1
return ShrunkCovariance(cov, shrinkage); // Shrinkage of the matrix
}
//---------------------------------------------------------------------------------------------------
//---------------------------------------------------------------------------------------------------
bool CovarianceMatrixOAS(const Eigen::MatrixXd& samples, Eigen::MatrixXd& cov)
{
const size_t n = samples.rows(), S = samples.cols(); // Number of Features & Samples => N & S
CovarianceMatrixCOV(samples, cov); // Initial Covariance Matrix => Cov
// Compute Shrinkage : Formula from Chen et al.'s
const double mu = cov.trace() / n,
mu2 = mu * mu,
alpha = cov.cwiseProduct(cov).mean(),
num = alpha + mu2,
den = (S + 1) * (alpha - mu2 / n),
shrinkage = (den == 0) ? 1.0 : std::min(num / den, 1.0);
return ShrunkCovariance(cov, shrinkage); // Shrinkage of the matrix
}
//---------------------------------------------------------------------------------------------------
//---------------------------------------------------------------------------------------------------
bool CovarianceMatrixMCD(const Eigen::MatrixXd& samples, Eigen::MatrixXd& cov) { return CovarianceMatrixIDE(samples, cov); }
//---------------------------------------------------------------------------------------------------
//---------------------------------------------------------------------------------------------------
bool CovarianceMatrixCOR(const Eigen::MatrixXd& samples, Eigen::MatrixXd& cov)
{
const size_t n = samples.rows(); // Number of Features => N
CovarianceMatrixCOV(samples, cov); // Initial Covariance Matrix => Cov
const Eigen::MatrixXd d = cov.diagonal().cwiseSqrt(); // Squared root of diagonal
for (size_t i = 0; i < n; ++i) { for (size_t j = 0; j < n; ++j) { cov(i, j) /= d(i) * d(j); } }
return true;
}
//---------------------------------------------------------------------------------------------------
//---------------------------------------------------------------------------------------------------
bool CovarianceMatrixIDE(const Eigen::MatrixXd& samples, Eigen::MatrixXd& cov)
{
cov = Eigen::MatrixXd::Identity(samples.rows(), samples.rows());
return true;
}
//---------------------------------------------------------------------------------------------------
//***********************************************************
//***********************************************************
//***********************************************************
} // namespace Geometry
@@ -0,0 +1,68 @@
#include "geometry/Distance.hpp"
#include "geometry/Basics.hpp"
#include <unsupported/Eigen/MatrixFunctions>
namespace Geometry {
//---------------------------------------------------------------------------------------------------
double Distance(const Eigen::MatrixXd& a, const Eigen::MatrixXd& b, const EMetric metric)
{
if (!HaveSameSize(a, b)) { return 0; }
switch (metric)
{
case EMetric::Riemann: return DistanceRiemann(a, b);
case EMetric::Euclidian: return DistanceEuclidian(a, b);
case EMetric::LogEuclidian: return DistanceLogEuclidian(a, b);
case EMetric::LogDet: return DistanceLogDet(a, b);
case EMetric::Kullback: return DistanceKullbackSym(a, b);
case EMetric::Wasserstein: return DistanceWasserstein(a, b);
case EMetric::Identity:
default: return 1.0;
}
}
//---------------------------------------------------------------------------------------------------
//---------------------------------------------------------------------------------------------------
double DistanceRiemann(const Eigen::MatrixXd& a, const Eigen::MatrixXd& b)
{
const Eigen::GeneralizedSelfAdjointEigenSolver<Eigen::MatrixXd> es(a, b);
const Eigen::ArrayXd result = es.eigenvalues();
return sqrt(result.log().square().sum());
}
//---------------------------------------------------------------------------------------------------
//---------------------------------------------------------------------------------------------------
double DistanceEuclidian(const Eigen::MatrixXd& a, const Eigen::MatrixXd& b) { return (b - a).norm(); }
//---------------------------------------------------------------------------------------------------
//---------------------------------------------------------------------------------------------------
double DistanceLogEuclidian(const Eigen::MatrixXd& a, const Eigen::MatrixXd& b) { return DistanceEuclidian(a.log(), b.log()); }
//---------------------------------------------------------------------------------------------------
//---------------------------------------------------------------------------------------------------
double DistanceLogDet(const Eigen::MatrixXd& a, const Eigen::MatrixXd& b)
{
return sqrt(log((0.5 * (a + b)).determinant()) - 0.5 * log(a.determinant() * b.determinant()));
}
//---------------------------------------------------------------------------------------------------
//---------------------------------------------------------------------------------------------------
double DistanceKullback(const Eigen::MatrixXd& a, const Eigen::MatrixXd& b)
{
return 0.5 * ((b.inverse() * a).trace() - a.rows() + log(b.determinant() / a.determinant()));
}
//---------------------------------------------------------------------------------------------------
//---------------------------------------------------------------------------------------------------
double DistanceKullbackSym(const Eigen::MatrixXd& a, const Eigen::MatrixXd& b) { return DistanceKullback(a, b) + DistanceKullback(b, a); }
//---------------------------------------------------------------------------------------------------
//---------------------------------------------------------------------------------------------------
double DistanceWasserstein(const Eigen::MatrixXd& a, const Eigen::MatrixXd& b)
{
const Eigen::MatrixXd sB = b.sqrt();
return sqrt((a + b - 2 * (sB * a * sB).sqrt()).trace());
}
//---------------------------------------------------------------------------------------------------
} // namespace Geometry
@@ -0,0 +1,92 @@
#include "geometry/Featurization.hpp"
#include <unsupported/Eigen/MatrixFunctions>
#include "geometry/Basics.hpp"
namespace Geometry {
#ifndef M_SQRT2
#define M_SQRT2 1.4142135623730950488016887242097
#endif
//---------------------------------------------------------------------------------------------------
bool Featurization(const Eigen::MatrixXd& in, Eigen::RowVectorXd& out, const bool tangent, const Eigen::MatrixXd& ref)
{
if (tangent) { return TangentSpace(in, out, ref); }
return SqueezeUpperTriangle(in, out, true);
}
//---------------------------------------------------------------------------------------------------
//---------------------------------------------------------------------------------------------------
bool UnFeaturization(const Eigen::RowVectorXd& in, Eigen::MatrixXd& out, const bool tangent, const Eigen::MatrixXd& ref)
{
if (tangent) { return UnTangentSpace(in, out, ref); }
return UnSqueezeUpperTriangle(in, out, true);
}
//---------------------------------------------------------------------------------------------------
//---------------------------------------------------------------------------------------------------
bool SqueezeUpperTriangle(const Eigen::MatrixXd& in, Eigen::RowVectorXd& out, const bool rowMajor)
{
if (!IsSquare(in)) { return false; } // Verification
const size_t n = in.rows(); // Number of Features => N
out.resize(n * (n + 1) / 2); // Resize
size_t idx = 0; // Row Index => idx
// Row Major or Diagonal Method
if (rowMajor) { for (size_t i = 0; i < n; ++i) { for (size_t j = i; j < n; ++j) { out[idx++] = in(i, j); } } }
else { for (size_t i = 0; i < n; ++i) { for (size_t j = i; j < n; ++j) { out[idx++] = in(j, j - i); } } }
return true;
}
//---------------------------------------------------------------------------------------------------
//---------------------------------------------------------------------------------------------------
bool UnSqueezeUpperTriangle(const Eigen::RowVectorXd& in, Eigen::MatrixXd& out, const bool rowMajor)
{
const size_t nR = in.size(), // Size of Row => Nr
n = int((sqrt(1 + 8 * nR) - 1) / 2); // Number of Features => N
if (n == 0) { return false; } // Verification
out.setZero(n, n); // Init
size_t idx = 0; // Row Index => idx
// Row Major or Diagonal Method
if (rowMajor) { for (size_t i = 0; i < n; ++i) { for (size_t j = i; j < n; ++j) { out(j, i) = out(i, j) = in[idx++]; } } }
else { for (size_t i = 0; i < n; ++i) { for (size_t j = i; j < n; ++j) { out(j - i, j) = out(j, j - i) = in[idx++]; } } }
return true;
}
//---------------------------------------------------------------------------------------------------
//---------------------------------------------------------------------------------------------------
bool TangentSpace(const Eigen::MatrixXd& in, Eigen::RowVectorXd& out, const Eigen::MatrixXd& ref)
{
if (!IsSquare(in)) { return false; } // Verification
const size_t n = in.rows(); // Number of Features => N
const Eigen::MatrixXd sC = (ref.size() == 0) ? Eigen::MatrixXd::Identity(n, n) : Eigen::MatrixXd(ref.sqrt()),
isC = sC.inverse(), // Inverse Square root of ref => isC
mJ = (isC * in * isC).log(), // Transformation Matrix => mJ
mCoeffs = M_SQRT2 * Eigen::MatrixXd(Eigen::MatrixXd::Ones(n, n).triangularView<Eigen::StrictlyUpper>())
+ Eigen::MatrixXd::Identity(n, n);
Eigen::RowVectorXd vJ, vCoeffs;
if (!SqueezeUpperTriangle(mJ, vJ, true)) { return false; } // Get upper triangle of J => vJ
if (!SqueezeUpperTriangle(mCoeffs, vCoeffs, true)) { return false; } // ... of Coefs => vCoeffs
out = vCoeffs.cwiseProduct(vJ); // element-wise multiplication
return true;
}
//---------------------------------------------------------------------------------------------------
//---------------------------------------------------------------------------------------------------
bool UnTangentSpace(const Eigen::RowVectorXd& in, Eigen::MatrixXd& out, const Eigen::MatrixXd& ref)
{
const size_t n = out.rows(); // Number of Features => N
if (!UnSqueezeUpperTriangle(in, out)) { return false; }
const Eigen::MatrixXd sC = (ref.size() == 0) ? Eigen::MatrixXd::Identity(n, n) : Eigen::MatrixXd(ref.sqrt()),
coeffs = Eigen::MatrixXd(out.triangularView<Eigen::StrictlyUpper>()) / M_SQRT2;
out = sC * (Eigen::MatrixXd(out.diagonal().asDiagonal()) + coeffs + coeffs.transpose()).exp() * sC;
return true;
}
//---------------------------------------------------------------------------------------------------
} // namespace Geometry
@@ -0,0 +1,57 @@
#include "geometry/Geodesic.hpp"
#include "geometry/Basics.hpp"
#include <unsupported/Eigen/MatrixFunctions>
namespace Geometry {
//---------------------------------------------------------------------------------------------------
bool Geodesic(const Eigen::MatrixXd& a, const Eigen::MatrixXd& b, Eigen::MatrixXd& g, const EMetric metric, const double alpha)
{
if (!HaveSameSize(a, b)) { return false; } // Verification same size
if (!IsSquare(a)) { return false; } // Verification square matrix
if (!InRange(alpha, 0, 1)) { return false; } // Verification alpha in [0;1]
switch (metric) // Switch metric
{
case EMetric::Riemann: return GeodesicRiemann(a, b, g, alpha);
case EMetric::Euclidian: return GeodesicEuclidian(a, b, g, alpha);
case EMetric::LogEuclidian: return GeodesicLogEuclidian(a, b, g, alpha);
case EMetric::Identity:
default: return GeodesicIdentity(a, b, g, alpha);
}
}
//---------------------------------------------------------------------------------------------------
//---------------------------------------------------------------------------------------------------
bool GeodesicRiemann(const Eigen::MatrixXd& a, const Eigen::MatrixXd& b, Eigen::MatrixXd& g, const double alpha)
{
const Eigen::MatrixXd sA = a.sqrt(), isA = sA.inverse();
g = sA * (isA * b * isA).pow(alpha) * sA;
return true;
}
//---------------------------------------------------------------------------------------------------
//---------------------------------------------------------------------------------------------------
bool GeodesicEuclidian(const Eigen::MatrixXd& a, const Eigen::MatrixXd& b, Eigen::MatrixXd& g, const double alpha)
{
g = (1 - alpha) * a + alpha * b;
return true;
}
//---------------------------------------------------------------------------------------------------
//---------------------------------------------------------------------------------------------------
bool GeodesicLogEuclidian(const Eigen::MatrixXd& a, const Eigen::MatrixXd& b, Eigen::MatrixXd& g, const double alpha)
{
g = ((1 - alpha) * a.log() + alpha * b.log()).exp();
return true;
}
//---------------------------------------------------------------------------------------------------
//---------------------------------------------------------------------------------------------------
bool GeodesicIdentity(const Eigen::MatrixXd& a, const Eigen::MatrixXd& /*b*/, Eigen::MatrixXd& g, const double /*alpha*/)
{
g = Eigen::MatrixXd::Identity(a.rows(), a.rows());
return true;
}
//---------------------------------------------------------------------------------------------------
} // namespace Geometry
@@ -0,0 +1,227 @@
#include "geometry/Metrics.hpp"
#include "geometry/Basics.hpp"
#include "geometry/Geodesic.hpp"
#include "geometry/Distance.hpp"
#include "geometry/Mean.hpp"
#include <unsupported/Eigen/MatrixFunctions>
#include <iostream>
namespace Geometry {
//static const double EPSILON = 0.000000001; // 10^{-9}
static const double EPSILON = 0.0001; // 10^{-4}
static const size_t ITER_MAX = 50;
//---------------------------------------------------------------------------------------------------
bool Mean(const std::vector<Eigen::MatrixXd>& covs, Eigen::MatrixXd& mean, const EMetric metric)
{
if (covs.empty()) { return false; } // If no matrix in vector
if (covs.size() == 1) // If just one matrix in vector
{
mean = covs[0];
return true;
}
if (!HaveSameSize(covs))
{
std::cout << "Matrices haven't same size." << std::endl;
return false;
}
// Force Square Matrix for non Euclidian and non Identity metric
if (!IsSquare(covs[0]) && (metric != EMetric::Euclidian && metric != EMetric::Identity))
{
std::cout << "Non Square Matrix is invalid with " << toString(metric) << " metric." << std::endl;
return false;
}
switch (metric) // Switch method
{
case EMetric::Riemann: return MeanRiemann(covs, mean);
case EMetric::Euclidian: return MeanEuclidian(covs, mean);
case EMetric::LogEuclidian: return MeanLogEuclidian(covs, mean);
case EMetric::LogDet: return MeanLogDet(covs, mean);
case EMetric::Kullback: return MeanKullback(covs, mean);
case EMetric::ALE: return MeanALE(covs, mean);
case EMetric::Harmonic: return MeanHarmonic(covs, mean);
case EMetric::Wasserstein: return MeanWasserstein(covs, mean);
case EMetric::Identity:
default: return MeanIdentity(covs, mean);
}
}
//---------------------------------------------------------------------------------------------------
//---------------------------------------------------------------------------------------------------
bool AJDPham(const std::vector<Eigen::MatrixXd>& covs, Eigen::MatrixXd& ajd, double /*epsilon*/, const int /*maxIter*/)
{
MeanIdentity(covs, ajd);
return true;
}
//---------------------------------------------------------------------------------------------------
//---------------------------------------------------------------------------------------------------
bool MeanRiemann(const std::vector<Eigen::MatrixXd>& covs, Eigen::MatrixXd& mean)
{
const size_t k = covs.size(), n = covs[0].rows(); // Number of Matrix & Features => K & N
size_t i = 0; // Index of Covariance Matrix => i
double nu = 1.0, // Coefficient change => nu
tau = std::numeric_limits<double>::max(), // Coefficient change criterion => tau
crit = std::numeric_limits<double>::max(); // Current change => crit
if (!MeanEuclidian(covs, mean)) { return false; } // Initial Mean
while (i < ITER_MAX && EPSILON < crit && EPSILON < nu) // Stopping criterion
{
i++; // Iteration Criterion
const Eigen::MatrixXd sC = mean.sqrt(), isC = sC.inverse(); // Square root & Inverse Square root of Mean => sC & isC
Eigen::MatrixXd mJ = Eigen::MatrixXd::Zero(n, n); // Change => J
for (const auto& cov : covs) { mJ += (isC * cov * isC).log(); } // Sum of log(isC*Ci*isC)
mJ /= double(k); // Normalization
crit = mJ.norm(); // Current change criterion
mean = sC * (nu * mJ).exp() * sC; // Update Mean => M = sC * exp(nu*J) * sC
const double h = nu * crit; // Update Coefficient change
if (h < tau)
{
nu *= 0.95;
tau = h;
}
else { nu *= 0.5; }
}
return true;
}
//---------------------------------------------------------------------------------------------------
//---------------------------------------------------------------------------------------------------
bool MeanEuclidian(const std::vector<Eigen::MatrixXd>& covs, Eigen::MatrixXd& mean)
{
const size_t k = covs.size(), n = covs[0].rows(); // Number of Matrix & Features => K & N
mean = Eigen::MatrixXd::Zero(n, n); // Initial Mean
for (const auto& cov : covs) { mean += cov; } // Sum of Ci
mean /= double(k); // Normalization
return true;
}
//---------------------------------------------------------------------------------------------------
//---------------------------------------------------------------------------------------------------
bool MeanLogEuclidian(const std::vector<Eigen::MatrixXd>& covs, Eigen::MatrixXd& mean)
{
const size_t k = covs.size(), n = covs[0].rows(); // Number of Matrix & Features => K & N
mean = Eigen::MatrixXd::Zero(n, n); // Initial Mean
for (const auto& cov : covs) { mean += cov.log(); } // Sum of log(Ci)
mean = (mean / double(k)).exp(); // Normalization
return true;
}
//---------------------------------------------------------------------------------------------------
//---------------------------------------------------------------------------------------------------
bool MeanLogDet(const std::vector<Eigen::MatrixXd>& covs, Eigen::MatrixXd& mean)
{
const size_t k = covs.size(), n = covs[0].rows(); // Number of Matrix & Features => K & N
size_t i = 0; // Index of Covariance Matrix => i
double crit = std::numeric_limits<double>::max(); // Current change => crit
if (!MeanEuclidian(covs, mean)) { return false; } // Initial Mean
while (i < ITER_MAX && EPSILON < crit) // Stopping criterion
{
i++; // Iteration Criterion
Eigen::MatrixXd mJ = Eigen::MatrixXd::Zero(n, n); // Change => J
for (const auto& cov : covs) { mJ += (0.5 * (cov + mean)).inverse(); } // Sum of ((Ci+M)/2)^{-1}
mJ = (mJ / double(k)).inverse(); // Normalization
crit = (mJ - mean).norm(); // Current change criterion
mean = mJ; // Update mean
}
return true;
}
//---------------------------------------------------------------------------------------------------
//---------------------------------------------------------------------------------------------------
bool MeanKullback(const std::vector<Eigen::MatrixXd>& covs, Eigen::MatrixXd& mean)
{
Eigen::MatrixXd m1, m2;
if (!MeanEuclidian(covs, m1)) { return false; }
if (!MeanHarmonic(covs, m2)) { return false; }
if (!GeodesicRiemann(m1, m2, mean, 0.5)) { return false; }
return true;
}
//---------------------------------------------------------------------------------------------------
//---------------------------------------------------------------------------------------------------
bool MeanWasserstein(const std::vector<Eigen::MatrixXd>& covs, Eigen::MatrixXd& mean)
{
const size_t k = covs.size(), n = covs[0].rows(); // Number of Matrix & Features => K & N
size_t i = 0; // Index of Covariance Matrix => i
double crit = std::numeric_limits<double>::max(); // Current change => crit
if (!MeanEuclidian(covs, mean)) { return false; } // Initial Mean
Eigen::MatrixXd sC = mean.sqrt(); // Square root of Mean => sC
while (i < ITER_MAX && EPSILON < crit) // Stopping criterion
{
i++; // Iteration Criterion
Eigen::MatrixXd mJ = Eigen::MatrixXd::Zero(n, n); // Change => J
for (const auto& cov : covs) { mJ += (sC * cov * sC).sqrt(); } // Sum of sqrt(sC*Ci*sC)
mJ /= double(k); // Normalization
const Eigen::MatrixXd sJ = mJ.sqrt(); // Square root of change => sJ
crit = (sJ - sC).norm(); // Current change criterion
sC = sJ; // Update sC
}
mean = sC * sC; // Un-square root
return true;
}
//---------------------------------------------------------------------------------------------------
//---------------------------------------------------------------------------------------------------
bool MeanALE(const std::vector<Eigen::MatrixXd>& covs, Eigen::MatrixXd& mean)
{
const size_t k = covs.size(), n = covs[0].rows(); // Number of Matrix & Features => K & N
size_t i = 0; // Index of Covariance Matrix => i
double crit = std::numeric_limits<double>::max(); // Change criterion => crit
if (!AJDPham(covs, mean)) { return false; } // Initial Mean
Eigen::MatrixXd mJ; // Change
while (i < ITER_MAX && EPSILON < crit) // Stopping criterion
{
i++; // Iteration Criterion
mJ = Eigen::MatrixXd::Zero(n, n); // Change => J
for (const auto& cov : covs) { mJ += (mean.transpose() * cov * mean).log(); } // Sum of log(C^T*Ci*C)
mJ /= double(k); // Normalization
Eigen::MatrixXd update = mJ.exp().diagonal().asDiagonal(); // Update Form => U
mean = mean * update.sqrt().inverse(); // Update Mean M = M * U^{-1/2}
crit = DistanceRiemann(Eigen::MatrixXd::Identity(n, n), update);
}
mJ = Eigen::MatrixXd::Zero(n, n); // Last Change => J
for (const auto& cov : covs) { mJ += (mean.transpose() * cov * mean).log(); } // Sum of log(C^T*Ci*C)
mJ /= double(k); // Normalization
Eigen::MatrixXd mA = mean.inverse();
mean = mA.transpose() * mJ.exp() * mA;
return true;
}
//---------------------------------------------------------------------------------------------------
//---------------------------------------------------------------------------------------------------
bool MeanHarmonic(const std::vector<Eigen::MatrixXd>& covs, Eigen::MatrixXd& mean)
{
const size_t k = covs.size(), n = covs[0].rows(); // Number of Matrix & Features => K & N
mean = Eigen::MatrixXd::Zero(n, n); // Initial Mean
for (const auto& cov : covs) { mean += cov.inverse(); } // Sum of Inverse
mean = (mean / double(k)).inverse(); // Normalization and inverse
return true;
}
//---------------------------------------------------------------------------------------------------
//---------------------------------------------------------------------------------------------------
bool MeanIdentity(const std::vector<Eigen::MatrixXd>& covs, Eigen::MatrixXd& mean)
{
mean = Eigen::MatrixXd::Identity(covs[0].rows(), covs[0].cols());
return true;
}
//---------------------------------------------------------------------------------------------------
} // namespace Geometry
@@ -0,0 +1,151 @@
#include "geometry/Median.hpp"
#include <iostream>
#include "geometry/Basics.hpp"
#include "geometry/Featurization.hpp"
#include "geometry/Mean.hpp"
namespace Geometry {
//---------------------------------------------------------------------------------------------------
double Median(const Eigen::MatrixXd& m)
{
const std::vector<double> v(m.data(), m.data() + m.rows() * m.cols());
return Median(v);
}
//---------------------------------------------------------------------------------------------------
//---------------------------------------------------------------------------------------------------
bool Median(const std::vector<Eigen::MatrixXd>& matrices, Eigen::MatrixXd& median, const double epsilon, const size_t maxIter, const EMetric& metric)
{
if (matrices.empty()) { return false; } // If no matrix in vector
if (matrices.size() == 1) // If just one matrix in vector
{
median = matrices[0];
return true;
}
if (!HaveSameSize(matrices)) // If different sizes
{
std::cout << "Matrices have different sizes." << std::endl;
return false;
}
if (!IsSquare(matrices[0]) && metric == EMetric::Riemann) // If non square for Riemann metric
{
std::cout << "Non Square Matrix is invalid with " << toString(metric) << " metric." << std::endl;
return false;
}
switch (metric)
{
case EMetric::Riemann: return MedianRiemann(matrices, median, epsilon, maxIter);
case EMetric::Euclidian: return MedianEuclidian(matrices, median, epsilon, maxIter);
case EMetric::Identity: return MedianIdentity(matrices, median);
case EMetric::LogEuclidian:
case EMetric::LogDet:
case EMetric::Kullback:
case EMetric::ALE:
case EMetric::Harmonic:
case EMetric::Wasserstein:
std::cout << toString(metric) << " metric not implemented." << std::endl;
return false;
}
return true;
}
//---------------------------------------------------------------------------------------------------
//---------------------------------------------------------------------------------------------------
bool MedianEuclidian(const std::vector<Eigen::MatrixXd>& matrices, Eigen::MatrixXd& median, const double epsilon, const size_t maxIter)
{
if (matrices.empty() || matrices[0].size() == 0) { return false; }
const size_t n = matrices.size(); // Number of sample
// Initial Median is the median of each channel in all matrix of dataset
median = matrices[0]; // to copy size
for (size_t i = 0; i < size_t(median.size()); ++i)
{
std::vector<double> tmp;
tmp.reserve(n); // Reserve to optimize (a little) the pushback memory access.
for (const auto& cov : matrices) { tmp.push_back(cov.data()[i]); } // Stack value number i of all matrix
median.data()[i] = Median(tmp);
}
size_t iter = 0; // number of iteration
double gain = epsilon; // Gain since last compute
while (iter < maxIter && gain >= epsilon)
{
Eigen::MatrixXd prev = median; // Keep old median
median.setZero(); // Reset median
double sumCoefs = 0; // Sum of Coefficient
for (const auto& cov : matrices)
{
//Eigen::MatrixXd difference = cov - prev;
//double coef = sqrt(difference.cwiseProduct(difference).sum());
if (cov.isApprox(prev)) { continue; } // In this case, Median is exactly this current matrix so we don't consider this matrix
double coef = (cov - prev).norm();
// Personnal hack and security
coef = 1.0 / coef;
sumCoefs += coef; // Sum for normalization
median += coef * cov; // Add to the new median
}
if (sumCoefs > 0.0) { median /= sumCoefs; } // Normalize
gain = (median - prev).norm() / median.norm(); // It's the Frobenius norm
iter++;
}
return true;
}
//---------------------------------------------------------------------------------------------------
//---------------------------------------------------------------------------------------------------
bool MedianRiemann(const std::vector<Eigen::MatrixXd>& matrices, Eigen::MatrixXd& median, const double epsilon, const size_t maxIter)
{
if (matrices.empty() || !IsSquare(matrices[0])) { return false; }
const size_t n = matrices.size(); // Number of sample
const size_t nf = matrices[0].rows() * (matrices[0].rows() + 1) / 2; // Number of Features in tangent space
size_t iter = 0; // number of iteration
if (!MeanEuclidian(matrices, median)) { return false; } // Initialize Median
double gain = epsilon; // Gain since last compute
std::vector<Eigen::MatrixXd> mats;
mats.reserve(n);
for (const auto& m : matrices) { mats.push_back(m); }
while (iter < maxIter)
{
// Compute Tangent space of all matrices & sum of euclidian distance of each transposed matrix
std::vector<Eigen::RowVectorXd> ts(n);
double sum = 0.0;
for (size_t i = 0; i < n; ++i)
{
if (!TangentSpace(mats[i], ts[i], median)) { return false; }
sum += sqrt(ts[i].cwiseAbs2().sum());
}
if (std::abs((sum - gain) / gain) < epsilon) { break; } // std::abs call fabs to keep type
// Arithmetic median in tangent space
std::vector<std::vector<double>> transposeTs(nf, std::vector<double>(n));
Eigen::RowVectorXd featureMedian(nf);
for (size_t i = 0; i < n; ++i) { for (size_t j = 0; j < nf; ++j) { transposeTs[j][i] = ts[i][j]; } }
for (size_t j = 0; j < nf; ++j) { featureMedian[j] = Median(transposeTs[j]); }
// back to the manifold
Eigen::MatrixXd tmp;
if (!UnTangentSpace(featureMedian, tmp, median)) { return false; }
gain = sum; // Update gain
median = tmp; // Update Median
iter++;
}
return true;
}
//---------------------------------------------------------------------------------------------------
//---------------------------------------------------------------------------------------------------
bool MedianIdentity(const std::vector<Eigen::MatrixXd>& matrices, Eigen::MatrixXd& median)
{
median = Eigen::MatrixXd::Identity(matrices[0].rows(), matrices[0].cols());
return true;
}
//---------------------------------------------------------------------------------------------------
} // namespace Geometry
@@ -0,0 +1,243 @@
#include "geometry/Misc.hpp"
#include "geometry/Featurization.hpp"
#include <boost/math/special_functions/gamma.hpp>
#include <numeric> // std::iota
namespace Geometry {
///-------------------------------------------------------------------------------------------------
/// <summary> Get the sign of the specified value. </summary>
/// <param name="x"> The value. </param>
/// <returns> <c>1</c> if <c>x > 0</c>, <c>0</c> if <c>x == 0</c>, <c>-1</c> if <c>x < 0</c>. </returns>
template <typename T>
int sgn(T x) { return (T(0) < x) - (x < T(0)); }
///-------------------------------------------------------------------------------------------------
//---------------------------------------------------------------------------------------------------
/// <summary> Structure used for iota function for range of double. </summary>
struct SDoubleIota
{
explicit SDoubleIota(const double init = 0.0, const double inc = 1.0) : v(init), inc(inc) {}
operator double() const { return v; } // don't add explicit qualifier for iota functions (were template cast is used)
SDoubleIota& operator++()
{
v += inc;
return *this;
}
double v;
double inc;
};
//---------------------------------------------------------------------------------------------------
//---------------------------------------------------------------------------------------------------
/// <summary> Structure used for iota function for range of round index. </summary>
struct SRoundIndex
{
explicit SRoundIndex(const double init = 0.0, const double inc = 1.0) : v(init), inc(inc) {}
operator size_t() const { return size_t(std::round(v)); } // don't add explicit qualifier for iota functions (were template cast is used)
SRoundIndex& operator++()
{
v += inc;
return *this;
}
double v;
double inc;
};
//---------------------------------------------------------------------------------------------------
//---------------------------------------------------------------------------------------------------
std::vector<double> doubleRange(const double begin, const double end, const double step, const bool closed)
{
std::vector<double> res;
if (end < begin) { return res; }
const double size = (end - begin) / step;
// check if the end is inclued in range for size, we ceil for no modulo values and add the last value to the range
res.resize(size_t(std::ceil((closed && std::trunc(size) == size) ? size + 1 : size)));
std::iota(res.begin(), res.end(), SDoubleIota(begin, step));
return res;
}
//---------------------------------------------------------------------------------------------------
std::vector<size_t> RoundIndexRange(const double begin, const double end, const double step, const bool closed, const bool unique)
{
std::vector<size_t> res;
if (end < begin) { return res; }
const double size = (end - begin) / step;
// check if the end is inclued in range for size, we ceil for no modulo values and add the last value to the range
res.resize(size_t(std::ceil((closed && std::trunc(size) == size) ? size + 1 : size)));
std::iota(res.begin(), res.end(), SRoundIndex(begin, step));
if (unique)
{
const auto last = std::unique(res.begin(), res.end()); // Remove duplicate values (but after last, we have undefined value)
res.erase(last, res.end()); // Resize Vector (we erase after last)
}
return res;
}
//---------------------------------------------------------------------------------------------------
//---------------------------------------------------------------------------------------------------
std::vector<size_t> BinHist(const std::vector<double>& dataset, const size_t n)
{
std::vector<size_t> res(n, 0);
const double max = *std::max_element(dataset.begin(), dataset.end());
if (max == 0) { return res; } // if max is 0, coef can't be compute
const double coef = n / max;
for (const auto& data : dataset)
{
const size_t bin = size_t(std::floor(data * coef));
if (bin < n) { res[bin]++; }
else if (bin == n) { res[n - 1]++; } // if this data is equal to max
}
return res;
}
//---------------------------------------------------------------------------------------------------
//---------------------------------------------------------------------------------------------------
bool FitDistribution(const std::vector<double>& values, double& mu, double& sigma, const std::vector<double>& betas, const double minQuant,
const double maxQuant, const double minClean, const double maxDropout, const double stepBound, const double stepScale)
{
if (values.empty() || betas.empty() || minQuant < 0 || minQuant > 1 || maxQuant < 0 || maxQuant > 1 || minClean < 0 || maxDropout < 0
|| stepBound < 0.0001 || stepBound > 0.1 || stepScale < 0.0001 || stepScale > 0.1) { return false; }
//========== Scales ==========
const size_t nBeta = betas.size();
// Scales is a vector for each beta as :
// scale = beta/(2*gamma(1/beta)) with gamma the function as gamma(n) = (n-1)! for all integer greater than 0
std::vector<double> scales;
scales.reserve(nBeta);
std::transform(betas.begin(), betas.end(), std::back_inserter(scales), [](const double beta) -> double { return beta / (2 * tgamma(1 / beta)); });
//========== zBounds ==========
// zBounds is a vector of lower and upper bounds for each beta as : sign(quants-1/2) * gammaincinv(sign(quants-1/2) * (2*quants-1), 1/beta)^(1/beta);
// with gammaincinv the Inverse incomplete gamma function, here quants are the quantiles limit (by default [0.022 0.6])
std::vector<std::vector<double>> zBounds(nBeta);
const int signMin = sgn(minQuant - 0.5), signMax = sgn(maxQuant - 0.5);
const double coefMin = signMin * (2 * minQuant - 1), coefMax = signMax * (2 * maxQuant - 1);
for (size_t i = 0; i < nBeta; ++i)
{
if (betas[i] == 0) { zBounds[i] = { 0, 0 }; }
else
{
const double beta = 1 / betas[i];
zBounds[i] = {
signMin * pow(boost::math::gamma_p_inv(beta, coefMin), beta),
signMax * pow(boost::math::gamma_p_inv(beta, coefMax), beta)
};
}
}
//========== Sort Values ==========
// We sort values to access quantiles directly
const size_t n = values.size();
std::vector<double> newValues = values;
std::sort(newValues.begin(), newValues.end());
//========== Compute Index range ==========
// Width are the limit if all data is clean or artifacted. It's usefull for the for loop limit and step for each width possible
// Bounds are the range of begining value used to compute mu and sigma. It's usefull for the for loop limit and step for first index of value to take
// We create Vector for widths and bounds to precompute all round and avoid duplicate indexes in widths or bounds
std::vector<size_t> widths = RoundIndexRange(n * (maxQuant - minQuant) * minClean, n * (maxQuant - minQuant), n * stepScale, true, false);
std::reverse(widths.begin(), widths.end());
const std::vector<size_t> bounds = RoundIndexRange(n * minQuant, n * (minQuant + maxDropout), n * stepBound, true, false);
const size_t maxWidth = std::max(widths.front(), widths.back()); // to prevent if widths is in descending or ascending order
const size_t nBound = bounds.size();
//========== Compute Grid (with index range) ==========
// Create the Biggest table of data with width in column and bound in row
std::vector<std::vector<double>> grid(nBound);
std::vector<double> firsts(nBound);
for (size_t i = 0; i < nBound; ++i)
{
grid[i].reserve(maxWidth);
const auto first = newValues.begin() + bounds[i];
std::copy_n(first, maxWidth, std::back_inserter(grid[i]));
firsts[i] = grid[i][0];
for (auto& e : grid[i]) { e -= firsts[i]; } // Substract first value on all element
}
//========== Width Loop ==========
double bestKl = std::numeric_limits<double>::max();
size_t bestBeta = 0, bestId = 0, bestWidth = 0;
// for each interval width...
for (const auto& w : widths)
{
const size_t nbins = size_t(std::round(3 * log2(1 + (double(w) / 2))));
//========== Compute Histogramm ==========
std::vector<std::vector<double>> hist(nBound);
for (size_t i = 0; i < nBound; ++i)
{
hist[i].reserve(nbins);
std::vector<size_t> tmp = BinHist(std::vector<double>(grid[i].begin(), grid[i].begin() + w), nbins);
std::transform(tmp.begin(), tmp.end(), std::back_inserter(hist[i]), [](const size_t e) -> double { return log(e + 0.01); });
}
//========== Beta Loop ==========
for (size_t b = 0; b < nBeta; ++b)
{
//========== Compute Probability ==========
std::vector<double> prob(nbins);
double sumprob = 0.0;
for (size_t i = 0; i < nbins; ++i)
{
prob[i] = std::exp(-std::pow(std::abs(zBounds[b][0] + (((i + 0.5) / nbins) * (zBounds[b][1] - zBounds[b][0]))), betas[b])) * scales[b];
sumprob += prob[i];
}
if (sumprob != 0) { for (auto& p : prob) { p /= sumprob; } }
//========== Compute the Kullback-Leibler divergences ==========
//kl = sum(prob * (log(prob) - hist)) + log(w));
std::vector<double> kl(nBound, log(w));
for (size_t i = 0; i < nBound; ++i) { for (size_t j = 0; j < nbins; ++j) { kl[i] += prob[j] * (log(prob[j]) - hist[i][j]); } }
// Update Parameters
auto minIt = std::min_element(kl.begin(), kl.end());
if (*minIt < bestKl)
{
bestKl = *minIt;
bestBeta = b;
bestId = minIt - kl.begin();
bestWidth = w - 1;
}
}
}
double alpha = grid[bestId][bestWidth] / (zBounds[bestBeta][1] - zBounds[bestBeta][0]);
double beta = betas[bestBeta];
mu = firsts[bestId] - zBounds[bestBeta][0] * alpha;
sigma = sqrt(alpha * alpha * std::tgamma(3 / beta) / std::tgamma(1 / beta));
return true;
}
//---------------------------------------------------------------------------------------------------
void sortedEigenVector(const Eigen::MatrixXd& matrix, Eigen::MatrixXd& vectors, std::vector<double>& values, const EMetric /*metric*/)
{
// Compute Eigen Vector/Values
const Eigen::EigenSolver<Eigen::MatrixXd> es(matrix);
const Eigen::MatrixXd tmpVec = es.eigenvectors().real(); // It's complex by default but all imaginary part are 0
const Eigen::MatrixXd tmpVal = es.eigenvalues().real(); // It's complex by default but all imaginary part are 0
values = std::vector<double>(tmpVal.data(), tmpVal.data() + tmpVal.size());
// Get order of eigen values.
std::vector<size_t> idx(values.size());
std::iota(idx.begin(), idx.end(), 0);
std::stable_sort(idx.begin(), idx.end(), [&values](const size_t i1, const size_t i2) { return values[i1] < values[i2]; });
// Sort Eigen Values
std::stable_sort(values.begin(), values.end());
// Sort Eigen Vector
vectors = tmpVec; // copy matrix to set size easily
for (size_t i = 0; i < size_t(tmpVec.cols()); ++i) { vectors.col(i) = tmpVec.col(idx[i]); }
}
//---------------------------------------------------------------------------------------------------
} // namespace Geometry
@@ -0,0 +1,278 @@
#include "geometry/artifacts/CASR.hpp"
#include "geometry/Misc.hpp"
#include "geometry/Median.hpp"
#include "geometry/Covariance.hpp"
#include "geometry/Mean.hpp"
#include "geometry/classifier/IMatrixClassifier.hpp"
#include <boost/math/special_functions/detail/igamma_inverse.hpp>
#include <unsupported/Eigen/MatrixFunctions>
#include <cmath>
#include <numeric>
#include <iostream>
namespace Geometry {
///-------------------------------------------------------------------------------------------------
bool CASR::train(const std::vector<Eigen::MatrixXd>& dataset, const double rejectionLimit)
{
if (dataset.empty() || dataset[0].size() == 0) { return false; }
const size_t n = dataset.size(); // Number of samples
m_nChannel = dataset[0].rows(); // Number of channels
//========== Compute the covariance matrix ==========
std::vector<Eigen::MatrixXd> covs(n);
//for (size_t i = 0; i < n; ++i) { if (!CovarianceMatrixLWF(dataset[i], covs[i])) { return false; } } // We assume data is centered
for (size_t i = 0; i < n; ++i) { if (!CovarianceMatrix(dataset[i], covs[i], EEstimator::LWF, EStandardization::Center)) { return false; } }
//========== Compute Square Root of Median ==========
if (!Median(covs, m_median)) { return false; } // Geometric median independant of metric
m_median = m_median.sqrt();
//========== Compute Eigen vectors ==========
Eigen::MatrixXd eigVector;
std::vector<double> eigValues;
sortedEigenVector(m_median, eigVector, eigValues, m_metric); //Actually only Euclidian metric is implemented
//========== Compute the ponderate dataset ==========
std::vector<Eigen::MatrixXd> newDataset;
newDataset.reserve(n);
for (const auto& m : dataset) { newDataset.push_back((m.transpose() * eigVector)); } // Multiply by eigen vector (we transpose to have channels in column
for (auto& m : newDataset) { m = m.cwiseProduct(m); } // Square new signal
//========== Compute the "fit" distribution ==========
// Compute the RMS of each channel for each sample
std::vector<std::vector<double>> rms(m_nChannel, std::vector<double>(n));
for (size_t i = 0; i < n; ++i) { for (size_t j = 0; j < m_nChannel; ++j) { rms[j][i] = sqrt(newDataset[i].col(j).mean()); } }
// Compute the "fit" distribution
std::vector<double> mu(m_nChannel, 0.0), sigma(m_nChannel, 0.0);
for (size_t i = 0; i < m_nChannel; ++i) { FitDistribution(rms[i], mu[i], sigma[i]); }
// Compute the threshold Matrix
m_threshold = Eigen::MatrixXd::Zero(m_nChannel, m_nChannel);
for (size_t i = 0; i < m_nChannel; ++i) { m_threshold(i, i) = mu[i] + rejectionLimit * sigma[i]; }
m_threshold *= eigVector.transpose();
// Initialize Reconstruction matrix and trivial
m_r = Eigen::MatrixXd::Identity(m_nChannel, m_nChannel);
m_trivial = true;
return true;
}
bool CASR::process(const Eigen::MatrixXd& in, Eigen::MatrixXd& out)
{
// Check if input data is compatible with training data and if we don't limit so much the reconstruction
out = in;
if (size_t(out.rows()) != m_nChannel) { return false; }
const size_t begin = size_t((1.0 - m_maxChannel) * double(m_nChannel)); // We define the number of channels to non reconstruct
if (begin == m_nChannel) { return true; }
if (m_r.size() == 0) { m_r = Eigen::MatrixXd::Identity(m_nChannel, m_nChannel); }
// Compute Covariance matrix
Eigen::MatrixXd cov;
if (!CovarianceMatrix(in, cov, EEstimator::LWF, EStandardization::Center)) { return false; }
if (m_cov.size() == 0) { m_cov = cov; } // if first time
else { if (!Mean({ m_cov, cov }, m_cov, m_metric)) { return false; } } // else mean of the both
// Compute Eigen vector & values
Eigen::MatrixXd eigVector;
std::vector<double> eigValues;
sortedEigenVector(m_cov, eigVector, eigValues, m_metric);
// Check if eigen values is over threshold computed during train (ponderated by eigen vector)
Eigen::MatrixXd threshold = (m_threshold * eigVector).cwiseAbs2();
bool trivial = true;
std::vector<bool> keep(m_nChannel, true);
for (size_t i = begin; i < m_nChannel; ++i)
{
if (eigValues[i] >= threshold.col(i).sum())
{
keep[i] = false;
trivial = false;
}
}
// Check if All channels are clean
if (trivial) { m_r = Eigen::MatrixXd::Identity(m_nChannel, m_nChannel); }
else // if not...
{
// Compute the reconstruction matrix with bad channels
Eigen::MatrixXd tmp = eigVector.transpose() * m_median;
for (size_t i = begin; i < m_nChannel; ++i) { if (!keep[i]) { tmp.row(i).setZero(); } }
const Eigen::MatrixXd newR = m_median * tmp.completeOrthogonalDecomposition().pseudoInverse() * eigVector.transpose();
if (!m_trivial)
{
// Compute blend values for the samples
const size_t nSample = in.cols();
std::vector<double> blend(nSample);
std::iota(blend.begin(), blend.end(), 1); // Range 1 to nSample (inclued)
for (auto& b : blend) { b = (1 - cos(M_PI * (b / double(nSample)))) / 2.0; }
// Apply reconstruction ponderate by the blend (we considere the old reconstruction matrix for the second part)
Eigen::MatrixXd t1 = newR * in;
Eigen::MatrixXd t2 = m_r * in;
for (size_t i = 0; i < nSample; ++i) { out.col(i) = (blend[i] * t1.col(i)) + ((1 - blend[i]) * t2.col(i)); }
}
m_r = newR; // Update the reconstruction matrix
}
m_trivial = trivial;
return true;
}
///-------------------------------------------------------------------------------------------------
bool CASR::setMatrices(const Eigen::MatrixXd& median, const Eigen::MatrixXd& threshold, const Eigen::MatrixXd& reconstruct,
const Eigen::MatrixXd& covariance)
{
if (!IsSquare(median) || !HaveSameSize(median, threshold)
|| (reconstruct.size() != 0 && !HaveSameSize(median, reconstruct))
|| (covariance.size() != 0 && !HaveSameSize(median, covariance)))
{
std::cout << "All matrices must be square with same size (or empty for reconstruct and covariance matrix" << std::endl;
return false;
}
m_nChannel = median.rows();
m_median = median;
m_threshold = threshold;
m_r = reconstruct.size() != 0 ? reconstruct : Eigen::MatrixXd::Identity(m_nChannel, m_nChannel);
m_cov = covariance;
m_trivial = true;
return true;
}
///-------------------------------------------------------------------------------------------------
//***********************
//***** XML Manager *****
//***********************
///-------------------------------------------------------------------------------------------------
bool CASR::saveXML(const std::string& filename) const
{
tinyxml2::XMLDocument doc;
// Create Root
tinyxml2::XMLNode* root = doc.NewElement("ASR"); // Create root node
doc.InsertFirstChild(root); // Add root to XML
tinyxml2::XMLElement* data = doc.NewElement("ASR-data"); // Create data node
data->SetAttribute("metric", toString(m_metric).c_str()); // Set attribute metric
data->SetAttribute("nChannel", int(m_nChannel)); // Set attribute nCHannel
data->SetAttribute("maxChannel", int(m_maxChannel)); // Set attribute nCHannel
data->SetAttribute("trivial", m_trivial); // Set attribute nCHannel
tinyxml2::XMLElement* median = doc.NewElement("Median"); // Create Median node
if (!IMatrixClassifier::saveMatrix(median, m_median)) { return false; } // Save Median Matrix
data->InsertEndChild(median); // Add Median node to data node
tinyxml2::XMLElement* threshold = doc.NewElement("Threshold"); // Create Median node
if (!IMatrixClassifier::saveMatrix(threshold, m_threshold)) { return false; } // Save Median Matrix
data->InsertEndChild(threshold); // Add Median node to data node
tinyxml2::XMLElement* r = doc.NewElement("R"); // Create Median node
if (!IMatrixClassifier::saveMatrix(r, m_r)) { return false; } // Save Median Matrix
data->InsertEndChild(r); // Add Median node to data node
tinyxml2::XMLElement* cov = doc.NewElement("Cov"); // Create Median node
if (!IMatrixClassifier::saveMatrix(cov, m_cov)) { return false; } // Save Median Matrix
data->InsertEndChild(cov); // Add Median node to data node
root->InsertEndChild(data); // Add data to root
return doc.SaveFile(filename.c_str()) == 0; // save XML (if != 0 it means error)
}
///-------------------------------------------------------------------------------------------------
///-------------------------------------------------------------------------------------------------
bool CASR::loadXML(const std::string& filename)
{
// Load File
tinyxml2::XMLDocument xmlDoc;
if (xmlDoc.LoadFile(filename.c_str()) != 0) { return false; } // Check File Exist and Loading
// Load Root
tinyxml2::XMLNode* root = xmlDoc.FirstChild(); // Get Root Node
if (root == nullptr) { return false; } // Check Root Node Exist
// Load Data
tinyxml2::XMLElement* data = root->FirstChildElement("ASR-data"); // Get Data Node
if (data == nullptr) { return false; } // Check Root Node Exist
m_metric = StringToMetric(std::string(data->Attribute("metric")));
m_nChannel = data->IntAttribute("nChannel");
m_maxChannel = data->IntAttribute("maxChannel");
m_trivial = data->BoolAttribute("trivial");
tinyxml2::XMLElement* element = data->FirstChildElement("Median"); // Get Median Node
if (element == nullptr) { return false; } // Check if Node Exist
if (!IMatrixClassifier::loadMatrix(element, m_median)) { return false; } // Load Median Matrix
element = data->FirstChildElement("Threshold"); // Get Threshold Node
if (element == nullptr) { return false; } // Check if Node Exist
if (!IMatrixClassifier::loadMatrix(element, m_threshold)) { return false; } // Load Threshold Matrix
element = data->FirstChildElement("R"); // Get R Node
if (element == nullptr) { return false; } // Check if Node Exist
if (!IMatrixClassifier::loadMatrix(element, m_r)) { return false; } // Load R Matrix
element = data->FirstChildElement("Cov"); // Get Cov Node
if (element == nullptr) { return false; } // Check if Node Exist
if (!IMatrixClassifier::loadMatrix(element, m_cov)) { return false; } // Load Cov Matrix
return true;
}
///-------------------------------------------------------------------------------------------------
//*****************************
//***** Override Operator *****
//*****************************
///-------------------------------------------------------------------------------------------------
bool CASR::isEqual(const CASR& obj, const double precision) const
{
return m_metric == obj.m_metric && m_nChannel == obj.m_nChannel
&& abs(m_maxChannel - obj.m_maxChannel) < precision && m_trivial == obj.m_trivial
&& AreEquals(m_median, obj.m_median, precision) && AreEquals(m_threshold, obj.m_threshold, precision)
&& AreEquals(m_r, obj.m_r, precision) && AreEquals(m_cov, obj.m_cov, precision);
}
///-------------------------------------------------------------------------------------------------
///-------------------------------------------------------------------------------------------------
void CASR::copy(const CASR& obj)
{
m_metric = obj.m_metric;
m_nChannel = obj.m_nChannel;
m_maxChannel = obj.m_maxChannel;
m_trivial = obj.m_trivial;
m_median = obj.m_median;
m_threshold = obj.m_threshold;
m_r = obj.m_r;
m_cov = obj.m_cov;
}
///-------------------------------------------------------------------------------------------------
///-------------------------------------------------------------------------------------------------
std::stringstream CASR::print() const
{
std::stringstream ss;
ss << "Metric : " << toString(m_metric) << std::endl;
if (m_nChannel == 0) { ss << "Training not done" << std::endl; }
else
{
ss << "Training done." << std::endl;
ss << size_t(m_maxChannel * double(m_nChannel)) << "/" << m_nChannel << " channels can be reconstruted." << std::endl;
ss << "Median matrix is : " << std::endl << m_median << std::endl;
ss << "Threshold matrix is : " << std::endl << m_threshold << std::endl;
if (m_cov.size() == 0) { ss << "No process launched yet." << std::endl; }
else
{
ss << "Last sample " << (m_trivial ? "was" : "wasn't") << " trivial." << std::endl;
ss << "Last Reconstruction Matrix : " << std::endl << m_r << std::endl;
ss << "Last Covariance Matrix : " << std::endl << m_cov << std::endl;
}
}
return ss;
}
///-------------------------------------------------------------------------------------------------
} // namespace Geometry
@@ -0,0 +1,146 @@
#include "geometry/classifier/CBias.hpp"
#include "geometry/classifier/IMatrixClassifier.hpp"
#include "geometry/Mean.hpp"
#include "geometry/Basics.hpp"
#include "geometry/Geodesic.hpp"
#include <unsupported/Eigen/MatrixFunctions> // SQRT of Matrix
#include <iostream>
namespace Geometry {
///-------------------------------------------------------------------------------------------------
bool CBias::computeBias(const std::vector<std::vector<Eigen::MatrixXd>>& dataset, const EMetric metric) { return computeBias(Vector2DTo1D(dataset), metric); }
///-------------------------------------------------------------------------------------------------
///-------------------------------------------------------------------------------------------------
bool CBias::computeBias(const std::vector<Eigen::MatrixXd>& dataset, const EMetric metric)
{
if (!Mean(dataset, m_bias, metric)) { return false; } // Compute Bias reference
m_biasIS = m_bias.sqrt().inverse(); // Inverse Square root of Bias matrix => isR
m_n = 0;
return true;
}
///-------------------------------------------------------------------------------------------------
///-------------------------------------------------------------------------------------------------
void CBias::applyBias(const std::vector<std::vector<Eigen::MatrixXd>>& in, std::vector<std::vector<Eigen::MatrixXd>>& out)
{
const size_t n = in.size();
out.resize(n);
for (size_t i = 0; i < n; ++i) { applyBias(in[i], out[i]); }
}
///-------------------------------------------------------------------------------------------------
///-------------------------------------------------------------------------------------------------
void CBias::applyBias(const std::vector<Eigen::MatrixXd>& in, std::vector<Eigen::MatrixXd>& out)
{
const size_t n = in.size();
out.resize(n);
for (size_t i = 0; i < n; ++i) { applyBias(in[i], out[i]); }
}
///-------------------------------------------------------------------------------------------------
///-------------------------------------------------------------------------------------------------
void CBias::applyBias(const Eigen::MatrixXd& in, Eigen::MatrixXd& out) { out = m_biasIS * in * m_biasIS.transpose(); }
///-------------------------------------------------------------------------------------------------
///-------------------------------------------------------------------------------------------------
void CBias::updateBias(const Eigen::MatrixXd& sample, const EMetric metric)
{
m_n++; // Update number of classify
if (m_n == 1) { m_bias = sample; } // At the first pass we reinitialize the Bias
else { Geodesic(m_bias, sample, m_bias, metric, 1.0 / m_n); }
m_biasIS = m_bias.sqrt().inverse(); // Inverse Square root of Bias matrix => isR
}
///-------------------------------------------------------------------------------------------------
///-------------------------------------------------------------------------------------------------
void CBias::setBias(const Eigen::MatrixXd& bias)
{
m_bias = bias;
m_biasIS = m_bias.sqrt().inverse();
}
///-------------------------------------------------------------------------------------------------
///-------------------------------------------------------------------------------------------------
bool CBias::saveXML(const std::string& filename) const
{
tinyxml2::XMLDocument xmlDoc;
// Create Root
tinyxml2::XMLNode* root = xmlDoc.NewElement("Bias"); // Create root node
xmlDoc.InsertFirstChild(root); // Add root to XML
tinyxml2::XMLElement* data = xmlDoc.NewElement("Bias-data"); // Create data node
if (!saveAdditional(xmlDoc, data)) { return false; } // Save Optionnal Informations
root->InsertEndChild(data); // Add data to root
return xmlDoc.SaveFile(filename.c_str()) == 0; // save XML (if != 0 it means error)
}
///-------------------------------------------------------------------------------------------------
///-------------------------------------------------------------------------------------------------
bool CBias::loadXML(const std::string& filename)
{
// Load File
tinyxml2::XMLDocument xmlDoc;
if (xmlDoc.LoadFile(filename.c_str()) != 0) { return false; } // Check File Exist and Loading
// Load Root
tinyxml2::XMLNode* root = xmlDoc.FirstChild(); // Get Root Node
if (root == nullptr) { return false; } // Check Root Node Exist
// Load Data
tinyxml2::XMLElement* data = root->FirstChildElement("Bias-data"); // Get Data Node
if (!loadAdditional(data)) { return false; } // Load Optionnal Informations
return true;
}
///-------------------------------------------------------------------------------------------------
///-------------------------------------------------------------------------------------------------
bool CBias::saveAdditional(tinyxml2::XMLDocument& doc, tinyxml2::XMLElement* data) const
{
tinyxml2::XMLElement* bias = doc.NewElement("Bias"); // Create Bias node
bias->SetAttribute("n", int(m_n)); // Set attribute class number of trials
if (!IMatrixClassifier::saveMatrix(bias, m_bias)) { return false; } // Save class
data->InsertEndChild(bias); // Add class node to data node
return true;
}
///-------------------------------------------------------------------------------------------------
///-------------------------------------------------------------------------------------------------
bool CBias::loadAdditional(tinyxml2::XMLElement* data)
{
tinyxml2::XMLElement* bias = data->FirstChildElement("Bias"); // Get LDA Weight Node
m_n = bias->IntAttribute("n"); // Get the number of Trials for this class
if (!IMatrixClassifier::loadMatrix(bias, m_bias)) { return false; } // Load Reference Matrix
m_biasIS = m_bias.sqrt().inverse();
return true;
}
///-------------------------------------------------------------------------------------------------
///-------------------------------------------------------------------------------------------------
bool CBias::isEqual(const CBias& obj, const double precision) const { return AreEquals(m_bias, obj.m_bias, precision) && m_n == obj.m_n; }
///-------------------------------------------------------------------------------------------------
///-------------------------------------------------------------------------------------------------
void CBias::copy(const CBias& obj)
{
m_bias = obj.m_bias;
m_biasIS = obj.m_biasIS;
m_n = obj.m_n;
}
///-------------------------------------------------------------------------------------------------
///-------------------------------------------------------------------------------------------------
std::stringstream CBias::print() const
{
std::stringstream ss;
ss << "Number of Classification : " << m_n << std::endl;
ss << "Bias Matrix : ";
if (m_bias.size() != 0) { ss << std::endl << m_bias.format(MATRIX_FORMAT) << std::endl; }
else { ss << "Not Computed" << std::endl; }
return ss;
}
///-------------------------------------------------------------------------------------------------
} // namespace Geometry
@@ -0,0 +1,41 @@
#include "geometry/classifier/CMatrixClassifierFgMDM.hpp"
#include "geometry/Mean.hpp"
namespace Geometry {
///-------------------------------------------------------------------------------------------------
CMatrixClassifierFgMDM::~CMatrixClassifierFgMDM()
{
for (auto& v : m_dataset) { v.clear(); }
m_dataset.clear();
}
///-------------------------------------------------------------------------------------------------
///-------------------------------------------------------------------------------------------------
bool CMatrixClassifierFgMDM::train(const std::vector<std::vector<Eigen::MatrixXd>>& dataset)
{
m_dataset = dataset;
return train();
}
///-------------------------------------------------------------------------------------------------
///-------------------------------------------------------------------------------------------------
bool CMatrixClassifierFgMDM::classify(const Eigen::MatrixXd& sample, size_t& classId, std::vector<double>& distance,
std::vector<double>& probability, const EAdaptations adaptation, const size_t& realClassId)
{
if (!CMatrixClassifierFgMDMRT::classify(sample, classId, distance, probability, EAdaptations::None)) { return false; }
// Adaptation
if (adaptation == EAdaptations::None) { return true; }
// Get class id for adaptation and increase number of trials, expected if supervised, predicted if unsupervised
const size_t id = adaptation == EAdaptations::Supervised ? realClassId : classId;
if (id >= m_nbClass) { return false; } // Check id (if supervised and bad input)
m_nbTrials[id]++; // Update number of trials for the class id
m_dataset[id].push_back(sample); // Update the dataset
// Retrain
return train();
}
///-------------------------------------------------------------------------------------------------
} // namespace Geometry
@@ -0,0 +1,121 @@
#include "geometry/classifier/CMatrixClassifierFgMDMRT.hpp"
#include "geometry/Mean.hpp"
#include "geometry/Basics.hpp"
#include "geometry/Featurization.hpp"
#include "geometry/Classification.hpp"
#include <iostream>
namespace Geometry {
///-------------------------------------------------------------------------------------------------
bool CMatrixClassifierFgMDMRT::train(const std::vector<std::vector<Eigen::MatrixXd>>& dataset)
{
if (dataset.empty()) { return false; }
if (!Mean(Vector2DTo1D(dataset), m_ref, EMetric::Riemann)) { return false; } // Compute Reference matrix
// Transform to the Tangent Space
const size_t nbClass = dataset.size();
std::vector<std::vector<Eigen::RowVectorXd>> tsSample(nbClass);
for (size_t k = 0; k < nbClass; ++k)
{
const size_t nbTrials = dataset[k].size();
tsSample[k].resize(nbTrials);
for (size_t i = 0; i < nbTrials; ++i) { if (!TangentSpace(dataset[k][i], tsSample[k][i], m_ref)) { return false; } }
}
// Compute FgDA Weight
if (!FgDACompute(tsSample, m_weight)) { return false; }
// Convert dataset
std::vector<std::vector<Eigen::MatrixXd>> newDataset(nbClass);
std::vector<std::vector<Eigen::RowVectorXd>> filtered(nbClass);
for (size_t k = 0; k < nbClass; ++k)
{
const size_t nbTrials = dataset[k].size();
newDataset[k].resize(nbTrials);
filtered[k].resize(nbTrials);
for (size_t i = 0; i < nbTrials; ++i)
{
if (!FgDAApply(tsSample[k][i], filtered[k][i], m_weight)) { return false; } // Apply Filter
if (!UnTangentSpace(filtered[k][i], newDataset[k][i], m_ref)) { return false; } // Return to Matrix Space
}
}
return CMatrixClassifierMDM::train(newDataset); // Train MDM
}
///-------------------------------------------------------------------------------------------------
///-------------------------------------------------------------------------------------------------
bool CMatrixClassifierFgMDMRT::classify(const Eigen::MatrixXd& sample, size_t& classId, std::vector<double>& distance,
std::vector<double>& probability, const EAdaptations adaptation, const size_t& realClassId)
{
Eigen::RowVectorXd tsSample, filtered;
Eigen::MatrixXd newSample;
if (!TangentSpace(sample, tsSample, m_ref)) { return false; } // Transform to the Tangent Space
if (!FgDAApply(tsSample, filtered, m_weight)) { return false; } // Apply Filter
if (!UnTangentSpace(filtered, newSample, m_ref)) { return false; } // Return to Matrix Space
return CMatrixClassifierMDM::classify(newSample, classId, distance, probability, adaptation, realClassId);
}
///-------------------------------------------------------------------------------------------------
///-------------------------------------------------------------------------------------------------
bool CMatrixClassifierFgMDMRT::isEqual(const CMatrixClassifierFgMDMRT& obj, const double precision) const
{
if (!CMatrixClassifierMDM::isEqual(obj, precision)) { return false; } // Compare base members
if (!AreEquals(m_ref, obj.m_ref, precision)) { return false; } // Compare Reference
if (!AreEquals(m_weight, obj.m_weight, precision)) { return false; } // Compare Weight
return true;
}
///-------------------------------------------------------------------------------------------------
///-------------------------------------------------------------------------------------------------
void CMatrixClassifierFgMDMRT::copy(const CMatrixClassifierFgMDMRT& obj)
{
CMatrixClassifierMDM::copy(obj);
m_ref = obj.m_ref;
m_weight = obj.m_weight;
}
///-------------------------------------------------------------------------------------------------
///-------------------------------------------------------------------------------------------------
bool CMatrixClassifierFgMDMRT::saveAdditional(tinyxml2::XMLDocument& doc, tinyxml2::XMLElement* data) const
{
// Save Reference
tinyxml2::XMLElement* reference = doc.NewElement("Reference"); // Create Reference node
if (!saveMatrix(reference, m_ref)) { return false; } // Save class
data->InsertEndChild(reference); // Add class node to data node
// Save Weight
tinyxml2::XMLElement* weight = doc.NewElement("Weight"); // Create LDA Weight node
if (!saveMatrix(weight, m_weight)) { return false; } // Save class
data->InsertEndChild(weight); // Add class node to data node
return true;
}
///-------------------------------------------------------------------------------------------------
///-------------------------------------------------------------------------------------------------
bool CMatrixClassifierFgMDMRT::loadAdditional(tinyxml2::XMLElement* data)
{
// Load Reference
tinyxml2::XMLElement* ref = data->FirstChildElement("Reference"); // Get Reference Node
if (!loadMatrix(ref, m_ref)) { return false; } // Load Reference Matrix
// Load Weight
tinyxml2::XMLElement* weight = data->FirstChildElement("Weight"); // Get LDA Weight Node
return loadMatrix(weight, m_weight); // Load LDA Weight Matrix
}
///-------------------------------------------------------------------------------------------------
///-------------------------------------------------------------------------------------------------
std::stringstream CMatrixClassifierFgMDMRT::printAdditional() const
{
std::stringstream ss;
ss << "Reference matrix : " << std::endl << m_ref.format(MATRIX_FORMAT) << std::endl; // Reference
ss << "Weight matrix : " << std::endl << m_weight.format(MATRIX_FORMAT) << std::endl; // Print Weight
return ss;
}
///-------------------------------------------------------------------------------------------------
} // namespace Geometry
@@ -0,0 +1,85 @@
#include "geometry/classifier/CMatrixClassifierFgMDMRTRebias.hpp"
#include "geometry/Mean.hpp"
#include "geometry/Covariance.hpp"
#include <unsupported/Eigen/MatrixFunctions> // SQRT of Matrix
namespace Geometry {
//**********************
//***** Classifier *****
//**********************
///-------------------------------------------------------------------------------------------------
bool CMatrixClassifierFgMDMRTRebias::train(const std::vector<std::vector<Eigen::MatrixXd>>& dataset)
{
if (!m_bias.computeBias(dataset, m_metric)) { return false; }
std::vector<std::vector<Eigen::MatrixXd>> newDataset;
m_bias.applyBias(dataset, newDataset);
if (!CMatrixClassifierFgMDMRT::train(newDataset)) { return false; } // Train FgMDM
const Eigen::MatrixXd identity = Eigen::MatrixXd::Identity(m_ref.rows(), m_ref.cols()); // Identity matrix
if (AreEquals(m_ref, identity)) { m_ref = identity; } // Normally it's always the case with Identity matrix we simplify future operation
return true;
}
///-------------------------------------------------------------------------------------------------
///-------------------------------------------------------------------------------------------------
bool CMatrixClassifierFgMDMRTRebias::classify(const Eigen::MatrixXd& sample, size_t& classId, std::vector<double>& distance,
std::vector<double>& probability, const EAdaptations adaptation, const size_t& realClassId)
{
if (!IsSquare(sample)) { return false; } // Verification if it's a square matrix
Eigen::MatrixXd newSample;
m_bias.applyBias(sample, newSample);
m_bias.updateBias(sample, m_metric);
return CMatrixClassifierFgMDMRT::classify(newSample, classId, distance, probability, adaptation, realClassId);
}
///-------------------------------------------------------------------------------------------------
//***********************
//***** XML Manager *****
//***********************
///-------------------------------------------------------------------------------------------------
bool CMatrixClassifierFgMDMRTRebias::saveAdditional(tinyxml2::XMLDocument& doc, tinyxml2::XMLElement* data) const
{
if (!CMatrixClassifierFgMDMRT::saveAdditional(doc, data)) { return false; }
if (!m_bias.saveAdditional(doc, data)) { return false; }
return true;
}
///-------------------------------------------------------------------------------------------------
///-------------------------------------------------------------------------------------------------
bool CMatrixClassifierFgMDMRTRebias::loadAdditional(tinyxml2::XMLElement* data)
{
if (!CMatrixClassifierFgMDMRT::loadAdditional(data)) { return false; }
if (!m_bias.loadAdditional(data)) { return false; }
return true;
}
///-------------------------------------------------------------------------------------------------
//*****************************
//***** Override Operator *****
//*****************************
///-------------------------------------------------------------------------------------------------
bool CMatrixClassifierFgMDMRTRebias::isEqual(const CMatrixClassifierFgMDMRTRebias& obj, const double precision) const
{
return CMatrixClassifierFgMDMRT::isEqual(obj, precision) && m_bias == obj.m_bias;
}
///-------------------------------------------------------------------------------------------------
///-------------------------------------------------------------------------------------------------
void CMatrixClassifierFgMDMRTRebias::copy(const CMatrixClassifierFgMDMRTRebias& obj)
{
CMatrixClassifierFgMDMRT::copy(obj);
m_bias = obj.m_bias;
}
///-------------------------------------------------------------------------------------------------
///-------------------------------------------------------------------------------------------------
std::stringstream CMatrixClassifierFgMDMRTRebias::printAdditional() const
{
std::stringstream ss = CMatrixClassifierFgMDMRT::printAdditional();
ss << m_bias;
return ss;
}
///-------------------------------------------------------------------------------------------------
} // namespace Geometry
@@ -0,0 +1,177 @@
#include "geometry/classifier/CMatrixClassifierMDM.hpp"
#include "geometry/Mean.hpp"
#include "geometry/Distance.hpp"
#include "geometry/Basics.hpp"
#include "geometry/Geodesic.hpp"
#include <unsupported/Eigen/MatrixFunctions> // SQRT of Matrix
namespace Geometry {
//***********************
//***** Constructor *****
//***********************
///-------------------------------------------------------------------------------------------------
CMatrixClassifierMDM::CMatrixClassifierMDM(const size_t nbClass, const EMetric metric)
{
CMatrixClassifierMDM::setClassCount(nbClass);
m_metric = metric;
}
///-------------------------------------------------------------------------------------------------
///-------------------------------------------------------------------------------------------------
CMatrixClassifierMDM::~CMatrixClassifierMDM()
{
m_means.clear();
m_nbTrials.clear();
}
///-------------------------------------------------------------------------------------------------
//**********************
//***** Classifier *****
//**********************
///-------------------------------------------------------------------------------------------------
void CMatrixClassifierMDM::setClassCount(const size_t nbClass)
{
if (m_nbClass != nbClass || m_means.size() != nbClass || m_nbTrials.size() != nbClass)
{
IMatrixClassifier::setClassCount(nbClass);
m_means.resize(m_nbClass);
m_nbTrials.resize(nbClass);
}
}
///-------------------------------------------------------------------------------------------------
///-------------------------------------------------------------------------------------------------
bool CMatrixClassifierMDM::train(const std::vector<std::vector<Eigen::MatrixXd>>& dataset)
{
if (dataset.empty()) { return false; }
setClassCount(dataset.size()); // Change the number of classes if needed
for (size_t k = 0; k < m_nbClass; ++k) // for each class
{
if (!Mean(dataset[k], m_means[k], m_metric)) { return false; } // Compute the mean of each class
m_nbTrials[k] = dataset[k].size();
}
return true;
}
///-------------------------------------------------------------------------------------------------
///-------------------------------------------------------------------------------------------------
bool CMatrixClassifierMDM::classify(const Eigen::MatrixXd& sample, size_t& classId, std::vector<double>& distance,
std::vector<double>& probability, const EAdaptations adaptation, const size_t& realClassId)
{
if (!IsSquare(sample)) { return false; } // Verification if it's a square matrix
double distMin = std::numeric_limits<double>::max(); // Init of distance min
// Compute Distances
distance.resize(m_nbClass);
for (size_t k = 0; k < m_nbClass; ++k)
{
distance[k] = Distance(sample, m_means[k], m_metric);
if (distMin > distance[k])
{
classId = k;
distMin = distance[k];
}
}
// Compute Probabilities (personnal method)
probability.resize(m_nbClass);
double sumProbability = 0.0;
for (size_t k = 0; k < m_nbClass; ++k)
{
probability[k] = distMin / distance[k];
sumProbability += probability[k];
}
for (auto& p : probability) { p /= sumProbability; }
// Adaptation
if (adaptation == EAdaptations::None) { return true; }
// Get class id for adaptation and increase number of trials, expected if supervised, predicted if unsupervised
const size_t id = adaptation == EAdaptations::Supervised ? realClassId : classId;
if (id >= m_nbClass) { return false; } // Check id (if supervised and bad input)
m_nbTrials[id]++; // Update number of trials for the class id
return Geodesic(m_means[id], sample, m_means[id], m_metric, 1.0 / m_nbTrials[id]);
}
///-------------------------------------------------------------------------------------------------
//***********************
//***** XML Manager *****
//***********************
///-------------------------------------------------------------------------------------------------
bool CMatrixClassifierMDM::saveClasses(tinyxml2::XMLDocument& doc, tinyxml2::XMLElement* data) const
{
for (size_t k = 0; k < m_nbClass; ++k) // for each class
{
tinyxml2::XMLElement* element = doc.NewElement("Class"); // Create class node
element->SetAttribute("class-id", int(k)); // Set attribute class id (0 to K)
element->SetAttribute("nb-trials", int(m_nbTrials[k])); // Set attribute class number of trials
if (!saveMatrix(element, m_means[k])) { return false; } // Save class Matrix Reference
data->InsertEndChild(element); // Add class node to data node
}
return true;
}
///-------------------------------------------------------------------------------------------------
///-------------------------------------------------------------------------------------------------
bool CMatrixClassifierMDM::loadClasses(tinyxml2::XMLElement* data)
{
tinyxml2::XMLElement* element = data->FirstChildElement("Class"); // Get First Class Node
for (size_t k = 0; k < m_nbClass; ++k) // for each class
{
if (element == nullptr) { return false; } // Check if Node Exist
const size_t idx = element->IntAttribute("class-id"); // Get Id (normally idx == k)
if (idx != k) { return false; } // Check Id
m_nbTrials[k] = element->IntAttribute("nb-trials"); // Get the number of Trials for this class
if (!loadMatrix(element, m_means[k])) { return false; } // Load Class Matrix
element = element->NextSiblingElement("Class"); // Next Class
}
return true;
}
///-------------------------------------------------------------------------------------------------
//*****************************
//***** Override Operator *****
//*****************************
///-------------------------------------------------------------------------------------------------
std::stringstream CMatrixClassifierMDM::printClasses() const
{
std::stringstream ss;
for (size_t i = 0; i < m_nbClass; ++i)
{
ss << "Mean of class " << i << " (" << m_nbTrials[i] << " trials): ";
if (m_means[i].size() != 0) { ss << std::endl << m_means[i].format(MATRIX_FORMAT) << std::endl; }
else { ss << "Not Computed" << std::endl; }
}
return ss;
}
///-------------------------------------------------------------------------------------------------
///-------------------------------------------------------------------------------------------------
bool CMatrixClassifierMDM::isEqual(const CMatrixClassifierMDM& obj, const double precision) const
{
if (!IMatrixClassifier::isEqual(obj)) { return false; }
if (m_nbClass != obj.getClassCount()) { return false; }
for (size_t i = 0; i < m_nbClass; ++i)
{
if (!AreEquals(m_means[i], obj.m_means[i], precision)) { return false; }
if (m_nbTrials[i] != obj.m_nbTrials[i]) { return false; }
}
return true;
}
///-------------------------------------------------------------------------------------------------
///-------------------------------------------------------------------------------------------------
void CMatrixClassifierMDM::copy(const CMatrixClassifierMDM& obj)
{
IMatrixClassifier::copy(obj);
setClassCount(m_nbClass);
for (size_t i = 0; i < m_nbClass; ++i)
{
m_means[i] = obj.m_means[i];
m_nbTrials[i] = obj.m_nbTrials[i];
}
}
///-------------------------------------------------------------------------------------------------
} // namespace Geometry
@@ -0,0 +1,81 @@
#include "geometry/classifier/CMatrixClassifierMDMRebias.hpp"
#include "geometry/Mean.hpp"
#include "geometry/Basics.hpp"
#include <unsupported/Eigen/MatrixFunctions> // SQRT of Matrix
namespace Geometry {
//**********************
//***** Classifier *****
//**********************
///-------------------------------------------------------------------------------------------------
bool CMatrixClassifierMDMRebias::train(const std::vector<std::vector<Eigen::MatrixXd>>& dataset)
{
if (!m_bias.computeBias(dataset, m_metric)) { return false; }
std::vector<std::vector<Eigen::MatrixXd>> newDataset;
m_bias.applyBias(dataset, newDataset);
return CMatrixClassifierMDM::train(newDataset); // Train MDM
}
///-------------------------------------------------------------------------------------------------
///-------------------------------------------------------------------------------------------------
bool CMatrixClassifierMDMRebias::classify(const Eigen::MatrixXd& sample, size_t& classId, std::vector<double>& distance,
std::vector<double>& probability, const EAdaptations adaptation, const size_t& realClassId)
{
if (!IsSquare(sample)) { return false; } // Verification if it's a square matrix
Eigen::MatrixXd newSample;
m_bias.applyBias(sample, newSample);
m_bias.updateBias(sample, m_metric);
return CMatrixClassifierMDM::classify(newSample, classId, distance, probability, adaptation, realClassId);
}
///-------------------------------------------------------------------------------------------------
//***********************
//***** XML Manager *****
//***********************
///-------------------------------------------------------------------------------------------------
bool CMatrixClassifierMDMRebias::saveAdditional(tinyxml2::XMLDocument& doc, tinyxml2::XMLElement* data) const
{
if (!CMatrixClassifierMDM::saveAdditional(doc, data)) { return false; }
if (!m_bias.saveAdditional(doc, data)) { return false; }
return true;
}
///-------------------------------------------------------------------------------------------------
///-------------------------------------------------------------------------------------------------
bool CMatrixClassifierMDMRebias::loadAdditional(tinyxml2::XMLElement* data)
{
if (!CMatrixClassifierMDM::loadAdditional(data)) { return false; }
if (!m_bias.loadAdditional(data)) { return false; }
return true;
}
///-------------------------------------------------------------------------------------------------
//*****************************
//***** Override Operator *****
//*****************************
///-------------------------------------------------------------------------------------------------
bool CMatrixClassifierMDMRebias::isEqual(const CMatrixClassifierMDMRebias& obj, const double precision) const
{
return CMatrixClassifierMDM::isEqual(obj, precision) && m_bias == obj.m_bias;
}
///-------------------------------------------------------------------------------------------------
///-------------------------------------------------------------------------------------------------
void CMatrixClassifierMDMRebias::copy(const CMatrixClassifierMDMRebias& obj)
{
CMatrixClassifierMDM::copy(obj);
m_bias = obj.m_bias;
}
///-------------------------------------------------------------------------------------------------
///-------------------------------------------------------------------------------------------------
std::stringstream CMatrixClassifierMDMRebias::printAdditional() const
{
std::stringstream ss = CMatrixClassifierMDM::printAdditional();
ss << m_bias;
return ss;
}
///-------------------------------------------------------------------------------------------------
} // namespace Geometry
@@ -0,0 +1,172 @@
#include "geometry/classifier/IMatrixClassifier.hpp"
#include <iostream>
namespace Geometry {
//***********************
//***** Constructor *****
//***********************
///-------------------------------------------------------------------------------------------------
IMatrixClassifier::IMatrixClassifier(const size_t nbClass, const EMetric metric)
{
IMatrixClassifier::setClassCount(nbClass);
m_metric = metric;
}
///-------------------------------------------------------------------------------------------------
///-------------------------------------------------------------------------------------------------
void IMatrixClassifier::setClassCount(const size_t nbClass) { m_nbClass = nbClass; }
///-------------------------------------------------------------------------------------------------
///-------------------------------------------------------------------------------------------------
bool IMatrixClassifier::classify(const Eigen::MatrixXd& sample, size_t& classId, const EAdaptations adaptation, const size_t& realClassId)
{
std::vector<double> distance, probability;
return classify(sample, classId, distance, probability, adaptation, realClassId);
}
///-------------------------------------------------------------------------------------------------
//***********************
//***** XML Manager *****
//***********************
///-------------------------------------------------------------------------------------------------
bool IMatrixClassifier::saveXML(const std::string& filename) const
{
tinyxml2::XMLDocument xmlDoc;
// Create Root
tinyxml2::XMLNode* root = xmlDoc.NewElement("Classifier"); // Create root node
xmlDoc.InsertFirstChild(root); // Add root to XML
tinyxml2::XMLElement* data = xmlDoc.NewElement("Classifier-data"); // Create data node
if (!saveHeader(data)) { return false; } // Save Header attribute
if (!saveAdditional(xmlDoc, data)) { return false; } // Save Optionnal Informations
if (!saveClasses(xmlDoc, data)) { return false; } // Save Classes
root->InsertEndChild(data); // Add data to root
return xmlDoc.SaveFile(filename.c_str()) == 0; // save XML (if != 0 it means error)
}
///-------------------------------------------------------------------------------------------------
///-------------------------------------------------------------------------------------------------
bool IMatrixClassifier::loadXML(const std::string& filename)
{
// Load File
tinyxml2::XMLDocument xmlDoc;
if (xmlDoc.LoadFile(filename.c_str()) != 0) { return false; } // Check File Exist and Loading
// Load Root
tinyxml2::XMLNode* root = xmlDoc.FirstChild(); // Get Root Node
if (root == nullptr) { return false; } // Check Root Node Exist
// Load Data
tinyxml2::XMLElement* data = root->FirstChildElement("Classifier-data"); // Get Data Node
if (data == nullptr) { return false; } // Check Root Node Exist
if (!loadHeader(data)) { return false; } // Load Header attribute
if (!loadAdditional(data)) { return false; } // Load Optionnal Informations
if (!loadClasses(data)) { return false; } // Load Classes
return true;
}
///-------------------------------------------------------------------------------------------------
///-------------------------------------------------------------------------------------------------
bool IMatrixClassifier::convertMatrixToXMLFormat(const Eigen::MatrixXd& in, std::stringstream& out)
{
out << in.format(MATRIX_FORMAT);
return true;
}
///-------------------------------------------------------------------------------------------------
///-------------------------------------------------------------------------------------------------
bool IMatrixClassifier::convertXMLFormatToMatrix(std::stringstream& in, Eigen::MatrixXd& out, const size_t rows, const size_t cols)
{
out = Eigen::MatrixXd::Identity(rows, cols); // Init With Identity Matrix (in case of)
for (size_t i = 0; i < rows; ++i) // Fill Matrix
{
for (size_t j = 0; j < cols; ++j) { in >> out(i, j); }
}
return true;
}
///-------------------------------------------------------------------------------------------------
///-------------------------------------------------------------------------------------------------
bool IMatrixClassifier::saveMatrix(tinyxml2::XMLElement* element, const Eigen::MatrixXd& matrix)
{
element->SetAttribute("size", int(matrix.rows())); // Set Matrix size NxN
std::stringstream ss;
convertMatrixToXMLFormat(matrix, ss);
element->SetText(ss.str().c_str()); // Write Means Value
return true;
}
///-------------------------------------------------------------------------------------------------
///-------------------------------------------------------------------------------------------------
bool IMatrixClassifier::loadMatrix(tinyxml2::XMLElement* element, Eigen::MatrixXd& matrix)
{
const size_t size = element->IntAttribute("size"); // Get number of row/col
if (size == 0) { return true; }
std::stringstream ss(element->GetText()); // String stream to parse Matrix value
convertXMLFormatToMatrix(ss, matrix, size, size);
return true;
}
///-------------------------------------------------------------------------------------------------
//*****************************
//***** Override Operator *****
//*****************************
///-------------------------------------------------------------------------------------------------
bool IMatrixClassifier::isEqual(const IMatrixClassifier& obj, const double /*precision*/) const
{
return m_metric == obj.m_metric && m_nbClass == obj.getClassCount();
}
///-------------------------------------------------------------------------------------------------
///-------------------------------------------------------------------------------------------------
void IMatrixClassifier::copy(const IMatrixClassifier& obj)
{
m_metric = obj.m_metric;
setClassCount(obj.getClassCount());
}
/// -------------------------------------------------------------------------------------------------
/// -------------------------------------------------------------------------------------------------
std::stringstream IMatrixClassifier::print() const { return std::stringstream(printHeader().str() + printAdditional().str() + printClasses().str()); }
/// -------------------------------------------------------------------------------------------------
/// -------------------------------------------------------------------------------------------------
std::stringstream IMatrixClassifier::printHeader() const
{
std::stringstream ss;
ss << getType() << " Classifier" << std::endl;
ss << "Metric : " << toString(m_metric) << std::endl;
ss << "Number of Classes : " << m_nbClass << std::endl;
return ss;
}
///-------------------------------------------------------------------------------------------------
//*******************************************
//***** XML Manager (Private Functions) *****
//*******************************************
///-------------------------------------------------------------------------------------------------
bool IMatrixClassifier::saveHeader(tinyxml2::XMLElement* data) const
{
data->SetAttribute("type", getType().c_str()); // Set attribute classifier type
data->SetAttribute("class-count", int(m_nbClass)); // Set attribute class count
data->SetAttribute("metric", toString(m_metric).c_str()); // Set attribute metric
return true;
}
///-------------------------------------------------------------------------------------------------
///-------------------------------------------------------------------------------------------------
bool IMatrixClassifier::loadHeader(tinyxml2::XMLElement* data)
{
if (data == nullptr) { return false; } // Check if Node Exist
const std::string classifierType = data->Attribute("type"); // Get type
if (classifierType != getType()) { return false; } // Check Type
setClassCount(data->IntAttribute("class-count")); // Update Number of classes
m_metric = StringToMetric(data->Attribute("metric")); // Update Metric
return true;
}
///-------------------------------------------------------------------------------------------------
} // namespace Geometry
@@ -0,0 +1,50 @@
PROJECT(test-geometry)
FILE(GLOB_RECURSE TESTS_SRC_FILES *.cpp *.hpp)
ADD_EXECUTABLE(${PROJECT_NAME} ${TESTS_SRC_FILES})
SET_PROPERTY(TARGET ${PROJECT_NAME} PROPERTY FOLDER ${TESTS_FOLDER}) # Place project in folder unit-test (for some IDE)
# Modify library prefixes and suffixes to comply to Windows or Linux naming
IF(WIN32)
SET(CMAKE_FIND_LIBRARY_PREFIXES "")
SET(CMAKE_FIND_LIBRARY_SUFFIXES ".lib" ".dll")
ELSEIF(APPLE)
SET(CMAKE_FIND_LIBRARY_PREFIXES "lib")
SET(CMAKE_FIND_LIBRARY_SUFFIXES ".dylib" ".a")
ELSE()
SET(CMAKE_FIND_LIBRARY_PREFIXES "lib")
SET(CMAKE_FIND_LIBRARY_SUFFIXES ".so" ".a")
ENDIF()
FIND_PATH(PATH_GTEST ${CMAKE_FIND_LIBRARY_PREFIXES}gtest PATHS ${LIST_DEPENDENCIES_PATH} PATH_SUFFIXES gtest)
SET(GTEST_ROOT ${PATH_GTEST}/${CMAKE_FIND_LIBRARY_PREFIXES}gtest)
FIND_PACKAGE(GTest REQUIRED)
TARGET_LINK_LIBRARIES(${PROJECT_NAME} ${GTEST_BOTH_LIBRARIES})
INCLUDE_DIRECTORIES(${GTEST_INCLUDE_DIRS})
# OpenViBE Module
INCLUDE("FindModuleGeometry")
# OpenViBE Third Party
INCLUDE("FindThirdPartyEigen")
INCLUDE("FindThirdPartyBoost")
# ---------------------------------
# Target macros
# Defines target operating system, architecture and compiler
# ---------------------------------
SET_BUILD_PLATFORM()
# -----------------------------
# Install files
# -----------------------------
ADD_TEST(NAME test_Geometry COMMAND ${PROJECT_NAME})
OV_INSTALL_LAUNCH_SCRIPT(SCRIPT_PREFIX "${PROJECT_NAME}" EXECUTABLE_NAME "${PROJECT_NAME}")
INSTALL(TARGETS ${PROJECT_NAME}
RUNTIME DESTINATION ${DIST_BINDIR}
LIBRARY DESTINATION ${DIST_LIBDIR}
ARCHIVE DESTINATION ${DIST_LIBDIR})
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,36 @@
#include "gtest/gtest.h"
// ReSharper disable CppUnusedIncludeDirective
#include "test_Basics.hpp"
#include "test_Covariance.hpp"
#include "test_Mean.hpp"
#include "test_Median.hpp"
#include "test_Misc.hpp"
#include "test_Distance.hpp"
#include "test_Geodesics.hpp"
#include "test_Featurization.hpp"
#include "test_Classifier.hpp"
#include "test_MatrixClassifier.hpp"
#include "test_ASR.hpp"
// ReSharper restore CppUnusedIncludeDirective
int main(int argc, char** argv)
{
try
{
//Code coverage tips (this functions are used only if tests failed)
const size_t dumS = 0;
const double dumD = 0;
const Eigen::MatrixXd dumM = Eigen::MatrixXd::Identity(2, 2);
const std::vector<int> dumV = { 0, 0 };
const Geometry::CMatrixClassifierMDM dumC;
ErrorMsg("", dumS, dumS);
ErrorMsg("", dumD, dumD);
ErrorMsg("", dumM, dumM);
ErrorMsg("", dumV, dumV);
ErrorMsg("", dumC, dumC);
testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}
catch (std::exception&) { return 1; }
}
@@ -0,0 +1,120 @@
///-------------------------------------------------------------------------------------------------
///
/// \file misc.hpp
/// \brief Some constants and functions for google tests
/// \author Thibaut Monseigne (Inria).
/// \version 0.1.
/// \date 26/10/2018.
/// \copyright <a href="https://choosealicense.com/licenses/agpl-3.0/">GNU Affero General Public License v3.0</a>.
/// \remarks
/// - For this test I compare the results with the <a href="https://github.com/alexandrebarachant/pyRiemann">pyRiemann</a> library (<a href="https://github.com/alexandrebarachant/pyRiemann/blob/master/LICENSE">License</a>) or <a href="http://scikit-learn.org">sklearn</a> if pyRiemman just redirect the function.
///
///-------------------------------------------------------------------------------------------------
#pragma once
#include <cmath>
#include <vector>
#include <type_traits>
#include <geometry/classifier/IMatrixClassifier.hpp>
#include <geometry/artifacts/CASR.hpp>
const std::string SEP = "\n====================\n";
//*********************************************************************************
//********** Comparison of values with epsilon tolerance for google test **********
//*********************************************************************************
/// <summary> Check if two doubles are almost equal. </summary>
/// <param name="x"> The first value. </param>
/// <param name="y"> The second value. </param>
/// <param name="epsilon"> (Optional) The epsilon tolerance. </param>
/// <returns> True if almost equal, false if not. </returns>
inline bool isAlmostEqual(const double x, const double y, const double epsilon = 0.0001) { return std::abs(x - y) < epsilon; }
/// <summary> Check if sum of two vectors are almost equal. </summary>
/// <typeparam name="T"> Generic numeric type parameter. </typeparam>
/// \copydetails isAlmostEqual(const double, const double, const double)
template <typename T, typename = typename std::enable_if<std::is_arithmetic<T>::value, T>::type>
bool isAlmostEqual(const std::vector<T>& x, const std::vector<T>& y, const double epsilon = 0.0001)
{
double xsum = 0.0, ysum = 0.0;
for (const auto& n : x) { xsum += n; }
for (const auto& n : y) { ysum += n; }
return (x.size() == y.size() && isAlmostEqual(xsum, ysum, epsilon));
}
/// <summary> Check if sum of two matrix are almost equal. </summary>
/// \copydetails isAlmostEqual(const double, const double, const double)
inline bool isAlmostEqual(const Eigen::MatrixXd& x, const Eigen::MatrixXd& y, const double epsilon = 0.0001)
{
return x.size() == y.size() && isAlmostEqual(x.cwiseAbs().sum(), y.cwiseAbs().sum(), epsilon);
}
//*****************************************************************
//********** Error Message Standardization for googltest **********
//*****************************************************************
/// <summary> Error message for size_t. </summary>
/// <param name="name"> The name of the test. </param>
/// <param name="ref"> The reference value. </param>
/// <param name="calc"> The calculate value. </param>
/// <returns> Error message. </returns>
inline std::string ErrorMsg(const std::string& name, const size_t ref, const size_t calc)
{
std::stringstream ss;
ss << SEP << name << " : Reference : " << ref << ", \tCompute : " << calc << SEP;
return ss.str();
}
/// <summary> Error message for doubles. </summary>
/// \copydetails ErrorMsg(const std::string&, const size_t, const size_t)
inline std::string ErrorMsg(const std::string& name, const double ref, const double calc)
{
std::stringstream ss;
ss << SEP << name << " : Reference : " << ref << ", \tCompute : " << calc << SEP;
return ss.str();
}
/// <summary> Error message for numeric vector. </summary>
/// <typeparam name="T"> Generic numeric type parameter. </typeparam>
/// \copydetails ErrorMsg(const std::string&, const size_t, const size_t)
template <typename T, typename = typename std::enable_if<std::is_arithmetic<T>::value, T>::type>
std::string ErrorMsg(const std::string& name, const std::vector<T>& ref, const std::vector<T>& calc)
{
std::stringstream ss;
ss << SEP << name << " : " << std::endl << " Reference : \t[";
for (const T& t : ref) { ss << t << ", "; }
if (!ref.empty()) { ss.seekp(ss.str().length() - 2); }
ss << "] " << std::endl << " Compute : \t[";
for (const T& t : calc) { ss << t << ", "; }
if (!ref.empty()) { ss.seekp(ss.str().length() - 2); }
ss << "] " << SEP;
return ss.str();
}
/// <summary> Error message for matrix. </summary>
/// \copydetails ErrorMsg(const std::string&, const size_t, const size_t)
inline std::string ErrorMsg(const std::string& name, const Eigen::MatrixXd& ref, const Eigen::MatrixXd& calc)
{
std::stringstream ss;
ss << SEP << name << " : " << std::endl << "********** Reference **********\n" << ref << std::endl << "********** Compute **********\n" << calc << SEP;
return ss.str();
}
/// <summary> Error message for matrix Classifier. </summary>
/// \copydetails ErrorMsg(const std::string&, const size_t, const size_t)
inline std::string ErrorMsg(const std::string& name, const Geometry::IMatrixClassifier& ref, const Geometry::IMatrixClassifier& calc)
{
std::stringstream ss;
ss << SEP << name << " : " << std::endl << "********** Reference **********\n" << ref << std::endl << "********** Compute **********\n" << calc << SEP;
return ss.str();
}
/// <summary> Error message for ASR. </summary>
/// \copydetails ErrorMsg(const std::string&, const size_t, const size_t)
inline std::string ErrorMsg(const std::string& name, const Geometry::CASR& ref, const Geometry::CASR& calc)
{
std::stringstream ss;
ss << SEP << name << " : " << std::endl << "********** Reference **********\n" << ref << std::endl << "********** Compute **********\n" << calc << SEP;
return ss.str();
}
@@ -0,0 +1,81 @@
///-------------------------------------------------------------------------------------------------
///
/// \file test_ASR.hpp
/// \brief Tests for Artifact Subspace Reconstruction.
/// \author Thibaut Monseigne (Inria).
/// \version 1.0.
/// \date 29/07/2020.
/// \copyright <a href="https://choosealicense.com/licenses/agpl-3.0/">GNU Affero General Public License v3.0</a>.
/// \remarks We use the EEglab Matlab plugin to compare result for validation
///
///-------------------------------------------------------------------------------------------------
#pragma once
#include "gtest/gtest.h"
#include "init.hpp"
#include "misc.hpp"
#include <geometry/artifacts/CASR.hpp>
#include <geometry/Basics.hpp>
//---------------------------------------------------------------------------------------------------
class Tests_ASR : public testing::Test
{
protected:
std::vector<Eigen::MatrixXd> m_dataset;
void SetUp() override { m_dataset = Geometry::Vector2DTo1D(InitDataset::Dataset()); }
};
//---------------------------------------------------------------------------------------------------
//---------------------------------------------------------------------------------------------------
TEST_F(Tests_ASR, Train_Euclidian)
{
const Geometry::CASR ref = InitASR::Euclidian::Reference();
const Geometry::CASR calc(Geometry::EMetric::Euclidian, m_dataset);
EXPECT_TRUE(calc == ref) << ErrorMsg("Train ASR in Euclidian metric", ref, calc);
}
//---------------------------------------------------------------------------------------------------
//---------------------------------------------------------------------------------------------------
TEST_F(Tests_ASR, Train_Riemann)
{
std::cout << "Riemannian Eigen Value isn't implemented, so result is same as Euclidian metric." << std::endl;
const Geometry::CASR ref = InitASR::Riemann::Reference();
const Geometry::CASR calc(Geometry::EMetric::Riemann, m_dataset);
EXPECT_TRUE(calc == ref) << ErrorMsg("Train ASR in Riemann metric", ref, calc);
}
//---------------------------------------------------------------------------------------------------
//---------------------------------------------------------------------------------------------------
TEST_F(Tests_ASR, Process)
{
m_dataset = InitDataset::FirstClassDataset();
Geometry::CASR calc(Geometry::EMetric::Euclidian, m_dataset);
std::vector<Eigen::MatrixXd> testset = InitDataset::SecondClassDataset();
std::vector<Eigen::MatrixXd> result(testset.size());
for (size_t i = 0; i < testset.size(); ++i)
{
testset[i] *= 2;
EXPECT_TRUE(calc.process(testset[i], result[i])) << "ASR Process fail for sample " + std::to_string(i) + ".\n";
}
for (size_t i = 1; i < testset.size(); ++i)
{
EXPECT_FALSE(isAlmostEqual(result[i], testset[i])) << "the sample " + std::to_string(i) + " wasn't reconstructed.\n";
}
}
//---------------------------------------------------------------------------------------------------
//---------------------------------------------------------------------------------------------------
TEST_F(Tests_ASR, Save)
{
Geometry::CASR calc;
const Geometry::CASR ref = InitASR::Euclidian::Reference();
EXPECT_TRUE(ref.saveXML("test_ASR_Save.xml")) << "Error during Saving : " << std::endl << ref << std::endl;
EXPECT_TRUE(calc.loadXML("test_ASR_Save.xml")) << "Error during Loading : " << std::endl << calc << std::endl;
EXPECT_TRUE(ref == calc) << ErrorMsg("ASR Save", ref, calc);
}
//---------------------------------------------------------------------------------------------------
@@ -0,0 +1,150 @@
///-------------------------------------------------------------------------------------------------
///
/// \file test_Basics.hpp
/// \brief Tests for Basic functions of Module.
/// \author Thibaut Monseigne (Inria).
/// \version 1.0.
/// \date 09/01/2019.
/// \copyright <a href="https://choosealicense.com/licenses/agpl-3.0/">GNU Affero General Public License v3.0</a>.
///
///-------------------------------------------------------------------------------------------------
#pragma once
#include "gtest/gtest.h"
#include "init.hpp"
#include "misc.hpp"
#include <geometry/Basics.hpp>
#include <geometry/Metrics.hpp>
//---------------------------------------------------------------------------------------------------
class Tests_Basics : public testing::Test
{
protected:
std::vector<std::vector<Eigen::MatrixXd>> m_dataSet;
void SetUp() override { m_dataSet = InitDataset::Dataset(); }
};
//---------------------------------------------------------------------------------------------------
//---------------------------------------------------------------------------------------------------
TEST_F(Tests_Basics, MatrixStandardization)
{
std::vector<std::vector<Eigen::MatrixXd>> calcC, refC = InitBasics::Center::Reference();
std::vector<std::vector<Eigen::MatrixXd>> calcS, refS = InitBasics::StandardScaler::Reference();
calcC.resize(m_dataSet.size());
calcS.resize(m_dataSet.size());
for (size_t k = 0; k < m_dataSet.size(); ++k)
{
calcC[k].resize(m_dataSet[k].size());
calcS[k].resize(m_dataSet[k].size());
for (size_t i = 0; i < m_dataSet[k].size(); ++i)
{
EXPECT_TRUE(MatrixStandardization(m_dataSet[k][i], calcC[k][i], Geometry::EStandardization::Center)) << "Error During Centerization" << std::endl;
EXPECT_TRUE(MatrixStandardization(m_dataSet[k][i], calcS[k][i], Geometry::EStandardization::StandardScale)) << "Error During Standard Scaler" << std::endl;
const std::string title = "Matrix Center Sample [" + std::to_string(k) + "][" + std::to_string(i) + "]";
EXPECT_TRUE(isAlmostEqual(refC[k][i], calcC[k][i])) << ErrorMsg(title, refC[k][i], calcC[k][i]);
EXPECT_TRUE(isAlmostEqual(refS[k][i], calcS[k][i])) << ErrorMsg(title, refS[k][i], calcS[k][i]);
}
}
}
//---------------------------------------------------------------------------------------------------
//---------------------------------------------------------------------------------------------------
TEST_F(Tests_Basics, GetElements)
{
Eigen::RowVectorXd ref(3);
const std::vector<size_t> idx{ 0, 4, 7 };
ref << -3, -6, -1;
const Eigen::RowVectorXd calc = Geometry::GetElements(m_dataSet[0][0].row(0), idx); // row = -3, -4, -5, -4, -6, -1, -4, -1, -3, -1
EXPECT_TRUE(isAlmostEqual(ref, calc)) << ErrorMsg("GetElements", ref, calc);
}
//---------------------------------------------------------------------------------------------------
//---------------------------------------------------------------------------------------------------
TEST_F(Tests_Basics, ARange)
{
const std::vector<size_t> ref{ 1, 3, 5, 7, 9 },
calc = Geometry::ARange(size_t(1), size_t(10), size_t(2));
EXPECT_TRUE(isAlmostEqual(ref, calc)) << ErrorMsg("ARange", ref, calc);
}
//---------------------------------------------------------------------------------------------------
//---------------------------------------------------------------------------------------------------
TEST_F(Tests_Basics, Vector2DTo1D)
{
std::vector<Eigen::MatrixXd> calc = Geometry::Vector2DTo1D(m_dataSet);
bool equal = true;
size_t idx = 0;
for (auto& set : m_dataSet) { for (const auto& data : set) { if (!isAlmostEqual(data, calc[idx++])) { equal = false; } } }
EXPECT_TRUE(equal) << "Vector2DTo1D fail";
}
//---------------------------------------------------------------------------------------------------
//---------------------------------------------------------------------------------------------------
TEST_F(Tests_Basics, Metrics)
{
EXPECT_TRUE(toString(Geometry::EMetric::Riemann) == "Riemann");
EXPECT_TRUE(toString(Geometry::EMetric::Euclidian) == "Euclidian");
EXPECT_TRUE(toString(Geometry::EMetric::LogEuclidian) == "Log Euclidian");
EXPECT_TRUE(toString(Geometry::EMetric::LogDet) == "Log Determinant");
EXPECT_TRUE(toString(Geometry::EMetric::Kullback) == "Kullback");
EXPECT_TRUE(toString(Geometry::EMetric::ALE) == "AJD-based log-Euclidean");
EXPECT_TRUE(toString(Geometry::EMetric::Harmonic) == "Harmonic");
EXPECT_TRUE(toString(Geometry::EMetric::Wasserstein) == "Wasserstein");
EXPECT_TRUE(toString(Geometry::EMetric::Identity) == "Identity");
EXPECT_TRUE(Geometry::StringToMetric("Riemann") == Geometry::EMetric::Riemann);
EXPECT_TRUE(Geometry::StringToMetric("Euclidian") == Geometry::EMetric::Euclidian);
EXPECT_TRUE(Geometry::StringToMetric("Log Euclidian") == Geometry::EMetric::LogEuclidian);
EXPECT_TRUE(Geometry::StringToMetric("Log Determinant") == Geometry::EMetric::LogDet);
EXPECT_TRUE(Geometry::StringToMetric("Kullback") == Geometry::EMetric::Kullback);
EXPECT_TRUE(Geometry::StringToMetric("AJD-based log-Euclidean") == Geometry::EMetric::ALE);
EXPECT_TRUE(Geometry::StringToMetric("Harmonic") == Geometry::EMetric::Harmonic);
EXPECT_TRUE(Geometry::StringToMetric("Wasserstein") == Geometry::EMetric::Wasserstein);
EXPECT_TRUE(Geometry::StringToMetric("Identity") == Geometry::EMetric::Identity);
EXPECT_TRUE(Geometry::StringToMetric("") == Geometry::EMetric::Identity);
}
//---------------------------------------------------------------------------------------------------
//---------------------------------------------------------------------------------------------------
TEST_F(Tests_Basics, Validation)
{
const Eigen::MatrixXd m1 = Eigen::MatrixXd::Zero(2, 2),
m2 = Eigen::MatrixXd::Zero(1, 2),
m3;
std::vector<Eigen::MatrixXd> v;
EXPECT_TRUE(Geometry::InRange(1, 0, 2)); // 0 <= 1 <= 2 ?
EXPECT_FALSE(Geometry::InRange(2, 0, 1)); // 0 <= 2 <= 1 ?
EXPECT_FALSE(Geometry::AreNotEmpty(v)); // Empty Vector
v.push_back(m3);
EXPECT_FALSE(Geometry::AreNotEmpty(v)); // Vector with one empty matix
v.push_back(m1);
EXPECT_FALSE(Geometry::AreNotEmpty(v)); // Vector With one empty matrix and one non empty matrix
v.clear();
v.push_back(m1);
v.push_back(m2);
EXPECT_TRUE(Geometry::AreNotEmpty(v)); // Vector With two non empty matrix
EXPECT_TRUE(Geometry::HaveSameSize(m1, m1)); // Same matrix
EXPECT_FALSE(Geometry::HaveSameSize(m3, m3)); // Same but empty
EXPECT_FALSE(Geometry::HaveSameSize(m1, m2)); // DIfferents
EXPECT_FALSE(Geometry::HaveSameSize(m1, m3)); // One empty
EXPECT_FALSE(Geometry::HaveSameSize(v)); // Two different
EXPECT_FALSE(Geometry::AreSquare(v)); // One square
v.clear();
v.push_back(m1);
v.push_back(m1);
EXPECT_TRUE(Geometry::HaveSameSize(v) && Geometry::AreSquare(v)); // Same matrix
Geometry::MatrixPrint(m1); // Only to check
Geometry::MatrixPrint(m3); // Only to check
std::vector<std::string> vs = Geometry::Split("0,1,2,3.a\n", ",");
EXPECT_TRUE(vs.size() == 4 && vs[0] == "0" && vs[1] == "1" && vs[2] == "2" && vs[3] == "3.a") << vs.size() << " " << vs[0] << " " << vs[1] << " " << vs[2] << " " << vs[3] << std::endl;
}
//---------------------------------------------------------------------------------------------------
@@ -0,0 +1,53 @@
///-------------------------------------------------------------------------------------------------
///
/// \file test_Classifier.hpp
/// \brief Tests for Classifier Functions.
/// \author Thibaut Monseigne (Inria).
/// \version 1.0.
/// \date 09/01/2019.
/// \copyright <a href="https://choosealicense.com/licenses/agpl-3.0/">GNU Affero General Public License v3.0</a>.
///
///-------------------------------------------------------------------------------------------------
#pragma once
#include "gtest/gtest.h"
#include "misc.hpp"
#include "init.hpp"
#include <geometry/Classification.hpp>
//---------------------------------------------------------------------------------------------------
class Tests_Classifier : public testing::Test
{
protected:
std::vector<std::vector<Eigen::RowVectorXd>> m_dataSet;
void SetUp() override
{
const std::vector<Eigen::RowVectorXd> tmp = InitFeaturization::TangentSpace::Reference();
m_dataSet = Geometry::Vector1DTo2D(tmp, { NB_TRIALS1, NB_TRIALS2 });
}
};
//---------------------------------------------------------------------------------------------------
//---------------------------------------------------------------------------------------------------
TEST_F(Tests_Classifier, LSQR)
{
const Eigen::MatrixXd ref = InitClassif::LSQR::Reference();
Eigen::MatrixXd calc;
Geometry::LSQR(m_dataSet, calc);
EXPECT_TRUE(isAlmostEqual(ref, calc)) << ErrorMsg("LSQR", ref, calc);
}
//---------------------------------------------------------------------------------------------------
//---------------------------------------------------------------------------------------------------
TEST_F(Tests_Classifier, FgDACompute)
{
const Eigen::MatrixXd ref = InitClassif::FgDACompute::Reference();
Eigen::MatrixXd calc;
Geometry::FgDACompute(m_dataSet, calc);
EXPECT_TRUE(isAlmostEqual(ref, calc)) << ErrorMsg("FgDA", ref, calc);
}
//---------------------------------------------------------------------------------------------------
@@ -0,0 +1,158 @@
///-------------------------------------------------------------------------------------------------
///
/// \file test_Covariance.hpp
/// \brief Tests for Covariance Matrix Functions.
/// \author Thibaut Monseigne (Inria).
/// \version 1.0.
/// \date 09/01/2019.
/// \copyright <a href="https://choosealicense.com/licenses/agpl-3.0/">GNU Affero General Public License v3.0</a>.
///
///-------------------------------------------------------------------------------------------------
#pragma once
#include "gtest/gtest.h"
#include "misc.hpp"
#include "init.hpp"
#include <geometry/Covariance.hpp>
//---------------------------------------------------------------------------------------------------
class Tests_Covariances : public testing::Test
{
protected:
std::vector<std::vector<Eigen::MatrixXd>> m_dataSet;
void SetUp() override { m_dataSet = InitDataset::Dataset(); }
};
//---------------------------------------------------------------------------------------------------
//---------------------------------------------------------------------------------------------------
TEST_F(Tests_Covariances, Covariance_Matrix_COR)
{
std::vector<std::vector<Eigen::MatrixXd>> calc, ref = InitCovariance::COR::Dataset();
calc.resize(m_dataSet.size());
for (size_t k = 0; k < m_dataSet.size(); ++k)
{
calc[k].resize(m_dataSet[k].size());
for (size_t i = 0; i < m_dataSet[k].size(); ++i)
{
CovarianceMatrix(m_dataSet[k][i], calc[k][i], Geometry::EEstimator::COR, Geometry::EStandardization::None);
const std::string title = "Covariance Matrix COR Sample [" + std::to_string(k) + "][" + std::to_string(i) + "]";
EXPECT_TRUE(isAlmostEqual(ref[k][i], calc[k][i])) << ErrorMsg(title, ref[k][i], calc[k][i]);
}
}
}
//---------------------------------------------------------------------------------------------------
//---------------------------------------------------------------------------------------------------
TEST_F(Tests_Covariances, Covariance_Matrix_COV)
{
std::vector<std::vector<Eigen::MatrixXd>> calc, ref = InitCovariance::COV::Dataset();
calc.resize(m_dataSet.size());
for (size_t k = 0; k < m_dataSet.size(); ++k)
{
calc[k].resize(m_dataSet[k].size());
for (size_t i = 0; i < m_dataSet[k].size(); ++i)
{
CovarianceMatrix(m_dataSet[k][i], calc[k][i], Geometry::EEstimator::COV, Geometry::EStandardization::None);
const std::string title = "Covariance Matrix COV Sample [" + std::to_string(k) + "][" + std::to_string(i) + "]";
EXPECT_TRUE(isAlmostEqual(ref[k][i], calc[k][i])) << ErrorMsg(title, ref[k][i], calc[k][i]);
}
}
}
//---------------------------------------------------------------------------------------------------
//---------------------------------------------------------------------------------------------------
TEST_F(Tests_Covariances, Covariance_Matrix_LWF)
{
std::vector<std::vector<Eigen::MatrixXd>> calc, ref = InitCovariance::LWF::Reference();
calc.resize(m_dataSet.size());
for (size_t k = 0; k < m_dataSet.size(); ++k)
{
calc[k].resize(m_dataSet[k].size());
for (size_t i = 0; i < m_dataSet[k].size(); ++i)
{
CovarianceMatrix(m_dataSet[k][i], calc[k][i], Geometry::EEstimator::LWF, Geometry::EStandardization::Center);
const std::string title = "Covariance Matrix LWF Sample [" + std::to_string(k) + "][" + std::to_string(i) + "]";
EXPECT_TRUE(isAlmostEqual(ref[k][i], calc[k][i])) << ErrorMsg(title, ref[k][i], calc[k][i]);
}
}
}
//---------------------------------------------------------------------------------------------------
//---------------------------------------------------------------------------------------------------
TEST_F(Tests_Covariances, Covariance_Matrix_MCD)
{
std::cout << "Not implemented" << std::endl;
std::vector<std::vector<Eigen::MatrixXd>> calc;
//std::vector<std::vector<Eigen::MatrixXd>> ref = InitCovariance::MCD::Reference();
calc.resize(m_dataSet.size());
for (size_t k = 0; k < m_dataSet.size(); ++k)
{
calc[k].resize(m_dataSet[k].size());
for (size_t i = 0; i < m_dataSet[k].size(); ++i)
{
CovarianceMatrix(m_dataSet[k][i], calc[k][i], Geometry::EEstimator::MCD, Geometry::EStandardization::Center);
//const std::string title = "Covariance Matrix MCD Sample [" + std::to_string(k) + "][" + std::to_string(i) + "]";
//EXPECT_TRUE(isAlmostEqual(ref[k][i], calc[k][i])) << ErrorMsg(title, ref[k][i], calc[k][i]);
}
}
}
//---------------------------------------------------------------------------------------------------
//---------------------------------------------------------------------------------------------------
TEST_F(Tests_Covariances, Covariance_Matrix_OAS)
{
std::vector<std::vector<Eigen::MatrixXd>> calc, ref = InitCovariance::OAS::Reference();
calc.resize(m_dataSet.size());
for (size_t k = 0; k < m_dataSet.size(); ++k)
{
calc[k].resize(m_dataSet[k].size());
for (size_t i = 0; i < m_dataSet[k].size(); ++i)
{
CovarianceMatrix(m_dataSet[k][i], calc[k][i], Geometry::EEstimator::OAS, Geometry::EStandardization::Center);
const std::string title = "Covariance Matrix OAS Sample [" + std::to_string(k) + "][" + std::to_string(i) + "]";
EXPECT_TRUE(isAlmostEqual(ref[k][i], calc[k][i])) << ErrorMsg(title, ref[k][i], calc[k][i]);
}
}
}
//---------------------------------------------------------------------------------------------------
//---------------------------------------------------------------------------------------------------
TEST_F(Tests_Covariances, Covariance_Matrix_SCM)
{
std::vector<std::vector<Eigen::MatrixXd>> calc, ref = InitCovariance::SCM::Reference();
calc.resize(m_dataSet.size());
for (size_t k = 0; k < m_dataSet.size(); ++k)
{
calc[k].resize(m_dataSet[k].size());
for (size_t i = 0; i < m_dataSet[k].size(); ++i)
{
CovarianceMatrix(m_dataSet[k][i], calc[k][i], Geometry::EEstimator::SCM, Geometry::EStandardization::None);
const std::string title = "Covariance Matrix SCM Sample [" + std::to_string(k) + "][" + std::to_string(i) + "]";
EXPECT_TRUE(isAlmostEqual(ref[k][i], calc[k][i])) << ErrorMsg(title, ref[k][i], calc[k][i]);
}
}
}
//---------------------------------------------------------------------------------------------------
//---------------------------------------------------------------------------------------------------
TEST_F(Tests_Covariances, Covariance_Matrix_IDE)
{
std::vector<std::vector<Eigen::MatrixXd>> calc;
const Eigen::MatrixXd ref = Eigen::MatrixXd::Identity(NB_CHAN, NB_CHAN);
calc.resize(m_dataSet.size());
for (size_t k = 0; k < m_dataSet.size(); ++k)
{
calc[k].resize(m_dataSet[k].size());
for (size_t i = 0; i < m_dataSet[k].size(); ++i)
{
CovarianceMatrix(m_dataSet[k][i], calc[k][i], Geometry::EEstimator::IDE, Geometry::EStandardization::None);
const std::string title = "Covariance Matrix IDE Sample [" + std::to_string(k) + "][" + std::to_string(i) + "]";
EXPECT_TRUE(isAlmostEqual(ref, calc[k][i])) << ErrorMsg(title, ref, calc[k][i]);
}
}
}
//---------------------------------------------------------------------------------------------------
@@ -0,0 +1,119 @@
///-------------------------------------------------------------------------------------------------
///
/// \file test_Distance.hpp
/// \brief Tests for Distance Functions.
/// \author Thibaut Monseigne (Inria).
/// \version 1.0.
/// \date 09/01/2019.
/// \copyright <a href="https://choosealicense.com/licenses/agpl-3.0/">GNU Affero General Public License v3.0</a>.
///
///-------------------------------------------------------------------------------------------------
#pragma once
#include "gtest/gtest.h"
#include "misc.hpp"
#include "init.hpp"
#include <geometry/Distance.hpp>
//---------------------------------------------------------------------------------------------------
class Tests_Distances : public testing::Test
{
protected:
std::vector<Eigen::MatrixXd> m_dataSet;
void SetUp() override { m_dataSet = Geometry::Vector2DTo1D(InitCovariance::LWF::Reference()); }
};
//---------------------------------------------------------------------------------------------------
//---------------------------------------------------------------------------------------------------
TEST_F(Tests_Distances, Euclidian)
{
const std::vector<double> ref = InitDistance::Euclidian::Reference();
const Eigen::MatrixXd mean = InitMeans::Euclidian::Reference();
for (size_t i = 0; i < m_dataSet.size(); ++i)
{
const double calc = Distance(mean, m_dataSet[i], Geometry::EMetric::Euclidian);
EXPECT_TRUE(isAlmostEqual(ref[i], calc)) << ErrorMsg("Distance Euclidian Sample [" + std::to_string(i) + "]", ref[i], calc);
}
}
//---------------------------------------------------------------------------------------------------
//---------------------------------------------------------------------------------------------------
TEST_F(Tests_Distances, LogEuclidian)
{
const std::vector<double> ref = InitDistance::LogEuclidian::Reference();
const Eigen::MatrixXd mean = InitMeans::LogEuclidian::Reference();
for (size_t i = 0; i < m_dataSet.size(); ++i)
{
const double calc = Distance(mean, m_dataSet[i], Geometry::EMetric::LogEuclidian);
EXPECT_TRUE(isAlmostEqual(ref[i], calc)) << ErrorMsg("Distance LogEuclidian Sample [" + std::to_string(i) + "]", ref[i], calc);
}
}
//---------------------------------------------------------------------------------------------------
//---------------------------------------------------------------------------------------------------
TEST_F(Tests_Distances, Riemann)
{
const std::vector<double> ref = InitDistance::Riemann::Reference();
const Eigen::MatrixXd mean = InitMeans::Riemann::Reference();
for (size_t i = 0; i < m_dataSet.size(); ++i)
{
const double calc = Distance(mean, m_dataSet[i], Geometry::EMetric::Riemann);
EXPECT_TRUE(isAlmostEqual(ref[i], calc)) << ErrorMsg("Distance Riemann Sample [" + std::to_string(i) + "]", ref[i], calc);
}
}
//---------------------------------------------------------------------------------------------------
//---------------------------------------------------------------------------------------------------
TEST_F(Tests_Distances, LogDet)
{
const std::vector<double> ref = InitDistance::LogDeterminant::Reference();
const Eigen::MatrixXd mean = InitMeans::LogDeterminant::Reference();
for (size_t i = 0; i < m_dataSet.size(); ++i)
{
const double calc = Distance(mean, m_dataSet[i], Geometry::EMetric::LogDet);
EXPECT_TRUE(isAlmostEqual(ref[i], calc)) << ErrorMsg("Distance LogDet Sample [" + std::to_string(i) + "]", ref[i], calc);
}
}
//---------------------------------------------------------------------------------------------------
//---------------------------------------------------------------------------------------------------
TEST_F(Tests_Distances, Kullback)
{
const std::vector<double> ref = InitDistance::Kullback::Reference();
const Eigen::MatrixXd mean = InitMeans::Kullback::Reference();
for (size_t i = 0; i < m_dataSet.size(); ++i)
{
const double calc = Distance(mean, m_dataSet[i], Geometry::EMetric::Kullback);
EXPECT_TRUE(isAlmostEqual(ref[i], calc)) << ErrorMsg("Distance Kullback Sample [" + std::to_string(i) + "]", ref[i], calc);
}
}
//---------------------------------------------------------------------------------------------------
//---------------------------------------------------------------------------------------------------
TEST_F(Tests_Distances, Wasserstein)
{
const std::vector<double> ref = InitDistance::Wasserstein::Reference();
const Eigen::MatrixXd mean = InitMeans::Wasserstein::Reference();
for (size_t i = 0; i < m_dataSet.size(); ++i)
{
const double calc = Distance(mean, m_dataSet[i], Geometry::EMetric::Wasserstein);
EXPECT_TRUE(isAlmostEqual(ref[i], calc)) << ErrorMsg("Distance Wasserstein Sample [" + std::to_string(i) + "]", ref[i], calc);
}
}
//---------------------------------------------------------------------------------------------------
//---------------------------------------------------------------------------------------------------
TEST_F(Tests_Distances, Identity)
{
const Eigen::MatrixXd mean = InitMeans::Wasserstein::Reference();
for (size_t i = 0; i < m_dataSet.size(); ++i)
{
const double calc = Distance(mean, m_dataSet[i], Geometry::EMetric::Identity);
EXPECT_TRUE(isAlmostEqual(1, calc)) << ErrorMsg("Distance Wasserstein Sample [" + std::to_string(i) + "]", 1, calc);
}
}
//---------------------------------------------------------------------------------------------------
@@ -0,0 +1,91 @@
///-------------------------------------------------------------------------------------------------
///
/// \file test_Featurization.hpp
/// \brief Tests for Matrix Featurization Functions.
/// \author Thibaut Monseigne (Inria).
/// \version 1.0.
/// \date 09/01/2019.
/// \copyright <a href="https://choosealicense.com/licenses/agpl-3.0/">GNU Affero General Public License v3.0</a>.
///
///-------------------------------------------------------------------------------------------------
#pragma once
#include "gtest/gtest.h"
#include "misc.hpp"
#include "init.hpp"
#include <geometry/Featurization.hpp>
//---------------------------------------------------------------------------------------------------
class Tests_Featurization : public testing::Test
{
protected:
std::vector<Eigen::MatrixXd> m_dataSet;
void SetUp() override { m_dataSet = Geometry::Vector2DTo1D(InitCovariance::LWF::Reference()); }
};
//---------------------------------------------------------------------------------------------------
//---------------------------------------------------------------------------------------------------
TEST_F(Tests_Featurization, TangentSpace)
{
const std::vector<Eigen::RowVectorXd> ref = InitFeaturization::TangentSpace::Reference();
const Eigen::MatrixXd mean = InitMeans::Riemann::Reference();
for (size_t i = 0; i < m_dataSet.size(); ++i)
{
Eigen::RowVectorXd calc;
EXPECT_TRUE(Geometry::Featurization(m_dataSet[i], calc, true, mean)) << "Error During Processing";
EXPECT_TRUE(isAlmostEqual(ref[i], calc)) << ErrorMsg("TangentSpace Sample [" + std::to_string(i) + "]", ref[i], calc);
}
}
//---------------------------------------------------------------------------------------------------
//---------------------------------------------------------------------------------------------------
TEST_F(Tests_Featurization, UnTangentSpace)
{
const std::vector<Eigen::RowVectorXd> ref = InitFeaturization::TangentSpace::Reference();
const Eigen::MatrixXd mean = InitMeans::Riemann::Reference();
for (size_t i = 0; i < m_dataSet.size(); ++i)
{
Eigen::MatrixXd calc;
EXPECT_TRUE(Geometry::UnFeaturization(ref[i], calc, true, mean)) << "Error During Processing";
EXPECT_TRUE(isAlmostEqual(m_dataSet[i], calc)) << ErrorMsg("UnTangentSpace Sample [" + std::to_string(i) + "]", m_dataSet[i], calc);
}
}
//---------------------------------------------------------------------------------------------------
//---------------------------------------------------------------------------------------------------
TEST_F(Tests_Featurization, Squeeze)
{
const std::vector<Eigen::RowVectorXd> ref = InitFeaturization::Squeeze::Reference();
const std::vector<Eigen::RowVectorXd> refDiag = InitFeaturization::SqueezeDiag::Reference();
const Eigen::MatrixXd mean = InitMeans::Riemann::Reference();
for (size_t i = 0; i < m_dataSet.size(); ++i)
{
Eigen::RowVectorXd calc;
EXPECT_TRUE(Geometry::Featurization(m_dataSet[i], calc, false, mean)) << "Error During Processing";
EXPECT_TRUE(isAlmostEqual(ref[i], calc)) << ErrorMsg("Squeeze Sample [" + std::to_string(i) + "]", ref[i], calc);
EXPECT_TRUE(Geometry::SqueezeUpperTriangle(m_dataSet[i], calc, false)) << "Error During Processing";
EXPECT_TRUE(isAlmostEqual(refDiag[i], calc)) << ErrorMsg("Squeeze Sample [" + std::to_string(i) + "]", refDiag[i], calc);
}
}
//---------------------------------------------------------------------------------------------------
//---------------------------------------------------------------------------------------------------
TEST_F(Tests_Featurization, UnSqueeze)
{
const std::vector<Eigen::RowVectorXd> ref = InitFeaturization::Squeeze::Reference();
const std::vector<Eigen::RowVectorXd> refDiag = InitFeaturization::SqueezeDiag::Reference();
const Eigen::MatrixXd mean = InitMeans::Riemann::Reference();
for (size_t i = 0; i < m_dataSet.size(); ++i)
{
Eigen::MatrixXd calc;
EXPECT_TRUE(Geometry::UnFeaturization(ref[i], calc, false, mean)) << "Error During Processing";
EXPECT_TRUE(isAlmostEqual(m_dataSet[i], calc)) << ErrorMsg("UnSqueeze Sample [" + std::to_string(i) + "]", m_dataSet[i], calc);
EXPECT_TRUE(Geometry::UnSqueezeUpperTriangle(refDiag[i], calc, false)) << "Error During Processing";
EXPECT_TRUE(isAlmostEqual(m_dataSet[i], calc)) << ErrorMsg("UnSqueeze Sample [" + std::to_string(i) + "]", m_dataSet[i], calc);
}
}
//---------------------------------------------------------------------------------------------------
@@ -0,0 +1,84 @@
///-------------------------------------------------------------------------------------------------
///
/// \file test_Geodesics.hpp
/// \brief Tests for Geodesic Functions.
/// \author Thibaut Monseigne (Inria).
/// \version 1.0.
/// \date 09/01/2019.
/// \copyright <a href="https://choosealicense.com/licenses/agpl-3.0/">GNU Affero General Public License v3.0</a>.
///
///-------------------------------------------------------------------------------------------------
#pragma once
#include "gtest/gtest.h"
#include "misc.hpp"
#include "init.hpp"
#include <geometry/Geodesic.hpp>
//---------------------------------------------------------------------------------------------------
class Tests_Geodesic : public testing::Test
{
protected:
std::vector<Eigen::MatrixXd> m_dataSet;
void SetUp() override { m_dataSet = Geometry::Vector2DTo1D(InitCovariance::LWF::Reference()); }
};
//---------------------------------------------------------------------------------------------------
//---------------------------------------------------------------------------------------------------
TEST_F(Tests_Geodesic, Euclidian)
{
const std::vector<Eigen::MatrixXd> ref = InitGeodesics::Euclidian::Reference();
const Eigen::MatrixXd mean = InitMeans::Euclidian::Reference();
for (size_t i = 0; i < m_dataSet.size(); ++i)
{
Eigen::MatrixXd calc;
Geodesic(mean, m_dataSet[i], calc, Geometry::EMetric::Euclidian, 0.5);
EXPECT_TRUE(isAlmostEqual(ref[i], calc)) << ErrorMsg("Geodesic Euclidian Sample [" + std::to_string(i) + "]", ref[i], calc);
}
}
//---------------------------------------------------------------------------------------------------
//---------------------------------------------------------------------------------------------------
TEST_F(Tests_Geodesic, LogEuclidian)
{
const std::vector<Eigen::MatrixXd> ref = InitGeodesics::LogEuclidian::Reference();
const Eigen::MatrixXd mean = InitMeans::LogEuclidian::Reference();
for (size_t i = 0; i < m_dataSet.size(); ++i)
{
Eigen::MatrixXd calc;
Geodesic(mean, m_dataSet[i], calc, Geometry::EMetric::LogEuclidian, 0.5);
EXPECT_TRUE(isAlmostEqual(ref[i], calc)) << ErrorMsg("Geodesic LogEuclidian Sample [" + std::to_string(i) + "]", ref[i], calc);
}
}
//---------------------------------------------------------------------------------------------------
//---------------------------------------------------------------------------------------------------
TEST_F(Tests_Geodesic, Riemann)
{
const std::vector<Eigen::MatrixXd> ref = InitGeodesics::Riemann::Reference();
const Eigen::MatrixXd mean = InitMeans::Riemann::Reference();
for (size_t i = 0; i < m_dataSet.size(); ++i)
{
Eigen::MatrixXd calc;
Geodesic(mean, m_dataSet[i], calc, Geometry::EMetric::Riemann, 0.5);
EXPECT_TRUE(isAlmostEqual(ref[i], calc)) << ErrorMsg("Geodesic Riemann Sample [" + std::to_string(i) + "]", ref[i], calc);
}
}
//---------------------------------------------------------------------------------------------------
//---------------------------------------------------------------------------------------------------
TEST_F(Tests_Geodesic, Identity)
{
const Eigen::MatrixXd mean = InitMeans::Riemann::Reference(), ref = Eigen::MatrixXd::Identity(NB_CHAN, NB_CHAN);
for (size_t i = 0; i < m_dataSet.size(); ++i)
{
Eigen::MatrixXd calc;
Geodesic(mean, m_dataSet[i], calc, Geometry::EMetric::Identity, 0.5);
EXPECT_TRUE(isAlmostEqual(ref, calc)) << ErrorMsg("Geodesic Identity Sample [" + std::to_string(i) + "]", ref, calc);
}
}
//---------------------------------------------------------------------------------------------------
@@ -0,0 +1,296 @@
///-------------------------------------------------------------------------------------------------
///
/// \file test_MatrixClassifier.hpp
/// \brief Tests for Matrix Classifiers.
/// \author Thibaut Monseigne (Inria).
/// \version 1.0.
/// \date 09/01/2019.
/// \copyright <a href="https://choosealicense.com/licenses/agpl-3.0/">GNU Affero General Public License v3.0</a>.
/// \remarks
/// - For this tests I compare the results with the <a href="https://github.com/alexandrebarachant/pyRiemann">pyRiemann</a> library (<a href="https://github.com/alexandrebarachant/pyRiemann/blob/master/LICENSE">License</a>) or <a href="http://scikit-learn.org">sklearn</a> if pyRiemman just redirect the function.
/// - For the adaptation Classification tests I compare the results with the <a href="https://github.com/alexandrebarachant/covariancetoolbox">covariancetoolbox</a> Matlab library (<a href="https://github.com/alexandrebarachant/covariancetoolbox/blob/master/COPYING">License</a>).
/// - The Matlab toolbox is older and Riemannian mean estimation is diff�rent the test are adapted to switch between the two library
///
///-------------------------------------------------------------------------------------------------
#pragma once
#include "gtest/gtest.h"
#include "misc.hpp"
#include "init.hpp"
#include <geometry/classifier/CMatrixClassifierMDM.hpp>
#include <geometry/classifier/CMatrixClassifierMDMRebias.hpp>
#include <geometry/classifier/CMatrixClassifierFgMDM.hpp>
#include <geometry/classifier/CMatrixClassifierFgMDMRT.hpp>
#include <geometry/classifier/CMatrixClassifierFgMDMRTRebias.hpp>
static const std::vector<std::vector<double>> EMPTY_DIST;
//---------------------------------------------------------------------------------------------------
static void TestClassify(Geometry::IMatrixClassifier& calc, const std::vector<std::vector<Eigen::MatrixXd>>& dataset, const std::vector<size_t>& prediction,
const std::vector<std::vector<double>>& predictionDistance, const Geometry::EAdaptations& adapt)
{
Eigen::MatrixXd result = Eigen::MatrixXd::Zero(NB_CLASS, NB_CLASS);
size_t idx = 0;
for (size_t k = 0; k < dataset.size(); ++k)
{
for (size_t i = 0; i < dataset[k].size(); ++i)
{
const std::string text = "sample [" + std::to_string(k) + "][" + std::to_string(i) + "]";
size_t classid = 0;
std::vector<double> distance, probability;
EXPECT_TRUE(calc.classify(dataset[k][i], classid, distance, probability, adapt, k)) << "Error during Classify " << text;
if (idx < prediction.size()) { EXPECT_TRUE(prediction[idx] == classid) << ErrorMsg("Prediction " + text, prediction[idx], classid); }
if (idx < predictionDistance.size())
{
EXPECT_TRUE(isAlmostEqual(predictionDistance[idx], distance)) << ErrorMsg("Prediction Distance " + text, predictionDistance[idx], distance);
}
idx++;
result(k, classid)++;
}
}
std::cout << "***** Classifier : *****" << std::endl << calc << std::endl << "***** Result : *****" << std::endl << result << std::endl;
}
//---------------------------------------------------------------------------------------------------
//---------------------------------------------------------------------------------------------------
class Tests_MatrixClassifier : public testing::Test
{
protected:
std::vector<std::vector<Eigen::MatrixXd>> m_dataSet;
void SetUp() override { m_dataSet = InitCovariance::LWF::Reference(); }
};
//---------------------------------------------------------------------------------------------------
//---------------------------------------------------------------------------------------------------
TEST_F(Tests_MatrixClassifier, MDM_Train)
{
const Geometry::CMatrixClassifierMDM ref = InitMatrixClassif::MDM::Reference();
Geometry::CMatrixClassifierMDM calc;
EXPECT_TRUE(calc.train(m_dataSet)) << "Error during Training : " << std::endl << calc << std::endl;
EXPECT_TRUE(ref == calc) << ErrorMsg("MDM Train", ref, calc);
}
//---------------------------------------------------------------------------------------------------
//---------------------------------------------------------------------------------------------------
TEST_F(Tests_MatrixClassifier, MDM_Classifify)
{
Geometry::CMatrixClassifierMDM calc = InitMatrixClassif::MDM::ReferenceMatlab();
TestClassify(calc, m_dataSet, InitMatrixClassif::MDM::Prediction(), InitMatrixClassif::MDM::PredictionDistance(), Geometry::EAdaptations::None);
const Geometry::CMatrixClassifierMDM ref = InitMatrixClassif::MDM::ReferenceMatlab(); // No Change
EXPECT_TRUE(ref == calc) << ErrorMsg("MDM Classify Change without adaptation mode", ref, calc);
}
//---------------------------------------------------------------------------------------------------
//---------------------------------------------------------------------------------------------------
TEST_F(Tests_MatrixClassifier, MDM_Classifify_Adapt_Supervised)
{
Geometry::CMatrixClassifierMDM calc = InitMatrixClassif::MDM::ReferenceMatlab();
TestClassify(calc, m_dataSet, InitMatrixClassif::MDM::PredictionSupervised(), InitMatrixClassif::MDM::PredictionDistanceSupervised(),
Geometry::EAdaptations::Supervised);
const Geometry::CMatrixClassifierMDM ref = InitMatrixClassif::MDM::AfterSupervised();
EXPECT_TRUE(ref == calc) << ErrorMsg("MDM Adapt Classify after Supervised adaptation", ref, calc);
}
//---------------------------------------------------------------------------------------------------
//---------------------------------------------------------------------------------------------------
TEST_F(Tests_MatrixClassifier, MDM_Classifify_Adapt_Unsupervised)
{
Geometry::CMatrixClassifierMDM calc = InitMatrixClassif::MDM::ReferenceMatlab();
TestClassify(calc, m_dataSet, InitMatrixClassif::MDM::PredictionUnSupervised(), InitMatrixClassif::MDM::PredictionDistanceUnSupervised(),
Geometry::EAdaptations::Unsupervised);
const Geometry::CMatrixClassifierMDM ref = InitMatrixClassif::MDM::AfterUnSupervised();
EXPECT_TRUE(ref == calc) << ErrorMsg("MDM Adapt Classify after Unsupervised adaptation", ref, calc);
}
//---------------------------------------------------------------------------------------------------
//---------------------------------------------------------------------------------------------------
TEST_F(Tests_MatrixClassifier, MDM_Save)
{
Geometry::CMatrixClassifierMDM calc;
const Geometry::CMatrixClassifierMDM ref = InitMatrixClassif::MDM::Reference();
EXPECT_TRUE(ref.saveXML("test_MDM_Save.xml")) << "Error during Saving : " << std::endl << ref << std::endl;
EXPECT_TRUE(calc.loadXML("test_MDM_Save.xml")) << "Error during Loading : " << std::endl << calc << std::endl;
EXPECT_TRUE(ref == calc) << ErrorMsg("MDM Save", ref, calc);
}
//---------------------------------------------------------------------------------------------------
//---------------------------------------------------------------------------------------------------
TEST_F(Tests_MatrixClassifier, FgMDMRT_Train)
{
const Geometry::CMatrixClassifierFgMDMRT ref = InitMatrixClassif::FgMDMRT::Reference();
Geometry::CMatrixClassifierFgMDMRT calc;
EXPECT_TRUE(calc.train(m_dataSet)) << "Error during Training : " << std::endl << calc << std::endl;
EXPECT_TRUE(ref == calc) << ErrorMsg("FgMDM Train", ref, calc);
}
//---------------------------------------------------------------------------------------------------
//---------------------------------------------------------------------------------------------------
TEST_F(Tests_MatrixClassifier, FgMDMRT_Classifify)
{
Geometry::CMatrixClassifierFgMDMRT calc = InitMatrixClassif::FgMDMRT::Reference();
TestClassify(calc, m_dataSet, InitMatrixClassif::FgMDMRT::Prediction(), InitMatrixClassif::FgMDMRT::PredictionDistance(), Geometry::EAdaptations::None);
const Geometry::CMatrixClassifierFgMDMRT ref = InitMatrixClassif::FgMDMRT::Reference();
EXPECT_TRUE(ref == calc) << ErrorMsg("FgMDM Classify Change without adaptation mode", ref, calc);
}
//---------------------------------------------------------------------------------------------------
//---------------------------------------------------------------------------------------------------
TEST_F(Tests_MatrixClassifier, FgMDMRT_Classifify_Adapt_Supervised)
{
Geometry::CMatrixClassifierFgMDMRT calc = InitMatrixClassif::FgMDMRT::Reference();
TestClassify(calc, m_dataSet, InitMatrixClassif::FgMDMRT::PredictionSupervised(), EMPTY_DIST, Geometry::EAdaptations::Supervised);
//const Geometry::CMatrixClassifierFgMDMRT ref = InitMatrixClassif::FgMDMRT::AfterSupervised();
//EXPECT_TRUE(ref == calc) << ErrorMsg("FgMDM Adapt Classify after Supervised RT adaptation", ref, calc);
}
//---------------------------------------------------------------------------------------------------
//---------------------------------------------------------------------------------------------------
TEST_F(Tests_MatrixClassifier, FgMDMRT_Classifify_Adapt_Unsupervised)
{
Geometry::CMatrixClassifierFgMDMRT calc(InitMatrixClassif::FgMDMRT::Reference());
TestClassify(calc, m_dataSet, InitMatrixClassif::FgMDMRT::PredictionUnSupervised(), EMPTY_DIST, Geometry::EAdaptations::Unsupervised);
//const Geometry::CMatrixClassifierFgMDMRT ref = InitMatrixClassif::FgMDMRT::AfterUnSupervised();
//EXPECT_TRUE(ref == calc) << ErrorMsg("FgMDM Adapt Classify after Unsupervised RT adaptation", ref, calc);
}
//---------------------------------------------------------------------------------------------------
//---------------------------------------------------------------------------------------------------
TEST_F(Tests_MatrixClassifier, FgMDMRT_Save)
{
Geometry::CMatrixClassifierFgMDMRT calc;
const Geometry::CMatrixClassifierFgMDMRT ref = InitMatrixClassif::FgMDMRT::Reference();
EXPECT_TRUE(ref.saveXML("test_FgMDM_Save.xml")) << "Error during Saving : " << std::endl << ref << std::endl;
EXPECT_TRUE(calc.loadXML("test_FgMDM_Save.xml")) << "Error during Loading : " << std::endl << calc << std::endl;
EXPECT_TRUE(ref == calc) << ErrorMsg("FgMDM Save", ref, calc);
}
//---------------------------------------------------------------------------------------------------
//---------------------------------------------------------------------------------------------------
TEST_F(Tests_MatrixClassifier, FgMDM_Classifify_Adapt_Supervised)
{
Geometry::CMatrixClassifierFgMDM calc = InitMatrixClassif::FgMDM::Reference();
TestClassify(calc, m_dataSet, InitMatrixClassif::FgMDM::PredictionSupervised(), EMPTY_DIST, Geometry::EAdaptations::Supervised);
//const Geometry::CMatrixClassifierFgMDM ref = InitMatrixClassif::FgMDM::AfterSupervised();
//EXPECT_TRUE(ref == calc) << ErrorMsg("FgMDM Adapt Classify after Supervised adaptation", ref, calc);
}
//---------------------------------------------------------------------------------------------------
//---------------------------------------------------------------------------------------------------
TEST_F(Tests_MatrixClassifier, FgMDM_Classifify_Adapt_Unsupervised)
{
Geometry::CMatrixClassifierFgMDM calc = InitMatrixClassif::FgMDM::Reference();
TestClassify(calc, m_dataSet, InitMatrixClassif::FgMDM::PredictionUnSupervised(), EMPTY_DIST, Geometry::EAdaptations::Unsupervised);
//const Geometry::CMatrixClassifierFgMDM ref = InitMatrixClassif::FgMDM::AfterUnSupervised();
//EXPECT_TRUE(ref == calc) << ErrorMsg("FgMDM Adapt Classify after Unsupervised adaptation", ref, calc);
}
//---------------------------------------------------------------------------------------------------
//---------------------------------------------------------------------------------------------------
TEST_F(Tests_MatrixClassifier, MDM_Rebias_Train)
{
Geometry::CMatrixClassifierMDMRebias calc;
EXPECT_TRUE(calc.train(m_dataSet)) << "Error during Training : " << std::endl << calc << std::endl;
//const Geometry::CMatrixClassifierMDMRebias ref = InitMatrixClassif::MDMRebias::Reference();
//EXPECT_TRUE(ref == calc) << ErrorMsg("MDM Rebias Train", ref, calc); // The mean method is different in matlab toolbox and python toolbox
}
//---------------------------------------------------------------------------------------------------
//---------------------------------------------------------------------------------------------------
TEST_F(Tests_MatrixClassifier, MDM_Rebias_Classifify)
{
Geometry::CMatrixClassifierMDMRebias calc = InitMatrixClassif::MDMRebias::Reference();
TestClassify(calc, m_dataSet, InitMatrixClassif::MDMRebias::Prediction(), InitMatrixClassif::MDMRebias::PredictionDistance(), Geometry::EAdaptations::None);
const Geometry::CMatrixClassifierMDMRebias ref = InitMatrixClassif::MDMRebias::After(); // No Class change but Rebias yes
EXPECT_TRUE(ref == calc) << ErrorMsg("MDM Rebias Classify Change without adaptation mode", ref, calc);
}
//---------------------------------------------------------------------------------------------------
//---------------------------------------------------------------------------------------------------
TEST_F(Tests_MatrixClassifier, MDM_Rebias_Classifify_Adapt_Supervised)
{
Geometry::CMatrixClassifierMDMRebias calc = InitMatrixClassif::MDMRebias::Reference();
TestClassify(calc, m_dataSet, InitMatrixClassif::MDMRebias::PredictionSupervised(), InitMatrixClassif::MDMRebias::PredictionDistanceSupervised(),
Geometry::EAdaptations::Supervised);
const Geometry::CMatrixClassifierMDMRebias ref = InitMatrixClassif::MDMRebias::AfterSupervised();
EXPECT_TRUE(ref == calc) << ErrorMsg("MDM Rebias Adapt Classify after Supervised adaptation", ref, calc);
}
//---------------------------------------------------------------------------------------------------
//---------------------------------------------------------------------------------------------------
TEST_F(Tests_MatrixClassifier, MDM_Rebias_Classifify_Adapt_Unsupervised)
{
Geometry::CMatrixClassifierMDMRebias calc = InitMatrixClassif::MDMRebias::Reference();
TestClassify(calc, m_dataSet, InitMatrixClassif::MDMRebias::PredictionUnSupervised(), InitMatrixClassif::MDMRebias::PredictionDistanceUnSupervised(),
Geometry::EAdaptations::Unsupervised);
const Geometry::CMatrixClassifierMDMRebias ref = InitMatrixClassif::MDMRebias::AfterUnSupervised();
EXPECT_TRUE(ref == calc) << ErrorMsg("MDM Rebias Adapt Classify after Unsupervised adaptation", ref, calc);
}
//---------------------------------------------------------------------------------------------------
//---------------------------------------------------------------------------------------------------
TEST_F(Tests_MatrixClassifier, MDM_Rebias_Save)
{
Geometry::CMatrixClassifierMDMRebias calc;
const Geometry::CMatrixClassifierMDMRebias ref = InitMatrixClassif::MDMRebias::Reference();
EXPECT_TRUE(ref.saveXML("test_MDM_Rebias_Save.xml")) << "Error during Saving : " << std::endl << ref << std::endl;
EXPECT_TRUE(calc.loadXML("test_MDM_Rebias_Save.xml")) << "Error during Loading : " << std::endl << calc << std::endl;
EXPECT_TRUE(ref == calc) << ErrorMsg("MDM Rebias Save", ref, calc);
}
//---------------------------------------------------------------------------------------------------
//---------------------------------------------------------------------------------------------------
TEST_F(Tests_MatrixClassifier, FgMDM_RT_Rebias_Train)
{
Geometry::CMatrixClassifierFgMDMRTRebias calc;
EXPECT_TRUE(calc.train(m_dataSet)) << "Error during Training : " << std::endl << calc << std::endl;
const Geometry::CMatrixClassifierFgMDMRTRebias ref = InitMatrixClassif::FgMDMRTRebias::Reference();
EXPECT_TRUE(ref == calc) << ErrorMsg("FgMDM Rebias Train", ref, calc); // The mean method is different in matlab toolbox and python toolbox
}
//---------------------------------------------------------------------------------------------------
//---------------------------------------------------------------------------------------------------
TEST_F(Tests_MatrixClassifier, FgMDM_RT_Rebias_Save)
{
Geometry::CMatrixClassifierFgMDMRTRebias calc;
const Geometry::CMatrixClassifierFgMDMRTRebias ref = InitMatrixClassif::FgMDMRTRebias::Reference();
EXPECT_TRUE(ref.saveXML("test_FgMDM_Rebias_Save.xml")) << "Error during Saving : " << std::endl << ref << std::endl;
EXPECT_TRUE(calc.loadXML("test_FgMDM_Rebias_Save.xml")) << "Error during Loading : " << std::endl << calc << std::endl;
EXPECT_TRUE(ref == calc) << ErrorMsg("FgMDM Rebias Save", ref, calc);
}
//---------------------------------------------------------------------------------------------------
//---------------------------------------------------------------------------------------------------
TEST_F(Tests_MatrixClassifier, FgMDM_RT_Rebias_Classifify)
{
Geometry::CMatrixClassifierFgMDMRTRebias calc = InitMatrixClassif::FgMDMRTRebias::Reference();
TestClassify(calc, m_dataSet, InitMatrixClassif::FgMDMRTRebias::Prediction(), EMPTY_DIST, Geometry::EAdaptations::None);
//const Geometry::CMatrixClassifierFgMDMRTRebias ref = InitMatrixClassif::FgMDMRTRebias::After();
//EXPECT_TRUE(ref == calc) << ErrorMsg("FgMDM Rebias Classify Change without adaptation mode", ref, calc);
}
//---------------------------------------------------------------------------------------------------
//---------------------------------------------------------------------------------------------------
TEST_F(Tests_MatrixClassifier, FgMDM_RT_Rebias_Classifify_Adapt_Supervised)
{
Geometry::CMatrixClassifierFgMDMRTRebias calc = InitMatrixClassif::FgMDMRTRebias::Reference();
TestClassify(calc, m_dataSet, InitMatrixClassif::FgMDMRTRebias::PredictionSupervised(), EMPTY_DIST, Geometry::EAdaptations::Supervised);
//const Geometry::CMatrixClassifierFgMDMRTRebias ref = InitMatrixClassif::FgMDMRTRebias::AfterSupervised();
//EXPECT_TRUE(ref == calc) << ErrorMsg("FgMDM Rebias Adapt Classify after Supervised adaptation", ref, calc);
}
//---------------------------------------------------------------------------------------------------
//---------------------------------------------------------------------------------------------------
TEST_F(Tests_MatrixClassifier, FgMDM_RT_Rebias_Classifify_Adapt_Unsupervised)
{
Geometry::CMatrixClassifierFgMDMRTRebias calc = InitMatrixClassif::FgMDMRTRebias::Reference();
TestClassify(calc, m_dataSet, InitMatrixClassif::FgMDMRTRebias::PredictionUnSupervised(), EMPTY_DIST, Geometry::EAdaptations::Unsupervised);
//const Geometry::CMatrixClassifierFgMDMRTRebias ref = InitMatrixClassif::FgMDMRTRebias::AfterUnSupervised();
//EXPECT_TRUE(ref == calc) << ErrorMsg("FgMDM Rebias Adapt Classify after Unsupervised adaptation", ref, calc);
}
//---------------------------------------------------------------------------------------------------
@@ -0,0 +1,135 @@
///-------------------------------------------------------------------------------------------------
///
/// \file test_Mean.hpp
/// \brief Tests for Mean Functions.
/// \author Thibaut Monseigne (Inria).
/// \version 1.0.
/// \date 09/01/2019.
/// \copyright <a href="https://choosealicense.com/licenses/agpl-3.0/">GNU Affero General Public License v3.0</a>.
///
///-------------------------------------------------------------------------------------------------
#pragma once
#include "gtest/gtest.h"
#include "misc.hpp"
#include "init.hpp"
#include <geometry/Mean.hpp>
//---------------------------------------------------------------------------------------------------
class Tests_Means : public testing::Test
{
protected:
std::vector<Eigen::MatrixXd> m_dataSet;
void SetUp() override { m_dataSet = Geometry::Vector2DTo1D(InitCovariance::LWF::Reference()); }
};
//---------------------------------------------------------------------------------------------------
//---------------------------------------------------------------------------------------------------
TEST_F(Tests_Means, BadInput)
{
std::vector<Eigen::MatrixXd> bad;
Eigen::MatrixXd calc;
EXPECT_FALSE(Mean(bad, calc, Geometry::EMetric::Riemann));
bad.emplace_back(Eigen::MatrixXd::Zero(1, 2));
bad.emplace_back(Eigen::MatrixXd::Zero(1, 2));
EXPECT_FALSE(Mean(bad, calc, Geometry::EMetric::Riemann));
bad.emplace_back(Eigen::MatrixXd::Zero(2, 2));
EXPECT_FALSE(Mean(bad, calc, Geometry::EMetric::Riemann));
}
//---------------------------------------------------------------------------------------------------
//---------------------------------------------------------------------------------------------------
TEST_F(Tests_Means, Euclidian)
{
const Eigen::MatrixXd ref = InitMeans::Euclidian::Reference();
Eigen::MatrixXd calc;
Mean(m_dataSet, calc, Geometry::EMetric::Euclidian);
EXPECT_TRUE(isAlmostEqual(ref, calc)) << ErrorMsg("Mean Matrix Euclidian", ref, calc);
}
//---------------------------------------------------------------------------------------------------
//---------------------------------------------------------------------------------------------------
TEST_F(Tests_Means, LogEuclidian)
{
const Eigen::MatrixXd ref = InitMeans::LogEuclidian::Reference();
Eigen::MatrixXd calc;
Mean(m_dataSet, calc, Geometry::EMetric::LogEuclidian);
EXPECT_TRUE(isAlmostEqual(ref, calc)) << ErrorMsg("Mean Matrix LogEuclidian", ref, calc);
}
//---------------------------------------------------------------------------------------------------
//---------------------------------------------------------------------------------------------------
TEST_F(Tests_Means, Riemann)
{
const Eigen::MatrixXd ref = InitMeans::Riemann::Reference();
Eigen::MatrixXd calc;
Mean(m_dataSet, calc, Geometry::EMetric::Riemann);
EXPECT_TRUE(isAlmostEqual(ref, calc)) << ErrorMsg("Mean Matrix Riemann", ref, calc);
}
//---------------------------------------------------------------------------------------------------
//---------------------------------------------------------------------------------------------------
TEST_F(Tests_Means, LogDet)
{
const Eigen::MatrixXd ref = InitMeans::LogDeterminant::Reference();
Eigen::MatrixXd calc;
Mean(m_dataSet, calc, Geometry::EMetric::LogDet);
EXPECT_TRUE(isAlmostEqual(ref, calc)) << ErrorMsg("Mean Matrix LogDet", ref, calc);
}
//---------------------------------------------------------------------------------------------------
//---------------------------------------------------------------------------------------------------
TEST_F(Tests_Means, Kullback)
{
const Eigen::MatrixXd ref = InitMeans::Kullback::Reference();
Eigen::MatrixXd calc;
Mean(m_dataSet, calc, Geometry::EMetric::Kullback);
EXPECT_TRUE(isAlmostEqual(ref, calc)) << ErrorMsg("Mean Matrix Kullback", ref, calc);
}
//---------------------------------------------------------------------------------------------------
//---------------------------------------------------------------------------------------------------
TEST_F(Tests_Means, Wasserstein)
{
std::cout << "Precision Error" << std::endl;
Eigen::MatrixXd calc;
Mean(m_dataSet, calc, Geometry::EMetric::Wasserstein);
//const Eigen::MatrixXd ref = InitMeans::Wasserstein::Reference();
//EXPECT_TRUE(isAlmostEqual(ref, calc)) << ErrorMsg("Mean Matrix Wasserstein", ref, calc);
}
//---------------------------------------------------------------------------------------------------
//---------------------------------------------------------------------------------------------------
TEST_F(Tests_Means, ALE)
{
std::cout << "Not implemented" << std::endl;
Eigen::MatrixXd calc;
Mean(m_dataSet, calc, Geometry::EMetric::ALE);
//const Eigen::MatrixXd ref = InitMeans::ALE::Reference();
//EXPECT_TRUE(isAlmostEqual(ref, calc)) << ErrorMsg("Mean Matrix ALE", ref, calc);
}
//---------------------------------------------------------------------------------------------------
//---------------------------------------------------------------------------------------------------
TEST_F(Tests_Means, Harmonic)
{
const Eigen::MatrixXd ref = InitMeans::Harmonic::Reference();
Eigen::MatrixXd calc;
Mean(m_dataSet, calc, Geometry::EMetric::Harmonic);
EXPECT_TRUE(isAlmostEqual(ref, calc)) << ErrorMsg("Mean Matrix Harmonic", ref, calc);
}
//---------------------------------------------------------------------------------------------------
//---------------------------------------------------------------------------------------------------
TEST_F(Tests_Means, Identity)
{
const Eigen::MatrixXd ref = InitMeans::Identity::Reference();
Eigen::MatrixXd calc;
Mean(m_dataSet, calc, Geometry::EMetric::Identity);
EXPECT_TRUE(isAlmostEqual(ref, calc)) << ErrorMsg("Mean Matrix Identity", ref, calc);
}
//---------------------------------------------------------------------------------------------------
@@ -0,0 +1,86 @@
///-------------------------------------------------------------------------------------------------
///
/// \file test_Median.hpp
/// \brief Tests for Median Functions.
/// \author Thibaut Monseigne (Inria).
/// \version 1.0.
/// \date 29/07/2020.
/// \copyright <a href="https://choosealicense.com/licenses/agpl-3.0/">GNU Affero General Public License v3.0</a>.
/// \remarks We use the EEglab Matlab plugin to compare result for validation
///
///-------------------------------------------------------------------------------------------------
#pragma once
#include "gtest/gtest.h"
#include "init.hpp"
#include "misc.hpp"
#include <geometry/Basics.hpp>
#include <geometry/Median.hpp>
//---------------------------------------------------------------------------------------------------
class Tests_Median : public testing::Test
{
protected:
std::vector<Eigen::MatrixXd> m_dataSet;
void SetUp() override { m_dataSet = Geometry::Vector2DTo1D(InitCovariance::LWF::Reference()); }
};
//---------------------------------------------------------------------------------------------------
//---------------------------------------------------------------------------------------------------
TEST_F(Tests_Median, Simple_Median)
{
std::vector<double> v{ 5, 6, 4, 3, 2, 6, 7, 9, 3 };
double calc = Geometry::Median(v);
EXPECT_EQ(calc, 5);
v.pop_back();
calc = Geometry::Median(v);
EXPECT_EQ(calc, 5.5);
Eigen::MatrixXd m(3, 3);
m << 5, 6, 4, 3, 2, 6, 7, 9, 3;
calc = Geometry::Median(m);
EXPECT_EQ(calc, 5);
}
//---------------------------------------------------------------------------------------------------
//---------------------------------------------------------------------------------------------------
TEST_F(Tests_Median, Euclidian)
{
Eigen::MatrixXd calc;
Eigen::MatrixXd ref(3, 3);
ref << 1.749537973777478, 0.002960131606861, 0.020507254841909,
0.002960131606861, 1.754563395557952, 0.043042786354499,
0.020507254841909, 0.043042786354499, 1.057672472691352;
EXPECT_TRUE(Geometry::Median(m_dataSet, calc, 0.0001, 50, Geometry::EMetric::Euclidian)) << "Error During Median Computing";
EXPECT_TRUE(isAlmostEqual(ref, calc)) << ErrorMsg("Euclidian Median of Dataset", ref, calc);
}
//---------------------------------------------------------------------------------------------------
//---------------------------------------------------------------------------------------------------
TEST_F(Tests_Median, Riemann)
{
Eigen::MatrixXd calc;
Eigen::MatrixXd ref(3, 3);
ref << 1.851330747504982, 0.002002346316770, 0.022122030618131,
0.002002346316770, 1.644242996651016, 0.033655563302757,
0.022122030618131, 0.033655563302757, 0.851184143800763;
EXPECT_TRUE(Geometry::Median(m_dataSet, calc, 0.0001, 50, Geometry::EMetric::Riemann)) << "Error During Median Computes";
EXPECT_TRUE(isAlmostEqual(ref, calc)) << ErrorMsg("Riemann Median of Dataset", ref, calc);
}
//---------------------------------------------------------------------------------------------------
//---------------------------------------------------------------------------------------------------
TEST_F(Tests_Median, Identity)
{
const Eigen::MatrixXd ref = InitMeans::Identity::Reference();
Eigen::MatrixXd calc;
EXPECT_TRUE(Geometry::Median(m_dataSet, calc, 0.0001, 50, Geometry::EMetric::Identity)) << "Error During Median Computes";
EXPECT_TRUE(isAlmostEqual(ref, calc)) << ErrorMsg("Identity Median of Dataset", ref, calc);
}
//---------------------------------------------------------------------------------------------------
@@ -0,0 +1,158 @@
///-------------------------------------------------------------------------------------------------
///
/// \file test_Misc.hpp
/// \brief Tests for Misc Functions of module.
/// \author Thibaut Monseigne (Inria).
/// \version 1.0.
/// \date 29/07/2020.
/// \copyright <a href="https://choosealicense.com/licenses/agpl-3.0/">GNU Affero General Public License v3.0</a>.
/// \remarks We use the EEglab Matlab plugin to compare result for validation
///
///-------------------------------------------------------------------------------------------------
#pragma once
#include "gtest/gtest.h"
#include "init.hpp"
#include "misc.hpp"
#include <geometry/Misc.hpp>
#include <geometry/Basics.hpp>
//---------------------------------------------------------------------------------------------------
class Tests_Misc : public testing::Test
{
//protected:
// std::vector<Eigen::MatrixXd> m_dataSet;
//
// void SetUp() override { m_dataSet = Vector2DTo1D(InitCovariance::LWF::Reference()); }
};
//---------------------------------------------------------------------------------------------------
//---------------------------------------------------------------------------------------------------
TEST_F(Tests_Misc, Double_Range)
{
const std::vector<double> calc1 = Geometry::doubleRange(0, 10, 2), calc2 = Geometry::doubleRange(0, 10, 2, false),
calc3 = Geometry::doubleRange(0.15, 3.05, 0.5), calc4 = Geometry::doubleRange(0.15, 3.05, 0.5, false),
ref1 = { 0, 2, 4, 6, 8, 10 }, ref2 = { 0, 2, 4, 6, 8 },
ref3 = { 0.15, 0.65, 1.15, 1.65, 2.15, 2.65 }, ref4 = { 0.15, 0.65, 1.15, 1.65, 2.15, 2.65 };
EXPECT_TRUE(isAlmostEqual(ref1, calc1)) << ErrorMsg("Double closed Range with integer value", ref1, calc1);
EXPECT_TRUE(isAlmostEqual(ref2, calc2)) << ErrorMsg("Double opened Range with integer value", ref2, calc2);
EXPECT_TRUE(isAlmostEqual(ref3, calc3)) << ErrorMsg("Double closed Range with double value", ref3, calc3);
EXPECT_TRUE(isAlmostEqual(ref4, calc4)) << ErrorMsg("Double opened Range with double value", ref4, calc4);
}
//---------------------------------------------------------------------------------------------------
//---------------------------------------------------------------------------------------------------
TEST_F(Tests_Misc, Round_Index_Range)
{
const std::vector<size_t> calc1 = Geometry::RoundIndexRange(0, 10, 2), calc2 = Geometry::RoundIndexRange(0, 10, 2, false),
calc3 = Geometry::RoundIndexRange(0.15, 3.15, 0.2), calc4 = Geometry::RoundIndexRange(0.15, 3.05, 0.2, false, false),
ref1 = { 0, 2, 4, 6, 8, 10 }, ref2 = { 0, 2, 4, 6, 8 },
ref3 = { 0, 1, 2, 3 }, ref4 = { 0, 0, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 3, 3, 3 };
EXPECT_TRUE(isAlmostEqual(ref1, calc1)) << ErrorMsg("Round Index closed Range with integer value", ref1, calc1);
EXPECT_TRUE(isAlmostEqual(ref2, calc2)) << ErrorMsg("Round Index opened Range with integer value", ref2, calc2);
EXPECT_TRUE(isAlmostEqual(ref3, calc3)) << ErrorMsg("Round Index closed Range with double value", ref3, calc3);
EXPECT_TRUE(isAlmostEqual(ref4, calc4)) << ErrorMsg("Round Index opened Range with double value", ref4, calc4);
}
//---------------------------------------------------------------------------------------------------
//---------------------------------------------------------------------------------------------------
TEST_F(Tests_Misc, Bin_Histogramm)
{
//========== Create Dataset ==========
const std::vector<Eigen::MatrixXd> matrices = Geometry::Vector2DTo1D(InitDataset::Dataset());
std::vector<std::vector<double>> dataset(NB_CHAN);
// Transform Dataset to vector per channel
for (size_t i = 0; i < NB_CHAN; ++i) { dataset[i].reserve(NB_SAMPLE * matrices.size()); }
for (const auto& m : matrices) { for (size_t i = 0; i < NB_CHAN; ++i) { for (size_t j = 0; j < NB_SAMPLE; ++j) { dataset[i].push_back(m(i, j)); } } }
// Sort and remove first (to begin by 0)
for (auto& d : dataset)
{
std::sort(d.begin(), d.end());
const auto first = d[0];
for (auto& e : d) { e -= first; }
}
//========== Create Ref ==========
const std::vector<std::vector<size_t>> ref =
{
{ 12, 10, 0, 15, 15, 0, 6, 12, 0, 11, 11, 0, 11, 14, 3 },
{ 17, 0, 26, 0, 0, 18, 0, 28, 0, 0, 15, 0, 7, 0, 9 },
{ 36, 0, 0, 34, 0, 0, 0, 0, 0, 15, 0, 0, 20, 0, 15 }
};
//========== Test ==========
for (size_t i = 0; i < NB_CHAN; ++i)
{
const std::vector<size_t> hist = Geometry::BinHist(dataset[i], 15);
EXPECT_TRUE(isAlmostEqual(hist, ref[i])) << ErrorMsg("Bin Histogramm", hist, ref[i]);
}
}
//---------------------------------------------------------------------------------------------------
//---------------------------------------------------------------------------------------------------
TEST_F(Tests_Misc, Fit_Distribution)
{
const std::vector<Eigen::MatrixXd> matrices = Geometry::Vector2DTo1D(InitDataset::Dataset());
std::vector<std::vector<double>> dataset(NB_CHAN);
// Transform Dataset to vector per channel
for (size_t i = 0; i < NB_CHAN; ++i) { dataset[i].reserve(NB_SAMPLE * matrices.size()); }
for (const auto& m : matrices) { for (size_t i = 0; i < NB_CHAN; ++i) { for (size_t j = 0; j < NB_SAMPLE; ++j) { dataset[i].push_back(m(i, j)); } } }
// Begin Fit Distribution
std::vector<double> mu(NB_CHAN), sigma(NB_CHAN);
const std::vector<double> refMu = { -0.840258269642149, - 2.10169835819046, 0.898301641809541 },
refSigma = { 2.76541902273525, 0.435493584265319, 0.435493584265319 };
for (size_t i = 0; i < NB_CHAN; ++i)
{
Geometry::FitDistribution(dataset[i], mu[i], sigma[i]);
EXPECT_TRUE(isAlmostEqual(mu[i], refMu[i])) << ErrorMsg("Fit Distribution Mu", mu[i], refMu[i]);
EXPECT_TRUE(isAlmostEqual(sigma[i], refSigma[i])) << ErrorMsg("Fit Distribution Sigma", sigma[i], refSigma[i]);
}
}
//---------------------------------------------------------------------------------------------------
//---------------------------------------------------------------------------------------------------
TEST_F(Tests_Misc, Sorted_Eigen_Vector_Euclidian)
{
std::vector<Eigen::MatrixXd> matrices = Geometry::Vector2DTo1D(InitCovariance::LWF::Reference());
const size_t n = matrices.size();
std::vector<Eigen::MatrixXd> vectors = InitEigenVector::Euclidian::Vectors();
std::vector<std::vector<double>> values = InitEigenVector::Euclidian::Values();
for (size_t i = 0; i < n; ++i)
{
Eigen::MatrixXd vec;
std::vector<double> val;
Geometry::sortedEigenVector(matrices[i], vec, val, Geometry::EMetric::Euclidian);
EXPECT_TRUE(isAlmostEqual(vectors[i], vec)) << ErrorMsg("Eigen Vector sample " + std::to_string(i) + " : ", vectors[i], vec);
EXPECT_TRUE(isAlmostEqual(values[i], val)) << ErrorMsg("Eigen Value sample " + std::to_string(i) + " : ", values[i], val);
}
}
//---------------------------------------------------------------------------------------------------
/*
//---------------------------------------------------------------------------------------------------
TEST_F(Tests_Misc, Sorted_Eigen_Vector_Riemann)
{
std::cout << "Not implemented" << std::endl;
std::vector<Eigen::MatrixXd> matrices = Geometry::Vector2DTo1D(InitCovariance::LWF::Reference());
const size_t n = matrices.size();
//std::vector<Eigen::MatrixXd> vectors = InitEigenVector::Riemann::Vectors();
//std::vector<std::vector<double>> values = InitEigenVector::Riemann::Values();
for (size_t i = 0; i < n; ++i)
{
Eigen::MatrixXd vec;
std::vector<double> val;
Geometry::sortedEigenVector(matrices[i], vec, val, Geometry::EMetric::Riemann);
//EXPECT_TRUE(isAlmostEqual(vectors[i], vec)) << ErrorMsg("Eigen Vector sample " + std::to_string(i) + " : ", vectors[i], vec);
//EXPECT_TRUE(isAlmostEqual(values[i], val)) << ErrorMsg("Eigen Value sample " + std::to_string(i) + " : ", values[i], val);
}
}
//---------------------------------------------------------------------------------------------------
*/