41#include <Eigen/Eigenvalues>
59: m_inverseOperator(p_inverseOperator)
61, m_iELoretaMaxIter(20)
63, m_bELoretaForceEqual(false)
73: m_inverseOperator(p_inverseOperator)
75, m_iELoretaMaxIter(20)
77, m_bELoretaForceEqual(false)
91 qint32 nave = p_fiffEvoked.
nave;
93 if (!m_inverseOperator.check_ch_names(p_fiffEvoked.
info)) {
94 qWarning(
"Channel name check failed.");
105 qInfo(
"Picked %d channels from the data", t_fiffEvoked.
info.
nchan);
108 float tmin = p_fiffEvoked.
times[0];
109 float tstep = 1 / t_fiffEvoked.
info.
sfreq;
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;
131 qInfo(
"combining the current components...");
133 MatrixXd sol1(sol.rows() / 3, sol.cols());
134 for (qint32 i = 0; i < sol.cols(); ++i) {
136 sol1.block(0, i, sol.rows() / 3, 1) = tmp.cwiseSqrt();
138 sol.resize(sol1.rows(), sol1.cols());
144 sol = inv.noisenorm * sol;
145 }
else if (m_bsLORETA) {
146 qInfo(
"(sLORETA)...");
147 sol = inv.noisenorm * sol;
148 }
else if (m_beLoreta) {
149 qInfo(
"(eLORETA)...");
150 sol = inv.noisenorm * sol;
155 VectorXi p_vecVertices(inv.src[0].vertno.size() + inv.src[1].vertno.size());
156 p_vecVertices << inv.src[0].vertno, inv.src[1].vertno;
162 InvSourceEstimate stc(sol, p_vecVertices, tmin, tstep);
166 stc.nVerticesLh =
static_cast<int>(inv.src[0].vertno.size());
175 if (!m_inverseOperator.check_ch_names(p_fiffEvoked.
info)) {
176 qWarning(
"Channel name check failed.");
190 inv = m_inverseOperator.prepare_inverse_operator(nave, m_fLambda, m_bdSPM || m_beLoreta, m_bsLORETA);
197 qInfo(
"Computing inverse...");
198 inv.assemble_kernel(label, m_sMethod, pick_normal, K, noise_norm, vertno);
200 std::cout <<
"K " << K.rows() <<
" x " << K.cols() << std::endl;
209 return "Minimum Norm Estimate";
216 return m_inverseOperator.src;
223 if (method.compare(
"MNE") == 0)
225 else if (method.compare(
"dSPM") == 0)
227 else if (method.compare(
"sLORETA") == 0)
229 else if (method.compare(
"eLORETA") == 0)
232 qWarning(
"Method not recognized!");
237 qInfo(
"\tSet minimum norm method to %s.", method.toUtf8().constData());
244 int nActive = (
dSPM ? 1 : 0) + (
sLORETA ? 1 : 0) + (eLoreta ? 1 : 0);
246 qWarning(
"Only one method can be active at a time! - Activating dSPM");
254 m_beLoreta = eLoreta;
257 m_sMethod = QString(
"dSPM");
259 m_sMethod = QString(
"sLORETA");
261 m_sMethod = QString(
"eLORETA");
263 m_sMethod = QString(
"MNE");
277 m_iELoretaMaxIter = maxIter;
279 m_bELoretaForceEqual = forceEqual;
284void InvMinimumNorm::computeELoreta()
295 qInfo(
"Computing eLORETA source weights...");
300 qWarning(
"InvMinimumNorm::computeELoreta - Inverse operator missing eigen structures!");
305 const MatrixXd& eigenLeads = inv.
eigen_leads->data;
306 const VectorXd& sing = inv.
sing;
308 MatrixXd G = eigenFields.transpose() * sing.asDiagonal() * eigenLeads.transpose();
310 const int nChan =
static_cast<int>(G.rows());
312 const int nOrient =
static_cast<int>(G.cols()) / nSrc;
314 if (nOrient != 1 && nOrient != 3) {
315 qWarning(
"InvMinimumNorm::computeELoreta - Unexpected n_orient: %d", nOrient);
320 if (inv.source_cov && inv.source_cov->data.size() > 0) {
321 for (
int i = 0; i < G.cols(); ++i) {
322 double sc = inv.source_cov->data(i, 0);
324 G.col(i) /= std::sqrt(sc);
329 VectorXd sourceStd = VectorXd::Ones(G.cols());
330 if (inv.orient_prior && inv.orient_prior->data.size() > 0) {
331 for (
int i = 0; i < G.cols() && i < inv.orient_prior->data.rows(); ++i) {
332 double op = inv.orient_prior->data(i, 0);
334 sourceStd(i) *= std::sqrt(op);
337 for (
int i = 0; i < G.cols(); ++i) {
338 G.col(i) *= sourceStd(i);
343 if (inv.noise_cov && !inv.noise_cov->diag) {
344 for (
int i = 0; i < inv.noise_cov->eig.size(); ++i) {
345 if (inv.noise_cov->eig(i) > 0)
348 }
else if (inv.noise_cov) {
352 nNonZero = std::min(nNonZero, nChan);
354 double lambda2 =
static_cast<double>(m_fLambda);
359 const bool useScalar = (nOrient == 1 || m_bELoretaForceEqual);
362 std::vector<Matrix3d> R_mat;
365 R_vec = VectorXd::Ones(
static_cast<Eigen::Index
>(nSrc) * nOrient);
367 for (
int i = 0; i < R_vec.size(); ++i) {
368 R_vec(i) *= sourceStd(i) * sourceStd(i);
372 for (
int s = 0; s < nSrc; ++s) {
373 R_mat[s] = Matrix3d::Identity();
375 for (
int a = 0; a < 3; ++a) {
376 for (
int b = 0; b < 3; ++b) {
377 R_mat[s](a, b) *= sourceStd(s * 3 + a) * sourceStd(s * 3 + b);
383 qInfo(
" Fitting up to %d iterations (n_orient=%d, force_equal=%s)...",
384 m_iELoretaMaxIter, nOrient, m_bELoretaForceEqual ?
"true" :
"false");
387 auto computeGRGt = [&]() -> MatrixXd {
392 for (
int i = 0; i < G.cols(); ++i) {
393 GR.col(i) *= R_vec(i);
395 GRGt = GR * G.transpose();
398 MatrixXd RGt = MatrixXd::Zero(nSrc * 3, nChan);
399 for (
int s = 0; s < nSrc; ++s) {
401 MatrixXd Gs = G.middleCols(s * 3, 3);
403 RGt.middleRows(s * 3, 3) = R_mat[s] * Gs.transpose();
408 double trace = GRGt.trace();
409 double norm = trace /
static_cast<double>(nNonZero);
415 for (
auto& Rm : R_mat)
421 MatrixXd GRGt = computeGRGt();
423 for (
int kk = 0; kk < m_iELoretaMaxIter; ++kk) {
425 SelfAdjointEigenSolver<MatrixXd> eig(GRGt);
426 VectorXd s = eig.eigenvalues().cwiseAbs();
427 MatrixXd u = eig.eigenvectors();
431 std::vector<int> idx(s.size());
432 std::iota(idx.begin(), idx.end(), 0);
433 std::sort(idx.begin(), idx.end(), [&s](
int a,
int b) { return s(a) > s(b); });
435 MatrixXd uKeep(nChan, nNonZero);
436 VectorXd sKeep(nNonZero);
437 for (
int i = 0; i < nNonZero && i < static_cast<int>(idx.size()); ++i) {
438 uKeep.col(i) = u.col(idx[i]);
439 sKeep(i) = s(idx[i]);
443 VectorXd sInv(nNonZero);
444 for (
int i = 0; i < nNonZero; ++i) {
445 sInv(i) = (sKeep(i) > 0) ? 1.0 / (sKeep(i) + lambda2) : 0.0;
447 MatrixXd N = uKeep * sInv.asDiagonal() * uKeep.transpose();
451 std::vector<Matrix3d> R_old_mat;
461 for (
int i = 0; i < nSrc; ++i) {
462 double val = (NG.col(i).array() * G.col(i).array()).sum();
463 R_vec(i) = (val > 1e-30) ? 1.0 / std::sqrt(val) : 1.0;
465 }
else if (m_bELoretaForceEqual) {
467 for (
int s_idx = 0; s_idx < nSrc; ++s_idx) {
468 MatrixXd Gs = G.middleCols(s_idx * 3, 3);
469 Matrix3d M = Gs.transpose() * N * Gs;
472 SelfAdjointEigenSolver<Matrix3d> eigM(M);
473 Vector3d mEig = eigM.eigenvalues();
474 const double limit = mEig(2) * 1e-7;
476 bool degenerate =
false;
477 for (
int d = 0; d < 3; ++d) {
479 meanSqrt += std::sqrt(mEig(d));
484 for (
int d = 0; d < 3; ++d) {
485 R_vec(s_idx * 3 + d) = (degenerate || meanSqrt <= 0) ? 0.0 : 1.0 / meanSqrt;
490 for (
int s_idx = 0; s_idx < nSrc; ++s_idx) {
491 MatrixXd Gs = G.middleCols(s_idx * 3, 3);
492 Matrix3d M = Gs.transpose() * N * Gs;
495 SelfAdjointEigenSolver<Matrix3d> eigM(M);
496 Vector3d mEig = eigM.eigenvalues();
497 Matrix3d mVec = eigM.eigenvectors();
499 for (
int d = 0; d < 3; ++d) {
500 mPow(d) = (mEig(d) > 1e-7 * mEig(2)) ? std::pow(mEig(d), -0.5) : 0.0;
502 R_mat[s_idx] = mVec * mPow.asDiagonal() * mVec.transpose();
508 for (
int i = 0; i < R_vec.size(); ++i) {
509 R_vec(i) *= sourceStd(i) * sourceStd(i);
512 for (
int s_idx = 0; s_idx < nSrc; ++s_idx) {
513 for (
int a = 0; a < 3; ++a) {
514 for (
int b = 0; b < 3; ++b) {
515 R_mat[s_idx](a, b) *= sourceStd(s_idx * 3 + a) * sourceStd(s_idx * 3 + b);
521 GRGt = computeGRGt();
524 double deltaNum = 0.0, deltaDen = 0.0;
526 deltaNum = (R_vec - R_old_vec).norm();
527 deltaDen = R_old_vec.norm();
529 for (
int s_idx = 0; s_idx < nSrc; ++s_idx) {
530 Matrix3d diff = R_mat[s_idx] - R_old_mat[s_idx];
531 deltaNum += diff.squaredNorm();
532 deltaDen += R_old_mat[s_idx].squaredNorm();
534 deltaNum = std::sqrt(deltaNum);
535 deltaDen = std::sqrt(deltaDen);
537 double delta = (deltaDen > 1e-30) ? deltaNum / deltaDen : 0.0;
539 if (delta < m_dELoretaEps) {
540 qInfo(
" eLORETA converged on iteration %d (delta=%.2e < eps=%.2e)", kk + 1, delta, m_dELoretaEps);
543 if (kk == m_iELoretaMaxIter - 1) {
544 qWarning(
" eLORETA weight fitting did not converge after %d iterations (delta=%.2e)", m_iELoretaMaxIter, delta);
549 for (
int i = 0; i < G.cols(); ++i) {
550 G.col(i) /= sourceStd(i);
556 std::vector<Matrix3d> R_sqrt_mat;
558 R_sqrt_vec = R_vec.cwiseSqrt();
560 R_sqrt_mat.resize(nSrc);
561 for (
int s_idx = 0; s_idx < nSrc; ++s_idx) {
562 SelfAdjointEigenSolver<Matrix3d> eigR(R_mat[s_idx]);
563 Vector3d rEig = eigR.eigenvalues();
564 Matrix3d rVec = eigR.eigenvectors();
566 for (
int d = 0; d < 3; ++d) {
567 rSqrt(d) = (rEig(d) > 1e-7 * rEig(2)) ? std::sqrt(rEig(d)) : 0.0;
569 R_sqrt_mat[s_idx] = rVec * rSqrt.asDiagonal() * rVec.transpose();
576 for (
int i = 0; i < A.cols(); ++i) {
577 A.col(i) *= R_sqrt_vec(i);
580 for (
int s_idx = 0; s_idx < nSrc; ++s_idx) {
581 MatrixXd Gs = G.middleCols(s_idx * 3, 3);
582 A.middleCols(s_idx * 3, 3) = Gs * R_sqrt_mat[s_idx];
588 JacobiSVD<MatrixXd>
svd(A, ComputeThinU | ComputeThinV);
589 const VectorXd newSing =
svd.singularValues();
590 const MatrixXd newU =
svd.matrixU();
591 const MatrixXd newV =
svd.matrixV();
594 MatrixXd weightedLeads = newV;
596 for (
int i = 0; i < weightedLeads.rows(); ++i) {
597 weightedLeads.row(i) *= R_sqrt_vec(i);
601 for (
int s_idx = 0; s_idx < nSrc; ++s_idx) {
602 MatrixXd Vs = newV.middleRows(s_idx * 3, 3);
603 weightedLeads.middleRows(s_idx * 3, 3) = R_sqrt_mat[s_idx] * Vs;
609 inv.eigen_fields->data = newU.transpose();
610 inv.eigen_fields->nrow =
static_cast<int>(newU.cols());
611 inv.eigen_fields->ncol =
static_cast<int>(newU.rows());
612 inv.eigen_leads->ncol =
static_cast<int>(weightedLeads.cols());
613 inv.eigen_leads->data = weightedLeads;
614 inv.eigen_leads_weighted =
true;
617 VectorXd reginv = VectorXd::Zero(newSing.size());
618 for (
int i = 0; i < std::min<int>(nNonZero,
static_cast<int>(newSing.size())); ++i) {
620 reginv(i) = newSing(i) / (newSing(i) * newSing(i) + lambda2);
624 qInfo(
" eLORETA inverse operator updated. [done]");
#define FIFFV_MNE_FREE_ORI
Single averaged evoked response: time axis, samples, baseline, channel info and processing history.
Eigen::JacobiSVD< Eigen::Matrix3f > svd(S, Eigen::ComputeFullU|Eigen::ComputeFullV)
SSP projection item: a named projection vector set with active/desired flags, parsed from FIFFB_PROJ_...
InvSourceEstimate value type — central source-space data container produced by every INVLIB inverse s...
Linear minimum-norm inverse solver — MNE, dSPM, sLORETA and eLORETA from a precomputed MNEInverseOper...
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
static fiff_int_t make_projector(const QList< FiffProj > &projs, const QStringList &ch_names, Eigen::MatrixXd &proj, const QStringList &bads=defaultQStringList, Eigen::MatrixXd &U=defaultMatrixXd)
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)
MNELIB::MNEMneData mneData(const FIFFLIB::FiffEvoked &p_fiffEvoked, double snr=0.0)
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
Data associated with MNE computations for each mneMeasDataSet.
static MNEMneData compute(const MNEInverseOperator &inv, const Eigen::MatrixXd &data, double snr)
List of MNESourceSpace objects forming a subject source space.