v2.0.0
Loading...
Searching...
No Matches
fiff_evoked.cpp
Go to the documentation of this file.
1//=============================================================================================================
18
19//=============================================================================================================
20// INCLUDES
21//=============================================================================================================
22
23#include "fiff_evoked.h"
24#include "fiff_stream.h"
25#include "fiff_tag.h"
26#include "fiff_dir_node.h"
27
28#include <math/numerics.h>
29#include <QDebug>
30
31#include <stdexcept>
32//=============================================================================================================
33// USED NAMESPACES
34//=============================================================================================================
35
36using namespace FIFFLIB;
37using namespace UTILSLIB;
38using namespace Eigen;
39
40//=============================================================================================================
41// DEFINE MEMBER METHODS
42//=============================================================================================================
43
45: nave(-1)
46, aspect_kind(-1)
47, first(-1)
48, last(-1)
49, baseline(qMakePair(-1.0f, -1.0f))
50{
51
52}
53
54//=============================================================================================================
55
56FiffEvoked::FiffEvoked(QIODevice& p_IODevice,
57 QVariant setno,
58 QPair<float,float> t_baseline,
59 bool proj,
60 fiff_int_t p_aspect_kind)
61{
62 if(!FiffEvoked::read(p_IODevice, *this, setno, t_baseline, proj, p_aspect_kind))
63 {
64 baseline = t_baseline;
65
66 throw std::runtime_error("Fiff evoked data not found");
67 }
68}
69
70//=============================================================================================================
71
73: info(p_FiffEvoked.info)
74, nave(p_FiffEvoked.nave)
75, aspect_kind(p_FiffEvoked.aspect_kind)
76, first(p_FiffEvoked.first)
77, last(p_FiffEvoked.last)
78, comment(p_FiffEvoked.comment)
79, times(p_FiffEvoked.times)
80, data(p_FiffEvoked.data)
81, proj(p_FiffEvoked.proj)
82, baseline(p_FiffEvoked.baseline)
83{
84
85}
86
87//=============================================================================================================
88
93
94//=============================================================================================================
95
97{
98 info.clear();
99 nave = -1;
100 aspect_kind = -1;
101 first = -1;
102 last = -1;
103 comment = QString("");
104 times = RowVectorXf();
105 data = MatrixXd();
106 proj = MatrixXd();
107}
108
109//=============================================================================================================
110
111FiffEvoked FiffEvoked::pick_channels(const QStringList& include,
112 const QStringList& exclude) const
113{
114 if(include.size() == 0 && exclude.size() == 0)
115 return FiffEvoked(*this);
116
117 RowVectorXi sel = FiffInfo::pick_channels(this->info.ch_names, include, exclude);
118 if (sel.cols() == 0)
119 {
120 qWarning("Warning : No channels match the selection.\n");
121 return FiffEvoked(*this);
122 }
123
124 FiffEvoked res(*this);
125 //
126 // Modify the measurement info
127 //
128 res.info = FiffInfo(res.info.pick_info(sel));
129 //
130 // Create the reduced data set
131 //
132 MatrixXd selBlock(1,1);
133
134 if(selBlock.rows() != sel.cols() || selBlock.cols() != res.data.cols())
135 selBlock.resize(sel.cols(), res.data.cols());
136 for(qint32 l = 0; l < sel.cols(); ++l)
137 {
138 if(sel(0,l) <= res.data.rows()) {
139 selBlock.block(l,0,1,selBlock.cols()) = res.data.block(sel(0,l),0,1,selBlock.cols());
140 } else {
141 qWarning("FiffEvoked::pick_channels - Warning : Selected channel index out of bound.\n");
142 }
143 }
144 res.data.resize(sel.cols(), res.data.cols());
145 res.data = selBlock;
146
147 return res;
148}
149
150//=============================================================================================================
151
152bool FiffEvoked::read(QIODevice& p_IODevice,
153 FiffEvoked& p_FiffEvoked,
154 QVariant setno,
155 QPair<float,float> t_baseline,
156 bool proj,
157 fiff_int_t p_aspect_kind)
158{
159 p_FiffEvoked.clear();
160
161 //
162 // Open the file
163 //
164 FiffStream::SPtr t_pStream(new FiffStream(&p_IODevice));
165 QString t_sFileName = t_pStream->streamName();
166
167 qInfo("Reading %s ...\n",t_sFileName.toUtf8().constData());
168
169 if(!t_pStream->open())
170 return false;
171 //
172 // Read the measurement info
173 //
176 if(!t_pStream->read_meas_info(t_pStream->dirtree(), info, meas))
177 return false;
178 info.filename = t_sFileName; //move fname storage to read_meas_info member function
179 //
180 // Locate the data of interest
181 //
182 QList<FiffDirNode::SPtr> processed = meas->dir_tree_find(FIFFB_PROCESSED_DATA);
183 if (processed.size() == 0)
184 {
185 qWarning("Could not find processed data");
186 return false;
187 }
188 //
189 QList<FiffDirNode::SPtr> evoked_node = meas->dir_tree_find(FIFFB_EVOKED);
190 if (evoked_node.size() == 0)
191 {
192 qWarning("Could not find evoked data");
193 return false;
194 }
195
196 // convert setno to an integer
197 if(!setno.isValid())
198 {
199 if (evoked_node.size() > 1)
200 {
201 QStringList comments;
202 QList<fiff_int_t> aspect_kinds;
203 QString t;
204 if(!t_pStream->get_evoked_entries(evoked_node, comments, aspect_kinds, t))
205 t = QString("None found, must use integer");
206 qWarning("%lld datasets present, setno parameter must be set. Candidate setno names:\n%s", evoked_node.size(), t.toUtf8().constData());
207 return false;
208 }
209 else
210 setno = 0;
211 }
212 else
213 {
214 // find string-based entry
215 bool t_bIsInteger = true;
216 setno.toInt(&t_bIsInteger);
217 if(!t_bIsInteger)
218 {
219 if(p_aspect_kind != FIFFV_ASPECT_AVERAGE && p_aspect_kind != FIFFV_ASPECT_STD_ERR)
220 {
221 qWarning("kindStat must be \"FIFFV_ASPECT_AVERAGE\" or \"FIFFV_ASPECT_STD_ERR\"");
222 return false;
223 }
224
225 QStringList comments;
226 QList<fiff_int_t> aspect_kinds;
227 QString t;
228 t_pStream->get_evoked_entries(evoked_node, comments, aspect_kinds, t);
229
230 bool found = false;
231 for(qint32 i = 0; i < comments.size(); ++i)
232 {
233 if(comments[i].compare(setno.toString()) == 0 && p_aspect_kind == aspect_kinds[i])
234 {
235 setno = i;
236 found = true;
237 break;
238 }
239 }
240 if(!found)
241 {
242 qWarning() << "setno " << setno << " (" << p_aspect_kind << ") not found, out of found datasets:\n " << t;
243 return false;
244 }
245 }
246 }
247
248 if (setno.toInt() >= evoked_node.size() || setno.toInt() < 0)
249 {
250 qWarning("Data set selector out of range");
251 return false;
252 }
253
254 FiffDirNode::SPtr my_evoked = evoked_node[setno.toInt()];
255
256 //
257 // Identify the aspects
258 //
259 QList<FiffDirNode::SPtr> aspects = my_evoked->dir_tree_find(FIFFB_ASPECT);
260
261 if(aspects.size() > 1)
262 qInfo("\tMultiple (%lld) aspects found. Taking first one.\n", aspects.size());
263
264 FiffDirNode::SPtr my_aspect = aspects[0];
265
266 //
267 // Now find the data in the evoked block
268 //
269 fiff_int_t nchan = 0;
270 float sfreq = -1.0f;
271 QList<FiffChInfo> chs;
272 fiff_int_t kind, pos, first=0, last=0;
273 FiffTag::UPtr t_pTag;
274 QString comment("");
275 qint32 k;
276 for (k = 0; k < my_evoked->nent(); ++k)
277 {
278 kind = my_evoked->dir[k]->kind;
279 pos = my_evoked->dir[k]->pos;
280 switch (kind)
281 {
282 case FIFF_COMMENT:
283 t_pStream->read_tag(t_pTag,pos);
284 comment = t_pTag->toString();
285 break;
287 t_pStream->read_tag(t_pTag,pos);
288 first = *t_pTag->toInt();
289 break;
290 case FIFF_LAST_SAMPLE:
291 t_pStream->read_tag(t_pTag,pos);
292 last = *t_pTag->toInt();
293 break;
294 case FIFF_NCHAN:
295 t_pStream->read_tag(t_pTag,pos);
296 nchan = *t_pTag->toInt();
297 break;
298 case FIFF_SFREQ:
299 t_pStream->read_tag(t_pTag,pos);
300 sfreq = *t_pTag->toFloat();
301 break;
302 case FIFF_CH_INFO:
303 t_pStream->read_tag(t_pTag, pos);
304 chs.append( t_pTag->toChInfo() );
305 break;
306 }
307 }
308 if (comment.isEmpty())
309 comment = QString("No comment");
310
311 //
312 // Local channel information?
313 //
314 if (nchan > 0)
315 {
316 if (chs.size() == 0)
317 {
318 qWarning("Local channel information was not found when it was expected.");
319 return false;
320 }
321 if (chs.size() != nchan)
322 {
323 qWarning("Number of channels and number of channel definitions are different.");
324 return false;
325 }
326 info.chs = chs;
327 info.nchan = nchan;
328 qInfo("\tFound channel information in evoked data. nchan = %d\n",nchan);
329 if (sfreq > 0.0f)
330 info.sfreq = sfreq;
331 }
332 qint32 nsamp = last-first+1;
333 qInfo("\tFound the data of interest:\n");
334 qInfo("\t\tt = %10.2f ... %10.2f ms (%s)\n", 1000*static_cast<float>(first)/info.sfreq, 1000*static_cast<float>(last)/info.sfreq,comment.toUtf8().constData());
335 if (info.comps.size() > 0)
336 qInfo("\t\t%lld CTF compensation matrices available\n", info.comps.size());
337
338 //
339 // Read the data in the aspect block
340 //
341 fiff_int_t aspect_kind = -1;
342 fiff_int_t nave = -1;
343 QList<FiffTag> epoch;
344 for (k = 0; k < my_aspect->nent(); ++k)
345 {
346 kind = my_aspect->dir[k]->kind;
347 pos = my_aspect->dir[k]->pos;
348
349 switch (kind)
350 {
351 case FIFF_COMMENT:
352 t_pStream->read_tag(t_pTag, pos);
353 comment = t_pTag->toString();
354 break;
355 case FIFF_ASPECT_KIND:
356 t_pStream->read_tag(t_pTag, pos);
357 aspect_kind = *t_pTag->toInt();
358 break;
359 case FIFF_NAVE:
360 t_pStream->read_tag(t_pTag, pos);
361 nave = *t_pTag->toInt();
362 break;
363 case FIFF_EPOCH:
364 t_pStream->read_tag(t_pTag, pos);
365 epoch.append(FiffTag(*t_pTag));
366 break;
367 }
368 }
369 if (nave == -1)
370 nave = 1;
371 qInfo("\t\tnave = %d - aspect type = %d\n", nave, aspect_kind);
372
373 qint32 nepoch = epoch.size();
374 MatrixXd all_data;
375 if (nepoch == 1)
376 {
377 //
378 // Only one epoch
379 //
380 all_data = epoch[0].toFloatMatrix().cast<double>();
381 all_data.transposeInPlace();
382 //
383 // May need a transpose if the number of channels is one
384 //
385 if (all_data.cols() == 1 && info.nchan == 1)
386 all_data.transposeInPlace();
387 }
388 else
389 {
390 //
391 // Put the old style epochs together
392 //
393 all_data = epoch[0].toFloatMatrix().cast<double>();
394 all_data.transposeInPlace();
395 qint32 oldsize;
396 for (k = 1; k < nepoch; ++k)
397 {
398 oldsize = all_data.rows();
399 MatrixXd tmp = epoch[k].toFloatMatrix().cast<double>();
400 tmp.transposeInPlace();
401 all_data.conservativeResize(oldsize+tmp.rows(), all_data.cols());
402 all_data.block(oldsize, 0, tmp.rows(), tmp.cols()) = tmp;
403 }
404 }
405 if (all_data.cols() != nsamp)
406 {
407 qWarning("Incorrect number of samples (%d instead of %d)", (int) all_data.cols(), nsamp);
408 return false;
409 }
410
411 //
412 // Calibrate
413 //
414 qInfo("\n\tPreprocessing...\n");
415 qInfo("\t%d channels remain after picking\n",info.nchan);
416
417 using T = Eigen::Triplet<double>;
418 std::vector<T> tripletList;
419 tripletList.reserve(info.nchan);
420 for(k = 0; k < info.nchan; ++k)
421 tripletList.push_back(T(k, k, info.chs[k].cal));
422 SparseMatrix<double> cals(info.nchan, info.nchan);
423 cals.setFromTriplets(tripletList.begin(), tripletList.end());
424
425 all_data = cals * all_data;
426
427 RowVectorXf times = RowVectorXf(last-first+1);
428 for (k = 0; k < times.size(); ++k)
429 times[k] = static_cast<float>(first+k) / info.sfreq;
430
431 //
432 // Set up projection
433 //
434 if(info.projs.size() == 0 || !proj)
435 {
436 qInfo("\tNo projector specified for these data.\n");
437 p_FiffEvoked.proj = MatrixXd();
438 }
439 else
440 {
441 // Create the projector
442 MatrixXd projection;
443 qint32 nproj = info.make_projector(projection);
444 if(nproj == 0)
445 {
446 qWarning("\tThe projection vectors do not apply to these channels\n");
447 p_FiffEvoked.proj = MatrixXd();
448 }
449 else
450 {
451 qInfo("\tCreated an SSP operator (subspace dimension = %d)\n", nproj);
452 p_FiffEvoked.proj = projection;
453 }
454
455 // The projection items have been activated
457 }
458
459 if(p_FiffEvoked.proj.rows() > 0)
460 {
461 all_data = p_FiffEvoked.proj * all_data;
462 qInfo("\tSSP projectors applied to the evoked data\n");
463 }
464
465 // Put it all together
466 p_FiffEvoked.info = info;
467 p_FiffEvoked.nave = nave;
468 p_FiffEvoked.aspect_kind = aspect_kind;
469 p_FiffEvoked.first = first;
470 p_FiffEvoked.last = last;
471 p_FiffEvoked.comment = comment;
472 p_FiffEvoked.times = times;
473 p_FiffEvoked.data = all_data;
474
475 // Run baseline correction only if explicitly requested
476 // A baseline of (-1, -1) or (first == second) means "no baseline correction"
477 // This matches mne-python (baseline=None) and SVN-MNE (no --bmin/--bmax) behavior
478 if (t_baseline.first != t_baseline.second) {
479 p_FiffEvoked.applyBaselineCorrection(t_baseline);
480 } else {
481 p_FiffEvoked.baseline = t_baseline;
482 qInfo("\tNo baseline correction applied\n");
483 }
484
485 return true;
486}
487
488//=============================================================================================================
489
490void FiffEvoked::setInfo(const FiffInfo &p_info,
491 bool proj)
492{
493 info = p_info;
494 //
495 // Set up projection
496 //
497 if(info.projs.size() == 0 || !proj)
498 {
499 qInfo("\tNo projector specified for these data.\n");
500 this->proj = MatrixXd();
501 }
502 else
503 {
504 // Create the projector
505 MatrixXd projection;
506 qint32 nproj = info.make_projector(projection);
507 if(nproj == 0)
508 {
509 qWarning("\tThe projection vectors do not apply to these channels\n");
510 this->proj = MatrixXd();
511 }
512 else
513 {
514 qInfo("\tCreated an SSP operator (subspace dimension = %d)\n", nproj);
515 this->proj = projection;
516 }
517
518 // The projection items have been activated
520 }
521}
522
523//=============================================================================================================
524
525FiffEvoked & FiffEvoked::operator+=(const MatrixXd &newData)
526{
527 //Init matrix if necessary
528 if(nave == -1 || nave == 0) {
529 data = MatrixXd::Zero(newData.rows(),newData.cols());
530 }
531
532 if(data.cols() == newData.cols() && data.rows() == newData.rows()) {
533 //Revert old averaging
534 data = data*nave;
535
536 //Do new averaging
537 data += newData;
538 if(nave <= 0) {
539 nave = 1;
540 } else {
541 nave++;
542 }
543
544 data /= nave;
545 }
546
547 return *this;
548}
549
550//=============================================================================================================
551
552void FiffEvoked::applyBaselineCorrection(QPair<float, float>& p_baseline)
553{
554 // Skip baseline correction if sentinel value (-1, -1) or equal bounds are passed
555 if (p_baseline.first == p_baseline.second) {
556 qInfo("\tNo baseline correction applied\n");
557 return;
558 }
559
560 // Run baseline correction
561 qInfo("Applying baseline correction ... (mode: mean)\n");
562 this->data = Numerics::rescale(this->data, this->times, p_baseline, QString("mean"));
563 this->baseline = p_baseline;
564}
Recursive node of the parsed FIFF block tree (FIFFB_* hierarchy with directory entries and children).
#define FIFF_NCHAN
Definition fiff_file.h:446
#define FIFFB_ASPECT
Definition fiff_file.h:361
#define FIFF_FIRST_SAMPLE
Definition fiff_file.h:454
#define FIFFV_ASPECT_AVERAGE
Definition fiff_file.h:431
#define FIFF_NAVE
Definition fiff_file.h:453
#define FIFF_COMMENT
Definition fiff_file.h:452
#define FIFF_ASPECT_KIND
Definition fiff_file.h:456
#define FIFFB_PROCESSED_DATA
Definition fiff_file.h:358
#define FIFFV_ASPECT_STD_ERR
Definition fiff_file.h:432
#define FIFF_EPOCH
Definition fiff_file.h:551
#define FIFFB_EVOKED
Definition fiff_file.h:359
#define FIFF_LAST_SAMPLE
Definition fiff_file.h:455
#define FIFF_CH_INFO
Definition fiff_file.h:449
#define FIFF_SFREQ
Definition fiff_file.h:447
Single averaged evoked response: time axis, samples, baseline, channel info and processing history.
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,...
General numerical helpers: GCD, log2, histogram binning, baseline rescaling, sparsity tests.
FIFF file I/O, in-memory data structures and high-level readers/writers.
Shared utilities (I/O helpers, spectral analysis, layout management, warp algorithms).
QSharedPointer< FiffDirNode > SPtr
Eigen::MatrixXd proj
Eigen::RowVectorXf times
Eigen::MatrixXd data
FiffEvoked pick_channels(const QStringList &include=defaultQStringList, const QStringList &exclude=defaultQStringList) const
void setInfo(const FiffInfo &p_info, bool proj=true)
fiff_int_t aspect_kind
FiffEvoked & operator+=(const Eigen::MatrixXd &newData)
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)
void applyBaselineCorrection(QPair< float, float > &p_baseline)
QPair< float, float > baseline
Full FIFF measurement info: per-channel descriptors, sampling and filter setup, projectors,...
Definition fiff_info.h:88
FiffInfo pick_info(const Eigen::RowVectorXi &sel=defaultVectorXi) const
static Eigen::RowVectorXi pick_channels(const QStringList &ch_names, const QStringList &include=defaultQStringList, const QStringList &exclude=defaultQStringList)
static void activate_projs(QList< FiffProj > &p_qListFiffProj)
Definition fiff_proj.cpp:93
FIFF tag-stream reader/writer: wraps a QIODevice and exposes typed read_* / write_* methods for every...
QSharedPointer< FiffStream > SPtr
FIFF tag: 16-byte header (kind, type, size, next) plus payload, with typed decoders for every FIFFT_*...
Definition fiff_tag.h:159
std::unique_ptr< FiffTag > UPtr
Definition fiff_tag.h:164
static Eigen::MatrixXd rescale(const Eigen::MatrixXd &data, const Eigen::RowVectorXf &times, const QPair< float, float > &baseline, QString mode)