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