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(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 if (mapReject.isEmpty()) {
276 return bReject;
277 }
278
279 for (int i = 0; i < pFiffInfo.chs.size(); ++i) {
280 const FiffChInfo& ch = pFiffInfo.chs.at(i);
281 QString key;
282 if (ch.kind == FIFFV_MEG_CH && ch.unit == FIFF_UNIT_T) {
283 key = QStringLiteral("mag");
284 } else if (ch.kind == FIFFV_MEG_CH && ch.unit == FIFF_UNIT_T_M) {
285 key = QStringLiteral("grad");
286 } else if (ch.kind == FIFFV_EEG_CH) {
287 key = QStringLiteral("eeg");
288 } else if (ch.kind == FIFFV_EOG_CH) {
289 key = QStringLiteral("eog");
290 }
291 // Like mne.Epochs(reject=...): only channel types with a limit are checked
292 if (!mapReject.contains(key) || lExcludeChs.contains(ch.ch_name) || pFiffInfo.bads.contains(ch.ch_name) || ch.chpos.coil_type == FIFFV_COIL_BABY_REF_MAG || ch.chpos.coil_type == FIFFV_COIL_BABY_REF_MAG2) {
293 continue;
294 }
295 ArtifactRejectionData tempData;
296 tempData.data = data.row(i);
297 tempData.dThreshold = mapReject.value(key);
298 tempData.sChName = ch.ch_name;
299 lchData.append(tempData);
300 }
301
302 if (lchData.isEmpty()) {
303 qWarning() << "[MNEEpochDataList::checkForArtifact] No channels found to scan for artifacts. Do not reject. Returning.";
304
305 return bReject;
306 }
307
308 //qDebug() << "MNEEpochDataList::checkForArtifact - lchData.size()" << lchData.size();
309
310 //Start the concurrent processing
311 QFuture<void> future = QtConcurrent::map(lchData, checkChThreshold);
312 future.waitForFinished();
313
314 for (int i = 0; i < lchData.size(); ++i) {
315 if (lchData.at(i).bRejected) {
316 bReject = true;
317 qInfo().noquote() << "[MNEEpochDataList::checkForArtifact] Reject trial because of channel" << lchData.at(i).sChName;
318 break;
319 }
320 }
321
322 return bReject;
323}
324
325//=============================================================================================================
326
327void MNEEpochDataList::checkChThreshold(ArtifactRejectionData& inputData)
328{
329 RowVectorXd temp = inputData.data;
330
331 // Remove offset
332 //temp = temp.array() - temp(0);
333
334 double min = temp.minCoeff();
335 double max = temp.maxCoeff();
336
337 // Peak to Peak
338 double pp = max - min;
339
340 if (std::fabs(pp) > inputData.dThreshold) {
341 inputData.bRejected = true;
342 } else {
343 inputData.bRejected = false;
344 }
345
346 // qDebug() << "MNEEpochDataList::checkChThreshold - min" << min;
347 // qDebug() << "MNEEpochDataList::checkChThreshold - max" << max;
348 // qDebug() << "MNEEpochDataList::checkChThreshold - pp" << pp;
349 // qDebug() << "MNEEpochDataList::checkChThreshold - inputData.dThreshold" << inputData.dThreshold;
350
351 // //If absolute vaue of min or max if bigger than threshold -> reject
352 // if((std::fabs(min) > inputData.dThreshold) || (std::fabs(max) > inputData.dThreshold)) {
353 // inputData.bRejected = true;
354 // } else {
355 // inputData.bRejected = false;
356 // }
357}
358
359//=============================================================================================================
360
362 const MatrixXi& events,
363 const QList<int>& eventCodes,
364 const QStringList& comments,
365 float tmin,
366 float tmax,
367 const QMap<QString, double>& mapReject,
368 const QPair<float, float>& baseline,
369 bool proj)
370{
371 FiffEvokedSet evokedSet;
372 evokedSet.info = raw.info;
373
374 float sfreq = raw.info.sfreq;
375
376 bool doBaseline = (baseline.first != baseline.second);
377
378 // Process each category (event code)
379 for (int j = 0; j < eventCodes.size(); ++j) {
380 int eventCode = eventCodes[j];
381 QString comment = (j < comments.size()) ? comments[j]
382 : QString("cat_%1").arg(eventCode);
383
384 // Read epochs for this event code using the existing readEpochs
385 MNEEpochDataList epochList = MNEEpochDataList::readEpochs(raw, events,
386 tmin, tmax,
387 eventCode,
388 mapReject);
389
390 if (epochList.isEmpty()) {
391 qWarning() << "[MNEEpochDataList::averageCategories] No epochs found for event"
392 << eventCode << "- skipping category.";
393 continue;
394 }
395
396 // Apply baseline correction
397 if (doBaseline) {
398 epochList.applyBaselineCorrection(baseline);
399 }
400
401 // Drop rejected epochs
402 epochList.dropRejected();
403
404 if (epochList.isEmpty()) {
405 qWarning() << "[MNEEpochDataList::averageCategories] All epochs rejected for event"
406 << eventCode << "- skipping category.";
407 continue;
408 }
409
410 // Compute the average
411 int minSamp = static_cast<int>(std::round(tmin * sfreq));
412 int maxSamp = static_cast<int>(std::round(tmax * sfreq));
413
414 FiffEvoked evoked = epochList.average(raw.info,
415 minSamp,
416 maxSamp,
417 defaultVectorXi,
418 proj);
419
420 evoked.comment = comment;
421 evoked.baseline = doBaseline ? baseline : QPair<float, float>(0.0f, 0.0f);
422 evokedSet.evoked.append(evoked);
423 }
424
425 return evokedSet;
426}
427
428//=============================================================================================================
429
431 const MatrixXi& matEvents,
432 float fTMinS,
433 float fTMaxS,
434 qint32 eventType,
435 bool bApplyBaseline,
436 float fTBaselineFromS,
437 float fTBaselineToS,
438 const QMap<QString, double>& mapReject,
439 const QStringList& lExcludeChs,
440 const RowVectorXi& picks)
441{
442 MNEEpochDataList lstEpochDataList = MNEEpochDataList::readEpochs(raw,
443 matEvents,
444 fTMinS,
445 fTMaxS,
446 eventType,
447 mapReject,
448 lExcludeChs,
449 picks);
450
451 if (bApplyBaseline) {
452 QPair<float, float> baselinePair(fTBaselineFromS, fTBaselineToS);
453 lstEpochDataList.applyBaselineCorrection(baselinePair);
454 }
455
456 if (!mapReject.isEmpty()) {
457 lstEpochDataList.dropRejected();
458 }
459
460 if (lstEpochDataList.isEmpty())
461 return FiffEvoked();
462
463 const fiff_int_t first = static_cast<fiff_int_t>(std::lround(fTMinS * raw.info.sfreq));
464 FiffEvoked evoked = lstEpochDataList.average(raw.info,
465 first,
466 first + static_cast<fiff_int_t>(lstEpochDataList.first()->epoch.cols()) - 1);
467 evoked.baseline = bApplyBaseline ? QPair<float, float>(fTBaselineFromS, fTBaselineToS)
468 : QPair<float, float>(0.0f, 0.0f);
469 return evoked;
470}
constexpr auto FIFF_UNIT_T_M
#define FIFFV_EEG_CH
#define FIFFV_MEG_CH
constexpr auto FIFF_UNIT_T
constexpr auto FIFFV_COIL_BABY_REF_MAG
constexpr auto FIFFV_COIL_BABY_REF_MAG2
constexpr auto FIFFV_EOG_CH
Set of averaged evoked responses sharing a FiffInfo, plus the ave-style category / rejection descript...
constexpr auto FIFFV_ASPECT_AVERAGE
Definition fiff_file.h:429
constexpr auto FIFFV_ASPECT_STD_ERR
Definition fiff_file.h:430
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).
Per-channel FIFF descriptor: identifiers, kind, calibration, coil type, channel-frame coil position a...
fiff_int_t coil_type
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())