{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "\n\n# Parsing events from raw data\n\nThis tutorial describes how to read experimental events from raw recordings,\nand how to convert between the two different representations of events within\nMNE-Python (Events arrays and Annotations objects).\n\nIn the `introductory tutorial ` we saw an\nexample of reading experimental events from a :term:`\"STIM\" channel `; here we'll discuss :term:`events` and :term:`annotations` more\nbroadly, give more detailed information about reading from STIM channels, and\ngive an example of reading events that are in a marker file or included in\nthe data file as an embedded array. The tutorials `tut-event-arrays` and\n`tut-annotate-raw` discuss how to plot, combine, load, save, and\nexport :term:`events` and `~mne.Annotations` (respectively), and the\nlatter tutorial also covers interactive annotation of `~mne.io.Raw`\nobjects.\n\nWe'll begin by loading the Python modules we need, and loading the same\n`example data ` we used in the `introductory tutorial\n`, but to save memory we'll crop the `~mne.io.Raw` object\nto just 60 seconds before loading it into RAM:\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "# Authors: The MNE-Python contributors.\n# License: BSD-3-Clause\n# Copyright the MNE-Python contributors." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "import numpy as np\n\nimport mne\n\nsample_data_folder = mne.datasets.sample.data_path()\nsample_data_raw_file = sample_data_folder / \"MEG\" / \"sample\" / \"sample_audvis_raw.fif\"\nraw = mne.io.read_raw_fif(sample_data_raw_file)\nraw.crop(tmax=60).load_data()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## The Events and Annotations data structures\n\nGenerally speaking, both the Events and `~mne.Annotations` data\nstructures serve the same purpose: they provide a mapping between times\nduring an EEG/MEG recording and a description of what happened at those\ntimes. In other words, they associate a *when* with a *what*. The main\ndifferences are:\n\n1. **Units**: the Events data structure represents the *when* in terms of\n samples, whereas the `~mne.Annotations` data structure represents\n the *when* in seconds.\n2. **Limits on the description**: the Events data structure represents the\n *what* as an integer \"Event ID\" code, whereas the `~mne.Annotations` data\n structure represents the *what* as a string.\n3. **How duration is encoded**: Events in an Event array do not have a\n duration (though it is possible to represent duration with pairs of\n onset/offset events within an Events array), whereas each element of an\n `~mne.Annotations` object necessarily includes a duration (though\n the duration can be zero if an instantaneous event is desired).\n4. **Internal representation**: Events are stored as an ordinary\n :class:`NumPy array `, whereas `~mne.Annotations` is\n a :class:`list`-like class defined in MNE-Python.\n\n\n## What is a STIM channel?\n\nA :term:`stim channel` (short for \"stimulus channel\") is a channel that does\nnot receive signals from an EEG, MEG, or other sensor. Instead, STIM channels\nrecord voltages (usually short, rectangular DC pulses of fixed magnitudes\nsent from the experiment-controlling computer) that are time-locked to\nexperimental events, such as the onset of a stimulus or a button-press\nresponse by the subject (those pulses are sometimes called `TTL`_ pulses,\nevent pulses, trigger signals, or just \"triggers\"). In other cases, these\npulses may not be strictly time-locked to an experimental event, but instead\nmay occur in between trials to indicate the type of stimulus (or experimental\ncondition) that is about to occur on the upcoming trial.\n\nThe DC pulses may be all on one STIM channel (in which case different\nexperimental events or trial types are encoded as different voltage\nmagnitudes), or they may be spread across several channels, in which case the\nchannel(s) on which the pulse(s) occur can be used to encode different events\nor conditions. Even on systems with multiple STIM channels, there is often\none channel that records a weighted sum of the other STIM channels, in such a\nway that voltage levels on that channel can be unambiguously decoded as\nparticular event types. On older Neuromag systems (such as that used to\nrecord the sample data) this \"summation channel\" was typically ``STI 014``;\non newer systems it is more commonly ``STI101``. You can see the STIM\nchannels in the raw data file here:\n\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "raw.copy().pick(picks=\"stim\").plot(start=3, duration=6)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "You can see that ``STI 014`` (the summation channel) contains pulses of\ndifferent magnitudes whereas pulses on other channels have consistent\nmagnitudes. You can also see that every time there is a pulse on one of the\nother STIM channels, there is a corresponding pulse on ``STI 014``.\n\n.. TODO: somewhere in prev. section, link out to a table of which systems\n have STIM channels vs. which have marker files or embedded event arrays\n (once such a table has been created).\n\n\n## Converting a STIM channel signal to an Events array\n\nIf your data has events recorded on a STIM channel, you can convert them into\nan events array using `~mne.find_events`. The sample number of the onset\n(or offset) of each pulse is recorded as the event time, the pulse magnitudes\nare converted into integers, and these pairs of sample numbers plus integer\ncodes are stored in :class:`NumPy arrays ` (usually called\n\"the events array\" or just \"the events\"). In its simplest form, the function\nrequires only the `~mne.io.Raw` object, and the name of the channel(s)\nfrom which to read events:\n\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "events = mne.find_events(raw, stim_channel=\"STI 014\")\nprint(events[:5]) # show the first 5" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ ".. admonition:: The middle column of the Events array\n :class: sidebar note\n\n MNE-Python events are actually *three* values: in between the sample\n number and the integer event code is a value indicating what the event\n code was on the immediately preceding sample. In practice, that value is\n almost always ``0``, but it can be used to detect the *endpoint* of an\n event whose duration is longer than one sample. See the documentation of\n `~mne.find_events` for more details.\n\nIf you don't provide the name of a STIM channel, `~mne.find_events`\nwill first look for MNE-Python `config variables `\nfor variables ``MNE_STIM_CHANNEL``, ``MNE_STIM_CHANNEL_1``, etc. If those are\nnot found, channels ``STI 014`` and ``STI101`` are tried, followed by the\nfirst channel with type \"STIM\" present in ``raw.ch_names``. If you regularly\nwork with data from several different MEG systems with different STIM channel\nnames, setting the ``MNE_STIM_CHANNEL`` config variable may not be very\nuseful, but for researchers whose data is all from a single system it can be\na time-saver to configure that variable once and then forget about it.\n\n`~mne.find_events` has several options, including options for aligning\nevents to the onset or offset of the STIM channel pulses, setting the minimum\npulse duration, and handling of consecutive pulses (with no return to zero\nbetween them). For example, you can effectively encode event duration by\npassing ``output='step'`` to `~mne.find_events`; see the documentation\nof `~mne.find_events` for details. More information on working with\nevents arrays (including how to plot, combine, load, and save event arrays)\ncan be found in the tutorial `tut-event-arrays`.\n\n\n## Reading embedded events as Annotations\n\nSome EEG/MEG systems generate files where events are stored in a separate\ndata array rather than as pulses on one or more STIM channels. For example,\nthe EEGLAB format stores events as a collection of arrays in the :file:`.set`\nfile. When reading those files, MNE-Python will automatically convert the\nstored events into an `~mne.Annotations` object and store it as the\n:attr:`~mne.io.Raw.annotations` attribute of the `~mne.io.Raw` object:\n\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "testing_data_folder = mne.datasets.testing.data_path()\neeglab_raw_file = testing_data_folder / \"EEGLAB\" / \"test_raw.set\"\neeglab_raw = mne.io.read_raw_eeglab(eeglab_raw_file)\nprint(eeglab_raw.annotations)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The core data within an `~mne.Annotations` object is accessible\nthrough three of its attributes: ``onset``, ``duration``, and\n``description``. Here we can see that there were 154 events stored in the\nEEGLAB file, they all had a duration of zero seconds, there were two\ndifferent types of events, and the first event occurred about 1 second after\nthe recording began:\n\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "print(len(eeglab_raw.annotations))\nprint(set(eeglab_raw.annotations.duration))\nprint(set(eeglab_raw.annotations.description))\nprint(eeglab_raw.annotations.onset[0])" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "More information on working with `~mne.Annotations` objects, including\nhow to add annotations to `~mne.io.Raw` objects interactively, and how\nto plot, concatenate, load, save, and export `~mne.Annotations`\nobjects can be found in the tutorial `tut-annotate-raw`.\n\n\n## Converting between Events arrays and Annotations objects\n\nOnce your experimental events are read into MNE-Python (as either an Events\narray or an `~mne.Annotations` object), you can easily convert between\nthe two formats as needed. You might do this because, e.g., an Events array\nis needed for epoching continuous data, or because you want to take advantage\nof the \"annotation-aware\" capability of some functions, which automatically\nomit spans of data if they overlap with certain annotations.\n\nTo convert an `~mne.Annotations` object to an Events array, use the\nfunction `mne.events_from_annotations` on the `~mne.io.Raw` file\ncontaining the annotations. This function will assign an integer Event ID to\neach unique element of ``raw.annotations.description``, and will return the\nmapping of descriptions to integer Event IDs along with the derived Event\narray. By default, one event will be created at the onset of each annotation;\nthis can be modified via the ``chunk_duration`` parameter of\n`~mne.events_from_annotations` to create equally spaced events within\neach annotation span (see `chunk-duration`, below, or see\n`fixed-length-events` for direct creation of an Events array of\nequally-spaced events).\n\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "events_from_annot, event_dict = mne.events_from_annotations(eeglab_raw)\nprint(event_dict)\nprint(events_from_annot[:5])" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "If you want to control which integers are mapped to each unique description\nvalue, you can pass a :class:`dict` specifying the mapping as the\n``event_id`` parameter of `~mne.events_from_annotations`; this\n:class:`dict` will be returned unmodified as the ``event_dict``.\n\nNote that this ``event_dict`` can be used when creating `~mne.Epochs` from\n`~mne.io.Raw` objects, as demonstrated in the tutorial\n`tut-epochs-class`.\n\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "custom_mapping = {\"rt\": 77, \"square\": 42}\n(events_from_annot, event_dict) = mne.events_from_annotations(\n eeglab_raw, event_id=custom_mapping\n)\nprint(event_dict)\nprint(events_from_annot[:5])" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "To make the opposite conversion (from an Events array to an\n`~mne.Annotations` object), you can create a mapping from integer\nEvent ID to string descriptions, use `~mne.annotations_from_events`\nto construct the `~mne.Annotations` object, and call the\n`~mne.io.Raw.set_annotations` method to add the annotations to the\n`~mne.io.Raw` object.\n\nBecause the `sample data ` was recorded on a Neuromag\nsystem (where sample numbering starts when the acquisition system is\ninitiated, not when the *recording* is initiated), we also need to pass in\nthe ``orig_time`` parameter so that the onsets are properly aligned relative\nto the start of recording:\n\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "mapping = {\n 1: \"auditory/left\",\n 2: \"auditory/right\",\n 3: \"visual/left\",\n 4: \"visual/right\",\n 5: \"smiley\",\n 32: \"buttonpress\",\n}\nannot_from_events = mne.annotations_from_events(\n events=events,\n event_desc=mapping,\n sfreq=raw.info[\"sfreq\"],\n orig_time=raw.info[\"meas_date\"],\n)\nraw.set_annotations(annot_from_events)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Now, the annotations will appear automatically when plotting the raw data,\nand will be color-coded by their label value:\n\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "raw.plot(start=5, duration=5)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "\n## Making multiple events per annotation\n\nAs mentioned above, you can generate equally-spaced events from an\n`~mne.Annotations` object using the ``chunk_duration`` parameter of\n`~mne.events_from_annotations`. For example, suppose we have an\nannotation in our `~mne.io.Raw` object indicating when the subject was\nin REM sleep, and we want to perform a resting-state analysis on those spans\nof data. We can create an Events array with a series of equally-spaced events\nwithin each \"REM\" span, and then use those events to generate (potentially\noverlapping) epochs that we can analyze further.\n\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "# create the REM annotations\nrem_annot = mne.Annotations(onset=[5, 41], duration=[16, 11], description=[\"REM\"] * 2)\nraw.set_annotations(rem_annot)\n(rem_events, rem_event_dict) = mne.events_from_annotations(raw, chunk_duration=1.5)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Now we can check that our events indeed fall in the ranges 5-21 seconds and\n41-52 seconds, and are ~1.5 seconds apart (modulo some jitter due to the\nsampling frequency). Here are the event times rounded to the nearest\nmillisecond:\n\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "print(np.round((rem_events[:, 0] - raw.first_samp) / raw.info[\"sfreq\"], 3))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Other examples of resting-state analysis can be found in the online\ndocumentation for `~mne.make_fixed_length_events`.\n\n.. LINKS\n\n\n" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.12.2" } }, "nbformat": 4, "nbformat_minor": 0 }