47#include <Eigen/Eigenvalues>
78VectorXd computeRowPsd(
const VectorXd& row,
int nFft,
double sfreq)
81 VectorXd segment = VectorXd::Zero(nFft);
82 int copyLen = std::min(
static_cast<int>(row.size()), nFft);
83 segment.head(copyLen) = row.head(copyLen);
86 for (
int i = 0; i < copyLen; ++i) {
87 double w = 0.5 * (1.0 - std::cos(2.0 * M_PI * i / (copyLen - 1)));
92 int nFreqs = nFft / 2 + 1;
95 for (
int f = 0; f < nFreqs; ++f) {
96 double re = 0.0, im = 0.0;
97 for (
int t = 0; t < nFft; ++t) {
98 double angle = -2.0 * M_PI * f * t / nFft;
99 re += segment(t) * std::cos(angle);
100 im += segment(t) * std::sin(angle);
102 psd(f) = (re * re + im * im) / (sfreq * nFft);
104 if (f > 0 && f < nFreqs - 1)
118 const QList<MatrixXd>& epochs,
121 const QString& method,
126 QList<InvSourceEstimate> results;
128 if (epochs.isEmpty()) {
129 qWarning() <<
"[applyInverseEpochs] No epochs provided.";
137 mn.doInverseSetup(1, pickNormal);
139 for (
int i = 0; i < epochs.size(); ++i) {
142 qWarning() <<
"[applyInverseEpochs] Epoch" << i <<
"produced empty source estimate.";
156 const QString& method,
169 if (picks.size() == 0) {
170 qWarning() <<
"[applyInverseRaw] No channels match the inverse operator.";
175 MatrixXd data, times;
178 if (data.cols() == 0) {
179 qWarning() <<
"[applyInverseRaw] No data read from raw file.";
183 float tmin =
static_cast<float>(from) / raw.
info.
sfreq;
198 if (!fwd.
sol || fwd.
sol->data.size() == 0 || stc.
isEmpty()) {
199 qWarning() <<
"[applyForward] Empty forward solution or source estimate.";
203 const int nHemi = fixed.
src.
size();
208 const MatrixXd& gain = fixed.
sol->data;
210 QVector<int> columns(
static_cast<int>(stc.
data.rows()));
212 if (gain.cols() != stc.
data.rows()) {
213 qWarning() <<
"[applyForward] Forward columns" << gain.cols() <<
"!= source estimate rows" << stc.
data.rows();
216 std::iota(columns.begin(), columns.end(), 0);
219 for (
int r = 0; r < columns.size(); ++r) {
220 const int hemi = (r < nLh || nHemi == 1) ? 0 : 1;
223 for (
int h = 0; h < hemi; ++h)
225 const auto* hit = std::find(vertno.data(), vertno.data() + vertno.size(), stc.
vertices(r));
226 if (hit == vertno.data() + vertno.size()) {
227 qWarning() <<
"[applyForward] Vertex" << stc.
vertices(r) <<
"is not in the forward solution.";
230 columns[r] = offset +
static_cast<int>(hit - vertno.data());
235 RowVectorXi sel(fixed.
sol->row_names.isEmpty() ? gain.rows() : fixed.
sol->row_names.size());
236 if (fixed.
sol->row_names.isEmpty()) {
237 if (info.
nchan != gain.rows()) {
238 qWarning() <<
"[applyForward] Unnamed forward rows" << gain.rows() <<
"!= info channels" << info.
nchan;
241 std::iota(sel.begin(), sel.end(), 0);
243 for (
int c = 0; c < fixed.
sol->row_names.size(); ++c) {
244 sel(c) =
static_cast<int>(info.
ch_names.indexOf(fixed.
sol->row_names[c]));
246 qWarning() <<
"[applyForward] Channel" << fixed.
sol->row_names[c] <<
"of the forward is not in the info.";
252 MatrixXd selected(gain.rows(), columns.size());
253 for (
int r = 0; r < columns.size(); ++r)
254 selected.col(r) = gain.col(columns[r]);
258 evoked.
first =
static_cast<int>(std::lround(stc.
tmin * info.
sfreq));
259 evoked.
last = evoked.
first +
static_cast<int>(stc.
data.cols()) - 1;
261 evoked.
comment = QStringLiteral(
"Simulated");
275 qWarning() <<
"[estimateSnr] Could not prepare the inverse operator.";
276 return QPair<VectorXd, VectorXd>();
279 const MatrixXd whiteEf = inv.
eigen_fields->data * white;
283 rank =
static_cast<int>((inv.
noise_cov->eig.array() > 0).count());
289 const int nTimes =
static_cast<int>(white.cols());
290 VectorXd snr = white.colwise().squaredNorm().transpose() / rank;
291 VectorXd lambda2 = VectorXd::Constant(nTimes, 10.0);
292 std::vector<bool> remaining(nTimes,
true);
293 for (
int t = 0; t < nTimes; ++t) {
295 lambda2(t) = std::numeric_limits<double>::infinity();
296 remaining[t] =
false;
300 const ArrayXd sing2 = inv.
sing.array().square();
302 bool converged =
false;
303 for (
int iter = 0; iter < 1000 && !converged; ++iter) {
305 for (
int t = 0; t < nTimes; ++t) {
308 const ArrayXd keep = (inv.
sing.array() == 0).select(1.0, lambda2(t) / (sing2 + lambda2(t)));
309 if ((whiteEf.col(t).array() * keep).matrix().squaredNorm() < limit) {
310 remaining[t] =
false;
318 qWarning() <<
"[estimateSnr] SNR estimation did not converge.";
320 return QPair<VectorXd, VectorXd>(snr.cwiseSqrt(), lambda2.cwiseSqrt().cwiseInverse());
329 const int dim = noiseCov.
dim;
332 qWarning() <<
"[computeWhitener] Empty noise covariance.";
333 return QPair<MatrixXd, int>(MatrixXd(), 0);
341 if (noiseCov.
eig.size() > 0 && noiseCov.
eigvec.size() > 0) {
345 SelfAdjointEigenSolver<MatrixXd> solver(noiseCov.
data);
346 eig = solver.eigenvalues();
347 eigvec = solver.eigenvectors().transpose();
352 const bool prepared = noiseCov.
eig.size() > 0 && noiseCov.
eigvec.size() > 0;
354 double maxEig = eig.maxCoeff();
355 double threshold = prepared ? 0.0 : maxEig * 1e-10;
357 for (
int i = 0; i < eig.size(); ++i) {
358 if (eig(i) > threshold)
367 VectorXd invSqrtEig = VectorXd::Zero(eig.size());
368 int effectiveRank = 0;
371 for (
int i = eig.size() - 1; i >= 0 && effectiveRank < rank; --i) {
372 if (eig(i) > 1e-30) {
373 invSqrtEig(i) = 1.0 / std::sqrt(eig(i));
378 MatrixXd whitener = invSqrtEig.asDiagonal() * eigvec;
380 return QPair<MatrixXd, int>(whitener, effectiveRank);
393 qWarning() <<
"[computeSourcePsd] Empty source estimate.";
394 return QPair<MatrixXd, VectorXd>();
397 const int nSources =
static_cast<int>(stc.
data.rows());
398 const int nTimes =
static_cast<int>(stc.
data.cols());
405 const int nFreqs = nFft / 2 + 1;
408 VectorXd freqs(nFreqs);
409 for (
int f = 0; f < nFreqs; ++f) {
410 freqs(f) =
static_cast<double>(f) * sfreq / nFft;
414 int fminIdx = 0, fmaxIdx = nFreqs - 1;
415 for (
int f = 0; f < nFreqs; ++f) {
416 if (freqs(f) >= fmin) {
421 for (
int f = nFreqs - 1; f >= 0; --f) {
422 if (freqs(f) <= fmax) {
428 int nBandFreqs = fmaxIdx - fminIdx + 1;
429 if (nBandFreqs <= 0) {
430 qWarning() <<
"[computeSourcePsd] No frequencies in range.";
431 return QPair<MatrixXd, VectorXd>();
435 MatrixXd psd(nSources, nBandFreqs);
437 for (
int s = 0; s < nSources; ++s) {
438 VectorXd fullPsd = computeRowPsd(stc.
data.row(s).transpose(), nFft, sfreq);
439 psd.row(s) = fullPsd.segment(fminIdx, nBandFreqs).transpose();
442 VectorXd bandFreqs = freqs.segment(fminIdx, nBandFreqs);
444 return QPair<MatrixXd, VectorXd>(psd, bandFreqs);
452 const QMap<QString, QPair<float, float>>& bands)
454 QMap<QString, VectorXd> result;
456 if (stc.
isEmpty() || bands.isEmpty()) {
465 const int nSources =
static_cast<int>(psd.rows());
466 const int nFreqs =
static_cast<int>(freqs.size());
467 double df = (nFreqs > 1) ? (freqs(1) - freqs(0)) : 1.0;
469 for (
auto it = bands.constBegin(); it != bands.constEnd(); ++it) {
470 float bfmin = it.value().first;
471 float bfmax = it.value().second;
473 VectorXd bandPower = VectorXd::Zero(nSources);
475 for (
int f = 0; f < nFreqs; ++f) {
476 if (freqs(f) >= bfmin && freqs(f) <= bfmax) {
477 bandPower += psd.col(f) * df;
481 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.
Forward solution (gain matrix mapping source dipoles to sensor measurements).
Container pairing the left and right cortical source spaces of a subject.
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 FIFFLIB::FiffEvoked applyForward(const MNELIB::MNEForwardSolution &fwd, const InvSourceEstimate &stc, const FIFFLIB::FiffInfo &info)
Project a source estimate to the sensors, as mne.apply_forward.
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
Full FIFF measurement info: per-channel descriptors, sampling and filter setup, projectors,...
FiffInfo pick_info(const Eigen::RowVectorXi &sel=defaultVectorXi) 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)
In-memory representation of an -fwd.fif forward solution.
void convert_to_surf_ori()
MNELIB::MNESourceSpaces src
bool isFixedOrient() const
FIFFLIB::FiffNamedMatrix::SDPtr sol
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
MNEHemisphere * hemisphereAt(qint32 idx)