From 3e42a9074c29ef77ed245b70e007e147ebbd1487 Mon Sep 17 00:00:00 2001
From: Arthur Desbois <arthur.desbois@gmail.com>
Date: Mon, 19 Apr 2021 15:33:28 +0200
Subject: [PATCH] Added Graph Laplacian box Graph Laplacian + Denoising
 computation from Connectivity Matrices

---
 .../connectivity/graphLaplacian.cpp           | 253 ++++++++++++++++++
 .../algorithms/connectivity/graphLaplacian.h  |  77 ++++++
 .../CBoxAlgorithmGraphLaplacian.cpp           | 110 ++++++++
 .../CBoxAlgorithmGraphLaplacian.h             | 109 ++++++++
 .../signal-processing/src/ovp_defines.h       |   5 +
 .../signal-processing/src/ovp_main.cpp        |  16 +-
 6 files changed, 569 insertions(+), 1 deletion(-)
 create mode 100644 plugins/processing/signal-processing/src/algorithms/connectivity/graphLaplacian.cpp
 create mode 100644 plugins/processing/signal-processing/src/algorithms/connectivity/graphLaplacian.h
 create mode 100644 plugins/processing/signal-processing/src/box-algorithms/connectivity/CBoxAlgorithmGraphLaplacian.cpp
 create mode 100644 plugins/processing/signal-processing/src/box-algorithms/connectivity/CBoxAlgorithmGraphLaplacian.h

diff --git a/plugins/processing/signal-processing/src/algorithms/connectivity/graphLaplacian.cpp b/plugins/processing/signal-processing/src/algorithms/connectivity/graphLaplacian.cpp
new file mode 100644
index 000000000..8926f7c49
--- /dev/null
+++ b/plugins/processing/signal-processing/src/algorithms/connectivity/graphLaplacian.cpp
@@ -0,0 +1,253 @@
+#include "graphLaplacian.h"
+#include <openvibe/ov_all.h>
+#include <cmath>
+#include <Eigen/Dense>
+#include <Eigen/Eigenvalues>
+#include <iostream>
+
+bool GraphLaplacian::initialize(EGraphLaplacianDenoisingType denoisingType, size_t denoisingSize, bool denoising, bool debugLogs, EGraphLaplacianOutputType outputType)
+{
+	m_denoisingSize = denoisingSize;
+	m_denoising = denoising;
+	m_debugLogs = debugLogs;
+
+	m_outputType = outputType;
+	m_denoisingType = denoisingType;
+
+	return true;
+}
+
+
+bool GraphLaplacian::process(const OpenViBE::IMatrix& in, OpenViBE::IMatrix& out)
+{
+	// in matrix : A (freq x N x N)
+	// GraphLaplacian computation (for each frequency) : Lf = Deg(Af) - Af
+	// with Deg(Af) = diag(sum_rows(Af))
+
+	// Conversion from OpenViBE's IMatrix of 3 dimensions (fftsize x nchan x nchan)
+	// to std:vector<Eigen::Matrix> (vect of fftsize x (nChan x  nChan) )
+	std::vector< Eigen::Matrix<double, Eigen::Dynamic, Eigen::Dynamic> > laplacianMatrices(in.getDimensionSize(0), Eigen::MatrixXd(in.getDimensionSize(1),in.getDimensionSize(2)));
+	std::vector< Eigen::Matrix<double, Eigen::Dynamic, Eigen::Dynamic> > adjacencyMatrices(in.getDimensionSize(0), Eigen::MatrixXd(in.getDimensionSize(1),in.getDimensionSize(2)));
+
+	size_t fftSize = in.getDimensionSize(0);
+	size_t nbChan = in.getDimensionSize(1);
+
+	const double* inbuffer = in.getBuffer();
+	for (size_t fftIdx = 0; fftIdx < fftSize; ++fftIdx) {
+		for (size_t chan0 = 0; chan0 < nbChan; ++chan0) {
+			for (size_t chan1 = 0; chan1 < nbChan; ++chan1) {
+				// inverted sign to put (-A) in (L = D - A)
+				laplacianMatrices[fftIdx](chan0, chan1) = -inbuffer[fftIdx*nbChan*nbChan + chan0*nbChan + chan1];
+			}
+		}
+	}
+
+	if(m_debugLogs) {
+		std::cout << "GraphLaplacian : process() this matrix (freq0) : " << std::endl;
+		for (size_t chan0 = 0; chan0 < nbChan; ++chan0) {
+			std::cout << -laplacianMatrices[0].row(chan0) << std::endl;
+		}
+		std::cout << std::endl;
+	}
+
+	// Degree matrix and laplacian computation
+	for(size_t fftIdx = 0; fftIdx < fftSize; ++fftIdx) {
+		for (size_t rowIdx = 0; rowIdx < nbChan; ++rowIdx) {
+			double deg = 0.0;
+			// sum of the row values
+			for(size_t colIdx = 0; colIdx < nbChan; ++colIdx) {
+				deg += laplacianMatrices[fftIdx](rowIdx, colIdx);
+			}
+			// degree value in the diagonal (inverted sign since we already inverted before)
+			laplacianMatrices[fftIdx](rowIdx, rowIdx) = -deg;
+		}
+	}
+
+	if(m_debugLogs) {
+		std::cout << "GraphLaplacian matrix for freq0  (pre denoising) :" << std::endl;
+		for (size_t chan0 = 0; chan0 < nbChan; ++chan0) {
+			std::cout << laplacianMatrices[0].row(chan0) << std::endl;
+		}
+		std::cout << std::endl;
+	}
+
+	// At this point, the GraphLaplacian matrix L=D-A is fully computed.
+	// Now, depending on the parameters, we can perform "denoising" by manipulating the eigenvectors & eigenvalues
+
+	if(m_denoising) {
+		// /!\ we assume the connectivity/adjacency matrix is symmetric and self adjoint.
+		// Its eigenvalues/vectors are real valued.
+
+		Eigen::SelfAdjointEigenSolver<Eigen::MatrixXd> esright(laplacianMatrices[0].rows());
+		Eigen::SelfAdjointEigenSolver<Eigen::MatrixXd> esleft(laplacianMatrices[0].cols());
+
+		for(size_t fftIdx = 0; fftIdx < fftSize; ++fftIdx) {
+
+			// compute the eigenValues and RIGHT eigenVectors of GraphLaplacian Matrix for freq fftIdx
+			// right eigenvectors are V as A.V = V.D (with D = diag(eigenvalues)) (=> A = V.D.V(-1))
+			esright.compute(laplacianMatrices[fftIdx]);
+
+			// compute the eigenValues and LEFT eigenVectors of GraphLaplacian Matrix for freq fftIdx
+			// left eigenvectors are W as W'.A = D.W' (with D = diag(eigenvalues)) (=> A = W'(-1).D.W')
+			esleft.compute(laplacianMatrices[fftIdx].transpose());
+
+			Eigen::MatrixXd D = esright.eigenvalues().asDiagonal();
+			Eigen::MatrixXd V = esright.eigenvectors();
+			Eigen::MatrixXd W = esleft.eigenvectors();
+
+			size_t nbEigen = V.rows();
+
+			// When using SelfAdjointEigenSolver, the eigenvalues (and associated vectors) are
+			// already sorted from smallest to largest value.
+
+			if(m_debugLogs && fftIdx == 0) {
+
+				std::cout << "GraphLaplacian : eigenvalues of the GraphLaplacian (freq0) : " << std::endl;
+				for (size_t idx = 0; idx < nbEigen; ++idx) {
+					std::cout << D.row(idx) << std::endl;
+				}
+				std::cout << std::endl;
+
+				std::cout << "GraphLaplacian : eigenvectors of the GraphLaplacian (freq0) : " << std::endl;
+				for (size_t idx = 0; idx < nbEigen; ++idx) {
+					std::cout << V.row(idx) << std::endl;
+				}
+				std::cout << std::endl;
+			}
+
+			// "Filter" the eigenvectors & values
+			Eigen::MatrixXd Dfilt;
+			Eigen::MatrixXd Vfilt;
+			Eigen::MatrixXd Wfilt;
+
+			switch(m_denoisingType) {
+
+				case EGraphLaplacianDenoisingType::LowPass :
+					// We keep the m_L first columns of V, W, and L first eigenvalues in D
+					{size_t L = std::min(m_denoisingSize, nbEigen);
+					Dfilt = D.block(0, 0, L, L);
+					Vfilt = V.block(0, 0, V.rows(), L);
+					Wfilt = W.block(0, 0, W.rows(), L);}
+					break;
+
+				case EGraphLaplacianDenoisingType::MidCut :
+					// We CUT the m_M "mid" columns of V, W, and m_M "mid" eigenvalues in D
+					if(m_denoisingSize < nbEigen) {
+						size_t nbCols = nbEigen - m_denoisingSize;
+						size_t nbColsLeft = (size_t)(nbCols/2);
+						size_t nbColsRight = nbCols - nbColsRight;
+						Dfilt = Eigen::MatrixXd::Zero(nbCols,nbCols);
+						Vfilt.resize(V.rows(), nbCols);
+						Wfilt.resize(W.rows(), nbCols);
+						size_t idxColNew = 0;
+						for(size_t idxCol = 0; idxCol < nbColsLeft; idxCol++) {
+							Dfilt(idxColNew,idxColNew) = D(idxCol,idxCol);
+							Vfilt.col(idxColNew) = V.col(idxCol);
+							Wfilt.col(idxColNew++) = W.col(idxCol);
+						}
+
+						for(size_t idxCol = V.cols()-nbColsRight; idxCol <  V.cols(); idxCol++) {
+							Dfilt(idxColNew,idxColNew) = D(idxCol,idxCol);
+							Vfilt.col(idxColNew) = V.col(idxCol);
+							Wfilt.col(idxColNew++) = W.col(idxCol);
+						}
+					} else {
+						// Everything is cut...!
+						// TODO : error message ?
+						Dfilt = Eigen::MatrixXd(0,0);
+						Vfilt = Eigen::MatrixXd(0,0);
+						Wfilt = Eigen::MatrixXd(0,0);
+					}
+					break;
+
+				case EGraphLaplacianDenoisingType::HighPass :
+					// We keep the m_H last columns of V, W, and m_H last eigenvalues in D
+					{size_t H = std::min(m_denoisingSize, nbEigen);
+					size_t idxStart = nbEigen-H;
+					Dfilt = D.block(idxStart, idxStart, H, H);
+					Vfilt = V.block(0, idxStart, V.rows(), H);
+					Wfilt = W.block(0, idxStart, W.rows(), H);}
+					break;
+
+				default:
+					// Do something if parameter values (L/M/H) conflict with eigen sizes?
+					break;
+			}
+
+			// Reconstruct the laplacian matrix L = W.D.V'
+			laplacianMatrices[fftIdx] = Wfilt * Dfilt * Vfilt.transpose();
+
+			// Reconstruct the adjacency matrix A = -(L-D)
+			if(m_outputType == EGraphLaplacianOutputType::AdjacencyMatrix) {
+				for(size_t idxRow = 0; idxRow < laplacianMatrices[fftIdx].rows(); idxRow++) {
+					for(size_t idxCol = 0; idxCol < laplacianMatrices[fftIdx].cols(); idxCol++) {
+						adjacencyMatrices[fftIdx](idxRow,idxCol) = - (laplacianMatrices[fftIdx](idxRow,idxCol));
+					}
+				}
+				for(size_t idxRow = 0; idxRow < adjacencyMatrices[fftIdx].rows(); idxRow++) {
+					adjacencyMatrices[fftIdx](idxRow,idxRow) = 0.0;
+				}
+			}
+
+		}
+
+		if(m_debugLogs) {
+			std::cout << "GraphLaplacian matrix for freq0 (reconstructed post denoising) :" << std::endl;
+			for (size_t idx = 0; idx < laplacianMatrices[0].rows(); ++idx) {
+				std::cout << laplacianMatrices[0].row(idx) << std::endl;
+			}
+			std::cout << std::endl;
+		}
+
+	}
+
+	// Output : GraphLaplacian or Adjacency
+	// Conversion from eigen format to IMatrix format
+	// freq x N x N x 2 (real/imag)
+
+	if (out.getDimensionCount() != 3
+				|| out.getDimensionSize(0) != fftSize
+				|| out.getDimensionSize(1) != nbChan
+				|| out.getDimensionSize(2) != nbChan)
+	{
+		out.setDimensionCount(3);
+		out.setDimensionSize(0, fftSize);
+		out.setDimensionSize(1, nbChan);
+		out.setDimensionSize(2, nbChan);
+	}
+
+	double* outbuffer = out.getBuffer();
+	size_t outidx = 0;
+
+	if(m_outputType == EGraphLaplacianOutputType::GraphLaplacianMatrix) {
+
+		for (size_t fftIdx = 0; fftIdx < fftSize; ++fftIdx) {
+			for (size_t chan0 = 0; chan0 < nbChan; ++chan0) {
+				for (size_t chan1 = 0; chan1 < nbChan; ++chan1) {
+					outbuffer[outidx++] = laplacianMatrices[fftIdx](chan0, chan1);
+				}
+			}
+		}
+
+	} else {
+		if(m_denoising) {
+			for (size_t fftIdx = 0; fftIdx < fftSize; ++fftIdx) {
+				for (size_t chan0 = 0; chan0 < nbChan; ++chan0) {
+					for (size_t chan1 = 0; chan1 < nbChan; ++chan1) {
+						outbuffer[outidx++] = adjacencyMatrices[fftIdx](chan0, chan1);
+					}
+				}
+			}
+		} else {
+			// output = input but you computed lots of things that will not be used.
+			// why would you do this ?
+			// TODO : warning ?
+			for (size_t inIdx = 0; inIdx < in.getBufferElementCount(); ++inIdx) {
+				outbuffer[outidx++] = inbuffer[inIdx];
+			}
+		}
+
+	}
+
+	return true;
+}
diff --git a/plugins/processing/signal-processing/src/algorithms/connectivity/graphLaplacian.h b/plugins/processing/signal-processing/src/algorithms/connectivity/graphLaplacian.h
new file mode 100644
index 000000000..454f7d984
--- /dev/null
+++ b/plugins/processing/signal-processing/src/algorithms/connectivity/graphLaplacian.h
@@ -0,0 +1,77 @@
+///-------------------------------------------------------------------------------------------------
+///
+/// \file graphLaplacian.hpp
+/// \brief Graph Laplacian & denoising for BCI connectivity.
+/// \author Arthur Desbois (Inria).
+/// \version 1.0
+/// \date 28 Jan 2021
+/// \copyright <a href="https://choosealicense.com/licenses/agpl-3.0/">GNU Affero General Public License v3.0</a>.
+/// \remarks
+///-------------------------------------------------------------------------------------------------
+
+#pragma once
+#include <string>
+#include <openvibe/ov_all.h>
+#include <Eigen/Dense>
+
+enum class EGraphLaplacianOutputType
+{
+	GraphLaplacianMatrix,
+	AdjacencyMatrix
+};
+
+/// <summary>	Convert GraphLaplacian output to string. </summary>
+/// <param name="outputType">	Output type. </param>
+/// <returns>	std::string </returns>
+inline std::string toString(const EGraphLaplacianOutputType type)
+{
+	switch (type)
+	{
+		case EGraphLaplacianOutputType::GraphLaplacianMatrix: return "GraphLaplacian Matrix";
+		case EGraphLaplacianOutputType::AdjacencyMatrix: return "Adjacency Matrix";
+	}
+}
+
+enum class EGraphLaplacianDenoisingType
+{
+	LowPass,
+	MidCut,
+	HighPass
+};
+
+/// <summary>	Convert GraphLaplacian denoising type to string. </summary>
+/// <param name="outputType">	Denoising type. </param>
+/// <returns>	std::string </returns>
+inline std::string toString(const EGraphLaplacianDenoisingType type)
+{
+	switch (type)
+	{
+		case EGraphLaplacianDenoisingType::LowPass: return "Low Pass (lowest eigenvalues)";
+		case EGraphLaplacianDenoisingType::MidCut: return "Mid cut (lowest & highest eigenvalues)";
+		case EGraphLaplacianDenoisingType::HighPass: return "High Pass (highest eigenvalues)";
+	}
+}
+
+class GraphLaplacian
+{
+public:
+	GraphLaplacian() {};
+	~GraphLaplacian() {};
+
+	bool initialize(EGraphLaplacianDenoisingType denoisingType, size_t denoisingSize, bool denoising, bool debugLogs, EGraphLaplacianOutputType outputType);
+
+	bool process(const OpenViBE::IMatrix& in, OpenViBE::IMatrix& out);
+
+private:
+
+	size_t m_denoisingSize = 0;
+	bool m_denoising = false;
+
+	EGraphLaplacianOutputType m_outputType = EGraphLaplacianOutputType::GraphLaplacianMatrix;
+	EGraphLaplacianDenoisingType m_denoisingType = EGraphLaplacianDenoisingType::LowPass;
+
+	bool m_debugLogs = false;
+
+};
+
+
diff --git a/plugins/processing/signal-processing/src/box-algorithms/connectivity/CBoxAlgorithmGraphLaplacian.cpp b/plugins/processing/signal-processing/src/box-algorithms/connectivity/CBoxAlgorithmGraphLaplacian.cpp
new file mode 100644
index 000000000..1f5faa522
--- /dev/null
+++ b/plugins/processing/signal-processing/src/box-algorithms/connectivity/CBoxAlgorithmGraphLaplacian.cpp
@@ -0,0 +1,110 @@
+#include "CBoxAlgorithmGraphLaplacian.h"
+
+using namespace OpenViBE;
+using namespace /*OpenViBE::*/Kernel;
+using namespace /*OpenViBE::*/Plugins;
+using namespace /*OpenViBE::Plugins::*/SignalProcessing;
+
+bool CBoxAlgorithmGraphLaplacian::initialize()
+{
+	m_i0MatrixCodec.initialize(*this, 0);
+	m_o0MatrixCodec.initialize(*this, 0);
+	
+	m_iMatrix = m_i0MatrixCodec.getOutputMatrix();
+	m_oMatrix = m_o0MatrixCodec.getInputMatrix();
+
+	// Settings
+	m_outputType 	= EGraphLaplacianOutputType(uint64_t(FSettingValueAutoCast(*this->getBoxAlgorithmContext(), 0)));
+	m_denoisingType = EGraphLaplacianDenoisingType(uint64_t(FSettingValueAutoCast(*this->getBoxAlgorithmContext(), 1)));
+	m_denoisingSize	= FSettingValueAutoCast(*this->getBoxAlgorithmContext(), 2);
+	m_denoising	= FSettingValueAutoCast(*this->getBoxAlgorithmContext(), 3);
+	m_debugLogs	= FSettingValueAutoCast(*this->getBoxAlgorithmContext(), 4);
+
+	laplacian.initialize(m_denoisingType, m_denoisingSize, m_denoising, m_debugLogs, m_outputType);
+
+	if(m_denoising) {
+		if(m_denoisingSize <= 0) OV_ERROR_KRF("L, M, H must be positive", Kernel::ErrorType::BadInput);
+		if( m_denoisingType ==  EGraphLaplacianDenoisingType::MidCut ) OV_ERROR_KRF("Mid-cut not yet available", Kernel::ErrorType::BadInput);
+	}
+
+	return true;
+}
+/*******************************************************************************/
+
+bool CBoxAlgorithmGraphLaplacian::uninitialize()
+{
+	m_i0MatrixCodec.uninitialize();
+	m_o0MatrixCodec.uninitialize();
+	return true;
+}
+
+bool CBoxAlgorithmGraphLaplacian::processInput(const size_t )
+{
+	getBoxAlgorithmContext()->markAlgorithmAsReadyToProcess();
+	return true;
+}
+
+/*******************************************************************************/
+
+
+bool CBoxAlgorithmGraphLaplacian::process()
+{
+	const IBox& staticBoxContext=this->getStaticBoxContext();
+	IBoxIO& boxContext = this->getDynamicBoxContext();
+
+	for (size_t i = 0; i < boxContext.getInputChunkCount(0); ++i)
+	{
+		m_i0MatrixCodec.decode(i);
+
+		const uint64_t tStart = boxContext.getInputChunkStartTime(0, i);	// Time Code Chunk Start
+		const uint64_t tEnd   = boxContext.getInputChunkEndTime(0, i);		// Time Code Chunk End
+
+		if(m_i0MatrixCodec.isHeaderReceived()) {// Header received. This happens only once when pressing "play".
+
+			IMatrix* matrix = m_i0MatrixCodec.getOutputMatrix(); // the StreamedMatrix of samples.
+
+			// OUTPUT MATRIX INIT
+			m_oMatrix->setDimensionCount(matrix->getDimensionCount());
+			m_oMatrix->setDimensionSize(0, matrix->getDimensionSize(0)); // frequencies
+			m_oMatrix->setDimensionSize(1, matrix->getDimensionSize(1)); // channels
+			m_oMatrix->setDimensionSize(2, matrix->getDimensionSize(2)); // channels
+			Toolkit::Matrix::clearContent(*m_oMatrix);
+
+			m_o0MatrixCodec.encodeHeader(); // Pass the header to the next boxes
+
+			if(m_debugLogs) {
+				this->getLogManager() << LogLevel_Info << "Header buffer processed\n";
+			}
+
+
+		}
+		else if(m_i0MatrixCodec.isBufferReceived())	{
+
+			IMatrix* matrix = m_i0MatrixCodec.getOutputMatrix(); // the StreamedMatrix of samples.
+
+			if(m_debugLogs) {
+				this->getLogManager() << LogLevel_Info << "Matrix received : " << matrix->getDimensionSize(0) << "x" << matrix->getDimensionSize(1) << "x" << matrix->getDimensionSize(2)  << "\n";
+			}
+
+			OV_ERROR_UNLESS_KRF(laplacian.process(*matrix, *m_oMatrix),	"GraphLaplacian error", ErrorType::BadProcessing);
+
+			if(m_debugLogs) {
+				this->getLogManager() << LogLevel_Info << "GraphLaplacian computed : " << m_oMatrix->getDimensionSize(0) << "x" << m_oMatrix->getDimensionSize(1) << "x" << m_oMatrix->getDimensionSize(2) << "\n";
+			}
+
+			m_o0MatrixCodec.encodeBuffer();
+
+
+		}
+		else if(m_i0MatrixCodec.isEndReceived()) {
+			m_o0MatrixCodec.encodeEnd();
+		}
+
+		// send the output chunk. The dates are the same as the input chunk
+		boxContext.markOutputAsReadyToSend(0, tStart, tEnd);
+
+	}
+
+	return true;
+}
+
diff --git a/plugins/processing/signal-processing/src/box-algorithms/connectivity/CBoxAlgorithmGraphLaplacian.h b/plugins/processing/signal-processing/src/box-algorithms/connectivity/CBoxAlgorithmGraphLaplacian.h
new file mode 100644
index 000000000..4de579960
--- /dev/null
+++ b/plugins/processing/signal-processing/src/box-algorithms/connectivity/CBoxAlgorithmGraphLaplacian.h
@@ -0,0 +1,109 @@
+///-------------------------------------------------------------------------------------------------
+/// 
+/// \file CBoxAlgorithmGraphLaplacian.h
+/// \brief Classes of the Box GraphLaplacian
+/// \author Arthur DESBOIS (INRIA).
+/// \version 0.0.1.
+/// \date Thu Jan 28 16:18:49 2021.
+/// \copyright <a href="https://choosealicense.com/licenses/agpl-3.0/">GNU Affero General Public License v3.0</a>.
+/// 
+///-------------------------------------------------------------------------------------------------
+#pragma once
+
+#include "ovp_defines.h"
+
+#include <openvibe/ov_all.h>
+#include <toolkit/ovtk_all.h>
+#include <Eigen/Dense>
+#include "graphLaplacian.h"
+
+
+#define OV_AttributeId_Box_FlagIsUnstable				OpenViBE::CIdentifier(0x666FFFFF, 0x666FFFFF)
+
+namespace OpenViBE
+{
+	namespace Plugins
+	{
+		namespace SignalProcessing
+		{
+			/// <summary> The class CBoxAlgorithmGraphLaplacian describes the box. </summary>
+			class CBoxAlgorithmGraphLaplacian final : virtual public Toolkit::TBoxAlgorithm<IBoxAlgorithm>
+			{
+			public:
+				void release() override { delete this; }
+
+				bool initialize() override;
+				bool uninitialize() override;
+
+				bool processInput(const size_t index) override;
+				bool process() override;
+
+				// As we do with any class in openvibe, we use the macro below to associate this box to an unique identifier. 
+				// The inheritance information is also made available, as we provide the superclass Toolkit::TBoxAlgorithm < IBoxAlgorithm >
+				_IsDerivedFromClass_Final_(Toolkit::TBoxAlgorithm<IBoxAlgorithm>, OVP_ClassId_BoxAlgorithm_GraphLaplacian)
+
+			protected:
+
+				// Algo class instance
+				GraphLaplacian laplacian;
+
+				// Codecs
+				Toolkit::TStreamedMatrixDecoder<CBoxAlgorithmGraphLaplacian> m_i0MatrixCodec;
+				Toolkit::TStreamedMatrixEncoder<CBoxAlgorithmGraphLaplacian> m_o0MatrixCodec;
+
+				// Matrices
+				IMatrix* m_iMatrix = nullptr;		// Input Matrix pointer
+				IMatrix* m_oMatrix = nullptr;		// Output Matrix pointer
+
+				// Settings
+				EGraphLaplacianOutputType m_outputType = EGraphLaplacianOutputType::GraphLaplacianMatrix;
+				EGraphLaplacianDenoisingType m_denoisingType = EGraphLaplacianDenoisingType::LowPass;
+				size_t m_denoisingSize = 0;
+
+				bool m_denoising = false;	// enabling/disabling denoising TODO : Keep ?
+
+				bool m_debugLogs = false;
+
+				Kernel::ELogLevel m_logLevel    = Kernel::LogLevel_Info;
+			};
+
+			/// <summary> Descriptor of the box GraphLaplacian. </summary>
+			class CBoxAlgorithmGraphLaplacianDesc final : virtual public IBoxAlgorithmDesc
+			{
+			public:
+
+				void release() override { }
+
+				CString getName() const override { return CString("GraphLaplacian"); }
+				CString getAuthorName() const override { return CString("Arthur DESBOIS"); }
+				CString getAuthorCompanyName() const override { return CString("INRIA"); }
+				CString getShortDescription() const override { return CString("GraphLaplacian / denoising for BCI connectivity"); }
+				CString getDetailedDescription() const override { return CString("Computes laplacian & denoising process"); }
+				CString getCategory() const override { return CString("Signal processing/Connectivity"); }
+				CString getVersion() const override { return CString("0.0.1"); }
+				CString getStockItemName() const override { return CString("gtk-zoom-out"); }
+
+				CIdentifier getCreatedClass() const override { return OVP_ClassId_BoxAlgorithm_GraphLaplacian; }
+				IPluginObject* create() override { return new CBoxAlgorithmGraphLaplacian; }
+
+				bool getBoxPrototype(Kernel::IBoxProto& prototype) const override
+				{
+					prototype.addInput("Input matrix",OV_TypeId_StreamedMatrix);
+					prototype.addOutput("Output matrix",OV_TypeId_StreamedMatrix);
+
+					prototype.addSetting("Output Type", OV_TypeId_GraphLaplacian_OutputType, toString(EGraphLaplacianOutputType::GraphLaplacianMatrix).c_str());
+					prototype.addSetting("Denoising Type", OV_TypeId_GraphLaplacian_DenoisingType, toString(EGraphLaplacianDenoisingType::LowPass).c_str());
+					prototype.addSetting("Denoising Size",OV_TypeId_Integer, "0");
+					prototype.addSetting("Denoising activated",OV_TypeId_Boolean,"false");
+
+					prototype.addSetting("DEBUG LOGS",OV_TypeId_Boolean,"false");
+
+					prototype.addFlag(OV_AttributeId_Box_FlagIsUnstable);
+
+					return true;
+				}
+				_IsDerivedFromClass_Final_(IBoxAlgorithmDesc, OVP_ClassId_BoxAlgorithm_GraphLaplacianDesc)
+			};
+		}  // namespace SignalProcessing
+	}  // namespace Plugins
+}  // namespace OpenViBE
diff --git a/plugins/processing/signal-processing/src/ovp_defines.h b/plugins/processing/signal-processing/src/ovp_defines.h
index e2e45757f..4d0281196 100755
--- a/plugins/processing/signal-processing/src/ovp_defines.h
+++ b/plugins/processing/signal-processing/src/ovp_defines.h
@@ -58,6 +58,8 @@
 #define OVP_ClassId_BoxAlgorithm_IFFTboxDesc						OpenViBE::CIdentifier(0xD533E997, 0x4AFD2423)
 #define OVP_ClassId_BoxAlgorithm_Matrix3dTo2d						OpenViBE::CIdentifier(0x3ab2b81e, 0x73ef01a5)
 #define OVP_ClassId_BoxAlgorithm_Matrix3dTo2dDesc					OpenViBE::CIdentifier(0xb9099590, 0xae33d758)
+#define OVP_ClassId_BoxAlgorithm_GraphLaplacian						OpenViBE::CIdentifier(0x35202ffc, 0xa6a312cc)
+#define OVP_ClassId_BoxAlgorithm_GraphLaplacianDesc					OpenViBE::CIdentifier(0x58b723d7, 0xfb32fdf7)
 
 // Type definitions
 //---------------------------------------------------------------------------------------------------
@@ -155,4 +157,7 @@ enum class EWaveletLevel { L1, L2, L3, L4, L5 };
 #define OVP_TypeId_Connectivity_Metric                                                      OpenViBE::CIdentifier(0x9188339d, 0x5da83a84)
 #define OV_TypeId_ConnectivityMeasure_WindowMethod                                          OpenViBE::CIdentifier(0x8815bfa7, 0x557b102f)
 
+#define OV_TypeId_GraphLaplacian_OutputType                 								OpenViBE::CIdentifier(0x8f5633a5, 0x6cc7f6e9)
+#define OV_TypeId_GraphLaplacian_DenoisingType               								OpenViBE::CIdentifier(0xbe346f0b, 0xe0ee4117)
+
 #define OV_AttributeId_Box_FlagIsUnstable													OpenViBE::CIdentifier(0x666FFFFF, 0x666FFFFF)
diff --git a/plugins/processing/signal-processing/src/ovp_main.cpp b/plugins/processing/signal-processing/src/ovp_main.cpp
index 12d8bd202..8ad813570 100755
--- a/plugins/processing/signal-processing/src/ovp_main.cpp
+++ b/plugins/processing/signal-processing/src/ovp_main.cpp
@@ -28,6 +28,8 @@
 
 #include "box-algorithms/CBoxAlgorithmMatrix3dTo2d.hpp"
 
+#include "box-algorithms/connectivity/CBoxAlgorithmGraphLaplacian.h"
+
 namespace OpenViBE {
 namespace Plugins {
 namespace SignalProcessing {
@@ -39,8 +41,9 @@ OVP_Declare_Begin()
 	//OVP_Declare_New(CBoxAlgorithmNullDesc)
 	OVP_Declare_New(CBoxAlgorithmERSPAverageDesc)
 	OVP_Declare_New(CBoxAlgorithmQuadraticFormDesc)
-	OVP_Declare_New(CEpochVarianceDesc);
+	OVP_Declare_New(CEpochVarianceDesc)
 	OVP_Declare_New(CBoxAlgorithmMatrix3dTo2dDesc)
+	OVP_Declare_New(CBoxAlgorithmGraphLaplacianDesc)
 
 #if defined TARGET_HAS_ThirdPartyEIGEN
 	OVP_Declare_New(CBoxAlgorithmARCoefficientsDesc);
@@ -159,6 +162,17 @@ OVP_Declare_Begin()
 	context.getTypeManager().registerEnumerationEntry(OVP_TypeId_Connectivity_Metric, toString(EConnectMetric::MagnitudeSquaredCoherence).c_str(), size_t(EConnectMetric::MagnitudeSquaredCoherence));
 	context.getTypeManager().registerEnumerationEntry(OVP_TypeId_Connectivity_Metric, toString(EConnectMetric::ImaginaryCoherence).c_str(), size_t(EConnectMetric::ImaginaryCoherence));
 	context.getTypeManager().registerEnumerationEntry(OVP_TypeId_Connectivity_Metric, toString(EConnectMetric::AbsImaginaryCoherence).c_str(), size_t(EConnectMetric::AbsImaginaryCoherence));
+
+	context.getTypeManager().registerEnumerationType(OV_TypeId_GraphLaplacian_OutputType, "Output Type");
+	context.getTypeManager().registerEnumerationEntry(OV_TypeId_GraphLaplacian_OutputType, toString(EGraphLaplacianOutputType::GraphLaplacianMatrix).c_str(), size_t(EGraphLaplacianOutputType::GraphLaplacianMatrix));
+	context.getTypeManager().registerEnumerationEntry(OV_TypeId_GraphLaplacian_OutputType, toString(EGraphLaplacianOutputType::AdjacencyMatrix).c_str(), size_t(EGraphLaplacianOutputType::AdjacencyMatrix));
+
+	context.getTypeManager().registerEnumerationType(OV_TypeId_GraphLaplacian_DenoisingType, "Denoising Type");
+	context.getTypeManager().registerEnumerationEntry(OV_TypeId_GraphLaplacian_DenoisingType, toString(EGraphLaplacianDenoisingType::LowPass).c_str(), size_t(EGraphLaplacianDenoisingType::LowPass));
+	context.getTypeManager().registerEnumerationEntry(OV_TypeId_GraphLaplacian_DenoisingType, toString(EGraphLaplacianDenoisingType::MidCut).c_str(), size_t(EGraphLaplacianDenoisingType::MidCut));
+	context.getTypeManager().registerEnumerationEntry(OV_TypeId_GraphLaplacian_DenoisingType, toString(EGraphLaplacianDenoisingType::HighPass).c_str(), size_t(EGraphLaplacianDenoisingType::HighPass));
+
+
 #endif
 OVP_Declare_End()
 
-- 
GitLab