v2.0.0
Loading...
Searching...
No Matches
simulate.cpp
Go to the documentation of this file.
1//=============================================================================================================
12
13//=============================================================================================================
14// INCLUDES
15//=============================================================================================================
16
17#include "simulate.h"
18
20#include <fiff/fiff_evoked.h>
21#include <fiff/fiff_info.h>
22#include <fiff/fiff_cov.h>
24
25//=============================================================================================================
26// QT INCLUDES
27//=============================================================================================================
28
29#include <QDebug>
30
31//=============================================================================================================
32// EIGEN INCLUDES
33//=============================================================================================================
34
35#include <Eigen/Dense>
36#include <Eigen/Eigenvalues>
37
38//=============================================================================================================
39// STL INCLUDES
40//=============================================================================================================
41
42#include <cmath>
43#include <random>
44
45//=============================================================================================================
46// USED NAMESPACES
47//=============================================================================================================
48
49using namespace UTILSLIB;
50using namespace INVLIB;
51using namespace FIFFLIB;
52using namespace MNELIB;
53using namespace Eigen;
54
55//=============================================================================================================
56// DEFINE FUNCTIONS
57//=============================================================================================================
58
60 const VectorXi& activeVertices,
61 const VectorXi& allVertices,
62 const SimulateStcParams& params)
63{
64 const int nAll = static_cast<int>(allVertices.size());
65 const int nActive = static_cast<int>(activeVertices.size());
66 const float tstep = 1.0f / params.sfreq;
67 const int nTimes = static_cast<int>(params.duration * params.sfreq);
68
69 if (nTimes <= 0 || nAll <= 0) {
70 qWarning() << "[simulateStc] Invalid parameters.";
71 return InvSourceEstimate();
72 }
73
74 // Build mapping: active vertex -> row in allVertices
75 QList<int> activeRows;
76 for (int a = 0; a < nActive; ++a) {
77 bool found = false;
78 for (int v = 0; v < nAll; ++v) {
79 if (allVertices(v) == activeVertices(a)) {
80 activeRows.append(v);
81 found = true;
82 break;
83 }
84 }
85 if (!found) {
86 qWarning() << "[simulateStc] Active vertex" << activeVertices(a) << "not in allVertices.";
87 return InvSourceEstimate();
88 }
89 }
90
91 // Generate Gaussian-envelope waveforms for each active source
92 MatrixXd data = MatrixXd::Zero(nAll, nTimes);
93
94 std::mt19937 gen(params.seed);
95 std::uniform_real_distribution<double> timeDist(0.2, 0.8);
96
97 for (int a = 0; a < nActive; ++a) {
98 // Center the Gaussian at a random fraction of the duration
99 double centerFrac = timeDist(gen);
100 double centerSample = centerFrac * nTimes;
101 double sigma = nTimes * 0.1; // 10% of duration
102
103 for (int t = 0; t < nTimes; ++t) {
104 double exponent = -0.5 * std::pow((t - centerSample) / sigma, 2.0);
105 data(activeRows[a], t) = std::exp(exponent);
106 }
107 }
108
109 return InvSourceEstimate(data, allVertices, params.tmin, tstep);
110}
111
112//=============================================================================================================
113
115 const MatrixXd& waveforms,
116 const VectorXi& activeVertices,
117 const VectorXi& allVertices,
118 float tmin,
119 float tstep)
120{
121 const int nAll = static_cast<int>(allVertices.size());
122 const int nActive = static_cast<int>(activeVertices.size());
123 const int nTimes = static_cast<int>(waveforms.cols());
124
125 if (waveforms.rows() != nActive) {
126 qWarning() << "[simulateStcFromWaveforms] waveforms.rows() != activeVertices.size()";
127 return InvSourceEstimate();
128 }
129
130 MatrixXd data = MatrixXd::Zero(nAll, nTimes);
131
132 for (int a = 0; a < nActive; ++a) {
133 for (int v = 0; v < nAll; ++v) {
134 if (allVertices(v) == activeVertices(a)) {
135 data.row(v) = waveforms.row(a);
136 break;
137 }
138 }
139 }
140
141 return InvSourceEstimate(data, allVertices, tmin, tstep);
142}
143
144//=============================================================================================================
145
147 const MNEForwardSolution& fwd,
148 const InvSourceEstimate& stc,
149 const FiffInfo& info,
150 const FiffCov& noiseCov,
151 int nave,
152 int seed)
153{
154 // First generate noiseless data
155 FiffEvoked evoked = simulateEvokedNoiseless(fwd, stc, info);
156
157 if (evoked.data.size() == 0) return evoked;
158
159 const int nChan = static_cast<int>(evoked.data.rows());
160 const int nTimes = static_cast<int>(evoked.data.cols());
161
162 // Add noise from covariance
163 if (noiseCov.data.size() > 0 && noiseCov.data.rows() == nChan) {
164 // Decompose noise covariance: Cov = V * D * V^T
165 // Noise samples: V * sqrt(D) * randn / sqrt(nave)
166 SelfAdjointEigenSolver<MatrixXd> solver(noiseCov.data);
167 VectorXd eigvals = solver.eigenvalues();
168 MatrixXd eigvecs = solver.eigenvectors();
169
170 // Clamp negative eigenvalues
171 for (int i = 0; i < eigvals.size(); ++i) {
172 if (eigvals(i) < 0) eigvals(i) = 0;
173 }
174
175 MatrixXd sqrtCov = eigvecs * eigvals.cwiseSqrt().asDiagonal();
176
177 std::mt19937 gen(seed);
178 std::normal_distribution<double> dist(0.0, 1.0);
179
180 MatrixXd noise(nChan, nTimes);
181 for (int i = 0; i < nChan; ++i) {
182 for (int j = 0; j < nTimes; ++j) {
183 noise(i, j) = dist(gen);
184 }
185 }
186
187 double scaleFactor = 1.0 / std::sqrt(static_cast<double>(nave));
188 evoked.data += sqrtCov * noise * scaleFactor;
189 }
190
191 evoked.nave = nave;
192 return evoked;
193}
194
195//=============================================================================================================
196
198 const MNEForwardSolution& fwd,
199 const InvSourceEstimate& stc,
200 const FiffInfo& info)
201{
202 FiffEvoked evoked;
203
204 if (!fwd.sol || fwd.sol->data.size() == 0) {
205 qWarning() << "[simulateEvokedNoiseless] Forward solution has no gain matrix.";
206 return evoked;
207 }
208
209 if (stc.isEmpty()) {
210 qWarning() << "[simulateEvokedNoiseless] Source estimate is empty.";
211 return evoked;
212 }
213
214 MatrixXd G = fwd.sol->data; // (n_channels x n_dipoles)
215 const int nChan = static_cast<int>(G.rows());
216 const int nDipoles = static_cast<int>(G.cols());
217 const int nSrc = static_cast<int>(stc.data.rows());
218 const int nTimes = static_cast<int>(stc.data.cols());
219
220 if (nDipoles != nSrc) {
221 qWarning() << "[simulateEvokedNoiseless] Leadfield columns" << nDipoles
222 << "!= source estimate rows" << nSrc;
223 return evoked;
224 }
225
226 // Sensor data = G * stc.data
227 evoked.data = G * stc.data;
228
229 // Set times
230 evoked.times.resize(nTimes);
231 for (int t = 0; t < nTimes; ++t) {
232 evoked.times(t) = stc.tmin + t * stc.tstep;
233 }
234
235 evoked.first = 0;
236 evoked.last = nTimes - 1;
237 evoked.nave = 1;
238 evoked.comment = "Simulated";
239 evoked.info = info;
240
241 return evoked;
242}
Forward solution (gain matrix mapping source dipoles to sensor measurements).
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.
InvSourceEstimate value type — central source-space data container produced by every INVLIB inverse s...
Simulation utilities for source estimates and evoked data.
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).
DSPSHARED_EXPORT INVLIB::InvSourceEstimate simulateStc(const Eigen::VectorXi &activeVertices, const Eigen::VectorXi &allVertices, const SimulateStcParams &params=SimulateStcParams())
Create a synthetic source time course.
DSPSHARED_EXPORT INVLIB::InvSourceEstimate simulateStcFromWaveforms(const Eigen::MatrixXd &waveforms, const Eigen::VectorXi &activeVertices, const Eigen::VectorXi &allVertices, float tmin=0.0f, float tstep=0.001f)
Create a synthetic source time course from custom waveforms.
DSPSHARED_EXPORT FIFFLIB::FiffEvoked simulateEvokedNoiseless(const MNELIB::MNEForwardSolution &fwd, const INVLIB::InvSourceEstimate &stc, const FIFFLIB::FiffInfo &info)
Simulate evoked data without noise.
Definition simulate.cpp:197
DSPSHARED_EXPORT FIFFLIB::FiffEvoked simulateEvoked(const MNELIB::MNEForwardSolution &fwd, const INVLIB::InvSourceEstimate &stc, const FIFFLIB::FiffInfo &info, const FIFFLIB::FiffCov &noiseCov, int nave=1, int seed=42)
Simulate evoked data from a source estimate and forward model.
Definition simulate.cpp:146
Inverse source estimation (MNE, dSPM, sLORETA, dipole fitting).
Parameters for source time course simulation.
Definition simulate.h:66
FIFF noise / data covariance: matrix, channel names, kind, applied projectors, bads,...
Definition fiff_cov.h:79
Eigen::MatrixXd data
Definition fiff_cov.h:248
Single averaged evoked response: time axis, data, baseline, channel info and averaging metadata.
Definition fiff_evoked.h:75
Eigen::RowVectorXf times
Eigen::MatrixXd data
Full FIFF measurement info: per-channel descriptors, sampling and filter setup, projectors,...
Definition fiff_info.h:88
Source-space inverse-solution container with dense grid plus optional focal-dipole,...
In-memory representation of an -fwd.fif forward solution.
FIFFLIB::FiffNamedMatrix::SDPtr sol