def find_result_path(self): """ Identifies the directory path pointing to where results stored Following BIDS requirements, we only have either the subject folder or both subject and session. Parameters ---------- none Returns ------- result_path: str location of results files within BIDS folder """ params = _parse_bids_filename(self.unique_name, verbose=False) if params["ses"] is None: result_path = os.path.join(self.root_path, "derivatives", "automagic", f"sub-{params['sub']}") else: result_path = os.path.join( self.root_path, "derivatives", "automagic", f"sub-{params['sub']}", f"ses-{params['ses']}", ) return result_path
def test_parse_bids_filename(fname): """Test parsing entities from a bids filename.""" params = _parse_bids_filename(fname, verbose=False) assert params['sub'] == '01' assert params['ses'] == '02' assert params['run'] == '3' assert params['task'] == 'test' assert list(params.keys()) == ['sub', 'ses', 'task', 'acq', 'run', 'proc', 'space', 'recording', 'kind']
def get_matched_empty_room(bids_fname, bids_root): """Get matching empty room file. Parameters ---------- bids_fname : str The filename for which to find the matching empty room file. bids_root : str Path to the BIDS root folder. Returns ------- er_fname : str | None. The filename corresponding to the empty room. Returns None if no file found. """ bids_fname = op.basename(bids_fname) _, ext = _parse_ext(bids_fname) raw = read_raw_bids(bids_fname, bids_root) if raw.info['meas_date'] is None: raise ValueError('Measurement date not available. Cannot get matching' ' empty room file') ref_date = raw.info['meas_date'] if not isinstance(ref_date, datetime): # for MNE < v0.20 ref_date = datetime.fromtimestamp(raw.info['meas_date'][0]) search_path = make_bids_folders(bids_root=bids_root, subject='emptyroom', session='**', make_dir=False) search_path = op.join(search_path, '**', '**%s' % ext) er_fnames = glob.glob(search_path) best_er_fname = None min_seconds = np.inf for er_fname in er_fnames: params = _parse_bids_filename(er_fname, verbose=False) dt = datetime.strptime(params['ses'], '%Y%m%d') dt = dt.replace(tzinfo=ref_date.tzinfo) delta_t = dt - ref_date if abs(delta_t.total_seconds()) < min_seconds: min_seconds = abs(delta_t.total_seconds()) best_er_fname = er_fname return best_er_fname
def convert_img_to_bids(image_input, bids_root, bids_fname, verbose=True): """Run Bids Conversion script to be updated. Performs BIDS conversion for Ct/T1/DTI/fMRI data. TODO: demo for DTI/FMRI """ if verbose: print(f"bids_root is {bids_root}") print(f"Reading in image files from: {image_input}") # create temporary filepath to store the nifti file with tempfile.TemporaryDirectory() as tmpdir: output_fpath = Path(tmpdir, "tmp.nii").as_posix() if len([x for x in Path(image_input).glob("*.dcm")]) > 0: print("Converting dicom -> Nifti...") # try to run mrconvert and reorient to `LAS` direction # try: image_input = _convert_dicom_to_nifti(image_input, output_fpath) # except Exception as e: # "mrconvert {params.CT_FOLDER} {output.CT_bids_fname};" else: print( "Passed NIFTI image, so skipping mrconvert from dicom -> nifti..." ) image_input = str(image_input) print(image_input) # determine the BIDS identifiers params = _parse_bids_filename(bids_fname, verbose=True) subject = params["sub"] session = params["ses"] print("\n\nWriting now to BIDS...") # write to BIDS anat_dir = write_anat( bids_root, subject, t1w=image_input, session=session, overwrite=True, verbose=True, )
def __init__(self, bids_root, bids_fname: str, verbose: bool = True): super(BidsRun, self).__init__(bids_root=bids_root) # ensures just base path self.bids_fname = os.path.basename(bids_fname) # what is the modality -- meg, eeg, ieeg to read self.bids_basename = "_".join(bids_fname.split("_")[:-1]) self.kind = bids_fname.split("_")[-1].split(".")[0] # extract BIDS parameters from the bids filename to be loaded/modified # gets: subjectid, sessionid, acquisition type, task name, runid, file extension params = _parse_bids_filename(self.bids_fname, verbose=verbose) self.subject_id, self.session_id = params["sub"], params["ses"] self.acquisition, self.task, self.run = ( params["acq"], params["task"], params["run"], ) _, self.ext = _parse_ext(self.bids_fname) self.result_fpath = None # instantiate a loader/writer self.loader = BidsLoader( bids_root=self.bids_root, bids_basename=self.bids_basename, kind=self.kind, datatype=self.ext, ) self.writer = BidsWriter( bids_root=self.bids_root, bids_basename=self.bids_basename, kind=self.kind, datatype=self.ext, ) if not os.path.exists(self.loader.datafile_fpath): raise RuntimeError( f"Bids dataset run does not exist at {self.loader.datafile_fpath}. " f"Please first create the dataset in BIDS format." )
def __init__( self, bids_root, bids_basename, kind=None, datatype="fif", verbose: bool = True ): self.bids_root = bids_root self.bids_basename = bids_basename # extract BIDS parameters from the bids filename to be loaded/modified # gets: subjectid, sessionid, acquisition type, task name, run, file extension params = _parse_bids_filename(self.bids_basename, verbose=verbose) self.subject_id, self.session_id = params["sub"], params["ses"] self.acquisition, self.task, self.run = ( params["acq"], params["task"], params["run"], ) self.ext = datatype.strip(".") if kind: self.kind = kind else: self.kind = self.acquisition if "meg" in self.kind: self.kind = "meg" elif "ieeg" in self.kind or "ecog" in self.kind or "seeg" in self.kind: self.kind = "ieeg" elif "eeg" in self.kind: self.kind = "eeg" self.subjdir = os.path.join( bids_root, make_bids_basename(subject=self.subject_id) ) self.sessiondir = os.path.join( self.subjdir, make_bids_basename(session=self.session_id) ) # 'ses-' + self.session_id) self.runs_datadir = os.path.join(self.sessiondir, self.kind) SUPPORTED_DATATYPES = ["edf", "fif", "nii", "nii.gz", "mgz"] if self.ext.strip(".") not in SUPPORTED_DATATYPES: raise ValueError( f"Supported data file types are: {SUPPORTED_DATATYPES}. " f"You passed {self.ext}" )
def load_data(bids_basename): # read_raw_bids automatically # - populates bad channels using the BIDS channels.tsv # - sets channels types according to BIDS channels.tsv `type` column # - sets raw.annotations using the BIDS events.tsv params = _parse_bids_filename(bids_basename, verbose=False) subject = params['sub'] session = params['ses'] extra_params = dict() if config.allow_maxshield: extra_params['allow_maxshield'] = config.allow_maxshield raw = read_raw_bids(bids_basename=bids_basename, bids_root=config.bids_root, extra_params=extra_params, kind=config.get_kind()) # XXX hack to deal with dates that fif files cannot handle if config.daysback is not None: raw.anonymize(daysback=config.daysback) if subject != 'emptyroom': # Crop the data. if config.crop is not None: raw.crop(*config.crop) # Rename events. if config.rename_events: rename_events(raw=raw, subject=subject, session=session) raw.load_data() if hasattr(raw, 'fix_mag_coil_types'): raw.fix_mag_coil_types() return raw
def setup_fragility( subject_id: str, acquisition_id: str, run_id: str, kind: str, bids_root: Union[str, Path], reference: str, deriv_path: Union[str, Path] = None, overwrite: bool = False, ): """ Setup fragility parameters and files to run analysis. Parameters ---------- subject_id : acquisition_id: run_id : bids_root : reference : deriv_path : overwrite : bool Returns ------- deriv_path: """ verbose = False # initialize a pybids layout # layout = bids.BIDSLayout(bids_root) # subj_runs = layout.get_runs(subject=subject_id) # get all the subject runs subj_dir = Path(bids_root / f"sub-{subject_id}") _fnames = natsorted( [ x.name for x in subj_dir.rglob(f"*run-{run_id}_{kind}.vhdr") if acquisition_id in x.name ] ) # run analysis for bids_fname in _fnames: params = _parse_bids_filename(bids_fname, verbose=verbose) subject = params["sub"] session = params["ses"] task = params["task"] acquisition = params["acq"] run_id = params["run"] output_fname = make_bids_basename( subject, session, task, acquisition, run_id, processing="fragility", suffix=f"{kind}.npz", ) deriv_path = main( bids_fname, bids_root, output_fname, reference, deriv_path=deriv_path, verbose=verbose, overwrite=overwrite, ) return deriv_path
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 if the original file format is BIDS-supported for that modality. Otherwise, this function will convert to a BIDS-supported file format while warning the user. For EEG and iEEG data, conversion will be to BrainVision format, for MEG conversion will be to FIF. 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 the 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. Returns ------- output_path : str The path of the root of the BIDS compatible folder. 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 check_version('mne', '0.17'): raise ValueError('Your version of MNE is too old. ' 'Please update to 0.17 or newer.') 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) # check whether the info provided indicates that the data is emptyroom # data emptyroom = False if subject_id == 'emptyroom' and task == 'noise': emptyroom = True # check the session date provided is consistent with the value in raw meas_date = raw.info.get('meas_date', None) if meas_date is not None: er_date = datetime.fromtimestamp( raw.info['meas_date'][0]).strftime('%Y%m%d') if er_date != session_id: raise ValueError("Date provided for session doesn't match " "session date.") 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_tsv_fname = make_bids_basename(prefix=output_path, suffix='participants.tsv') participants_json_fname = make_bids_basename(prefix=output_path, suffix='participants.json') 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', '.con', '.sqd']: 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') # save all meta data _participants_tsv(raw, subject_id, participants_tsv_fname, overwrite, verbose) _participants_json(participants_json_fname, True, verbose) _scans_tsv(raw, op.join(kind, bids_fname), scans_fname, overwrite, verbose) # TODO: Implement coordystem.json and electrodes.tsv for EEG and iEEG if kind == 'meg' and not emptyroom: _coordsystem_json(raw, unit, orient, manufacturer, coordsystem_fname, overwrite, verbose) events, event_id = _read_events(events_data, event_id, raw, ext) if events is not None and len(events) > 0 and not emptyroom: _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 = op.join(data_path, bids_fname) if os.path.exists(bids_fname) and not overwrite: raise FileExistsError('"%s" already exists. Please set ' # noqa: F821 'overwrite to True.' % bids_fname) _mkdir_p(os.path.dirname(bids_fname)) if verbose: print('Copying data files to %s' % op.splitext(bids_fname)[0]) convert = ext not in ALLOWED_EXTENSIONS[kind] # Copy the imaging data files if convert: if kind == 'meg': raise ValueError('Got file extension %s for MEG data, ' + 'expected one of %s' % ALLOWED_EXTENSIONS['meg']) if verbose: warn('Converting data files to BrainVision format') if not check_version('pybv', '0.2'): raise ImportError('pybv >=0.2.0 is required for converting ' + '%s files to Brainvision format' % ext) from pybv import write_brainvision events, event_id = events_from_annotations(raw) write_brainvision(raw.get_data(), raw.info['sfreq'], raw.ch_names, op.splitext(op.basename(bids_fname))[0], op.dirname(bids_fname), events[:, [0, 2]], resolution=1e-6) elif ext == '.fif': n_rawfiles = len(raw.filenames) if n_rawfiles > 1: split_naming = 'bids' raw.save(bids_fname, split_naming=split_naming, overwrite=True) else: # This ensures that single FIF files do not have the part param raw.save(bids_fname, split_naming='neuromag', overwrite=True) # CTF data is saved and renamed in a directory elif ext == '.ds': copyfile_ctf(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) elif ext == '.pdf': copyfile_bti(raw_orig, op.join(data_path, bids_raw_folder)) 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'] 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_fname = make_bids_basename( subject=subject_id, session=session_id, task=task, run=run, acquisition=key, suffix='markers%s' % marker_ext, prefix=data_path) sh.copyfile(value, marker_fname) return output_path
def _setup_main_eeg(): """ We copy some explicit example from MNE-BIDS and modify small details. Specifically, we will follow these steps: 1. Download repository, and use the data in example directory: data/ bids_layout/ sourcedata/ derivatives/ sub-XXX/ sub-XXY/ ... 2. Load the source raw data, extract information, preprocess certain things and save in a new BIDS directory 3. Check the result and compare it with the standard """ ############################################################################### # Step 1: Prepare the data # ------------------------- # # First, we need some data to work with. We will use some sample simulated scalp and # iEEG data. For each subject, there are "seizure" events. For the present example, we will # show how to format the data for two modalities to comply with the Brain Imaging Data Structure # (`BIDS <http://bids.neuroimaging.io/>`_). # # The data are in the `European Data Format <https://www.edfplus.info/>`_ # '.edf', which is good for us because next to the BrainVision format, EDF is # one of the recommended file formats for EEG BIDS. However, apart from the # data format, we need to build a directory structure and supply meta data # files to properly *bidsify* this data. # # Conveniently, there is already a data loading function available with # MNE-Python: DATADIR = os.getcwd() bids_root = os.path.join(DATADIR, "./data/bids_layout/") RUN_IEEG = False # either run scalp, or iEEG line_freq = ( 60 # user should set the line frequency, since MNE-BIDS defaults to 50 Hz ) test_subjectid = "0001" test_sessionid = "seizure" test_task = "monitor" authors = ["Adam Li", "Patrick Myers"] if RUN_IEEG: edf_fpaths = [ os.path.join(bids_root, "sourcedata", "ieeg_ecog_test.edf") ] modality = "ecog" else: edf_fpath1 = os.path.join(bids_root, "sourcedata", "scalp_test.edf") edf_fpath2 = os.path.join(bids_root, "sourcedata", "scalp_test_2.edf") edf_fpaths = [edf_fpath1, edf_fpath2] modality = "eeg" ############################################################################### # Let's see whether the data has been downloaded using a quick visualization # of the directory tree. data_dir = os.path.join(bids_root, "sourcedata") print_dir_tree(data_dir) ############################################################################### # Step 2: Formatting as BIDS # -------------------------- # # Let's start by formatting a single subject. We are reading the data using # MNE-Python's io module and the :func:`read_raw_edf` function. Note that we # must use `preload=False`, the default in MNE-Python. It prevents the data # from being loaded and modified when converting to BIDS. # # Note that kind and acquisition currently almost stand for the same thing. # Please read the BIDS docs to get acquainted. # create the BIDS directory structure if not os.path.exists(bids_root): print("Making bids root directory.") make_bids_folders( output_path=bids_root, session=test_sessionid, subject=test_subjectid, kind=modality, ) for i, edf_fpath in enumerate(edf_fpaths): """ Write data file into BIDS format """ test_runid = i # add a bids run bids_basename = make_bids_basename( subject=test_subjectid, session=test_sessionid, task=test_task, run=test_runid, acquisition=modality, ) print("Loading filepath: ", edf_fpath) print("Writing to bidsroot: ", bids_root) print("Bids basenmae; ", bids_basename) # call bidsbuilder pipeline bids_deriv_root = BidsConverter.convert_to_bids( edf_fpath=edf_fpath, bids_root=bids_root, bids_basename=bids_basename, line_freq=line_freq, overwrite=True, ) # currently write_raw_bids overwrites make_dataset_description # TODO: put this on the top when PR gets merged. make_dataset_description(os.path.join(bids_root), name="test_bids_dataset", authors=authors) ############################################################################### # What does our fresh BIDS directory look like? print_dir_tree(bids_root) ############################################################################### # Step 3: Check and compare and read in the data # ------------------------------------------------------------ # Now we have written our BIDS directory. if modality in ["ecog", "seeg"]: kind = "ieeg" elif modality == "eeg": kind = "eeg" bids_fname = bids_basename + f"_{kind}.edf" print("Trying to read from: ", bids_fname) # use MNE-BIDS function to read in the data raw = read_raw_bids(bids_fname, bids_root) print("Read successfully using MNE-BIDS") # use BidsRun object, which just simply adds additional functionality # bidsrun = BidsRun(tmp_bids_root, bids_fname) # raw = bidsrun.load_data() print(raw) ############################################################################### # Step 4: Run BIDS-Validate # ------------------------------------------------------------ # Now we have written our BIDS directory. # save a fif copy and reload it # TODO: re-check when pybids is updated. # currently, use the https://bids-standard.github.io/bids-validator/ and see that it is verified params = _parse_bids_filename(bids_basename, True) print(raw.info) fif_data_path = make_bids_folders( subject=params["sub"], session=params["ses"], kind=kind, output_path=bids_root, overwrite=False, verbose=True, ) rel_bids_root = f"/sub-0001/ses-seizure/{kind}/" path = os.path.join(rel_bids_root, bids_fname) is_valid = BIDSValidator().is_bids(path) print(BIDSValidator().is_top_level(path)) print(BIDSValidator().is_associated_data(path)) print(BIDSValidator().is_session_level(path)) print(BIDSValidator().is_subject_level(path)) print(BIDSValidator().is_phenotypic(path)) print(BIDSValidator().is_file(path)) print("checked filepath: ", os.path.join(rel_bids_root, bids_fname)) print(is_valid)
def read_raw_bids(bids_fname, bids_root, 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_fname : str Full name of the data file bids_root : str Path to root of the BIDS folder 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)``. verbose : bool The verbosity level Returns ------- raw : instance of Raw The data as MNE-Python Raw object. """ # Full path to data file is needed so that mne-bids knows # what is the modality -- meg, eeg, ieeg to read bids_fname = op.basename(bids_fname) bids_basename = '_'.join(bids_fname.split('_')[:-1]) kind = bids_fname.split('_')[-1].split('.')[0] _, ext = _parse_ext(bids_fname) # Get the BIDS parameters (=entities) params = _parse_bids_filename(bids_basename, verbose) # Construct the path to the "kind" where the data is stored # Subject is mandatory ... kind_dir = op.join(bids_root, 'sub-{}'.format(params['sub'])) # Session is optional ... if params['ses'] is not None: kind_dir = op.join(kind_dir, 'ses-{}'.format(params['ses'])) # Kind is mandatory kind_dir = op.join(kind_dir, kind) config = None if ext in ('.fif', '.ds', '.vhdr', '.edf', '.bdf', '.set', '.sqd', '.con'): bids_fpath = op.join(kind_dir, bids_basename + '_{}{}'.format(kind, ext)) elif ext == '.pdf': bids_raw_folder = op.join(kind_dir, bids_basename + '_{}'.format(kind)) bids_fpath = glob.glob(op.join(bids_raw_folder, 'c,rf*'))[0] config = op.join(bids_raw_folder, 'config') if extra_params is None: extra_params = dict() 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_fname, bids_root, 'events.tsv', allow_fail=True) 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_fname, bids_root, 'channels.tsv', allow_fail=True) if channels_fname is not None: raw = _handle_channels_reading(channels_fname, bids_fname, raw) # Try to find an associated electrodes.tsv and coordsystem.json # to get information about the status and type of present channels electrodes_fname = _find_matching_sidecar(bids_fname, bids_root, 'electrodes.tsv', allow_fail=True) coordsystem_fname = _find_matching_sidecar(bids_fname, bids_root, 'coordsystem.json', allow_fail=True) if electrodes_fname is not None: if coordsystem_fname is None: raise RuntimeError("BIDS mandates that the coordsystem.json " "should exist if electrodes.tsv does. " "Please create coordsystem.json for" "{}".format(bids_basename)) # Get MRI landmarks from the JSON sidecar with open(coordsystem_fname, 'r') as fin: coordsystem_json = json.load(fin) # Get coordinate frames that electrode coordinates are in if kind == "meg": coord_frame = coordsystem_json['MEGCoordinateSystem'] elif kind == "ieeg": coord_frame = coordsystem_json['iEEGCoordinateSystem'] else: # noqa raise RuntimeError("Kind {} not supported yet for " "coordsystem.json and " "electrodes.tsv.".format(kind)) # read in electrode coordinates and attach to raw raw = _handle_electrodes_reading(electrodes_fname, coord_frame, raw, verbose) # Try to find an associated sidecar.json to get information about the # recording snapshot sidecar_fname = _find_matching_sidecar(bids_fname, bids_root, '{}.json'.format(kind), allow_fail=True) if sidecar_fname is not None: raw = _handle_info_reading(sidecar_fname, raw, verbose=verbose) return raw
def read_raw_bids(bids_fname, bids_root, 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_fname : str Full name of the data file bids_root : str Path to root of the BIDS folder 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)``. verbose : bool The verbosity level Returns ------- raw : instance of Raw The data as MNE-Python Raw object. """ # Full path to data file is needed so that mne-bids knows # what is the modality -- meg, eeg, ieeg to read bids_fname = op.basename(bids_fname) bids_basename = '_'.join(bids_fname.split('_')[:-1]) kind = bids_fname.split('_')[-1].split('.')[0] _, ext = _parse_ext(bids_fname) # Get the BIDS parameters (=entities) params = _parse_bids_filename(bids_basename, verbose) # Construct the path to the "kind" where the data is stored # Subject is mandatory ... kind_dir = op.join(bids_root, 'sub-{}'.format(params['sub'])) # Session is optional ... if params['ses'] is not None: kind_dir = op.join(kind_dir, 'ses-{}'.format(params['ses'])) # Kind is mandatory kind_dir = op.join(kind_dir, kind) config = None if ext in ('.fif', '.ds', '.vhdr', '.edf', '.bdf', '.set', '.sqd', '.con'): bids_fpath = op.join(kind_dir, bids_basename + '_{}{}'.format(kind, ext)) elif ext == '.pdf': bids_raw_folder = op.join(kind_dir, bids_basename + '_{}'.format(kind)) bids_fpath = glob.glob(op.join(bids_raw_folder, 'c,rf*'))[0] config = op.join(bids_raw_folder, 'config') if extra_params is None: extra_params = dict() 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_fname, bids_root, 'events.tsv', allow_fail=True) 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_fname, bids_root, 'channels.tsv', allow_fail=True) if channels_fname is not None: raw = _handle_channels_reading(channels_fname, bids_fname, raw) return raw
def main( bids_fname, bids_root, output_fname, reference, deriv_path=None, verbose=False, overwrite=False, ): """ Run an Example EZTrack core analysis. Parameters ---------- bids_fname : str The full basename of the bids file, resulting from make_bids_basename bids_root : Union[str or Path] The base directory where the bids data is stored output_fname : str The name of the output file verbose : bool Whether to display output overwrite: bool Whether to overwrite existing output """ # determine kind from bids_basename params = _parse_bids_filename(bids_fname, verbose=verbose) acquisition = params["acq"] subject = params["sub"] # load the data raw = read_raw_bids(bids_fname, bids_root, verbose=verbose) # get rid of bad ch_names raw.load_data() bad_chs = raw.info["bads"] raw = raw.drop_channels(bad_chs) # determine kind from the raw input kind = _handle_kind(raw) # only keep EEG/SEEG channel pick_dict = {f"{acquisition}": True} raw = raw.pick_types(**pick_dict) # preprocess the data using preprocess pipeline if kind == "eeg": raw = preprocess_eeg(raw, bad_chs=[]) elif kind == "ieeg": raw = preprocess_ieeg(raw, bad_chs=[]) # validation checks on raw data validate_raw_metadata(raw) # run fragility analysis if deriv_path is None: deriv_path = Path( Path(bids_root) / "derivatives" / "fragility" / reference / subject ) fragility_results, metadata = analyze_data( raw, deriv_path, output_fname, reference=reference, overwrite=overwrite, verbose=verbose, ) pertmats, adjmats, delvecs_array = fragility_results # extract results from tuple # run validation checks on fragility output validate_eztrack_result(pertmats, metadata) # draw heatmap main_draw_heatmap(deriv_path, output_fname) return deriv_path
def preprocess_into_fif(bids_fname, bids_root, kind="eeg", overwrite=True): """ Preprocess the edf file into fif format for easier manipulation. TODO: 1. Determine if this is necessary. EDF files on one hand are hard to modify, but fif files have a lot of constraints. 2. We should try to discuss w/ some folks that do this, or ask on gitter for mne_bids 3. See: https://gist.github.com/skjerns/bc660ef59dca0dbd53f00ed38c42f6be/812cd1d4be24c0730db449ecc6eb0065da68ca51 Parameters ---------- bids_fname : Union[str, os.PathLike] The path to the bids format edf file bids_root : Union[str, os.PathLike] The base directory for bids data kind : str The type of data contained in the edf file. overwrite : bool Whether to overwrite an existing converted file. Returns ------- The path of the converted fif file """ params = _parse_bids_filename(bids_fname, verbose=False) bids_basename = make_bids_basename( subject=params["sub"], session=params["ses"], run=params["run"], suffix="eeg.edf", ) raw = mne_bids.read_raw_bids(bids_basename, bids_root) # get events and convert to annotations events, events_id = mne.events_from_annotations(raw) onsets = events[:, 0] / raw.info["sfreq"] durations = np.zeros_like(onsets) # assumes instantaneous events descriptions = events[:, 2] annotations = mne.annotations.Annotations( onset=onsets, duration=durations, description=descriptions, orig_time=raw.info["meas_date"], ) # add a bids run preprocessed_dir = os.path.join( bids_root, make_bids_basename(subject=params["sub"]), make_bids_basename(session=params["ses"]), f"{kind}", ) if not os.path.exists(preprocessed_dir): os.makedirs(preprocessed_dir) bids_basename = make_bids_basename( subject=params["sub"], session=params["ses"], run=params["run"], processing="fif", suffix="raw", ) # save a fif copy and reload it raw.save(os.path.join(preprocessed_dir, bids_basename + ".fif"), overwrite=overwrite) raw = mne.io.read_raw_fif( os.path.join(preprocessed_dir, bids_basename + ".fif")) # convert into fif bids_root = write_raw_bids( raw, bids_basename, bids_root=bids_root, overwrite=overwrite, # events_data=events, verbose=False, ) return bids_root
def _read_dig_bids(electrodes_fpath, coordsystem_fpath, raw, kind, verbose): """Read MNE-Python formatted DigMontage from BIDS files. Handles coordinatesystem.json and electrodes.tsv reading to DigMontage. Parameters ---------- electrodes_fpath : str Filepath of the electrodes.tsv to read. coordsystem_fpath : str Filepath of the coordsystem.json to read. raw : instance of Raw The data as MNE-Python Raw object. kind : str Type of the data as in ALLOWED_KINDS. verbose : bool Set verbose output to true or false. Returns ------- raw : instance of Raw The data as MNE-Python Raw object. """ # get the space entity params = _parse_bids_filename(electrodes_fpath, verbose) space = params['space'] if space is None: space = '' space = space.lower() # read in coordinate information coord_frame, coord_unit = _handle_coordsystem_reading( coordsystem_fpath, kind, verbose) if kind == 'meg': if coord_frame not in BIDS_MEG_COORDINATE_FRAMES: warn("MEG Coordinate frame is not accepted " "BIDS keyword. The allowed keywords are: " "{}".format(BIDS_MEG_COORDINATE_FRAMES)) coord_frame = None elif coord_frame == 'other': warn("Coordinate frame of MEG data can't be determined " "when 'other'. The currently accepted keywords are: " "{}".format(BIDS_MEG_COORDINATE_FRAMES)) coord_frame = None else: coord_frame = BIDS_TO_MNE_FRAMES.get(coord_frame, None) elif kind == 'ieeg': if coord_frame not in BIDS_IEEG_COORDINATE_FRAMES: warn("iEEG Coordinate frame is not accepted " "BIDS keyword. The allowed keywords are: " "{}".format(BIDS_IEEG_COORDINATE_FRAMES)) coord_frame = None elif coord_frame == 'pixels': warn("Coordinate frame of iEEG data in pixels does not " "get read in by mne-python. Skipping reading of " "electrodes.tsv ...") coord_frame = None elif coord_frame == 'acpc': coord_frame = BIDS_TO_MNE_FRAMES.get(coord_frame, None) elif coord_frame == 'other': # XXX: We allow 'other' coordinate frames, but must be mne-python if space not in BIDS_TO_MNE_FRAMES: # default coordinate frames to available ones in mne-python # noqa: see https://bids-specification.readthedocs.io/en/stable/99-appendices/08-coordinate-systems.html warn("Defaulting coordinate frame to unknown " "from coordinate system input {}".format(coord_frame)) coord_frame = BIDS_TO_MNE_FRAMES.get(space, None) elif kind == 'eeg': # only accept captrak if coord_frame not in BIDS_EEG_COORDINATE_FRAMES: warn("EEG Coordinate frame is not accepted " "BIDS keyword. The allowed keywords are: " "{}".format(BIDS_IEEG_COORDINATE_FRAMES)) coord_frame = None else: coord_frame = BIDS_TO_MNE_FRAMES.get(coord_frame, None) # check coordinate units if coord_unit not in BIDS_COORDINATE_UNITS: warn("Coordinate unit is not an accepted BIDS unit for {}. " "Please specify to be one of {}. Skipping electrodes.tsv " "reading...".format(electrodes_fpath, BIDS_COORDINATE_UNITS)) coord_frame = None # only set montage if coordinate frame was properly parsed if coord_frame is not None: # read in electrode coordinates and attach to raw raw = _handle_electrodes_reading(electrodes_fpath, coord_frame, coord_unit, raw, verbose) return raw
def get_head_mri_trans(bids_basename, bids_root): """Produce transformation matrix from MEG and MRI landmark points. Will attempt to read the landmarks of Nasion, LPA, and RPA from the sidecar files of (i) the MEG and (ii) the T1 weighted MRI data. The two sets of points will then be used to calculate a transformation matrix from head coordinates to MRI coordinates. Parameters ---------- bids_basename : str | BIDSPath The base filename of the BIDS-compatible file. Typically, this can be generated using :func:`mne_bids.make_bids_basename`. bids_root : str | pathlib.Path Path to root of the BIDS folder Returns ------- trans : instance of mne.transforms.Transform The data transformation matrix from head to MRI coordinates """ if not has_nibabel(): # pragma: no cover raise ImportError('This function requires nibabel.') import nibabel as nib # convert to BIDS Path if isinstance(bids_basename, str): params = _parse_bids_filename(bids_basename, False) bids_basename = BIDSPath(subject=params.get('sub'), session=params.get('ses'), recording=params.get('rec'), acquisition=params.get('acq'), processing=params.get('proc'), space=params.get('space'), run=params.get('run'), task=params.get('task')) # Get the sidecar file for MRI landmarks bids_fname = bids_basename.get_bids_fname(kind='meg', bids_root=bids_root) t1w_json_path = _find_matching_sidecar(bids_fname, bids_root, 'T1w.json') # Get MRI landmarks from the JSON sidecar with open(t1w_json_path, 'r') as f: t1w_json = json.load(f) mri_coords_dict = t1w_json.get('AnatomicalLandmarkCoordinates', dict()) mri_landmarks = np.asarray( (mri_coords_dict.get('LPA', np.nan), mri_coords_dict.get('NAS', np.nan), mri_coords_dict.get('RPA', np.nan))) if np.isnan(mri_landmarks).any(): raise RuntimeError( 'Could not parse T1w sidecar file: "{}"\n\n' 'The sidecar file MUST contain a key ' '"AnatomicalLandmarkCoordinates" pointing to a ' 'dict with keys "LPA", "NAS", "RPA". ' 'Yet, the following structure was found:\n\n"{}"'.format( t1w_json_path, t1w_json)) # The MRI landmarks are in "voxels". We need to convert the to the # neuromag RAS coordinate system in order to compare the with MEG landmarks # see also: `mne_bids.write.write_anat` t1w_path = t1w_json_path.replace('.json', '.nii') if not op.exists(t1w_path): t1w_path += '.gz' # perhaps it is .nii.gz? ... else raise an error if not op.exists(t1w_path): raise RuntimeError( 'Could not find the T1 weighted MRI associated ' 'with "{}". Tried: "{}" but it does not exist.'.format( t1w_json_path, t1w_path)) t1_nifti = nib.load(t1w_path) # Convert to MGH format to access vox2ras method t1_mgh = nib.MGHImage(t1_nifti.dataobj, t1_nifti.affine) # now extract transformation matrix and put back to RAS coordinates of MRI vox2ras_tkr = t1_mgh.header.get_vox2ras_tkr() mri_landmarks = apply_trans(vox2ras_tkr, mri_landmarks) mri_landmarks = mri_landmarks * 1e-3 # Get MEG landmarks from the raw file _, ext = _parse_ext(bids_basename) extra_params = None if ext == '.fif': extra_params = dict(allow_maxshield=True) raw = read_raw_bids(bids_basename=bids_basename, bids_root=bids_root, extra_params=extra_params, kind='meg') meg_coords_dict = _extract_landmarks(raw.info['dig']) meg_landmarks = np.asarray((meg_coords_dict['LPA'], meg_coords_dict['NAS'], meg_coords_dict['RPA'])) # Given the two sets of points, fit the transform trans_fitted = fit_matched_points(src_pts=meg_landmarks, tgt_pts=mri_landmarks) trans = mne.transforms.Transform(fro='head', to='mri', trans=trans_fitted) return trans
def read_raw_bids(bids_basename, bids_root, kind=None, 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_basename : str | BIDSPath The base filename of the BIDS compatible files. Typically, this can be generated using :func:`mne_bids.make_bids_basename`. bids_root : str | pathlib.Path Path to root of the BIDS folder kind : str | None The kind of recording to read. If ``None`` and only one kind (e.g., only EEG or only 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)``. verbose : bool The verbosity level. Returns ------- raw : instance of Raw The data as MNE-Python Raw object. Raises ------ RuntimeError If multiple recording kinds are present in the dataset, but ``kind=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 ``kind`` cannot be found in the dataset. """ # convert to BIDS Path if isinstance(bids_basename, str): params = _parse_bids_filename(bids_basename, verbose) bids_basename = BIDSPath(subject=params.get('sub'), session=params.get('ses'), recording=params.get('rec'), acquisition=params.get('acq'), processing=params.get('proc'), space=params.get('space'), run=params.get('run'), task=params.get('task')) sub = bids_basename.subject ses = bids_basename.session acq = bids_basename.acquisition if kind is None: kind = _infer_kind(bids_basename=bids_basename, bids_root=bids_root, sub=sub, ses=ses) data_dir = make_bids_folders(subject=sub, session=ses, kind=kind, make_dir=False) bids_fname = bids_basename.get_bids_fname(kind=kind, bids_root=bids_root) if op.splitext(bids_fname)[1] == '.pdf': bids_raw_folder = op.join(bids_root, data_dir, f'{bids_basename}_{kind}') bids_fpath = glob.glob(op.join(bids_raw_folder, 'c,rf*'))[0] config = op.join(bids_raw_folder, 'config') else: bids_fpath = op.join(bids_root, data_dir, bids_fname) config = None if extra_params is None: extra_params = dict() 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_fname, bids_root, 'events.tsv', allow_fail=True) 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_fname, bids_root, 'channels.tsv', allow_fail=True) if channels_fname is not None: raw = _handle_channels_reading(channels_fname, bids_fname, raw) # Try to find an associated electrodes.tsv and coordsystem.json # to get information about the status and type of present channels search_modifier = f'acq-{acq}' if acq else '' elec_suffix = f'{search_modifier}*_electrodes.tsv' coord_suffix = f'{search_modifier}*_coordsystem.json' electrodes_fname = _find_matching_sidecar(bids_fname, bids_root, suffix=elec_suffix, allow_fail=True) coordsystem_fname = _find_matching_sidecar(bids_fname, bids_root, suffix=coord_suffix, allow_fail=True) if electrodes_fname is not None: if coordsystem_fname is None: raise RuntimeError("BIDS mandates that the coordsystem.json " "should exist if electrodes.tsv does. " "Please create coordsystem.json for" "{}".format(bids_basename)) if kind in ['meg', 'eeg', 'ieeg']: raw = _read_dig_bids(electrodes_fname, coordsystem_fname, raw, kind, verbose) # Try to find an associated sidecar.json to get information about the # recording snapshot sidecar_fname = _find_matching_sidecar(bids_fname, bids_root, '{}.json'.format(kind), allow_fail=True) if sidecar_fname is not None: raw = _handle_info_reading(sidecar_fname, raw, verbose=verbose) # read in associated subject info from participants.tsv participants_tsv_fpath = op.join(bids_root, 'participants.tsv') subject = f"sub-{bids_basename.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 _extract_run(fname): params = _parse_bids_filename(fname, verbose=False) return params["run"]
def test_fragility_analysis_ieeg(bids_root, bids_fname): """Integration test for running full fragility analysis after data is preprocessed and loaded.""" # read in raw dataset raw = read_raw_bids(bids_fname, bids_root) sfreq = raw.info["sfreq"] reference = "average" # determine kind from bids_basename params = _parse_bids_filename(bids_fname, verbose=False) kind = params["kind"] # preprocess the data using preprocess pipeline raw.info["line_freq"] = 60 raw = preprocess_ieeg(raw, bad_chs=[]) # verification 2.2.2 (SRS Input File Format - 7.3.2.2): _rawdata = np.random.random((201, 5000)) chs = [] for i in range(201): chs.append(f"ch{i}") _info = mne.create_info(ch_names=chs, sfreq=sfreq, ch_types="ecog") _raw = mne.io.RawArray(_rawdata, info=_info) expected_msg = ( f"EZTrackRuntimeError: EZTrack needs to have channel count of less than 200 channels " f"to run in an allotted period of time. " f"The current dataset has {len(chs)} chs." ) with pytest.raises( SystemExit, match=rf"{expected_msg}", ): validate_raw_metadata(_raw) # verification 2.2.1 (SRS Bad Channels - 7.3.2.1): _raw.info["bads"] = _raw.ch_names[0:5] expected_msg = "['ch0', 'ch1', 'ch2', 'ch3', 'ch4']" with pytest.raises(SystemExit, match=rf"{expected_msg}"): validate_raw_metadata(_raw) # only analyze 5 seconds rawdata = raw.get_data(start=0, stop=int(sfreq * 5)) raw = mne.io.RawArray(rawdata, info=raw.info) # should raise an error, if there is no # PowerLineFrequency set with pytest.raises(SystemExit, match=r"EZTrackRuntimeError: Line frequency"): raw.info["line_freq"] = None raw = preprocess_ieeg(raw, bad_chs=[]) # run fragility analysis with tempfile.TemporaryDirectory() as bids_root: output_fname = bids_fname.replace("vhdr", "npz") fragility_results, metadata = analyze_data( raw, bids_root, output_fname, reference ) # test that we get the same results if already ran _fragility_results, _metadata = analyze_data( raw, bids_root, output_fname, reference ) for i in range(3): np.testing.assert_array_almost_equal( fragility_results[i], _fragility_results[i] ) # verification 2.3 (SRS Output File Format - 7.3.3): output_fpath = metadata["output_fpath"] assert output_fpath.endswith(".npz") with np.load(output_fpath) as data_dict: pertmats = data_dict["pertmats"] # verification 2.2.4 (SRS Output Data Format - 7.3.2.3): pertmats, adjmats, delvecs_arr = fragility_results pertmats = pertmats.T adjmats = adjmats.T with pytest.raises(SystemExit, match=r"EZTrackRuntimeError: End EZTrack result"): validate_eztrack_result(pertmats, metadata) # check output dimensions of fragility analysis pertmats, adjmats, delvecs_arr = fragility_results n_chs = len(raw.ch_names) n_wins = metadata["numwins"] assert pertmats.shape == (n_chs, n_wins) assert delvecs_arr.shape == (n_chs, n_chs, n_wins) assert adjmats.shape == (n_chs, n_chs, n_wins) # check still works if non-seizure session is used with tempfile.TemporaryDirectory() as bids_root: output_fname = output_fname.replace("seizure", "interictal") fragility_results, metadata = analyze_data( raw, bids_root, output_fname, reference ) # check that visualization runs without error with tempfile.TemporaryDirectory() as figdir: fig_fname = output_fname.split(".")[0] ax, outputfig_fpath = generate_heatmap( pertmats, metadata["ch_names"], figdir, fig_fname ) assert str(outputfig_fpath).endswith(".pdf")
def read_raw_bids(bids_fname, bids_root, allow_maxshield=False, 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_fname : str Full name of the data file bids_root : str Path to root of the BIDS folder allow_maxshield : bool | str (default False) If True, allow loading of data that has been recorded with internal active compensation (MaxShield). Data recorded with MaxShield should generally not be loaded directly, but should first be processed using SSS/tSSS to remove the compensation signals that may also affect brain activity. Can also be “yes” to load without eliciting a warning. verbose : bool The verbosity level Returns ------- raw : instance of Raw The data as MNE-Python Raw object. """ # Full path to data file is needed so that mne-bids knows # what is the modality -- meg, eeg, ieeg to read bids_fname = op.basename(bids_fname) bids_basename = '_'.join(bids_fname.split('_')[:-1]) kind = bids_fname.split('_')[-1].split('.')[0] _, ext = _parse_ext(bids_fname) # Get the BIDS parameters (=entities) params = _parse_bids_filename(bids_basename, verbose) # Construct the path to the "kind" where the data is stored # Subject is mandatory ... kind_dir = op.join(bids_root, 'sub-{}'.format(params['sub'])) # Session is optional ... if params['ses'] is not None: kind_dir = op.join(kind_dir, 'ses-{}'.format(params['ses'])) # Kind is mandatory kind_dir = op.join(kind_dir, kind) config = None if ext in ('.fif', '.ds', '.vhdr', '.edf', '.bdf', '.set', '.sqd', '.con'): bids_fpath = op.join(kind_dir, bids_basename + '_{}{}'.format(kind, ext)) elif ext == '.pdf': bids_raw_folder = op.join(kind_dir, bids_basename + '_{}'.format(kind)) bids_fpath = glob.glob(op.join(bids_raw_folder, 'c,rf*'))[0] config = op.join(bids_raw_folder, 'config') raw = _read_raw(bids_fpath, electrode=None, hsp=None, hpi=None, config=config, allow_maxshield=allow_maxshield, verbose=None) # Try to find an associated events.tsv to get information about the # events in the recorded data events_fname = _find_matching_sidecar(bids_fname, bids_root, 'events.tsv', allow_fail=True) 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_fname, bids_root, 'channels.tsv', allow_fail=True) if channels_fname is not None: raw = _handle_channels_reading(channels_fname, bids_fname, raw) return raw
def get_matched_empty_room(bids_basename, bids_root): """Get matching empty-room file for an MEG recording. Parameters ---------- bids_basename : str | BIDSPath The base filename of the BIDS-compatible file. Typically, this can be generated using :func:`mne_bids.make_bids_basename`. bids_root : str | pathlib.Path Path to the BIDS root folder. Returns ------- er_basename : str | None. The basename corresponding to the best-matching empty-room measurement. Returns None if none was found. """ # convert to BIDS Path if isinstance(bids_basename, str): params = _parse_bids_filename(bids_basename, False) bids_basename = BIDSPath(subject=params.get('sub'), session=params.get('ses'), recording=params.get('rec'), acquisition=params.get('acq'), processing=params.get('proc'), space=params.get('space'), run=params.get('run'), task=params.get('task')) kind = 'meg' # We're only concerned about MEG data here bids_fname = bids_basename.get_bids_fname(kind=kind, bids_root=bids_root) _, ext = _parse_ext(bids_fname) if ext == '.fif': extra_params = dict(allow_maxshield=True) else: extra_params = None raw = read_raw_bids(bids_basename=bids_basename, bids_root=bids_root, kind=kind, extra_params=extra_params) if raw.info['meas_date'] is None: raise ValueError('The provided recording does not have a measurement ' 'date set. Cannot get matching empty-room file.') ref_date = raw.info['meas_date'] if not isinstance(ref_date, datetime): # for MNE < v0.20 ref_date = datetime.fromtimestamp(raw.info['meas_date'][0]) emptyroom_dir = pathlib.Path( make_bids_folders(bids_root=bids_root, subject='emptyroom', make_dir=False)) if not emptyroom_dir.exists(): return None # Find the empty-room recording sessions. emptyroom_session_dirs = [ x for x in emptyroom_dir.iterdir() if x.is_dir() and str(x.name).startswith('ses-') ] if not emptyroom_session_dirs: # No session sub-directories found emptyroom_session_dirs = [emptyroom_dir] # Now try to discover all recordings inside the session directories. allowed_extensions = list(reader.keys()) # `.pdf` is just a "virtual" extension for BTi data (which is stored inside # a dedicated directory that doesn't have an extension) del allowed_extensions[allowed_extensions.index('.pdf')] candidate_er_fnames = [] for session_dir in emptyroom_session_dirs: dir_contents = glob.glob( op.join(session_dir, kind, f'sub-emptyroom_*_{kind}*')) for item in dir_contents: item = pathlib.Path(item) if ((item.suffix in allowed_extensions) or (not item.suffix and item.is_dir())): # Hopefully BTi? candidate_er_fnames.append(item.name) # Walk through recordings, trying to extract the recording date: # First, from the filename; and if that fails, from `info['meas_date']`. best_er_basename = None min_delta_t = np.inf date_tie = False failed_to_get_er_date_count = 0 for er_fname in candidate_er_fnames: params = _parse_bids_filename(er_fname, verbose=False) er_meas_date = None er_bids_path = BIDSPath(subject='emptyroom', session=params.get('ses', None), task=params.get('task', None), acquisition=params.get('acq', None), run=params.get('run', None), processing=params.get('proc', None), recording=params.get('rec', None), space=params.get('space', None)) er_basename = str(er_bids_path) # Try to extract date from filename. if params['ses'] is not None: try: er_meas_date = datetime.strptime(params['ses'], '%Y%m%d') except (ValueError, TypeError): # There is a session in the filename, but it doesn't encode a # valid date. pass if er_meas_date is None: # No luck so far! Check info['meas_date'] _, ext = _parse_ext(er_fname) if ext == '.fif': extra_params = dict(allow_maxshield=True) else: extra_params = None er_raw = read_raw_bids(bids_basename=er_basename, bids_root=bids_root, kind=kind, extra_params=extra_params) er_meas_date = er_raw.info['meas_date'] if er_meas_date is None: # There's nothing we can do. failed_to_get_er_date_count += 1 continue er_meas_date = er_meas_date.replace(tzinfo=ref_date.tzinfo) delta_t = er_meas_date - ref_date if abs(delta_t.total_seconds()) == min_delta_t: date_tie = True elif abs(delta_t.total_seconds()) < min_delta_t: min_delta_t = abs(delta_t.total_seconds()) best_er_basename = er_basename date_tie = False if failed_to_get_er_date_count > 0: msg = (f'Could not retrieve the empty-room measurement date from ' f'a total of {failed_to_get_er_date_count} recording(s).') warn(msg) if date_tie: msg = ('Found more than one matching empty-room measurement with the ' 'same recording date. Selecting the first match.') warn(msg) return best_er_basename
print("Read successfully using MNE-BIDS") # use BidsRun object, which just simply adds additional functionality # bidsrun = BidsRun(tmp_bids_root, bids_fname) # raw = bidsrun.load_data() print(raw) ############################################################################### # Step 4: Run BIDS-Validate # ------------------------------------------------------------ # Now we have written our BIDS directory. # save a fif copy and reload it # TODO: re-check when pybids is updated. # currently, use the https://bids-standard.github.io/bids-validator/ and see that it is verified params = _parse_bids_filename(bids_basename, True) print(raw.info) fif_data_path = make_bids_folders( subject=params["sub"], session=params["ses"], kind=kind, bids_root=bids_root, overwrite=False, verbose=True, ) rel_bids_root = f"/sub-0001/ses-seizure/{kind}/" path = os.path.join(rel_bids_root, bids_fname) is_valid = BIDSValidator().is_bids(path) print(BIDSValidator().is_top_level(path)) print(BIDSValidator().is_associated_data(path))
def _write_dig_bids(electrodes_fname, coordsystem_fname, data_path, raw, kind, overwrite=False, verbose=True): """Write BIDS formatted DigMontage from Raw instance. Handles coordinatesystem.json and electrodes.tsv writing from DigMontage. Parameters ---------- electrodes_fname : str Filename to save the electrodes.tsv to. coordsystem_fname : str Filename to save the coordsystem.json to. data_path : str | pathlib.Path Path to the data directory raw : instance of Raw The data as MNE-Python Raw object. 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. """ # write electrodes data for iEEG and EEG unit = "m" # defaults to meters params = _parse_bids_filename(electrodes_fname, verbose) subject_id = params['sub'] session_id = params['ses'] acquisition = params['acq'] # 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) if verbose: print("Writing electrodes file to... ", electrodes_fname) print("Writing coordsytem file to... ", coordsystem_fname) if kind == "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_fname = make_bids_basename( subject=subject_id, session=session_id, acquisition=acquisition, space=coord_frame, suffix='coordsystem.json', prefix=data_path) electrodes_fname = make_bids_basename(subject=subject_id, session=session_id, acquisition=acquisition, space=coord_frame, suffix='electrodes.tsv', prefix=data_path) coord_frame = 'Other' # Now write the data to the elec coords and the coordsystem _electrodes_tsv(raw, electrodes_fname, kind, overwrite, verbose) _coordsystem_json(raw, unit, 'n/a', coord_frame, coordsystem_fname, kind, overwrite, 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_fname)) elif kind == '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_fname, kind, overwrite, verbose) _coordsystem_json(raw, 'm', 'RAS', 'CapTrak', coordsystem_fname, kind, overwrite, 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 convert_to_bids( edf_fpath, bids_root, bids_basename, coords_fpath=None, excluded_contacts=None, eog_contacts=None, misc_contacts=None, overwrite=False, line_freq=60.0, ): """ Convert the passed edf file into the Bids format. # TODO: - Clean up how write_raw_bids is called - eliminate redundant writing/reading using temporaryDirectory Parameters ---------- edf_fpath : Union[str, os.PathLike] The location the edf file. bids_root : Union[str, os.PathLike] The base directory for newly created bids files. bids_basename : str The base name of the new data files excluded_contacts : list Contacts to be excluded from conversion eog_contacts : list Contacts to be annotated as EOG. misc_contacts : list Contacts to be annotated as Misc. overwrite : bool Whether to overwrite an existing converted file. Returns ------- The path to the new data file. """ if excluded_contacts is None: excluded_contacts = ["-", ""] raw = mne.io.read_raw_edf( edf_fpath, preload=False, verbose="ERROR", exclude=excluded_contacts, eog=eog_contacts, misc=misc_contacts, ) if line_freq is not None: raw.info["line_freq"] = line_freq annonymize_dict = None # { # "daysback": 10000, # "keep_his": True, # } # extract parameters from bids_basenmae params = _parse_bids_filename(bids_basename, True) subject, session = params["sub"], params["ses"] acquisition, kind = params["acq"], params["kind"] task = params["task"] # read in the events from the EDF file events_data, events_id = mne.events_from_annotations(raw) print(events_data, events_id) channel_scrub = ChannelScrub # convert the channel types based on acquisition if necessary if acquisition is not None: ch_modality_map = {ch: acquisition for ch in raw.ch_names} raw.set_channel_types(ch_modality_map) ch_type_mapping = channel_scrub.label_channel_types(raw.ch_names) raw.set_channel_types((ch_type_mapping)) # reformat channel text if necessary channel_scrub.channel_text_scrub(raw) # look for bad channels that are obvious channel_names = raw.ch_names bad_channels = channel_scrub.look_for_bad_channels(channel_names) bad_channels_dict = {} for bad in bad_channels: bad_channels_dict[ bad] = f"Scrubbed channels containing markers {', '.join(BAD_MARKERS)}" raw.info["bads"] = bad_channels if coords_fpath: ch_pos = dict() with open(coords_fpath, "r") as fp: # strip of newline character lines = [line.rstrip("\n") for line in fp] for line in lines: ch_name = line.split(" ")[0] coord = line.split(" ")[1:] ch_pos[ch_name] = [float(x) for x in coord] unit = "mm" if unit != "m": ch_pos = { ch_name: np.divide(coord, 1000) for ch_name, coord in ch_pos.items() } montage = mne.channels.make_dig_montage(ch_pos=ch_pos, coord_frame="head") # TODO: remove. purely for testing scenario # ch_names = raw.ch_names # elec = np.random.random_sample((len(ch_names), 3)) # assume in mm # elec = elec / 1000 # convert to meters # montage = mne.channels.make_dig_montage(ch_pos=dict(zip(ch_names, elec)), # coord_frame='head') else: montage = None if montage is not None: if not isinstance(montage, mne.channels.DigMontage): raise TypeError("Montage passed in should be of type: " "`mne.channels.DigMontage`.") raw.set_montage(montage) print("Set montage: ") print(len(raw.info["ch_names"])) print(raw.info["dig"]) print(raw) # actually perform write_raw bids bids_root = write_raw_bids( raw, bids_basename, bids_root, events_data=events_data, event_id=events_id, overwrite=overwrite, # anonymize=annonymize_dict, verbose=False, ) # save a fif copy and reload it kind = _handle_kind(raw) fif_data_path = make_bids_folders( subject=subject, session=session, kind=kind, output_path=bids_root, overwrite=False, verbose=True, ) bids_fname = bids_basename + f"_{kind}.fif" deriv_bids_root = os.path.join(bids_root, "derivatives") print("Should be saving for: ", bids_fname) with tempfile.TemporaryDirectory() as tmp_bids_root: raw.save(os.path.join(tmp_bids_root, bids_fname), overwrite=overwrite) raw = mne.io.read_raw_fif(os.path.join(tmp_bids_root, bids_fname)) print(raw, bids_basename) print(raw.filenames) _, ext = _parse_ext(raw.filenames[0]) print(ext) # actually perform write_raw bids bids_root = write_raw_bids( raw, bids_basename, deriv_bids_root, events_data=events_data, event_id=events_id, overwrite=overwrite, # anonymize=annonymize_dict, verbose=False, ) return bids_root