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)
158 return evoked;
159
160 const int nChan = static_cast<int>(evoked.data.rows());
161 const int nTimes = static_cast<int>(evoked.data.cols());
162
163 // Add noise from covariance
164 if (noiseCov.data.size() > 0 && noiseCov.data.rows() == nChan) {
165 // Decompose noise covariance: Cov = V * D * V^T
166 // Noise samples: V * sqrt(D) * randn / sqrt(nave)
167 SelfAdjointEigenSolver<MatrixXd> solver(noiseCov.data);
168 VectorXd eigvals = solver.eigenvalues();
169 MatrixXd eigvecs = solver.eigenvectors();
170
171 // Clamp negative eigenvalues
172 for (int i = 0; i < eigvals.size(); ++i) {
173 if (eigvals(i) < 0)
174 eigvals(i) = 0;
175 }
176
177 MatrixXd sqrtCov = eigvecs * eigvals.cwiseSqrt().asDiagonal();
178
179 std::mt19937 gen(seed);
180 std::normal_distribution<double> dist(0.0, 1.0);
181
182 MatrixXd noise(nChan, nTimes);
183 for (int i = 0; i < nChan; ++i) {
184 for (int j = 0; j < nTimes; ++j) {
185 noise(i, j) = dist(gen);
186 }
187 }
188
189 double scaleFactor = 1.0 / std::sqrt(static_cast<double>(nave));
190 evoked.data += sqrtCov * noise * scaleFactor;
191 }
192
193 evoked.nave = nave;
194 return evoked;
195}
196
197//=============================================================================================================
198
200 const MNEForwardSolution& fwd,
201 const InvSourceEstimate& stc,
202 const FiffInfo& info)
203{
204 FiffEvoked evoked;
205
206 if (!fwd.sol || fwd.sol->data.size() == 0) {
207 qWarning() << "[simulateEvokedNoiseless] Forward solution has no gain matrix.";
208 return evoked;
209 }
210
211 if (stc.isEmpty()) {
212 qWarning() << "[simulateEvokedNoiseless] Source estimate is empty.";
213 return evoked;
214 }
215
216 MatrixXd G = fwd.sol->data; // (n_channels x n_dipoles)
217 const int nDipoles = static_cast<int>(G.cols());
218 const int nSrc = static_cast<int>(stc.data.rows());
219 const int nTimes = static_cast<int>(stc.data.cols());
220
221 if (nDipoles != nSrc) {
222 qWarning() << "[simulateEvokedNoiseless] Leadfield columns" << nDipoles
223 << "!= source estimate rows" << nSrc;
224 return evoked;
225 }
226
227 // Sensor data = G * stc.data
228 evoked.data = G * stc.data;
229
230 // Set times
231 evoked.times.resize(nTimes);
232 for (int t = 0; t < nTimes; ++t) {
233 evoked.times(t) = stc.tmin + t * stc.tstep;
234 }
235
236 evoked.first = 0;
237 evoked.last = nTimes - 1;
238 evoked.nave = 1;
239 evoked.comment = "Simulated";
240 evoked.info = info;
241
242 return evoked;
243}
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...
Noise / data covariance matrix as stored under FIFFB_MNE_COV, with channel names, kind,...
InvSourceEstimate value type — central source-space data container produced by every INVLIB inverse s...
Simulation utilities for source estimates and evoked data.
Forward solution (gain matrix mapping source dipoles to sensor measurements).
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:199
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:73
FIFF noise / data covariance: matrix, channel names, kind, applied projectors, bads,...
Definition fiff_cov.h:82
Eigen::MatrixXd data
Definition fiff_cov.h:253
Single averaged evoked response: time axis, data, baseline, channel info and averaging metadata.
Definition fiff_evoked.h:77
Eigen::RowVectorXf times
Eigen::MatrixXd data
Full FIFF measurement info: per-channel descriptors, sampling and filter setup, projectors,...
Definition fiff_info.h:90
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