v2.0.0
Loading...
Searching...
No Matches
dataloader.cpp
Go to the documentation of this file.
1//=============================================================================================================
34
35//=============================================================================================================
36// INCLUDES
37//=============================================================================================================
38
39#include "dataloader.h"
40#include "core/surfacekeys.h"
43
44#include <QFile>
45#include <QFileInfo>
46#include <QDebug>
47#include <QCoreApplication>
48
49#include <Eigen/Dense>
50#include <fiff/fiff.h>
51#include <fiff/fiff_constants.h>
52#include <fiff/fiff_stream.h>
54#include <mne/mne_bem.h>
55#include <fs/fs_surface.h>
56
57using namespace FIFFLIB;
58using namespace MNELIB;
59using namespace INVLIB;
60
61//=============================================================================================================
62// STATIC METHODS
63//=============================================================================================================
64
66 const QString &megHelmetOverridePath)
67{
68 SensorLoadResult result;
69
70 QFile file(fifPath);
71 if (!file.exists()) return result;
72
73 FiffInfo info;
74 FiffDigPointSet digSet;
75 FiffStream::SPtr stream(new FiffStream(&file));
76
77 if (stream->open()) {
78 FiffDirNode::SPtr tree = stream->dirtree();
79 FiffDirNode::SPtr nodeInfo;
80 if (stream->read_meas_info(tree, info, nodeInfo)) {
81 result.hasInfo = !info.isEmpty();
82 }
83 }
84
85 if (result.hasInfo) {
86 result.info = info;
87
88 // Prepare Device->Head transformation
89 QMatrix4x4 devHeadQTrans;
90 bool hasDevHead = false;
91 if (!info.dev_head_t.isEmpty() &&
94 !info.dev_head_t.trans.isIdentity()) {
95 hasDevHead = true;
96 devHeadQTrans = SURFACEKEYS::toQMatrix4x4(info.dev_head_t.trans);
97 }
98
99 result.devHeadTrans = devHeadQTrans;
100 result.hasDevHead = hasDevHead;
101
102 for (const auto &ch : info.chs) {
103 if (ch.kind == FIFFV_MEG_CH) {
104 QVector3D pos(ch.chpos.r0(0), ch.chpos.r0(1), ch.chpos.r0(2));
105
106 if (hasDevHead) {
107 pos = devHeadQTrans.map(pos);
108 }
109
110 auto *item = new SensorTreeItem(ch.ch_name, pos, QColor(100, 100, 100), 0.01f);
111
112 // Store coil orientation
113 QMatrix4x4 orient;
114 for (int r = 0; r < 3; ++r)
115 for (int c = 0; c < 3; ++c)
116 orient(r, c) = ch.coil_trans(r, c);
117 if (hasDevHead) {
118 QMatrix4x4 devHeadRot;
119 for (int r = 0; r < 3; ++r)
120 for (int c = 0; c < 3; ++c)
121 devHeadRot(r, c) = devHeadQTrans(r, c);
122 orient = devHeadRot * orient;
123 }
124 item->setOrientation(orient);
125
126 if (ch.unit == FIFF_UNIT_T_M) {
127 result.megGradItems.append(item);
128 } else {
129 result.megMagItems.append(item);
130 }
131 } else if (ch.kind == FIFFV_EEG_CH) {
132 QVector3D pos(ch.chpos.r0(0), ch.chpos.r0(1), ch.chpos.r0(2));
133 result.eegItems.append(new SensorTreeItem(ch.ch_name, pos, QColor(0, 200, 220), 0.002f));
134 }
135 }
136
137 // ── MEG helmet surface ──────────────────────────────────────
138 if (!result.megGradItems.isEmpty() || !result.megMagItems.isEmpty()) {
139 auto pickHelmetFile = [&info]() -> QString {
140 int coilType = -1;
141 int nMeg = 0;
142 for (const auto &ch : info.chs) {
143 if (ch.kind == FIFFV_MEG_CH) {
144 coilType = ch.chpos.coil_type & 0xFFFF;
145 ++nMeg;
146 }
147 }
148
149 QString fileName = "306m.fif";
150 if (coilType == FIFFV_COIL_BABY_GRAD) {
151 fileName = "BabySQUID.fif";
152 } else if (coilType == FIFFV_COIL_NM_122) {
153 fileName = "122m.fif";
154 } else if (coilType == FIFFV_COIL_CTF_GRAD) {
155 fileName = "CTF_275.fif";
156 } else if (coilType == FIFFV_COIL_KIT_GRAD) {
157 fileName = "KIT.fif";
158 } else if (coilType == FIFFV_COIL_MAGNES_MAG || coilType == FIFFV_COIL_MAGNES_GRAD) {
159 fileName = (nMeg > 150) ? "Magnes_3600wh.fif" : "Magnes_2500wh.fif";
160 } else if (coilType / 1000 == 3) {
161 fileName = "306m.fif";
162 }
163
164 return QCoreApplication::applicationDirPath()
165 + "/../resources/general/sensorSurfaces/" + fileName;
166 };
167
168 QString helmetPath;
169 if (!megHelmetOverridePath.isEmpty()) {
170 helmetPath = megHelmetOverridePath;
171 if (!QFile::exists(helmetPath)) {
172 qWarning() << "MEG helmet override file not found:" << helmetPath
173 << "- falling back to auto selection.";
174 helmetPath.clear();
175 }
176 }
177
178 if (helmetPath.isEmpty()) {
179 helmetPath = pickHelmetFile();
180#ifdef __EMSCRIPTEN__
181 // WASM: applicationDirPath()/../ may not resolve correctly.
182 // Try the preloaded virtual FS path directly.
183 if (!QFile::exists(helmetPath)) {
184 QString fn = QFileInfo(helmetPath).fileName();
185 if (fn.isEmpty()) fn = QStringLiteral("306m.fif");
186 helmetPath = QStringLiteral("/resources/general/sensorSurfaces/") + fn;
187 }
188#endif
189 }
190
191 if (!QFile::exists(helmetPath)) {
192 QString fallback = QCoreApplication::applicationDirPath()
193 + "/../resources/general/sensorSurfaces/306m.fif";
194 if (QFile::exists(fallback)) {
195 helmetPath = fallback;
196 }
197 }
198
199#ifdef __EMSCRIPTEN__
200 // WASM fallback: sensor surfaces are preloaded into the
201 // Emscripten virtual FS at /resources/general/sensorSurfaces/.
202 // applicationDirPath() may not resolve the /../ correctly.
203 if (!QFile::exists(helmetPath)) {
204 QString wasmFallback = QStringLiteral("/resources/general/sensorSurfaces/")
205 + QFileInfo(helmetPath).fileName();
206 if (wasmFallback.endsWith('/')) wasmFallback += "306m.fif";
207 if (QFile::exists(wasmFallback)) {
208 helmetPath = wasmFallback;
209 }
210 }
211#endif
212
213 if (!QFile::exists(helmetPath)) {
214 qWarning() << "MEG helmet surface file not found. Checked:" << helmetPath;
215 }
216
217 if (QFile::exists(helmetPath)) {
218 QFile helmetFile(helmetPath);
219 MNEBem helmetBem(helmetFile);
220 if (helmetBem.size() > 0) {
221 MNEBemSurface helmetSurf = helmetBem[0];
222 if (helmetSurf.nn.rows() != helmetSurf.rr.rows()) {
223 helmetSurf.nn = FSLIB::FsSurface::compute_normals(Eigen::MatrixX3f(helmetSurf.rr), Eigen::MatrixX3i(helmetSurf.itris));
224 }
225
226 if (hasDevHead) {
227 QMatrix3x3 normalMat = devHeadQTrans.normalMatrix();
228 for (int i = 0; i < helmetSurf.rr.rows(); ++i) {
229 QVector3D pos(helmetSurf.rr(i, 0), helmetSurf.rr(i, 1), helmetSurf.rr(i, 2));
230 pos = devHeadQTrans.map(pos);
231 helmetSurf.rr(i, 0) = pos.x();
232 helmetSurf.rr(i, 1) = pos.y();
233 helmetSurf.rr(i, 2) = pos.z();
234
235 QVector3D nn(helmetSurf.nn(i, 0), helmetSurf.nn(i, 1), helmetSurf.nn(i, 2));
236 const float *d = normalMat.constData();
237 float nx = d[0] * nn.x() + d[3] * nn.y() + d[6] * nn.z();
238 float ny = d[1] * nn.x() + d[4] * nn.y() + d[7] * nn.z();
239 float nz = d[2] * nn.x() + d[5] * nn.y() + d[8] * nn.z();
240 QVector3D n = QVector3D(nx, ny, nz).normalized();
241 helmetSurf.nn(i, 0) = n.x();
242 helmetSurf.nn(i, 1) = n.y();
243 helmetSurf.nn(i, 2) = n.z();
244 }
245 }
246
247 auto helmetSurface = std::make_shared<BrainSurface>();
248 helmetSurface->fromBemSurface(helmetSurf, QColor(0, 0, 77, 200));
249 helmetSurface->setVisible(true);
250 result.helmetSurface = helmetSurface;
251 } else {
252 qWarning() << "DataLoader::loadSensors: helmetBem[0] has 0 verts/tris!";
253 }
254 }
255 }
256 }
257
258 // ── Digitizer points ────────────────────────────────────────────
259 if (result.hasInfo && info.dig.size() > 0) {
260 result.digitizerPoints = info.dig;
261 result.hasDigitizer = true;
262 } else if (!result.hasInfo) {
263 file.reset();
264 digSet = FiffDigPointSet(file);
265 if (digSet.size() > 0) {
266 for (int i = 0; i < digSet.size(); ++i) {
267 result.digitizerPoints.append(digSet[i]);
268 }
269 result.hasDigitizer = true;
270 }
271 }
272
273 return result;
274}
275
276//=============================================================================================================
277
278std::shared_ptr<BrainSurface> DataLoader::loadHelmetSurface(
279 const QString &helmetFilePath,
280 const QMatrix4x4 &devHeadTrans,
281 bool applyTrans)
282{
283 if (!QFile::exists(helmetFilePath)) {
284 qWarning() << "DataLoader::loadHelmetSurface: file not found:" << helmetFilePath;
285 return nullptr;
286 }
287
288 QFile helmetFile(helmetFilePath);
289 MNEBem helmetBem(helmetFile);
290 if (helmetBem.size() == 0) {
291 qWarning() << "DataLoader::loadHelmetSurface: no BEM surfaces in" << helmetFilePath;
292 return nullptr;
293 }
294
295 MNEBemSurface helmetSurf = helmetBem[0];
296
297 if (helmetSurf.nn.rows() != helmetSurf.rr.rows()) {
298 helmetSurf.nn = FSLIB::FsSurface::compute_normals(Eigen::MatrixX3f(helmetSurf.rr), Eigen::MatrixX3i(helmetSurf.itris));
299 }
300
301 if (applyTrans) {
302 QMatrix3x3 normalMat = devHeadTrans.normalMatrix();
303 for (int i = 0; i < helmetSurf.rr.rows(); ++i) {
304 QVector3D pos(helmetSurf.rr(i, 0), helmetSurf.rr(i, 1), helmetSurf.rr(i, 2));
305 pos = devHeadTrans.map(pos);
306 helmetSurf.rr(i, 0) = pos.x();
307 helmetSurf.rr(i, 1) = pos.y();
308 helmetSurf.rr(i, 2) = pos.z();
309
310 QVector3D nn(helmetSurf.nn(i, 0), helmetSurf.nn(i, 1), helmetSurf.nn(i, 2));
311 const float *d = normalMat.constData();
312 float nx = d[0] * nn.x() + d[3] * nn.y() + d[6] * nn.z();
313 float ny = d[1] * nn.x() + d[4] * nn.y() + d[7] * nn.z();
314 float nz = d[2] * nn.x() + d[5] * nn.y() + d[8] * nn.z();
315 QVector3D n = QVector3D(nx, ny, nz).normalized();
316 helmetSurf.nn(i, 0) = n.x();
317 helmetSurf.nn(i, 1) = n.y();
318 helmetSurf.nn(i, 2) = n.z();
319 }
320 }
321
322 auto surface = std::make_shared<BrainSurface>();
323 surface->fromBemSurface(helmetSurf, QColor(0, 0, 77, 200));
324 surface->setVisible(true);
325
326 return surface;
327}
328
329//=============================================================================================================
330
331InvEcdSet DataLoader::loadDipoles(const QString &dipPath)
332{
333 InvEcdSet ecdSet = InvEcdSet::read_dipoles_dip(dipPath);
334 if (ecdSet.size() == 0) {
335 qWarning() << "DataLoader: Failed to load dipoles from" << dipPath;
336 }
337 return ecdSet;
338}
339
340//=============================================================================================================
341
343{
344 QFile file(fwdPath);
345 if (!file.exists()) {
346 qWarning() << "DataLoader: Source space file not found:" << fwdPath;
347 return {};
348 }
349
350 MNESourceSpaces srcSpace;
351 FiffStream::SPtr stream(new FiffStream(&file));
352 if (!stream->open()) {
353 qWarning() << "DataLoader: Failed to open FIF stream for source space";
354 return {};
355 }
356
357 if (!MNESourceSpaces::readFromStream(stream, true, srcSpace)) {
358 qWarning() << "DataLoader: Failed to read source space from" << fwdPath;
359 return {};
360 }
361
362 if (srcSpace.isEmpty()) {
363 qWarning() << "DataLoader: Source space is empty";
364 return {};
365 }
366
367 return srcSpace;
368}
369
370//=============================================================================================================
371
372bool DataLoader::loadHeadToMriTransform(const QString &transPath,
373 FiffCoordTrans &trans)
374{
375 QFile file(transPath);
376
377 FiffCoordTrans raw;
378 if (!FiffCoordTrans::read(file, raw)) {
379 qWarning() << "DataLoader: Failed to load transformation from" << transPath;
380 return false;
381 }
382 file.close();
383
384 if (raw.from == FIFFV_COORD_HEAD && raw.to == FIFFV_COORD_MRI) {
385 trans = raw;
386 } else if (raw.from == FIFFV_COORD_MRI && raw.to == FIFFV_COORD_HEAD) {
387 // Invert: MRI->Head becomes Head->MRI
388 trans.from = raw.to;
389 trans.to = raw.from;
390 trans.trans = raw.trans.inverse();
391 trans.invtrans = raw.trans;
392 } else {
393 qWarning() << "DataLoader: Loaded transformation is not Head<->MRI (from"
394 << raw.from << "to" << raw.to << "). Using as is.";
395 trans = raw;
396 }
397
398 return true;
399}
400
401//=============================================================================================================
402
403FiffEvoked DataLoader::loadEvoked(const QString &evokedPath, int aveIndex)
404{
405 QFile file(evokedPath);
406 if (!file.exists()) {
407 qWarning() << "DataLoader: Sensor evoked file not found:" << evokedPath;
408 return {};
409 }
410
411 FiffEvoked evoked(file, aveIndex);
412 if (evoked.isEmpty()) {
413 qWarning() << "DataLoader: Failed to read evoked data from" << evokedPath;
414 }
415 return evoked;
416}
417
418//=============================================================================================================
419
420QStringList DataLoader::probeEvokedSets(const QString &evokedPath)
421{
422 QStringList result;
423 QFile file(evokedPath);
424 if (!file.exists()) {
425 return result;
426 }
427
428 FiffEvokedSet evokedSet(file);
429 for (int i = 0; i < evokedSet.evoked.size(); ++i) {
430 const auto &ev = evokedSet.evoked.at(i);
431 QString label = QString("%1: %2 (%3, nave=%4)")
432 .arg(i)
433 .arg(ev.comment.isEmpty() ? QStringLiteral("Set %1").arg(i) : ev.comment)
434 .arg(ev.aspectKindToString())
435 .arg(ev.nave);
436 result.append(label);
437 }
438 return result;
439}
MNEBem class declaration.
FiffDigPointSet class declaration.
FIFF class declaration, which provides static wrapper functions to stay consistent with mne matlab to...
FiffStream class declaration.
Fiff constants.
#define FIFFV_COIL_CTF_GRAD
#define FIFFV_EEG_CH
#define FIFFV_COORD_DEVICE
#define FIFFV_COIL_NM_122
#define FIFFV_COIL_MAGNES_MAG
#define FIFFV_MEG_CH
#define FIFFV_COIL_BABY_GRAD
#define FIFFV_COORD_HEAD
#define FIFFV_COORD_MRI
#define FIFFV_COIL_MAGNES_GRAD
#define FIFFV_COIL_KIT_GRAD
#define FIFF_UNIT_T_M
FsSurface class declaration.
SensorTreeItem class declaration.
BrainSurface class declaration.
FsSurface key constants and type-to-key mappings.
DataLoader — static helpers for loading MNE data files.
Core MNE data structures (source spaces, source estimates, hemispheres).
FIFF file I/O and data structures (raw, epochs, evoked, covariance, forward).
QMatrix4x4 toQMatrix4x4(const Eigen::Matrix4f &m)
Inverse source estimation (MNE, dSPM, sLORETA, dipole fitting).
static SensorLoadResult loadSensors(const QString &fifPath, const QString &megHelmetOverridePath={})
static QStringList probeEvokedSets(const QString &evokedPath)
static MNELIB::MNESourceSpaces loadSourceSpace(const QString &fwdPath)
static bool loadHeadToMriTransform(const QString &transPath, FIFFLIB::FiffCoordTrans &trans)
static std::shared_ptr< BrainSurface > loadHelmetSurface(const QString &helmetFilePath, const QMatrix4x4 &devHeadTrans=QMatrix4x4(), bool applyTrans=false)
static INVLIB::InvEcdSet loadDipoles(const QString &dipPath)
static FIFFLIB::FiffEvoked loadEvoked(const QString &evokedPath, int aveIndex=0)
Return value bundling loaded sensor geometry, labels, and channel-to-sensor mapping.
Definition dataloader.h:87
std::shared_ptr< BrainSurface > helmetSurface
May be null.
Definition dataloader.h:97
QList< QStandardItem * > megGradItems
Ownership passes to caller.
Definition dataloader.h:92
FIFFLIB::FiffInfo info
Channel / dig info.
Definition dataloader.h:91
QMatrix4x4 devHeadTrans
Device→Head transform (identity if absent).
Definition dataloader.h:99
QList< QStandardItem * > eegItems
Definition dataloader.h:94
bool hasDevHead
Whether a valid dev→head transform was found.
Definition dataloader.h:100
QList< FIFFLIB::FiffDigPoint > digitizerPoints
Definition dataloader.h:95
QList< QStandardItem * > megMagItems
Definition dataloader.h:93
Tree item representing MEG or EEG sensor positions in the 3-D scene hierarchy.
Coordinate transformation description.
Eigen::Matrix< float, 4, 4, Eigen::DontAlign > trans
Eigen::Matrix< float, 4, 4, Eigen::DontAlign > invtrans
Holds a set of digitizer points.
QSharedPointer< FiffDirNode > SPtr
bool isEmpty() const
QList< FiffEvoked > evoked
FIFF measurement file information.
Definition fiff_info.h:86
QList< FiffDigPoint > dig
Definition fiff_info.h:273
QList< FiffChInfo > chs
FiffCoordTrans dev_head_t
FIFF File I/O routines.
QSharedPointer< FiffStream > SPtr
static Eigen::MatrixX3f compute_normals(const Eigen::MatrixX3f &rr, const Eigen::MatrixX3i &tris)
Holds a set of Electric Current Dipoles.
Definition inv_ecd_set.h:81
qint32 size() const
static InvEcdSet read_dipoles_dip(const QString &fileName)
BEM descritpion.
Definition mne_bem.h:90
qint32 size() const
Definition mne_bem.h:271
BEM surface provides geometry information.
Source Space descritpion.
static bool readFromStream(FIFFLIB::FiffStream::SPtr &p_pStream, bool add_geom, MNESourceSpaces &p_SourceSpace)
static bool read(QIODevice &p_IODevice, FiffCoordTrans &p_Trans)