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

dataset: avima #15

Open
wants to merge 34 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
25854fe
removed version spec for mne in requirements
JasperVanDenBosch Aug 1, 2019
b97e92e
Merge branch 'master' into dataset-avima
JasperVanDenBosch Aug 1, 2019
e4a1923
in the process of splitting off preproc of one file
JasperVanDenBosch Aug 5, 2019
459ea14
working on basics
JasperVanDenBosch Aug 5, 2019
9c4e329
some reorganizing
JasperVanDenBosch Aug 6, 2019
495f060
simplified eegprep
JasperVanDenBosch Aug 6, 2019
774ba5c
fixed output filename
JasperVanDenBosch Aug 6, 2019
61b5ab9
changed output file naming
JasperVanDenBosch Aug 6, 2019
da492db
introduced arg parser
JasperVanDenBosch Nov 22, 2019
9f99c1a
updated readme
JasperVanDenBosch Nov 22, 2019
0a48610
fixed versions of mne and autoreject
JasperVanDenBosch Nov 22, 2019
07b851f
main.run() collects args
JasperVanDenBosch Nov 22, 2019
0f42b5a
Merge branch 'master' into dataset-avima
JasperVanDenBosch Nov 22, 2019
58400a5
simplified Singularity recipe
JasperVanDenBosch Nov 22, 2019
03e3746
notes on i/o approach
JasperVanDenBosch Nov 22, 2019
b171235
building new pipeline-based approach
JasperVanDenBosch Nov 23, 2019
490b5a8
can run whole dataset
JasperVanDenBosch Nov 23, 2019
8513c5c
worked out SubjectJob
JasperVanDenBosch Nov 23, 2019
eeb2a87
introduced Log
JasperVanDenBosch Nov 23, 2019
7218817
implemented log.received_arguments()
JasperVanDenBosch Nov 23, 2019
719c1ba
created new jobs for one run
JasperVanDenBosch Nov 23, 2019
c6ce51c
InputOutput can provide a sub-scope
JasperVanDenBosch Nov 23, 2019
6f62994
cleaned up basic jobs
JasperVanDenBosch Nov 24, 2019
40edeae
implemented io.get_filepath()
JasperVanDenBosch Nov 24, 2019
3603d80
implemented a key/value memory storage
JasperVanDenBosch Nov 24, 2019
1aa73fc
fixed input/output issues
JasperVanDenBosch Nov 24, 2019
0c9ae8d
io will overwrite existing object
JasperVanDenBosch Nov 24, 2019
5295ef9
parent jobs can cleanup children in cleanup phase
JasperVanDenBosch Nov 24, 2019
9f0eff1
writing epochs to file
JasperVanDenBosch Nov 24, 2019
244c4fb
fixed frozenset.name bug
JasperVanDenBosch Nov 24, 2019
403dc50
changed name of epochs object
JasperVanDenBosch Nov 24, 2019
4e82394
can read edf files
JasperVanDenBosch Jan 8, 2020
ebb3414
fixed deprecated way to read montage
JasperVanDenBosch Jan 8, 2020
710f1aa
improved log messages of memory store
JasperVanDenBosch Jan 8, 2020
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 22 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,19 +1,38 @@
# EEGprep
Standardized EEG preprocessing

[![https://www.singularity-hub.org/static/img/hosted-singularity--hub-%23e32929.svg](https://www.singularity-hub.org/static/img/hosted-singularity--hub-%23e32929.svg)](https://singularity-hub.org/collections/3833)


## Singularity

Build the EEGprep singularity image:
Download the EEGprep singularity image:
```
sudo singularity build eegprep.simg Singularity
singularity pull --name eegprep.simg shub://Charestlab/eegprep
```

Run EEGprep on your data:
```
singularity run -c -e --bind /your/data/dir/:/data eegprep.simg
```
where /your/data/dir/ contains a *BIDS* folder.

## Commandline

You can run eegprep on the commandline. Start by running `eegprep -h` and you'll see:
```
usage: eegprep [-h] [-s SUBJECT_INDEX] [-l SUBJECT_LABEL] [data_directory]

positional arguments:
data_directory root data directory

optional arguments:
-h, --help show this help message and exit
-s SUBJECT_INDEX, --subject-index SUBJECT_INDEX
index of subject to work on when sorted alphabetically
-l SUBJECT_LABEL, --subject-label SUBJECT_LABEL
label of subject to work on

```

## Configuration

Expand Down
6 changes: 3 additions & 3 deletions Singularity
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,10 @@ From: python:3.7
dist/eegprep-0.1.tar.gz .

%post
pip install numpy ipython
pip install --no-cache-dir -U https://api.github.com/repos/mne-tools/mne-python/zipball/master#egg=mne
pip install --no-cache-dir -U https://api.github.com/repos/autoreject/autoreject/zipball/master#egg=autoreject
pip install eegprep-0.1.tar.gz

%runscript
exec eegprep

%labels
Version 0.1
24 changes: 24 additions & 0 deletions eegprep/args.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
from argparse import ArgumentParser


def parse_arguments(args=None):
"""Parse commandline parameters

Args:
args (str, optional): String of arguments, for testing purposes only.
Defaults to None.

Returns:
Namespace: Object with parsed arguments as properties
"""
parser = ArgumentParser()
parser.add_argument('data_directory', type=str, nargs='?', default='/data',
help='root data directory')
parser.add_argument('--dry-run', action='store_true',
help='rshow assembled pipeline but do not run analyses or store files')
subject = parser.add_mutually_exclusive_group()
subject.add_argument('-s', '--subject-index', type=int,
help='index of subject to work on, when sorted alphabetically')
subject.add_argument('-l', '--subject-label', type=str,
help='label of subject to work on')
return parser.parse_args(args)
19 changes: 0 additions & 19 deletions eegprep/bids/naming.py

This file was deleted.

14 changes: 14 additions & 0 deletions eegprep/configuration.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,18 @@
"""[summary]

Previously used like:

# print('data directory: {}'.format(datadir))
# conf_file_path = join(datadir, 'eegprep.conf')
# config = Configuration()
# config.setDefaults(defaults)
# if os.path.isfile(conf_file_path):
# with open(conf_file_path) as fh:
# conf_string = fh.read()
# config.updateFromString(conf_string)
# print('configuration:')
# print(config)
"""

class Configuration(object):

Expand Down
130 changes: 130 additions & 0 deletions eegprep/input_output.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
from bids import BIDSLayout
import copy
from os.path import join, dirname, isdir
from os import makedirs


class InputOutput(object):

def __init__(self, log, memory, root_dir, scope=None, layout=None):
self.log = log
self.memory = memory
self.root_dir = root_dir
self._layout = layout or None
self.scope = scope or dict()

@property
def layout(self):
"""pyBIDS layout object, lazily loaded.

If the layout has not been created yet, it will
be done here, which takes time (<1min) for larger datasets.

Returns:
bids.BIDSLayout: The BIDS layout object
"""
if self._layout is None:
self.log.discovering_data()
self._layout = BIDSLayout(self.root_dir)
return self._layout

def describe_scope(self):
return ' '.join([f'{k[:3]}={v}' for k, v in self.scope.items()])

def for_(self, subject=None, session=None, run=None):
new_scope = copy.copy(self.scope)
filters = dict(subject=subject, session=session, run=run)
for spec, val in filters.items():
if val is not None:
new_scope[spec] = val
return InputOutput(
self.log,
self.memory,
self.root_dir,
new_scope,
self.layout
)

def get_subject_labels(self):
subjects = self.layout.get(
return_type='id',
target='subject',
datatype='eeg'
)
self.log.found_subjects(subjects)
return subjects

def get_session_labels(self):
return self.layout.get(
return_type='id',
target='session',
datatype='eeg',
**self.scope
)

def get_run_labels(self):
return self.layout.get(
return_type='id',
target='run',
datatype='eeg',
**self.scope
)

def get_filepath(self, suffix):
fpaths = self.layout.get(
return_type='filename',
suffix=suffix,
datatype='eeg',
**self.scope
)
fpaths = [f for f in fpaths if '.json' not in f]
assert len(fpaths) == 1
return fpaths[0]

def store_object(self, obj, name, job):
# first delete existing copies (overwriting)
self.memory.delete(name=name, **self.scope)
identifiers = dict(name=name, job=job.get_id(), **self.scope)
self.memory.store(obj, **identifiers)

def retrieve_objects(self, name):
filters = dict(name=name, **self.scope)
return self.memory.find(**filters)

def retrieve_object(self, name):
objects = self.retrieve_objects(name)
assert len(objects) == 1
return objects[0]

def expire_output_of(self, job):
self.memory.delete(job=job.get_id(), **self.scope)

def write_output_of(self, job):
keys = self.memory.find_matching_keys(job=job.get_id(), **self.scope)
for key in keys:
name = dict(key)['name']
self.write_object(name, self.memory.get(key))

def write_object(self, name, obj):
fpath = self.build_fpath(suffix=name, ext='fif')
self.ensure_dir(dirname(fpath))
self.log.writing_object(obj, fpath)
obj.save(fpath)

def ensure_dir(self, dirpath):
if not isdir(dirpath):
makedirs(dirpath)

def build_fpath(self, suffix, ext):
outdir = join(self.root_dir, 'derivatives', 'eegprep')
for entity in ('subject', 'session'):
if entity in self.scope:
label = self.scope[entity]
outdir = join(outdir, f'{entity[:3]}-{label}')
fname = ''
for entity in ('subject', 'session'):
if entity in self.scope:
label = self.scope[entity]
fname += f'{entity[:3]}-{label}_'
fname += f'{suffix}.{ext}'
return join(outdir, fname)
File renamed without changes.
43 changes: 43 additions & 0 deletions eegprep/jobs/base.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@


class BaseJob(object):

def __init__(self, io, log):
self.io = io
self.log = log
self.jobs_to_expire = []
self.jobs_to_write = []

def get_id(self):
return self.__class__.__name__.replace('Job', '')

def describe(self):
"""Return a string that describes this job

Returns:
str: one-line string describing this job and it's scope
"""
scope = self.io.describe_scope()
return scope + ' ' + self.get_id()

def add_to(self, pipeline):
self.add_children_to(pipeline)
pipeline.add(self)

def add_children_to(self, pipeline):
pass

def run(self):
pass

def cleanup(self):
for job in self.jobs_to_write:
self.io.write_output_of(job)
for job in self.jobs_to_expire:
self.io.expire_output_of(job)

def expire_output_on_cleanup(self, job):
self.jobs_to_expire.append(job)

def write_output_on_cleanup(self, job):
self.jobs_to_write.append(job)
10 changes: 10 additions & 0 deletions eegprep/jobs/concat_epochs.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
from eegprep.jobs.base import BaseJob
import mne


class ConcatEpochsJob(BaseJob):

def run(self):
epochs_per_run = self.io.retrieve_objects('epo')
epochs = mne.epochs.concatenate_epochs(epochs_per_run)
self.io.store_object(epochs, name='epo', job=self)
20 changes: 20 additions & 0 deletions eegprep/jobs/epoch.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
from eegprep.jobs.base import BaseJob
import mne


class EpochJob(BaseJob):

def run(self):
raw = self.io.retrieve_object('raw')
# additional options: consecutive=False, min_duration=0.005)
events = mne.find_events(raw, verbose=False)
picks = mne.pick_types(raw.info, eeg=True)
epochs_params = dict(
events=events,
tmin=-0.2,
tmax=3.1,
picks=picks,
verbose=False
)
epochs = mne.Epochs(raw, preload=True, **epochs_params)
self.io.store_object(epochs, name='epo', job=self)
9 changes: 9 additions & 0 deletions eegprep/jobs/filter.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
from eegprep.jobs.base import BaseJob


class FilterJob(BaseJob):

def run(self):
raw = self.io.retrieve_object('raw')
raw.filter(l_freq=0.05, h_freq=45, fir_design='firwin')
self.io.store_object(raw, name='raw', job=self)
46 changes: 46 additions & 0 deletions eegprep/jobs/read.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import mne, pandas
from eegprep.jobs.base import BaseJob
from eegprep.guess import guess_montage


class ReadJob(BaseJob):

def run(self):

fpath_raw = self.io.get_filepath(suffix='eeg')
ext = fpath_raw[-3:]
raw_funcs = {
'bdf': mne.io.read_raw_bdf,
'edf': mne.io.read_raw_edf
}
raw = raw_funcs[ext](fpath_raw, preload=True, verbose=False)

# Set channel types and select reference channels
fpath_channels = self.io.get_filepath(suffix='channels')
channels = pandas.read_csv(fpath_channels, index_col='name', sep='\t')
bids2mne = {
'MISC': 'misc',
'EEG': 'eeg',
'EOG': 'eog',
'VEOG': 'eog',
'TRIG': 'stim',
'REF': 'eeg',
}
channels['mne'] = channels.type.replace(bids2mne)
raw.set_channel_types(channels.mne.to_dict())

# set bad channels
# raw.info['bads'] = channels[channels.status=='bad'].index.tolist()

# Set reference
refChannels = channels[channels.type=='REF'].index.tolist()
raw.set_eeg_reference(ref_channels=refChannels)
# can now drop reference electrodes
raw.set_channel_types({k: 'misc' for k in refChannels})

# tell MNE about electrode locations
montageName = guess_montage(raw.ch_names)
montage = mne.channels.make_standard_montage(kind=montageName)
raw.set_montage(montage, verbose=False)

self.io.store_object(raw, name='raw', job=self)
Loading