From 4564c92a3bd62118fb214db95d162c0362246265 Mon Sep 17 00:00:00 2001 From: Mainak Jas Date: Wed, 3 Oct 2018 23:05:45 -0400 Subject: [PATCH] Simplify raw_to_bids --- doc/api.rst | 13 +++- examples/convert_ds000117.py | 17 +++-- examples/convert_mne_sample.py | 14 ++-- examples/make_eeg_bids.py | 25 ++++--- mne_bids/__init__.py | 2 +- mne_bids/io.py | 4 +- mne_bids/mne_bids.py | 116 ++++++++++----------------------- mne_bids/utils.py | 14 ++++ 8 files changed, 94 insertions(+), 111 deletions(-) diff --git a/doc/api.rst b/doc/api.rst index 7513a941f4..3b8a745105 100644 --- a/doc/api.rst +++ b/doc/api.rst @@ -13,7 +13,17 @@ MNE BIDS (:py:mod:`mne_bids.mne_bids`): .. autosummary:: :toctree: generated/ - raw_to_bids + write_raw_bids + make_bids_filename + +IO (:py:mod:`mne_bids.io`): + +.. current_module:: mne_bids.io + +.. autosummary:: + :toctree: generated/ + + read_raw Utils (:py:mod:`mne_bids.utils`): @@ -24,5 +34,4 @@ Utils (:py:mod:`mne_bids.utils`): print_dir_tree make_bids_folders - make_bids_filename make_dataset_description diff --git a/examples/convert_ds000117.py b/examples/convert_ds000117.py index e72861c44a..57684d5346 100644 --- a/examples/convert_ds000117.py +++ b/examples/convert_ds000117.py @@ -24,7 +24,8 @@ # Let us import ``mne_bids`` import os.path as op -from mne_bids import raw_to_bids +from mne_bids import raw_to_bids, make_bids_filename +from mne_bids.io import read_raw from mne_bids.datasets import fetch_faces_data from mne_bids.utils import print_dir_tree @@ -63,13 +64,17 @@ for subject_id in subject_ids: subject = 'sub%03d' % subject_id for run in runs: - raw_file = op.join(data_path, repo, subject, 'MEG', - 'run_%02d_raw.fif' % run) + fname = op.join(data_path, repo, subject, 'MEG', + 'run_%02d_raw.fif' % run) + + raw = read_raw(fname) + bids_fname = make_bids_filename( + subject_id='%02d' % subject_id, session='01', run=run, + task='VisualFaces', suffix='meg.fif') # Make it BIDS compatible - raw_to_bids(subject_id='%02d' % subject_id, session_id='01', run=run, - task='VisualFaces', raw_file=raw_file, - event_id=event_id, output_path=output_path, overwrite=True) + raw_to_bids(raw, output_path, bids_fname, event_id=event_id, + overwrite=True) ############################################################################### # Now let's see the structure of the BIDS folder we created. diff --git a/examples/convert_mne_sample.py b/examples/convert_mne_sample.py index 86ce6867bb..79fa3c0a61 100644 --- a/examples/convert_mne_sample.py +++ b/examples/convert_mne_sample.py @@ -19,7 +19,8 @@ import os.path as op from mne.datasets import sample -from mne_bids import raw_to_bids +from mne_bids import write_raw_bids, make_bids_filename +from mne_bids.io import read_raw from mne_bids.utils import print_dir_tree ############################################################################### @@ -29,17 +30,18 @@ event_id = {'Auditory/Left': 1, 'Auditory/Right': 2, 'Visual/Left': 3, 'Visual/Right': 4, 'Smiley': 5, 'Button': 32} -raw_file = op.join(data_path, 'MEG', 'sample', 'sample_audvis_raw.fif') +fname = op.join(data_path, 'MEG', 'sample', 'sample_audvis_raw.fif') events_data = op.join(data_path, 'MEG', 'sample', 'sample_audvis_raw-eve.fif') output_path = op.join(data_path, '..', 'MNE-sample-data-bids') ############################################################################### # Finally, we specify the raw_file and events_data -raw_to_bids(subject_id='01', run='01', session_id='01', task='audiovisual', - raw_file=raw_file, events_data=events_data, - output_path=output_path, event_id=event_id, - overwrite=True) +raw = read_raw(fname) +bids_fname = make_bids_filename(subject='01', run='01', session='01', + task='audiovisual', suffix='meg.fif') +write_raw_bids(raw, bids_fname, output_path, events_data=events_data, + event_id=event_id, overwrite=True) ############################################################################### # Now let's see the structure of the BIDS folder we created. diff --git a/examples/make_eeg_bids.py b/examples/make_eeg_bids.py index 3287b83f4e..f5b118ead8 100644 --- a/examples/make_eeg_bids.py +++ b/examples/make_eeg_bids.py @@ -25,7 +25,7 @@ from mne.datasets import eegbci from mne.io import read_raw_edf -from mne_bids import raw_to_bids +from mne_bids import write_raw_bids, make_bids_filename from mne_bids.utils import print_dir_tree ############################################################################### @@ -103,7 +103,7 @@ # * output_path # # ... as you can see in the docstring: -print(raw_to_bids.__doc__) +print(write_raw_bids.__doc__) ############################################################################### # We loaded 'S001R02.edf', which corresponds to subject 1 in the second task. @@ -129,8 +129,10 @@ trial_type = {'rest': 0, 'imagine left fist': 1, 'imagine right fist': 2} # Now convert our data to be in a new BIDS dataset. -raw_to_bids(subject_id=subject_id, task=task, raw_file=raw_file, - output_path=output_path, kind=kind, event_id=trial_type) +bids_fname = make_bids_filename(subject=subject_id, task=task, + suffix='eeg.edf') +write_raw_bids(raw_file, bids_fname, output_path, kind=kind, + event_id=trial_type) ############################################################################### # What does our fresh BIDS directory look like? @@ -162,16 +164,11 @@ 'S{:03}R{:02}.edf'.format(subj_idx, task_idx)) raw = read_raw_edf(edf_path, preload=True) - # `kind` and `trial_type` were already defined above - raw_to_bids(subject_id='{:03}'.format(subj_idx), - task=task_names[task_idx], - run=run_mapping[task_idx], - raw_file=raw, - output_path=output_path, - kind=kind, - event_id=trial_type, - overwrite=False - ) + bids_fname = make_bids_filename( + subject='{:03}'.format(subj_idx), task=task_names[task_idx], + run=run_mapping[task_idx], suffix='eeg.edf') + write_raw_bids(raw, bids_fname, output_path, kind=kind, + event_id=trial_type, overwrite=False) ############################################################################### # Step 3: Check and compare with standard diff --git a/mne_bids/__init__.py b/mne_bids/__init__.py index c4899432f5..8638b010fe 100644 --- a/mne_bids/__init__.py +++ b/mne_bids/__init__.py @@ -3,6 +3,6 @@ __version__ = '0.1.dev0' -from .mne_bids import raw_to_bids # noqa +from .mne_bids import write_raw_bids # noqa from .utils import make_bids_folders, make_bids_filename # noqa from .config import BIDS_VERSION # noqa diff --git a/mne_bids/io.py b/mne_bids/io.py index 6fec70df74..00449ce34d 100644 --- a/mne_bids/io.py +++ b/mne_bids/io.py @@ -32,8 +32,8 @@ def _parse_ext(raw_fname, verbose=False): return fname, ext -def _read_raw(raw_fname, electrode=None, hsp=None, hpi=None, config=None, - montage=None, verbose=None): +def read_raw(raw_fname, electrode=None, hsp=None, hpi=None, config=None, + montage=None, verbose=None): """Read a raw file into MNE, making inferences based on extension.""" fname, ext = _parse_ext(raw_fname) diff --git a/mne_bids/mne_bids.py b/mne_bids/mne_bids.py index f8367997f0..4d9b0dc389 100644 --- a/mne_bids/mne_bids.py +++ b/mne_bids/mne_bids.py @@ -19,7 +19,6 @@ from mne.io.pick import channel_type from mne.io import BaseRaw from mne.channels.channels import _unit2human -from mne.externals.six import string_types from datetime import datetime from warnings import warn @@ -29,8 +28,8 @@ make_dataset_description, _write_json, _read_events, _mkdir_p, age_on_date, copyfile_brainvision, copyfile_eeglab, - _infer_eeg_placement_scheme) -from .io import (_parse_ext, _read_raw, ALLOWED_EXTENSIONS) + _infer_eeg_placement_scheme, _parse_bids_filename) +from .io import (_parse_ext, ALLOWED_EXTENSIONS) ALLOWED_KINDS = ['meg', 'eeg', 'ieeg'] @@ -485,30 +484,20 @@ def _sidecar_json(raw, task, manufacturer, fname, kind, eeg_ref=None, return fname -def raw_to_bids(subject_id, task, raw_file, output_path, session_id=None, - acquisition=None, run=None, kind='meg', events_data=None, - event_id=None, hpi=None, electrode=None, hsp=None, - eeg_ref=None, eeg_gnd=None, config=None, overwrite=True, - verbose=True): +def write_raw_bids(raw, bids_fname, output_path, kind='meg', events_data=None, + event_id=None, eeg_ref=None, eeg_gnd=None, overwrite=True, + verbose=True): """Walk over a folder of files and create BIDS compatible folder. Parameters ---------- - subject_id : str - The subject name in BIDS compatible format ('01', '02', etc.) - task : str - Name of the task the data is based on. - raw_file : str | instance of mne.Raw + raw : instance of mne.Raw The raw data. If a string, it is assumed to be the path to the raw data file. Otherwise it must be an instance of mne.Raw - output_path : str - The path of the BIDS compatible folder - session_id : str | None - The session name in BIDS compatible format. - acquisition : str | None - Acquisition parameter for the dataset. - run : int | None - The run number for this dataset. + bids_fname : str + The file path of the BIDS compatible raw file. In the case of multifile + systems (e.g., vhdr, .ds etc.), this is the path to a folder containing + all the files. kind : str, one of ('meg', 'eeg', 'ieeg') The kind of data being converted. Defaults to "meg". events_data : str | array | None @@ -517,25 +506,11 @@ def raw_to_bids(subject_id, task, raw_file, output_path, session_id=None, inferred from the stim channel using `mne.find_events`. event_id : dict | None The event id dict used to create a 'trial_type' column in events.tsv - hpi : None | str | list of str - Marker points representing the location of the marker coils with - respect to the MEG Sensors, or path to a marker file. - If list, all of the markers will be averaged together. - electrode : None | str - Digitizer points representing the location of the fiducials and the - marker coils with respect to the digitized head shape, or path to a - file containing these points. - hsp : None | str | array, shape = (n_points, 3) - Digitizer head shape points, or path to head shape file. If more than - 10`000 points are in the head shape, they are automatically decimated. eeg_ref : str Description of the type of reference used and (when applicable) of location of the reference electrode. Defaults to None. eeg_gnd : str Description of the location of the ground electrode. Defaults to None. - config : str | None - A path to the configuration file to use if the data is from a BTi - system. overwrite : bool If the file already exists, whether to overwrite it. verbose : bool @@ -549,26 +524,22 @@ def raw_to_bids(subject_id, task, raw_file, output_path, session_id=None, of the participant correctly. """ - if isinstance(raw_file, string_types): - # We must read in the raw data - raw = _read_raw(raw_file, electrode=electrode, hsp=hsp, hpi=hpi, - config=config, verbose=verbose) - _, ext = _parse_ext(raw_file, verbose=verbose) - raw_fname = raw_file - elif isinstance(raw_file, BaseRaw): - # We got a raw mne object, get back the filename if possible - # Assume that if no filename attr exists, it's a fif file. - raw = raw_file.copy() - if hasattr(raw, 'filenames'): - _, ext = _parse_ext(raw.filenames[0], verbose=verbose) - raw_fname = raw.filenames[0] - else: - # FIXME: How to get the filename if no filenames attribute? - raw_fname = 'unknown_file_name' - ext = '.fif' - else: - raise ValueError('raw_file must be an instance of str or BaseRaw, ' - 'got %s' % type(raw_file)) + if not isinstance(raw, BaseRaw): + raise ValueError('raw_file must be an instance of BaseRaw, ' + 'got %s' % type(raw)) + + # We got a raw mne object, get back the filename if possible + # Assume that if no filename attr exists, it's a fif file. + if not hasattr(raw, 'filenames'): + raise ValueError('raw.filenames is missing.') + + _, ext = _parse_ext(raw.filenames[0], verbose=verbose) + raw_fname = raw.filenames[0] + + params = _parse_bids_filename(bids_fname) + subject_id, session_id = params['sub'], params['ses'] + acquisition, task, run = params['acq'], params['task'], params['run'] + fname = os.path.join(output_path, bids_fname) data_path = make_bids_folders(subject=subject_id, session=session_id, kind=kind, root=output_path, @@ -594,18 +565,6 @@ def raw_to_bids(subject_id, task, raw_file, output_path, session_id=None, data_meta_fname = make_bids_filename( subject=subject_id, session=session_id, task=task, run=run, acquisition=acquisition, suffix='%s.json' % kind, prefix=data_path) - if ext in ['.fif', '.ds', '.vhdr', '.edf', '.bdf', '.set', '.cnt']: - raw_file_bids = make_bids_filename( - subject=subject_id, session=session_id, task=task, run=run, - acquisition=acquisition, suffix='%s%s' % (kind, ext)) - else: - raw_folder = make_bids_filename( - subject=subject_id, session=session_id, task=task, run=run, - acquisition=acquisition, suffix='%s' % kind) - raw_file_bids = make_bids_filename( - subject=subject_id, session=session_id, task=task, run=run, - acquisition=acquisition, suffix='%s%s' % (kind, ext), - prefix=raw_folder) events_tsv_fname = make_bids_filename( subject=subject_id, session=session_id, task=task, acquisition=acquisition, run=run, suffix='events.tsv', @@ -636,18 +595,15 @@ def raw_to_bids(subject_id, task, raw_file, output_path, session_id=None, eeg_gnd, verbose) _participants_tsv(raw, subject_id, "n/a", participants_fname, verbose) _channels_tsv(raw, channels_fname, verbose) - _scans_tsv(raw, os.path.join(kind, raw_file_bids), scans_fname, verbose) + _scans_tsv(raw, os.path.join(kind, bids_fname), scans_fname, verbose) - # set the raw file name to now be the absolute path to ensure the files - # are placed in the right location - raw_file_bids = os.path.join(data_path, raw_file_bids) - if os.path.exists(raw_file_bids) and not overwrite: + if os.path.exists(fname) and not overwrite: raise ValueError('"%s" already exists. Please set' - ' overwrite to True.' % raw_file_bids) - _mkdir_p(os.path.dirname(raw_file_bids)) + ' overwrite to True.' % fname) + _mkdir_p(os.path.dirname(fname)) if verbose: - print('Writing data files to %s' % raw_file_bids) + print('Writing data files to %s' % fname) if ext not in ALLOWED_EXTENSIONS: raise ValueError('ext must be in %s, got %s' @@ -657,17 +613,17 @@ def raw_to_bids(subject_id, task, raw_file, output_path, session_id=None, # Re-save FIF files to fix the file pointer for files with multiple parts # This is WIP, see: https://github.com/mne-tools/mne-python/pull/5470 if ext in ['.fif']: - raw.save(raw_file_bids, overwrite=overwrite) + raw.save(fname, overwrite=overwrite) # CTF data is saved in a directory elif ext == '.ds': - sh.copytree(raw_fname, raw_file_bids) + sh.copytree(raw_fname, fname) # BrainVision is multifile, copy over all of them and fix pointers elif ext == '.vhdr': - copyfile_brainvision(raw_fname, raw_file_bids) + copyfile_brainvision(raw_fname, fname) # EEGLAB .set might be accompanied by a .fdt - find out and copy it too elif ext == '.set': - copyfile_eeglab(raw_fname, raw_file_bids) + copyfile_eeglab(raw_fname, fname) else: - sh.copyfile(raw_fname, raw_file_bids) + sh.copyfile(raw_fname, fname) return output_path diff --git a/mne_bids/utils.py b/mne_bids/utils.py index 22a6a5ea36..ecc3ec86c1 100644 --- a/mne_bids/utils.py +++ b/mne_bids/utils.py @@ -60,6 +60,20 @@ def _mkdir_p(path, overwrite=False, verbose=False): raise +def _parse_bids_filename(fname): + """Get dict from BIDS fname.""" + params = dict(sub=None, ses=None, task=None, acq=None, + run=None, proc=None, recording=None, space=None) + entities = fname.split('_') + for entity in entities[:-1]: + key, value = entity.split('-') + if key not in params: + raise KeyError('Unexpected entity found in filename %s' % entity) + params[key] = value + params['suffix'] = entities[-1] + return params + + def make_bids_filename(subject=None, session=None, task=None, acquisition=None, run=None, processing=None, recording=None, space=None, suffix=None, prefix=None):