def test_handle_channel_type_casing(tmpdir): """Test that non-uppercase entries in the `type` column are accepted.""" bids_path = _bids_path.copy().update(root=tmpdir) raw = _read_raw_fif(raw_fname, verbose=False) write_raw_bids(raw, bids_path, overwrite=True, verbose=False) ch_path = bids_path.copy().update(root=tmpdir, datatype='meg', suffix='channels', extension='.tsv') bids_channels_fname = ch_path.fpath # Convert all channel type entries to lowercase. channels_data = _from_tsv(bids_channels_fname) channels_data['type'] = [t.lower() for t in channels_data['type']] _to_tsv(channels_data, bids_channels_fname) with pytest.warns(RuntimeWarning, match='lowercase spelling'): read_raw_bids(bids_path)
def test_read_participants_handedness_and_sex_mapping(hand_bids, hand_mne, sex_bids, sex_mne): """Test we're correctly mapping handedness and sex between BIDS and MNE.""" bids_root = _TempDir() bids_path = _bids_path.copy().update(root=bids_root, datatype='meg') participants_tsv_fpath = op.join(bids_root, 'participants.tsv') raw = _read_raw_fif(raw_fname, verbose=False) # Avoid that we end up with subject information stored in the raw data. raw.info['subject_info'] = {} write_raw_bids(raw, bids_path, overwrite=True, verbose=False) participants_tsv = _from_tsv(participants_tsv_fpath) participants_tsv['hand'][0] = hand_bids participants_tsv['sex'][0] = sex_bids _to_tsv(participants_tsv, participants_tsv_fpath) raw = read_raw_bids(bids_path=bids_path) assert raw.info['subject_info']['hand'] is hand_mne assert raw.info['subject_info']['sex'] is sex_mne
def test_bads_reading(): bids_root = _TempDir() bids_path = _bids_path.copy().update(root=bids_root, datatype='meg') bads_raw = ['MEG 0112', 'MEG 0113'] bads_sidecar = ['EEG 053', 'MEG 2443'] # Produce conflicting information between raw and sidecar file. raw = _read_raw_fif(raw_fname, verbose=False) raw.info['bads'] = bads_sidecar write_raw_bids(raw, bids_path, verbose=False) raw = _read_raw(bids_path.copy().update(extension='.fif').fpath, preload=True) raw.info['bads'] = bads_raw raw.save(raw.filenames[0], overwrite=True) # Upon reading the data, only the sidecar info should be present. raw = read_raw_bids(bids_path=bids_path, verbose=False) assert len(raw.info['bads']) == len(bads_sidecar) assert set(raw.info['bads']) == set(bads_sidecar)
def test_handle_info_reading(): """Test reading information from a BIDS sidecar.json file.""" bids_root = _TempDir() # read in USA dataset, so it should find 50 Hz raw = mne.io.read_raw_fif(raw_fname) raw.info['line_freq'] = 60 # write copy of raw with line freq of 60 # bids basename and fname bids_basename = make_bids_basename(subject='01', session='01', task='audiovisual', run='01') kind = "meg" bids_fname = bids_basename + '_{}.fif'.format(kind) write_raw_bids(raw, bids_basename, bids_root, overwrite=True) # find sidecar JSON fname sidecar_fname = _find_matching_sidecar(bids_fname, bids_root, '{}.json'.format(kind), allow_fail=True) # assert that we get the same line frequency set raw = mne_bids.read_raw_bids(bids_fname, bids_root) assert raw.info['line_freq'] == 60 # 2. if line frequency is not set in raw file, then default to sidecar raw.info['line_freq'] = None write_raw_bids(raw, bids_basename, bids_root, overwrite=True) _update_sidecar(sidecar_fname, "PowerLineFrequency", 55) raw = mne_bids.read_raw_bids(bids_fname, bids_root) assert raw.info['line_freq'] == 55 # make a copy of the sidecar in "derivatives/" # to check that we make sure we always get the right sidecar # in addition, it should not break the sidecar reading # in `read_raw_bids` deriv_dir = op.join(bids_root, "derivatives") sidecar_copy = op.join(deriv_dir, op.basename(sidecar_fname)) os.mkdir(deriv_dir) with open(sidecar_fname, "r") as fin: sidecar_json = json.load(fin) sidecar_json["PowerLineFrequency"] = 45 _write_json(sidecar_copy, sidecar_json) raw = mne_bids.read_raw_bids(bids_fname, bids_root) assert raw.info['line_freq'] == 55 # 3. if line frequency is set in raw file, but not sidecar raw.info['line_freq'] = 60 write_raw_bids(raw, bids_basename, bids_root, overwrite=True) _update_sidecar(sidecar_fname, "PowerLineFrequency", "n/a") raw = mne_bids.read_raw_bids(bids_fname, bids_root) assert raw.info['line_freq'] == 60 # 4. assert that we get an error when sidecar json doesn't match _update_sidecar(sidecar_fname, "PowerLineFrequency", 55) with pytest.raises(ValueError, match="Line frequency in sidecar json"): raw = mne_bids.read_raw_bids(bids_fname, bids_root)
def test_keep_essential_annotations(tmpdir): """Test that essential Annotations are not omitted during I/O roundtrip.""" raw = _read_raw_fif(raw_fname) annotations = mne.Annotations(onset=[raw.times[0]], duration=[1], description=['BAD_ACQ_SKIP']) raw.set_annotations(annotations) # Write data, remove events.tsv, then try to read again bids_path = BIDSPath(subject='01', task='task', datatype='meg', root=tmpdir) with pytest.warns(RuntimeWarning, match='Acquisition skips detected'): write_raw_bids(raw, bids_path, overwrite=True) bids_path.copy().update(suffix='events', extension='.tsv').fpath.unlink() raw_read = read_raw_bids(bids_path) assert len(raw_read.annotations) == len(raw.annotations) == 1 assert (raw_read.annotations[0]['description'] == raw.annotations[0] ['description'])
def test_get_head_mri_trans_ctf(fname, tmpdir): """Test getting a trans object from BIDS data in CTF.""" import nibabel as nib ctf_data_path = op.join(testing.data_path(), 'CTF') raw_ctf_fname = op.join(ctf_data_path, fname) raw_ctf = _read_raw_ctf(raw_ctf_fname, clean_names=True) bids_path = _bids_path.copy().update(root=tmpdir) write_raw_bids(raw_ctf, bids_path, overwrite=False) # Take a fake trans trans = mne.read_trans(raw_fname.replace('_raw.fif', '-trans.fif')) # Get the T1 weighted MRI data file ... test write_anat with a nibabel # image instead of a file path t1w_mgh = op.join(data_path, 'subjects', 'sample', 'mri', 'T1.mgz') t1w_mgh = nib.load(t1w_mgh) t1w_bids_path = BIDSPath(subject=subject_id, session=session_id, acquisition=acq, root=tmpdir) landmarks = get_anat_landmarks(t1w_mgh, raw_ctf.info, trans, fs_subject='sample', fs_subjects_dir=op.join( data_path, 'subjects')) write_anat(t1w_mgh, bids_path=t1w_bids_path, landmarks=landmarks) # Try to get trans back through fitting points estimated_trans = get_head_mri_trans(bids_path=bids_path, extra_params=dict(clean_names=True), fs_subject='sample', fs_subjects_dir=op.join( data_path, 'subjects')) assert_almost_equal(trans['trans'], estimated_trans['trans'])
def test_report(tmpdir): """Test that report generated works as intended.""" bids_root = str(tmpdir) raw = mne.io.read_raw_fif(raw_fname, verbose=False) raw.info['line_freq'] = 60 bids_path.update(root=bids_root) write_raw_bids(raw, bids_path, overwrite=True, verbose=False) report = make_report(bids_root) expected_report = \ f"""This dataset was created by [Unspecified] and conforms to BIDS version {BIDS_VERSION}. This report was generated with MNE-BIDS (https://doi.org/10.21105/joss.01896). The dataset consists of 1 participants (sex were all unknown; handedness were all unknown; ages all unknown) and 1 recording sessions: 01. Data was recorded using a MEG system (Elekta manufacturer) sampled at 300.31 Hz with line noise at 60.0 Hz. The following software filters were applied during recording: SpatialCompensation. There was 1 scan in total. Recording durations ranged from 20.0 to 20.0 seconds (mean = 20.0, std = 0.0), for a total of 20.0 seconds of data recorded over all scans. For each dataset, there were on average 376.0 (std = 0.0) recording channels per scan, out of which 374.0 (std = 0.0) were used in analysis (2.0 +/- 0.0 were removed from analysis).""" # noqa assert report == expected_report
def test_report(): """Test that report generated works as intended.""" bids_root = _TempDir() raw = mne.io.read_raw_fif(raw_fname, verbose=False) raw.info['line_freq'] = 60 bids_path.update(root=bids_root) write_raw_bids(raw, bids_path, overwrite=True, verbose=False) report = make_report(bids_root) print(report) expected_report = \ """This dataset was created with BIDS version 1.4.0 by Please cite MNE-BIDS in your publication before removing this (citations in README). This report was generated with MNE-BIDS (https://doi.org/10.21105/joss.01896). The dataset consists of 1 participants (sex were all unknown; handedness were all unknown; ages all unknown)and 1 recording sessions: 01. Data was recorded using a MEG system (Elekta manufacturer) sampled at 300.31 Hz with line noise at 60 Hz. There was 1 scan in total. Recording durations ranged from 20.0 to 20.0 seconds (mean = 20.0, std = 0.0), for a total of 20.0 seconds of data recorded over all scans. For each dataset, there were on average 376.0 (std = 0.0) recording channels per scan, out of which 374.0 (std = 0.0) were used in analysis (2.0 +/- 0.0 were removed from analysis).""" # noqa assert report == expected_report
def test_get_matched_emptyroom_ties(): """Test that we receive a warning on a date tie.""" bids_root = _TempDir() session = '20010101' er_dir = make_bids_folders(subject='emptyroom', session=session, kind='meg', bids_root=bids_root) meas_date = (datetime.strptime(session, '%Y%m%d').replace(tzinfo=timezone.utc)) raw = mne.io.read_raw_fif(raw_fname) er_raw_fname = op.join(data_path, 'MEG', 'sample', 'ernoise_raw.fif') raw.copy().crop(0, 10).save(er_raw_fname, overwrite=True) er_raw = mne.io.read_raw_fif(er_raw_fname) if check_version('mne', '0.20'): raw.set_meas_date(meas_date) er_raw.set_meas_date(meas_date) else: raw.info['meas_date'] = (meas_date.timestamp(), 0) er_raw.info['meas_date'] = (meas_date.timestamp(), 0) write_raw_bids(raw, bids_basename, bids_root, overwrite=True) er_bids_path = BIDSPath(subject='emptyroom', session=session) er_basename_1 = str(er_bids_path) er_basename_2 = make_bids_basename(subject='emptyroom', session=session, task='noise') er_raw.save(op.join(er_dir, f'{er_basename_1}_meg.fif')) er_raw.save(op.join(er_dir, f'{er_basename_2}_meg.fif')) with pytest.warns(RuntimeWarning, match='Found more than one'): get_matched_empty_room(bids_basename=bids_basename, bids_root=bids_root)
def test_read_raw_kind(): """Test that read_raw_bids() can infer the kind if need be.""" bids_root = _TempDir() raw = mne.io.read_raw_fif(raw_fname, verbose=False) write_raw_bids(raw, bids_basename, bids_root, overwrite=True, verbose=False) raw_1 = read_raw_bids(bids_basename=bids_basename, bids_root=bids_root, kind='meg') raw_2 = read_raw_bids(bids_basename=bids_basename, bids_root=bids_root, kind=None) raw_3 = read_raw_bids(bids_basename=bids_basename, bids_root=bids_root) raw_1.crop(0, 2).load_data() raw_2.crop(0, 2).load_data() raw_3.crop(0, 2).load_data() assert raw_1 == raw_2 assert raw_1 == raw_3
def test_line_freq_estimation(): """Test estimating line frequency.""" bids_root = _TempDir() # read in USA dataset, so it should find 50 Hz raw = mne.io.read_raw_fif(raw_fname) kind = "meg" # assert that we get the same line frequency set bids_fname = bids_basename.copy().update(suffix=f'{kind}.fif') # find sidecar JSON fname write_raw_bids(raw, bids_basename, bids_root, overwrite=True) sidecar_fname = _find_matching_sidecar(bids_fname, bids_root, '{}.json'.format(kind), allow_fail=True) # 1. when nothing is set, default to use PSD estimation -> should be 60 # for `sample` dataset raw.info['line_freq'] = None write_raw_bids(raw, bids_basename, bids_root, overwrite=True) _update_sidecar(sidecar_fname, "PowerLineFrequency", "n/a") with pytest.warns(RuntimeWarning, match="No line frequency found"): raw = read_raw_bids(bids_basename=bids_basename, bids_root=bids_root, kind=kind) assert raw.info['line_freq'] == 60 # test that `somato` dataset finds 50 Hz (EU dataset) somato_raw = mne.io.read_raw_fif(somato_raw_fname) somato_raw.info['line_freq'] = None write_raw_bids(somato_raw, bids_basename, bids_root, overwrite=True) sidecar_fname = _find_matching_sidecar(bids_fname, bids_root, '{}.json'.format(kind), allow_fail=True) _update_sidecar(sidecar_fname, "PowerLineFrequency", "n/a") with pytest.warns(RuntimeWarning, match="No line frequency found"): somato_raw = read_raw_bids(bids_basename=bids_basename, bids_root=bids_root, kind=kind) assert somato_raw.info['line_freq'] == 50 # assert that line_freq should be None when # all picks are not meg/eeg/ecog/seeg somato_raw.info['line_freq'] = None somato_raw.set_channel_types({ somato_raw.ch_names[i]: 'bio' for i in range(len(somato_raw.ch_names)) }) somato_raw = _handle_info_reading(sidecar_fname, somato_raw, verbose=True) assert somato_raw.info['line_freq'] is None
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']
def test_handle_chpi_reading(tmpdir): """Test reading of cHPI information.""" raw = _read_raw_fif(raw_fname_chpi, allow_maxshield=True) root = tmpdir.mkdir('chpi') bids_path = BIDSPath(subject='01', session='01', task='audiovisual', run='01', root=root, datatype='meg') bids_path = write_raw_bids(raw, bids_path) raw_read = read_raw_bids(bids_path) assert raw_read.info['hpi_subsystem'] is not None # cause conflicts between cHPI info in sidecar and raw data meg_json_path = bids_path.copy().update(suffix='meg', extension='.json') with open(meg_json_path, 'r', encoding='utf-8') as f: meg_json_data = json.load(f) # cHPI frequency mismatch meg_json_data_freq_mismatch = meg_json_data.copy() meg_json_data_freq_mismatch['HeadCoilFrequency'][0] = 123 with open(meg_json_path, 'w', encoding='utf-8') as f: json.dump(meg_json_data_freq_mismatch, f) with pytest.raises(ValueError, match='cHPI coil frequencies'): raw_read = read_raw_bids(bids_path) # cHPI "off" according to sidecar, but present in the data meg_json_data_chpi_mismatch = meg_json_data.copy() meg_json_data_chpi_mismatch['ContinuousHeadLocalization'] = False with open(meg_json_path, 'w', encoding='utf-8') as f: json.dump(meg_json_data_chpi_mismatch, f) raw_read = read_raw_bids(bids_path) assert raw_read.info['hpi_subsystem'] is None assert raw_read.info['hpi_meas'] == []
def test_handle_info_reading(tmpdir): """Test reading information from a BIDS sidecar.json file.""" # read in USA dataset, so it should find 50 Hz raw = _read_raw_fif(raw_fname) # write copy of raw with line freq of 60 # bids basename and fname bids_path = BIDSPath(subject='01', session='01', task='audiovisual', run='01', root=tmpdir) suffix = "meg" bids_fname = bids_path.copy().update(suffix=suffix, extension='.fif') write_raw_bids(raw, bids_path, overwrite=True) # find sidecar JSON fname bids_fname.update(datatype=suffix) sidecar_fname = _find_matching_sidecar(bids_fname, suffix=suffix, extension='.json') # assert that we get the same line frequency set raw = read_raw_bids(bids_path=bids_path) assert raw.info['line_freq'] == 60 # setting line_freq to None should produce 'n/a' in the JSON sidecar raw.info['line_freq'] = None write_raw_bids(raw, bids_path, overwrite=True) raw = read_raw_bids(bids_path=bids_path) assert raw.info['line_freq'] is None with open(sidecar_fname, 'r', encoding='utf-8') as fin: sidecar_json = json.load(fin) assert sidecar_json["PowerLineFrequency"] == 'n/a' # 2. if line frequency is not set in raw file, then ValueError del raw.info['line_freq'] with pytest.raises(ValueError, match="PowerLineFrequency .* required"): write_raw_bids(raw, bids_path, overwrite=True) # make a copy of the sidecar in "derivatives/" # to check that we make sure we always get the right sidecar # in addition, it should not break the sidecar reading # in `read_raw_bids` raw.info['line_freq'] = 60 write_raw_bids(raw, bids_path, overwrite=True) deriv_dir = tmpdir.mkdir("derivatives") sidecar_copy = deriv_dir / op.basename(sidecar_fname) with open(sidecar_fname, "r", encoding='utf-8') as fin: sidecar_json = json.load(fin) sidecar_json["PowerLineFrequency"] = 45 _write_json(sidecar_copy, sidecar_json) raw = read_raw_bids(bids_path=bids_path) assert raw.info['line_freq'] == 60 # 3. assert that we get an error when sidecar json doesn't match _update_sidecar(sidecar_fname, "PowerLineFrequency", 55) with pytest.raises(ValueError, match="Line frequency in sidecar json"): raw = read_raw_bids(bids_path=bids_path) assert raw.info['line_freq'] == 55
def test_get_head_mri_trans(tmpdir): """Test getting a trans object from BIDS data.""" import nibabel as nib event_id = { 'Auditory/Left': 1, 'Auditory/Right': 2, 'Visual/Left': 3, 'Visual/Right': 4, 'Smiley': 5, 'Button': 32 } events_fname = op.join(data_path, 'MEG', 'sample', 'sample_audvis_trunc_raw-eve.fif') subjects_dir = op.join(data_path, 'subjects') # Drop unknown events. events = mne.read_events(events_fname) events = events[events[:, 2] != 0] # Write it to BIDS raw = _read_raw_fif(raw_fname) bids_path = _bids_path.copy().update(root=tmpdir) write_raw_bids(raw, bids_path, events_data=events, event_id=event_id, overwrite=False) # We cannot recover trans if no MRI has yet been written with pytest.raises(RuntimeError, match='Did not find any T1w'): estimated_trans = get_head_mri_trans(bids_path=bids_path, fs_subject='sample', fs_subjects_dir=subjects_dir) # Write some MRI data and supply a `trans` so that a sidecar gets written trans = mne.read_trans(raw_fname.replace('_raw.fif', '-trans.fif')) # Get the T1 weighted MRI data file ... test write_anat with a nibabel # image instead of a file path t1w_mgh = op.join(data_path, 'subjects', 'sample', 'mri', 'T1.mgz') t1w_mgh = nib.load(t1w_mgh) landmarks = get_anat_landmarks(t1w_mgh, raw.info, trans, fs_subject='sample', fs_subjects_dir=subjects_dir) t1w_bids_path = write_anat(t1w_mgh, bids_path=bids_path, landmarks=landmarks, verbose=True) anat_dir = bids_path.directory # Try to get trans back through fitting points estimated_trans = get_head_mri_trans(bids_path=bids_path, fs_subject='sample', fs_subjects_dir=subjects_dir) assert trans['from'] == estimated_trans['from'] assert trans['to'] == estimated_trans['to'] assert_almost_equal(trans['trans'], estimated_trans['trans']) # provoke an error by introducing NaNs into MEG coords raw.info['dig'][0]['r'] = np.full(3, np.nan) sh.rmtree(anat_dir) bad_landmarks = get_anat_landmarks(t1w_mgh, raw.info, trans, 'sample', op.join(data_path, 'subjects')) write_anat(t1w_mgh, bids_path=t1w_bids_path, landmarks=bad_landmarks) with pytest.raises(RuntimeError, match='AnatomicalLandmarkCoordinates'): estimated_trans = get_head_mri_trans(bids_path=t1w_bids_path, fs_subject='sample', fs_subjects_dir=subjects_dir) # test we are permissive for different casings of landmark names in the # sidecar, and also accept "nasion" instead of just "NAS" raw = _read_raw_fif(raw_fname) write_raw_bids(raw, bids_path, events_data=events, event_id=event_id, overwrite=True) # overwrite with new acq t1w_bids_path = write_anat(t1w_mgh, bids_path=bids_path, landmarks=landmarks, overwrite=True) t1w_json_fpath = t1w_bids_path.copy().update(extension='.json').fpath with t1w_json_fpath.open('r', encoding='utf-8') as f: t1w_json = json.load(f) coords = t1w_json['AnatomicalLandmarkCoordinates'] coords['lpa'] = coords['LPA'] coords['Rpa'] = coords['RPA'] coords['Nasion'] = coords['NAS'] del coords['LPA'], coords['RPA'], coords['NAS'] _write_json(t1w_json_fpath, t1w_json, overwrite=True) estimated_trans = get_head_mri_trans(bids_path=bids_path, fs_subject='sample', fs_subjects_dir=subjects_dir) assert_almost_equal(trans['trans'], estimated_trans['trans']) # Test t1_bids_path parameter # # Case 1: different BIDS roots meg_bids_path = _bids_path.copy().update(root=tmpdir / 'meg_root') t1_bids_path = _bids_path.copy().update(root=tmpdir / 'mri_root') raw = _read_raw_fif(raw_fname) write_raw_bids(raw, bids_path=meg_bids_path) landmarks = get_anat_landmarks(t1w_mgh, raw.info, trans, fs_subject='sample', fs_subjects_dir=subjects_dir) write_anat(t1w_mgh, bids_path=t1_bids_path, landmarks=landmarks) read_trans = get_head_mri_trans(bids_path=meg_bids_path, t1_bids_path=t1_bids_path, fs_subject='sample', fs_subjects_dir=subjects_dir) assert np.allclose(trans['trans'], read_trans['trans']) # Case 2: different sessions raw = _read_raw_fif(raw_fname) meg_bids_path = _bids_path.copy().update(root=tmpdir / 'session_test', session='01') t1_bids_path = meg_bids_path.copy().update(session='02') write_raw_bids(raw, bids_path=meg_bids_path) write_anat(t1w_mgh, bids_path=t1_bids_path, landmarks=landmarks) read_trans = get_head_mri_trans(bids_path=meg_bids_path, t1_bids_path=t1_bids_path, fs_subject='sample', fs_subjects_dir=subjects_dir) assert np.allclose(trans['trans'], read_trans['trans']) # Test that incorrect subject directory throws error with pytest.raises(ValueError, match='Could not find'): estimated_trans = get_head_mri_trans(bids_path=bids_path, fs_subject='bad', fs_subjects_dir=subjects_dir)
def test_get_matched_empty_room(): """Test reading of empty room data.""" bids_root = _TempDir() raw = mne.io.read_raw_fif(raw_fname) bids_basename = make_bids_basename(subject='01', session='01', task='audiovisual', run='01') write_raw_bids(raw, bids_basename, bids_root, overwrite=True) er_basename = get_matched_empty_room(bids_basename=bids_basename, bids_root=bids_root) assert er_basename is None # testing data has no noise recording, so save the actual data # as if it were noise er_raw_fname = op.join(data_path, 'MEG', 'sample', 'ernoise_raw.fif') raw.crop(0, 10).save(er_raw_fname, overwrite=True) er_raw = mne.io.read_raw_fif(er_raw_fname) er_date = er_raw.info['meas_date'] if not isinstance(er_date, datetime): # mne < v0.20 er_date = datetime.fromtimestamp(er_raw.info['meas_date'][0]) er_date = er_date.strftime('%Y%m%d') er_bids_basename = make_bids_basename(subject='emptyroom', task='noise', session=er_date) write_raw_bids(er_raw, er_bids_basename, bids_root, overwrite=True) recovered_er_basename = get_matched_empty_room(bids_basename=bids_basename, bids_root=bids_root) assert er_bids_basename == recovered_er_basename # assert that we get best emptyroom if there are multiple available sh.rmtree(op.join(bids_root, 'sub-emptyroom')) dates = ['20021204', '20021201', '20021001'] for date in dates: er_bids_basename.update(session=date) er_meas_date = datetime.strptime(date, '%Y%m%d') er_meas_date = er_meas_date.replace(tzinfo=timezone.utc) if check_version('mne', '0.20'): er_raw.set_meas_date(er_meas_date) else: er_raw.info['meas_date'] = (er_meas_date.timestamp(), 0) write_raw_bids(er_raw, er_bids_basename, bids_root) best_er_basename = get_matched_empty_room(bids_basename=bids_basename, bids_root=bids_root) assert '20021204' in best_er_basename # assert that we get error if meas_date is not available. raw = read_raw_bids(bids_basename=bids_basename, bids_root=bids_root, kind='meg') if check_version('mne', '0.20'): raw.set_meas_date(None) else: raw.info['meas_date'] = None raw.annotations.orig_time = None anonymize_info(raw.info) write_raw_bids(raw, bids_basename, bids_root, overwrite=True) with pytest.raises(ValueError, match='The provided recording does not ' 'have a measurement date set'): get_matched_empty_room(bids_basename=bids_basename, bids_root=bids_root)
def test_handle_ieeg_coords_reading(bids_basename): """Test reading iEEG coordinates from BIDS files.""" bids_root = _TempDir() data_path = op.join(testing.data_path(), 'EDF') raw_fname = op.join(data_path, 'test_reduced.edf') bids_fname = bids_basename.copy().update(suffix='ieeg.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}) # coordinate frames in mne-python should all map correctly # 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)) coordinate_frames = ['mri', 'ras'] for coord_frame in coordinate_frames: # XXX: mne-bids doesn't support multiple electrodes.tsv files sh.rmtree(bids_root) montage = mne.channels.make_dig_montage(ch_pos=ch_pos, coord_frame=coord_frame) raw.set_montage(montage) write_raw_bids(raw, bids_basename, bids_root, overwrite=True, verbose=False) # read in raw file w/ updated coordinate frame # and make sure all digpoints are correct coordinate frames raw_test = read_raw_bids(bids_basename=bids_basename, bids_root=bids_root, verbose=False) coord_frame_int = MNE_STR_TO_FRAME[coord_frame] for digpoint in raw_test.info['dig']: assert digpoint['coord_frame'] == coord_frame_int # start w/ new bids root sh.rmtree(bids_root) write_raw_bids(raw, bids_basename, bids_root, overwrite=True, verbose=False) # obtain the sensor positions and assert ch_coords are same raw_test = read_raw_bids(bids_basename=bids_basename, bids_root=bids_root, verbose=False) 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']) # read in the data and assert montage is the same # regardless of 'm', 'cm', 'mm', or 'pixel' scalings = {'m': 1, 'cm': 100, 'mm': 1000} coordsystem_fname = _find_matching_sidecar(bids_fname, bids_root, suffix='coordsystem.json', allow_fail=True) electrodes_fname = _find_matching_sidecar(bids_fname, bids_root, "electrodes.tsv", allow_fail=True) orig_electrodes_dict = _from_tsv(electrodes_fname, [str, float, float, float, str]) # not BIDS specified should not be read coord_unit = 'km' scaling = 0.001 _update_sidecar(coordsystem_fname, 'iEEGCoordinateUnits', coord_unit) electrodes_dict = _from_tsv(electrodes_fname, [str, float, float, float, str]) for axis in ['x', 'y', 'z']: electrodes_dict[axis] = \ np.multiply(orig_electrodes_dict[axis], scaling) _to_tsv(electrodes_dict, electrodes_fname) with pytest.warns(RuntimeWarning, match='Coordinate unit is not ' 'an accepted BIDS unit'): raw_test = read_raw_bids(bids_basename=bids_basename, bids_root=bids_root, verbose=False) # correct BIDS units should scale to meters properly for coord_unit, scaling in scalings.items(): # update coordinate SI units _update_sidecar(coordsystem_fname, 'iEEGCoordinateUnits', coord_unit) electrodes_dict = _from_tsv(electrodes_fname, [str, float, float, float, str]) for axis in ['x', 'y', 'z']: electrodes_dict[axis] = \ np.multiply(orig_electrodes_dict[axis], scaling) _to_tsv(electrodes_dict, electrodes_fname) # read in raw file w/ updated montage raw_test = read_raw_bids(bids_basename=bids_basename, bids_root=bids_root, verbose=False) # obtain the sensor positions and make sure they're the same assert_dig_allclose(raw.info, raw_test.info) # XXX: Improve by changing names to 'unknown' coordframe (needs mne PR) # check that coordinate systems other coordinate systems should be named # in the file and not the CoordinateSystem, which is reserved for keywords coordinate_frames = ['lia', 'ria', 'lip', 'rip', 'las'] for coord_frame in coordinate_frames: # update coordinate units _update_sidecar(coordsystem_fname, 'iEEGCoordinateSystem', coord_frame) # read in raw file w/ updated coordinate frame # and make sure all digpoints are MRI coordinate frame with pytest.warns(RuntimeWarning, match="iEEG Coordinate frame is " "not accepted BIDS keyword"): raw_test = read_raw_bids(bids_basename=bids_basename, bids_root=bids_root, verbose=False) assert raw_test.info['dig'] is None # ACPC should be read in as RAS for iEEG _update_sidecar(coordsystem_fname, 'iEEGCoordinateSystem', 'acpc') raw_test = read_raw_bids(bids_basename=bids_basename, bids_root=bids_root, verbose=False) coord_frame_int = MNE_STR_TO_FRAME['ras'] for digpoint in raw_test.info['dig']: assert digpoint['coord_frame'] == coord_frame_int # test error message if electrodes don't match write_raw_bids(raw, bids_basename, bids_root, overwrite=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_basename=bids_basename, bids_root=bids_root, verbose=False) # make sure montage is set if there are coordinates w/ 'n/a' raw.info['bads'] = [] write_raw_bids(raw, bids_basename, bids_root, overwrite=True, verbose=False) electrodes_dict = _from_tsv(electrodes_fname) for axis in ['x', 'y', 'z']: electrodes_dict[axis][0] = 'n/a' electrodes_dict[axis][3] = 'n/a' _to_tsv(electrodes_dict, electrodes_fname) # test if montage is correctly set via mne-bids # electrode coordinates should be nan # when coordinate is 'n/a' nan_chs = [electrodes_dict['name'][i] for i in [0, 3]] with pytest.warns(RuntimeWarning, match='There are channels ' 'without locations'): raw = read_raw_bids(bids_basename=bids_basename, bids_root=bids_root, verbose=False) for idx, ch in enumerate(raw.info['chs']): if ch['ch_name'] in nan_chs: assert all(np.isnan(ch['loc'][:3])) else: assert not any(np.isnan(ch['loc'][:3])) assert ch['ch_name'] not in raw.info['bads']
def test_handle_eeg_coords_reading(): """Test reading iEEG 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 'eeg' data raw.set_channel_types({ch: 'eeg' 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)) # # create montage in 'unknown' coordinate frame # # and assert coordsystem/electrodes sidecar tsv don't exist montage = mne.channels.make_dig_montage(ch_pos=ch_pos, coord_frame="unknown") raw.set_montage(montage) with pytest.warns(RuntimeWarning, match="Skipping EEG electrodes.tsv"): write_raw_bids(raw, bids_basename, bids_root, overwrite=True) coordsystem_fname = _find_matching_sidecar(bids_basename, bids_root, suffix='coordsystem.json', allow_fail=True) electrodes_fname = _find_matching_sidecar(bids_basename, bids_root, suffix="electrodes.tsv", allow_fail=True) assert coordsystem_fname is None assert electrodes_fname is None # create montage in head frame and set should result in # warning if landmarks not set montage = mne.channels.make_dig_montage(ch_pos=ch_pos, coord_frame="head") raw.set_montage(montage) with pytest.warns(RuntimeWarning, match='Setting montage not possible ' 'if anatomical landmarks'): write_raw_bids(raw, bids_basename, bids_root, overwrite=True) montage = mne.channels.make_dig_montage(ch_pos=ch_pos, coord_frame="head", nasion=[1, 0, 0], lpa=[0, 1, 0], rpa=[0, 0, 1]) raw.set_montage(montage) write_raw_bids(raw, bids_basename, bids_root, overwrite=True) # obtain the sensor positions and assert ch_coords are same raw_test = read_raw_bids(bids_basename, bids_root, verbose=True) assert not object_diff(raw.info['chs'], raw_test.info['chs']) # modify coordinate frame to not-captrak coordsystem_fname = _find_matching_sidecar(bids_basename, bids_root, suffix='coordsystem.json', allow_fail=True) _update_sidecar(coordsystem_fname, 'EEGCoordinateSystem', 'besa') with pytest.warns(RuntimeWarning, match='EEG Coordinate frame is not ' 'accepted BIDS keyword'): raw_test = read_raw_bids(bids_basename, bids_root) assert raw_test.info['dig'] is None
def test_write_read_fif_split_file(tmp_path, monkeypatch): """Test split files are read correctly.""" # load raw test file, extend it to be larger than 2gb, and save it bids_root = tmp_path / 'bids' tmp_dir = tmp_path / 'tmp' tmp_dir.mkdir() bids_path = _bids_path.copy().update(root=bids_root, datatype='meg') raw = _read_raw_fif(raw_fname, verbose=False) bids_path.update(acquisition=None) write_raw_bids(raw, bids_path, verbose=False) bids_path.update(acquisition='01') n_channels = len(raw.ch_names) n_times = int(2.5e6 / n_channels) # enough to produce a 10MB split data = np.empty((n_channels, n_times), dtype=np.float32) raw = mne.io.RawArray(data, raw.info) big_fif_fname = pathlib.Path(tmp_dir) / 'test_raw.fif' split_size = '10MB' raw.save(big_fif_fname, split_size=split_size) raw = _read_raw_fif(big_fif_fname, verbose=False) with monkeypatch.context() as m: # Force MNE-BIDS to split at 10MB m.setattr(mne_bids.write, '_FIFF_SPLIT_SIZE', split_size) write_raw_bids(raw, bids_path, verbose=False) # test whether split raw files were read correctly raw1 = read_raw_bids(bids_path=bids_path) assert 'split-01' in str(bids_path.fpath) bids_path.update(split='01') raw2 = read_raw_bids(bids_path=bids_path) bids_path.update(split='02') raw3 = read_raw_bids(bids_path=bids_path) assert len(raw) == len(raw1) assert len(raw) == len(raw2) assert len(raw) > len(raw3) # check that split files both appear in scans.tsv scans_tsv = BIDSPath( subject=subject_id, session=session_id, suffix='scans', extension='.tsv', root=bids_root) scan_data = _from_tsv(scans_tsv) scan_fnames = scan_data['filename'] scan_acqtime = scan_data['acq_time'] assert len(scan_fnames) == 3 assert 'split-01' in scan_fnames[0] and 'split-02' in scan_fnames[1] # check that the acq_times in scans.tsv are the same assert scan_acqtime[0] == scan_acqtime[1] # check the recordings are in the correct order assert raw2.first_time < raw3.first_time # check whether non-matching acq_times are caught scan_data['acq_time'][0] = scan_acqtime[0].split('.')[0] _to_tsv(scan_data, scans_tsv) with pytest.raises(ValueError, match='Split files must have the same acq_time.'): read_raw_bids(bids_path) # reset scans.tsv file for downstream tests scan_data['acq_time'][0] = scan_data['acq_time'][1] _to_tsv(scan_data, scans_tsv)
def test_get_head_mri_trans(): """Test getting a trans object from BIDS data.""" import nibabel as nib event_id = { 'Auditory/Left': 1, 'Auditory/Right': 2, 'Visual/Left': 3, 'Visual/Right': 4, 'Smiley': 5, 'Button': 32 } events_fname = op.join(data_path, 'MEG', 'sample', 'sample_audvis_trunc_raw-eve.fif') # Write it to BIDS raw = mne.io.read_raw_fif(raw_fname) bids_root = _TempDir() with pytest.warns(RuntimeWarning, match='No line frequency'): write_raw_bids(raw, bids_basename, bids_root, events_data=events_fname, event_id=event_id, overwrite=False) # We cannot recover trans, if no MRI has yet been written with pytest.raises(RuntimeError): estimated_trans = get_head_mri_trans(bids_basename=bids_basename, bids_root=bids_root) # Write some MRI data and supply a `trans` so that a sidecar gets written trans = mne.read_trans(raw_fname.replace('_raw.fif', '-trans.fif')) # Get the T1 weighted MRI data file ... test write_anat with a nibabel # image instead of a file path t1w_mgh = op.join(data_path, 'subjects', 'sample', 'mri', 'T1.mgz') t1w_mgh = nib.load(t1w_mgh) anat_dir = write_anat(bids_root, subject_id, t1w_mgh, session_id, acq, raw=raw, trans=trans, verbose=True) # Try to get trans back through fitting points estimated_trans = get_head_mri_trans(bids_basename=bids_basename, bids_root=bids_root) assert trans['from'] == estimated_trans['from'] assert trans['to'] == estimated_trans['to'] assert_almost_equal(trans['trans'], estimated_trans['trans']) print(trans) print(estimated_trans) # provoke an error by introducing NaNs into MEG coords with pytest.raises(RuntimeError, match='AnatomicalLandmarkCoordinates'): raw.info['dig'][0]['r'] = np.ones(3) * np.nan sh.rmtree(anat_dir) write_anat(bids_root, subject_id, t1w_mgh, session_id, acq, raw=raw, trans=trans, verbose=True) estimated_trans = get_head_mri_trans(bids_basename=bids_basename, bids_root=bids_root)
def test_get_head_mri_trans(tmp_path): """Test getting a trans object from BIDS data.""" import nibabel as nib event_id = {'Auditory/Left': 1, 'Auditory/Right': 2, 'Visual/Left': 3, 'Visual/Right': 4, 'Smiley': 5, 'Button': 32} events_fname = op.join(data_path, 'MEG', 'sample', 'sample_audvis_trunc_raw-eve.fif') subjects_dir = op.join(data_path, 'subjects') # Drop unknown events. events = mne.read_events(events_fname) events = events[events[:, 2] != 0] # Write it to BIDS raw = _read_raw_fif(raw_fname) bids_path = _bids_path.copy().update( root=tmp_path, datatype='meg', suffix='meg' ) write_raw_bids(raw, bids_path, events_data=events, event_id=event_id, overwrite=False) # We cannot recover trans if no MRI has yet been written with pytest.raises(FileNotFoundError, match='Did not find'): estimated_trans = get_head_mri_trans( bids_path=bids_path, fs_subject='sample', fs_subjects_dir=subjects_dir) # Write some MRI data and supply a `trans` so that a sidecar gets written trans = mne.read_trans(raw_fname.replace('_raw.fif', '-trans.fif')) # Get the T1 weighted MRI data file ... test write_anat with a nibabel # image instead of a file path t1w_mgh = op.join(data_path, 'subjects', 'sample', 'mri', 'T1.mgz') t1w_mgh = nib.load(t1w_mgh) landmarks = get_anat_landmarks( t1w_mgh, raw.info, trans, fs_subject='sample', fs_subjects_dir=subjects_dir) t1w_bids_path = bids_path.copy().update( datatype='anat', suffix='T1w' ) t1w_bids_path = write_anat( t1w_mgh, bids_path=t1w_bids_path, landmarks=landmarks, verbose=True ) anat_dir = t1w_bids_path.directory # Try to get trans back through fitting points estimated_trans = get_head_mri_trans( bids_path=bids_path, fs_subject='sample', fs_subjects_dir=subjects_dir) assert trans['from'] == estimated_trans['from'] assert trans['to'] == estimated_trans['to'] assert_almost_equal(trans['trans'], estimated_trans['trans']) # provoke an error by introducing NaNs into MEG coords raw.info['dig'][0]['r'] = np.full(3, np.nan) sh.rmtree(anat_dir) bad_landmarks = get_anat_landmarks(t1w_mgh, raw.info, trans, 'sample', op.join(data_path, 'subjects')) write_anat(t1w_mgh, bids_path=t1w_bids_path, landmarks=bad_landmarks) with pytest.raises(RuntimeError, match='AnatomicalLandmarkCoordinates'): estimated_trans = get_head_mri_trans(bids_path=t1w_bids_path, fs_subject='sample', fs_subjects_dir=subjects_dir) # test raw with no fiducials to provoke error t1w_bids_path = write_anat( # put back t1w_mgh, bids_path=t1w_bids_path, landmarks=landmarks, overwrite=True ) montage = raw.get_montage() montage.remove_fiducials() raw_test = raw.copy() raw_test.set_montage(montage) raw_test.save(bids_path.fpath, overwrite=True) with pytest.raises(RuntimeError, match='Could not extract fiducial'): get_head_mri_trans(bids_path=bids_path, fs_subject='sample', fs_subjects_dir=subjects_dir) # test we are permissive for different casings of landmark names in the # sidecar, and also accept "nasion" instead of just "NAS" raw = _read_raw_fif(raw_fname) write_raw_bids(raw, bids_path, events_data=events, event_id=event_id, overwrite=True) # overwrite with new acq t1w_bids_path = write_anat( t1w_mgh, bids_path=t1w_bids_path, landmarks=landmarks, overwrite=True ) t1w_json_fpath = t1w_bids_path.copy().update(extension='.json').fpath with t1w_json_fpath.open('r', encoding='utf-8') as f: t1w_json = json.load(f) coords = t1w_json['AnatomicalLandmarkCoordinates'] coords['lpa'] = coords['LPA'] coords['Rpa'] = coords['RPA'] coords['Nasion'] = coords['NAS'] del coords['LPA'], coords['RPA'], coords['NAS'] _write_json(t1w_json_fpath, t1w_json, overwrite=True) estimated_trans = get_head_mri_trans( bids_path=bids_path, fs_subject='sample', fs_subjects_dir=subjects_dir) assert_almost_equal(trans['trans'], estimated_trans['trans']) # Test t1_bids_path parameter # # Case 1: different BIDS roots meg_bids_path = _bids_path.copy().update( root=tmp_path / 'meg_root', datatype='meg', suffix='meg' ) t1_bids_path = _bids_path.copy().update( root=tmp_path / 'mri_root', task=None, run=None ) raw = _read_raw_fif(raw_fname) write_raw_bids(raw, bids_path=meg_bids_path) landmarks = get_anat_landmarks( t1w_mgh, raw.info, trans, fs_subject='sample', fs_subjects_dir=subjects_dir) write_anat(t1w_mgh, bids_path=t1_bids_path, landmarks=landmarks) read_trans = get_head_mri_trans( bids_path=meg_bids_path, t1_bids_path=t1_bids_path, fs_subject='sample', fs_subjects_dir=subjects_dir) assert np.allclose(trans['trans'], read_trans['trans']) # Case 2: different sessions raw = _read_raw_fif(raw_fname) meg_bids_path = _bids_path.copy().update( root=tmp_path / 'session_test', session='01', datatype='meg', suffix='meg' ) t1_bids_path = meg_bids_path.copy().update( session='02', task=None, run=None, datatype='anat', suffix='T1w' ) write_raw_bids(raw, bids_path=meg_bids_path) write_anat(t1w_mgh, bids_path=t1_bids_path, landmarks=landmarks) read_trans = get_head_mri_trans( bids_path=meg_bids_path, t1_bids_path=t1_bids_path, fs_subject='sample', fs_subjects_dir=subjects_dir) assert np.allclose(trans['trans'], read_trans['trans']) # Test that incorrect subject directory throws error with pytest.raises(ValueError, match='Could not find'): estimated_trans = get_head_mri_trans( bids_path=bids_path, fs_subject='bad', fs_subjects_dir=subjects_dir) # Case 3: write with suffix for kind landmarks2 = landmarks.copy() landmarks2.dig[0]['r'] *= -1 landmarks2.save(tmp_path / 'landmarks2.fif') landmarks2 = tmp_path / 'landmarks2.fif' write_anat(t1w_mgh, bids_path=t1_bids_path, overwrite=True, deface=True, landmarks={"coreg": landmarks, "deface": landmarks2}) read_trans1 = get_head_mri_trans( bids_path=meg_bids_path, t1_bids_path=t1_bids_path, fs_subject='sample', fs_subjects_dir=subjects_dir, kind="coreg") assert np.allclose(trans['trans'], read_trans1['trans']) read_trans2 = get_head_mri_trans( bids_path=meg_bids_path, t1_bids_path=t1_bids_path, fs_subject='sample', fs_subjects_dir=subjects_dir, kind="deface") assert not np.allclose(trans['trans'], read_trans2['trans']) # Test we're respecting existing suffix & data type # The following path is supposed to mimic a derivative generated by the # MNE-BIDS-Pipeline. # # XXX We MAY want to revise this once the BIDS-Pipeline produces more # BIDS-compatible output, e.g. including `channels.tsv` files for written # Raw data etc. raw = _read_raw_fif(raw_fname) deriv_root = tmp_path / 'derivatives' / 'mne-bids-pipeline' electrophys_path = ( deriv_root / 'sub-01' / 'eeg' / 'sub-01_task-av_proc-filt_raw.fif' ) electrophys_path.parent.mkdir(parents=True) raw.save(electrophys_path) electrophys_bids_path = BIDSPath( subject='01', task='av', datatype='eeg', processing='filt', suffix='raw', extension='.fif', root=deriv_root, check=False ) t1_bids_path = _bids_path.copy().update( root=tmp_path / 'mri_root', task=None, run=None ) with pytest.warns(RuntimeWarning, match='Did not find any channels.tsv'): get_head_mri_trans( bids_path=electrophys_bids_path, t1_bids_path=t1_bids_path, fs_subject='sample', fs_subjects_dir=subjects_dir ) # bids_path without datatype is deprecated bids_path = electrophys_bids_path.copy().update(datatype=None) with pytest.raises(FileNotFoundError): # defaut location is all wrong! with pytest.warns(DeprecationWarning, match='no datatype'): get_head_mri_trans( bids_path=bids_path, t1_bids_path=t1_bids_path, fs_subject='sample', fs_subjects_dir=subjects_dir ) # bids_path without suffix is deprecated bids_path = electrophys_bids_path.copy().update(suffix=None) with pytest.raises(FileNotFoundError): # defaut location is all wrong! with pytest.warns(DeprecationWarning, match='no datatype'): get_head_mri_trans( bids_path=bids_path, t1_bids_path=t1_bids_path, fs_subject='sample', fs_subjects_dir=subjects_dir ) # Should fail for an unsupported coordinate frame raw = _read_raw_fif(raw_fname) bids_root = tmp_path / 'unsupported_coord_frame' bids_path = BIDSPath( subject='01', task='av', datatype='meg', suffix='meg', extension='.fif', root=bids_root ) t1_bids_path = _bids_path.copy().update( root=tmp_path / 'mri_root', task=None, run=None ) write_raw_bids(raw=raw, bids_path=bids_path, verbose=False)
def test_read_participants_data(): """Test reading information from a BIDS sidecar.json file.""" bids_root = _TempDir() raw = mne.io.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_basename, bids_root, overwrite=True, verbose=False) raw = read_raw_bids(bids_basename=bids_basename, bids_root=bids_root, kind='meg') 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 # if modifying participants tsv, then read_raw_bids reflects that participants_tsv_fpath = op.join(bids_root, '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_basename=bids_basename, bids_root=Path(bids_root), kind='meg') 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_basename=bids_basename, bids_root=Path(bids_root), kind='meg') 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 = mne.io.read_raw_fif(raw_fname, verbose=False) write_raw_bids(raw, bids_basename, bids_root, overwrite=True, verbose=False) os.remove(participants_tsv_fpath) with pytest.warns(RuntimeWarning, match='Participants file not found'): raw = read_raw_bids(bids_basename=bids_basename, bids_root=Path(bids_root), kind='meg') assert raw.info['subject_info'] is None
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 test_read_raw_bids_pathlike(tmpdir): """Test that read_raw_bids() can handle a Path-like bids_root.""" bids_path = _bids_path.copy().update(root=tmpdir, datatype='meg') raw = _read_raw_fif(raw_fname, verbose=False) write_raw_bids(raw, bids_path, overwrite=True, verbose=False) raw = read_raw_bids(bids_path=bids_path)
def test_handle_ieeg_coords_reading(bids_path, tmpdir): """Test reading iEEG coordinates from BIDS files.""" data_path = op.join(testing.data_path(), 'EDF') raw_fname = op.join(data_path, 'test_reduced.edf') bids_fname = bids_path.copy().update(datatype='ieeg', suffix='ieeg', extension='.edf', root=tmpdir) raw = _read_raw_edf(raw_fname) # ensure we are writing 'ecog'/'ieeg' data raw.set_channel_types({ch: 'ecog' for ch in raw.ch_names}) # coordinate frames in mne-python should all map correctly # 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)) coordinate_frames = ['mni_tal'] for coord_frame in coordinate_frames: # XXX: mne-bids doesn't support multiple electrodes.tsv files sh.rmtree(tmpdir) montage = mne.channels.make_dig_montage(ch_pos=ch_pos, coord_frame=coord_frame) raw.set_montage(montage) write_raw_bids(raw, bids_fname, overwrite=True, verbose=False) # read in raw file w/ updated coordinate frame # and make sure all digpoints are correct coordinate frames raw_test = read_raw_bids(bids_path=bids_fname, verbose=False) coord_frame_int = MNE_STR_TO_FRAME[coord_frame] for digpoint in raw_test.info['dig']: assert digpoint['coord_frame'] == coord_frame_int # start w/ new bids root sh.rmtree(tmpdir) write_raw_bids(raw, bids_fname, overwrite=True, verbose=False) # obtain the sensor positions and assert ch_coords are same raw_test = read_raw_bids(bids_path=bids_fname, verbose=False) 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']) # read in the data and assert montage is the same # regardless of 'm', 'cm', 'mm', or 'pixel' scalings = {'m': 1, 'cm': 100, 'mm': 1000} bids_fname.update(root=tmpdir) coordsystem_fname = _find_matching_sidecar(bids_fname, suffix='coordsystem', extension='.json') electrodes_fname = _find_matching_sidecar(bids_fname, suffix='electrodes', extension='.tsv') orig_electrodes_dict = _from_tsv(electrodes_fname, [str, float, float, float, str]) # not BIDS specified should not be read coord_unit = 'km' scaling = 0.001 _update_sidecar(coordsystem_fname, 'iEEGCoordinateUnits', coord_unit) electrodes_dict = _from_tsv(electrodes_fname, [str, float, float, float, str]) for axis in ['x', 'y', 'z']: electrodes_dict[axis] = \ np.multiply(orig_electrodes_dict[axis], scaling) _to_tsv(electrodes_dict, electrodes_fname) with pytest.warns(RuntimeWarning, match='Coordinate unit is not ' 'an accepted BIDS unit'): raw_test = read_raw_bids(bids_path=bids_fname, verbose=False) # correct BIDS units should scale to meters properly for coord_unit, scaling in scalings.items(): # update coordinate SI units _update_sidecar(coordsystem_fname, 'iEEGCoordinateUnits', coord_unit) electrodes_dict = _from_tsv(electrodes_fname, [str, float, float, float, str]) for axis in ['x', 'y', 'z']: electrodes_dict[axis] = \ np.multiply(orig_electrodes_dict[axis], scaling) _to_tsv(electrodes_dict, electrodes_fname) # read in raw file w/ updated montage raw_test = read_raw_bids(bids_path=bids_fname, verbose=False) # obtain the sensor positions and make sure they're the same assert_dig_allclose(raw.info, raw_test.info) # XXX: Improve by changing names to 'unknown' coordframe (needs mne PR) # check that coordinate systems other coordinate systems should be named # in the file and not the CoordinateSystem, which is reserved for keywords coordinate_frames = ['Other'] for coord_frame in coordinate_frames: # update coordinate units _update_sidecar(coordsystem_fname, 'iEEGCoordinateSystem', coord_frame) # read in raw file w/ updated coordinate frame # and make sure all digpoints are MRI coordinate frame with pytest.warns(RuntimeWarning, match="Defaulting coordinate " "frame to unknown"): raw_test = read_raw_bids(bids_path=bids_fname, verbose=False) assert raw_test.info['dig'] is not None # check that standard template identifiers that are unsupported in # mne-python coordinate frames, still get read in, but produce a warning coordinate_frames = [ 'individual', 'fsnative', 'scanner', 'ICBM452AirSpace', 'NIHPD' ] for coord_frame in coordinate_frames: # update coordinate units _update_sidecar(coordsystem_fname, 'iEEGCoordinateSystem', coord_frame) # read in raw file w/ updated coordinate frame # and make sure all digpoints are MRI coordinate frame with pytest.warns(RuntimeWarning, match=f"iEEG Coordinate frame {coord_frame} " f"is not a readable BIDS keyword "): raw_test = read_raw_bids(bids_path=bids_fname, verbose=False) assert raw_test.info['dig'] is not None # ACPC should be read in as RAS for iEEG _update_sidecar(coordsystem_fname, 'iEEGCoordinateSystem', 'ACPC') raw_test = read_raw_bids(bids_path=bids_fname, verbose=False) coord_frame_int = MNE_STR_TO_FRAME['mri'] for digpoint in raw_test.info['dig']: assert digpoint['coord_frame'] == coord_frame_int # if we delete the coordsystem.json file, an error will be raised os.remove(coordsystem_fname) with pytest.raises(RuntimeError, match='BIDS mandates that ' 'the coordsystem.json'): raw = read_raw_bids(bids_path=bids_fname, verbose=False) # test error message if electrodes is not a subset of Raw bids_path.update(root=tmpdir) write_raw_bids(raw, bids_path, overwrite=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) # popping off channels should not result in an error # however, a warning will be raised through mne-python with pytest.warns(RuntimeWarning, match='DigMontage is ' 'only a subset of info'): read_raw_bids(bids_path=bids_fname, verbose=False) # make sure montage is set if there are coordinates w/ 'n/a' raw.info['bads'] = [] write_raw_bids(raw, bids_path, overwrite=True, verbose=False) electrodes_dict = _from_tsv(electrodes_fname) for axis in ['x', 'y', 'z']: electrodes_dict[axis][0] = 'n/a' electrodes_dict[axis][3] = 'n/a' _to_tsv(electrodes_dict, electrodes_fname) # test if montage is correctly set via mne-bids # electrode coordinates should be nan # when coordinate is 'n/a' nan_chs = [electrodes_dict['name'][i] for i in [0, 3]] with pytest.warns(RuntimeWarning, match='There are channels ' 'without locations'): raw = read_raw_bids(bids_path=bids_fname, verbose=False) for idx, ch in enumerate(raw.info['chs']): if ch['ch_name'] in nan_chs: assert all(np.isnan(ch['loc'][:3])) else: assert not any(np.isnan(ch['loc'][:3])) assert ch['ch_name'] not in raw.info['bads']
def test_handle_info_reading(tmpdir): """Test reading information from a BIDS sidecar JSON file.""" # read in USA dataset, so it should find 50 Hz raw = _read_raw_fif(raw_fname) # write copy of raw with line freq of 60 # bids basename and fname bids_path = BIDSPath(subject='01', session='01', task='audiovisual', run='01', root=tmpdir) suffix = "meg" bids_fname = bids_path.copy().update(suffix=suffix, extension='.fif') write_raw_bids(raw, bids_path, overwrite=True) # find sidecar JSON fname bids_fname.update(datatype=suffix) sidecar_fname = _find_matching_sidecar(bids_fname, suffix=suffix, extension='.json') # assert that we get the same line frequency set raw = read_raw_bids(bids_path=bids_path) assert raw.info['line_freq'] == 60 # setting line_freq to None should produce 'n/a' in the JSON sidecar raw.info['line_freq'] = None write_raw_bids(raw, bids_path, overwrite=True) raw = read_raw_bids(bids_path=bids_path) assert raw.info['line_freq'] is None with open(sidecar_fname, 'r', encoding='utf-8') as fin: sidecar_json = json.load(fin) assert sidecar_json["PowerLineFrequency"] == 'n/a' # 2. if line frequency is not set in raw file, then ValueError del raw.info['line_freq'] with pytest.raises(ValueError, match="PowerLineFrequency .* required"): write_raw_bids(raw, bids_path, overwrite=True) # check whether there are "Extra points" in raw.info['dig'] if # DigitizedHeadPoints is set to True and not otherwise n_dig_points = 0 for dig_point in raw.info['dig']: if dig_point['kind'] == FIFF.FIFFV_POINT_EXTRA: n_dig_points += 1 if sidecar_json['DigitizedHeadPoints']: assert n_dig_points > 0 else: assert n_dig_points == 0 # check whether any of NAS/LPA/RPA are present in raw.info['dig'] # DigitizedLandmark is set to True, and False otherwise landmark_present = False for dig_point in raw.info['dig']: if dig_point['kind'] in [ FIFF.FIFFV_POINT_LPA, FIFF.FIFFV_POINT_RPA, FIFF.FIFFV_POINT_NASION ]: landmark_present = True break if landmark_present: assert sidecar_json['DigitizedLandmarks'] is True else: assert sidecar_json['DigitizedLandmarks'] is False # make a copy of the sidecar in "derivatives/" # to check that we make sure we always get the right sidecar # in addition, it should not break the sidecar reading # in `read_raw_bids` raw.info['line_freq'] = 60 write_raw_bids(raw, bids_path, overwrite=True) deriv_dir = tmpdir.mkdir("derivatives") sidecar_copy = deriv_dir / op.basename(sidecar_fname) with open(sidecar_fname, "r", encoding='utf-8') as fin: sidecar_json = json.load(fin) sidecar_json["PowerLineFrequency"] = 45 _write_json(sidecar_copy, sidecar_json) raw = read_raw_bids(bids_path=bids_path) assert raw.info['line_freq'] == 60 # 3. assert that we get an error when sidecar json doesn't match _update_sidecar(sidecar_fname, "PowerLineFrequency", 55) with pytest.warns(RuntimeWarning, match="Defaulting to .* sidecar JSON"): raw = read_raw_bids(bids_path=bids_path) assert raw.info['line_freq'] == 55
def test_get_head_mri_trans(tmpdir): """Test getting a trans object from BIDS data.""" import nibabel as nib event_id = { 'Auditory/Left': 1, 'Auditory/Right': 2, 'Visual/Left': 3, 'Visual/Right': 4, 'Smiley': 5, 'Button': 32 } events_fname = op.join(data_path, 'MEG', 'sample', 'sample_audvis_trunc_raw-eve.fif') # Drop unknown events. events = mne.read_events(events_fname) events = events[events[:, 2] != 0] # Write it to BIDS raw = _read_raw_fif(raw_fname) bids_path = _bids_path.copy().update(root=tmpdir) write_raw_bids(raw, bids_path, events_data=events, event_id=event_id, overwrite=False) # We cannot recover trans, if no MRI has yet been written with pytest.raises(RuntimeError): estimated_trans = get_head_mri_trans(bids_path=bids_path) # Write some MRI data and supply a `trans` so that a sidecar gets written trans = mne.read_trans(raw_fname.replace('_raw.fif', '-trans.fif')) # Get the T1 weighted MRI data file ... test write_anat with a nibabel # image instead of a file path t1w_mgh = op.join(data_path, 'subjects', 'sample', 'mri', 'T1.mgz') t1w_mgh = nib.load(t1w_mgh) t1w_bidspath = BIDSPath(subject=subject_id, session=session_id, acquisition=acq, root=tmpdir) t1w_bidspath = write_anat(t1w_mgh, bids_path=t1w_bidspath, raw=raw, trans=trans, verbose=True) anat_dir = t1w_bidspath.directory # Try to get trans back through fitting points estimated_trans = get_head_mri_trans(bids_path=bids_path) assert trans['from'] == estimated_trans['from'] assert trans['to'] == estimated_trans['to'] assert_almost_equal(trans['trans'], estimated_trans['trans']) print(trans) print(estimated_trans) # provoke an error by introducing NaNs into MEG coords with pytest.raises(RuntimeError, match='AnatomicalLandmarkCoordinates'): raw.info['dig'][0]['r'] = np.ones(3) * np.nan sh.rmtree(anat_dir) bids_path = write_anat(t1w_mgh, bids_path=t1w_bidspath, raw=raw, trans=trans, verbose=True) estimated_trans = get_head_mri_trans(bids_path=bids_path)
def test_handle_eeg_coords_reading(tmp_path): """Test reading iEEG coordinates from BIDS files.""" bids_path = BIDSPath( subject=subject_id, session=session_id, run=run, acquisition=acq, task=task, root=tmp_path) data_path = op.join(testing.data_path(), 'EDF') raw_fname = op.join(data_path, 'test_reduced.edf') raw = _read_raw_edf(raw_fname) # ensure we are writing 'eeg' data raw.set_channel_types({ch: 'eeg' 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)) # # create montage in 'unknown' coordinate frame # # and assert coordsystem/electrodes sidecar tsv don't exist montage = mne.channels.make_dig_montage(ch_pos=ch_pos, coord_frame="unknown") raw.set_montage(montage) with pytest.raises(RuntimeError, match="'head' coordinate frame " "must contain"): write_raw_bids(raw, bids_path, overwrite=True) bids_path.update(root=tmp_path) coordsystem_fname = _find_matching_sidecar(bids_path, suffix='coordsystem', extension='.json', on_error='warn') electrodes_fname = _find_matching_sidecar(bids_path, suffix='electrodes', extension='.tsv', on_error='warn') assert coordsystem_fname is None assert electrodes_fname is None # create montage in head frame and set should result in # an error if landmarks are not set montage = mne.channels.make_dig_montage(ch_pos=ch_pos, coord_frame="head") raw.set_montage(montage) with pytest.raises(RuntimeError, match="'head' coordinate frame " "must contain"): write_raw_bids(raw, bids_path, overwrite=True) montage = mne.channels.make_dig_montage(ch_pos=ch_pos, coord_frame="head", nasion=[1, 0, 0], lpa=[0, 1, 0], rpa=[0, 0, 1]) raw.set_montage(montage) write_raw_bids(raw, bids_path, overwrite=True) # obtain the sensor positions and assert ch_coords are same raw_test = read_raw_bids(bids_path, verbose=True) assert not object_diff(raw.info['chs'], raw_test.info['chs']) # modify coordinate frame to not-captrak coordsystem_fname = _find_matching_sidecar(bids_path, suffix='coordsystem', extension='.json') _update_sidecar(coordsystem_fname, 'EEGCoordinateSystem', 'besa') with pytest.warns(RuntimeWarning, match='is not a BIDS-acceptable ' 'coordinate frame for EEG'): raw_test = read_raw_bids(bids_path) assert raw_test.info['dig'] is None