v2.0.0
Loading...
Searching...
No Matches
inv_trap_music.cpp
Go to the documentation of this file.
1//=============================================================================================================
19
20//=============================================================================================================
21// INCLUDES
22//=============================================================================================================
23
24#include "inv_trap_music.h"
25
26//=============================================================================================================
27// EIGEN INCLUDES
28//=============================================================================================================
29
30#include <Eigen/SVD>
31#include <Eigen/Dense>
32
33//=============================================================================================================
34// QT INCLUDES
35//=============================================================================================================
36
37#include <QDebug>
38
39//=============================================================================================================
40// STD INCLUDES
41//=============================================================================================================
42
43#include <cmath>
44
45//=============================================================================================================
46// USED NAMESPACES
47//=============================================================================================================
48
49using namespace INVLIB;
50using namespace Eigen;
51
52//=============================================================================================================
53// DEFINE MEMBER METHODS
54//=============================================================================================================
55
56InvTrapMusic::InvTrapMusic(int iMaxSources, double dThreshold)
57: m_iMaxSources(iMaxSources)
58, m_dThreshold(dThreshold)
59{
60}
61
62//=============================================================================================================
63
64QList<TrapMusicDipole> InvTrapMusic::compute(const MatrixXd& matLeadField,
65 const MatrixXd& matData,
66 const MatrixXd& matSourcePos,
67 int iNOrient) const
68{
69 QList<TrapMusicDipole> dipoles;
70
71 const int nCh = static_cast<int>(matLeadField.rows());
72 const int nSrc = static_cast<int>(matSourcePos.rows());
73
74 if (nCh == 0 || matData.rows() != nCh || matLeadField.cols() != static_cast<Eigen::Index>(nSrc) * iNOrient) {
75 qWarning() << "[InvTrapMusic::compute] Dimension mismatch.";
76 return dipoles;
77 }
78
79 // Estimate signal subspace dimension from SVD of data
80 JacobiSVD<MatrixXd> dataSvd(matData, ComputeThinU);
81 const VectorXd& singVals = dataSvd.singularValues();
82
83 // Determine signal subspace dimension: look for a significant gap in singular values
84 int nSignal = 1;
85 for (int i = 1; i < singVals.size(); ++i) {
86 if (singVals[i] < singVals[0] * 0.05) // Below 5% of max
87 break;
88 ++nSignal;
89 }
90 nSignal = std::max(nSignal, m_iMaxSources);
91 nSignal = std::min(nSignal, static_cast<int>(std::min(dataSvd.matrixU().cols(), static_cast<Index>(nCh / 2))));
92
93 // Signal subspace: U_s columns
94 MatrixXd signalSubspace = dataSvd.matrixU().leftCols(nSignal);
95
96 // Topographies G_k o_k of the sources found so far; the RAP step projects out their span.
97 MatrixXd found(nCh, 0);
98 MatrixXd projector = MatrixXd::Identity(nCh, nCh);
99
100 for (int iter = 0; iter < m_iMaxSources; ++iter) {
101 MatrixXd projLF = projector * matLeadField;
102 MatrixXd projSS = projector * signalSubspace;
103
104 // Truncation step: iteration k keeps n - k + 1 dimensions of the projected signal subspace
105 const int ssDim = static_cast<int>(projSS.cols()) - iter;
106 if (ssDim < 1)
107 break;
108 JacobiSVD<MatrixXd> ssSvd(projSS, ComputeThinU);
109 const MatrixXd truncSS = ssSvd.matrixU().leftCols(ssDim);
110
111 const VectorXd correlations = scanCorrelations(projLF, truncSS, iNOrient);
112 Index bestIdx = 0;
113 const double bestCorr = correlations.maxCoeff(&bestIdx);
114 if (bestCorr < m_dThreshold)
115 break;
116
117 TrapMusicDipole dipole;
118 dipole.sourceIdx = static_cast<int>(bestIdx);
119 dipole.correlation = bestCorr;
120 dipole.position = matSourcePos.row(bestIdx).transpose();
121
122 // The orientation maximises the correlation of the projected topography P G_k o with the subspace.
123 const int colStart = static_cast<int>(bestIdx) * iNOrient;
124 VectorXd orient = VectorXd::Ones(1);
125 if (iNOrient > 1) {
126 const MatrixXd projSrc = projLF.middleCols(colStart, iNOrient);
127 // Maximise ||U^T P G o|| / ||P G o||, a generalised eigenproblem with the Gram matrix of P G.
128 const MatrixXd gram = projSrc.transpose() * projSrc;
129 const MatrixXd fit = projSrc.transpose() * truncSS * truncSS.transpose() * projSrc;
130 GeneralizedSelfAdjointEigenSolver<MatrixXd> ges(fit, gram);
131 orient = ges.eigenvectors().col(iNOrient - 1);
132 orient.normalize();
133 dipole.orientation = Vector3d(orient(0), orient(1), orient(2));
134 } else {
135 dipole.orientation = Vector3d(0, 0, 1);
136 }
137 dipoles.append(dipole);
138
139 // RAP step: P = I - A A^+ with A the found topographies
140 found.conservativeResize(NoChange, found.cols() + 1);
141 found.col(found.cols() - 1) = matLeadField.middleCols(colStart, iNOrient) * orient;
142 const MatrixXd q = found.householderQr().householderQ() * MatrixXd::Identity(nCh, found.cols());
143 projector = MatrixXd::Identity(nCh, nCh) - q * q.transpose();
144 }
145
146 return dipoles;
147}
148
149//=============================================================================================================
150
151VectorXd InvTrapMusic::scanCorrelations(const MatrixXd& matLeadField,
152 const MatrixXd& matSignalSubspace,
153 int iNOrient)
154{
155 const int nSrcTotal = static_cast<int>(matLeadField.cols()) / iNOrient;
156 VectorXd correlations(nSrcTotal);
157
158 for (int s = 0; s < nSrcTotal; ++s) {
159 const MatrixXd G_s = matLeadField.middleCols(s * iNOrient, iNOrient);
160 if (G_s.norm() <= 1e-15) {
161 correlations[s] = 0.0;
162 continue;
163 }
164 // Subspace correlation (Mosher & Leahy): largest singular value of U_s^T orth(G_s), the best
165 // orientation's cosine with the signal subspace.
166 JacobiSVD<MatrixXd> gSvd(G_s, ComputeThinU);
167 const VectorXd& sv = gSvd.singularValues();
168 int rank = 0;
169 while (rank < sv.size() && sv(rank) > 1e-10 * sv(0))
170 ++rank;
171 const MatrixXd basis = gSvd.matrixU().leftCols(rank);
172 correlations[s] = JacobiSVD<MatrixXd>(matSignalSubspace.transpose() * basis).singularValues()(0);
173 }
174
175 return correlations;
176}
Truncated RAP-MUSIC (TRAP-MUSIC) source-localisation algorithm — sub-space truncation per iteration f...
Inverse source estimation (MNE, dSPM, sLORETA, dipole fitting).
Result of a TRAP-MUSIC source scan.
Eigen::Vector3d position
Eigen::Vector3d orientation
static Eigen::VectorXd scanCorrelations(const Eigen::MatrixXd &matLeadField, const Eigen::MatrixXd &matSignalSubspace, int iNOrient)
Compute the MUSIC-type subspace correlation for all source locations.
InvTrapMusic(int iMaxSources=5, double dThreshold=0.85)
Construct TRAP-MUSIC scanner.
QList< TrapMusicDipole > compute(const Eigen::MatrixXd &matLeadField, const Eigen::MatrixXd &matData, const Eigen::MatrixXd &matSourcePos, int iNOrient=3) const
Compute TRAP-MUSIC source localization.