Source code for moabb.datasets.bnci

"""
BNCI 2014-001 Motor imagery dataset.
"""

import numpy as np
from mne import create_info
from mne.channels import make_standard_montage
from mne.io import RawArray
from mne.utils import verbose
from scipy.io import loadmat

from moabb.datasets import download as dl
from moabb.datasets.base import BaseDataset


BNCI_URL = "http://bnci-horizon-2020.eu/database/data-sets/"
BBCI_URL = "http://doc.ml.tu-berlin.de/bbci/"


def data_path(url, path=None, force_update=False, update_path=None, verbose=None):
    return [dl.data_dl(url, "BNCI", path, force_update, verbose)]


@verbose
def load_data(
    subject,
    dataset="001-2014",
    path=None,
    force_update=False,
    update_path=None,
    base_url=BNCI_URL,
    verbose=None,
):  # noqa: D301
    """Get paths to local copies of a BNCI dataset files.

    This will fetch data for a given BNCI dataset. Report to the bnci website
    for a complete description of the experimental setup of each dataset.

    Parameters
    ----------
    subject : int
        The subject to load.
    dataset : string
        The bnci dataset name.
    path : None | str
        Location of where to look for the BNCI data storing location.
        If None, the environment variable or config parameter
        ``MNE_DATASETS_BNCI_PATH`` is used. If it doesn't exist, the
        "~/mne_data" directory is used. If the BNCI dataset
        is not found under the given path, the data
        will be automatically downloaded to the specified folder.
    force_update : bool
        Force update of the dataset even if a local copy exists.
    update_path : bool | None
        If True, set the MNE_DATASETS_BNCI_PATH in mne-python
        config to the given path. If None, the user is prompted.
    verbose : bool, str, int, or None
        If not None, override default verbose level (see :func:`mne.verbose`
        and :ref:`Logging documentation <tut_logging>` for more).

    Returns
    -------
    raws : list
        List of raw instances for each non consecutive recording. Depending
        on the dataset it could be a BCI run or a different recording session.
    event_id: dict
        dictonary containing events and their code.
    """
    dataset_list = {
        "001-2014": _load_data_001_2014,
        "002-2014": _load_data_002_2014,
        "004-2014": _load_data_004_2014,
        "008-2014": _load_data_008_2014,
        "009-2014": _load_data_009_2014,
        "001-2015": _load_data_001_2015,
        "003-2015": _load_data_003_2015,
        "004-2015": _load_data_004_2015,
        "009-2015": _load_data_009_2015,
        "010-2015": _load_data_010_2015,
        "012-2015": _load_data_012_2015,
        "013-2015": _load_data_013_2015,
    }

    baseurl_list = {
        "001-2014": BNCI_URL,
        "002-2014": BNCI_URL,
        "001-2015": BNCI_URL,
        "004-2014": BNCI_URL,
        "008-2014": BNCI_URL,
        "009-2014": BNCI_URL,
        "003-2015": BNCI_URL,
        "004-2015": BNCI_URL,
        "009-2015": BBCI_URL,
        "010-2015": BBCI_URL,
        "012-2015": BBCI_URL,
        "013-2015": BNCI_URL,
    }

    if dataset not in dataset_list.keys():
        raise ValueError(
            "Dataset '%s' is not a valid BNCI dataset ID. "
            "Valid dataset are %s." % (dataset, ", ".join(dataset_list.keys()))
        )

    return dataset_list[dataset](
        subject, path, force_update, update_path, baseurl_list[dataset], verbose
    )


@verbose
def _load_data_001_2014(
    subject,
    path=None,
    force_update=False,
    update_path=None,
    base_url=BNCI_URL,
    verbose=None,
):
    """Load data for 001-2014 dataset."""
    if (subject < 1) or (subject > 9):
        raise ValueError("Subject must be between 1 and 9. Got %d." % subject)

    # fmt: off
    ch_names = [
        "Fz", "FC3", "FC1", "FCz", "FC2", "FC4", "C5", "C3", "C1", "Cz", "C2",
        "C4", "C6", "CP3", "CP1", "CPz", "CP2", "CP4", "P1", "Pz", "P2", "POz",
        "EOG1", "EOG2", "EOG3",
    ]
    # fmt: on
    ch_types = ["eeg"] * 22 + ["eog"] * 3

    sessions = {}
    for r in ["T", "E"]:
        url = "{u}001-2014/A{s:02d}{r}.mat".format(u=base_url, s=subject, r=r)
        filename = data_path(url, path, force_update, update_path)
        runs, ev = _convert_mi(filename[0], ch_names, ch_types)
        # FIXME: deal with run with no event (1:3) and name them
        sessions["session_%s" % r] = {"run_%d" % ii: run for ii, run in enumerate(runs)}
    return sessions


@verbose
def _load_data_002_2014(
    subject,
    path=None,
    force_update=False,
    update_path=None,
    base_url=BNCI_URL,
    verbose=None,
):
    """Load data for 002-2014 dataset."""
    if (subject < 1) or (subject > 14):
        raise ValueError("Subject must be between 1 and 14. Got %d." % subject)

    runs = []
    for r in ["T", "E"]:
        url = "{u}002-2014/S{s:02d}{r}.mat".format(u=base_url, s=subject, r=r)
        filename = data_path(url, path, force_update, update_path)[0]

        # FIXME: electrode position and name are not provided directly.
        raws, _ = _convert_mi(filename, None, ["eeg"] * 15)
        runs.extend(raws)

    runs = {"run_%d" % ii: run for ii, run in enumerate(runs)}
    return {"session_0": runs}


@verbose
def _load_data_004_2014(
    subject,
    path=None,
    force_update=False,
    update_path=None,
    base_url=BNCI_URL,
    verbose=None,
):
    """Load data for 004-2014 dataset."""
    if (subject < 1) or (subject > 9):
        raise ValueError("Subject must be between 1 and 9. Got %d." % subject)

    ch_names = ["C3", "Cz", "C4", "EOG1", "EOG2", "EOG3"]
    ch_types = ["eeg"] * 3 + ["eog"] * 3

    sessions = []
    for r in ["T", "E"]:
        url = "{u}004-2014/B{s:02d}{r}.mat".format(u=base_url, s=subject, r=r)
        filename = data_path(url, path, force_update, update_path)[0]
        raws, _ = _convert_mi(filename, ch_names, ch_types)
        sessions.extend(raws)

    sessions = {"session_%d" % ii: {"run_0": run} for ii, run in enumerate(sessions)}
    return sessions


@verbose
def _load_data_008_2014(
    subject,
    path=None,
    force_update=False,
    update_path=None,
    base_url=BNCI_URL,
    verbose=None,
):
    """Load data for 008-2014 dataset."""
    if (subject < 1) or (subject > 8):
        raise ValueError("Subject must be between 1 and 8. Got %d." % subject)

    url = "{u}008-2014/A{s:02d}.mat".format(u=base_url, s=subject)
    filename = data_path(url, path, force_update, update_path)[0]

    run = loadmat(filename, struct_as_record=False, squeeze_me=True)["data"]
    raw, event_id = _convert_run_p300_sl(run, verbose=verbose)

    sessions = {"session_0": {"run_0": raw}}

    return sessions


@verbose
def _load_data_009_2014(
    subject,
    path=None,
    force_update=False,
    update_path=None,
    base_url=BNCI_URL,
    verbose=None,
):
    """Load data for 009-2014 dataset."""
    if (subject < 1) or (subject > 10):
        raise ValueError("Subject must be between 1 and 10. Got %d." % subject)

    # FIXME there is two type of speller, grid speller and geo-speller.
    # we load only grid speller data
    url = "{u}009-2014/A{s:02d}S.mat".format(u=base_url, s=subject)
    filename = data_path(url, path, force_update, update_path)[0]

    data = loadmat(filename, struct_as_record=False, squeeze_me=True)["data"]
    sess = []
    event_id = {}
    for run in data:
        raw, ev = _convert_run_p300_sl(run, verbose=verbose)
        sess.append(raw)
        event_id.update(ev)

    sessions = {}
    for i, sessi in enumerate(sess):
        sessions["session_" + str(i)] = {"run_0": sessi}

    return sessions


@verbose
def _load_data_001_2015(
    subject,
    path=None,
    force_update=False,
    update_path=None,
    base_url=BNCI_URL,
    verbose=None,
):
    """Load data for 001-2015 dataset."""
    if (subject < 1) or (subject > 12):
        raise ValueError("Subject must be between 1 and 12. Got %d." % subject)

    if subject in [8, 9, 10, 11]:
        ses = ["A", "B", "C"]  # 3 sessions for those subjects
    else:
        ses = ["A", "B"]

    # fmt: off
    ch_names = [
        "FC3", "FCz", "FC4", "C5", "C3", "C1", "Cz",
        "C2", "C4", "C6", "CP3", "CPz", "CP4",
    ]
    # fmt: on
    ch_types = ["eeg"] * 13

    sessions = {}
    for r in ses:
        url = "{u}001-2015/S{s:02d}{r}.mat".format(u=base_url, s=subject, r=r)
        filename = data_path(url, path, force_update, update_path)
        runs, ev = _convert_mi(filename[0], ch_names, ch_types)
        sessions["session_%s" % r] = {"run_%d" % ii: run for ii, run in enumerate(runs)}
    return sessions


@verbose
def _load_data_003_2015(
    subject,
    path=None,
    force_update=False,
    update_path=None,
    base_url=BNCI_URL,
    verbose=None,
):
    """Load data for 003-2015 dataset."""
    if (subject < 1) or (subject > 10):
        raise ValueError("Subject must be between 1 and 12. Got %d." % subject)

    url = "{u}003-2015/s{s:d}.mat".format(u=base_url, s=subject)
    filename = data_path(url, path, force_update, update_path)[0]

    data = loadmat(filename, struct_as_record=False, squeeze_me=True)
    data = data["s%d" % subject]
    sfreq = 256.0

    ch_names = ["Fz", "Cz", "P3", "Pz", "P4", "PO7", "Oz", "PO8", "Target", "Flash"]

    ch_types = ["eeg"] * 8 + ["stim"] * 2
    montage = make_standard_montage("standard_1005")

    info = create_info(ch_names=ch_names, ch_types=ch_types, sfreq=sfreq)

    sessions = {}
    sessions["session_0"] = {}
    for ri, run in enumerate([data.train, data.test]):
        # flash events on the channel 9
        flashs = run[9:10]
        ix_flash = flashs[0] > 0
        flashs[0, ix_flash] += 2  # add 2 to avoid overlapp on event id
        flash_code = np.unique(flashs[0, ix_flash])

        if len(flash_code) == 36:
            # char mode
            evd = {"Char%d" % ii: (ii + 2) for ii in range(1, 37)}
        else:
            # row / column mode
            evd = {"Col%d" % ii: (ii + 2) for ii in range(1, 7)}
            evd.update({"Row%d" % ii: (ii + 8) for ii in range(1, 7)})

        # target events are on channel 10
        targets = np.zeros_like(flashs)
        targets[0, ix_flash] = run[10, ix_flash] + 1

        eeg_data = np.r_[run[1:-2] * 1e-6, targets, flashs]
        raw = RawArray(data=eeg_data, info=info, verbose=verbose)
        raw.set_montage(montage)
        sessions["session_0"]["run_" + str(ri)] = raw

    return sessions


@verbose
def _load_data_004_2015(
    subject,
    path=None,
    force_update=False,
    update_path=None,
    base_url=BNCI_URL,
    verbose=None,
):
    """Load data for 004-2015 dataset."""
    if (subject < 1) or (subject > 9):
        raise ValueError("Subject must be between 1 and 9. Got %d." % subject)

    subjects = ["A", "C", "D", "E", "F", "G", "H", "J", "L"]

    url = "{u}004-2015/{s}.mat".format(u=base_url, s=subjects[subject - 1])
    filename = data_path(url, path, force_update, update_path)[0]

    # fmt: off
    ch_names = [
        "AFz", "F7", "F3", "Fz", "F4", "F8", "FC3", "FCz", "FC4", "T3", "C3",
        "Cz", "C4", "T4", "CP3", "CPz", "CP4", "P7", "P5", "P3", "P1", "Pz",
        "P2", "P4", "P6", "P8", "PO3", "PO4", "O1", "O2",
    ]
    # fmt: on
    ch_types = ["eeg"] * 30
    raws, ev = _convert_mi(filename, ch_names, ch_types)
    sessions = {"session_%d" % ii: {"run_0": run} for ii, run in enumerate(raws)}
    return sessions


@verbose
def _load_data_009_2015(
    subject,
    path=None,
    force_update=False,
    update_path=None,
    base_url=BBCI_URL,
    verbose=None,
):
    """Load data for 009-2015 dataset."""
    if (subject < 1) or (subject > 21):
        raise ValueError("Subject must be between 1 and 21. Got %d." % subject)

    # fmt: off
    subjects = [
        "fce", "kw", "faz", "fcj", "fcg", "far", "faw", "fax", "fcc", "fcm", "fas",
        "fch", "fcd", "fca", "fcb", "fau", "fci", "fav", "fat", "fcl", "fck",
    ]
    # fmt: on
    s = subjects[subject - 1]
    url = "{u}BNCIHorizon2020-AMUSE/AMUSE_VP{s}.mat".format(u=base_url, s=s)
    filename = data_path(url, path, force_update, update_path)[0]

    ch_types = ["eeg"] * 60 + ["eog"] * 2

    return _convert_bbci(filename, ch_types, verbose=None)


@verbose
def _load_data_010_2015(
    subject,
    path=None,
    force_update=False,
    update_path=None,
    base_url=BBCI_URL,
    verbose=None,
):
    """Load data for 010-2015 dataset."""
    if (subject < 1) or (subject > 12):
        raise ValueError("Subject must be between 1 and 12. Got %d." % subject)

    # fmt: off
    subjects = [
        "fat", "gcb", "gcc", "gcd", "gce", "gcf",
        "gcg", "gch", "iay", "icn", "icr", "pia",
    ]
    # fmt: on

    s = subjects[subject - 1]
    url = "{u}BNCIHorizon2020-RSVP/RSVP_VP{s}.mat".format(u=base_url, s=s)
    filename = data_path(url, path, force_update, update_path)[0]

    ch_types = ["eeg"] * 63

    return _convert_bbci(filename, ch_types, verbose=None)


@verbose
def _load_data_012_2015(
    subject,
    path=None,
    force_update=False,
    update_path=None,
    base_url=BBCI_URL,
    verbose=None,
):
    """Load data for 012-2015 dataset."""
    if (subject < 1) or (subject > 12):
        raise ValueError("Subject must be between 1 and 12. Got %d." % subject)

    subjects = ["nv", "nw", "nx", "ny", "nz", "mg", "oa", "ob", "oc", "od", "ja", "oe"]

    s = subjects[subject - 1]
    url = "{u}BNCIHorizon2020-PASS2D/PASS2D_VP{s}.mat".format(u=base_url, s=s)
    filename = data_path(url, path, force_update, update_path)[0]

    ch_types = ["eeg"] * 63

    return _convert_bbci(filename, ch_types, verbose=None)


@verbose
def _load_data_013_2015(
    subject,
    path=None,
    force_update=False,
    update_path=None,
    base_url=BNCI_URL,
    verbose=None,
):
    """Load data for 013-2015 dataset."""
    if (subject < 1) or (subject > 6):
        raise ValueError("Subject must be between 1 and 6. Got %d." % subject)

    data_paths = []
    for r in ["s1", "s2"]:
        url = "{u}013-2015/Subject{s:02d}_{r}.mat".format(u=base_url, s=subject, r=r)
        data_paths.extend(data_path(url, path, force_update, update_path))

    raws = []
    event_id = {}

    for filename in data_paths:
        data = loadmat(filename, struct_as_record=False, squeeze_me=True)
        for run in data["run"]:
            raw, evd = _convert_run_epfl(run, verbose=verbose)
            raws.append(raw)
            event_id.update(evd)
    return raws, event_id


def _convert_mi(filename, ch_names, ch_types):
    """
    Processes (Graz) motor imagery data from MAT files, returns list of
    recording runs.
    """
    runs = []
    event_id = {}
    data = loadmat(filename, struct_as_record=False, squeeze_me=True)

    if isinstance(data["data"], np.ndarray):
        run_array = data["data"]
    else:
        run_array = [data["data"]]

    for run in run_array:
        raw, evd = _convert_run(run, ch_names, ch_types, None)
        if raw is None:
            continue
        runs.append(raw)
        event_id.update(evd)
    # change labels to match rest
    standardize_keys(event_id)
    return runs, event_id


def standardize_keys(d):
    master_list = [
        ["both feet", "feet"],
        ["left hand", "left_hand"],
        ["right hand", "right_hand"],
        ["FEET", "feet"],
        ["HAND", "right_hand"],
        ["NAV", "navigation"],
        ["SUB", "subtraction"],
        ["WORD", "word_ass"],
    ]
    for old, new in master_list:
        if old in d.keys():
            d[new] = d.pop(old)


@verbose
def _convert_run(run, ch_names=None, ch_types=None, verbose=None):
    """Convert one run to raw."""
    # parse eeg data
    event_id = {}
    n_chan = run.X.shape[1]
    montage = make_standard_montage("standard_1005")
    eeg_data = 1e-6 * run.X
    sfreq = run.fs

    if not ch_names:
        ch_names = ["EEG%d" % ch for ch in range(1, n_chan + 1)]
        montage = None  # no montage

    if not ch_types:
        ch_types = ["eeg"] * n_chan

    trigger = np.zeros((len(eeg_data), 1))
    # some runs does not contains trials i.e baseline runs
    if len(run.trial) > 0:
        trigger[run.trial - 1, 0] = run.y
    else:
        return None, None

    eeg_data = np.c_[eeg_data, trigger]
    ch_names = ch_names + ["stim"]
    ch_types = ch_types + ["stim"]
    event_id = {ev: (ii + 1) for ii, ev in enumerate(run.classes)}
    info = create_info(ch_names=ch_names, ch_types=ch_types, sfreq=sfreq)
    raw = RawArray(data=eeg_data.T, info=info, verbose=verbose)
    raw.set_montage(montage)
    return raw, event_id


@verbose
def _convert_run_p300_sl(run, verbose=None):
    """Convert one p300 run from santa lucia file format."""
    montage = make_standard_montage("standard_1005")
    eeg_data = 1e-6 * run.X
    sfreq = 256
    ch_names = list(run.channels) + ["Target stim", "Flash stim"]
    ch_types = ["eeg"] * len(run.channels) + ["stim"] * 2

    flash_stim = run.y_stim
    flash_stim[flash_stim > 0] += 2
    eeg_data = np.c_[eeg_data, run.y, flash_stim]
    event_id = {ev: (ii + 1) for ii, ev in enumerate(run.classes)}
    event_id.update({ev: (ii + 3) for ii, ev in enumerate(run.classes_stim)})
    info = create_info(ch_names=ch_names, ch_types=ch_types, sfreq=sfreq)
    raw = RawArray(data=eeg_data.T, info=info, verbose=verbose)
    raw.set_montage(montage)
    return raw, event_id


@verbose
def _convert_bbci(filename, ch_types, verbose=None):
    """Convert one file in bbci format."""
    raws = []
    event_id = {}

    data = loadmat(filename, struct_as_record=False, squeeze_me=True)
    for run in data["data"]:
        raw, evd = _convert_run_bbci(run, ch_types, verbose)
        raws.append(raw)
        event_id.update(evd)

    return raws, event_id


@verbose
def _convert_run_bbci(run, ch_types, verbose=None):
    """Convert one run to raw."""
    # parse eeg data
    montage = make_standard_montage("standard_1005")
    eeg_data = 1e-6 * run.X
    sfreq = run.fs

    ch_names = list(run.channels)

    trigger = np.zeros((len(eeg_data), 1))
    trigger[run.trial - 1, 0] = run.y
    event_id = {ev: (ii + 1) for ii, ev in enumerate(run.classes)}

    flash = np.zeros((len(eeg_data), 1))
    flash[run.trial - 1, 0] = run.y_stim + 2
    ev_fl = {"Stim%d" % (stim): (stim + 2) for stim in np.unique(run.y_stim)}
    event_id.update(ev_fl)

    eeg_data = np.c_[eeg_data, trigger, flash]
    ch_names = ch_names + ["Target", "Flash"]
    ch_types = ch_types + ["stim"] * 2

    info = create_info(ch_names=ch_names, ch_types=ch_types, sfreq=sfreq)
    raw = RawArray(data=eeg_data.T, info=info, verbose=verbose)
    raw.set_montage(montage)
    return raw, event_id


@verbose
def _convert_run_epfl(run, verbose=None):
    """Convert one run to raw."""
    # parse eeg data
    event_id = {}

    montage = make_standard_montage("standard_1005")
    eeg_data = 1e-6 * run.eeg
    sfreq = run.header.SampleRate

    ch_names = list(run.header.Label[:-1])
    ch_types = ["eeg"] * len(ch_names)

    trigger = np.zeros((len(eeg_data), 1))

    for ii, typ in enumerate(run.header.EVENT.TYP):
        if typ in [6, 9]:  # Error
            trigger[run.header.EVENT.POS[ii] - 1, 0] = 2
        elif typ in [5, 10]:  # correct
            trigger[run.header.EVENT.POS[ii] - 1, 0] = 1

    eeg_data = np.c_[eeg_data, trigger]
    ch_names = ch_names + ["stim"]
    ch_types = ch_types + ["stim"]
    event_id = {"correct": 1, "error": 2}

    info = create_info(ch_names=ch_names, ch_types=ch_types, sfreq=sfreq)
    raw = RawArray(data=eeg_data.T, info=info, verbose=verbose)
    raw.set_montage(montage)
    return raw, event_id


class MNEBNCI(BaseDataset):
    """Base BNCI dataset"""

    def _get_single_subject_data(self, subject):
        """return data for a single subject"""
        sessions = load_data(subject=subject, dataset=self.code, verbose=False)
        return sessions

    def data_path(
        self, subject, path=None, force_update=False, update_path=None, verbose=None
    ):
        return load_data(
            subject=subject,
            dataset=self.code,
            verbose=verbose,
            update_path=update_path,
            path=path,
            force_update=force_update,
        )


[docs]class BNCI2014001(MNEBNCI): """BNCI 2014-001 Motor Imagery dataset. Dataset IIa from BCI Competition 4 [1]_. **Dataset Description** This data set consists of EEG data from 9 subjects. The cue-based BCI paradigm consisted of four different motor imagery tasks, namely the imag- ination of movement of the left hand (class 1), right hand (class 2), both feet (class 3), and tongue (class 4). Two sessions on different days were recorded for each subject. Each session is comprised of 6 runs separated by short breaks. One run consists of 48 trials (12 for each of the four possible classes), yielding a total of 288 trials per session. The subjects were sitting in a comfortable armchair in front of a computer screen. At the beginning of a trial ( t = 0 s), a fixation cross appeared on the black screen. In addition, a short acoustic warning tone was presented. After two seconds ( t = 2 s), a cue in the form of an arrow pointing either to the left, right, down or up (corresponding to one of the four classes left hand, right hand, foot or tongue) appeared and stayed on the screen for 1.25 s. This prompted the subjects to perform the desired motor imagery task. No feedback was provided. The subjects were ask to carry out the motor imagery task until the fixation cross disappeared from the screen at t = 6 s. Twenty-two Ag/AgCl electrodes (with inter-electrode distances of 3.5 cm) were used to record the EEG; the montage is shown in Figure 3 left. All signals were recorded monopolarly with the left mastoid serving as reference and the right mastoid as ground. The signals were sampled with. 250 Hz and bandpass-filtered between 0.5 Hz and 100 Hz. The sensitivity of the amplifier was set to 100 μV . An additional 50 Hz notch filter was enabled to suppress line noise References ---------- .. [1] Tangermann, M., Müller, K.R., Aertsen, A., Birbaumer, N., Braun, C., Brunner, C., Leeb, R., Mehring, C., Miller, K.J., Mueller-Putz, G. and Nolte, G., 2012. Review of the BCI competition IV. Frontiers in neuroscience, 6, p.55. """ def __init__(self): super().__init__( subjects=list(range(1, 10)), sessions_per_subject=2, events={"left_hand": 1, "right_hand": 2, "feet": 3, "tongue": 4}, code="001-2014", interval=[2, 6], paradigm="imagery", doi="10.3389/fnins.2012.00055", )
[docs]class BNCI2014002(MNEBNCI): """BNCI 2014-002 Motor Imagery dataset. Motor Imagery Dataset from [1]_. **Dataset description** The session consisted of eight runs, five of them for training and three with feedback for validation. One run was composed of 20 trials. Taken together, we recorded 50 trials per class for training and 30 trials per class for validation. Participants had the task of performing sustained (5 seconds) kinaesthetic motor imagery (MI) of the right hand and of the feet each as instructed by the cue. At 0 s, a white colored cross appeared on screen, 2 s later a beep sounded to catch the participant’s attention. The cue was displayed from 3 s to 4 s. Participants were instructed to start with MI as soon as they recognized the cue and to perform the indicated MI until the cross disappeared at 8 s. A rest period with a random length between 2 s and 3 s was presented between trials. Participants did not receive feedback during training. Feedback was presented in form of a white coloured bar-graph. The length of the bar-graph reflected the amount of correct classifications over the last second. EEG was measured with a biosignal amplifier and active Ag/AgCl electrodes (g.USBamp, g.LADYbird, Guger Technologies OG, Schiedlberg, Austria) at a sampling rate of 512 Hz. The electrodes placement was designed for obtaining three Laplacian derivations. Center electrodes at positions C3, Cz, and C4 and four additional electrodes around each center electrode with a distance of 2.5 cm, 15 electrodes total. The reference electrode was mounted on the left mastoid and the ground electrode on the right mastoid. The 13 participants were aged between 20 and 30 years, 8 naive to the task, and had no known medical or neurological diseases. References ----------- .. [1] Steyrl, D., Scherer, R., Faller, J. and Müller-Putz, G.R., 2016. Random forests in non-invasive sensorimotor rhythm brain-computer interfaces: a practical and convenient non-linear classifier. Biomedical Engineering/Biomedizinische Technik, 61(1), pp.77-86. """ def __init__(self): super().__init__( subjects=list(range(1, 15)), sessions_per_subject=1, events={"right_hand": 1, "feet": 2}, code="002-2014", interval=[3, 8], paradigm="imagery", doi="10.1515/bmt-2014-0117", )
[docs]class BNCI2014004(MNEBNCI): """BNCI 2014-004 Motor Imagery dataset. Dataset B from BCI Competition 2008. **Dataset description** This data set consists of EEG data from 9 subjects of a study published in [1]_. The subjects were right-handed, had normal or corrected-to-normal vision and were paid for participating in the experiments. All volunteers were sitting in an armchair, watching a flat screen monitor placed approximately 1 m away at eye level. For each subject 5 sessions are provided, whereby the first two sessions contain training data without feedback (screening), and the last three sessions were recorded with feedback. Three bipolar recordings (C3, Cz, and C4) were recorded with a sampling frequency of 250 Hz.They were bandpass- filtered between 0.5 Hz and 100 Hz, and a notch filter at 50 Hz was enabled. The placement of the three bipolar recordings (large or small distances, more anterior or posterior) were slightly different for each subject (for more details see [1]). The electrode position Fz served as EEG ground. In addition to the EEG channels, the electrooculogram (EOG) was recorded with three monopolar electrodes. The cue-based screening paradigm consisted of two classes, namely the motor imagery (MI) of left hand (class 1) and right hand (class 2). Each subject participated in two screening sessions without feedback recorded on two different days within two weeks. Each session consisted of six runs with ten trials each and two classes of imagery. This resulted in 20 trials per run and 120 trials per session. Data of 120 repetitions of each MI class were available for each person in total. Prior to the first motor im- agery training the subject executed and imagined different movements for each body part and selected the one which they could imagine best (e. g., squeezing a ball or pulling a brake). Each trial started with a fixation cross and an additional short acoustic warning tone (1 kHz, 70 ms). Some seconds later a visual cue was presented for 1.25 seconds. Afterwards the subjects had to imagine the corresponding hand movement over a period of 4 seconds. Each trial was followed by a short break of at least 1.5 seconds. A randomized time of up to 1 second was added to the break to avoid adaptation For the three online feedback sessions four runs with smiley feedback were recorded, whereby each run consisted of twenty trials for each type of motor imagery. At the beginning of each trial (second 0) the feedback (a gray smiley) was centered on the screen. At second 2, a short warning beep (1 kHz, 70 ms) was given. The cue was presented from second 3 to 7.5. At second 7.5 the screen went blank and a random interval between 1.0 and 2.0 seconds was added to the trial. References ---------- .. [1] R. Leeb, F. Lee, C. Keinrath, R. Scherer, H. Bischof, G. Pfurtscheller. Brain-computer communication: motivation, aim, and impact of exploring a virtual apartment. IEEE Transactions on Neural Systems and Rehabilitation Engineering 15, 473–482, 2007 """ def __init__(self): super().__init__( subjects=list(range(1, 10)), sessions_per_subject=5, events={"left_hand": 1, "right_hand": 2}, code="004-2014", interval=[3, 7.5], paradigm="imagery", doi="10.1109/TNSRE.2007.906956", )
[docs]class BNCI2014008(MNEBNCI): """BNCI 2014-008 P300 dataset Dataset from [1]_. **Dataset description** This dataset represents a complete record of P300 evoked potentials using a paradigm originally described by Farwell and Donchin [2]_. In these sessions, 8 users with amyotrophic lateral sclerosis (ALS) focused on one out of 36 different characters. The objective in this contest is to predict the correct character in each of the provided character selection epochs. We included in the study a total of eight volunteers, all naïve to BCI training. Scalp EEG signals were recorded (g.MOBILAB, g.tec, Austria) from eight channels according to 10–10 standard (Fz, Cz, Pz, Oz, P3, P4, PO7 and PO8) using active electrodes (g.Ladybird, g.tec, Austria). All channels were referenced to the right earlobe and grounded to the left mastoid. The EEG signal was digitized at 256 Hz and band-pass filtered between 0.1 and 30 Hz. Participants were required to copy spell seven predefined words of five characters each (runs), by controlling a P300 matrix speller. Rows and columns on the interface were randomly intensified for 125ms, with an inter stimulus interval (ISI) of 125ms, yielding a 250 ms lag between the appearance of two stimuli (stimulus onset asynchrony, SOA). In the first three runs (15 trials in total) EEG data was stored to perform a calibration of the BCI classifier. Thus no feedback was provided to the participant up to this point. A stepwise linear discriminant analysis (SWLDA) was applied to the data from the three calibration runs (i.e., runs 1–3) to determine the classifier weights (i.e., classifier coefficients). These weights were then applied during the subsequent four testing runs (i.e., runs 4–7) when participants were provided with feedback. References ---------- .. [1] A. Riccio, L. Simione, F. Schettini, A. Pizzimenti, M. Inghilleri, M. O. Belardinelli, D. Mattia, and F. Cincotti (2013). Attention and P300-based BCI performance in people with amyotrophic lateral sclerosis. Front. Hum. Neurosci., vol. 7:, pag. 732. .. [2] L. A. Farwell and E. Donchin, Talking off the top of your head: toward a mental prosthesis utilizing eventrelated brain potentials, Electroencephalogr. Clin. Neurophysiol., vol. 70, n. 6, pagg. 510–523, 1988. """ def __init__(self): super().__init__( subjects=list(range(1, 9)), sessions_per_subject=1, events={"Target": 2, "NonTarget": 1}, code="008-2014", interval=[0, 1.0], paradigm="p300", doi="10.3389/fnhum.2013.00732", )
[docs]class BNCI2014009(MNEBNCI): """BNCI 2014-009 P300 dataset. Dataset from [1]_. **Dataset description** This dataset presents a complete record of P300 evoked potentials using two different paradigms: a paradigm based on the P300 Speller in overt attention condition and a paradigm based used in covert attention condition. In these sessions, 10 healthy subjects focused on one out of 36 different characters. The objective was to predict the correct character in each of the provided character selection epochs. (Note: right now only the overt attention data is available via MOABB) In the first interface, cues are organized in a 6×6 matrix and each character is always visible on the screen and spatially separated from the others. By design, no fixation cue is provided, as the subject is expected to gaze at the target character. Stimulation consists in the intensification of whole lines (rows or columns) of six characters. Ten healthy subjects (10 female, mean age = 26.8 ± 5.6, table I) with previous experience with P300-based BCIs attended 3 recording sessions. Scalp EEG potentials were measured using 16 Ag/AgCl electrodes that covered the left, right and central scalp (Fz, FCz, Cz, CPz, Pz, Oz, F3, F4, C3, C4, CP3, CP4, P3, P4, PO7, PO8) per the 10-10 standard. Each electrode was referenced to the linked earlobes and grounded to the right mastoid. The EEG was acquired at 256 Hz, high pass- and low pass-filtered with cutoff frequencies of 0.1 Hz and 20 Hz, respectively. Each subject attended 4 recording sessions. During each session, the subject performed three runs with each of the stimulation interfaces. References ---------- .. [1] P Aricò, F Aloise, F Schettini, S Salinari, D Mattia and F Cincotti (2013). Influence of P300 latency jitter on event related potential- based brain–computer interface performance. Journal of Neural Engineering, vol. 11, number 3. """ def __init__(self): super().__init__( subjects=list(range(1, 11)), sessions_per_subject=3, events={"Target": 2, "NonTarget": 1}, code="009-2014", interval=[0, 0.8], paradigm="p300", doi="10.1088/1741-2560/11/3/035008", )
[docs]class BNCI2015001(MNEBNCI): """BNCI 2015-001 Motor Imagery dataset. Dataset from [1]_. **Dataset description** We acquired the EEG from three Laplacian derivations, 3.5 cm (center-to- center) around the electrode positions (according to International 10-20 System of Electrode Placement) C3 (FC3, C5, CP3 and C1), Cz (FCz, C1, CPz and C2) and C4 (FC4, C2, CP4 and C6). The acquisition hardware was a g.GAMMAsys active electrode system along with a g.USBamp amplifier (g.tec, Guger Tech- nologies OEG, Graz, Austria). The system sampled at 512 Hz, with a bandpass filter between 0.5 and 100 Hz and a notch filter at 50 Hz. The order of the channels in the data is FC3, FCz, FC4, C5, C3, C1, Cz, C2, C4, C6, CP3, CPz, CP4. The task for the user was to perform sustained right hand versus both feet movement imagery starting from the cue (second 3) to the end of the cross period (sec- ond 8). A trial started with 3 s of reference period, followed by a brisk audible cue and a visual cue (arrow right for right hand, arrow down for both feet) from second 3 to 4.25. The activity period, where the users received feedback, lasted from second 4 to 8. There was a random 2 to 3 s pause between the trials. References ---------- .. [1] J. Faller, C. Vidaurre, T. Solis-Escalante, C. Neuper and R. Scherer (2012). Autocalibration and recurrent adaptation: Towards a plug and play online ERD- BCI. IEEE Transactions on Neural Systems and Rehabilitation Engineering, 20(3), 313-319. """ def __init__(self): # FIXME: some participant have 3 sessions super().__init__( subjects=list(range(1, 13)), sessions_per_subject=2, events={"right_hand": 1, "feet": 2}, code="001-2015", interval=[0, 5], paradigm="imagery", doi="10.1109/tnsre.2012.2189584", )
[docs]class BNCI2015003(MNEBNCI): """BNCI 2015-003 P300 dataset. Dataset from [1]_. **Dataset description** This dataset contains recordings from 10 subjects performing a visual P300 task for spelling. Results were published in [1]_. Sampling frequency was 256 Hz and there were 8 electrodes ('Fz', 'Cz', 'P3', 'Pz', 'P4', 'PO7', 'Oz', 'PO8') which were referenced to the right earlobe. Each subject participated in only one session. For more information, see [1]_. References ---------- .. [1] C. Guger, S. Daban, E. Sellers, C. Holzner, G. Krausz, R. Carabalona, F. Gramatica, and G. Edlinger (2009). How many people are able to control a P300-based brain-computer interface (BCI)?. Neuroscience Letters, vol. 462, pp. 94–98. """ def __init__(self): super().__init__( subjects=list(range(1, 11)), sessions_per_subject=1, events={"Target": 2, "NonTarget": 1}, code="003-2015", interval=[0, 0.8], paradigm="p300", doi="10.1016/j.neulet.2009.06.045", )
[docs]class BNCI2015004(MNEBNCI): """BNCI 2015-004 Motor Imagery dataset. Dataset from [1]_. **Dataset description** We provide EEG data recorded from nine users with disability (spinal cord injury and stroke) on two different days (sessions). Users performed, follow- ing a cue-guided experimental paradigm, five distinct mental tasks (MT). MTs include mental word association (condition WORD), mental subtraction (SUB), spatial navigation (NAV), right hand motor imagery (HAND) and feet motor imagery (FEET). Details on the experimental paradigm are summarized in Figure 1. The session for a single subject consisted of 8 runs resulting in 40 trials of each class for each day. One single experimental run consisted of 25 cues, with 5 of each mental task. Cues were presented in random order. EEG was recorded from 30 electrode channels placed on the scalp according to the international 10-20 system. Electrode positions included channels AFz, F7, F3, Fz, F4, F8, FC3, FCz, FC4, T3, C3, Cz, C4, T4, CP3, CPz,CP4, P7, P5, P3, P1, Pz, P2, P4, P6, P8, PO3, PO4, O1, and O2. Reference and ground were placed at the left and right mastoid, respectively. The g.tec GAMMAsys system with g.LADYbird active electrodes and two g.USBamp biosignal amplifiers (Guger Technolgies, Graz, Austria) was used for recording. EEG was band pass filtered 0.5-100 Hz (notch filter at 50 Hz) and sampled at a rate of 256 Hz. The duration of a single imagery trials is 10 s. At t = 0 s, a cross was presented in the middle of the screen. Participants were asked to relax and fixate the cross to avoid eye movements. At t = 3 s, a beep was sounded to get the participant’s attention. The cue indicating the requested imagery task, one out of five graphical symbols, was presented from t = 3 s to t = 4.25 s. At t = 10 s, a second beep was sounded and the fixation-cross disappeared, which indicated the end of the trial. A variable break (inter-trial-interval, ITI) lasting between 2.5 s and 3.5 s occurred before the start of the next trial. Participants were asked to avoid movements during the imagery period, and to move and blink during the ITI. Experimental runs began and ended with a blank screen (duration 4 s) References ---------- .. [1] Scherer R, Faller J, Friedrich EVC, Opisso E, Costa U, Kübler A, et al. (2015) Individually Adapted Imagery Improves Brain-Computer Interface Performance in End-Users with Disability. PLoS ONE 10(5). https://doi.org/10.1371/journal.pone.0123727 """ def __init__(self): super().__init__( subjects=list(range(1, 10)), sessions_per_subject=2, events=dict(right_hand=4, feet=5, navigation=3, subtraction=2, word_ass=1), code="004-2015", interval=[3, 10], paradigm="imagery", doi="10.1371/journal.pone.0123727", )