v2.0.0
Loading...
Searching...
No Matches
fiff_evoked_set.cpp
Go to the documentation of this file.
1//=============================================================================================================
21
22//=============================================================================================================
23// INCLUDES
24//=============================================================================================================
25
26#include "fiff_evoked_set.h"
27#include "fiff_events.h"
28#include "fiff_raw_data.h"
29#include "fiff_tag.h"
30#include "fiff_dir_node.h"
31#include "fiff_stream.h"
32#include "fiff_file.h"
33#include "fiff_constants.h"
34
35//=============================================================================================================
36// EIGEN INCLUDES
37//=============================================================================================================
38
39#include <Eigen/SparseCore>
40
41#include <cmath>
42
43//=============================================================================================================
44// QT INCLUDES
45//=============================================================================================================
46
47#include <QFile>
48#include <QDebug>
49
50#include <stdexcept>
51//=============================================================================================================
52// USED NAMESPACES
53//=============================================================================================================
54
55using namespace FIFFLIB;
56using namespace Eigen;
57
58//=============================================================================================================
59// DEFINE MEMBER METHODS
60//=============================================================================================================
61
63{
64 qRegisterMetaType<FIFFLIB::FiffEvokedSet>("FIFFLIB::FiffEvokedSet");
65 qRegisterMetaType<FIFFLIB::FiffEvokedSet::SPtr>("FIFFLIB::FiffEvokedSet::SPtr");
66}
67
68//=============================================================================================================
69
70FiffEvokedSet::FiffEvokedSet(QIODevice& p_IODevice)
71{
72 qRegisterMetaType<FIFFLIB::FiffEvokedSet>("FIFFLIB::FiffEvokedSet");
73 qRegisterMetaType<FIFFLIB::FiffEvokedSet::SPtr>("FIFFLIB::FiffEvokedSet::SPtr");
74
75 if(!FiffEvokedSet::read(p_IODevice, *this))
76 {
77 throw std::runtime_error("Fiff evoked data set not found");
78 }
79}
80
81//=============================================================================================================
82
83FiffEvokedSet::FiffEvokedSet(const FiffEvokedSet& p_FiffEvokedSet)
84: info(p_FiffEvokedSet.info)
85, evoked(p_FiffEvokedSet.evoked)
86{
87}
88
89//=============================================================================================================
90
92{
93}
94
95//=============================================================================================================
96
98{
99 info.clear();
100 evoked.clear();
101}
102
103//=============================================================================================================
104
105FiffEvokedSet FiffEvokedSet::pick_channels(const QStringList& include,
106 const QStringList& exclude) const
107{
108 FiffEvokedSet res;
109
110 //
111 // Update info to match the channel selection
112 //
113 RowVectorXi sel = FiffInfo::pick_channels(this->info.ch_names, include, exclude);
114 if (sel.cols() > 0) {
115 res.info = this->info.pick_info(sel);
116 } else {
117 res.info = this->info;
118 }
119
120 QList<FiffEvoked>::ConstIterator ev;
121 for(ev = evoked.begin(); ev != evoked.end(); ++ev)
122 res.evoked.push_back(ev->pick_channels(include, exclude));
123
124 return res;
125}
126
127//=============================================================================================================
128
129bool FiffEvokedSet::compensate_to(FiffEvokedSet& p_FiffEvokedSet,
130 fiff_int_t to) const
131{
132 qint32 now = p_FiffEvokedSet.info.get_current_comp();
133 FiffCtfComp ctf_comp;
134
135 if(now == to)
136 {
137 qInfo("Data is already compensated as desired.\n");
138 return false;
139 }
140
141 //Make the compensator and apply it to all data sets
142 p_FiffEvokedSet.info.make_compensator(now,to,ctf_comp);
143
144 for(qsizetype i=0; i < p_FiffEvokedSet.evoked.size(); ++i)
145 {
146 p_FiffEvokedSet.evoked[i].data = ctf_comp.data->data*p_FiffEvokedSet.evoked[i].data;
147 }
148
149 //Update the compensation info in the channel descriptors
150 p_FiffEvokedSet.info.set_current_comp(to);
151
152 return true;
153}
154
155//=============================================================================================================
156
157bool FiffEvokedSet::find_evoked(const FiffEvokedSet& p_FiffEvokedSet) const
158{
159 if(!p_FiffEvokedSet.evoked.size()) {
160 qWarning("No evoked response data sets in %s\n",p_FiffEvokedSet.info.filename.toUtf8().constData());
161 return false;
162 }
163 else
164 qInfo("\nFound %lld evoked response data sets in %s :\n",p_FiffEvokedSet.evoked.size(),p_FiffEvokedSet.info.filename.toUtf8().constData());
165
166 for(qint32 i = 0; i < p_FiffEvokedSet.evoked.size(); ++i) {
167 qInfo("%s (%s)\n",p_FiffEvokedSet.evoked.at(i).comment.toUtf8().constData(),p_FiffEvokedSet.evoked.at(i).aspectKindToString().toUtf8().constBegin());
168 }
169
170 return true;
171}
172
173//=============================================================================================================
174
175bool FiffEvokedSet::read(QIODevice& p_IODevice,
176 FiffEvokedSet& p_FiffEvokedSet, QPair<float,float> baseline,
177 bool proj)
178{
179 p_FiffEvokedSet.clear();
180
181 //
182 // Open the file
183 //
184 FiffStream::SPtr t_pStream(new FiffStream(&p_IODevice));
185 QString t_sFileName = t_pStream->streamName();
186
187 qInfo("Exploring %s ...\n",t_sFileName.toUtf8().constData());
188
189 if(!t_pStream->open())
190 return false;
191 //
192 // Read the measurement info
193 //
194 FiffDirNode::SPtr meas;
195 if(!t_pStream->read_meas_info(t_pStream->dirtree(), p_FiffEvokedSet.info, meas))
196 return false;
197 p_FiffEvokedSet.info.filename = t_sFileName; //move fname storage to read_meas_info member function
198 //
199 // Locate the data of interest
200 //
201 QList<FiffDirNode::SPtr> processed = meas->dir_tree_find(FIFFB_PROCESSED_DATA);
202 if (processed.size() == 0)
203 {
204 qWarning("Could not find processed data");
205 return false;
206 }
207 //
208 QList<FiffDirNode::SPtr> evoked_node = meas->dir_tree_find(FIFFB_EVOKED);
209 if (evoked_node.size() == 0)
210 {
211 qWarning("Could not find evoked data");
212 return false;
213 }
214
215 QStringList comments;
216 QList<fiff_int_t> aspect_kinds;
217 QString t;
218 if(!t_pStream->get_evoked_entries(evoked_node, comments, aspect_kinds, t))
219 t = QString("None found, must use integer");
220 qInfo("\tFound %lld datasets\n", evoked_node.size());
221
222 for(qint32 i = 0; i < comments.size(); ++i)
223 {
224 QFile t_file(p_FiffEvokedSet.info.filename);
225 qInfo(">> Processing %s <<\n", comments[i].toUtf8().constData());
226 FiffEvoked t_FiffEvoked;
227 if(FiffEvoked::read(t_file, t_FiffEvoked, i, baseline, proj))
228 p_FiffEvokedSet.evoked.push_back(t_FiffEvoked);
229 }
230
231 return true;
232}
233
234//=============================================================================================================
235
236bool FiffEvokedSet::save(const QString &fileName) const
237{
238 if (fileName.isEmpty()) {
239 qWarning() << "[FiffEvokedSet::save] Output file not specified.";
240 return false;
241 }
242
243 QFile file(fileName);
244 FiffStream::SPtr pStream = FiffStream::start_file(file);
245 if (!pStream) {
246 qWarning() << "[FiffEvokedSet::save] Cannot open" << fileName;
247 return false;
248 }
249
250 pStream->write_evoked_set(*this);
251 pStream->end_file();
252
253 qInfo() << "[FiffEvokedSet::save] Saved" << evoked.size()
254 << "average(s) to" << fileName;
255 return true;
256}
257
258//=============================================================================================================
259
260FiffEvokedSet FiffEvokedSet::computeGrandAverage(const QList<FiffEvokedSet> &evokedSets)
261{
262 FiffEvokedSet grandAvg;
263
264 if (evokedSets.isEmpty()) {
265 qWarning() << "[FiffEvokedSet::computeGrandAverage] No evoked sets provided.";
266 return grandAvg;
267 }
268
269 grandAvg = evokedSets[0];
270
271 for (int f = 1; f < evokedSets.size(); ++f) {
272 const FiffEvokedSet &eset = evokedSets[f];
273 int nCat = qMin(grandAvg.evoked.size(), eset.evoked.size());
274 for (int j = 0; j < nCat; ++j) {
275 if (grandAvg.evoked[j].data.cols() == eset.evoked[j].data.cols() &&
276 grandAvg.evoked[j].data.rows() == eset.evoked[j].data.rows()) {
277 grandAvg.evoked[j].data += eset.evoked[j].data;
278 grandAvg.evoked[j].nave += eset.evoked[j].nave;
279 }
280 }
281 }
282
283 for (int j = 0; j < grandAvg.evoked.size(); ++j) {
284 grandAvg.evoked[j].data /= static_cast<double>(evokedSets.size());
285 }
286
287 return grandAvg;
288}
289
290//=============================================================================================================
291
292void FiffEvokedSet::subtractBaseline(Eigen::MatrixXd &epoch, int bminSamp, int bmaxSamp)
293{
294 if (bminSamp < 0) bminSamp = 0;
295 if (bmaxSamp >= epoch.cols()) bmaxSamp = static_cast<int>(epoch.cols()) - 1;
296 if (bminSamp >= bmaxSamp) return;
297
298 int nBase = bmaxSamp - bminSamp + 1;
299 for (int c = 0; c < epoch.rows(); ++c) {
300 double baseVal = epoch.row(c).segment(bminSamp, nBase).mean();
301 epoch.row(c).array() -= baseVal;
302 }
303}
304
305//=============================================================================================================
306
308 const AverageDescription &desc,
309 const MatrixXi &events,
310 QString &log)
311{
312 FiffEvokedSet evokedSet;
313 evokedSet.info = raw.info;
314
315 float sfreq = raw.info.sfreq;
316 int nchan = raw.info.nchan;
317
318 log.clear();
319 log += QString("Averaging: %1\n").arg(desc.comment);
320
321 // Process each category
322 for (int j = 0; j < desc.categories.size(); ++j) {
323 const AverageCategory &cat = desc.categories[j];
324
325 // Compute sample indices
326 int minSamp = static_cast<int>(std::round(cat.tmin * sfreq));
327 int maxSamp = static_cast<int>(std::round(cat.tmax * sfreq));
328 int ns = maxSamp - minSamp + 1;
329 int delaySamp = static_cast<int>(std::round(cat.delay * sfreq));
330
331 // Baseline sample range (relative to epoch start)
332 int bminSamp = 0, bmaxSamp = 0;
333 if (cat.doBaseline) {
334 bminSamp = static_cast<int>(std::round(cat.bmin * sfreq)) - minSamp;
335 bmaxSamp = static_cast<int>(std::round(cat.bmax * sfreq)) - minSamp;
336 }
337
338 // Accumulator
339 MatrixXd sumData = MatrixXd::Zero(nchan, ns);
340 MatrixXd sumSqData = MatrixXd::Zero(nchan, ns);
341 int nave = 0;
342
343 log += QString("\n Category: %1\n").arg(cat.comment);
344 log += QString(" t = %.1f ... %.1f ms\n").arg(1000.0 * cat.tmin).arg(1000.0 * cat.tmax);
345
346 // Iterate over events
347 for (int k = 0; k < events.rows(); ++k) {
348 if (!FiffEvents::matchEvent(cat, events, k))
349 continue;
350
351 int evSample = events(k, 0);
352 int epochStart = evSample + delaySamp + minSamp;
353 int epochEnd = evSample + delaySamp + maxSamp;
354
355 // Check bounds
356 if (epochStart < raw.first_samp || epochEnd > raw.last_samp)
357 continue;
358
359 // Read epoch
360 MatrixXd epochData;
361 MatrixXd epochTimes;
362 if (!raw.read_raw_segment(epochData, epochTimes, epochStart, epochEnd)) {
363 log += QString(" Error reading epoch at sample %1\n").arg(evSample);
364 continue;
365 }
366
367 // Artifact rejection
368 QString rejReason;
369 if (!checkArtifacts(epochData, raw.info, raw.info.bads, desc.rej, rejReason)) {
370 log += QString(" %1 %2 %3 %4 [%5] %6 [omit]\n")
371 .arg(evSample, 7)
372 .arg(static_cast<float>(evSample) / sfreq, -10, 'f', 3)
373 .arg(events(k, 1), 3)
374 .arg(events(k, 2), 3)
375 .arg(cat.comment)
376 .arg(rejReason);
377 continue;
378 }
379
380 // Baseline correction
381 if (cat.doBaseline) {
382 subtractBaseline(epochData, bminSamp, bmaxSamp);
383 }
384
385 // Absolute value
386 if (cat.doAbs) {
387 epochData = epochData.cwiseAbs();
388 }
389
390 // Accumulate
391 sumData += epochData;
392 if (cat.doStdErr) {
393 sumSqData += epochData.cwiseProduct(epochData);
394 }
395 nave++;
396
397 log += QString(" %1 %2 %3 %4 [%5]\n")
398 .arg(evSample, 7)
399 .arg(static_cast<float>(evSample) / sfreq, -10, 'f', 3)
400 .arg(events(k, 1), 3)
401 .arg(events(k, 2), 3)
402 .arg(cat.comment);
403 }
404
405 // Compute average
406 FiffEvoked evoked;
407 evoked.comment = cat.comment;
408 evoked.first = minSamp;
409 evoked.last = maxSamp;
410 evoked.nave = nave;
411
412 // Build times vector
413 RowVectorXf times(ns);
414 for (int s = 0; s < ns; ++s)
415 times(s) = static_cast<float>(minSamp + s) / sfreq;
416 evoked.times = times;
417
418 if (nave > 0) {
419 evoked.data = sumData / static_cast<double>(nave);
420 } else {
421 evoked.data = MatrixXd::Zero(nchan, ns);
422 }
423
424 evoked.info = raw.info;
425
426 evokedSet.evoked.append(evoked);
427 log += QString(" nave = %1\n").arg(nave);
428 }
429
430 return evokedSet;
431}
432
433//=============================================================================================================
434
435bool FiffEvokedSet::checkArtifacts(const MatrixXd &epoch,
436 const FiffInfo &info,
437 const QStringList &bads,
438 const RejectionParams &rej,
439 QString &reason)
440{
441 for (int c = 0; c < epoch.rows(); ++c) {
442 // Skip bad channels
443 if (bads.contains(info.ch_names[c]))
444 continue;
445
446 double minVal = epoch.row(c).minCoeff();
447 double maxVal = epoch.row(c).maxCoeff();
448 double pp = maxVal - minVal;
449
450 int chKind = info.chs[c].kind;
451 int chUnit = info.chs[c].unit;
452
453 if (chKind == FIFFV_MEG_CH) {
454 if (chUnit == FIFF_UNIT_T) {
455 // Magnetometer
456 if (rej.megMagReject > 0 && pp > rej.megMagReject) {
457 reason = QString("%1 : %.1f fT > %.1f fT")
458 .arg(info.ch_names[c])
459 .arg(pp * 1e15).arg(rej.megMagReject * 1e15);
460 return false;
461 }
462 if (rej.megMagFlat > 0 && pp < rej.megMagFlat) {
463 reason = QString("%1 : %.1f fT < %.1f fT (flat)")
464 .arg(info.ch_names[c])
465 .arg(pp * 1e15).arg(rej.megMagFlat * 1e15);
466 return false;
467 }
468 } else {
469 // Gradiometer
470 if (rej.megGradReject > 0 && pp > rej.megGradReject) {
471 reason = QString("%1 : %.1f fT/cm > %.1f fT/cm")
472 .arg(info.ch_names[c])
473 .arg(pp * 1e13).arg(rej.megGradReject * 1e13);
474 return false;
475 }
476 if (rej.megGradFlat > 0 && pp < rej.megGradFlat) {
477 reason = QString("%1 : %.1f fT/cm < %.1f fT/cm (flat)")
478 .arg(info.ch_names[c])
479 .arg(pp * 1e13).arg(rej.megGradFlat * 1e13);
480 return false;
481 }
482 }
483 } else if (chKind == FIFFV_EEG_CH) {
484 if (rej.eegReject > 0 && pp > rej.eegReject) {
485 reason = QString("%1 : %.1f uV > %.1f uV")
486 .arg(info.ch_names[c])
487 .arg(pp * 1e6).arg(rej.eegReject * 1e6);
488 return false;
489 }
490 if (rej.eegFlat > 0 && pp < rej.eegFlat) {
491 reason = QString("%1 : %.1f uV < %.1f uV (flat)")
492 .arg(info.ch_names[c])
493 .arg(pp * 1e6).arg(rej.eegFlat * 1e6);
494 return false;
495 }
496 } else if (chKind == FIFFV_EOG_CH) {
497 if (rej.eogReject > 0 && pp > rej.eogReject) {
498 reason = QString("%1 : %.1f uV > %.1f uV (EOG)")
499 .arg(info.ch_names[c])
500 .arg(pp * 1e6).arg(rej.eogReject * 1e6);
501 return false;
502 }
503 if (rej.eogFlat > 0 && pp < rej.eogFlat) {
504 reason = QString("%1 : EOG flat").arg(info.ch_names[c]);
505 return false;
506 }
507 } else if (chKind == FIFFV_ECG_CH) {
508 if (rej.ecgReject > 0 && pp > rej.ecgReject) {
509 reason = QString("%1 : %.2f mV > %.2f mV (ECG)")
510 .arg(info.ch_names[c])
511 .arg(pp * 1e3).arg(rej.ecgReject * 1e3);
512 return false;
513 }
514 if (rej.ecgFlat > 0 && pp < rej.ecgFlat) {
515 reason = QString("%1 : ECG flat").arg(info.ch_names[c]);
516 return false;
517 }
518 }
519 }
520 return true;
521}
FIFF continuous raw recording: FiffInfo plus a directory of FIFF_DATA_BUFFER tags for random-access s...
Symbolic FIFF tag, block, value, unit and channel-type constants shared across FIFFLIB.
#define FIFFV_EOG_CH
#define FIFFV_EEG_CH
#define FIFFV_MEG_CH
#define FIFF_UNIT_T
#define FIFFV_ECG_CH
Stim-channel event list (sample, previous value, new value triples) with FIFF read/write helpers.
Recursive node of the parsed FIFF block tree (FIFFB_* hierarchy with directory entries and children).
Set of averaged evoked responses sharing a FiffInfo, plus the ave-style category / rejection descript...
FIFF tag-kind, block-kind and type-code numerical definitions, authoritative for FIFFLIB.
#define FIFFB_PROCESSED_DATA
Definition fiff_file.h:358
#define FIFFB_EVOKED
Definition fiff_file.h:359
FIFF tag: the 16-byte tag header (kind, type, size, next) plus its decoded payload.
FIFF binary tag-stream layer: wraps a QIODevice to read and write FIFF tags, directories,...
FIFF file I/O, in-memory data structures and high-level readers/writers.
Top-level MNE-C ave-description record: comment, category list, shared rejection limits and output fi...
RejectionParams rej
QList< AverageCategory > categories
Set of FiffEvoked instances sharing one FiffInfo, plus channel-picking and compensation helpers.
bool compensate_to(FiffEvokedSet &p_FiffEvokedSet, fiff_int_t to) const
bool find_evoked(const FiffEvokedSet &p_FiffEvokedSet) const
static void subtractBaseline(Eigen::MatrixXd &epoch, int bminSamp, int bmaxSamp)
Subtract baseline from each channel of an epoch.
static bool checkArtifacts(const Eigen::MatrixXd &epoch, const FiffInfo &info, const QStringList &bads, const RejectionParams &rej, QString &reason)
static FiffEvokedSet computeAverages(const FiffRawData &raw, const AverageDescription &desc, const Eigen::MatrixXi &events, QString &log)
FiffEvokedSet pick_channels(const QStringList &include=defaultQStringList, const QStringList &exclude=defaultQStringList) const
static FiffEvokedSet computeGrandAverage(const QList< FiffEvokedSet > &evokedSets)
static bool read(QIODevice &p_IODevice, FiffEvokedSet &p_FiffEvokedSet, QPair< float, float > baseline=defaultFloatPair, bool proj=true)
QList< FiffEvoked > evoked
bool save(const QString &fileName) const
Full FIFF measurement info: per-channel descriptors, sampling and filter setup, projectors,...
Definition fiff_info.h:88
fiff_int_t last_samp
FiffInfo info
bool read_raw_segment(Eigen::MatrixXd &data, Eigen::MatrixXd &times, fiff_int_t from=-1, fiff_int_t to=-1, const Eigen::RowVectorXi &sel=defaultRowVectorXi, bool do_debug=false) const