def copyfile_kit(src, dest, subject_id, session_id, task, run, _init_kwargs): """Copy and rename KIT files to a new location. Parameters ---------- src : str Path to the source raw .con or .sqd folder. dest : str Path to the destination of the new bids folder. subject_id : str | None The subject ID. Corresponds to "sub". session_id : str | None The session identifier. Corresponds to "ses". task : str | None The task identifier. Corresponds to "task". run : int | None The run number. Corresponds to "run". _init_kwargs : dict Extract information of marker and headpoints """ # create parent directories in case it does not exist yet _mkdir_p(op.dirname(dest)) # KIT data requires the marker file to be copied over too sh.copyfile(src, dest) data_path = op.split(dest)[0] datatype = 'meg' if 'mrk' in _init_kwargs: hpi = _init_kwargs['mrk'] acq_map = dict() if isinstance(hpi, list): if _get_mrk_meas_date(hpi[0]) > _get_mrk_meas_date(hpi[1]): raise ValueError('Markers provided in incorrect order.') _, marker_ext = _parse_ext(hpi[0]) acq_map = dict(zip(['pre', 'post'], hpi)) else: _, marker_ext = _parse_ext(hpi) acq_map[None] = hpi for key, value in acq_map.items(): marker_path = BIDSPath( subject=subject_id, session=session_id, task=task, run=run, acquisition=key, suffix='markers', extension=marker_ext, datatype=datatype) sh.copyfile(value, op.join(data_path, marker_path.basename)) for acq in ['elp', 'hsp']: if acq in _init_kwargs: position_file = _init_kwargs[acq] task, run, acq = None, None, acq.upper() position_ext = '.pos' position_path = BIDSPath( subject=subject_id, session=session_id, task=task, run=run, acquisition=acq, suffix='headshape', extension=position_ext, datatype=datatype) sh.copyfile(position_file, op.join(data_path, position_path.basename))
def _write_dig_bids(bids_path, raw, overwrite=False, verbose=True): """Write BIDS formatted DigMontage from Raw instance. Handles coordinatesystem.json and electrodes.tsv writing from DigMontage. Parameters ---------- bids_path : BIDSPath Path in the BIDS dataset to save the ``electrodes.tsv`` and ``coordsystem.json`` file for. ``datatype`` attribute must be ``eeg``, or ``ieeg``. For ``meg`` data, ``electrodes.tsv`` are not saved. raw : instance of Raw The data as MNE-Python Raw object. overwrite : bool Whether to overwrite the existing file. Defaults to False. verbose : bool Set verbose output to true or false. """ # write electrodes data for iEEG and EEG unit = "m" # defaults to meters # get coordinate frame from digMontage digpoint = raw.info['dig'][0] if any(digpoint['coord_frame'] != _digpoint['coord_frame'] for _digpoint in raw.info['dig']): warn("Not all digpoints have the same coordinate frame. " "Skipping electrodes.tsv writing...") return # get the accepted mne-python coordinate frames coord_frame_int = int(digpoint['coord_frame']) mne_coord_frame = MNE_FRAME_TO_STR.get(coord_frame_int, None) coord_frame = MNE_TO_BIDS_FRAMES.get(mne_coord_frame, None) # create electrodes/coordsystem files using a subset of entities # that are specified for these files in the specification coord_file_entities = { 'root': bids_path.root, 'datatype': bids_path.datatype, 'subject': bids_path.subject, 'session': bids_path.session, 'acquisition': bids_path.acquisition, 'space': bids_path.space } datatype = bids_path.datatype electrodes_path = BIDSPath(**coord_file_entities, suffix='electrodes', extension='.tsv') coordsystem_path = BIDSPath(**coord_file_entities, suffix='coordsystem', extension='.json') if verbose: print("Writing electrodes file to... ", electrodes_path) print("Writing coordsytem file to... ", coordsystem_path) if datatype == "ieeg": if coord_frame is not None: # XXX: To improve when mne-python allows coord_frame='unknown' if coord_frame not in BIDS_IEEG_COORDINATE_FRAMES: coordsystem_path.update(space=coord_frame) electrodes_path.update(space=coord_frame) coord_frame = 'Other' # Now write the data to the elec coords and the coordsystem _electrodes_tsv(raw, electrodes_path, datatype, overwrite, verbose) _coordsystem_json(raw=raw, unit=unit, hpi_coord_system='n/a', sensor_coord_system=coord_frame, fname=coordsystem_path, datatype=datatype, overwrite=overwrite, verbose=verbose) else: # default coordinate frame to mri if not available warn("Coordinate frame of iEEG coords missing/unknown " "for {}. Skipping reading " "in of montage...".format(electrodes_path)) elif datatype == 'eeg': # We only write EEG electrodes.tsv and coordsystem.json # if we have LPA, RPA, and NAS available to rescale to a known # coordinate system frame coords = _extract_landmarks(raw.info['dig']) landmarks = set(['RPA', 'NAS', 'LPA']) == set(list(coords.keys())) # XXX: to be improved to allow rescaling if landmarks are present # mne-python automatically converts unknown coord frame to head if coord_frame_int == FIFF.FIFFV_COORD_HEAD and landmarks: # Now write the data _electrodes_tsv(raw, electrodes_path, datatype, overwrite, verbose) _coordsystem_json(raw=raw, unit='m', hpi_coord_system='n/a', sensor_coord_system='CapTrak', fname=coordsystem_path, datatype=datatype, overwrite=overwrite, verbose=verbose) else: warn("Skipping EEG electrodes.tsv... " "Setting montage not possible if anatomical " "landmarks (NAS, LPA, RPA) are missing, " "and coord_frame is not 'head'.")
def read_raw_bids(bids_path, extra_params=None, verbose=True): """Read BIDS compatible data. Will attempt to read associated events.tsv and channels.tsv files to populate the returned raw object with raw.annotations and raw.info['bads']. Parameters ---------- bids_path : mne_bids.BIDSPath The file to read. The :class:`mne_bids.BIDSPath` instance passed here **must** have the ``.root`` attribute set. The ``.datatype`` attribute **may** be set. If ``.datatype`` is not set and only one data type (e.g., only EEG or MEG data) is present in the dataset, it will be selected automatically. extra_params : None | dict Extra parameters to be passed to MNE read_raw_* functions. If a dict, for example: ``extra_params=dict(allow_maxshield=True)``. Note that the ``exclude`` parameter, which is supported by some MNE-Python readers, is not supported; instead, you need to subset your channels **after** reading. verbose : bool The verbosity level. Returns ------- raw : mne.io.Raw The data as MNE-Python Raw object. Raises ------ RuntimeError If multiple recording data types are present in the dataset, but ``datatype=None``. RuntimeError If more than one data files exist for the specified recording. RuntimeError If no data file in a supported format can be located. ValueError If the specified ``datatype`` cannot be found in the dataset. """ if not isinstance(bids_path, BIDSPath): raise RuntimeError('"bids_path" must be a BIDSPath object. Please ' 'instantiate using mne_bids.BIDSPath().') bids_path = bids_path.copy() sub = bids_path.subject ses = bids_path.session bids_root = bids_path.root datatype = bids_path.datatype suffix = bids_path.suffix # check root available if bids_root is None: raise ValueError('The root of the "bids_path" must be set. ' 'Please use `bids_path.update(root="<root>")` ' 'to set the root of the BIDS folder to read.') # infer the datatype and suffix if they are not present in the BIDSPath if datatype is None: datatype = _infer_datatype(root=bids_root, sub=sub, ses=ses) bids_path.update(datatype=datatype) if suffix is None: bids_path.update(suffix=datatype) data_dir = bids_path.directory bids_fname = bids_path.fpath.name if op.splitext(bids_fname)[1] == '.pdf': bids_raw_folder = op.join(data_dir, f'{bids_path.basename}') bids_fpath = glob.glob(op.join(bids_raw_folder, 'c,rf*'))[0] config = op.join(bids_raw_folder, 'config') else: bids_fpath = op.join(data_dir, bids_fname) config = None if extra_params is None: extra_params = dict() elif 'exclude' in extra_params: del extra_params['exclude'] logger.info('"exclude" parameter is not supported by read_raw_bids') raw = _read_raw(bids_fpath, electrode=None, hsp=None, hpi=None, config=config, verbose=None, **extra_params) # Try to find an associated events.tsv to get information about the # events in the recorded data events_fname = _find_matching_sidecar(bids_path, suffix='events', extension='.tsv', on_error='warn') if events_fname is not None: raw = _handle_events_reading(events_fname, raw) # Try to find an associated channels.tsv to get information about the # status and type of present channels channels_fname = _find_matching_sidecar(bids_path, suffix='channels', extension='.tsv', on_error='warn') if channels_fname is not None: raw = _handle_channels_reading(channels_fname, raw) # Try to find an associated electrodes.tsv and coordsystem.json # to get information about the status and type of present channels on_error = 'warn' if suffix == 'ieeg' else 'ignore' electrodes_fname = _find_matching_sidecar(bids_path, suffix='electrodes', extension='.tsv', on_error=on_error) coordsystem_fname = _find_matching_sidecar(bids_path, suffix='coordsystem', extension='.json', on_error=on_error) if electrodes_fname is not None: if coordsystem_fname is None: raise RuntimeError(f"BIDS mandates that the coordsystem.json " f"should exist if electrodes.tsv does. " f"Please create coordsystem.json for" f"{bids_path.basename}") if datatype in ['meg', 'eeg', 'ieeg']: raw = _read_dig_bids(electrodes_fname, coordsystem_fname, raw, datatype, verbose) # Try to find an associated sidecar .json to get information about the # recording snapshot sidecar_fname = _find_matching_sidecar(bids_path, suffix=datatype, extension='.json', on_error='warn') if sidecar_fname is not None: raw = _handle_info_reading(sidecar_fname, raw, verbose=verbose) # read in associated scans filename scans_fname = BIDSPath(subject=bids_path.subject, session=bids_path.session, suffix='scans', extension='.tsv', root=bids_path.root).fpath if scans_fname.exists(): raw = _handle_scans_reading(scans_fname, raw, bids_path, verbose=verbose) # read in associated subject info from participants.tsv participants_tsv_fpath = op.join(bids_root, 'participants.tsv') subject = f"sub-{bids_path.subject}" if op.exists(participants_tsv_fpath): raw = _handle_participants_reading(participants_tsv_fpath, raw, subject, verbose=verbose) else: warn("Participants file not found for {}... Not reading " "in any particpants.tsv data.".format(bids_fname)) return raw
def _summarize_channels_tsv(root, scans_fpaths, verbose=True): """Summarize channels.tsv data in BIDS root directory. Currently, summarizes all REQUIRED components of channels data, and some RECOMMENDED and OPTIONAL components. Parameters ---------- root : str | pathlib.Path The path of the root of the BIDS compatible folder. scans_fpaths : list A list of all *_scans.tsv files in ``root``. The summary will occur for all scans listed in the *_scans.tsv files. verbose : bool Returns ------- template_dict : dict A dictionary of values for various template strings. """ root = Path(root) # keep track of channel type, status ch_status_count = {'bad': [], 'good': []} ch_count = [] # loop through each scan for scan_fpath in scans_fpaths: # load in the scans.tsv file # and read metadata for each scan scans_tsv = _from_tsv(scan_fpath) scans = scans_tsv['filename'] for scan in scans: # summarize metadata of recordings bids_path, _ = _parse_ext(scan) datatype = op.dirname(scan) if datatype not in ['meg', 'eeg', 'ieeg']: continue # convert to BIDS Path params = get_entities_from_fname(bids_path) bids_path = BIDSPath(root=root, **params) # XXX: improve to allow emptyroom if bids_path.subject == 'emptyroom': continue channels_fname = _find_matching_sidecar(bids_path=bids_path, suffix='channels', extension='.tsv') # summarize channels.tsv channels_tsv = _from_tsv(channels_fname) for status in ch_status_count.keys(): ch_status = [ ch for ch in channels_tsv['status'] if ch == status ] ch_status_count[status].append(len(ch_status)) ch_count.append(len(channels_tsv['name'])) # create summary template strings for status template_dict = { 'mean_chs': np.mean(ch_count), 'std_chs': np.std(ch_count), 'mean_good_chs': np.mean(ch_status_count['good']), 'std_good_chs': np.std(ch_status_count['good']), 'mean_bad_chs': np.mean(ch_status_count['bad']), 'std_bad_chs': np.std(ch_status_count['bad']), } for key, val in template_dict.items(): template_dict[key] = round(val, 2) return template_dict
def _summarize_sidecar_json(root, scans_fpaths, verbose=True): """Summarize scans in BIDS root directory. Parameters ---------- root : str | pathlib.Path The path of the root of the BIDS compatible folder. scans_fpaths : list A list of all *_scans.tsv files in ``root``. The summary will occur for all scans listed in the *_scans.tsv files. verbose : bool Set verbose output to true or false. Returns ------- template_dict : dict A dictionary of values for various template strings. """ n_scans = 0 powerlinefreqs, sfreqs = set(), set() manufacturers = set() length_recordings = [] # loop through each scan for scan_fpath in scans_fpaths: # load in the scans.tsv file # and read metadata for each scan scans_tsv = _from_tsv(scan_fpath) scans = scans_tsv['filename'] for scan in scans: # summarize metadata of recordings bids_path, ext = _parse_ext(scan) datatype = op.dirname(scan) if datatype not in ALLOWED_DATATYPES: continue n_scans += 1 # convert to BIDS Path params = get_entities_from_fname(bids_path) bids_path = BIDSPath(root=root, **params) # XXX: improve to allow emptyroom if bids_path.subject == 'emptyroom': continue sidecar_fname = _find_matching_sidecar(bids_path=bids_path, suffix=datatype, extension='.json') with open(sidecar_fname, 'r', encoding='utf-8-sig') as fin: sidecar_json = json.load(fin) # aggregate metadata from each scan # REQUIRED kwargs sfreq = sidecar_json['SamplingFrequency'] powerlinefreq = str(sidecar_json['PowerLineFrequency']) software_filters = sidecar_json.get('SoftwareFilters') if not software_filters: software_filters = 'n/a' # RECOMMENDED kwargs manufacturer = sidecar_json.get('Manufacturer', 'n/a') record_duration = sidecar_json.get('RecordingDuration', 'n/a') sfreqs.add(str(np.round(sfreq, 2))) powerlinefreqs.add(str(powerlinefreq)) if manufacturer != 'n/a': manufacturers.add(manufacturer) length_recordings.append(record_duration) # XXX: length summary is only allowed, if no 'n/a' was found if any([dur == 'n/a' for dur in length_recordings]): length_recordings = None template_dict = { 'n_scans': n_scans, 'manufacturer': list(manufacturers), 'sfreq': sfreqs, 'powerlinefreq': powerlinefreqs, 'software_filters': software_filters, 'length_recordings': length_recordings, } return template_dict
def convert_dataset_to_bids(root, bids_identifiers, montage="standard_1020", ext=".edf"): """ Convert a sample dataset to bids format. You will need to modify this function depending on your sourcedata structure. This current formula assumes the source data files are named as follows: <sub>_<run>.edf Parameters ---------- root: Path Absolute path to the bids root directory bids_identifiers: dict Dictionary of bids identifiers you want to apply to your dataset globally montage: str The name of the montage to apply ext: str The extension of the files in the sourcedata directory Returns ------- """ source_dir = root / "sourcedata" # Find all the potential files in the sourcedata directory source_fpaths = [ fpath for fpath in source_dir.glob(f"*{ext}") if fpath.is_file() ] # Grab the global bids identifiers session = bids_identifiers.get("session") task = bids_identifiers.get("task") acquisition = bids_identifiers.get("acquisition") datatype = bids_identifiers.get("datatype") for ind, fpath in enumerate(source_fpaths): # Assumes sourcedata filename structure as <subject>_<run><ext> subject, run = fpath.name.split("_") run = run.replace(ext, "") # Set up the bids path for this individual source file bids_path = BIDSPath( root=root, subject=subject, session=session, acquisition=acquisition, task=task, run=run, datatype=datatype, ) # Convert the file to bids bids_path = write_epitrack_bids(source_path=fpath, bids_path=bids_path, overwrite=True, montage=montage, verbose=False) # It is often useful to be able to backtrack the bids file to the sourcefile. # This will add an additional column in the scans.tsv file to allow this # back-tracking append_original_fname_to_scans( Path(fpath).name, root, bids_path.basename)
def _write_dig_bids(bids_path, raw, montage=None, acpc_aligned=False, overwrite=False): """Write BIDS formatted DigMontage from Raw instance. Handles coordsystem.json and electrodes.tsv writing from DigMontage. Parameters ---------- bids_path : BIDSPath Path in the BIDS dataset to save the ``electrodes.tsv`` and ``coordsystem.json`` file for. ``datatype`` attribute must be ``eeg``, or ``ieeg``. For ``meg`` data, ``electrodes.tsv`` are not saved. raw : mne.io.Raw The data as MNE-Python Raw object. montage : mne.channels.DigMontage | None The montage to use rather than the one in ``raw`` if it must be transformed from the "head" coordinate frame. acpc_aligned : bool Whether "mri" space is aligned to ACPC. overwrite : bool Whether to overwrite the existing file. Defaults to False. """ # write electrodes data for iEEG and EEG unit = "m" # defaults to meters if montage is None: montage = raw.get_montage() else: # assign montage to raw but supress any coordinate transforms montage = montage.copy() # don't modify original montage_coord_frame = montage.get_positions()['coord_frame'] fids = [ d for d in montage.dig # save to add back if d['kind'] == FIFF.FIFFV_POINT_CARDINAL ] montage.remove_fiducials() # prevent coordinate transform with warnings.catch_warnings(): warnings.filterwarnings(action='ignore', category=RuntimeWarning, message='.*nasion not found', module='mne') raw.set_montage(montage) for ch in raw.info['chs']: ch['coord_frame'] = MNE_STR_TO_FRAME[montage_coord_frame] for d in raw.info['dig']: d['coord_frame'] = MNE_STR_TO_FRAME[montage_coord_frame] with raw.info._unlock(): # add back fiducials raw.info['dig'] = fids + raw.info['dig'] # get the accepted mne-python coordinate frames coord_frame_int = int(montage.dig[0]['coord_frame']) mne_coord_frame = MNE_FRAME_TO_STR.get(coord_frame_int, None) coord_frame = MNE_TO_BIDS_FRAMES.get(mne_coord_frame, None) if coord_frame == 'CapTrak' and bids_path.datatype in ('eeg', 'nirs'): pos = raw.get_montage().get_positions() if any([pos[fid_key] is None for fid_key in ('nasion', 'lpa', 'rpa')]): raise RuntimeError("'head' coordinate frame must contain nasion " "and left and right pre-auricular point " "landmarks") if bids_path.datatype == 'ieeg' and bids_path.space in (None, 'ACPC') and \ mne_coord_frame == 'ras': if not acpc_aligned: raise RuntimeError( '`acpc_aligned` is False, if your T1 is not aligned ' 'to ACPC and the coordinates are in fact in ACPC ' 'space there will be no way to relate the coordinates ' 'to the T1. If the T1 is ACPC-aligned, use ' '`acpc_aligned=True`') coord_frame = 'ACPC' if bids_path.space is None: # no space, use MNE coord frame if coord_frame is None: # if no MNE coord frame, skip warn("Coordinate frame could not be inferred from the raw object " "and the BIDSPath.space was none, skipping the writing of " "channel positions") return else: # either a space and an MNE coord frame or just a space if coord_frame is None: # just a space, use that coord_frame = bids_path.space else: # space and raw have coordinate frame, check match if bids_path.space != coord_frame and not ( coord_frame == 'fsaverage' and bids_path.space == 'MNI305'): # fsaverage == MNI305 raise ValueError('Coordinates in the raw object or montage ' f'are in the {coord_frame} coordinate ' 'frame but BIDSPath.space is ' f'{bids_path.space}') # create electrodes/coordsystem files using a subset of entities # that are specified for these files in the specification coord_file_entities = { 'root': bids_path.root, 'datatype': bids_path.datatype, 'subject': bids_path.subject, 'session': bids_path.session, 'acquisition': bids_path.acquisition, 'space': None if bids_path.datatype == 'nirs' else coord_frame } channels_suffix = \ 'optodes' if bids_path.datatype == 'nirs' else 'electrodes' _channels_fun = _write_optodes_tsv if bids_path.datatype == 'nirs' else \ _write_electrodes_tsv channels_path = BIDSPath(**coord_file_entities, suffix=channels_suffix, extension='.tsv') coordsystem_path = BIDSPath(**coord_file_entities, suffix='coordsystem', extension='.json') # Now write the data to the elec coords and the coordsystem _channels_fun(raw, channels_path, bids_path.datatype, overwrite) _write_coordsystem_json(raw=raw, unit=unit, hpi_coord_system='n/a', sensor_coord_system=coord_frame, fname=coordsystem_path, datatype=bids_path.datatype, overwrite=overwrite)
def _write_dig_bids(bids_path, raw, montage=None, acpc_aligned=False, overwrite=False): """Write BIDS formatted DigMontage from Raw instance. Handles coordinatesystem.json and electrodes.tsv writing from DigMontage. Parameters ---------- bids_path : mne_bids.BIDSPath Path in the BIDS dataset to save the ``electrodes.tsv`` and ``coordsystem.json`` file for. ``datatype`` attribute must be ``eeg``, or ``ieeg``. For ``meg`` data, ``electrodes.tsv`` are not saved. raw : mne.io.Raw The data as MNE-Python Raw object. montage : mne.channels.DigMontage | None The montage to use rather than the one in ``raw`` if it must be transformed from the "head" coordinate frame. acpc_aligned : bool Whether "mri" space is aligned to ACPC. overwrite : bool Whether to overwrite the existing file. Defaults to False. """ # write electrodes data for iEEG and EEG unit = "m" # defaults to meters if montage is None: montage = raw.get_montage() else: # prevent transformation back to "head", only should be used # in this specific circumstance if bids_path.datatype == 'ieeg': montage.remove_fiducials() raw.set_montage(montage) # get coordinate frame from digMontage digpoint = montage.dig[0] if any(digpoint['coord_frame'] != _digpoint['coord_frame'] for _digpoint in montage.dig): warn("Not all digpoints have the same coordinate frame. " "Skipping electrodes.tsv writing...") return # get the accepted mne-python coordinate frames coord_frame_int = int(digpoint['coord_frame']) mne_coord_frame = MNE_FRAME_TO_STR.get(coord_frame_int, None) coord_frame = MNE_TO_BIDS_FRAMES.get(mne_coord_frame, None) if bids_path.datatype == 'ieeg' and mne_coord_frame == 'mri': if acpc_aligned: coord_frame = 'ACPC' else: raise RuntimeError( '`acpc_aligned` is False, if your T1 is not aligned ' 'to ACPC and the coordinates are in fact in ACPC ' 'space there will be no way to relate the coordinates ' 'to the T1. If the T1 is ACPC-aligned, use ' '`acpc_aligned=True`') # create electrodes/coordsystem files using a subset of entities # that are specified for these files in the specification coord_file_entities = { 'root': bids_path.root, 'datatype': bids_path.datatype, 'subject': bids_path.subject, 'session': bids_path.session, 'acquisition': bids_path.acquisition, 'space': bids_path.space } datatype = bids_path.datatype electrodes_path = BIDSPath(**coord_file_entities, suffix='electrodes', extension='.tsv') coordsystem_path = BIDSPath(**coord_file_entities, suffix='coordsystem', extension='.json') logger.info(f'Writing electrodes file to... {electrodes_path}') logger.info(f'Writing coordsytem file to... {coordsystem_path}') if datatype == 'ieeg': if coord_frame is not None: # XXX: To improve when mne-python allows coord_frame='unknown' # coordinate frame is either coordsystem_path.update(space=coord_frame) electrodes_path.update(space=coord_frame) # Now write the data to the elec coords and the coordsystem _write_electrodes_tsv(raw, electrodes_path, datatype, overwrite) _write_coordsystem_json(raw=raw, unit=unit, hpi_coord_system='n/a', sensor_coord_system=(coord_frame, mne_coord_frame), fname=coordsystem_path, datatype=datatype, overwrite=overwrite) else: # default coordinate frame to mri if not available warn("Coordinate frame of iEEG coords missing/unknown " "for {}. Skipping reading " "in of montage...".format(electrodes_path)) elif datatype == 'eeg': # We only write EEG electrodes.tsv and coordsystem.json # if we have LPA, RPA, and NAS available to rescale to a known # coordinate system frame coords = _extract_landmarks(raw.info['dig']) landmarks = set(['RPA', 'NAS', 'LPA']) == set(list(coords.keys())) # XXX: to be improved to allow rescaling if landmarks are present # mne-python automatically converts unknown coord frame to head if coord_frame_int == FIFF.FIFFV_COORD_HEAD and landmarks: # Now write the data _write_electrodes_tsv(raw, electrodes_path, datatype, overwrite) _write_coordsystem_json(raw=raw, unit='m', hpi_coord_system='n/a', sensor_coord_system='CapTrak', fname=coordsystem_path, datatype=datatype, overwrite=overwrite) else: warn("Skipping EEG electrodes.tsv... " "Setting montage not possible if anatomical " "landmarks (NAS, LPA, RPA) are missing, " "and coord_frame is not 'head'.")
def read_raw_bids(bids_path, extra_params=None, verbose=None): """Read BIDS compatible data. Will attempt to read associated events.tsv and channels.tsv files to populate the returned raw object with raw.annotations and raw.info['bads']. Parameters ---------- bids_path : BIDSPath The file to read. The :class:`mne_bids.BIDSPath` instance passed here **must** have the ``.root`` attribute set. The ``.datatype`` attribute **may** be set. If ``.datatype`` is not set and only one data type (e.g., only EEG or MEG data) is present in the dataset, it will be selected automatically. .. note:: If ``bids_path`` points to a symbolic link of a ``.fif`` file without a ``split`` entity, the link will be resolved before reading. extra_params : None | dict Extra parameters to be passed to MNE read_raw_* functions. Note that the ``exclude`` parameter, which is supported by some MNE-Python readers, is not supported; instead, you need to subset your channels **after** reading. %(verbose)s Returns ------- raw : mne.io.Raw The data as MNE-Python Raw object. Raises ------ RuntimeError If multiple recording data types are present in the dataset, but ``datatype=None``. RuntimeError If more than one data files exist for the specified recording. RuntimeError If no data file in a supported format can be located. ValueError If the specified ``datatype`` cannot be found in the dataset. """ if not isinstance(bids_path, BIDSPath): raise RuntimeError('"bids_path" must be a BIDSPath object. Please ' 'instantiate using mne_bids.BIDSPath().') bids_path = bids_path.copy() sub = bids_path.subject ses = bids_path.session bids_root = bids_path.root datatype = bids_path.datatype suffix = bids_path.suffix # check root available if bids_root is None: raise ValueError('The root of the "bids_path" must be set. ' 'Please use `bids_path.update(root="<root>")` ' 'to set the root of the BIDS folder to read.') # infer the datatype and suffix if they are not present in the BIDSPath if datatype is None: datatype = _infer_datatype(root=bids_root, sub=sub, ses=ses) bids_path.update(datatype=datatype) if suffix is None: bids_path.update(suffix=datatype) if bids_path.fpath.suffix == '.pdf': bids_raw_folder = bids_path.directory / f'{bids_path.basename}' raw_path = list(bids_raw_folder.glob('c,rf*'))[0] config_path = bids_raw_folder / 'config' else: raw_path = bids_path.fpath # Resolve for FIFF files if (raw_path.suffix == '.fif' and bids_path.split is None and raw_path.is_symlink()): target_path = raw_path.resolve() logger.info(f'Resolving symbolic link: ' f'{raw_path} -> {target_path}') raw_path = target_path config_path = None # Special-handle EDF filenames: we accept upper- and lower-case extensions if raw_path.suffix.lower() == '.edf': for extension in ('.edf', '.EDF'): candidate_path = raw_path.with_suffix(extension) if candidate_path.exists(): raw_path = candidate_path break if not raw_path.exists(): raise FileNotFoundError(f'File does not exist: {raw_path}') if config_path is not None and not config_path.exists(): raise FileNotFoundError(f'config directory not found: {config_path}') if extra_params is None: extra_params = dict() elif 'exclude' in extra_params: del extra_params['exclude'] logger.info('"exclude" parameter is not supported by read_raw_bids') if raw_path.suffix == '.fif' and 'allow_maxshield' not in extra_params: extra_params['allow_maxshield'] = True raw = _read_raw(raw_path, electrode=None, hsp=None, hpi=None, config_path=config_path, **extra_params) # Try to find an associated events.tsv to get information about the # events in the recorded data events_fname = _find_matching_sidecar(bids_path, suffix='events', extension='.tsv', on_error='warn') if events_fname is not None: raw = _handle_events_reading(events_fname, raw) # Try to find an associated channels.tsv to get information about the # status and type of present channels channels_fname = _find_matching_sidecar(bids_path, suffix='channels', extension='.tsv', on_error='warn') if channels_fname is not None: raw = _handle_channels_reading(channels_fname, raw) # Try to find an associated electrodes.tsv and coordsystem.json # to get information about the status and type of present channels on_error = 'warn' if suffix == 'ieeg' else 'ignore' electrodes_fname = _find_matching_sidecar(bids_path, suffix='electrodes', extension='.tsv', on_error=on_error) coordsystem_fname = _find_matching_sidecar(bids_path, suffix='coordsystem', extension='.json', on_error=on_error) if electrodes_fname is not None: if coordsystem_fname is None: raise RuntimeError(f"BIDS mandates that the coordsystem.json " f"should exist if electrodes.tsv does. " f"Please create coordsystem.json for" f"{bids_path.basename}") if datatype in ['meg', 'eeg', 'ieeg']: _read_dig_bids(electrodes_fname, coordsystem_fname, raw=raw, datatype=datatype) # Try to find an associated sidecar .json to get information about the # recording snapshot sidecar_fname = _find_matching_sidecar(bids_path, suffix=datatype, extension='.json', on_error='warn') if sidecar_fname is not None: raw = _handle_info_reading(sidecar_fname, raw) # read in associated scans filename scans_fname = BIDSPath(subject=bids_path.subject, session=bids_path.session, suffix='scans', extension='.tsv', root=bids_path.root).fpath if scans_fname.exists(): raw = _handle_scans_reading(scans_fname, raw, bids_path) # read in associated subject info from participants.tsv participants_tsv_path = bids_root / 'participants.tsv' subject = f"sub-{bids_path.subject}" if op.exists(participants_tsv_path): raw = _handle_participants_reading( participants_fname=participants_tsv_path, raw=raw, subject=subject) else: warn(f"participants.tsv file not found for {raw_path}") assert raw.annotations.orig_time == raw.info['meas_date'] return raw