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