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 run_maxwell_filter(*, cfg, subject, session=None): if cfg.proc and 'sss' in cfg.proc and cfg.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_out = BIDSPath(subject=subject, session=session, task=cfg.task, acquisition=cfg.acq, processing='sss', recording=cfg.rec, space=cfg.space, suffix='raw', extension='.fif', datatype=cfg.datatype, root=cfg.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. msg = f'Loading reference run: {cfg.mf_reference_run}.' logger.info(**gen_log_kwargs(message=msg, subject=subject, session=session)) reference_run_info = get_reference_run_info( subject=subject, session=session, run=cfg.mf_reference_run ) dev_head_t = reference_run_info['dev_head_t'] dig = reference_run_info['dig'] del reference_run_info for run in cfg.runs: bids_path_out.update(run=run) raw = import_experimental_data( cfg=cfg, subject=subject, session=session, run=run, save=False ) # Maxwell-filter experimental data. msg = 'Applying Maxwell filter to experimental data.' logger.info(**gen_log_kwargs(message=msg, subject=subject, session=session, run=run)) # Warn if no bad channels are set before Maxwell filter # Create a copy, we'll need this later for setting the bads of the # empty-room recording bads = raw.info['bads'].copy() if not bads: msg = 'Found no bad channels.' logger.warning(**gen_log_kwargs(message=msg, subject=subject, session=session, run=run)) if cfg.mf_st_duration: msg = ' st_duration=%d' % (cfg.mf_st_duration) logger.info(**gen_log_kwargs(message=msg, subject=subject, session=session, run=run)) # Keyword arguments shared between Maxwell filtering of the # experimental and the empty-room data. common_mf_kws = dict( calibration=cfg.mf_cal_fname, cross_talk=cfg.mf_ctc_fname, st_duration=cfg.mf_st_duration, origin=cfg.mf_head_origin, coord_frame='head', destination=dev_head_t ) raw_sss = mne.preprocessing.maxwell_filter(raw, **common_mf_kws) # Save only the channel types we wish to analyze (including the # channels marked as "bad"). # We do not run `raw_sss.pick()` here because it uses too much memory. picks = config.get_channels_to_analyze(raw.info) raw_sss.save(bids_path_out, picks=picks, split_naming='bids', overwrite=True) del raw_sss if cfg.interactive: # Load the data we have just written, because it contains only # the relevant channels. raw = mne.io.read_raw_fif(bids_path_out, allow_maxshield=True) raw.plot(n_channels=50, butterfly=True) # Empty-room processing. # Only process empty-room data once – we ensure this by simply checking # if the current run is the reference run, and only then initiate # empty-room processing. No sophisticated logic behind this – it's just # convenient to code it this way. if cfg.process_er and run == cfg.mf_reference_run: msg = 'Processing empty-room recording …' logger.info(**gen_log_kwargs(subject=subject, session=session, message=msg)) raw_er = import_er_data( cfg=cfg, subject=subject, session=session, bads=bads, save=False ) # Maxwell-filter empty-room data. msg = 'Applying Maxwell filter to empty-room recording' logger.info(**gen_log_kwargs(message=msg, 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. raw_sss = mne.io.read_raw_fif(bids_path_out) rank_exp = mne.compute_rank(raw_sss, rank='info')['meg'] rank_er = mne.compute_rank(raw_er_sss, 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 differently.') raise RuntimeError(msg) raw_er_fname_out = bids_path_out.copy().update( task='noise', run=None, processing='sss' ) # Save only the channel types we wish to analyze # (same as for experimental data above). raw_er_sss.save(raw_er_fname_out, picks=picks, overwrite=True, split_naming='bids') del raw_er_sss