36#include <QRegularExpression>
64 if (!read_from_fif(p_IODevice, *
this)) {
67 read_from_ascii(p_IODevice, *
this);
74 const QString& t_fileRawName,
77 QString eventName = t_sEventName;
81 if (eventName.isEmpty()) {
82 eventName = t_fileRawName;
83 p = eventName.indexOf(
".fif");
85 eventName.replace(p, 4,
"-eve.fif");
87 qWarning(
"Raw file name does not end properly\n");
91 t_EventFile.setFileName(eventName);
92 if (!read_from_fif(t_EventFile, p_Events)) {
93 qWarning(
"Error while read events.\n");
96 qInfo(
"Events read from %s\n", eventName.toUtf8().constData());
99 if (eventName.contains(
".fif")) {
100 t_EventFile.setFileName(eventName);
101 if (!read_from_fif(t_EventFile, p_Events)) {
102 qWarning(
"Error while read events.\n");
105 qInfo(
"Binary event file %s read\n", eventName.toUtf8().constData());
106 }
else if (eventName.contains(
".eve")) {
109 qWarning(
"Text file %s is not supported jet.\n", eventName.toUtf8().constData());
126 if (!t_pStream->open()) {
133 QList<FiffDirNode::SPtr> eventsBlocks = t_pStream->dirtree()->dir_tree_find(
FIFFB_MNE_EVENTS);
135 if (eventsBlocks.size() == 0) {
136 qWarning(
"Could not find event data\n");
146 quint32* serial_eventlist_uint =
nullptr;
147 qint32* serial_eventlist_int =
nullptr;
149 for (k = 0; k < eventsBlocks[0]->nent(); ++k) {
150 kind = eventsBlocks[0]->dir[k]->kind;
151 pos = eventsBlocks[0]->dir[k]->pos;
153 t_pStream->read_tag(t_pTag, pos);
155 serial_eventlist_uint = t_pTag->toUnsignedInt();
156 nelem = t_pTag->size() / 4;
160 serial_eventlist_int = t_pTag->toInt();
161 nelem = t_pTag->size() / 4;
168 if (serial_eventlist_uint ==
nullptr && serial_eventlist_int ==
nullptr) {
169 qWarning(
"Could not find any events\n");
173 p_Events.
events.resize(nelem / 3, 3);
174 if (serial_eventlist_uint !=
nullptr) {
175 for (k = 0; k < nelem / 3; ++k) {
176 p_Events.
events(k, 0) = serial_eventlist_uint[k * 3];
177 p_Events.
events(k, 1) = serial_eventlist_uint[k * 3 + 1];
178 p_Events.
events(k, 2) = serial_eventlist_uint[k * 3 + 2];
182 if (serial_eventlist_int !=
nullptr) {
183 for (k = 0; k < nelem / 3; ++k) {
184 p_Events.
events(k, 0) = serial_eventlist_int[k * 3];
185 p_Events.
events(k, 1) = serial_eventlist_int[k * 3 + 1];
186 p_Events.
events(k, 2) = serial_eventlist_int[k * 3 + 2];
198 if (!p_IODevice.open(QIODevice::ReadOnly | QIODevice::Text)) {
201 QTextStream textStream(&p_IODevice);
203 QList<int> sampleList;
204 QList<int> beforeList;
205 QList<int> afterList;
207 while (!textStream.atEnd()) {
208 QString line = textStream.readLine().trimmed();
219 const QStringList fields = line.split(QRegularExpression(
"\\s+"),
221 if (fields.isEmpty())
224 const int iOffset = (fields.size() >= 4) ? 1 : 0;
226 int iSample = fields.at(0).toInt();
227 int iBefore = (fields.size() > 1 + iOffset) ? fields.at(1 + iOffset).toInt() : 0;
228 int iAfter = (fields.size() > 2 + iOffset) ? fields.at(2 + iOffset).toInt() : 0;
230 sampleList.append(iSample);
231 beforeList.append(iBefore);
232 afterList.append(iAfter);
235 p_Events.
events.resize(sampleList.size(), 3);
237 for (
int i = 0; i < sampleList.size(); i++) {
238 p_Events.
events(i, 0) = sampleList[i];
239 p_Events.
events(i, 1) = beforeList[i];
240 p_Events.
events(i, 2) = afterList[i];
249 if (events.rows() == 0 || events.cols() < 3)
257 const Eigen::Matrix<int, Eigen::Dynamic, 3, Eigen::RowMajor> rows = events.leftCols(3);
271 if (!p_IODevice.open(QIODevice::WriteOnly | QIODevice::Text))
274 for (
int k = 0; k < events.rows(); ++k) {
275 int sample = events(k, 0);
276 int before = (events.cols() > 1) ? events(k, 1) : 0;
277 int after = (events.cols() > 2) ? events(k, 2) : 0;
278 float time = (sfreq > 0.0f) ?
static_cast<float>(sample) / sfreq : 0.0f;
279 QTextStream out(&p_IODevice);
280 out << QString(
"%1 %2 %3 %4\n")
282 .arg(time, -10,
'f', 3)
295 const QString& triggerCh,
296 unsigned int triggerMask,
299 QString stimCh = triggerCh.isEmpty() ? QString(
"STI 014") : triggerCh;
302 const QString wanted = QString(stimCh).remove(
' ');
303 int triggerChIdx = -1;
305 if (QString(raw.
info.
ch_names[k]).remove(
' ') == wanted) {
310 if (triggerChIdx < 0) {
311 qWarning() <<
"[FiffEvents::detect_from_raw] Trigger channel" << stimCh <<
"not found.";
319 RowVectorXi::LinSpaced(1, triggerChIdx, triggerChIdx))) {
320 qWarning() <<
"[FiffEvents::detect_from_raw] Could not read trigger channel data.";
324 RowVectorXd trigData = data.row(0);
325 int nSamples =
static_cast<int>(trigData.cols());
328 QList<int> eventSamples;
329 QList<int> eventBefore;
330 QList<int> eventAfter;
332 int prevVal =
static_cast<int>(trigData(0)) & triggerMask;
333 for (
int s = 1; s < nSamples; ++s) {
334 int curVal =
static_cast<int>(trigData(s)) & triggerMask;
335 if (curVal != prevVal) {
336 if (!leadingEdge || (leadingEdge && prevVal == 0 && curVal != 0)) {
337 eventSamples.append(
static_cast<int>(raw.
first_samp) + s);
338 eventBefore.append(prevVal);
339 eventAfter.append(curVal);
345 int nEvents = eventSamples.size();
346 p_Events.
events.resize(nEvents, 3);
347 for (
int k = 0; k < nEvents; ++k) {
348 p_Events.
events(k, 0) = eventSamples[k];
349 p_Events.
events(k, 1) = eventBefore[k];
350 p_Events.
events(k, 2) = eventAfter[k];
359 const MatrixXi& events,
362 if (eventIdx < 0 || eventIdx >= events.rows())
365 int evFrom = events(eventIdx, 1);
366 int evTo = events(eventIdx, 2);
370 for (
int k = 0; k < cat.
events.size(); ++k) {
371 if ((evFrom & ~cat.
ignore) == 0 &&
383 for (
int j = eventIdx - 1; j >= 0; --j) {
386 match = match && ((events(j, 2) & ~cat.prevIgnore) == cat.
prevEvent);
397 for (
int j = eventIdx + 1; j < events.rows(); ++j) {
400 match = match && ((events(j, 2) & ~cat.nextIgnore) == cat.
nextEvent);
417MatrixXi
selectRows(
const MatrixXi& events,
const std::vector<bool>& keep)
419 MatrixXi out(std::count(keep.cbegin(), keep.cend(),
true), events.cols());
421 for (Index i = 0; i < events.rows(); ++i) {
423 out.row(r++) = events.row(i);
434 std::vector<bool> keep(
events.rows());
435 for (Index i = 0; i <
events.rows(); ++i) {
436 const bool matches = !include.isEmpty()
437 ? include.contains(
events(i, 2)) || (step && include.contains(
events(i, 1)))
438 : !(exclude.contains(
events(i, 2)) || (step && exclude.contains(
events(i, 1))));
452 std::vector<bool> touched(
events.rows(),
false);
453 for (Index i = 0; i <
events.rows(); ++i) {
454 for (
int col = 1; col <= 2; ++col) {
455 if (ids.contains(
events(i, col))) {
456 out.
events(i, col) = newId;
461 if (!replaceEvents) {
463 MatrixXi all(out.
events.rows() + originals.rows(), 3);
464 all << out.
events, originals;
465 std::vector<Index> order(all.rows());
466 std::iota(order.begin(), order.end(), 0);
467 std::stable_sort(order.begin(), order.end(), [&all](Index a, Index b) {
468 return std::lexicographical_compare(all.row(a).begin(), all.row(a).end(), all.row(b).begin(), all.row(b).end());
470 out.
events.resize(all.rows(), 3);
471 for (Index i = 0; i < all.rows(); ++i)
472 out.
events.row(i) = all.row(order[i]);
481 QMap<int, int> counts;
482 for (
const int id : ids)
483 counts.insert(
id, 0);
484 for (Index i = 0; i <
events.rows(); ++i) {
485 if (ids.isEmpty() || ids.contains(
events(i, 2)))
496 if (
events.isEmpty() ||
events.size() != firstSamps.size() ||
events.size() != lastSamps.size())
501 out.
events.resize(rows, 3);
504 for (qsizetype k = 0; k <
events.size(); ++k) {
505 MatrixXi shifted =
events[k].events;
507 shifted.col(0).array() += offset + firstSamps[0] - firstSamps[k];
508 out.
events.middleRows(r, shifted.rows()) = shifted;
510 offset += lastSamps[k] - firstSamps[k] + 1;
519 std::vector<RowVector3i> steps;
520 for (Index t = 0; t + 1 < stimData.cols(); ++t) {
521 if ((stimData.col(t + 1).array() != stimData.col(t).array()).all())
522 steps.emplace_back(
static_cast<int>(t + 1) + firstSamp, stimData(0, t), stimData(0, t + 1));
527 if (padStart && steps.front()(1) != *padStart)
528 steps.insert(steps.begin(), RowVector3i(0, *padStart, steps.front()(1)));
529 if (padStop && steps.back()(2) != *padStop)
530 steps.emplace_back(
static_cast<int>(stimData.cols()) + firstSamp, steps.back()(2), *padStop);
532 const size_t n = steps.size();
533 std::vector<bool> close(n > 0 ? n - 1 : 0);
534 for (
size_t i = 0; i + 1 < n; ++i)
535 close[i] = steps[i + 1](0) - steps[i](0) <= std::abs(
merge);
536 if (std::find(close.cbegin(), close.cend(),
true) != close.cend()) {
538 std::vector<bool> keep(n,
true);
539 const std::vector<RowVector3i> original = steps;
540 for (
size_t i = 0; i + 1 < n; ++i) {
544 steps[i + 1](1) = original[i](1);
547 steps[i](2) = original[i + 1](2);
551 std::vector<RowVector3i> kept;
552 for (
size_t i = 0; i < n; ++i) {
553 if (keep[i] && steps[i](1) != steps[i](2))
554 kept.push_back(steps[i]);
559 out.
events.resize(
static_cast<Index
>(steps.size()), 3);
560 for (
size_t i = 0; i < steps.size(); ++i)
561 out.
events.row(
static_cast<Index
>(i)) = steps[i];
571 if (overlap < 0.0 || overlap >= duration || sfreq <= 0.0)
573 double first = std::nearbyint(start * sfreq);
574 double last = stop >= 0.0 ? std::nearbyint(stop * sfreq) : raw.
last_samp + 1.0;
581 last -= std::nearbyint(sfreq * duration);
582 const double stepSamples = sfreq * (duration - overlap);
583 const auto count =
static_cast<Index
>(std::max(0.0, std::ceil((last + 1.0 - first) / stepSamples)));
585 for (Index i = 0; i <
count; ++i) {
586 out.
events(i, 0) =
static_cast<int>(first +
static_cast<double>(i) * stepSamples);
Symbolic FIFF tag, block, value, unit and channel-type constants shared across FIFFLIB.
#define FIFF_MNE_EVENT_LIST
FIFF tag: the 16-byte tag header (kind, type, size, next) plus its decoded payload.
Set of averaged evoked responses sharing a FiffInfo, plus the ave-style category / rejection descript...
FIFF binary tag-stream layer: wraps a QIODevice to read and write FIFF tags, directories,...
MatrixXi selectRows(const MatrixXi &events, const std::vector< bool > &keep)
FIFF tag-kind, block-kind and type-code numerical definitions, authoritative for FIFFLIB.
Recursive node of the parsed FIFF block tree (FIFFB_* hierarchy with directory entries and children).
Stim-channel event list (sample, previous value, new value triples) with FIFF read/write helpers.
FIFF continuous raw recording: FiffInfo plus a directory of FIFF_DATA_BUFFER tags for random-access s...
FIFF file I/O, in-memory data structures and high-level readers/writers.
FIFF event list: (sample, prev, new) integer triples with read / write / detect / filter helpers.
static FiffEvents concatenate(const QList< FiffEvents > &events, const QList< int > &firstSamps, const QList< int > &lastSamps)
QMap< int, int > count(const QList< int > &ids={}) const
bool write_to_ascii(QIODevice &p_IODevice, float sfreq=0.0f) const
static bool matchEvent(const AverageCategory &cat, const Eigen::MatrixXi &events, int eventIdx)
static bool read(const QString &t_sEventName, const QString &t_fileRawName, FiffEvents &p_Events)
FiffEvents merge(const QList< int > &ids, int newId, bool replaceEvents=true) const
static bool read_from_fif(QIODevice &p_IODevice, FiffEvents &p_Events)
static FiffEvents make_fixed_length(const FiffRawData &raw, int id=1, double start=0.0, double stop=-1.0, double duration=1.0, bool firstSamp=true, double overlap=0.0)
bool write_to_fif(QIODevice &p_IODevice) const
FiffEvents pick(const QList< int > &include, const QList< int > &exclude={}, bool step=false) const
static bool detect_from_raw(const FiffRawData &raw, FiffEvents &p_Events, const QString &triggerCh=QString("STI 014"), unsigned int triggerMask=0xFFFFFFFF, bool leadingEdge=true)
static FiffEvents find_stim_steps(const Eigen::MatrixXi &stimData, int firstSamp, std::optional< int > padStart=std::nullopt, std::optional< int > padStop=std::nullopt, int merge=0)
static bool read_from_ascii(QIODevice &p_IODevice, FiffEvents &p_Events)
One averaging category in an MNE-C ave-description file: trigger logic, timing window,...
QVector< unsigned int > events
Continuous FIFF raw recording: FiffInfo plus a random-access directory of FIFF_DATA_BUFFER tags.
bool read_raw_segment(Eigen::MatrixXd &data, Eigen::MatrixXd ×, fiff_int_t from=-1, fiff_int_t to=-1, const Eigen::RowVectorXi &sel=defaultRowVectorXi, bool do_debug=false) const
FIFF tag-stream reader/writer: wraps a QIODevice and exposes typed read_* / write_* methods for every...
QSharedPointer< FiffStream > SPtr
static FiffStream::SPtr start_file(QIODevice &p_IODevice)
std::unique_ptr< FiffTag > UPtr