44#include <Eigen/Eigenvalues>
70VectorXd computeRowPsd(
const VectorXd& row,
int nFft,
double sfreq)
73 VectorXd segment = VectorXd::Zero(nFft);
74 int copyLen = std::min(
static_cast<int>(row.size()), nFft);
75 segment.head(copyLen) = row.head(copyLen);
78 for (
int i = 0; i < copyLen; ++i) {
79 double w = 0.5 * (1.0 - std::cos(2.0 *
M_PI * i / (copyLen - 1)));
84 int nFreqs = nFft / 2 + 1;
87 for (
int f = 0; f < nFreqs; ++f) {
88 double freq =
static_cast<double>(f) * sfreq / nFft;
89 double re = 0.0, im = 0.0;
90 for (
int t = 0; t < nFft; ++t) {
91 double angle = -2.0 *
M_PI * f * t / nFft;
92 re += segment(t) * std::cos(angle);
93 im += segment(t) * std::sin(angle);
95 psd(f) = (re * re + im * im) / (sfreq * nFft);
97 if (f > 0 && f < nFreqs - 1) psd(f) *= 2.0;
110 const QList<MatrixXd>& epochs,
113 const QString& method,
118 QList<InvSourceEstimate> results;
120 if (epochs.isEmpty()) {
121 qWarning() <<
"[applyInverseEpochs] No epochs provided.";
129 mn.doInverseSetup(1, pickNormal);
131 for (
int i = 0; i < epochs.size(); ++i) {
134 qWarning() <<
"[applyInverseEpochs] Epoch" << i <<
"produced empty source estimate.";
148 const QString& method,
159 if (picks.size() == 0) {
160 qWarning() <<
"[applyInverseRaw] No channels match the inverse operator.";
165 MatrixXd data, times;
168 if (data.cols() == 0) {
169 qWarning() <<
"[applyInverseRaw] No data read from raw file.";
173 float tmin =
static_cast<float>(from) / raw.
info.
sfreq;
178 mn.doInverseSetup(1, pickNormal);
180 return mn.calculateInverse(data, tmin, tstep, pickNormal);
188 const QString& method)
191 float lambda2 = 1.0f / (snr * snr);
198 qWarning() <<
"[estimateSnr] Failed to compute source estimate.";
199 return QPair<VectorXd, RowVectorXf>();
204 const int nTimes =
static_cast<int>(stc.
data.cols());
205 VectorXd snrTimeCourse(nTimes);
207 for (
int t = 0; t < nTimes; ++t) {
208 snrTimeCourse(t) = std::sqrt(stc.
data.col(t).squaredNorm()
209 /
static_cast<double>(stc.
data.rows()));
212 return QPair<VectorXd, RowVectorXf>(snrTimeCourse, stc.
times);
221 const int dim = noiseCov.
dim;
224 qWarning() <<
"[computeWhitener] Empty noise covariance.";
225 return QPair<MatrixXd, int>(MatrixXd(), 0);
232 if (noiseCov.
eig.size() > 0 && noiseCov.
eigvec.size() > 0) {
237 SelfAdjointEigenSolver<MatrixXd> solver(noiseCov.
data);
238 eig = solver.eigenvalues();
239 eigvec = solver.eigenvectors();
244 double maxEig = eig.maxCoeff();
245 double threshold = maxEig * 1e-10;
247 for (
int i = 0; i < eig.size(); ++i) {
248 if (eig(i) > threshold) ++rank;
250 if (rank == 0) rank = 1;
255 VectorXd invSqrtEig = VectorXd::Zero(eig.size());
256 int effectiveRank = 0;
259 for (
int i = eig.size() - 1; i >= 0 && effectiveRank < rank; --i) {
260 if (eig(i) > 1e-30) {
261 invSqrtEig(i) = 1.0 / std::sqrt(eig(i));
266 MatrixXd whitener = invSqrtEig.asDiagonal() * eigvec.transpose();
268 return QPair<MatrixXd, int>(whitener, effectiveRank);
281 qWarning() <<
"[computeSourcePsd] Empty source estimate.";
282 return QPair<MatrixXd, VectorXd>();
285 const int nSources =
static_cast<int>(stc.
data.rows());
286 const int nTimes =
static_cast<int>(stc.
data.cols());
288 if (nFft <= 0) nFft = nTimes;
289 if (fmax < 0) fmax = sfreq / 2.0f;
291 const int nFreqs = nFft / 2 + 1;
294 VectorXd freqs(nFreqs);
295 for (
int f = 0; f < nFreqs; ++f) {
296 freqs(f) =
static_cast<double>(f) * sfreq / nFft;
300 int fminIdx = 0, fmaxIdx = nFreqs - 1;
301 for (
int f = 0; f < nFreqs; ++f) {
302 if (freqs(f) >= fmin) { fminIdx = f;
break; }
304 for (
int f = nFreqs - 1; f >= 0; --f) {
305 if (freqs(f) <= fmax) { fmaxIdx = f;
break; }
308 int nBandFreqs = fmaxIdx - fminIdx + 1;
309 if (nBandFreqs <= 0) {
310 qWarning() <<
"[computeSourcePsd] No frequencies in range.";
311 return QPair<MatrixXd, VectorXd>();
315 MatrixXd psd(nSources, nBandFreqs);
317 for (
int s = 0; s < nSources; ++s) {
318 VectorXd fullPsd = computeRowPsd(stc.
data.row(s).transpose(), nFft, sfreq);
319 psd.row(s) = fullPsd.segment(fminIdx, nBandFreqs).transpose();
322 VectorXd bandFreqs = freqs.segment(fminIdx, nBandFreqs);
324 return QPair<MatrixXd, VectorXd>(psd, bandFreqs);
332 const QMap<QString, QPair<float, float>>& bands)
334 QMap<QString, VectorXd> result;
336 if (stc.
isEmpty() || bands.isEmpty()) {
342 if (psd.size() == 0)
return result;
344 const int nSources =
static_cast<int>(psd.rows());
345 const int nFreqs =
static_cast<int>(freqs.size());
346 double df = (nFreqs > 1) ? (freqs(1) - freqs(0)) : 1.0;
348 for (
auto it = bands.constBegin(); it != bands.constEnd(); ++it) {
349 float bfmin = it.value().first;
350 float bfmax = it.value().second;
352 VectorXd bandPower = VectorXd::Zero(nSources);
354 for (
int f = 0; f < nFreqs; ++f) {
355 if (freqs(f) >= bfmin && freqs(f) <= bfmax) {
356 bandPower += psd.col(f) * df;
360 result[it.key()] = bandPower;
Pre-computed inverse operator (whitened SVD of the forward model) for MNE/dSPM/sLORETA.
FIFF continuous raw recording: FiffInfo plus a directory of FIFF_DATA_BUFFER tags for random-access s...
Full FIFF measurement metadata: everything from FIFFB_MEAS / FIFFB_MEAS_INFO needed to interpret a re...
Noise / data covariance matrix as stored under FIFFB_MNE_COV, with channel names, kind,...
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...
Top-level convenience entry points that mirror MNE-Python's apply_inverse_* / compute_source_psd help...
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 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 in blocks.
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.
INVSHARED_EXPORT QPair< Eigen::VectorXd, Eigen::RowVectorXf > estimateSnr(const FIFFLIB::FiffEvoked &evoked, const MNELIB::MNEInverseOperator &inverse, const QString &method="dSPM")
Estimate SNR from evoked data and inverse operator.
FIFF noise / data covariance: matrix, channel names, kind, applied projectors, bads,...
Single averaged evoked response: time axis, data, baseline, channel info and averaging metadata.
static Eigen::RowVectorXi pick_channels(const QStringList &ch_names, const QStringList &include=defaultQStringList, const QStringList &exclude=defaultQStringList)
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)
MNE-style inverse operator.
FIFFLIB::FiffCov::SDPtr noise_cov