Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Resting state with dataset and example #400

Merged
merged 59 commits into from Jun 22, 2023
Merged
Show file tree
Hide file tree
Changes from 58 commits
Commits
Show all changes
59 commits
Select commit Hold shift + click to select a range
910090d
In some places, the virtual reality dataset code was wrong.
May 31, 2023
c12940d
fix: PC data not downloading.
May 31, 2023
cd96bea
push example from Pedro
May 31, 2023
da628d5
fix error with datframe initialization
May 31, 2023
c083802
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] May 31, 2023
69d73ee
add whats new
May 31, 2023
09f6ca2
add test
May 31, 2023
e53c883
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] May 31, 2023
a3ee57c
fix pytest/unittest
May 31, 2023
a1296a2
Merge branch 'develop' of github.com:gcattan/moabb into develop
May 31, 2023
a402bd3
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] May 31, 2023
3e9405d
replace logging by warnings library
May 31, 2023
6cf2a79
Merge branch 'develop' of github.com:gcattan/moabb into develop
May 31, 2023
6695995
move docstring to the top
May 31, 2023
2713cec
Merge branch 'develop' into develop
gcattan May 31, 2023
370c05f
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] May 31, 2023
19e70f5
Merge branch 'develop' into develop
bruAristimunha May 31, 2023
7e47a46
test completed
May 31, 2023
7e91ffc
Merge branch 'develop' of github.com:gcattan/moabb into develop
May 31, 2023
183d354
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] May 31, 2023
ac1ed24
leftover
May 31, 2023
ef2a214
Merge branch 'develop' of github.com:gcattan/moabb into develop
May 31, 2023
6b5dc5f
typo ><
May 31, 2023
46be265
Merge branch 'develop' into develop
sylvchev May 31, 2023
4d21fe4
Update examples/vr_pc_p300_different_epoch_size.py
gcattan Jun 1, 2023
367a98f
rename into plot_vr_pc_p300_different_epoch_size.py
Jun 1, 2023
7d5644e
- Add figure plot
Jun 2, 2023
ea0f1fe
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Jun 2, 2023
01dea81
Merge branch 'develop' into develop
bruAristimunha Jun 2, 2023
d8bd45a
Merge branch 'develop' into develop
gcattan Jun 5, 2023
182c575
Merge branch 'develop' into develop
bruAristimunha Jun 7, 2023
75b4ac6
Merge branch 'develop' into develop
bruAristimunha Jun 7, 2023
f360bf7
Update plot_vr_pc_p300_different_epoch_size.py
gcattan Jun 7, 2023
7007b1b
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Jun 7, 2023
b6fbcfc
Merge branch 'NeuroTechX:develop' into develop
gcattan Jun 8, 2023
8020c1c
Create resting_state.py
gcattan Jun 13, 2023
52805b3
push resting state
gcattan Jun 13, 2023
22ccf8a
add dataset
gcattan Jun 13, 2023
81470db
push example
gcattan Jun 13, 2023
3512d8c
couple of bug fixes
Jun 13, 2023
9ad7c7c
add a condition to p300 to ignore Target/NonTarget check
Jun 14, 2023
d349590
working example
Jun 14, 2023
6ea9373
improve doc
Jun 14, 2023
326c25b
Merge pull request #2 from gcattan/resting-state
gcattan Jun 14, 2023
8067a70
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Jun 14, 2023
6b29039
Update whats_new.rst
gcattan Jun 14, 2023
acbb5e2
Update phmd_ml.py
gcattan Jun 14, 2023
2711bcd
Update plot_phmd_ml_spectrum.py
gcattan Jun 19, 2023
b8709a5
complete documentation
gcattan Jun 21, 2023
a5faa81
improve lisibility
gcattan Jun 21, 2023
67790a3
push test
gcattan Jun 21, 2023
8ae5a00
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Jun 21, 2023
9a9c314
fix tests
gcattan Jun 21, 2023
3db0d42
event_list missing in initialization. Correct typo.
gcattan Jun 21, 2023
e2e65ab
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Jun 21, 2023
f6307fe
fix typo
gcattan Jun 21, 2023
12434fe
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Jun 21, 2023
4334f0b
Applying and improving small details inside the tutorial
bruAristimunha Jun 21, 2023
585735e
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Jun 21, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
13 changes: 13 additions & 0 deletions docs/source/dataset_summary.rst
Expand Up @@ -78,6 +78,19 @@ SSVEP
Wang2016,34,62,40,6,5s,250Hz,1


Resting States
======================

Include neuro experiments where the participant is not actively doing something.
For example, recoding the EEG of a subject while s/he is having the eye closed or opened
is a resting state experiment.

.. csv-table::
:header: Dataset, #Subj, #Chan, #Classes, #Blocks / class, Trials length, Sampling rate, #Sessions
:class: sortable

HeadMountedDisplay,12,16,2,10,60s,512Hz,1


Submit a new dataset
~~~~~~~~~~~~~~~~~~~~
Expand Down
11 changes: 11 additions & 0 deletions docs/source/datasets.rst
Expand Up @@ -75,6 +75,17 @@ SSVEP Datasets
Lee2019_SSVEP


----------------------
Resting State Datasets
----------------------

.. autosummary::
:toctree: generated/
:template: class.rst

HeadMountedDisplay


------------
Base & Utils
------------
Expand Down
1 change: 1 addition & 0 deletions docs/source/whats_new.rst
Expand Up @@ -25,6 +25,7 @@ Enhancements
- Adding second deployment of the documentation (:gh:`374` by `Bruno Aristimunha`_)
- Adding Parallel evaluation for :func:`moabb.evaluations.WithinSessionEvaluation` , :func:`moabb.evaluations.CrossSessionEvaluation` (:gh:`364` by `Bruno Aristimunha`_)
- Add example with VirtualReality BrainInvaders dataset (:gh:`393` by `Gregoire Cattan`_ and `Pedro L. C. Rodrigues`_)
- Add resting state paradigm with dataset and example (:gh:`400` by `Gregoire Cattan`_ and `Pedro L. C. Rodrigues`_)

Bugs
~~~~
Expand Down
74 changes: 74 additions & 0 deletions examples/plot_phmd_ml_spectrum.py
@@ -0,0 +1,74 @@
"""
================================
Spectral analysis of the trials
================================

This example demonstrates how to perform spectral
analysis on epochs extracted from a specific subject
within the :class:`moabb.datasets.HeadMountedDisplay` dataset.

"""

# Authors: Pedro Rodrigues <pedro.rodrigues01@gmail.com>
# Modified by: Gregoire Cattan <gcattan@hotmail.fr>
# License: BSD (3-clause)

import warnings

import matplotlib.pyplot as plt
import numpy as np
from scipy.signal import welch

from moabb.datasets import HeadMountedDisplay
from moabb.paradigms import RestingStateToP300Adapter


warnings.filterwarnings("ignore")

###############################################################################
# Initialization
# ---------------
#
# 1) Specify the channel and subject to compute the power spectrum.
# 2) Create an instance of the :class:`moabb.datasets.HeadMountedDisplay` dataset.
# 3) Create an instance of the :class:`moabb.paradigms.RestingStateToP300Adapter` paradigm.
# By default, the data is filtered between 1-35 Hz,
# and epochs are extracted from 10 to 50 seconds after event tagging.

# Select channel and subject for the remaining of the example.
channel = "Cz"
subject = 1

dataset = HeadMountedDisplay()
events = ["on", "off"]
paradigm = RestingStateToP300Adapter(events=events, channels=[channel])


###############################################################################
# Estimate Power Spectral Density
# ---------------
# 1) Obtain the epochs for the specified subject.
# 2) Use Welch's method to estimate the power spectral density.

X, y, _ = paradigm.get_data(dataset, [subject])
f, S = welch(X, axis=-1, nperseg=1024, fs=paradigm.resample)

###############################################################################
# Display of the data
# ---------------
#
# Plot the averaged Power Spectral Density (PSD) for each label condition,
# using the selected channel specified at the beginning of the script.

fig, ax = plt.subplots(facecolor="white", figsize=(8.2, 5.1))
for condition in events:
mean_power = np.mean(S[y == condition], axis=0).flatten()
ax.plot(f, 10 * np.log10(mean_power), label=condition)

ax.set_xlim(paradigm.fmin, paradigm.fmax)
bruAristimunha marked this conversation as resolved.
Show resolved Hide resolved
ax.set_ylim(100, 135)
ax.set_ylabel("Spectrum Magnitude (dB)", fontsize=14)
ax.set_xlabel("Frequency (Hz)", fontsize=14)
ax.set_title("PSD for Channel " + channel, fontsize=16)
ax.legend()
fig.show()
1 change: 1 addition & 0 deletions moabb/datasets/__init__.py
Expand Up @@ -35,6 +35,7 @@
from .Lee2019 import Lee2019_ERP, Lee2019_MI, Lee2019_SSVEP
from .mpi_mi import MunichMI
from .neiry import DemonsP300
from .phmd_ml import HeadMountedDisplay
from .physionet_mi import PhysionetMI
from .schirrmeister2017 import Schirrmeister2017
from .sosulski2019 import Sosulski2019
Expand Down
124 changes: 124 additions & 0 deletions moabb/datasets/phmd_ml.py
@@ -0,0 +1,124 @@
import os

import mne
import numpy as np
from scipy.io import loadmat

from . import download as dl
from .base import BaseDataset


HEADMOUNTED_URL = "https://zenodo.org/record/2617085/files/"


class HeadMountedDisplay(BaseDataset):
"""
Passive Head Mounted Display with Music Listening dataset.

.. admonition:: Dataset summary


================= ======= ======= ========== ================= ============ =============== ===========
Name #Subj #Chan #Classes #Blocks/class Trials len Sampling rate #Sessions
================== ======= ======= ========== ================= ============ =============== ===========
HeadMountedDisplay 12 16 2 10 60s 512Hz 1
================== ======= ======= ========== ================= ============ =============== ===========

We describe the experimental procedures for a dataset that we have made publicly available
at https://doi.org/10.5281/zenodo.2617084 in mat (Mathworks, Natick, USA) and csv formats.
This dataset contains electroencephalographic recordings of 12 subjects listening to music
with and without a passive head-mounted display, that is, a head-mounted display which does
not include any electronics at the exception of a smartphone. The electroencephalographic
headset consisted of 16 electrodes. Data were recorded during a pilot experiment taking
place in the GIPSA-lab, Grenoble, France, in 2017 (Cattan and al, 2018).
The ID of this dataset is PHMDML.EEG.2017-GIPSA.

**full description of the experiment**
https://hal.archives-ouvertes.fr/hal-02085118

**Link to the data**
https://doi.org/10.5281/zenodo.2617084

**Authors**
Principal Investigator: Eng. Grégoire Cattan
Technical Supervisors: Eng. Pedro L. C. Rodrigues
Scientific Supervisor: Dr. Marco Congedo

**ID of the dataset**
PHMDML.EEG.2017-GIPSA

Notes
-----

.. versionadded:: 0.6.0

References
----------

.. [1] G. Cattan, P. L. Coelho Rodrigues, and M. Congedo,
‘Passive Head-Mounted Display Music-Listening EEG dataset’,
Gipsa-Lab ; IHMTEK, Research Report 2, Mar. 2019. doi: 10.5281/zenodo.2617084.


"""

def __init__(self):
super().__init__(
subjects=list(range(1, 12 + 1)),
sessions_per_subject=1,
events=dict(on=1, off=2),
code="PHMD-ML",
interval=[0, 1],
paradigm="rstate",
doi="https://doi.org/10.5281/zenodo.2617084 ",
)
self._chnames = [
"Fp1",
"Fp2",
"Fc5",
"Fz",
"Fc6",
"T7",
"Cz",
"T8",
"P7",
"P3",
"Pz",
"P4",
"P8",
"O1",
"Oz",
"O2",
"stim",
]
self._chtypes = ["eeg"] * 16 + ["stim"]

def _get_single_subject_data(self, subject):
"""return data for a single subject"""

filepath = self.data_path(subject)[0]
data = loadmat(os.path.join(filepath, os.listdir(filepath)[0]))

first_channel = 1
last_channel = 17
S = data["data"][:, first_channel:last_channel]
stim = data["data"][:, -1]

X = np.concatenate([S, stim[:, None]], axis=1).T

info = mne.create_info(
ch_names=self._chnames, sfreq=512, ch_types=self._chtypes, verbose=False
)
raw = mne.io.RawArray(data=X, info=info, verbose=False)
return {"session_0": {"run_0": raw}}

def data_path(
self, subject, path=None, force_update=False, update_path=None, verbose=None
):
if subject not in self.subject_list:
raise (ValueError("Invalid subject number"))

url = "{:s}subject_{:02d}.mat".format(HEADMOUNTED_URL, subject)
file_path = dl.data_path(url, "HEADMOUNTED")

return [file_path]
1 change: 1 addition & 0 deletions moabb/paradigms/__init__.py
Expand Up @@ -9,4 +9,5 @@

# flake8: noqa
from moabb.paradigms.p300 import *
from moabb.paradigms.resting_state import *
from moabb.paradigms.ssvep import *
22 changes: 17 additions & 5 deletions moabb/paradigms/p300.py
Expand Up @@ -168,11 +168,15 @@ def process_raw( # noqa: C901

# pick events, based on event_id
try:
if type(event_id["Target"]) is list and type(event_id["NonTarget"]) == list:
event_id_new = dict(Target=1, NonTarget=0)
events = mne.merge_events(events, event_id["Target"], 1)
events = mne.merge_events(events, event_id["NonTarget"], 0)
event_id = event_id_new
if "Target" in event_id and "NonTarget" in event_id:
if (
type(event_id["Target"]) is list
and type(event_id["NonTarget"]) == list
):
event_id_new = dict(Target=1, NonTarget=0)
events = mne.merge_events(events, event_id["Target"], 1)
events = mne.merge_events(events, event_id["NonTarget"], 0)
event_id = event_id_new
Comment on lines +171 to +179
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Sara04, is this related to your issue with the p300?

events = mne.pick_events(events, include=list(event_id.values()))
except RuntimeError:
# skip raw if no event found
Expand Down Expand Up @@ -317,6 +321,14 @@ def __init__(self, fmin=1, fmax=24, **kwargs):
raise (ValueError("P300 does not take argument filters"))
super().__init__(filters=[[fmin, fmax]], **kwargs)

@property
def fmax(self):
return self.filters[0][1]

@property
def fmin(self):
return self.filters[0][0]


class P300(SinglePass):
"""P300 for Target/NonTarget classification
Expand Down
81 changes: 81 additions & 0 deletions moabb/paradigms/resting_state.py
@@ -0,0 +1,81 @@
"""Resting state Paradigms

Regroups paradigms for experience where we record the EEG
and the participant is not doing an active task, such
as focusing, counting or speaking.

Typically, a open/close eye experiment, where we
record the EEG of a subject while he is having the eye open or close
is a resting state experiment.
"""

from moabb.paradigms.p300 import SinglePass


class RestingStateToP300Adapter(SinglePass):
"""Adapter to the P300 paradigm for resting state experiments.
It implements a SinglePass processing as for P300, except that:
- the name of the event is free (it is not enforced to Target/NonTarget as for P300)
- the default values are different. In particular, the length of the epochs is larger.

Parameters
----------
fmin: float (default 1)
cutoff frequency (Hz) for the high pass filter

fmax: float (default 35)
cutoff frequency (Hz) for the low pass filter

events: List of str | None (default None)
event to use for epoching. If None, default to all events defined in
the dataset.

tmin: float (default 10s)
Start time (in second) of the epoch, relative to the dataset specific
task interval e.g. tmin = 1 would mean the epoch will start 1 second
after the beginning of the task as defined by the dataset.

tmax: float | None, (default 50s)
End time (in second) of the epoch, relative to the beginning of the
dataset specific task interval. tmax = 5 would mean the epoch will end
5 second after the beginning of the task as defined in the dataset. If
None, use the dataset value.

resample: float | None (default 128)
If not None, resample the eeg data with the sampling rate provided.

baseline: None | tuple of length 2
The time interval to consider as “baseline” when applying baseline
correction. If None, do not apply baseline correction.
If a tuple (a, b), the interval is between a and b (in seconds),
including the endpoints.
Correction is applied by computing the mean of the baseline period
and subtracting it from the data (see mne.Epochs)

channels: list of str | None (default None)
list of channel to select. If None, use all EEG channels available in
the dataset.
"""

def __init__(self, fmin=1, fmax=35, tmin=10, tmax=50, resample=128, **kwargs):
super().__init__(
fmin=fmin, fmax=fmax, tmin=tmin, tmax=tmax, resample=resample, **kwargs
)

def used_events(self, dataset):
return {ev: dataset.event_id[ev] for ev in self.events}

def is_valid(self, dataset):
ret = True
if not (dataset.paradigm == "rstate"):
ret = False

if self.events:
if not set(self.events) <= set(dataset.event_id.keys()):
ret = False

return ret

@property
def scoring(self):
return "roc_auc"