{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "\n\n# Automated epochs metadata generation with variable time windows\n\nWhen working with :class:`~mne.Epochs`, `metadata ` can be\ninvaluable. There is an extensive tutorial on\n`how it can be generated automatically `.\nIn the brief examples below, we will demonstrate different ways to bound the time\nwindows used to generate the metadata.\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "# Authors: Richard H\u00f6chenberger \n#\n# License: BSD-3-Clause\n# Copyright the MNE-Python contributors." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We will use data from an EEG recording during an Eriksen flanker task. For the\npurpose of demonstration, we'll only load the first 60 seconds of data.\n\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "import mne\n\ndata_dir = mne.datasets.erp_core.data_path()\ninfile = data_dir / \"ERP-CORE_Subject-001_Task-Flankers_eeg.fif\"\n\nraw = mne.io.read_raw(infile, preload=True)\nraw.crop(tmax=60).filter(l_freq=0.1, h_freq=40)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Visualizing the events\n\nAll experimental events are stored in the :class:`~mne.io.Raw` instance as\n:class:`~mne.Annotations`. We first need to convert these to events and the\ncorresponding mapping from event codes to event names (``event_id``).\nWe then visualize the events.\n\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "all_events, all_event_id = mne.events_from_annotations(raw)\nmne.viz.plot_events(events=all_events, event_id=all_event_id, sfreq=raw.info[\"sfreq\"])" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "As you can see, there are four types of ``stimulus`` and two types of ``response``\nevents.\n\n## Declaring \"row events\"\n\nFor the sake of this example, we will assume that during analysis our epochs will be\ntime-locked to the stimulus onset events. Hence, we would like to create metadata with\none row per ``stimulus``. We can achieve this by specifying all stimulus event names\nas ``row_events``.\n\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "row_events = [\n \"stimulus/compatible/target_left\",\n \"stimulus/compatible/target_right\",\n \"stimulus/incompatible/target_left\",\n \"stimulus/incompatible/target_right\",\n]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Specifying metadata time windows\n\nNow, we will explore different ways of specifying the time windows around the\n``row_events`` when generating metadata. Any events falling within the same time\nwindow will be added to the same row in the metadata table.\n\n### Fixed time window\n\nA simple way to specify the time window extent is by specifying the time in seconds\nrelative to the row event. In the following example, the time window spans from the\nrow event (time point zero) up until three seconds later.\n\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "metadata_tmin = 0.0\nmetadata_tmax = 3.0\n\nmetadata, events, event_id = mne.epochs.make_metadata(\n events=all_events,\n event_id=all_event_id,\n tmin=metadata_tmin,\n tmax=metadata_tmax,\n sfreq=raw.info[\"sfreq\"],\n row_events=row_events,\n)\n\nmetadata" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "This looks good at the first glance. However, for example in the 2nd and 3rd row, we\nhave two responses listed (left and right). This is because the 3-second time window\nis obviously a bit too wide and captures more than one trial. While we could make it\nnarrower, this could lead to a loss of events \u2013 if the window might become **too**\nnarrow. Ultimately, this problem arises because the response time varies from trial\nto trial, so it's difficult for us to set a fixed upper bound for the time window.\n\n### Fixed time window with ``keep_first``\n\nOne workaround is using the ``keep_first`` parameter, which will create a new column\ncontaining the first event of the specified type.\n\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "metadata_tmin = 0.0\nmetadata_tmax = 3.0\nkeep_first = \"response\" # <-- new\n\nmetadata, events, event_id = mne.epochs.make_metadata(\n events=all_events,\n event_id=all_event_id,\n tmin=metadata_tmin,\n tmax=metadata_tmax,\n sfreq=raw.info[\"sfreq\"],\n row_events=row_events,\n keep_first=keep_first, # <-- new\n)\n\nmetadata" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "As you can see, a new column ``response`` was created with the time of the first\nresponse event falling inside the time window. The ``first_response`` column specifies\n**which** response occurred first (left or right).\n\n### Variable time window\n\nAnother way to address the challenge of variable time windows **without** the need to\ncreate new columns is by specifying ``tmin`` and ``tmax`` as event names. In this\nexample, we use ``tmin=row_events``, because we want the time window to start\nwith the time-locked event. ``tmax``, on the other hand, are the response events:\nThe first response event following ``tmin`` will be used to determine the duration of\nthe time window.\n\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "metadata_tmin = row_events\nmetadata_tmax = [\"response/left\", \"response/right\"]\n\nmetadata, events, event_id = mne.epochs.make_metadata(\n events=all_events,\n event_id=all_event_id,\n tmin=metadata_tmin,\n tmax=metadata_tmax,\n sfreq=raw.info[\"sfreq\"],\n row_events=row_events,\n)\n\nmetadata" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Variable time window (simplified)\n\nWe can slightly simplify the above code: Since ``tmin`` shall be set to the\n``row_events``, we can paass ``tmin=None``, which is a more convenient way to express\n``tmin=row_events``. The resulting metadata looks the same as in the previous example.\n\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "metadata_tmin = None # <-- new\nmetadata_tmax = [\"response/left\", \"response/right\"]\n\nmetadata, events, event_id = mne.epochs.make_metadata(\n events=all_events,\n event_id=all_event_id,\n tmin=metadata_tmin,\n tmax=metadata_tmax,\n sfreq=raw.info[\"sfreq\"],\n row_events=row_events,\n)\n\nmetadata" ] } ], "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 }