def run_maxwell_filter(subject, session=None): if config.proc and 'sss' in config.proc and config.use_maxwell_filter: raise ValueError(f'You cannot set use_maxwell_filter to True ' f'if data have already processed with Maxwell-filter.' f' Got proc={config.proc}.') bids_path_in = BIDSPath(subject=subject, session=session, task=config.get_task(), acquisition=config.acq, processing=config.proc, recording=config.rec, space=config.space, suffix=config.get_datatype(), datatype=config.get_datatype(), root=config.bids_root) bids_path_out = bids_path_in.copy().update(suffix='raw', root=config.deriv_root, check=False) # Load dev_head_t and digitization points from MaxFilter reference run. # Re-use in all runs and for processing empty-room recording. if config.use_maxwell_filter: reference_run = config.get_mf_reference_run() msg = f'Loading reference run: {reference_run}.' logger.info( gen_log_message(message=msg, step=1, subject=subject, session=session)) bids_path_in.update(run=reference_run) info = mne.io.read_info(bids_path_in.fpath) dev_head_t = info['dev_head_t'] dig = info['dig'] del reference_run, info for run_idx, run in enumerate(config.get_runs()): bids_path_in.update(run=run) bids_path_out.update(run=run) raw = load_data(bids_path_in) # Fix stimulation artifact if config.fix_stim_artifact: events, _ = mne.events_from_annotations(raw) raw = mne.preprocessing.fix_stim_artifact( raw, events=events, event_id=None, tmin=config.stim_artifact_tmin, tmax=config.stim_artifact_tmax, mode='linear') # Auto-detect bad channels. if config.find_flat_channels_meg or config.find_noisy_channels_meg: find_bad_channels(raw=raw, subject=subject, session=session, task=config.get_task(), run=run) # Maxwell-filter experimental data. if config.use_maxwell_filter: msg = 'Applying Maxwell filter to experimental data.' logger.info( gen_log_message(message=msg, step=1, subject=subject, session=session)) # Warn if no bad channels are set before Maxwell filter if not raw.info['bads']: msg = '\nFound no bad channels. \n ' logger.warning( gen_log_message(message=msg, subject=subject, step=1, session=session)) if config.mf_st_duration: msg = ' st_duration=%d' % (config.mf_st_duration) logger.info( gen_log_message(message=msg, step=1, subject=subject, session=session)) # Keyword arguments shared between Maxwell filtering of the # experimental and the empty-room data. common_mf_kws = dict(calibration=get_mf_cal_fname( subject, session), cross_talk=get_mf_ctc_fname(subject, session), st_duration=config.mf_st_duration, origin=config.mf_head_origin, coord_frame='head', destination=dev_head_t) raw_sss = mne.preprocessing.maxwell_filter(raw, **common_mf_kws) raw_out = raw_sss raw_fname_out = (bids_path_out.copy().update(processing='sss', extension='.fif')) elif config.ch_types == ['eeg']: msg = 'Not applying Maxwell filter to EEG data.' logger.info( gen_log_message(message=msg, step=1, subject=subject, session=session)) raw_out = raw raw_fname_out = bids_path_out.copy().update(extension='.fif') else: msg = ('Not applying Maxwell filter.\nIf you wish to apply it, ' 'set use_maxwell_filter=True in your configuration.') logger.info( gen_log_message(message=msg, step=1, subject=subject, session=session)) raw_out = raw raw_fname_out = bids_path_out.copy().update(extension='.fif') # Save only the channel types we wish to analyze (including the # channels marked as "bad"). # We do not run `raw_out.pick()` here because it uses too much memory. chs_to_include = config.get_channels_to_analyze(raw_out.info) raw_out.save(raw_fname_out, picks=chs_to_include, overwrite=True, split_naming='bids') del raw_out if config.interactive: # Load the data we have just written, because it contains only # the relevant channels. raw = mne.io.read_raw_fif(raw_fname_out, allow_maxshield=True) raw.plot(n_channels=50, butterfly=True) # Empty-room processing. # # We pick the empty-room recording closest in time to the first run # of the experimental session. if run_idx == 0 and config.process_er: msg = 'Processing empty-room recording …' logger.info( gen_log_message(step=1, subject=subject, session=session, message=msg)) bids_path_er_in = bids_path_in.find_empty_room() raw_er = load_data(bids_path_er_in) raw_er.info['bads'] = [ ch for ch in raw.info['bads'] if ch.startswith('MEG') ] # Maxwell-filter empty-room data. if config.use_maxwell_filter: msg = 'Applying Maxwell filter to empty-room recording' logger.info( gen_log_message(message=msg, step=1, subject=subject, session=session)) # We want to ensure we use the same coordinate frame origin in # empty-room and experimental data processing. To do this, we # inject the sensor locations and the head <> device transform # into the empty-room recording's info, and leave all other # parameters the same as for the experimental data. This is not # very clean, as we normally should not alter info manually, # except for info['bads']. Will need improvement upstream in # MNE-Python. raw_er.info['dig'] = dig raw_er.info['dev_head_t'] = dev_head_t raw_er_sss = mne.preprocessing.maxwell_filter( raw_er, **common_mf_kws) # Perform a sanity check: empty-room rank should match the # experimental data rank after Maxwell filtering. rank_exp = mne.compute_rank(raw, rank='info')['meg'] rank_er = mne.compute_rank(raw_er, rank='info')['meg'] if not np.isclose(rank_exp, rank_er): msg = (f'Experimental data rank {rank_exp:.1f} does not ' f'match empty-room data rank {rank_er:.1f} after ' f'Maxwell filtering. This indicates that the data ' f'were processed differenlty.') raise RuntimeError(msg) raw_er_out = raw_er_sss raw_er_fname_out = bids_path_out.copy().update( processing='sss') else: raw_er_out = raw_er raw_er_fname_out = bids_path_out.copy() raw_er_fname_out = raw_er_fname_out.update(task='noise', extension='.fif', run=None) # Save only the channel types we wish to analyze # (same as for experimental data above). raw_er_out.save(raw_er_fname_out, picks=chs_to_include, overwrite=True, split_naming='bids') del raw_er_out
def test_find_empty_room(return_bids_test_dir): """Test reading of empty room data.""" data_path = testing.data_path() raw_fname = op.join(data_path, 'MEG', 'sample', 'sample_audvis_trunc_raw.fif') bids_root = _TempDir() tmp_dir = _TempDir() raw = _read_raw_fif(raw_fname) bids_path = BIDSPath(subject='01', session='01', task='audiovisual', run='01', root=bids_root, suffix='meg') write_raw_bids(raw, bids_path, overwrite=True) # No empty-room data present. er_basename = bids_path.find_empty_room() assert er_basename is None # Now create data resembling an empty-room recording. # The testing data has no "noise" recording, so save the actual data # as named as if it were noise. We first need to write the FIFF file # before reading it back in. er_raw_fname = op.join(tmp_dir, 'ernoise_raw.fif') raw.copy().crop(0, 10).save(er_raw_fname, overwrite=True) er_raw = _read_raw_fif(er_raw_fname) if not isinstance(er_raw.info['meas_date'], datetime): # mne < v0.20 er_date = datetime.fromtimestamp(er_raw.info['meas_date'][0]) else: er_date = er_raw.info['meas_date'] er_date = er_date.strftime('%Y%m%d') er_bids_path = BIDSPath(subject='emptyroom', task='noise', session=er_date, suffix='meg', root=bids_root) write_raw_bids(er_raw, er_bids_path, overwrite=True) recovered_er_bids_path = bids_path.find_empty_room() assert er_bids_path == recovered_er_bids_path # 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_path.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_path) best_er_basename = bids_path.find_empty_room() assert best_er_basename.session == '20021204' with pytest.raises(ValueError, match='The root of the "bids_path" must be set'): bids_path.copy().update(root=None).find_empty_room() # assert that we get error if meas_date is not available. raw = read_raw_bids(bids_path=bids_path) 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_path, overwrite=True) with pytest.raises(ValueError, match='The provided recording does not ' 'have a measurement date set'): bids_path.find_empty_room()
def test_find_empty_room(return_bids_test_dir, tmpdir): """Test reading of empty room data.""" data_path = testing.data_path() raw_fname = op.join(data_path, 'MEG', 'sample', 'sample_audvis_trunc_raw.fif') bids_root = tmpdir.mkdir("bids") tmp_dir = tmpdir.mkdir("tmp") raw = _read_raw_fif(raw_fname) bids_path = BIDSPath(subject='01', session='01', task='audiovisual', run='01', root=bids_root, suffix='meg') write_raw_bids(raw, bids_path, overwrite=True) # No empty-room data present. er_basename = bids_path.find_empty_room() assert er_basename is None # Now create data resembling an empty-room recording. # The testing data has no "noise" recording, so save the actual data # as named as if it were noise. We first need to write the FIFF file # before reading it back in. er_raw_fname = op.join(tmp_dir, 'ernoise_raw.fif') raw.copy().crop(0, 10).save(er_raw_fname, overwrite=True) er_raw = _read_raw_fif(er_raw_fname) if not isinstance(er_raw.info['meas_date'], datetime): # pragma: no cover # mne < v0.20 er_date = datetime.fromtimestamp(er_raw.info['meas_date'][0]) else: er_date = er_raw.info['meas_date'] er_date = er_date.strftime('%Y%m%d') er_bids_path = BIDSPath(subject='emptyroom', task='noise', session=er_date, suffix='meg', root=bids_root) write_raw_bids(er_raw, er_bids_path, overwrite=True) recovered_er_bids_path = bids_path.find_empty_room() assert er_bids_path == recovered_er_bids_path # 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_path.update(session=date) er_meas_date = datetime.strptime(date, '%Y%m%d') er_meas_date = er_meas_date.replace(tzinfo=timezone.utc) er_raw.set_meas_date(er_meas_date) write_raw_bids(er_raw, er_bids_path) best_er_basename = bids_path.find_empty_room() assert best_er_basename.session == '20021204' with pytest.raises(ValueError, match='The root of the "bids_path" must be set'): bids_path.copy().update(root=None).find_empty_room() # assert that we get an error if meas_date is not available. raw = read_raw_bids(bids_path=bids_path) raw.set_meas_date(None) anonymize_info(raw.info) write_raw_bids(raw, bids_path, overwrite=True) with pytest.raises(ValueError, match='The provided recording does not ' 'have a measurement date set'): bids_path.find_empty_room() # test that the `AssociatedEmptyRoom` key in MEG sidecar is respected bids_root = tmpdir.mkdir('associated-empty-room') raw = _read_raw_fif(raw_fname) meas_date = datetime(year=2020, month=1, day=10, tzinfo=timezone.utc) er_date = datetime(year=2010, month=1, day=1, tzinfo=timezone.utc) raw.set_meas_date(meas_date) er_raw_matching_date = er_raw.copy().set_meas_date(meas_date) er_raw_associated = er_raw.copy().set_meas_date(er_date) # First write empty-room data # We write two empty-room recordings: one with a date matching exactly the # experimental measurement date, and one dated approx. 10 years earlier # We will want to enforce using the older recording via # `AssociatedEmptyRoom` (without AssociatedEmptyRoom, find_empty_room() # would return the recording with the matching date instead) er_matching_date_bids_path = BIDSPath(subject='emptyroom', session='20200110', task='noise', root=bids_root, datatype='meg', suffix='meg', extension='.fif') write_raw_bids(er_raw_matching_date, bids_path=er_matching_date_bids_path) er_associated_bids_path = (er_matching_date_bids_path.copy().update( session='20100101')) write_raw_bids(er_raw_associated, bids_path=er_associated_bids_path) # Now we write experimental data and associate it with the earlier # empty-room recording bids_path = (er_matching_date_bids_path.copy().update(subject='01', session=None, task='task')) write_raw_bids(raw, bids_path=bids_path, empty_room=er_associated_bids_path) # Retrieve empty-room BIDSPath assert bids_path.find_empty_room() == er_associated_bids_path # Should only work for MEG with pytest.raises(ValueError, match='only supported for MEG'): bids_path.copy().update(datatype='eeg').find_empty_room() # Don't create `AssociatedEmptyRoom` entry in sidecar – we should now # retrieve the empty-room recording closer in time write_raw_bids(raw, bids_path=bids_path, empty_room=None, overwrite=True) assert bids_path.find_empty_room() == er_matching_date_bids_path
############################################################################### # Just to illustrate, we can save more than one empty room file for different # dates. Here, they will all contain the same data but in your study, they # will be different on different days. dates = ['20021204', '20021201', '20021001'] for date in dates: er_bids_path.update(session=date) er_meas_date = datetime.strptime(date, '%Y%m%d') er_raw.set_meas_date(er_meas_date.replace(tzinfo=timezone.utc)) write_raw_bids(er_raw, er_bids_path, overwrite=True) ############################################################################### # Let us look at the directory structure print_dir_tree(bids_root) ############################################################################### # To get an accurate estimate of the noise, it is important that the empty # room recording be as close in date as the raw data. # We can retrieve the basename corresponding to the empty room # recording that is closest in time to the experimental measurement. er_bids_path = bids_path.find_empty_room() print(er_bids_path) ############################################################################### # Finally, we can read the empty room file using raw = read_raw_bids(bids_path=er_bids_path) print(raw)
raw = read_raw_bids(bids_path=bids_path) # %% # The resulting data is already in a convenient form to create epochs and # evoked data. events, event_id = mne.events_from_annotations(raw) epochs = mne.Epochs(raw, events, event_id) epochs['Auditory'].average().plot() # %% # We can easily get the :class:`mne_bids.BIDSPath` of the empty-room recording # that was associated with the experimental data while writing. The empty-room # data can then be loaded with :func:`read_raw_bids`. er_bids_path = bids_path.find_empty_room(use_sidecar_only=True) er_data = read_raw_bids(er_bids_path) er_data # %% # It is trivial to retrieve the path of the fine-calibration and crosstalk # files, too. print(bids_path.meg_calibration_fpath) print(bids_path.meg_crosstalk_fpath) # %% # The README created by :func:`write_raw_bids` also takes care of the citation # for mne-bids. If you are preparing a manuscript, please make sure to also # cite MNE-BIDS there. readme = op.join(output_path, 'README')