def test_bdf(_bids_validate): """Test write_raw_bids conversion for Biosemi data.""" output_path = _TempDir() data_path = op.join(base_path, 'edf', 'tests', 'data') raw_fname = op.join(data_path, 'test.bdf') raw = mne.io.read_raw_bdf(raw_fname) with pytest.warns(UserWarning, match='No line frequency found'): write_raw_bids(raw, bids_basename, output_path, overwrite=False) _bids_validate(output_path) # Test also the reading of channel types from channels.tsv # the first channel in the raw data is not MISC right now test_ch_idx = 0 assert coil_type(raw.info, test_ch_idx) != 'misc' # we will change the channel type to MISC and overwrite the channels file bids_fname = bids_basename + '_eeg.bdf' channels_fname = _find_matching_sidecar(bids_fname, output_path, 'channels.tsv') channels_dict = _from_tsv(channels_fname) channels_dict['type'][test_ch_idx] = 'MISC' _to_tsv(channels_dict, channels_fname) # Now read the raw data back from BIDS, with the tampered TSV, to show # that the channels.tsv truly influences how read_raw_bids sets ch_types # in the raw data object raw = read_raw_bids(bids_fname, output_path) assert coil_type(raw.info, test_ch_idx) == 'misc' # Test cropped assertion error raw = mne.io.read_raw_bdf(raw_fname) raw.crop(0, raw.times[-2]) with pytest.raises(AssertionError, match='cropped'): write_raw_bids(raw, bids_basename, output_path)
def _write_tsv(fname, dictionary, overwrite=False, verbose=None): """Write an ordered dictionary to a .tsv file.""" if op.exists(fname) and not overwrite: raise FileExistsError(f'"{fname}" already exists. ' 'Please set overwrite to True.') _to_tsv(dictionary, fname) logger.info(f"Writing '{fname}'...")
def _update_electrodes_tsv(electrodes_tsv_fpath, elec_labels_anat, atlas_depth): electrodes_tsv = _from_tsv(electrodes_tsv_fpath) if atlas_depth not in electrodes_tsv.keys(): electrodes_tsv[atlas_depth] = ["n/a"] * len(elec_labels_anat) for i in range(len(elec_labels_anat)): ch_name = electrodes_tsv["name"][i] print(ch_name, elec_labels_anat[i]) electrodes_tsv[atlas_depth][i] = elec_labels_anat[i] _to_tsv(electrodes_tsv, electrodes_tsv_fpath) return electrodes_tsv
def test_handle_events_reading(): """Test reading events from a BIDS events.tsv file.""" # We can use any `raw` for this raw = mne.io.read_raw_fif(raw_fname) # Create an arbitrary events.tsv file, to test we can deal with 'n/a' events = {'onset': [11, 12, 13], 'duration': ['n/a', 'n/a', 'n/a']} tmp_dir = _TempDir() events_fname = op.join(tmp_dir, 'sub-01_task-test_events.json') _to_tsv(events, events_fname) raw = _handle_events_reading(events_fname, raw) events, event_id = mne.events_from_annotations(raw)
def test_handle_events_reading(tmpdir): """Test reading events from a BIDS events.tsv file.""" # We can use any `raw` for this raw = _read_raw_fif(raw_fname) # Create an arbitrary events.tsv file, to test we can deal with 'n/a' # make sure we can deal w/ "#" characters events = { 'onset': [11, 12, 'n/a'], 'duration': ['n/a', 'n/a', 'n/a'], 'trial_type': ["rec start", "trial #1", "trial #2!"] } events_fname = tmpdir.mkdir('bids1') / 'sub-01_task-test_events.json' _to_tsv(events, events_fname) raw = _handle_events_reading(events_fname, raw) events, event_id = mne.events_from_annotations(raw) # Test with a `stim_type` column instead of `trial_type`. events = { 'onset': [11, 12, 'n/a'], 'duration': ['n/a', 'n/a', 'n/a'], 'stim_type': ["rec start", "trial #1", "trial #2!"] } events_fname = tmpdir.mkdir('bids2') / 'sub-01_task-test_events.json' _to_tsv(events, events_fname) with pytest.warns(RuntimeWarning, match='This column should be renamed'): raw = _handle_events_reading(events_fname, raw) events, event_id = mne.events_from_annotations(raw) # Test with same `trial_type` referring to different `value` events = { 'onset': [11, 12, 13], 'duration': ['n/a', 'n/a', 'n/a'], 'trial_type': ["event1", "event1", "event2"], 'value': [1, 2, 3] } events_fname = tmpdir.mkdir('bids3') / 'sub-01_task-test_events.json' _to_tsv(events, events_fname) raw = _handle_events_reading(events_fname, raw) events, event_id = mne.events_from_annotations(raw) assert len(events) == 3 assert 'event1/1' in event_id assert 'event1/2' in event_id # The event with unique value mapping should not be renamed assert 'event2' in event_id # Test without any kind of event description. events = {'onset': [11, 12, 'n/a'], 'duration': ['n/a', 'n/a', 'n/a']} events_fname = tmpdir.mkdir('bids4') / 'sub-01_task-test_events.json' _to_tsv(events, events_fname) raw = _handle_events_reading(events_fname, raw) events, event_id = mne.events_from_annotations(raw) ids = list(event_id.keys()) assert len(ids) == 1 assert ids == ['n/a']
def test_tsv_handler(tmp_path): """Test the TSV handling.""" # create some dummy data d = odict(a=[1, 2, 3, 4], b=['five', 'six', 'seven', 'eight']) assert _contains_row(d, {'a': 1, 'b': 'five'}) d2 = odict(a=[5], b=['nine']) d = _combine_rows(d, d2) assert 5 in d['a'] d2 = odict(a=[5]) d = _combine_rows(d, d2) assert 'n/a' in d['b'] d2 = odict(a=[5], b=['ten']) d = _combine_rows(d, d2, drop_column='a') # make sure that the repeated data was dropped assert 'nine' not in d['b'] print(_tsv_to_str(d)) d_path = tmp_path / 'output.tsv' # write the data to an output tsv file _to_tsv(d, d_path) # now read it back d = _from_tsv(d_path) # test reading the file in with the incorrect number of datatypes raises # an Error with pytest.raises(ValueError): d = _from_tsv(d_path, dtypes=[str]) # we can also pass just a single data type and it will be applied to all # columns d = _from_tsv(d_path, str) # remove any rows with 2 or 5 in them d = _drop(d, [2, 5], 'a') assert 2 not in d['a'] # test combining data with differing numbers of columns d = odict(a=[1, 2], b=['three', 'four']) d2 = odict(a=[4], b=['five'], c=[3.1415]) # raise error if a new column is tried to be added with pytest.raises(KeyError): d = _combine_rows(d, d2) d2 = odict(a=[5]) d = _combine_rows(d, d2) assert d['b'] == ['three', 'four', 'n/a'] assert _contains_row(d, {'a': 5}) # test reading a single column _to_tsv(odict(a=[1, 2, 3, 4]), d_path) d = _from_tsv(d_path) assert d['a'] == ['1', '2', '3', '4']
def test_read_participants_data(tmpdir): """Test reading information from a BIDS sidecar.json file.""" bids_path = _bids_path.copy().update(root=tmpdir, datatype='meg') raw = _read_raw_fif(raw_fname, verbose=False) # if subject info was set, we don't roundtrip birthday # due to possible anonymization in mne-bids subject_info = { 'hand': 1, 'sex': 2, } raw.info['subject_info'] = subject_info write_raw_bids(raw, bids_path, overwrite=True, verbose=False) raw = read_raw_bids(bids_path=bids_path) print(raw.info['subject_info']) assert raw.info['subject_info']['hand'] == 1 assert raw.info['subject_info']['sex'] == 2 assert raw.info['subject_info'].get('birthday', None) is None assert raw.info['subject_info']['his_id'] == f'sub-{bids_path.subject}' assert 'participant_id' not in raw.info['subject_info'] # if modifying participants tsv, then read_raw_bids reflects that participants_tsv_fpath = tmpdir / 'participants.tsv' participants_tsv = _from_tsv(participants_tsv_fpath) participants_tsv['hand'][0] = 'n/a' _to_tsv(participants_tsv, participants_tsv_fpath) raw = read_raw_bids(bids_path=bids_path) assert raw.info['subject_info']['hand'] == 0 assert raw.info['subject_info']['sex'] == 2 assert raw.info['subject_info'].get('birthday', None) is None # make sure things are read even if the entries don't make sense participants_tsv = _from_tsv(participants_tsv_fpath) participants_tsv['hand'][0] = 'righty' participants_tsv['sex'][0] = 'malesy' _to_tsv(participants_tsv, participants_tsv_fpath) with pytest.warns(RuntimeWarning, match='Unable to map'): raw = read_raw_bids(bids_path=bids_path) assert raw.info['subject_info']['hand'] is None assert raw.info['subject_info']['sex'] is None # make sure to read in if no participants file raw = _read_raw_fif(raw_fname, verbose=False) write_raw_bids(raw, bids_path, overwrite=True, verbose=False) os.remove(participants_tsv_fpath) with pytest.warns(RuntimeWarning, match='Participants file not found'): raw = read_raw_bids(bids_path=bids_path) assert raw.info['subject_info'] is None
def test_handle_coords_reading(): """Test reading coordinates from BIDS files.""" bids_root = _TempDir() data_path = op.join(testing.data_path(), 'EDF') raw_fname = op.join(data_path, 'test_reduced.edf') raw = mne.io.read_raw_edf(raw_fname) # ensure we are writing 'ecog'/'ieeg' data raw.set_channel_types({ch: 'ecog' for ch in raw.ch_names}) # set a `random` montage ch_names = raw.ch_names elec_locs = np.random.random((len(ch_names), 3)).astype(float) ch_pos = dict(zip(ch_names, elec_locs)) montage = mne.channels.make_dig_montage(ch_pos=ch_pos, coord_frame="mri") raw.set_montage(montage) write_raw_bids(raw, bids_basename, bids_root, overwrite=True) # read in the data and assert montage is the same bids_fname = bids_basename + "_ieeg.edf" raw_test = read_raw_bids(bids_fname, bids_root) # obtain the sensor positions orig_locs = raw.info['dig'][1] test_locs = raw_test.info['dig'][1] assert orig_locs == test_locs assert not object_diff(raw.info['chs'], raw_test.info['chs']) # test error message if electrodes don't match electrodes_fname = _find_matching_sidecar(bids_fname, bids_root, "electrodes.tsv", allow_fail=True) electrodes_dict = _from_tsv(electrodes_fname) # pop off 5 channels for key in electrodes_dict.keys(): for i in range(5): electrodes_dict[key].pop() _to_tsv(electrodes_dict, electrodes_fname) with pytest.raises(RuntimeError, match='Channels do not correspond'): raw_test = read_raw_bids(bids_fname, bids_root)
def test_handle_scans_reading(): """Test reading data from a BIDS scans.tsv file.""" bids_root = _TempDir() raw = _read_raw_fif(raw_fname) suffix = "meg" # write copy of raw with line freq of 60 # bids basename and fname bids_path = BIDSPath(subject='01', session='01', task='audiovisual', run='01', datatype=suffix, root=bids_root) bids_path = write_raw_bids(raw, bids_path, overwrite=True) raw_01 = read_raw_bids(bids_path) # find sidecar scans.tsv file and alter the # acquisition time to not have the optional microseconds scans_path = BIDSPath(subject=bids_path.subject, session=bids_path.session, root=bids_root, suffix='scans', extension='.tsv') scans_tsv = _from_tsv(scans_path) acq_time_str = scans_tsv['acq_time'][0] acq_time = datetime.strptime(acq_time_str, '%Y-%m-%dT%H:%M:%S.%fZ') acq_time = acq_time.replace(tzinfo=timezone.utc) new_acq_time = acq_time_str.split('.')[0] assert acq_time == raw_01.info['meas_date'] scans_tsv['acq_time'][0] = new_acq_time _to_tsv(scans_tsv, scans_path) # now re-load the data and it should be different # from the original date and the same as the newly altered date raw_02 = read_raw_bids(bids_path) new_acq_time += '.0Z' new_acq_time = datetime.strptime(new_acq_time, '%Y-%m-%dT%H:%M:%S.%fZ') new_acq_time = new_acq_time.replace(tzinfo=timezone.utc) assert raw_02.info['meas_date'] == new_acq_time assert new_acq_time != raw_01.info['meas_date']
with open(output_electrodes_tsv_fpath, "w", encoding="utf-8") as f: f.write("name\tx\ty\tz\n") for i, name in enumerate(electrodes_tsv.keys()): f.write("%s\t%.6f\t%.6f\t%.6f\n" % ( name, electrodes_tsv[name][0], electrodes_tsv[name][1], electrodes_tsv[name][2], )) # Output labeled .mat files with atlas, white matter, and brainmask information electrodes_tsv, electrodes_json = apply_atlas(bids_root, output_electrodes_tsv_fpath, inv_affine, fs_patient_dir, fs_lut_fpath) # save sidecar electrodes tsv _to_tsv(electrodes_tsv, output_electrodes_tsv_fpath) # save sidecar electrodes json with open(str(output_electrodes_tsv_fpath).replace(".tsv", ".json"), "w") as fout: json.dump(electrodes_json, fout, indent=4) # create a coordsystem JSON file output_coordsyste_fpath = str(output_electrodes_tsv_fpath).replace( "electrodes.tsv", "coordsystem.json") _write_coordsystem_json(fname=output_coordsyste_fpath, unit="mm", img_fname=mri_img_fpath)
assert labelsxyz.shape[0] == len(labels) # run coordinate transformation modified_coords = np.array( [ transform( coords, ct_nifti_img, mri_nifti_img, mapping_transformation_file, coordinate_type=coordinate_type, ) for coords in labelsxyz ] ) # resave the coordinate transformed data for i, (x, y, z) in enumerate(modified_coords): electrodes_tsv["x"][i] = x electrodes_tsv["y"][i] = y electrodes_tsv["z"][i] = z _to_tsv(electrodes_tsv, mri_coords_fpath) # resave the coordinate system file mri_coordsystem_fpath = mri_coords_fpath.replace( "electrodes.tsv", "coordsystem.json" ) unit = "mm" img_fname = mri_nifti_img _write_coordsystem_json(mri_coordsystem_fpath, unit, img_fname=img_fname)
def test_bads_reading(): bids_root = _TempDir() bids_path = _bids_path.copy().update(root=bids_root) data_path = BIDSPath(subject=subject_id, session=session_id, datatype='meg', root=bids_root).mkdir().directory ch_path = (bids_path.copy().update(suffix='channels', extension='.tsv')) channels_fname = op.join(data_path, ch_path.basename) raw_bids_fname = (bids_path.copy().update(root=bids_root, datatype='meg', suffix='meg', extension='.fif')) raw = _read_raw_fif(raw_fname, verbose=False) ########################################################################### # bads in FIF only, no `status` column in channels.tsv bads = ['EEG 053', 'MEG 2443'] raw.info['bads'] = bads write_raw_bids(raw, bids_path, overwrite=True, verbose=False) # Delete `status` column tsv_data = _from_tsv(channels_fname) del tsv_data['status'], tsv_data['status_description'] _to_tsv(tsv_data, fname=channels_fname) raw = read_raw_bids(bids_path=bids_path, verbose=False) assert raw.info['bads'] == bads ########################################################################### # bads in `status` column in channels.tsv, no bads in raw.info['bads'] bads = ['EEG 053', 'MEG 2443'] raw.info['bads'] = bads write_raw_bids(raw, bids_path, overwrite=True, verbose=False) # Remove info['bads'] from the raw file. raw = _read_raw_fif(raw_bids_fname, preload=True, verbose=False) raw.info['bads'] = [] raw.save(raw_bids_fname, overwrite=True, verbose=False) raw = read_raw_bids(bids_path=bids_path, verbose=False) assert type(raw.info['bads']) is list assert set(raw.info['bads']) == set(bads) ########################################################################### # Different bads in `status` column and raw.info['bads'] bads_bids = ['EEG 053', 'MEG 2443'] bads_raw = ['MEG 0112', 'MEG 0131'] raw.info['bads'] = bads_bids write_raw_bids(raw, bids_path, overwrite=True, verbose=False) # Replace info['bads'] in the raw file. raw = _read_raw_fif(raw_bids_fname, preload=True, verbose=False) raw.info['bads'] = bads_raw raw.save(raw_bids_fname, overwrite=True, verbose=False) with pytest.warns(RuntimeWarning, match='conflicting information'): raw = read_raw_bids(bids_path=bids_path, verbose=False) assert type(raw.info['bads']) is list assert set(raw.info['bads']) == set(bads_bids)
def append_original_fname_to_scans( orig_fname: str, bids_root: Union[str, Path], bids_fname: str, overwrite: bool = True, verbose: bool = True, ): """Append the original filename to *scans.tsv in BIDS data structure. This will also create a sidecar *scans.json file alongside to document a description of the added column in the scans.tsv file. Parameters ---------- orig_fname : str The original base filename that will be added into the 'original_filename' columnn. bids_root : str | Path The root to the BIDS dataset. bids_fname : str | BIDSPath The BIDS filename of the BIDSified dataset. This should correspond to a specific 'filename' in the *scans.tsv file. overwrite : bool Whether or not to overwrite the row. verbose : bool """ # create a BIDS path object noting that you only need # subject and session to define the *scans.tsv file entities = get_entities_from_fname(bids_fname) bids_path = BIDSPath(entities["subject"], entities["session"], root=bids_root) scans_fpath = bids_path.copy().update(suffix="scans", extension=".tsv") # make sure the path actually exists if not scans_fpath.fpath.exists(): raise OSError( f"Scans.tsv file {scans_fpath} does not " f"exist. Please check the path to ensure it is " f"valid." ) scans_tsv = _from_tsv(scans_fpath) # new filenames filenames = scans_tsv["filename"] ind = [i for i, fname in enumerate(filenames) if str(bids_fname) in fname] if len(ind) > 1: # pragma: no cover msg = ( "This should not happen. All scans should " "be uniquely identifiable from scans.tsv file. " "The current scans file has these filenames: " f"{filenames}." ) logger.exception(msg) raise RuntimeError(msg) if len(ind) == 0: msg = ( f"No filename, {bids_fname} found. " f"Scans.tsv has these files: {filenames}." ) logger.exception(msg) raise RuntimeError(msg) # write scans.json scans_json_path = _replace_ext(scans_fpath, "json") scans_json = { "original_filename": "The original filename of the converted BIDs dataset. " "Provides possibly ictal/interictal, asleep/awake and " "clinical seizure grouping (i.e. SZ2PG, etc.)." } _write_json(scans_json_path, scans_json, overwrite=True, verbose=verbose) # write in original filename if "original_filename" not in scans_tsv.keys(): scans_tsv["original_filename"] = ["n/a"] * len(filenames) if scans_tsv["original_filename"][ind[0]] == "n/a" or overwrite: scans_tsv["original_filename"][ind[0]] = orig_fname else: logger.warning( "Original filename has already been written here. " f"Skipping for {bids_fname}. It is written as " f"{scans_tsv['original_filename'][ind[0]]}." ) return # write the scans out _to_tsv(scans_tsv, scans_fpath)