v2.0.0
Loading...
Searching...
No Matches
mne_epoch_data_list.cpp
Go to the documentation of this file.
1//=============================================================================================================
19
20//=============================================================================================================
21// INCLUDES
22//=============================================================================================================
23
24#include "mne_epoch_data_list.h"
25
27
28//=============================================================================================================
29// QT INCLUDES
30//=============================================================================================================
31
32#include <QPointer>
33#include <QtConcurrent>
34#include <QDebug>
35
36//=============================================================================================================
37// STL INCLUDES
38//=============================================================================================================
39
40#include <cmath>
41
42//=============================================================================================================
43// USED NAMESPACES
44//=============================================================================================================
45
46using namespace FIFFLIB;
47using namespace MNELIB;
48using namespace UTILSLIB;
49using namespace Eigen;
50
51//=============================================================================================================
52// DEFINE MEMBER METHODS
53//=============================================================================================================
54
58
59//=============================================================================================================
60
62{
63 // MNEEpochDataList::iterator i;
64 // for( i = this->begin(); i!=this->end(); ++i) {
65 // if (*i)
66 // delete (*i);
67 // }
68}
69
70//=============================================================================================================
71
73 const MatrixXi& events,
74 float tmin,
75 float tmax,
76 qint32 event,
77 const QMap<QString, double>& mapReject,
78 const QStringList& lExcludeChs,
79 const RowVectorXi& picks)
80{
82
83 // Select the desired events
84 qint32 count = 0;
85 qint32 p;
86 MatrixXi selected = MatrixXi::Zero(1, events.rows());
87 for (p = 0; p < events.rows(); ++p) {
88 if (events(p, 1) == 0 && events(p, 2) == event) {
89 selected(0, count) = p;
90 ++count;
91 }
92 }
93 selected.conservativeResize(1, count);
94 if (count > 0) {
95 qInfo("[MNEEpochDataList::readEpochs] %d matching events found", count);
96 } else {
97 qWarning("[MNEEpochDataList::readEpochs] No desired events found.");
98 return MNEEpochDataList();
99 }
100
101 // If picks are empty, pick all
102 RowVectorXi picksNew = picks;
103 if (picks.cols() <= 0) {
104 picksNew.resize(raw.info.chs.size());
105 for (int i = 0; i < raw.info.chs.size(); ++i) {
106 picksNew(i) = i;
107 }
108 }
109
110 fiff_int_t event_samp, from, to;
111 fiff_int_t dropCount = 0;
112 MatrixXd timesDummy;
113 MatrixXd times;
114
115 std::unique_ptr<MNEEpochData> epoch(Q_NULLPTR);
116
117 for (p = 0; p < count; ++p) {
118 // Read a data segment
119 event_samp = events(selected(p), 0);
120 // Like mne.Epochs: both ends rounded to the nearest sample
121 from = event_samp + static_cast<fiff_int_t>(std::lround(tmin * raw.info.sfreq));
122 to = event_samp + static_cast<fiff_int_t>(std::lround(tmax * raw.info.sfreq));
123
124 epoch.reset(new MNEEpochData());
125
126 if (raw.read_raw_segment(epoch->epoch, timesDummy, from, to, picksNew)) {
127 if (p == 0) {
128 times.resize(1, to - from + 1);
129 for (qint32 i = 0; i < times.cols(); ++i)
130 times(0, i) = static_cast<float>(from - event_samp + i) / raw.info.sfreq;
131 }
132
133 epoch->event = event;
134 epoch->eventSample = event_samp;
135 epoch->tmin = tmin;
136 epoch->tmax = tmax;
137
138 epoch->bReject = checkForArtifact(epoch->epoch,
139 raw.info,
140 mapReject,
141 lExcludeChs);
142
143 if (epoch->bReject) {
144 dropCount++;
145 }
146
147 //Check if data block has the same size as the previous one
148 if (!data.isEmpty()) {
149 if (epoch->epoch.size() == data.last()->epoch.size()) {
150 data.append(MNEEpochData::SPtr(epoch.release())); //List takes ownwership of the pointer - no delete need
151 }
152 } else {
153 data.append(MNEEpochData::SPtr(epoch.release())); //List takes ownwership of the pointer - no delete need
154 }
155 } else {
156 qWarning("[MNEEpochDataList::readEpochs] Can't read the event data segments.");
157 }
158 }
159
160 qInfo().noquote() << "[MNEEpochDataList::readEpochs] Read a total of" << data.size() << "epochs of type" << event << "and marked" << dropCount << "for rejection.";
161
162 return data;
163}
164
165//=============================================================================================================
166
168 fiff_int_t first,
169 fiff_int_t last,
170 VectorXi sel,
171 bool proj) const
172{
173 FiffEvoked p_evoked;
174
175 qInfo("[MNEEpochDataList::average] Calculate evoked. ");
176
177 MatrixXd matAverage;
178
179 if (this->size() > 0) {
180 matAverage = MatrixXd::Zero(this->at(0)->epoch.rows(), this->at(0)->epoch.cols());
181 } else {
183 return p_evoked;
184 }
185
186 if (sel.size() > 0) {
187 p_evoked.nave = sel.size();
188
189 for (qint32 i = 0; i < sel.size(); ++i) {
190 matAverage.array() += this->at(sel(i))->epoch.array();
191 }
192 } else {
193 p_evoked.nave = this->size();
194
195 for (qint32 i = 0; i < this->size(); ++i) {
196 matAverage.array() += this->at(i)->epoch.array();
197 }
198 }
199 matAverage.array() /= p_evoked.nave;
200
201 qInfo("[MNEEpochDataList::average] %d averages used [done]", p_evoked.nave);
202
203 p_evoked.setInfo(info, proj);
204
206
207 p_evoked.first = first;
208 p_evoked.last = last;
209
210 // Sample times relative to the event, as in readEpochs (and mne.Epochs.times)
211 const long firstSample = std::lround(this->first()->tmin * info.sfreq);
212 p_evoked.times.resize(this->first()->epoch.cols());
213 for (Eigen::Index i = 0; i < p_evoked.times.size(); ++i) {
214 p_evoked.times[i] = static_cast<float>((firstSample + i) / info.sfreq);
215 }
216
217 p_evoked.comment = QString::number(this->at(0)->event);
218
219 if (p_evoked.proj.rows() > 0) {
220 matAverage = p_evoked.proj * matAverage;
221 qInfo("[MNEEpochDataList::average] SSP projectors applied to the evoked data");
222 }
223
224 p_evoked.data = matAverage;
225
226 return p_evoked;
227}
228
229//=============================================================================================================
230
231void MNEEpochDataList::applyBaselineCorrection(const QPair<float, float>& baseline)
232{
233 // Run baseline correction
234 QMutableListIterator<MNEEpochData::SPtr> i(*this);
235 while (i.hasNext()) {
236 i.next()->applyBaselineCorrection(baseline);
237 }
238}
239
240//=============================================================================================================
241
243{
244 QMutableListIterator<MNEEpochData::SPtr> i(*this);
245 while (i.hasNext()) {
246 if (i.next()->isRejected()) {
247 i.remove();
248 }
249 }
250}
251
252//=============================================================================================================
253
254void MNEEpochDataList::pick_channels(const RowVectorXi& sel)
255{
256 QMutableListIterator<MNEEpochData::SPtr> i(*this);
257 while (i.hasNext()) {
258 i.next()->pick_channels(sel);
259 }
260}
261
262//=============================================================================================================
263
264bool MNEEpochDataList::checkForArtifact(const MatrixXd& data,
265 const FiffInfo& pFiffInfo,
266 const QMap<QString, double>& mapReject,
267 const QStringList& lExcludeChs)
268{
269 //qDebug() << "MNEEpochDataList::checkForArtifact - Doing artifact reduction for" << mapReject;
270
271 bool bReject = false;
272
273 //Prepare concurrent data handling
274 QList<ArtifactRejectionData> lchData;
275 QList<int> lChTypes;
276
277 if (mapReject.contains("grad") ||
278 mapReject.contains("mag")) {
279 lChTypes << FIFFV_MEG_CH;
280 }
281
282 if (mapReject.contains("eeg")) {
283 lChTypes << FIFFV_EEG_CH;
284 }
285
286 if (mapReject.contains("eog")) {
287 lChTypes << FIFFV_EOG_CH;
288 }
289
290 if (lChTypes.isEmpty()) {
291 return bReject;
292 }
293
294 for (int i = 0; i < pFiffInfo.chs.size(); ++i) {
295 if (lChTypes.contains(pFiffInfo.chs.at(i).kind) && !lExcludeChs.contains(pFiffInfo.chs.at(i).ch_name) && !pFiffInfo.bads.contains(pFiffInfo.chs.at(i).ch_name) && pFiffInfo.chs.at(i).chpos.coil_type != FIFFV_COIL_BABY_REF_MAG && pFiffInfo.chs.at(i).chpos.coil_type != FIFFV_COIL_BABY_REF_MAG2) {
296 ArtifactRejectionData tempData;
297 tempData.data = data.row(i);
298
299 switch (pFiffInfo.chs.at(i).kind) {
300 case FIFFV_MEG_CH:
301 if (pFiffInfo.chs.at(i).unit == FIFF_UNIT_T) {
302 tempData.dThreshold = mapReject["mag"];
303 } else if (pFiffInfo.chs.at(i).unit == FIFF_UNIT_T_M) {
304 tempData.dThreshold = mapReject["grad"];
305 }
306 break;
307
308 case FIFFV_EEG_CH:
309 tempData.dThreshold = mapReject["eeg"];
310 break;
311
312 case FIFFV_EOG_CH:
313 tempData.dThreshold = mapReject["eog"];
314 break;
315 }
316
317 tempData.sChName = pFiffInfo.chs.at(i).ch_name;
318 lchData.append(tempData);
319 }
320 }
321
322 if (lchData.isEmpty()) {
323 qWarning() << "[MNEEpochDataList::checkForArtifact] No channels found to scan for artifacts. Do not reject. Returning.";
324
325 return bReject;
326 }
327
328 //qDebug() << "MNEEpochDataList::checkForArtifact - lchData.size()" << lchData.size();
329
330 //Start the concurrent processing
331 QFuture<void> future = QtConcurrent::map(lchData, checkChThreshold);
332 future.waitForFinished();
333
334 for (int i = 0; i < lchData.size(); ++i) {
335 if (lchData.at(i).bRejected) {
336 bReject = true;
337 qInfo().noquote() << "[MNEEpochDataList::checkForArtifact] Reject trial because of channel" << lchData.at(i).sChName;
338 break;
339 }
340 }
341
342 return bReject;
343}
344
345//=============================================================================================================
346
347void MNEEpochDataList::checkChThreshold(ArtifactRejectionData& inputData)
348{
349 RowVectorXd temp = inputData.data;
350
351 // Remove offset
352 //temp = temp.array() - temp(0);
353
354 double min = temp.minCoeff();
355 double max = temp.maxCoeff();
356
357 // Peak to Peak
358 double pp = max - min;
359
360 if (std::fabs(pp) > inputData.dThreshold) {
361 inputData.bRejected = true;
362 } else {
363 inputData.bRejected = false;
364 }
365
366 // qDebug() << "MNEEpochDataList::checkChThreshold - min" << min;
367 // qDebug() << "MNEEpochDataList::checkChThreshold - max" << max;
368 // qDebug() << "MNEEpochDataList::checkChThreshold - pp" << pp;
369 // qDebug() << "MNEEpochDataList::checkChThreshold - inputData.dThreshold" << inputData.dThreshold;
370
371 // //If absolute vaue of min or max if bigger than threshold -> reject
372 // if((std::fabs(min) > inputData.dThreshold) || (std::fabs(max) > inputData.dThreshold)) {
373 // inputData.bRejected = true;
374 // } else {
375 // inputData.bRejected = false;
376 // }
377}
378
379//=============================================================================================================
380
382 const MatrixXi& events,
383 const QList<int>& eventCodes,
384 const QStringList& comments,
385 float tmin,
386 float tmax,
387 const QMap<QString, double>& mapReject,
388 const QPair<float, float>& baseline,
389 bool proj)
390{
391 FiffEvokedSet evokedSet;
392 evokedSet.info = raw.info;
393
394 float sfreq = raw.info.sfreq;
395
396 bool doBaseline = (baseline.first != baseline.second);
397
398 // Process each category (event code)
399 for (int j = 0; j < eventCodes.size(); ++j) {
400 int eventCode = eventCodes[j];
401 QString comment = (j < comments.size()) ? comments[j]
402 : QString("cat_%1").arg(eventCode);
403
404 // Read epochs for this event code using the existing readEpochs
405 MNEEpochDataList epochList = MNEEpochDataList::readEpochs(raw, events,
406 tmin, tmax,
407 eventCode,
408 mapReject);
409
410 if (epochList.isEmpty()) {
411 qWarning() << "[MNEEpochDataList::averageCategories] No epochs found for event"
412 << eventCode << "- skipping category.";
413 continue;
414 }
415
416 // Apply baseline correction
417 if (doBaseline) {
418 epochList.applyBaselineCorrection(baseline);
419 }
420
421 // Drop rejected epochs
422 epochList.dropRejected();
423
424 if (epochList.isEmpty()) {
425 qWarning() << "[MNEEpochDataList::averageCategories] All epochs rejected for event"
426 << eventCode << "- skipping category.";
427 continue;
428 }
429
430 // Compute the average
431 int minSamp = static_cast<int>(std::round(tmin * sfreq));
432 int maxSamp = static_cast<int>(std::round(tmax * sfreq));
433
434 FiffEvoked evoked = epochList.average(raw.info,
435 minSamp,
436 maxSamp,
437 defaultVectorXi,
438 proj);
439
440 evoked.comment = comment;
441 evoked.baseline = doBaseline ? baseline : QPair<float, float>(0.0f, 0.0f);
442 evokedSet.evoked.append(evoked);
443 }
444
445 return evokedSet;
446}
447
448//=============================================================================================================
449
451 const MatrixXi& matEvents,
452 float fTMinS,
453 float fTMaxS,
454 qint32 eventType,
455 bool bApplyBaseline,
456 float fTBaselineFromS,
457 float fTBaselineToS,
458 const QMap<QString, double>& mapReject,
459 const QStringList& lExcludeChs,
460 const RowVectorXi& picks)
461{
462 MNEEpochDataList lstEpochDataList = MNEEpochDataList::readEpochs(raw,
463 matEvents,
464 fTMinS,
465 fTMaxS,
466 eventType,
467 mapReject,
468 lExcludeChs,
469 picks);
470
471 if (bApplyBaseline) {
472 QPair<float, float> baselinePair(fTBaselineFromS, fTBaselineToS);
473 lstEpochDataList.applyBaselineCorrection(baselinePair);
474 }
475
476 if (!mapReject.isEmpty()) {
477 lstEpochDataList.dropRejected();
478 }
479
480 FiffEvoked evoked = lstEpochDataList.average(raw.info,
481 0,
482 lstEpochDataList.first()->epoch.cols());
483 evoked.baseline = bApplyBaseline ? QPair<float, float>(fTBaselineFromS, fTBaselineToS)
484 : QPair<float, float>(0.0f, 0.0f);
485 return evoked;
486}
#define FIFFV_EOG_CH
#define FIFFV_EEG_CH
#define FIFFV_COIL_BABY_REF_MAG
#define FIFFV_MEG_CH
#define FIFF_UNIT_T
#define FIFF_UNIT_T_M
#define FIFFV_COIL_BABY_REF_MAG2
Set of averaged evoked responses sharing a FiffInfo, plus the ave-style category / rejection descript...
#define FIFFV_ASPECT_AVERAGE
Definition fiff_file.h:431
#define FIFFV_ASPECT_STD_ERR
Definition fiff_file.h:432
Ordered list of MNELIB::MNEEpochData objects sharing a common FIFFLIB::FiffInfo.
Core MNE data structures (source spaces, source estimates, hemispheres).
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).
Single averaged evoked response: time axis, data, baseline, channel info and averaging metadata.
Definition fiff_evoked.h:77
Eigen::MatrixXd proj
Eigen::RowVectorXf times
Eigen::MatrixXd data
fiff_int_t aspect_kind
QPair< float, float > baseline
void setInfo(const FiffInfo &p_info, bool applyProj=true)
Set of FiffEvoked instances sharing one FiffInfo, plus channel-picking and compensation helpers.
QList< FiffEvoked > evoked
Full FIFF measurement info: per-channel descriptors, sampling and filter setup, projectors,...
Definition fiff_info.h:90
QList< FiffChInfo > chs
Continuous FIFF raw recording: FiffInfo plus a random-access directory of FIFF_DATA_BUFFER tags.
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
Single epoch (trial slice) of sensor data with timing and rejection metadata.
QSharedPointer< MNEEpochData > SPtr
Per-channel peak-to-peak check record used internally by MNEEpochDataList::checkForArtifact.
FIFFLIB::FiffEvoked average(const FIFFLIB::FiffInfo &p_info, FIFFLIB::fiff_int_t first, FIFFLIB::fiff_int_t last, Eigen::VectorXi sel=FIFFLIB::defaultVectorXi, bool proj=false) const
void applyBaselineCorrection(const QPair< float, float > &baseline)
static MNEEpochDataList readEpochs(const FIFFLIB::FiffRawData &raw, const Eigen::MatrixXi &events, float tmin, float tmax, qint32 event, const QMap< QString, double > &mapReject, const QStringList &lExcludeChs=QStringList(), const Eigen::RowVectorXi &picks=Eigen::RowVectorXi())
static FIFFLIB::FiffEvoked computeAverage(const FIFFLIB::FiffRawData &raw, const Eigen::MatrixXi &matEvents, float fTMinS, float fTMaxS, qint32 eventType, bool bApplyBaseline, float fTBaselineFromS, float fTBaselineToS, const QMap< QString, double > &mapReject, const QStringList &lExcludeChs=QStringList(), const Eigen::RowVectorXi &vecPicks=Eigen::RowVectorXi())
void pick_channels(const Eigen::RowVectorXi &sel)
static FIFFLIB::FiffEvokedSet averageCategories(const FIFFLIB::FiffRawData &raw, const Eigen::MatrixXi &events, const QList< int > &eventCodes, const QStringList &comments, float tmin, float tmax, const QMap< QString, double > &mapReject=QMap< QString, double >(), const QPair< float, float > &baseline=QPair< float, float >(0.0f, 0.0f), bool proj=false)
static bool checkForArtifact(const Eigen::MatrixXd &data, const FIFFLIB::FiffInfo &pFiffInfo, const QMap< QString, double > &mapReject, const QStringList &lExcludeChs=QStringList())