Source code for mne_bids.mne_bids

"""Make BIDS compatible directory structures and infer meta data from MNE."""
# Authors: Mainak Jas <mainak.jas@telecom-paristech.fr>
#          Alexandre Gramfort <alexandre.gramfort@telecom-paristech.fr>
#          Teon Brooks <teon.brooks@gmail.com>
#          Chris Holdgraf <choldgraf@berkeley.edu>
#          Stefan Appelhoff <stefan.appelhoff@mailbox.org>
#          Matt Sanderson <matt.sanderson@mq.edu.au>
#
# License: BSD (3-clause)

import os
import errno
import os.path as op

import shutil as sh
import pandas as pd
from collections import defaultdict, OrderedDict

import numpy as np
from numpy.testing import assert_array_equal

from mne import Epochs
from mne.io.constants import FIFF
from mne.io.pick import channel_type
from mne.io import BaseRaw
from mne.channels.channels import _unit2human
from mne.utils import check_version

from datetime import datetime
from warnings import warn

from .pick import coil_type
from .utils import (make_bids_basename, make_bids_folders,
                    make_dataset_description, _write_json, _write_tsv,
                    _read_events, _mkdir_p, _age_on_date,
                    copyfile_brainvision, copyfile_eeglab,
                    _infer_eeg_placement_scheme, _parse_bids_filename,
                    _handle_kind)
from .io import _parse_ext, ALLOWED_EXTENSIONS, reader


ALLOWED_KINDS = ['meg', 'eeg', 'ieeg']

# Orientation of the coordinate system dependent on manufacturer
ORIENTATION = {'.sqd': 'ALS', '.con': 'ALS', '.fif': 'RAS', '.pdf': 'ALS',
               '.ds': 'ALS'}

UNITS = {'.sqd': 'm', '.con': 'm', '.fif': 'm', '.pdf': 'm', '.ds': 'cm'}

meg_manufacturers = {'.sqd': 'KIT/Yokogawa', '.con': 'KIT/Yokogawa',
                     '.fif': 'Elekta', '.pdf': '4D Magnes', '.ds': 'CTF',
                     '.meg4': 'CTF'}

eeg_manufacturers = {'.vhdr': 'BrainProducts', '.eeg': 'BrainProducts',
                     '.edf': 'Mixed', '.bdf': 'Biosemi', '.set': 'Mixed',
                     '.fdt': 'Mixed', '.cnt': 'Neuroscan'}

# Merge the manufacturer dictionaries in a python2 / python3 compatible way
MANUFACTURERS = dict()
MANUFACTURERS.update(meg_manufacturers)
MANUFACTURERS.update(eeg_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'],
                    'Biosemi': ['STI 014'],
                    'Neuroscan': ['STI 014']}


def _channels_tsv(raw, fname, overwrite=False, verbose=True):
    """Create a channels.tsv file and save it.

    Parameters
    ----------
    raw : instance of Raw
        The data as MNE-Python Raw object.
    fname : str
        Filename to save the channels.tsv to.
    overwrite : bool
        Whether to overwrite the existing file.
        Defaults to False.
    verbose : bool
        Set verbose output to true or false.

    """
    map_chs = defaultdict(lambda: 'OTHER')
    map_chs.update(meggradaxial='MEGGRADAXIAL',
                   megrefgradaxial='MEGREFGRADAXIAL',
                   meggradplanar='MEGGRADPLANAR',
                   megmag='MEGMAG', megrefmag='MEGREFMAG',
                   eeg='EEG', misc='MISC', stim='TRIG', emg='EMG',
                   ecog='ECOG', seeg='SEEG', eog='EOG', ecg='ECG')
    map_desc = defaultdict(lambda: 'Other type of channel')
    map_desc.update(meggradaxial='Axial Gradiometer',
                    megrefgradaxial='Axial Gradiometer Reference',
                    meggradplanar='Planar Gradiometer',
                    megmag='Magnetometer',
                    megrefmag='Magnetometer Reference',
                    stim='Trigger', eeg='ElectroEncephaloGram',
                    ecog='Electrocorticography',
                    seeg='StereoEEG',
                    ecg='ElectroCardioGram',
                    eog='ElectroOculoGram',
                    emg='ElectroMyoGram',
                    misc='Miscellaneous')
    get_specific = ('mag', 'ref_meg', 'grad')

    # get the manufacturer from the file in the Raw object
    manufacturer = None

    _, ext = _parse_ext(raw.filenames[0], verbose=verbose)
    manufacturer = MANUFACTURERS[ext]

    ignored_indexes = [raw.ch_names.index(ch_name) for ch_name in raw.ch_names
                       if ch_name in
                       IGNORED_CHANNELS.get(manufacturer, list())]

    status, ch_type, description = list(), list(), list()
    for idx, ch in enumerate(raw.info['ch_names']):
        status.append('bad' if ch in raw.info['bads'] else 'good')
        _channel_type = channel_type(raw.info, idx)
        if _channel_type in get_specific:
            _channel_type = coil_type(raw.info, idx)
        ch_type.append(map_chs[_channel_type])
        description.append(map_desc[_channel_type])
    low_cutoff, high_cutoff = (raw.info['highpass'], raw.info['lowpass'])
    units = [_unit2human.get(ch_i['unit'], 'n/a') for ch_i in raw.info['chs']]
    units = [u if u not in ['NA'] else 'n/a' for u in units]
    n_channels = raw.info['nchan']
    sfreq = raw.info['sfreq']

    df = pd.DataFrame(OrderedDict([
                      ('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)),
                      ('status', status)]))
    df.drop(ignored_indexes, inplace=True)

    _write_tsv(fname, df, overwrite, verbose)

    return fname


def _events_tsv(events, raw, fname, trial_type, overwrite=False,
                verbose=True):
    """Create an events.tsv file and save it.

    This function will write the mandatory 'onset', and 'duration' columns as
    well as the optional 'event_value' and 'event_sample'. The 'event_value'
    corresponds to the marker value as found in the TRIG channel of the
    recording. In addition, the 'trial_type' field can be written.

    Parameters
    ----------
    events : array, shape = (n_events, 3)
        The first column contains the event time in samples and the third
        column contains the event id. The second column is ignored for now but
        typically contains the value of the trigger channel either immediately
        before the event or immediately after.
    raw : instance of Raw
        The data as MNE-Python Raw object.
    fname : str
        Filename to save the events.tsv to.
    trial_type : dict | None
        Dictionary mapping a brief description key to an event id (value). For
        example {'Go': 1, 'No Go': 2}.
    overwrite : bool
        Whether to overwrite the existing file.
        Defaults to False.
    verbose : bool
        Set verbose output to true or false.

    Notes
    -----
    The function writes durations of zero for each event.

    """
    # Start by filling all data that we know into a df
    first_samp = raw.first_samp
    sfreq = raw.info['sfreq']
    events[:, 0] -= first_samp

    data = OrderedDict([('onset', events[:, 0]),
                        ('duration', np.zeros(events.shape[0])),
                        ('trial_type', events[:, 2]),
                        ('event_value', events[:, 2]),
                        ('event_sample', events[:, 0])])

    df = pd.DataFrame.from_dict(data)

    # Now check if trial_type is specified or should be removed
    if trial_type:
        trial_type_map = {v: k for k, v in trial_type.items()}
        df.trial_type = df.trial_type.map(trial_type_map)
    else:
        df.drop(labels=['trial_type'], axis=1, inplace=True)

    # Onset column needs to be specified in seconds
    df.onset /= sfreq

    _write_tsv(fname, df, overwrite, verbose)

    return fname


def _participants_tsv(raw, subject_id, group, fname, overwrite=False,
                      verbose=True):
    """Create a participants.tsv file and save it.

    This will append any new participant data to the current list if it
    exists. Otherwise a new file will be created with the provided information.

    Parameters
    ----------
    raw : instance of Raw
        The data as MNE-Python Raw object.
    subject_id : str
        The subject name in BIDS compatible format ('01', '02', etc.)
    group : str
        Name of group participant belongs to.
    fname : str
        Filename to save the participants.tsv to.
    overwrite : bool
        Whether to overwrite the existing file.
        Defaults to False.
        If there is already data for the given `subject_id` and overwrite is
        False, an error will be raised.
    verbose : bool
        Set verbose output to true or false.

    """
    subject_id = 'sub-' + subject_id
    data = {'participant_id': [subject_id]}

    subject_info = raw.info['subject_info']
    if subject_info is not None:
        genders = {0: 'U', 1: 'M', 2: 'F'}
        sex = genders[subject_info.get('sex', 0)]

        # determine the age of the participant
        age = subject_info.get('birthday', None)
        meas_date = raw.info.get('meas_date', None)
        if isinstance(meas_date, (tuple, list, np.ndarray)):
            meas_date = meas_date[0]

        if meas_date is not None and age is not None:
            bday = datetime(age[0], age[1], age[2])
            meas_datetime = datetime.fromtimestamp(meas_date)
            subject_age = _age_on_date(bday, meas_datetime)
        else:
            subject_age = "n/a"

        data.update({'age': [subject_age], 'sex': [sex], 'group': [group]})

    df = pd.DataFrame(data=data,
                      columns=['participant_id', 'age', 'sex', 'group'])

    if os.path.exists(fname):
        orig_df = pd.read_csv(fname, sep='\t')
        # whether the data exists identically in the current DataFrame
        exact_included = df.values.tolist()[0] in orig_df.values.tolist()
        # whether the subject id is in the existing DataFrame
        sid_included = subject_id in orig_df['participant_id'].values
        # if the subject data provided is different to the currently existing
        # data and overwrite is not True raise an error
        if (sid_included and not exact_included) and not overwrite:
            raise OSError(errno.EEXIST, '"%s" already exists in the '
                          'participant list. Please set overwrite to '
                          'True.' % subject_id)
        # otherwise add the new data
        df = orig_df.append(df)
        # and drop any duplicates as we want overwrite = True to force the old
        # data to be overwritten
        df.drop_duplicates(subset='participant_id', keep='last',
                           inplace=True)
        df = df.sort_values(by='participant_id')

    # overwrite is forced to True as all issues with overwrite == False have
    # been handled by this point
    _write_tsv(fname, df, True, verbose)

    return fname


def _scans_tsv(raw, raw_fname, fname, overwrite=False, verbose=True):
    """Create a scans.tsv file and save it.

    Parameters
    ----------
    raw : instance of Raw
        The data as MNE-Python Raw object.
    raw_fname : str
        Relative path to the raw data file.
    fname : str
        Filename to save the scans.tsv to.
    overwrite : bool
        Defaults to False.
        Whether to overwrite the existing data in the file.
        If there is already data for the given `fname` and overwrite is False,
        an error will be raised.
    verbose : bool
        Set verbose output to true or false.

    """
    # get measurement date from the data info
    meas_date = raw.info['meas_date']
    if isinstance(meas_date, (tuple, list, np.ndarray)):
        meas_date = meas_date[0]
        acq_time = datetime.fromtimestamp(
            meas_date).strftime('%Y-%m-%dT%H:%M:%S')
    else:
        acq_time = 'n/a'

    df = pd.DataFrame(data={'filename': ['%s' % raw_fname],
                            'acq_time': [acq_time]},
                      columns=['filename', 'acq_time'])

    if os.path.exists(fname):
        orig_df = pd.read_csv(fname, sep='\t')
        # if the file name is already in the file raise an error
        if raw_fname in orig_df['filename'].values and not overwrite:
            raise OSError(errno.EEXIST, '"%s" already exists in the '
                          'scans list. Please set overwrite to '
                          'True.' % raw_fname)
        # otherwise add the new data
        df = orig_df.append(df)
        # and drop any duplicates as we want overwrite = True to force the old
        # data to be overwritten
        df.drop_duplicates(subset='filename', keep='last', inplace=True)
        df = df.sort_values(by='acq_time')

    # overwrite is forced to True as all issues with overwrite == False have
    # been handled by this point
    _write_tsv(fname, df, True, verbose)

    return fname


def _coordsystem_json(raw, unit, orient, manufacturer, fname,
                      overwrite=False, verbose=True):
    """Create a coordsystem.json file and save it.

    Parameters
    ----------
    raw : instance of Raw
        The data as MNE-Python Raw object.
    unit : str
        Units to be used in the coordsystem specification.
    orient : str
        Used to define the coordinate system for the head coils.
    manufacturer : str
        Used to define the coordinate system for the MEG sensors.
    fname : str
        Filename to save the coordsystem.json to.
    overwrite : bool
        Whether to overwrite the existing file.
        Defaults to False.
    verbose : bool
        Set verbose output to true or false.

    """
    dig = raw.info['dig']
    coords = dict()
    fids = {d['ident']: d for d in dig if d['kind'] ==
            FIFF.FIFFV_POINT_CARDINAL}
    if fids:
        if FIFF.FIFFV_POINT_NASION in fids:
            coords['NAS'] = fids[FIFF.FIFFV_POINT_NASION]['r'].tolist()
        if FIFF.FIFFV_POINT_LPA in fids:
            coords['LPA'] = fids[FIFF.FIFFV_POINT_LPA]['r'].tolist()
        if FIFF.FIFFV_POINT_RPA in fids:
            coords['RPA'] = fids[FIFF.FIFFV_POINT_RPA]['r'].tolist()

    hpi = {d['ident']: d for d in dig if d['kind'] == FIFF.FIFFV_POINT_HPI}
    if hpi:
        for ident in hpi.keys():
            coords['coil%d' % ident] = hpi[ident]['r'].tolist()

    coord_frame = set([dig[ii]['coord_frame'] for ii in range(len(dig))])
    if len(coord_frame) > 1:
        err = 'All HPI and Fiducials must be in the same coordinate frame.'
        raise ValueError(err)

    fid_json = {'MEGCoordinateSystem': manufacturer,
                'MEGCoordinateUnits': unit,  # XXX validate this
                'HeadCoilCoordinates': coords,
                'HeadCoilCoordinateSystem': orient,
                'HeadCoilCoordinateUnits': unit  # XXX validate this
                }

    _write_json(fid_json, fname, overwrite)

    return fname


def _sidecar_json(raw, task, manufacturer, fname, kind, overwrite=False,
                  verbose=True):
    """Create a sidecar json file depending on the kind and save it.

    The sidecar json file provides meta data about the data of a certain kind.

    Parameters
    ----------
    raw : instance of Raw
        The data as MNE-Python Raw object.
    task : str
        Name of the task the data is based on.
    manufacturer : str
        Manufacturer of the acquisition system. For MEG also used to define the
        coordinate system for the MEG sensors.
    fname : str
        Filename to save the sidecar json to.
    kind : str
        Type of the data as in ALLOWED_KINDS.
    overwrite : bool
        Whether to overwrite the existing file.
        Defaults to False.
    verbose : bool
        Set verbose output to true or false. Defaults to true.

    """
    sfreq = raw.info['sfreq']
    powerlinefrequency = raw.info.get('line_freq', None)
    if powerlinefrequency is None:
        warn('No line frequency found, defaulting to 50 Hz')
        powerlinefrequency = 50

    if isinstance(raw, BaseRaw):
        rec_type = 'continuous'
    elif isinstance(raw, Epochs):
        rec_type = 'epoched'
    else:
        rec_type = 'n/a'

    # determine whether any channels have to be ignored:
    n_ignored = len([ch_name for ch_name in
                     IGNORED_CHANNELS.get(manufacturer, list()) if
                     ch_name in raw.ch_names])
    # all ignored channels are trigger channels at the moment...

    n_megchan = len([ch for ch in raw.info['chs']
                     if ch['kind'] == FIFF.FIFFV_MEG_CH])
    n_megrefchan = len([ch for ch in raw.info['chs']
                        if ch['kind'] == FIFF.FIFFV_REF_MEG_CH])
    n_eegchan = len([ch for ch in raw.info['chs']
                     if ch['kind'] == FIFF.FIFFV_EEG_CH])
    n_ecogchan = len([ch for ch in raw.info['chs']
                     if ch['kind'] == FIFF.FIFFV_ECOG_CH])
    n_seegchan = len([ch for ch in raw.info['chs']
                     if ch['kind'] == FIFF.FIFFV_SEEG_CH])
    n_eogchan = len([ch for ch in raw.info['chs']
                     if ch['kind'] == FIFF.FIFFV_EOG_CH])
    n_ecgchan = len([ch for ch in raw.info['chs']
                     if ch['kind'] == FIFF.FIFFV_ECG_CH])
    n_emgchan = len([ch for ch in raw.info['chs']
                     if ch['kind'] == FIFF.FIFFV_EMG_CH])
    n_miscchan = len([ch for ch in raw.info['chs']
                     if ch['kind'] == FIFF.FIFFV_MISC_CH])
    n_stimchan = len([ch for ch in raw.info['chs']
                     if ch['kind'] == FIFF.FIFFV_STIM_CH]) - n_ignored

    # Define modality-specific JSON dictionaries
    ch_info_json_common = [
        ('TaskName', task),
        ('Manufacturer', manufacturer),
        ('PowerLineFrequency', powerlinefrequency),
        ('SamplingFrequency', sfreq),
        ('SoftwareFilters', 'n/a'),
        ('RecordingDuration', raw.times[-1]),
        ('RecordingType', rec_type)]
    ch_info_json_meg = [
        ('DewarPosition', 'n/a'),
        ('DigitizedLandmarks', False),
        ('DigitizedHeadPoints', False),
        ('MEGChannelCount', n_megchan),
        ('MEGREFChannelCount', n_megrefchan)]
    ch_info_json_eeg = [
        ('EEGReference', 'n/a'),
        ('EEGGround', 'n/a'),
        ('EEGPlacementScheme', _infer_eeg_placement_scheme(raw)),
        ('Manufacturer', manufacturer)]
    ch_info_json_ieeg = [
        ('ECOGChannelCount', n_ecogchan),
        ('SEEGChannelCount', n_seegchan)]
    ch_info_ch_counts = [
        ('EEGChannelCount', n_eegchan),
        ('EOGChannelCount', n_eogchan),
        ('ECGChannelCount', n_ecgchan),
        ('EMGChannelCount', n_emgchan),
        ('MiscChannelCount', n_miscchan),
        ('TriggerChannelCount', n_stimchan)]

    # Stitch together the complete JSON dictionary
    ch_info_json = ch_info_json_common
    if kind == 'meg':
        append_kind_json = ch_info_json_meg
    elif kind == 'eeg':
        append_kind_json = ch_info_json_eeg
    elif kind == 'ieeg':
        append_kind_json = ch_info_json_ieeg
    else:
        raise ValueError('Unexpected "kind": {}'
                         ' Use one of: {}'.format(kind, ALLOWED_KINDS))

    ch_info_json += append_kind_json
    ch_info_json += ch_info_ch_counts
    ch_info_json = OrderedDict(ch_info_json)

    _write_json(ch_info_json, fname, overwrite, verbose)

    return fname


[docs]def write_raw_bids(raw, bids_basename, output_path, events_data=None, event_id=None, overwrite=False, verbose=True): """Walk over a folder of files and create BIDS compatible folder. .. warning:: The original files are simply copied over. This function cannot convert modify data files from one format to another. Modification of the original data files is not allowed. Parameters ---------- raw : instance of mne.io.Raw The raw data. It must be an instance of mne.Raw. The data should not be loaded on disk, i.e., raw.preload must be False. bids_basename : str The base filename of the BIDS compatible files. Typically, this can be generated using make_bids_basename. Example: sub-01_ses-01_task-testing_acq-01_run-01 This will write the following files in the correct subfolder of output_path: - sub-01_ses-01_task-testing_acq-01_run-01_meg.fif - sub-01_ses-01_task-testing_acq-01_run-01_meg.json - sub-01_ses-01_task-testing_acq-01_run-01_channels.tsv - sub-01_ses-01_task-testing_acq-01_run-01_coordsystem.json and the following one if events_data is not None - sub-01_ses-01_task-testing_acq-01_run-01_events.tsv and add a line to the following files: - participants.tsv - scans.tsv Note that the modality 'meg' is automatically inferred from the raw object and extension '.fif' is copied from raw.filenames. output_path : str The path of the root of the BIDS compatible folder. The session and subject specific folders will be populated automatically by parsing bids_basename. events_data : str | array | None The events file. If a string, a path to the events file. If an array, the MNE events array (shape n_events, 3). If None, events will be 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 overwrite : bool Whether to overwrite existing files or data in files. Defaults to False. If overwrite is True, any existing files with the same BIDS parameters will be overwritten with the exception of the `participants.tsv` and `scans.tsv` files. For these files, parts of pre-existing data that match the current data will be replaced. If overwrite is False, no existing data will be overwritten or replaced. verbose : bool If verbose is True, this will print a snippet of the sidecar files. If False, no content will be printed. Notes ----- For the participants.tsv file, the raw.info['subjects_info'] should be updated and raw.info['meas_date'] should not be None to compute the age of the participant correctly. """ if not isinstance(raw, BaseRaw): raise ValueError('raw_file must be an instance of BaseRaw, ' 'got %s' % type(raw)) if not hasattr(raw, 'filenames') or raw.filenames[0] is None: raise ValueError('raw.filenames is missing. Please set raw.filenames' 'as a list with the full path of original raw file.') if raw.preload is not False: raise ValueError('The data should not be preloaded.') raw = raw.copy() raw_fname = raw.filenames[0] if '.ds' in op.dirname(raw.filenames[0]): raw_fname = op.dirname(raw.filenames[0]) # point to file containing header info for multifile systems raw_fname = raw_fname.replace('.eeg', '.vhdr') raw_fname = raw_fname.replace('.fdt', '.set') _, ext = _parse_ext(raw_fname, verbose=verbose) raw_orig = reader[ext](**raw._init_kwargs) assert_array_equal(raw.times, raw_orig.times, "raw.times should not have changed since reading" " in from the file. It may have been cropped.") params = _parse_bids_filename(bids_basename, verbose) subject_id, session_id = params['sub'], params['ses'] acquisition, task, run = params['acq'], params['task'], params['run'] kind = _handle_kind(raw) bids_fname = bids_basename + '_%s%s' % (kind, ext) data_path = make_bids_folders(subject=subject_id, session=session_id, kind=kind, output_path=output_path, overwrite=False, verbose=verbose) if session_id is None: ses_path = os.sep.join(data_path.split(os.sep)[:-1]) else: ses_path = make_bids_folders(subject=subject_id, session=session_id, output_path=output_path, make_dir=False, overwrite=False, verbose=verbose) # create filenames scans_fname = make_bids_basename( subject=subject_id, session=session_id, suffix='scans.tsv', prefix=ses_path) participants_fname = make_bids_basename(prefix=output_path, suffix='participants.tsv') coordsystem_fname = make_bids_basename( subject=subject_id, session=session_id, acquisition=acquisition, suffix='coordsystem.json', prefix=data_path) sidecar_fname = make_bids_basename( subject=subject_id, session=session_id, task=task, run=run, acquisition=acquisition, suffix='%s.json' % kind, prefix=data_path) events_fname = make_bids_basename( subject=subject_id, session=session_id, task=task, acquisition=acquisition, run=run, suffix='events.tsv', prefix=data_path) channels_fname = make_bids_basename( subject=subject_id, session=session_id, task=task, run=run, acquisition=acquisition, suffix='channels.tsv', prefix=data_path) if ext not in ['.fif', '.ds', '.vhdr', '.edf', '.bdf', '.set', '.cnt']: bids_raw_folder = bids_fname.split('.')[0] bids_fname = op.join(bids_raw_folder, bids_fname) # Read in Raw object and extract metadata from Raw object if needed 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, verbose) _scans_tsv(raw, os.path.join(kind, bids_fname), scans_fname, overwrite, verbose) # TODO: Implement coordystem.json and electrodes.tsv for EEG and iEEG if kind == 'meg': _coordsystem_json(raw, unit, orient, manufacturer, coordsystem_fname, overwrite, verbose) events = _read_events(events_data, raw) if len(events) > 0: _events_tsv(events, raw, events_fname, event_id, overwrite, verbose) make_dataset_description(output_path, name=" ", verbose=verbose) _sidecar_json(raw, task, manufacturer, sidecar_fname, kind, overwrite, verbose) _channels_tsv(raw, channels_fname, overwrite, verbose) # set the raw file name to now be the absolute path to ensure the files # are placed in the right location bids_fname = os.path.join(data_path, bids_fname) if os.path.exists(bids_fname) and not overwrite: raise OSError(errno.EEXIST, '"%s" already exists. Please set ' 'overwrite to True.' % bids_fname) _mkdir_p(os.path.dirname(bids_fname)) if verbose: print('Copying data files to %s' % bids_fname) if ext not in ALLOWED_EXTENSIONS: raise ValueError('ext must be in %s, got %s' % (''.join(ALLOWED_EXTENSIONS), ext)) # Copy the imaging data files if ext in ['.fif']: n_rawfiles = len(raw.filenames) if n_rawfiles > 1: # TODO Update MNE requirement to version 0.17 when it's released if check_version('mne', '0.17.dev'): split_naming = 'bids' raw.save(bids_fname, split_naming=split_naming, overwrite=True) else: raise NotImplementedError( 'Renaming split fif files is not supported on your ' 'version of MNE. Please upgrade to at least "0.17.dev". ' '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) # CTF data is saved in a directory elif ext == '.ds': sh.copytree(raw_fname, bids_fname) # BrainVision is multifile, copy over all of them and fix pointers elif ext == '.vhdr': copyfile_brainvision(raw_fname, bids_fname) # EEGLAB .set might be accompanied by a .fdt - find out and copy it too elif ext == '.set': copyfile_eeglab(raw_fname, bids_fname) else: sh.copyfile(raw_fname, bids_fname) # KIT data requires the marker file to be copied over too if 'mrk' in raw._init_kwargs: hpi = raw._init_kwargs['mrk'] _, marker_ext = _parse_ext(hpi) marker_fname = make_bids_basename( subject=subject_id, session=session_id, task=task, run=run, acquisition=acquisition, suffix='markers%s' % marker_ext, prefix=os.path.join(data_path, bids_raw_folder)) sh.copyfile(hpi, marker_fname) return output_path