Cross-Subject SSVEP#

This example shows how to perform a cross-subject analysis on an SSVEP dataset. We will compare four pipelines :

  • Riemannian Geometry

  • CCA

  • TRCA

  • MsetCCA

We will use the SSVEP paradigm, which uses the AUC as metric.

# Authors: Sylvain Chevallier <sylvain.chevallier@uvsq.fr>
#
# License: BSD (3-clause)

import warnings

import matplotlib.pyplot as plt
import pandas as pd
import seaborn as sns
from pyriemann.estimation import Covariances
from pyriemann.tangentspace import TangentSpace
from sklearn.linear_model import LogisticRegression
from sklearn.pipeline import make_pipeline

import moabb
from moabb.datasets import Kalunga2016
from moabb.evaluations import CrossSubjectEvaluation
from moabb.paradigms import SSVEP, FilterBankSSVEP
from moabb.pipelines import SSVEP_CCA, SSVEP_TRCA, ExtendedSSVEPSignal, SSVEP_MsetCCA


warnings.simplefilter(action="ignore", category=FutureWarning)
warnings.simplefilter(action="ignore", category=RuntimeWarning)
moabb.set_log_level("info")

Loading Dataset#

We will load the data from the first 2 subjects of the SSVEP_Exo dataset and compare two algorithms on this set. One of the algorithms could only process class associated with a stimulation frequency, we will thus drop the resting class. As the resting class is the last defined class, picking the first three classes (out of four) allows to focus only on the stimulation frequency.

Choose Paradigm#

We define the paradigms (SSVEP, SSVEP TRCA, SSVEP MsetCCA, and FilterBankSSVEP) and use the dataset Kalunga2016. All 3 SSVEP paradigms applied a bandpass filter (10-42 Hz) on the data, which include all stimuli frequencies and their first harmonics, while the FilterBankSSVEP paradigm uses as many bandpass filters as there are stimulation frequencies (here 3). For each stimulation frequency the EEG is filtered with a 1 Hz-wide bandpass filter centered on the frequency. This results in n_classes copies of the signal, filtered for each class, as used in the filterbank motor imagery paradigms.

paradigm = SSVEP(fmin=10, fmax=42, n_classes=3)
paradigm_TRCA = SSVEP(fmin=10, fmax=42, n_classes=3)
paradigm_MSET_CCA = SSVEP(fmin=10, fmax=42, n_classes=3)
paradigm_fb = FilterBankSSVEP(filters=None, n_classes=3)

Classes are defined by the frequency of the stimulation, here we use the first two frequencies of the dataset, 13 and 17 Hz. The evaluation function uses a LabelEncoder, transforming them to 0 and 1

Create Pipelines#

Pipelines must be a dict of sklearn pipeline transformer. The first pipeline uses Riemannian geometry, by building an extended covariance matrices from the signal filtered around the considered frequency and applying a logistic regression in the tangent plane. The second pipeline relies on the above defined CCA classifier. The third pipeline relies on the TRCA algorithm, and the fourth uses the MsetCCA algorithm. Both CCA based methods (i.e. CCA and MsetCCA) used 3 CCA components.

Evaluation#

The evaluation will return a DataFrame containing an accuracy score for each subject / session of the dataset, and for each pipeline.

Results are saved into the database, so that if you add a new pipeline, it will not run again the evaluation unless a parameter has changed. Results can be overwritten if necessary.

overwrite = False  # set to True if we want to overwrite cached results

evaluation = CrossSubjectEvaluation(
    paradigm=paradigm, datasets=dataset, overwrite=overwrite
)
results = evaluation.process(pipelines)
/home/runner/work/moabb/moabb/.venv/lib/python3.9/site-packages/urllib3/connectionpool.py:1064: InsecureRequestWarning: Unverified HTTPS request is being made to host 'zenodo.org'. Adding certificate verification is strongly advised. See: https://urllib3.readthedocs.io/en/1.26.x/advanced-usage.html#ssl-warnings
  warnings.warn(
/home/runner/work/moabb/moabb/.venv/lib/python3.9/site-packages/urllib3/connectionpool.py:1064: InsecureRequestWarning: Unverified HTTPS request is being made to host 'zenodo.org'. Adding certificate verification is strongly advised. See: https://urllib3.readthedocs.io/en/1.26.x/advanced-usage.html#ssl-warnings
  warnings.warn(

  0%|                                              | 0.00/2.06M [00:00<?, ?B/s]
  1%|▏                                     | 13.3k/2.06M [00:00<00:20, 100kB/s]
  2%|▋                                     | 35.8k/2.06M [00:00<00:13, 153kB/s]
  5%|██                                     | 105k/2.06M [00:00<00:05, 353kB/s]
 12%|████▊                                  | 252k/2.06M [00:00<00:02, 709kB/s]
 26%|█████████▋                            | 524k/2.06M [00:00<00:01, 1.30MB/s]
 52%|███████████████████▏                 | 1.06M/2.06M [00:00<00:00, 2.45MB/s]
  0%|                                              | 0.00/2.06M [00:00<?, ?B/s]
100%|█████████████████████████████████████| 2.06M/2.06M [00:00<00:00, 8.02GB/s]
/home/runner/work/moabb/moabb/.venv/lib/python3.9/site-packages/urllib3/connectionpool.py:1064: InsecureRequestWarning: Unverified HTTPS request is being made to host 'zenodo.org'. Adding certificate verification is strongly advised. See: https://urllib3.readthedocs.io/en/1.26.x/advanced-usage.html#ssl-warnings
  warnings.warn(
/home/runner/work/moabb/moabb/.venv/lib/python3.9/site-packages/urllib3/connectionpool.py:1064: InsecureRequestWarning: Unverified HTTPS request is being made to host 'zenodo.org'. Adding certificate verification is strongly advised. See: https://urllib3.readthedocs.io/en/1.26.x/advanced-usage.html#ssl-warnings
  warnings.warn(

  0%|                                              | 0.00/2.82M [00:00<?, ?B/s]
  1%|▏                                     | 14.3k/2.82M [00:00<00:26, 106kB/s]
  1%|▌                                     | 39.9k/2.82M [00:00<00:16, 169kB/s]
  3%|█▎                                    | 96.3k/2.82M [00:00<00:08, 309kB/s]
  7%|██▋                                    | 193k/2.82M [00:00<00:05, 510kB/s]
 12%|████▌                                  | 326k/2.82M [00:00<00:03, 738kB/s]
 16%|██████▎                                | 454k/2.82M [00:00<00:02, 862kB/s]
 19%|███████▍                               | 541k/2.82M [00:00<00:02, 826kB/s]
 23%|█████████                              | 650k/2.82M [00:00<00:02, 863kB/s]
 28%|███████████                            | 797k/2.82M [00:01<00:02, 988kB/s]
 32%|████████████▍                          | 896k/2.82M [00:01<00:02, 945kB/s]
 35%|█████████████▋                         | 991k/2.82M [00:01<00:02, 904kB/s]
 38%|██████████████▌                       | 1.08M/2.82M [00:01<00:01, 869kB/s]
 43%|████████████████▎                     | 1.21M/2.82M [00:01<00:01, 928kB/s]
 48%|█████████████████▊                   | 1.35M/2.82M [00:01<00:01, 1.03MB/s]
 52%|███████████████████▋                  | 1.46M/2.82M [00:01<00:01, 984kB/s]
 55%|████████████████████▉                 | 1.56M/2.82M [00:01<00:01, 946kB/s]
 59%|██████████████████████▎               | 1.65M/2.82M [00:01<00:01, 908kB/s]
 62%|███████████████████████▌              | 1.74M/2.82M [00:02<00:01, 871kB/s]
 66%|█████████████████████████             | 1.86M/2.82M [00:02<00:01, 916kB/s]
 70%|██████████████████████████▋           | 1.97M/2.82M [00:02<00:00, 937kB/s]
 74%|███████████████████████████▉          | 2.07M/2.82M [00:02<00:00, 912kB/s]
 77%|█████████████████████████████▏        | 2.16M/2.82M [00:02<00:00, 875kB/s]
 80%|██████████████████████████████▍       | 2.25M/2.82M [00:02<00:00, 839kB/s]
 84%|███████████████████████████████▉      | 2.37M/2.82M [00:02<00:00, 883kB/s]
 88%|█████████████████████████████████▍    | 2.48M/2.82M [00:02<00:00, 914kB/s]
 91%|██████████████████████████████████▋   | 2.57M/2.82M [00:03<00:00, 876kB/s]
 95%|████████████████████████████████████▏ | 2.68M/2.82M [00:03<00:00, 883kB/s]
100%|█████████████████████████████████████▉| 2.81M/2.82M [00:03<00:00, 956kB/s]
  0%|                                              | 0.00/2.82M [00:00<?, ?B/s]
100%|█████████████████████████████████████| 2.82M/2.82M [00:00<00:00, 16.0GB/s]
/home/runner/work/moabb/moabb/moabb/datasets/preprocessing.py:279: UserWarning: warnEpochs <Epochs | 24 events (all good), 2 – 4 s (baseline off), ~785 kB, data loaded,
 '13': 8
 '17': 8
 '21': 8>
  warn(f"warnEpochs {epochs}")
/home/runner/work/moabb/moabb/moabb/datasets/preprocessing.py:279: UserWarning: warnEpochs <Epochs | 24 events (all good), 2 – 4 s (baseline off), ~785 kB, data loaded,
 '13': 8
 '17': 8
 '21': 8>
  warn(f"warnEpochs {epochs}")
/home/runner/work/moabb/moabb/.venv/lib/python3.9/site-packages/urllib3/connectionpool.py:1064: InsecureRequestWarning: Unverified HTTPS request is being made to host 'zenodo.org'. Adding certificate verification is strongly advised. See: https://urllib3.readthedocs.io/en/1.26.x/advanced-usage.html#ssl-warnings
  warnings.warn(
/home/runner/work/moabb/moabb/.venv/lib/python3.9/site-packages/urllib3/connectionpool.py:1064: InsecureRequestWarning: Unverified HTTPS request is being made to host 'zenodo.org'. Adding certificate verification is strongly advised. See: https://urllib3.readthedocs.io/en/1.26.x/advanced-usage.html#ssl-warnings
  warnings.warn(

  0%|                                              | 0.00/2.58M [00:00<?, ?B/s]
  1%|▏                                     | 14.3k/2.58M [00:00<00:23, 110kB/s]
  2%|▌                                     | 42.0k/2.58M [00:00<00:13, 183kB/s]
  4%|█▌                                     | 101k/2.58M [00:00<00:07, 331kB/s]
  8%|███                                    | 205k/2.58M [00:00<00:04, 551kB/s]
 11%|████▎                                  | 285k/2.58M [00:00<00:03, 600kB/s]
 17%|██████▋                                | 440k/2.58M [00:00<00:02, 853kB/s]
 21%|████████▏                              | 539k/2.58M [00:00<00:02, 849kB/s]
 27%|██████████▎                            | 685k/2.58M [00:00<00:01, 981kB/s]
 32%|████████████                          | 816k/2.58M [00:01<00:01, 1.03MB/s]
 41%|███████████████▏                     | 1.06M/2.58M [00:01<00:01, 1.36MB/s]
 46%|█████████████████▏                   | 1.20M/2.58M [00:01<00:01, 1.31MB/s]
 51%|███████████████████                  | 1.33M/2.58M [00:01<00:00, 1.26MB/s]
 58%|█████████████████████▎               | 1.49M/2.58M [00:01<00:00, 1.29MB/s]
 63%|███████████████████████▏             | 1.62M/2.58M [00:01<00:00, 1.24MB/s]
 67%|████████████████████████▉            | 1.74M/2.58M [00:01<00:00, 1.19MB/s]
 72%|██████████████████████████▋          | 1.86M/2.58M [00:01<00:00, 1.14MB/s]
 77%|████████████████████████████▌        | 1.99M/2.58M [00:01<00:00, 1.14MB/s]
 82%|██████████████████████████████▍      | 2.12M/2.58M [00:02<00:00, 1.14MB/s]
 87%|████████████████████████████████     | 2.24M/2.58M [00:02<00:00, 1.09MB/s]
 91%|█████████████████████████████████▌   | 2.35M/2.58M [00:02<00:00, 1.05MB/s]
 96%|███████████████████████████████████▎ | 2.47M/2.58M [00:02<00:00, 1.05MB/s]
100%|████████████████████████████████████▊| 2.57M/2.58M [00:02<00:00, 1.01MB/s]
  0%|                                              | 0.00/2.58M [00:00<?, ?B/s]
100%|█████████████████████████████████████| 2.58M/2.58M [00:00<00:00, 11.7GB/s]
/home/runner/work/moabb/moabb/.venv/lib/python3.9/site-packages/urllib3/connectionpool.py:1064: InsecureRequestWarning: Unverified HTTPS request is being made to host 'zenodo.org'. Adding certificate verification is strongly advised. See: https://urllib3.readthedocs.io/en/1.26.x/advanced-usage.html#ssl-warnings
  warnings.warn(
/home/runner/work/moabb/moabb/.venv/lib/python3.9/site-packages/urllib3/connectionpool.py:1064: InsecureRequestWarning: Unverified HTTPS request is being made to host 'zenodo.org'. Adding certificate verification is strongly advised. See: https://urllib3.readthedocs.io/en/1.26.x/advanced-usage.html#ssl-warnings
  warnings.warn(

  0%|                                              | 0.00/2.14M [00:00<?, ?B/s]
  1%|▎                                     | 14.3k/2.14M [00:00<00:20, 103kB/s]
  2%|▊                                     | 48.1k/2.14M [00:00<00:10, 207kB/s]
  5%|██                                     | 113k/2.14M [00:00<00:05, 362kB/s]
 11%|████▍                                  | 245k/2.14M [00:00<00:02, 666kB/s]
 24%|█████████▎                            | 524k/2.14M [00:00<00:01, 1.29MB/s]
 50%|██████████████████▎                  | 1.06M/2.14M [00:00<00:00, 2.44MB/s]
  0%|                                              | 0.00/2.14M [00:00<?, ?B/s]
100%|█████████████████████████████████████| 2.14M/2.14M [00:00<00:00, 7.32GB/s]
/home/runner/work/moabb/moabb/moabb/datasets/preprocessing.py:279: UserWarning: warnEpochs <Epochs | 24 events (all good), 2 – 4 s (baseline off), ~785 kB, data loaded,
 '13': 8
 '17': 8
 '21': 8>
  warn(f"warnEpochs {epochs}")
/home/runner/work/moabb/moabb/moabb/datasets/preprocessing.py:279: UserWarning: warnEpochs <Epochs | 24 events (all good), 2 – 4 s (baseline off), ~785 kB, data loaded,
 '13': 8
 '17': 8
 '21': 8>
  warn(f"warnEpochs {epochs}")

Kalunga2016-CrossSubject:   0%|          | 0/2 [00:00<?, ?it/s]
Kalunga2016-CrossSubject:  50%|#####     | 1/2 [00:00<00:00,  2.03it/s]
Kalunga2016-CrossSubject: 100%|##########| 2/2 [00:00<00:00,  2.04it/s]
Kalunga2016-CrossSubject: 100%|##########| 2/2 [00:00<00:00,  2.04it/s]

Filter bank processing, determine the filter automatically from the stimulation frequency values of events.

/home/runner/work/moabb/moabb/moabb/datasets/preprocessing.py:279: UserWarning: warnEpochs <Epochs | 24 events (all good), 2 – 4 s (baseline off), ~785 kB, data loaded,
 '13': 8
 '17': 8
 '21': 8>
  warn(f"warnEpochs {epochs}")
/home/runner/work/moabb/moabb/moabb/datasets/preprocessing.py:279: UserWarning: warnEpochs <Epochs | 24 events (all good), 2 – 4 s (baseline off), ~785 kB, data loaded,
 '13': 8
 '17': 8
 '21': 8>
  warn(f"warnEpochs {epochs}")
/home/runner/work/moabb/moabb/moabb/datasets/preprocessing.py:279: UserWarning: warnEpochs <Epochs | 24 events (all good), 2 – 4 s (baseline off), ~785 kB, data loaded,
 '13': 8
 '17': 8
 '21': 8>
  warn(f"warnEpochs {epochs}")
/home/runner/work/moabb/moabb/moabb/datasets/preprocessing.py:279: UserWarning: warnEpochs <Epochs | 24 events (all good), 2 – 4 s (baseline off), ~785 kB, data loaded,
 '13': 8
 '17': 8
 '21': 8>
  warn(f"warnEpochs {epochs}")
/home/runner/work/moabb/moabb/moabb/datasets/preprocessing.py:279: UserWarning: warnEpochs <Epochs | 24 events (all good), 2 – 4 s (baseline off), ~785 kB, data loaded,
 '13': 8
 '17': 8
 '21': 8>
  warn(f"warnEpochs {epochs}")
/home/runner/work/moabb/moabb/moabb/datasets/preprocessing.py:279: UserWarning: warnEpochs <Epochs | 24 events (all good), 2 – 4 s (baseline off), ~785 kB, data loaded,
 '13': 8
 '17': 8
 '21': 8>
  warn(f"warnEpochs {epochs}")
/home/runner/work/moabb/moabb/moabb/datasets/preprocessing.py:279: UserWarning: warnEpochs <Epochs | 24 events (all good), 2 – 4 s (baseline off), ~785 kB, data loaded,
 '13': 8
 '17': 8
 '21': 8>
  warn(f"warnEpochs {epochs}")
/home/runner/work/moabb/moabb/moabb/datasets/preprocessing.py:279: UserWarning: warnEpochs <Epochs | 24 events (all good), 2 – 4 s (baseline off), ~785 kB, data loaded,
 '13': 8
 '17': 8
 '21': 8>
  warn(f"warnEpochs {epochs}")
/home/runner/work/moabb/moabb/moabb/datasets/preprocessing.py:279: UserWarning: warnEpochs <Epochs | 24 events (all good), 2 – 4 s (baseline off), ~785 kB, data loaded,
 '13': 8
 '17': 8
 '21': 8>
  warn(f"warnEpochs {epochs}")
/home/runner/work/moabb/moabb/moabb/datasets/preprocessing.py:279: UserWarning: warnEpochs <Epochs | 24 events (all good), 2 – 4 s (baseline off), ~785 kB, data loaded,
 '13': 8
 '17': 8
 '21': 8>
  warn(f"warnEpochs {epochs}")
/home/runner/work/moabb/moabb/moabb/datasets/preprocessing.py:279: UserWarning: warnEpochs <Epochs | 24 events (all good), 2 – 4 s (baseline off), ~785 kB, data loaded,
 '13': 8
 '17': 8
 '21': 8>
  warn(f"warnEpochs {epochs}")
/home/runner/work/moabb/moabb/moabb/datasets/preprocessing.py:279: UserWarning: warnEpochs <Epochs | 24 events (all good), 2 – 4 s (baseline off), ~785 kB, data loaded,
 '13': 8
 '17': 8
 '21': 8>
  warn(f"warnEpochs {epochs}")

Kalunga2016-CrossSubject:   0%|          | 0/2 [00:00<?, ?it/s]
Kalunga2016-CrossSubject:  50%|#####     | 1/2 [00:00<00:00,  2.63it/s]
Kalunga2016-CrossSubject: 100%|##########| 2/2 [00:00<00:00,  2.65it/s]
Kalunga2016-CrossSubject: 100%|##########| 2/2 [00:00<00:00,  2.65it/s]

TRCA processing also relies on filter bank that is automatically designed.

/home/runner/work/moabb/moabb/moabb/datasets/preprocessing.py:279: UserWarning: warnEpochs <Epochs | 24 events (all good), 2 – 4 s (baseline off), ~785 kB, data loaded,
 '13': 8
 '17': 8
 '21': 8>
  warn(f"warnEpochs {epochs}")
/home/runner/work/moabb/moabb/moabb/datasets/preprocessing.py:279: UserWarning: warnEpochs <Epochs | 24 events (all good), 2 – 4 s (baseline off), ~785 kB, data loaded,
 '13': 8
 '17': 8
 '21': 8>
  warn(f"warnEpochs {epochs}")
/home/runner/work/moabb/moabb/moabb/datasets/preprocessing.py:279: UserWarning: warnEpochs <Epochs | 24 events (all good), 2 – 4 s (baseline off), ~785 kB, data loaded,
 '13': 8
 '17': 8
 '21': 8>
  warn(f"warnEpochs {epochs}")
/home/runner/work/moabb/moabb/moabb/datasets/preprocessing.py:279: UserWarning: warnEpochs <Epochs | 24 events (all good), 2 – 4 s (baseline off), ~785 kB, data loaded,
 '13': 8
 '17': 8
 '21': 8>
  warn(f"warnEpochs {epochs}")

Kalunga2016-CrossSubject:   0%|          | 0/2 [00:00<?, ?it/s]
Kalunga2016-CrossSubject:  50%|#####     | 1/2 [00:00<00:00,  1.29it/s]
Kalunga2016-CrossSubject: 100%|##########| 2/2 [00:01<00:00,  1.31it/s]
Kalunga2016-CrossSubject: 100%|##########| 2/2 [00:01<00:00,  1.31it/s]

MsetCCA processing

/home/runner/work/moabb/moabb/moabb/datasets/preprocessing.py:279: UserWarning: warnEpochs <Epochs | 24 events (all good), 2 – 4 s (baseline off), ~785 kB, data loaded,
 '13': 8
 '17': 8
 '21': 8>
  warn(f"warnEpochs {epochs}")
/home/runner/work/moabb/moabb/moabb/datasets/preprocessing.py:279: UserWarning: warnEpochs <Epochs | 24 events (all good), 2 – 4 s (baseline off), ~785 kB, data loaded,
 '13': 8
 '17': 8
 '21': 8>
  warn(f"warnEpochs {epochs}")
/home/runner/work/moabb/moabb/moabb/datasets/preprocessing.py:279: UserWarning: warnEpochs <Epochs | 24 events (all good), 2 – 4 s (baseline off), ~785 kB, data loaded,
 '13': 8
 '17': 8
 '21': 8>
  warn(f"warnEpochs {epochs}")
/home/runner/work/moabb/moabb/moabb/datasets/preprocessing.py:279: UserWarning: warnEpochs <Epochs | 24 events (all good), 2 – 4 s (baseline off), ~785 kB, data loaded,
 '13': 8
 '17': 8
 '21': 8>
  warn(f"warnEpochs {epochs}")

Kalunga2016-CrossSubject:   0%|          | 0/2 [00:00<?, ?it/s]
Kalunga2016-CrossSubject:  50%|#####     | 1/2 [00:00<00:00,  1.52it/s]
Kalunga2016-CrossSubject: 100%|##########| 2/2 [00:01<00:00,  1.59it/s]
Kalunga2016-CrossSubject: 100%|##########| 2/2 [00:01<00:00,  1.58it/s]

After processing the four, we simply concatenate the results.

results = pd.concat([results, results_fb, results_TRCA, results_MSET_CCA])

Plot Results#

Here we display the results as stripplot, with a pointplot for error bar.

fig, ax = plt.subplots(facecolor="white", figsize=[8, 4])
sns.stripplot(
    data=results,
    y="score",
    x="pipeline",
    ax=ax,
    jitter=True,
    alpha=0.5,
    zorder=1,
    palette="Set1",
)
sns.pointplot(data=results, y="score", x="pipeline", ax=ax, palette="Set1")
ax.set_ylabel("Accuracy")
ax.set_ylim(0.1, 0.6)
plt.show()
plot cross subject ssvep

Total running time of the script: ( 0 minutes 24.300 seconds)

Estimated memory usage: 12 MB

Gallery generated by Sphinx-Gallery