diff --git a/mne_bids/io.py b/mne_bids/io.py index 30494376d..34ebad689 100644 --- a/mne_bids/io.py +++ b/mne_bids/io.py @@ -17,7 +17,16 @@ '.cnt', # Neuroscan ] -ALLOWED_EXTENSIONS = allowed_extensions_meg + allowed_extensions_eeg +allowed_extensions_ieeg = ['.vhdr', # BrainVision, accompanied by .vmrk, .eeg + '.edf', # European Data Format + '.set', # EEGLAB, potentially accompanied by .fdt + '.mef', # MEF: Multiscale Electrophysiology File + '.nwb', # Neurodata without borders + ] + +ALLOWED_EXTENSIONS = (allowed_extensions_meg + + allowed_extensions_eeg + + allowed_extensions_ieeg) reader = {'.con': io.read_raw_kit, '.sqd': io.read_raw_kit, '.fif': io.read_raw_fif, '.pdf': io.read_raw_bti, @@ -43,8 +52,6 @@ def _read_raw(raw_fname, electrode=None, hsp=None, hpi=None, config=None, """Read a raw file into MNE, making inferences based on extension.""" fname, ext = _parse_ext(raw_fname) - # MEG File Types - # -------------- # KIT systems if ext in ['.con', '.sqd']: raw = io.read_raw_kit(raw_fname, elp=electrode, hsp=hsp, @@ -65,9 +72,15 @@ def _read_raw(raw_fname, electrode=None, hsp=None, hpi=None, config=None, elif ext in ['.edf', '.bdf']: raw = reader[ext](raw_fname, preload=True) + # MEF and NWB are allowed, but not yet implemented + elif ext in ['.mef', '.nwb']: + raise ValueError('Got "{}" as extension. This is an allowed extension ' + 'but there is no IO support for this file format yet.' + .format(ext)) + # No supported data found ... # --------------------------- else: - raise ValueError("Raw file name extension must be one of %\n" - "Got %" % (ALLOWED_EXTENSIONS, ext)) + raise ValueError('Raw file name extension must be one of {}\n' + 'Got {}'.format(ALLOWED_EXTENSIONS, ext)) return raw diff --git a/mne_bids/mne_bids.py b/mne_bids/mne_bids.py index b28292a4b..af534295c 100644 --- a/mne_bids/mne_bids.py +++ b/mne_bids/mne_bids.py @@ -52,19 +52,24 @@ '.meg4': 'CTF'} eeg_manufacturers = {'.vhdr': 'BrainProducts', '.eeg': 'BrainProducts', - '.edf': 'Mixed', '.bdf': 'Biosemi', '.set': 'Mixed', - '.fdt': 'Mixed', '.cnt': 'Neuroscan'} + '.edf': 'n/a', '.bdf': 'Biosemi', '.set': 'n/a', + '.fdt': 'n/a', '.cnt': 'Neuroscan'} + +ieeg_manufacturers = {'.vhdr': 'BrainProducts', '.eeg': 'BrainProducts', + '.edf': 'n/a', '.set': 'n/a', '.fdt': 'n/a', + '.mef': 'n/a', '.nwb': 'n/a'} # Merge the manufacturer dictionaries in a python2 / python3 compatible way MANUFACTURERS = dict() MANUFACTURERS.update(meg_manufacturers) MANUFACTURERS.update(eeg_manufacturers) +MANUFACTURERS.update(ieeg_manufacturers) # List of synthetic channels by manufacturer that are to be excluded from the # channel list. Currently this is only for stimulus channels. IGNORED_CHANNELS = {'KIT/Yokogawa': ['STI 014'], 'BrainProducts': ['STI 014'], - 'Mixed': ['STI 014'], + 'n/a': ['STI 014'], # for unknown manufacturers, ignore it 'Biosemi': ['STI 014'], 'Neuroscan': ['STI 014']} @@ -135,10 +140,10 @@ def _channels_tsv(raw, fname, overwrite=False, verbose=True): ('name', raw.info['ch_names']), ('type', ch_type), ('units', units), - ('description', description), - ('sampling_frequency', np.full((n_channels), sfreq)), ('low_cutoff', np.full((n_channels), low_cutoff)), ('high_cutoff', np.full((n_channels), high_cutoff)), + ('description', description), + ('sampling_frequency', np.full((n_channels), sfreq)), ('status', status)])) df.drop(ignored_indexes, inplace=True) @@ -486,6 +491,7 @@ def _sidecar_json(raw, task, manufacturer, fname, kind, overwrite=False, ('EEGPlacementScheme', _infer_eeg_placement_scheme(raw)), ('Manufacturer', manufacturer)] ch_info_json_ieeg = [ + ('iEEGReference', 'n/a'), ('ECOGChannelCount', n_ecogchan), ('SEEGChannelCount', n_seegchan)] ch_info_ch_counts = [ @@ -646,8 +652,6 @@ def write_raw_bids(raw, bids_basename, output_path, events_data=None, orient = ORIENTATION.get(ext, 'n/a') unit = UNITS.get(ext, 'n/a') manufacturer = MANUFACTURERS.get(ext, 'n/a') - if manufacturer == 'Mixed': - manufacturer = 'n/a' # save all meta data _participants_tsv(raw, subject_id, "n/a", participants_fname, overwrite, @@ -700,9 +704,7 @@ def write_raw_bids(raw, bids_basename, output_path, events_data=None, 'Please contact MNE developers if you have ' 'any questions.') else: - # TODO insert arg `split_naming=split_naming` - # when MNE releases 0.17 - raw.save(bids_fname, overwrite=True) + raw.save(bids_fname, overwrite=True, split_naming='bids') # CTF data is saved in a directory elif ext == '.ds': diff --git a/mne_bids/tests/test_io.py b/mne_bids/tests/test_io.py index a907e16ca..d1fb315a5 100644 --- a/mne_bids/tests/test_io.py +++ b/mne_bids/tests/test_io.py @@ -2,8 +2,11 @@ # Authors: Stefan Appelhoff # # License: BSD (3-clause) +import os.path as op import pytest +from mne.utils import _TempDir + from mne_bids.io import _parse_ext, _read_raw @@ -25,5 +28,17 @@ def test_read_raw(): """Test the raw reading.""" # Use a file ending that does not exist f = 'file.bogus' - with pytest.raises(ValueError): + with pytest.raises(ValueError, match='file name extension must be one of'): _read_raw(f) + + +def test_not_implemented(): + """Test the not yet implemented data formats raise an adequate error.""" + for not_implemented_ext in ['.mef', '.nwb']: + data_path = _TempDir() + raw_fname = op.join(data_path, 'test' + not_implemented_ext) + with open(raw_fname, 'w'): + pass + with pytest.raises(ValueError, match=('there is no IO support for ' + 'this file format yet')): + _read_raw(raw_fname) diff --git a/mne_bids/tests/test_mne_bids.py b/mne_bids/tests/test_mne_bids.py index 5a64914ef..235aadc9b 100644 --- a/mne_bids/tests/test_mne_bids.py +++ b/mne_bids/tests/test_mne_bids.py @@ -45,8 +45,6 @@ shell = True -# MEG Tests -# --------- def test_fif(): """Test functionality of the write_raw_bids conversion for fif.""" output_path = _TempDir() @@ -184,8 +182,6 @@ def test_bti(): run_subprocess(cmd, shell=shell) -# EEG Tests -# --------- def test_vhdr(): """Test write_raw_bids conversion for BrainVision data.""" output_path = _TempDir() @@ -205,6 +201,16 @@ def test_vhdr(): overwrite=True) assert len([f for f in os.listdir(data_path) if op.isfile(f)]) == 0 + # Also cover iEEG + # We use the same data and pretend that eeg channels are ecog + raw.set_channel_types({raw.ch_names[i]: 'ecog' + for i in mne.pick_types(raw.info, eeg=True)}) + output_path = _TempDir() + write_raw_bids(raw, bids_basename, output_path, overwrite=False) + + cmd = ['bids-validator', '--bep010', output_path] + run_subprocess(cmd, shell=shell) + def test_edf(): """Test write_raw_bids conversion for European Data Format data.""" @@ -244,6 +250,16 @@ def test_edf(): df = pd.read_csv(scans_tsv, sep='\t') assert df.shape[0] == 2 + # Also cover iEEG + # We use the same data and pretend that eeg channels are ecog + raw.set_channel_types({raw.ch_names[i]: 'ecog' + for i in mne.pick_types(raw.info, eeg=True)}) + output_path = _TempDir() + write_raw_bids(raw, bids_basename, output_path) + + cmd = ['bids-validator', '--bep010', output_path] + run_subprocess(cmd, shell=shell) + def test_bdf(): """Test write_raw_bids conversion for Biosemi data.""" @@ -283,6 +299,16 @@ def test_set(): cmd = ['bids-validator', '--bep006', output_path] run_subprocess(cmd, shell=shell) + # Also cover iEEG + # We use the same data and pretend that eeg channels are ecog + raw.set_channel_types({raw.ch_names[i]: 'ecog' + for i in mne.pick_types(raw.info, eeg=True)}) + output_path = _TempDir() + write_raw_bids(raw, bids_basename, output_path) + + cmd = ['bids-validator', '--bep010', output_path] + run_subprocess(cmd, shell=shell) + def test_cnt(): """Test write_raw_bids conversion for Neuroscan data.""" diff --git a/mne_bids/tests/test_utils.py b/mne_bids/tests/test_utils.py index 9619f777c..ef8534883 100644 --- a/mne_bids/tests/test_utils.py +++ b/mne_bids/tests/test_utils.py @@ -9,6 +9,7 @@ from datetime import datetime from scipy.io import savemat +from numpy.random import random import mne from mne.datasets import testing from mne.utils import _TempDir @@ -16,11 +17,40 @@ from mne_bids.utils import (make_bids_folders, make_bids_basename, _check_types, print_dir_tree, _age_on_date, _get_brainvision_paths, copyfile_brainvision, - copyfile_eeglab, _infer_eeg_placement_scheme) + copyfile_eeglab, _infer_eeg_placement_scheme, + _handle_kind) base_path = op.join(op.dirname(mne.__file__), 'io') +def test_handle_kind(): + """Test the automatic extraction of kind from the data.""" + # Create a dummy raw + n_channels = 1 + sampling_rate = 100 + data = random((n_channels, sampling_rate)) + channel_types = ['grad', 'eeg', 'ecog'] + expected_kinds = ['meg', 'eeg', 'ieeg'] + # do it once for each type ... and once for "no type" + for chtype, kind in zip(channel_types, expected_kinds): + info = mne.create_info(n_channels, sampling_rate, ch_types=[chtype]) + raw = mne.io.RawArray(data, info) + assert _handle_kind(raw) == kind + + # if the situation is ambiguous (EEG and iEEG channels both), raise error + with pytest.raises(ValueError, match='Both EEG and iEEG channels found'): + info = mne.create_info(2, sampling_rate, + ch_types=['eeg', 'ecog']) + raw = mne.io.RawArray(random((2, sampling_rate)), info) + _handle_kind(raw) + + # if we cannot find a proper channel type, we raise an error + with pytest.raises(ValueError, match='Neither MEG/EEG/iEEG channels'): + info = mne.create_info(n_channels, sampling_rate, ch_types=['misc']) + raw = mne.io.RawArray(data, info) + _handle_kind(raw) + + def test_print_dir_tree(): """Test printing a dir tree.""" with pytest.raises(ValueError): diff --git a/mne_bids/utils.py b/mne_bids/utils.py index 34a2ccfe6..6c4132ea5 100644 --- a/mne_bids/utils.py +++ b/mne_bids/utils.py @@ -86,12 +86,16 @@ def _parse_bids_filename(fname, verbose): def _handle_kind(raw): """Get kind.""" - if 'meg' in raw: + if 'eeg' in raw and 'ecog' in raw: + raise ValueError('Both EEG and iEEG channels found in data.' + 'There is currently no specification on how' + 'to handle this data. Please proceed manually.') + elif 'meg' in raw: kind = 'meg' - elif 'eeg' in raw: - kind = 'eeg' elif 'ecog' in raw: kind = 'ieeg' + elif 'eeg' in raw: + kind = 'eeg' else: raise ValueError('Neither MEG/EEG/iEEG channels found in data.' 'Please use raw.set_channel_types to set the '