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