v2.0.0
Loading...
Searching...
No Matches
rtsensorinterpolationmatworker.cpp
Go to the documentation of this file.
1//=============================================================================================================
12
13//=============================================================================================================
14// INCLUDES
15//=============================================================================================================
16
18#include <fwd/fwd_field_map.h>
19
20#include <fiff/fiff_ch_info.h>
21#include <fiff/fiff_constants.h>
23#include <fwd/fwd_coil_set.h>
24#include <fs/fs_surface.h>
25
26#include <QCoreApplication>
27#include <QDebug>
28#include <QVector3D>
29#include <QMatrix4x4>
30#include <cmath>
31
32using namespace FIFFLIB;
33using namespace DISP3DLIB;
34
35//=============================================================================================================
36// ANONYMOUS HELPERS
37//=============================================================================================================
38
39namespace
40{
41
45Eigen::Vector3f applyTransform(const Eigen::Vector3f &point,
46 const FiffCoordTrans &trans)
47{
48 if (trans.isEmpty()) return point;
49 float r[3] = {point.x(), point.y(), point.z()};
51 return Eigen::Vector3f(r[0], r[1], r[2]);
52}
53
57QMatrix4x4 toQMatrix4x4(const Eigen::Matrix4f &m)
58{
59 QMatrix4x4 q;
60 for (int r = 0; r < 4; ++r)
61 for (int c = 0; c < 4; ++c)
62 q(r, c) = m(r, c);
63 return q;
64}
65
66} // anonymous namespace
67
68//=============================================================================================================
69// MEMBER METHODS
70//=============================================================================================================
71
73 : QObject(parent)
74{
75}
76
77//=============================================================================================================
78
80{
81 QMutexLocker locker(&m_mutex);
82 m_evoked = evoked;
83 m_hasEvoked = (evoked.data.rows() > 0 && evoked.data.cols() > 0);
84}
85
86//=============================================================================================================
87
89 bool applySensorTrans)
90{
91 QMutexLocker locker(&m_mutex);
92 m_headToMriTrans = trans;
93 m_applySensorTrans = applySensorTrans;
94}
95
96//=============================================================================================================
97
99{
100 QMutexLocker locker(&m_mutex);
101 m_megOnHead = onHead;
102}
103
104//=============================================================================================================
105
107 const Eigen::MatrixX3f &vertices,
108 const Eigen::MatrixX3f &normals,
109 const Eigen::MatrixX3i &triangles)
110{
111 QMutexLocker locker(&m_mutex);
112 m_megSurfaceKey = surfaceKey;
113 m_megVertices = vertices;
114 m_megNormals = normals;
115 m_megTriangles = triangles;
116 m_hasMegSurface = (vertices.rows() > 0);
117}
118
119//=============================================================================================================
120
122 const Eigen::MatrixX3f &vertices)
123{
124 QMutexLocker locker(&m_mutex);
125 m_eegSurfaceKey = surfaceKey;
126 m_eegVertices = vertices;
127 m_hasEegSurface = (vertices.rows() > 0);
128}
129
130//=============================================================================================================
131
133{
134 QMutexLocker locker(&m_mutex);
135 m_bads = bads;
136}
137
138//=============================================================================================================
139
141{
142 // ── Snapshot parameters under lock ─────────────────────────────────
143 FiffEvoked evoked;
144 FiffCoordTrans headToMriTrans;
145 bool applySensorTrans;
146 bool megOnHead;
147 QString megSurfaceKey, eegSurfaceKey;
148 Eigen::MatrixX3f megVerts, megNorms, eegVerts;
149 Eigen::MatrixX3i megTris;
150 bool hasMegSurface, hasEegSurface, hasEvoked;
151 QStringList bads;
152
153 {
154 QMutexLocker locker(&m_mutex);
155 evoked = m_evoked;
156 hasEvoked = m_hasEvoked;
157 headToMriTrans = m_headToMriTrans;
158 applySensorTrans = m_applySensorTrans;
159 megOnHead = m_megOnHead;
160 megSurfaceKey = m_megSurfaceKey;
161 eegSurfaceKey = m_eegSurfaceKey;
162 megVerts = m_megVertices;
163 megNorms = m_megNormals;
164 megTris = m_megTriangles;
165 hasMegSurface = m_hasMegSurface;
166 eegVerts = m_eegVertices;
167 hasEegSurface = m_hasEegSurface;
168 bads = m_bads;
169 }
170
171 if (!hasEvoked) {
172 qDebug() << "RtSensorInterpolationMatWorker: No evoked data set, skipping.";
173 return;
174 }
175
176 qDebug() << "RtSensorInterpolationMatWorker: Computing mapping matrices...";
177
178 // ── Build coordinate transforms ────────────────────────────────────
179 bool hasDevHead = false;
180 QMatrix4x4 devHeadQt;
181 if (!evoked.info.dev_head_t.isEmpty() &&
184 !evoked.info.dev_head_t.trans.isIdentity()) {
185 hasDevHead = true;
186 devHeadQt = toQMatrix4x4(evoked.info.dev_head_t.trans);
187 }
188
189 QMatrix4x4 headToMri;
190 if (applySensorTrans && !headToMriTrans.isEmpty()) {
191 headToMri = toQMatrix4x4(headToMriTrans.trans);
192 }
193
194 // ── Classify channels ──────────────────────────────────────────────
195 QList<FiffChInfo> megChs, eegChs;
196 QVector<int> megPick, eegPick;
197
198 for (int k = 0; k < evoked.info.chs.size(); ++k) {
199 const auto &ch = evoked.info.chs[k];
200 if (bads.contains(ch.ch_name)) continue;
201
202 QVector3D pos(ch.chpos.r0(0), ch.chpos.r0(1), ch.chpos.r0(2));
203
204 if (ch.kind == FIFFV_MEG_CH) {
205 if (hasDevHead) pos = devHeadQt.map(pos);
206 if (applySensorTrans && !headToMriTrans.isEmpty()) pos = headToMri.map(pos);
207 megPick.append(k);
208 megChs.append(ch);
209 } else if (ch.kind == FIFFV_EEG_CH) {
210 if (applySensorTrans && !headToMriTrans.isEmpty()) pos = headToMri.map(pos);
211 eegPick.append(k);
212 eegChs.append(ch);
213 }
214 }
215
216 // ── Constants (matching MNE-Python) ────────────────────────────────
217 constexpr float kIntrad = 0.06f;
218 constexpr float kMegMiss = 1e-4f;
219 constexpr float kEegMiss = 1e-3f;
220 const Eigen::Vector3f defaultOrigin(0.0f, 0.0f, 0.04f);
221
222 FiffCoordTrans headMri = (applySensorTrans && !headToMriTrans.isEmpty())
223 ? headToMriTrans : FiffCoordTrans();
224 FiffCoordTrans devHead = (!evoked.info.dev_head_t.isEmpty() &&
227 ? evoked.info.dev_head_t : FiffCoordTrans();
228
229 // ── MEG mapping ────────────────────────────────────────────────────
230 if (hasMegSurface && !megChs.isEmpty()) {
231 Eigen::MatrixX3f norms = megNorms;
232
233 // Recompute normals if missing
234 if (norms.rows() != megVerts.rows()) {
235 if (megTris.rows() > 0) {
236 norms = FSLIB::FsSurface::compute_normals(megVerts, megTris);
237 }
238 }
239
240 if (megVerts.rows() > 0 && norms.rows() == megVerts.rows()) {
241 const QString coilPath = QCoreApplication::applicationDirPath()
242 + "/../resources/general/coilDefinitions/coil_def.dat";
243 auto templates =
245
246 if (templates) {
247 FiffCoordTrans devToTarget;
248 if (megOnHead && !headMri.isEmpty()) {
249 if (!devHead.isEmpty()) {
250 devToTarget = FiffCoordTrans::combine(
252 devHead, headMri);
253 }
254 } else if (!devHead.isEmpty()) {
255 devToTarget = devHead;
256 }
257
258 Eigen::Vector3f origin = defaultOrigin;
259 if (megOnHead && !headMri.isEmpty()) {
260 origin = applyTransform(origin, headMri);
261 }
262
263 auto coils = templates->create_meg_coils(
264 megChs, megChs.size(), FWDLIB::FWD_COIL_ACCURACY_NORMAL, devToTarget);
265
266 if (coils && coils->ncoil() > 0) {
268 *coils, megVerts, norms, origin, kIntrad, kMegMiss);
269
270 if (mat && mat->rows() > 0) {
271 qDebug() << "RtSensorInterpolationMatWorker: MEG mapping computed:"
272 << mat->rows() << "x" << mat->cols();
273 emit newMegMappingAvailable(megSurfaceKey,
274 std::shared_ptr<Eigen::MatrixXf>(std::move(mat)), megPick);
275 }
276 }
277 }
278 }
279 }
280
281 // ── EEG mapping ────────────────────────────────────────────────────
282 if (hasEegSurface && !eegChs.isEmpty()) {
283 if (eegVerts.rows() > 0) {
284 Eigen::Vector3f origin = defaultOrigin;
285 if (!headMri.isEmpty()) origin = applyTransform(origin, headMri);
286
287 auto eegCoils =
289 eegChs, eegChs.size(), headMri);
290
291 if (eegCoils && eegCoils->ncoil() > 0) {
293 *eegCoils, eegVerts, origin, kIntrad, kEegMiss);
294
295 if (mat && mat->rows() > 0) {
296 qDebug() << "RtSensorInterpolationMatWorker: EEG mapping computed:"
297 << mat->rows() << "x" << mat->cols();
298 emit newEegMappingAvailable(eegSurfaceKey,
299 std::shared_ptr<Eigen::MatrixXf>(std::move(mat)), eegPick);
300 }
301 }
302 }
303 }
304
305 qDebug() << "RtSensorInterpolationMatWorker: Mapping computation complete.";
306}
Background worker that recomputes the dense MEG / EEG sensor-to-surface mapping matrix off the GUI th...
Reader and in-memory representation of a single FreeSurfer triangular surface (e.g....
Sphere-model field interpolator that maps measured MEG/EEG values onto a dense scalp or cortical surf...
Container of FwdCoil instances representing either a sensor-type template database or a concrete per-...
return FiffCoordTrans(from_frame, to_frame, R, moveVec)
Symbolic FIFF tag, block, value, unit and channel-type constants shared across FIFFLIB.
#define FIFFV_EEG_CH
#define FIFFV_COORD_DEVICE
#define FIFFV_MEG_CH
#define FIFFV_COORD_HEAD
#define FIFFV_COORD_MRI
#define FIFFV_MOVE
FIFF channel descriptor record (FIFF_CH_INFO): per-channel logical/scanner numbers,...
4x4 affine FIFF coordinate transform (FIFF_COORD_TRANS) annotated with source/destination coordinate-...
FIFF file I/O, in-memory data structures and high-level readers/writers.
QMatrix4x4 toQMatrix4x4(const Eigen::Matrix4f &m)
3-D brain visualisation using the Qt RHI rendering backend.
constexpr int FWD_COIL_ACCURACY_NORMAL
Definition fwd_coil.h:76
void setEvoked(const FIFFLIB::FiffEvoked &evoked)
void newEegMappingAvailable(const QString &surfaceKey, std::shared_ptr< Eigen::MatrixXf > mappingMat, const QVector< int > &pick)
void setMegSurface(const QString &surfaceKey, const Eigen::MatrixX3f &vertices, const Eigen::MatrixX3f &normals, const Eigen::MatrixX3i &triangles)
void setTransform(const FIFFLIB::FiffCoordTrans &trans, bool applySensorTrans)
void newMegMappingAvailable(const QString &surfaceKey, std::shared_ptr< Eigen::MatrixXf > mappingMat, const QVector< int > &pick)
void setEegSurface(const QString &surfaceKey, const Eigen::MatrixX3f &vertices)
Labelled 4x4 FIFF affine: source frame, destination frame, rotation, translation and cached inverse.
static FiffCoordTrans combine(int from, int to, const FiffCoordTrans &t1, const FiffCoordTrans &t2)
Eigen::MatrixX3f apply_trans(const Eigen::MatrixX3f &rr, bool do_move=true) const
Eigen::Matrix< float, 4, 4, Eigen::DontAlign > trans
Single averaged evoked response: time axis, data, baseline, channel info and averaging metadata.
Definition fiff_evoked.h:75
Eigen::MatrixXd data
QList< FiffChInfo > chs
FiffCoordTrans dev_head_t
static Eigen::MatrixX3f compute_normals(const Eigen::MatrixX3f &rr, const Eigen::MatrixX3i &tris)
static FwdCoilSet::UPtr read_coil_defs(const QString &name)
static FwdCoilSet::UPtr create_eeg_els(const QList< FIFFLIB::FiffChInfo > &chs, int nch, const FIFFLIB::FiffCoordTrans &t=FIFFLIB::FiffCoordTrans())
static std::unique_ptr< Eigen::MatrixXf > computeEegMapping(const FwdCoilSet &coils, const Eigen::MatrixX3f &vertices, const Eigen::Vector3f &origin, float intrad=0.06f, float miss=1e-3f)
static std::unique_ptr< Eigen::MatrixXf > computeMegMapping(const FwdCoilSet &coils, const Eigen::MatrixX3f &vertices, const Eigen::MatrixX3f &normals, const Eigen::Vector3f &origin, float intrad=0.06f, float miss=1e-4f)