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