40#include <Eigen/Eigenvalues>
58: m_inverseOperator(p_inverseOperator)
60, m_iELoretaMaxIter(20)
62, m_bELoretaForceEqual(false)
72: m_inverseOperator(p_inverseOperator)
74, m_iELoretaMaxIter(20)
76, m_bELoretaForceEqual(false)
90 qint32 nave = p_fiffEvoked.
nave;
92 if(!m_inverseOperator.check_ch_names(p_fiffEvoked.
info)) {
93 qWarning(
"Channel name check failed.");
104 qInfo(
"Picked %d channels from the data", t_fiffEvoked.
info.
nchan);
107 float tmin = p_fiffEvoked.
times[0];
119 qWarning(
"InvMinimumNorm::calculateInverse - Inverse not setup -> call doInverseSetup first!");
123 if(K.cols() != data.rows()) {
124 qWarning() <<
"InvMinimumNorm::calculateInverse - Dimension mismatch between K.cols() and data.rows() -" << K.cols() <<
"and" << data.rows();
128 MatrixXd sol = K * data;
132 qInfo(
"combining the current components...");
134 MatrixXd sol1(sol.rows()/3,sol.cols());
135 for(qint32 i = 0; i < sol.cols(); ++i)
138 sol1.block(0,i,sol.rows()/3,1) = tmp.cwiseSqrt();
140 sol.resize(sol1.rows(),sol1.cols());
147 sol = inv.noisenorm*sol;
151 qInfo(
"(sLORETA)...");
152 sol = inv.noisenorm*sol;
156 qInfo(
"(eLORETA)...");
157 sol = inv.noisenorm*sol;
162 VectorXi p_vecVertices(inv.src[0].vertno.size() + inv.src[1].vertno.size());
163 p_vecVertices << inv.src[0].vertno, inv.src[1].vertno;
169 return InvSourceEstimate(sol, p_vecVertices, tmin, tstep);
179 inv = m_inverseOperator.prepare_inverse_operator(nave, m_fLambda, m_bdSPM || m_beLoreta, m_bsLORETA);
186 qInfo(
"Computing inverse...");
187 inv.assemble_kernel(label, m_sMethod, pick_normal, K, noise_norm, vertno);
189 std::cout <<
"K " << K.rows() <<
" x " << K.cols() << std::endl;
198 return "Minimum Norm Estimate";
205 return m_inverseOperator.src;
212 if(method.compare(
"MNE") == 0)
214 else if(method.compare(
"dSPM") == 0)
216 else if(method.compare(
"sLORETA") == 0)
218 else if(method.compare(
"eLORETA") == 0)
222 qWarning(
"Method not recognized!");
227 qInfo(
"\tSet minimum norm method to %s.", method.toUtf8().constData());
234 int nActive = (
dSPM ? 1 : 0) + (
sLORETA ? 1 : 0) + (eLoreta ? 1 : 0);
237 qWarning(
"Only one method can be active at a time! - Activating dSPM");
245 m_beLoreta = eLoreta;
248 m_sMethod = QString(
"dSPM");
250 m_sMethod = QString(
"sLORETA");
252 m_sMethod = QString(
"eLORETA");
254 m_sMethod = QString(
"MNE");
268 m_iELoretaMaxIter = maxIter;
270 m_bELoretaForceEqual = forceEqual;
275void InvMinimumNorm::computeELoreta()
286 qInfo(
"Computing eLORETA source weights...");
293 qWarning(
"InvMinimumNorm::computeELoreta - Inverse operator missing eigen structures!");
298 const MatrixXd &eigenLeads = inv.
eigen_leads->data;
299 const VectorXd &sing = inv.
sing;
303 MatrixXd G = eigenFields * sing.asDiagonal() * eigenLeads.transpose();
305 const int nChan =
static_cast<int>(G.rows());
307 const int nOrient =
static_cast<int>(G.cols()) / nSrc;
309 if(nOrient != 1 && nOrient != 3) {
310 qWarning(
"InvMinimumNorm::computeELoreta - Unexpected n_orient: %d", nOrient);
315 if(inv.source_cov && inv.source_cov->data.size() > 0) {
316 for(
int i = 0; i < G.cols(); ++i) {
317 double sc = inv.source_cov->data(i, 0);
318 if(sc > 0) G.col(i) /= std::sqrt(sc);
323 VectorXd sourceStd = VectorXd::Ones(G.cols());
324 if(inv.orient_prior && inv.orient_prior->data.size() > 0) {
325 for(
int i = 0; i < G.cols() && i < inv.orient_prior->data.rows(); ++i) {
326 double op = inv.orient_prior->data(i, 0);
327 if(op > 0) sourceStd(i) *= std::sqrt(op);
330 for(
int i = 0; i < G.cols(); ++i) {
331 G.col(i) *= sourceStd(i);
336 for(
int i = 0; i < sing.size(); ++i) {
337 if(std::abs(sing(i)) > 1e-10 * sing(0))
341 double lambda2 =
static_cast<double>(m_fLambda);
346 const bool useScalar = (nOrient == 1 || m_bELoretaForceEqual);
349 std::vector<Matrix3d> R_mat;
352 R_vec = VectorXd::Ones(
static_cast<Eigen::Index
>(nSrc) * nOrient);
354 for(
int i = 0; i < R_vec.size(); ++i) {
355 R_vec(i) *= sourceStd(i) * sourceStd(i);
359 for(
int s = 0; s < nSrc; ++s) {
360 R_mat[s] = Matrix3d::Identity();
362 for(
int a = 0; a < 3; ++a) {
363 for(
int b = 0; b < 3; ++b) {
364 R_mat[s](a, b) *= sourceStd(s * 3 + a) * sourceStd(s * 3 + b);
370 qInfo(
" Fitting up to %d iterations (n_orient=%d, force_equal=%s)...",
371 m_iELoretaMaxIter, nOrient, m_bELoretaForceEqual ?
"true" :
"false");
374 auto computeGRGt = [&]() -> MatrixXd {
379 for(
int i = 0; i < G.cols(); ++i) {
380 GR.col(i) *= R_vec(i);
382 GRGt = GR * G.transpose();
385 MatrixXd RGt = MatrixXd::Zero(nSrc * 3, nChan);
386 for(
int s = 0; s < nSrc; ++s) {
388 MatrixXd Gs = G.middleCols(s * 3, 3);
390 RGt.middleRows(s * 3, 3) = R_mat[s] * Gs.transpose();
395 double trace = GRGt.trace();
396 double norm = trace /
static_cast<double>(nNonZero);
399 if(useScalar) R_vec /= norm;
400 else for(
auto &Rm : R_mat) Rm /= norm;
405 MatrixXd GRGt = computeGRGt();
407 for(
int kk = 0; kk < m_iELoretaMaxIter; ++kk) {
409 SelfAdjointEigenSolver<MatrixXd> eig(GRGt);
410 VectorXd s = eig.eigenvalues().cwiseAbs();
411 MatrixXd u = eig.eigenvectors();
415 std::vector<int> idx(s.size());
416 std::iota(idx.begin(), idx.end(), 0);
417 std::sort(idx.begin(), idx.end(), [&s](
int a,
int b) { return s(a) > s(b); });
419 MatrixXd uKeep(nChan, nNonZero);
420 VectorXd sKeep(nNonZero);
421 for(
int i = 0; i < nNonZero && i < static_cast<int>(idx.size()); ++i) {
422 uKeep.col(i) = u.col(idx[i]);
423 sKeep(i) = s(idx[i]);
427 VectorXd sInv(nNonZero);
428 for(
int i = 0; i < nNonZero; ++i) {
429 sInv(i) = (sKeep(i) > 0) ? 1.0 / (sKeep(i) + lambda2) : 0.0;
431 MatrixXd N = uKeep * sInv.asDiagonal() * uKeep.transpose();
435 std::vector<Matrix3d> R_old_mat;
436 if(useScalar) R_old_vec = R_vec;
437 else R_old_mat = R_mat;
443 for(
int i = 0; i < nSrc; ++i) {
444 double val = (NG.col(i).array() * G.col(i).array()).sum();
445 R_vec(i) = (val > 1e-30) ? 1.0 / std::sqrt(val) : 1.0;
447 }
else if(m_bELoretaForceEqual) {
449 for(
int s_idx = 0; s_idx < nSrc; ++s_idx) {
450 MatrixXd Gs = G.middleCols(s_idx * 3, 3);
451 Matrix3d M = Gs.transpose() * N * Gs;
453 SelfAdjointEigenSolver<Matrix3d> eigM(M);
454 Vector3d mEig = eigM.eigenvalues();
455 double meanInvSqrt = 0;
456 for(
int d = 0; d < 3; ++d) {
457 meanInvSqrt += (mEig(d) > 1e-30) ? 1.0 / std::sqrt(mEig(d)) : 0.0;
460 for(
int d = 0; d < 3; ++d) {
461 R_vec(s_idx * 3 + d) = meanInvSqrt;
466 for(
int s_idx = 0; s_idx < nSrc; ++s_idx) {
467 MatrixXd Gs = G.middleCols(s_idx * 3, 3);
468 Matrix3d M = Gs.transpose() * N * Gs;
471 SelfAdjointEigenSolver<Matrix3d> eigM(M);
472 Vector3d mEig = eigM.eigenvalues();
473 Matrix3d mVec = eigM.eigenvectors();
475 for(
int d = 0; d < 3; ++d) {
476 mPow(d) = (mEig(d) > 1e-30) ? std::pow(mEig(d), -0.5) : 0.0;
478 R_mat[s_idx] = mVec * mPow.asDiagonal() * mVec.transpose();
484 for(
int i = 0; i < R_vec.size(); ++i) {
485 R_vec(i) *= sourceStd(i) * sourceStd(i);
488 for(
int s_idx = 0; s_idx < nSrc; ++s_idx) {
489 for(
int a = 0; a < 3; ++a) {
490 for(
int b = 0; b < 3; ++b) {
491 R_mat[s_idx](a, b) *= sourceStd(s_idx * 3 + a) * sourceStd(s_idx * 3 + b);
497 GRGt = computeGRGt();
500 double deltaNum = 0.0, deltaDen = 0.0;
502 deltaNum = (R_vec - R_old_vec).norm();
503 deltaDen = R_old_vec.norm();
505 for(
int s_idx = 0; s_idx < nSrc; ++s_idx) {
506 Matrix3d diff = R_mat[s_idx] - R_old_mat[s_idx];
507 deltaNum += diff.squaredNorm();
508 deltaDen += R_old_mat[s_idx].squaredNorm();
510 deltaNum = std::sqrt(deltaNum);
511 deltaDen = std::sqrt(deltaDen);
513 double delta = (deltaDen > 1e-30) ? deltaNum / deltaDen : 0.0;
515 if(delta < m_dELoretaEps) {
516 qInfo(
" eLORETA converged on iteration %d (delta=%.2e < eps=%.2e)", kk + 1, delta, m_dELoretaEps);
519 if(kk == m_iELoretaMaxIter - 1) {
520 qWarning(
" eLORETA weight fitting did not converge after %d iterations (delta=%.2e)", m_iELoretaMaxIter, delta);
525 for(
int i = 0; i < G.cols(); ++i) {
526 G.col(i) /= sourceStd(i);
531 std::vector<Matrix3d> R_sqrt_mat;
533 R_sqrt_vec = R_vec.cwiseSqrt();
535 R_sqrt_mat.resize(nSrc);
536 for(
int s_idx = 0; s_idx < nSrc; ++s_idx) {
537 SelfAdjointEigenSolver<Matrix3d> eigR(R_mat[s_idx]);
538 Vector3d rEig = eigR.eigenvalues();
539 Matrix3d rVec = eigR.eigenvectors();
541 for(
int d = 0; d < 3; ++d) {
542 rSqrt(d) = (rEig(d) > 1e-30) ? std::sqrt(rEig(d)) : 0.0;
544 R_sqrt_mat[s_idx] = rVec * rSqrt.asDiagonal() * rVec.transpose();
551 for(
int i = 0; i < A.cols(); ++i) {
552 A.col(i) *= R_sqrt_vec(i);
555 for(
int s_idx = 0; s_idx < nSrc; ++s_idx) {
556 MatrixXd Gs = G.middleCols(s_idx * 3, 3);
557 A.middleCols(s_idx * 3, 3) = Gs * R_sqrt_mat[s_idx];
563 JacobiSVD<MatrixXd>
svd(A, ComputeThinU | ComputeThinV);
564 const VectorXd newSing =
svd.singularValues();
565 const MatrixXd newU =
svd.matrixU();
566 const MatrixXd newV =
svd.matrixV();
569 MatrixXd weightedLeads = newV;
571 for(
int i = 0; i < weightedLeads.rows(); ++i) {
572 weightedLeads.row(i) *= R_sqrt_vec(i);
576 for(
int s_idx = 0; s_idx < nSrc; ++s_idx) {
577 MatrixXd Vs = newV.middleRows(s_idx * 3, 3);
578 weightedLeads.middleRows(s_idx * 3, 3) = R_sqrt_mat[s_idx] * Vs;
587 inv.eigen_fields->data = newU;
588 inv.eigen_leads->data = weightedLeads;
589 inv.eigen_leads_weighted =
true;
592 VectorXd reginv(newSing.size());
593 for(
int i = 0; i < newSing.size(); ++i) {
594 const double s2 = newSing(i) * newSing(i);
595 reginv(i) = (s2 > 1e-30) ? newSing(i) / (s2 + lambda2) : 0.0;
599 qInfo(
" eLORETA inverse operator updated. [done]");
Eigen::JacobiSVD< Eigen::Matrix3f > svd(S, Eigen::ComputeFullU|Eigen::ComputeFullV)
#define FIFFV_MNE_FREE_ORI
Single averaged evoked response: time axis, samples, baseline, channel info and processing history.
Linear minimum-norm inverse solver — MNE, dSPM, sLORETA and eLORETA from a precomputed MNEInverseOper...
InvSourceEstimate value type — central source-space data container produced by every INVLIB inverse s...
Static linear-algebra helpers: SVD-based conditioning, block-diagonal assembly, sorted index pairs.
Core MNE data structures (source spaces, source estimates, hemispheres).
FIFF file I/O, in-memory data structures and high-level readers/writers.
Shared utilities (I/O helpers, spectral analysis, layout management, warp algorithms).
Inverse source estimation (MNE, dSPM, sLORETA, dipole fitting).
Single averaged evoked response: time axis, data, baseline, channel info and averaging metadata.
FiffEvoked pick_channels(const QStringList &include=defaultQStringList, const QStringList &exclude=defaultQStringList) const
Source-space inverse-solution container with dense grid plus optional focal-dipole,...
virtual const MNELIB::MNESourceSpaces & getSourceSpace() const
void setELoretaOptions(int maxIter=20, double eps=1e-6, bool forceEqual=false)
virtual InvSourceEstimate calculateInverse(const FIFFLIB::FiffEvoked &p_fiffEvoked, bool pick_normal=false)
virtual void doInverseSetup(qint32 nave, bool pick_normal=false)
void setMethod(QString method)
void setRegularization(float lambda)
virtual const char * getName() const
InvMinimumNorm(const MNELIB::MNEInverseOperator &p_inverseOperator, float lambda, const QString method)
static Eigen::VectorXd combine_xyz(const Eigen::VectorXd &vec)
MNE-style inverse operator.
FIFFLIB::FiffNamedMatrix::SDPtr eigen_leads
FIFFLIB::fiff_int_t nsource
FIFFLIB::FiffNamedMatrix::SDPtr eigen_fields
List of MNESourceSpace objects forming a subject source space.