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