Skip to content

Commit

Permalink
Merge pull request #1 from NeuroTechX/master
Browse files Browse the repository at this point in the history
Update from original
  • Loading branch information
AliAbdulHussain authored Jan 20, 2021
2 parents 75a73d6 + 374a01f commit cb7c316
Show file tree
Hide file tree
Showing 13 changed files with 296 additions and 53 deletions.
38 changes: 38 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
name: Test

on:
push:
branches: [ master ]
pull_request:
branches: [ master ]

jobs:
test:
name: ${{ matrix.os }}, py-${{ matrix.python_version }}
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [ubuntu-18.04, windows-latest, macOS-latest]
python_version: [3.6]
steps:
- uses: actions/checkout@v2
- name: Set up Python
uses: actions/setup-python@v1
with:
python-version: ${{ matrix.python_version }}
- name: Install dependencies
shell: bash
run: |
python -m pip install --upgrade pip wheel flake8
pip install -r requirements.txt
pip install .
- name: Run tests
shell: bash
run: |
python -m unittest moabb.tests
python -m moabb.run --pipelines=./moabb/tests/test_pipelines/ --verbose
- name: Run linting
shell: bash
run: |
flake8 moabb
43 changes: 24 additions & 19 deletions LICENSE
Original file line number Diff line number Diff line change
@@ -1,24 +1,29 @@
Copyright © 2017, authors of moabb
BSD 3-Clause License

Copyright (c) 2017, authors of moabb
All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* Neither the names of moabb authors nor the names of any
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.

2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.

3. Neither the name of the copyright holder nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@

**This is work in progress. API will change significantly (as well as the results of the benchmark).**

[![Build Status](https://travis-ci.org/NeuroTechX/moabb.svg?branch=master)](https://travis-ci.org/NeuroTechX/moabb)
[![Build Status](https://github.com/NeuroTechX/moabb/workflows/Test/badge.svg)](https://github.com/NeuroTechX/moabb/actions?query=branch%3Amaster)

## Welcome!

Expand Down
7 changes: 7 additions & 0 deletions moabb/datasets/epfl.py
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,13 @@ def _get_single_run_data(self, file_path):
'MA2']
ch_types = ['eeg'] * 32 + ['misc'] * 2

# The last X entries are 0 for all signals. This leads to
# artifacts when epoching and band-pass filtering the data.
# Correct the signals for this.
sig_i = np.where(
np.diff(np.all(signals == 0, axis=0).astype(int)) != 0)[0][0]
signals = signals[:, :sig_i]
signals *= 1e-6 # data is stored as uV, but MNE expects V
# we have to re-reference the signals
# the average signal on the mastoids electrodes is used as reference
references = [32, 33]
Expand Down
5 changes: 3 additions & 2 deletions moabb/datasets/schirrmeister2017.py
Original file line number Diff line number Diff line change
Expand Up @@ -198,8 +198,9 @@ def get_all_sensors(filename, pattern=None):
"""
with h5py.File(filename, 'r') as h5file:
clab_set = h5file['nfo']['clab'][:].squeeze()
all_sensor_names = [''.join(chr(c) for c in h5file[obj_ref]) for
obj_ref in clab_set]
all_sensor_names = [''.join(
chr(c.squeeze()) for c in h5file[obj_ref])
for obj_ref in clab_set]
if pattern is not None:
all_sensor_names = filter(
lambda sname: re.search(pattern, sname),
Expand Down
4 changes: 2 additions & 2 deletions moabb/datasets/upper_limb.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from moabb.datasets.base import BaseDataset

from mne.io import read_raw_edf
from mne.io import read_raw_gdf
from mne.channels import make_standard_montage
import numpy as np

Expand Down Expand Up @@ -94,7 +94,7 @@ def _get_single_subject_data(self, subject):
montage = make_standard_montage('standard_1005')
data = {}
for ii, path in enumerate(paths):
raw = read_raw_edf(path, eog=eog, misc=range(64, 96),
raw = read_raw_gdf(path, eog=eog, misc=range(64, 96),
preload=True, verbose='ERROR')
raw.set_montage(montage)
# there is nan in the data
Expand Down
47 changes: 32 additions & 15 deletions moabb/paradigms/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,14 +75,11 @@ def process_raw(self, raw, dataset, return_epochs=False):
Parameters
----------
raw: mne.Raw instance
the raw EEG data.
dataset : dataset instance
The dataset corresponding to the raw file. mainly use to access
dataset specific information.
return_epochs: boolean
This flag specifies whether to return only the data array or the
complete processed mne.Epochs
Expand All @@ -93,10 +90,8 @@ def process_raw(self, raw, dataset, return_epochs=False):
the data that will be used as features for the model
Note: if return_epochs=True, this is mne.Epochs
if return_epochs=False, this is np.ndarray
labels: np.ndarray
the labels for training / evaluating the model
metadata: pd.DataFrame
A dataframe containing the metadata
Expand All @@ -105,18 +100,23 @@ def process_raw(self, raw, dataset, return_epochs=False):
event_id = self.used_events(dataset)

# find the events, first check stim_channels then annotations
stim_channels = mne.utils._get_stim_channel(
None, raw.info, raise_error=False)
stim_channels = mne.utils._get_stim_channel(None, raw.info,
raise_error=False)
if len(stim_channels) > 0:
events = mne.find_events(raw, shortest_event=0, verbose=False)
else:
events, _ = mne.events_from_annotations(raw, event_id=event_id,
verbose=False)
channels = () if self.channels is None else self.channels
try:
events, _ = mne.events_from_annotations(raw,
event_id=event_id,
verbose=False)
except ValueError:
events, _ = mne.events_from_annotations(raw, verbose=False)

# picks channels
picks = mne.pick_types(raw.info, eeg=True, stim=False,
include=channels)
if self.channels is None:
picks = mne.pick_types(raw.info, eeg=True, stim=False)
else:
picks = mne.pick_types(raw.info, stim=False, include=self.channels)

# pick events, based on event_id
try:
Expand All @@ -139,11 +139,23 @@ def process_raw(self, raw, dataset, return_epochs=False):
raw_f = raw.copy().filter(fmin, fmax, method='iir',
picks=picks, verbose=False)
# epoch data
baseline = self.baseline
if baseline is not None:
baseline = (self.baseline[0] + dataset.interval[0],
self.baseline[1] + dataset.interval[0])
bmin = baseline[0] if baseline[0] < tmin else tmin
bmax = baseline[1] if baseline[1] > tmax else tmax
else:
bmin = tmin
bmax = tmax
epochs = mne.Epochs(raw_f, events, event_id=event_id,
tmin=tmin, tmax=tmax, proj=False,
baseline=None, preload=True,
tmin=bmin, tmax=bmax, proj=False,
baseline=baseline, preload=True,
verbose=False, picks=picks,
event_repeated='drop',
on_missing='ignore')
if bmin < tmin or bmax > tmax:
epochs.crop(tmin=tmin, tmax=tmax)
if self.resample is not None:
epochs = epochs.resample(self.resample)
# rescale to work with uV
Expand Down Expand Up @@ -181,11 +193,16 @@ def get_data(self, dataset, subjects=None, return_epochs=False):
A dataset instance.
subjects: List of int
List of subject number
return_epochs: boolean
This flag specifies whether to return only the data array or the
complete processed mne.Epochs
returns
-------
X : np.ndarray
X : Union[np.ndarray, mne.Epochs]
the data that will be used as features for the model
Note: if return_epochs=True, this is mne.Epochs
if return_epochs=False, this is np.ndarray
labels: np.ndarray
the labels for training / evaluating the model
metadata: pd.DataFrame
Expand Down
29 changes: 27 additions & 2 deletions moabb/paradigms/motor_imagery.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,14 @@ class BaseMotorImagery(BaseParadigm):
5 second after the begining of the task as defined in the dataset. If
None, use the dataset value.
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.
Expand All @@ -45,11 +53,12 @@ class BaseMotorImagery(BaseParadigm):
"""

def __init__(self, filters=([7, 35],), events=None, tmin=0.0, tmax=None,
channels=None, resample=None):
baseline=None, channels=None, resample=None):
super().__init__()
self.filters = filters
self.channels = channels
self.events = events
self.channels = channels
self.baseline = baseline
self.resample = resample

if (tmax is not None):
Expand Down Expand Up @@ -120,6 +129,14 @@ class SinglePass(BaseMotorImagery):
5 second after the begining of the task as defined in the dataset. If
None, use the dataset value.
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.
Expand Down Expand Up @@ -298,6 +315,14 @@ class MotorImagery(SinglePass):
5 second after the begining of the task as defined in the dataset. If
None, use the dataset value.
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.
Expand Down
42 changes: 38 additions & 4 deletions moabb/paradigms/p300.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,14 @@ class BaseP300(BaseParadigm):
5 second after the begining of the task as defined in the dataset. If
None, use the dataset value.
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.
Expand All @@ -48,11 +56,12 @@ class BaseP300(BaseParadigm):
"""

def __init__(self, filters=([1, 24],), events=None, tmin=0.0, tmax=None,
channels=None, resample=None):
baseline=None, channels=None, resample=None):
super().__init__()
self.filters = filters
self.channels = channels
self.events = events
self.channels = channels
self.baseline = baseline
self.resample = resample

if (tmax is not None):
Expand Down Expand Up @@ -99,6 +108,12 @@ def process_raw(self, raw, dataset, return_epochs=False):

# 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
events = mne.pick_events(events, include=list(event_id.values()))
except RuntimeError:
# skip raw if no event found
Expand All @@ -118,11 +133,22 @@ def process_raw(self, raw, dataset, return_epochs=False):
raw_f = raw.copy().filter(fmin, fmax, method='iir',
picks=picks, verbose=False)
# epoch data
baseline = self.baseline
if baseline is not None:
baseline = (self.baseline[0] + dataset.interval[0],
self.baseline[1] + dataset.interval[0])
bmin = baseline[0] if baseline[0] < tmin else tmin
bmax = baseline[1] if baseline[1] > tmax else tmax
else:
bmin = tmin
bmax = tmax
epochs = mne.Epochs(raw_f, events, event_id=event_id,
tmin=tmin, tmax=tmax, proj=False,
baseline=None, preload=True,
tmin=bmin, tmax=bmax, proj=False,
baseline=baseline, preload=True,
verbose=False, picks=picks,
on_missing='ignore')
if bmin < tmin or bmax > tmax:
epochs.crop(tmin=tmin, tmax=tmax)
if self.resample is not None:
epochs = epochs.resample(self.resample)
# rescale to work with uV
Expand Down Expand Up @@ -187,6 +213,14 @@ class SinglePass(BaseP300):
5 second after the begining of the task as defined in the dataset. If
None, use the dataset value.
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.
Expand Down
Loading

0 comments on commit cb7c316

Please sign in to comment.