45#include <Eigen/Eigenvalues>
74VectorXd computeRowPsd(
const VectorXd& row,
int nFft,
double sfreq)
77 VectorXd segment = VectorXd::Zero(nFft);
78 int copyLen = std::min(
static_cast<int>(row.size()), nFft);
79 segment.head(copyLen) = row.head(copyLen);
82 for (
int i = 0; i < copyLen; ++i) {
83 double w = 0.5 * (1.0 - std::cos(2.0 *
M_PI * i / (copyLen - 1)));
88 int nFreqs = nFft / 2 + 1;
91 for (
int f = 0; f < nFreqs; ++f) {
92 double re = 0.0, im = 0.0;
93 for (
int t = 0; t < nFft; ++t) {
94 double angle = -2.0 *
M_PI * f * t / nFft;
95 re += segment(t) * std::cos(angle);
96 im += segment(t) * std::sin(angle);
98 psd(f) = (re * re + im * im) / (sfreq * nFft);
100 if (f > 0 && f < nFreqs - 1)
114 const QList<MatrixXd>& epochs,
117 const QString& method,
122 QList<InvSourceEstimate> results;
124 if (epochs.isEmpty()) {
125 qWarning() <<
"[applyInverseEpochs] No epochs provided.";
133 mn.doInverseSetup(1, pickNormal);
135 for (
int i = 0; i < epochs.size(); ++i) {
138 qWarning() <<
"[applyInverseEpochs] Epoch" << i <<
"produced empty source estimate.";
152 const QString& method,
165 if (picks.size() == 0) {
166 qWarning() <<
"[applyInverseRaw] No channels match the inverse operator.";
171 MatrixXd data, times;
174 if (data.cols() == 0) {
175 qWarning() <<
"[applyInverseRaw] No data read from raw file.";
179 float tmin =
static_cast<float>(from) / raw.
info.
sfreq;
199 qWarning() <<
"[estimateSnr] Could not prepare the inverse operator.";
200 return QPair<VectorXd, VectorXd>();
203 const MatrixXd whiteEf = inv.
eigen_fields->data * white;
207 rank =
static_cast<int>((inv.
noise_cov->eig.array() > 0).count());
213 const int nTimes =
static_cast<int>(white.cols());
214 VectorXd snr = white.colwise().squaredNorm().transpose() / rank;
215 VectorXd lambda2 = VectorXd::Constant(nTimes, 10.0);
216 std::vector<bool> remaining(nTimes,
true);
217 for (
int t = 0; t < nTimes; ++t) {
219 lambda2(t) = std::numeric_limits<double>::infinity();
220 remaining[t] =
false;
224 const ArrayXd sing2 = inv.
sing.array().square();
226 bool converged =
false;
227 for (
int iter = 0; iter < 1000 && !converged; ++iter) {
229 for (
int t = 0; t < nTimes; ++t) {
232 const ArrayXd keep = (inv.
sing.array() == 0).select(1.0, lambda2(t) / (sing2 + lambda2(t)));
233 if ((whiteEf.col(t).array() * keep).matrix().squaredNorm() < limit) {
234 remaining[t] =
false;
242 qWarning() <<
"[estimateSnr] SNR estimation did not converge.";
244 return QPair<VectorXd, VectorXd>(snr.cwiseSqrt(), lambda2.cwiseSqrt().cwiseInverse());
253 const int dim = noiseCov.
dim;
256 qWarning() <<
"[computeWhitener] Empty noise covariance.";
257 return QPair<MatrixXd, int>(MatrixXd(), 0);
265 if (noiseCov.
eig.size() > 0 && noiseCov.
eigvec.size() > 0) {
269 SelfAdjointEigenSolver<MatrixXd> solver(noiseCov.
data);
270 eig = solver.eigenvalues();
271 eigvec = solver.eigenvectors().transpose();
276 const bool prepared = noiseCov.
eig.size() > 0 && noiseCov.
eigvec.size() > 0;
278 double maxEig = eig.maxCoeff();
279 double threshold = prepared ? 0.0 : maxEig * 1e-10;
281 for (
int i = 0; i < eig.size(); ++i) {
282 if (eig(i) > threshold)
291 VectorXd invSqrtEig = VectorXd::Zero(eig.size());
292 int effectiveRank = 0;
295 for (
int i = eig.size() - 1; i >= 0 && effectiveRank < rank; --i) {
296 if (eig(i) > 1e-30) {
297 invSqrtEig(i) = 1.0 / std::sqrt(eig(i));
302 MatrixXd whitener = invSqrtEig.asDiagonal() * eigvec;
304 return QPair<MatrixXd, int>(whitener, effectiveRank);
317 qWarning() <<
"[computeSourcePsd] Empty source estimate.";
318 return QPair<MatrixXd, VectorXd>();
321 const int nSources =
static_cast<int>(stc.
data.rows());
322 const int nTimes =
static_cast<int>(stc.
data.cols());
329 const int nFreqs = nFft / 2 + 1;
332 VectorXd freqs(nFreqs);
333 for (
int f = 0; f < nFreqs; ++f) {
334 freqs(f) =
static_cast<double>(f) * sfreq / nFft;
338 int fminIdx = 0, fmaxIdx = nFreqs - 1;
339 for (
int f = 0; f < nFreqs; ++f) {
340 if (freqs(f) >= fmin) {
345 for (
int f = nFreqs - 1; f >= 0; --f) {
346 if (freqs(f) <= fmax) {
352 int nBandFreqs = fmaxIdx - fminIdx + 1;
353 if (nBandFreqs <= 0) {
354 qWarning() <<
"[computeSourcePsd] No frequencies in range.";
355 return QPair<MatrixXd, VectorXd>();
359 MatrixXd psd(nSources, nBandFreqs);
361 for (
int s = 0; s < nSources; ++s) {
362 VectorXd fullPsd = computeRowPsd(stc.
data.row(s).transpose(), nFft, sfreq);
363 psd.row(s) = fullPsd.segment(fminIdx, nBandFreqs).transpose();
366 VectorXd bandFreqs = freqs.segment(fminIdx, nBandFreqs);
368 return QPair<MatrixXd, VectorXd>(psd, bandFreqs);
376 const QMap<QString, QPair<float, float>>& bands)
378 QMap<QString, VectorXd> result;
380 if (stc.
isEmpty() || bands.isEmpty()) {
389 const int nSources =
static_cast<int>(psd.rows());
390 const int nFreqs =
static_cast<int>(freqs.size());
391 double df = (nFreqs > 1) ? (freqs(1) - freqs(0)) : 1.0;
393 for (
auto it = bands.constBegin(); it != bands.constEnd(); ++it) {
394 float bfmin = it.value().first;
395 float bfmax = it.value().second;
397 VectorXd bandPower = VectorXd::Zero(nSources);
399 for (
int f = 0; f < nFreqs; ++f) {
400 if (freqs(f) >= bfmin && freqs(f) <= bfmax) {
401 bandPower += psd.col(f) * df;
405 result[it.key()] = bandPower;
Single averaged evoked response: time axis, samples, baseline, channel info and processing history.
Full FIFF measurement metadata: everything from FIFFB_MEAS / FIFFB_MEAS_INFO needed to interpret a re...
SSP projection item: a named projection vector set with active/desired flags, parsed from FIFFB_PROJ_...
FIFF continuous raw recording: FiffInfo plus a directory of FIFF_DATA_BUFFER tags for random-access s...
Noise / data covariance matrix as stored under FIFFB_MNE_COV, with channel names, kind,...
Top-level convenience entry points that mirror MNE-Python's apply_inverse_* / compute_source_psd help...
Linear minimum-norm inverse solver — MNE, dSPM, sLORETA and eLORETA from a precomputed MNEInverseOper...
General numerical helpers: GCD, log2, histogram binning, baseline rescaling, sparsity tests.
Pre-computed inverse operator (whitened SVD of the forward model) for MNE/dSPM/sLORETA.
Core MNE data structures (source spaces, source estimates, hemispheres).
FIFF file I/O, in-memory data structures and high-level readers/writers.
Inverse source estimation (MNE, dSPM, sLORETA, dipole fitting).
INVSHARED_EXPORT QMap< QString, Eigen::VectorXd > computeSourceBandPower(const InvSourceEstimate &stc, float sfreq, const QMap< QString, QPair< float, float > > &bands)
Compute band power for source estimate.
INVSHARED_EXPORT QPair< Eigen::MatrixXd, int > computeWhitener(const FIFFLIB::FiffCov &noiseCov, int rank=0)
Compute whitening matrix from a noise covariance.
INVSHARED_EXPORT QPair< Eigen::VectorXd, Eigen::VectorXd > estimateSnr(const FIFFLIB::FiffEvoked &evoked, const MNELIB::MNEInverseOperator &inverse)
Estimate the SNR of evoked data as a function of time.
INVSHARED_EXPORT InvSourceEstimate applyInverseRaw(const FIFFLIB::FiffRawData &raw, const MNELIB::MNEInverseOperator &inverse, float lambda2, const QString &method="dSPM", int from=-1, int to=-1, bool pickNormal=false)
Apply inverse operator to raw data.
INVSHARED_EXPORT QList< InvSourceEstimate > applyInverseEpochs(const QList< Eigen::MatrixXd > &epochs, const MNELIB::MNEInverseOperator &inverse, float lambda2, const QString &method="dSPM", float tmin=0.0f, float tstep=0.001f, bool pickNormal=false)
Apply inverse operator to each epoch in a list.
INVSHARED_EXPORT QPair< Eigen::MatrixXd, Eigen::VectorXd > computeSourcePsd(const InvSourceEstimate &stc, float sfreq, float fmin=0.0f, float fmax=-1.0f, int nFft=0)
Compute PSD for a source estimate using Welch's method.
FIFF noise / data covariance: matrix, channel names, kind, applied projectors, bads,...
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 Eigen::RowVectorXi pick_channels(const QStringList &ch_names, const QStringList &include=defaultQStringList, const QStringList &exclude=defaultQStringList)
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)
Continuous FIFF raw recording: FiffInfo plus a random-access directory of FIFF_DATA_BUFFER tags.
bool read_raw_segment(Eigen::MatrixXd &data, Eigen::MatrixXd ×, fiff_int_t from=-1, fiff_int_t to=-1, const Eigen::RowVectorXi &sel=defaultRowVectorXi, bool do_debug=false) const
Source-space inverse-solution container with dense grid plus optional focal-dipole,...
virtual InvSourceEstimate calculateInverse(const FIFFLIB::FiffEvoked &p_fiffEvoked, bool pick_normal=false)
virtual void doInverseSetup(qint32 nave, bool pick_normal=false)
static double chi2Isf(double p, int dof)
MNE-style inverse operator.
QList< FIFFLIB::FiffProj > projs
MNEInverseOperator prepare_inverse_operator(qint32 nave, float lambda2, bool dSPM, bool sLORETA=false) const
Prepare the inverse operator for source estimation.
FIFFLIB::FiffCov::SDPtr noise_cov
FIFFLIB::FiffNamedMatrix::SDPtr eigen_fields