Skip to content

Commit

Permalink
Add option to ignore BrainVision marker types
Browse files Browse the repository at this point in the history
  • Loading branch information
cbrnr committed Jun 9, 2024
1 parent 07f429e commit aca320f
Show file tree
Hide file tree
Showing 3 changed files with 102 additions and 16 deletions.
9 changes: 7 additions & 2 deletions mne/annotations.py
Original file line number Diff line number Diff line change
Expand Up @@ -1143,7 +1143,7 @@ def _write_annotations_txt(fname, annot):

@fill_doc
def read_annotations(
fname, sfreq="auto", uint16_codec=None, encoding="utf8"
fname, sfreq="auto", uint16_codec=None, encoding="utf8", ignore_marker_types=False
) -> Annotations:
r"""Read annotations from a file.
Expand Down Expand Up @@ -1174,6 +1174,9 @@ def read_annotations(
arrays and can therefore help you solve this problem.
%(encoding_edf)s
Only used when reading EDF annotations.
ignore_marker_types : bool
If ``True``, ignore marker types in BrainVision files (and only use their
descriptions). Defaults to ``False``.
Returns
-------
Expand Down Expand Up @@ -1212,7 +1215,9 @@ def read_annotations(
annotations = _read_annotations_txt(fname)

elif name.endswith(("vmrk", "amrk")):
annotations = _read_annotations_brainvision(fname, sfreq=sfreq)
annotations = _read_annotations_brainvision(
fname, sfreq=sfreq, ignore_marker_types=ignore_marker_types
)

elif name.endswith("csv"):
annotations = _read_annotations_csv(fname)
Expand Down
66 changes: 52 additions & 14 deletions mne/io/brainvision/brainvision.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,11 @@ class RawBrainVision(BaseRaw):
scale : float
The scaling factor for EEG data. Unless specified otherwise by
header file, units are in microvolts. Default scale factor is 1.
ignore_marker_types : bool
If ``True``, ignore marker types and only use marker descriptions. Default is
``False``.
.. versionadded:: 1.8
%(preload)s
%(verbose)s
Expand All @@ -58,6 +63,15 @@ class RawBrainVision(BaseRaw):
impedances : dict
A dictionary of all electrodes and their impedances.
Notes
-----
BrainVision markers consist of a type and a description (in addition to other fields
like onset and duration). In contrast, annotations in MNE only have a description.
Therefore, a BrainVision marker of type "Stimulus" and description "S 1" will be
converted to an annotation "Stimulus/S 1" by default. If you want to ignore the
type and instead only use the description, set ``ignore_marker_types=True``, which
will convert the same marker to an annotation "S 1".
See Also
--------
mne.io.Raw : Documentation of attributes and methods.
Expand All @@ -72,6 +86,7 @@ def __init__(
eog=("HEOGL", "HEOGR", "VEOGb"),
misc="auto",
scale=1.0,
ignore_marker_types=False,
preload=False,
verbose=None,
): # noqa: D107
Expand Down Expand Up @@ -129,7 +144,9 @@ def __init__(
self.impedances = _parse_impedance(split_settings, self.info["meas_date"])

# Get annotations from marker file
annots = read_annotations(mrk_fname, info["sfreq"])
annots = read_annotations(
mrk_fname, info["sfreq"], ignore_marker_types=ignore_marker_types
)
self.set_annotations(annots)

# Drop the fake ahdr channel if needed
Expand Down Expand Up @@ -207,13 +224,15 @@ def _read_segments_c(raw, data, idx, fi, start, stop, cals, mult):
_mult_cal_one(data, block, idx, cals, mult)


def _read_mrk(fname):
def _read_mrk(fname, ignore_marker_types=False):
"""Read annotations from a vmrk/amrk file.
Parameters
----------
fname : str
vmrk/amrk file to be read.
ignore_marker_types : bool
If True, ignore marker types and only use marker descriptions. Default is False.
Returns
-------
Expand Down Expand Up @@ -293,36 +312,41 @@ def _read_mrk(fname):
this_duration = int(this_duration) if this_duration.isdigit() else 0
duration.append(this_duration)
onset.append(int(this_onset) - 1) # BV is 1-indexed, not 0-indexed
description.append(mtype + "/" + mdesc)
if not ignore_marker_types:
description.append(mtype + "/" + mdesc)
else:
description.append(mdesc)

return np.array(onset), np.array(duration), np.array(description), date_str


def _read_annotations_brainvision(fname, sfreq="auto"):
def _read_annotations_brainvision(fname, sfreq="auto", ignore_marker_types=False):
"""Create Annotations from BrainVision vmrk/amrk.
This function reads a .vmrk or .amrk file and makes an
:class:`mne.Annotations` object.
This function reads a .vmrk or .amrk file and creates an :class:`mne.Annotations`
object.
Parameters
----------
fname : str | object
The path to the .vmrk/.amrk file.
sfreq : float | 'auto'
The sampling frequency in the file. It's necessary
as Annotations are expressed in seconds and vmrk/amrk
files are in samples. If set to 'auto' then
the sfreq is taken from the .vhdr/.ahdr file that
has the same name (without file extension). So
data.vmrk/amrk looks for sfreq in data.vhdr or,
if it does not exist, in data.ahdr.
The sampling frequency in the file. This is necessary because Annotations are
expressed in seconds and vmrk/amrk files are in samples. If set to 'auto' then
the sfreq is taken from the .vhdr/.ahdr file with the same name (without file
extension). So data.vmrk/amrk looks for sfreq in data.vhdr or, if it does not
exist, in data.ahdr.
ignore_marker_types : bool
If True, ignore marker types and only use marker descriptions. Default is False.
Returns
-------
annotations : instance of Annotations
The annotations present in the file.
"""
onset, duration, description, date_str = _read_mrk(fname)
onset, duration, description, date_str = _read_mrk(
fname, ignore_marker_types=ignore_marker_types
)
orig_time = _str_to_meas_date(date_str)

if sfreq == "auto":
Expand Down Expand Up @@ -919,6 +943,7 @@ def read_raw_brainvision(
eog=("HEOGL", "HEOGR", "VEOGb"),
misc="auto",
scale=1.0,
ignore_marker_types=False,
preload=False,
verbose=None,
) -> RawBrainVision:
Expand All @@ -940,6 +965,9 @@ def read_raw_brainvision(
scale : float
The scaling factor for EEG data. Unless specified otherwise by
header file, units are in microvolts. Default scale factor is 1.
ignore_marker_types : bool
If ``True``, ignore marker types and only use marker descriptions. Default is
``False``.
%(preload)s
%(verbose)s
Expand All @@ -949,6 +977,15 @@ def read_raw_brainvision(
A Raw object containing BrainVision data.
See :class:`mne.io.Raw` for documentation of attributes and methods.
Notes
-----
BrainVision markers consist of a type and a description (in addition to other fields
like onset and duration). In contrast, annotations in MNE only have a description.
Therefore, a BrainVision marker of type "Stimulus" and description "S 1" will be
converted to an annotation "Stimulus/S 1" by default. If you want to ignore the
type and instead only use the description, set ``ignore_marker_types=True``, which
will convert the same marker to an annotation "S 1".
See Also
--------
mne.io.Raw : Documentation of attributes and methods of RawBrainVision.
Expand All @@ -958,6 +995,7 @@ def read_raw_brainvision(
eog=eog,
misc=misc,
scale=scale,
ignore_marker_types=ignore_marker_types,
preload=preload,
verbose=verbose,
)
Expand Down
43 changes: 43 additions & 0 deletions mne/io/brainvision/tests/test_brainvision.py
Original file line number Diff line number Diff line change
Expand Up @@ -661,6 +661,49 @@ def test_read_vmrk_annotations(tmp_path):
read_annotations(fname, sfreq=sfreq)


def test_ignore_marker_types():
"""Test ignore marker types."""
# default behavior (do not ignore marker types)
raw = read_raw_brainvision(vhdr_path)
expected_descriptions = [
"New Segment/",
"Stimulus/S253",
"Stimulus/S255",
"Event/254",
"Stimulus/S255",
"Event/254",
"Stimulus/S255",
"Stimulus/S253",
"Stimulus/S255",
"Response/R255",
"Event/254",
"Stimulus/S255",
"SyncStatus/Sync On",
"Optic/O 1",
]
assert_array_equal(raw.annotations.description, expected_descriptions)

# ignore marker types
raw = read_raw_brainvision(vhdr_path, ignore_marker_types=True)
expected_descriptions = [
"",
"S253",
"S255",
"254",
"S255",
"254",
"S255",
"S253",
"S255",
"R255",
"254",
"S255",
"Sync On",
"O 1",
]
assert_array_equal(raw.annotations.description, expected_descriptions)


@testing.requires_testing_data
def test_read_vhdr_annotations_and_events(tmp_path):
"""Test load brainvision annotations and parse them to events."""
Expand Down

0 comments on commit aca320f

Please sign in to comment.