Skip to content

Commit

Permalink
Add example
Browse files Browse the repository at this point in the history
  • Loading branch information
hoechenberger committed Feb 27, 2024
1 parent f9293d2 commit 6f03ce1
Show file tree
Hide file tree
Showing 2 changed files with 172 additions and 3 deletions.
171 changes: 171 additions & 0 deletions examples/preprocessing/epochs_metadata.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
"""
.. _epochs-metadata:
===============================================================
Automated epochs metadata generation with variable time windows
===============================================================
When working with :class:`~mne.Epochs`, :ref:`metadata <tut-epochs-metadata>` can be
invaluable. There is an extensive tutorial on
:ref:`how it can be generated automatically <tut-autogenerate-metadata>`.
In the brief examples below, we will demonstrate different ways to bound the time
windows used to generate the metadata.
"""
# Authors: Richard Höchenberger <[email protected]>
#
# License: BSD-3-Clause
# Copyright the MNE-Python contributors.

# %%
# We will use data from an EEG recording during an Eriksen flanker task task. For the
# purpose of demonstration, we'll only load the first 60 seconds of data.

import mne

data_dir = mne.datasets.erp_core.data_path()
infile = data_dir / "ERP-CORE_Subject-001_Task-Flankers_eeg.fif"

raw = mne.io.read_raw(infile, preload=True)
raw.crop(tmax=60).filter(l_freq=0.1, h_freq=40)

# %%
# Visualizing the events
# ^^^^^^^^^^^^^^^^^^^^^^
#
# All experimental events are stored in the :class:`mne.io.Raw` instance as
# :class:`mne.Annotations`. We first need to convert these to events and the
# corresponding mapping from event codes to event names (``event_id``). We then
# visualize the events.
all_events, all_event_id = mne.events_from_annotations(raw)
mne.viz.plot_events(events=all_events, event_id=all_event_id, sfreq=raw.info["sfreq"])


# %%
# As you can see, there are four types of ``stimulus`` and two types of ``response``
# events.
#
# Declaring "row events"
# ^^^^^^^^^^^^^^^^^^^^^^
#
# For the sake of this example, we will assume that during analysis our epochs will be
# time-locked to the stimulus onset events. Hence, we would like to create metadata with
# one row per stimulus. We can achieve this by specifying all stimulus event names as
# ``row_events``.

row_events = [
"stimulus/compatible/target_left",
"stimulus/compatible/target_right",
"stimulus/incompatible/target_left",
"stimulus/incompatible/target_right",
]

# %%
# Specifying metadata time windows
# ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
#
# Now, we will explore different ways of specifying the time windows around the
# ``row_events`` when generating metadata. Any events falling within the same time
# window will be added to the same row in the metadata table.
#
# Fixed time window
# ~~~~~~~~~~~~~~~~~
#
# A simple way to specify the time window extent is by specifying the time in seconds
# relative to the row event. In the following example, the time window spans from the
# row event (time point zero) up until three seconds later.

metadata_tmin = 0.0
metadata_tmax = 3.0

metadata, events, event_id = mne.epochs.make_metadata(
events=all_events,
event_id=all_event_id,
tmin=metadata_tmin,
tmax=metadata_tmax,
sfreq=raw.info["sfreq"],
row_events=row_events,
)

metadata

# %%
# This looks good at the first glance. However, for example in the 2nd and 3rd row, we
# have two responses listed (left and right). This is because the 3-second time window
# is obviously a bit too wide and captures more than one trial. While we could make it
# narrower, this could lead to a loss of events – if the window becomes **too** narrow.
# Ultimately, this problem arises because the response time varies from trial to trial,
# so it's difficult for us to set a fixed upper bound for the time window.
#
# Fixed time window with ``keep_first``
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
#
# One workaround is using the ``keep_first`` paramter, which will create a new column
# containing the first event of the specified type.

metadata_tmin = 0.0
metadata_tmax = 3.0
keep_first = "response" # <-- new

metadata, events, event_id = mne.epochs.make_metadata(
events=all_events,
event_id=all_event_id,
tmin=metadata_tmin,
tmax=metadata_tmax,
sfreq=raw.info["sfreq"],
row_events=row_events,
keep_first=keep_first, # <-- new
)

metadata

# %%
# As you can see, a new column ``response`` was created with the time of the first
# response event falling inside the time window. The ``first_response`` column specifies
# **which** response occurred first (left or right).
#
# Variable time window
# ~~~~~~~~~~~~~~~~~~~~
#
# Another way to address the challenge of variable time windows **without** the need to
# create new columns is by specifying ``tmin`` and ``tmax`` as event names. In this
# example, we use ``tmin=row_events``, because we want the time window to start
# with the time-locked event. ``tmax``, on the other hand, are the response events:
# The first response event following ``tmin`` will be used to determine the duration of
# the time window.

metadata_tmin = row_events
metadata_tmax = ["response/left", "response/right"]

metadata, events, event_id = mne.epochs.make_metadata(
events=all_events,
event_id=all_event_id,
tmin=metadata_tmin,
tmax=metadata_tmax,
sfreq=raw.info["sfreq"],
row_events=row_events,
)

metadata

# %%
# Variable time window (simplified)
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
#
# We can slightly simplify the above code: Since ``tmin`` shall be set to the
# ``row_events``, we can paass ``tmin=None``, which is a more convenient way to express
# ``tmin=row_events``. The resulting metadata looks the same as in the previous example.

metadata_tmin = None # <-- new
metadata_tmax = ["response/left", "response/right"]

metadata, events, event_id = mne.epochs.make_metadata(
events=all_events,
event_id=all_event_id,
tmin=metadata_tmin,
tmax=metadata_tmax,
sfreq=raw.info["sfreq"],
row_events=row_events,
)

metadata
4 changes: 1 addition & 3 deletions tutorials/epochs/40_autogenerate_metadata.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,13 +46,11 @@
# Copyright the MNE-Python contributors.
# %%

from pathlib import Path

import matplotlib.pyplot as plt

import mne

data_dir = Path(mne.datasets.erp_core.data_path())
data_dir = mne.datasets.erp_core.data_path()
infile = data_dir / "ERP-CORE_Subject-001_Task-Flankers_eeg.fif"

raw = mne.io.read_raw(infile, preload=True)
Expand Down

0 comments on commit 6f03ce1

Please sign in to comment.