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}
DataLoader — static helpers for loading MNE data files.
FsSurface key constants and type-to-key mappings.
BrainSurface class declaration.
SensorTreeItem class declaration.
FiffDigPointSet class declaration.
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
FIFF class declaration, which provides static wrapper functions to stay consistent with mne matlab to...
FsSurface class declaration.
MNEBem class declaration.
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)