def run_forward(subject, session=None): deriv_path = config.get_subject_deriv_path(subject=subject, session=session, kind=config.get_kind()) bids_basename = BIDSPath(subject=subject, session=session, task=config.get_task(), acquisition=config.acq, run=None, recording=config.rec, space=config.space, prefix=deriv_path, check=False) fname_evoked = bids_basename.copy().update(kind='ave', extension='.fif') fname_trans = bids_basename.copy().update(kind='trans', extension='.fif') fname_fwd = bids_basename.copy().update(kind='fwd', extension='.fif') msg = f'Input: {fname_evoked}, Output: {fname_fwd}' logger.info( gen_log_message(message=msg, step=10, subject=subject, session=session)) # Find the raw data file trans = get_head_mri_trans(bids_basename=(bids_basename.copy().update( run=config.get_runs()[0], prefix=None)), bids_root=config.bids_root) mne.write_trans(fname_trans, trans) src = mne.setup_source_space(subject, spacing=config.spacing, subjects_dir=config.get_fs_subjects_dir(), add_dist=False) evoked = mne.read_evokeds(fname_evoked, condition=0) # Here we only use 3-layers BEM only if EEG is available. if 'eeg' in config.ch_types: model = mne.make_bem_model(subject, ico=4, conductivity=(0.3, 0.006, 0.3), subjects_dir=config.get_fs_subjects_dir()) else: model = mne.make_bem_model(subject, ico=4, conductivity=(0.3, ), subjects_dir=config.get_fs_subjects_dir()) bem = mne.make_bem_solution(model) fwd = mne.make_forward_solution(evoked.info, trans, src, bem, mindist=config.mindist) mne.write_forward_solution(fname_fwd, fwd, overwrite=True)
def run_forward(subject, session=None): deriv_path = config.get_subject_deriv_path(subject=subject, session=session, kind=config.get_kind()) bids_basename = make_bids_basename(subject=subject, session=session, task=config.get_task(), acquisition=config.acq, run=None, processing=config.proc, recording=config.rec, space=config.space) fname_evoked = op.join(deriv_path, bids_basename + '-ave.fif') fname_trans = op.join(deriv_path, 'sub-{}'.format(subject) + '-trans.fif') fname_fwd = op.join(deriv_path, bids_basename + '-fwd.fif') msg = f'Input: {fname_evoked}, Output: {fname_fwd}' logger.info(gen_log_message(message=msg, step=10, subject=subject, session=session)) # Find the raw data file # XXX : maybe simplify bids_basename = make_bids_basename(subject=subject, session=session, task=config.get_task(), acquisition=config.acq, run=config.get_runs()[0], processing=config.proc, recording=config.rec, space=config.space) trans = get_head_mri_trans(bids_basename=bids_basename, bids_root=config.bids_root) mne.write_trans(fname_trans, trans) src = mne.setup_source_space(subject, spacing=config.spacing, subjects_dir=config.get_fs_subjects_dir(), add_dist=False) evoked = mne.read_evokeds(fname_evoked, condition=0) # Here we only use 3-layers BEM only if EEG is available. if 'eeg' in config.ch_types: model = mne.make_bem_model(subject, ico=4, conductivity=(0.3, 0.006, 0.3), subjects_dir=config.get_fs_subjects_dir()) else: model = mne.make_bem_model(subject, ico=4, conductivity=(0.3,), subjects_dir=config.get_fs_subjects_dir()) bem = mne.make_bem_solution(model) fwd = mne.make_forward_solution(evoked.info, trans, src, bem, mindist=config.mindist) mne.write_forward_solution(fname_fwd, fwd, overwrite=True)
def run_forward(subject, session=None): bids_path = BIDSPath(subject=subject, session=session, task=config.get_task(), acquisition=config.acq, run=None, recording=config.rec, space=config.space, extension='.fif', datatype=config.get_datatype(), root=config.deriv_root, check=False) fname_evoked = bids_path.copy().update(suffix='ave') fname_trans = bids_path.copy().update(suffix='trans') fname_fwd = bids_path.copy().update(suffix='fwd') msg = f'Input: {fname_evoked}, Output: {fname_fwd}' logger.info( gen_log_message(message=msg, step=10, subject=subject, session=session)) # Retrieve the head -> MRI transformation matrix from the MRI sidecar file # in the input data, and save it to an MNE "trans" file in the derivatives # folder. trans = get_head_mri_trans(bids_path.copy().update( run=config.get_runs()[0], root=config.bids_root)) mne.write_trans(fname_trans, trans) src = mne.setup_source_space(subject, spacing=config.spacing, subjects_dir=config.get_fs_subjects_dir(), add_dist=False) evoked = mne.read_evokeds(fname_evoked, condition=0) # Here we only use 3-layers BEM only if EEG is available. if 'eeg' in config.ch_types: model = mne.make_bem_model(subject, ico=4, conductivity=(0.3, 0.006, 0.3), subjects_dir=config.get_fs_subjects_dir()) else: model = mne.make_bem_model(subject, ico=4, conductivity=(0.3, ), subjects_dir=config.get_fs_subjects_dir()) bem = mne.make_bem_solution(model) fwd = mne.make_forward_solution(evoked.info, trans, src, bem, mindist=config.mindist) mne.write_forward_solution(fname_fwd, fwd, overwrite=True)
def run_group_average_source(*, cfg, subject='average'): """Run group average in source space""" mne.datasets.fetch_fsaverage(subjects_dir=config.get_fs_subjects_dir()) with config.get_parallel_backend(): parallel, run_func, _ = parallel_func(morph_stc, n_jobs=config.get_n_jobs()) all_morphed_stcs = parallel( run_func(cfg=cfg, subject=subject, fs_subject=config.get_fs_subject(subject), session=session) for subject, session in itertools.product(config.get_subjects(), config.get_sessions())) mean_morphed_stcs = np.array(all_morphed_stcs).mean(axis=0) # XXX to fix sessions = config.get_sessions() if sessions: session = sessions[0] else: session = None run_average(cfg=cfg, session=session, mean_morphed_stcs=mean_morphed_stcs)
def run_recon(root_dir, subject, fs_bids_app) -> None: logger.info(f"Running recon-all on subject {subject}. This will take " f"a LONG time – it's a good idea to let it run over night.") subjects_dir = Path(config.get_fs_subjects_dir()) subj_dir = subjects_dir / f"sub-{subject}" if subj_dir.exists(): logger.info(f"Subject {subject} is already present. Please delete the " f"directory if you want to recompute.") return env = os.environ if 'FREESURFER_HOME' not in env: raise RuntimeError("FreeSurfer is not available.") license_file = Path(f"{env['FREESURFER_HOME']}/license.txt") if not license_file.exists(): license_file = Path(f"{env['FREESURFER_HOME']}/.license") if not license_file.exists(): raise RuntimeError("FreeSurfer license file not found.") cmd = [ f"{sys.executable}", f"{fs_bids_app}", f"{root_dir}", f"{subjects_dir}", "participant", "--n_cpus=2", "--stages=all", "--skip_bids_validator", f"--license_file={license_file}", f"--participant_label={subject}" ] logger.debug("Running: " + " ".join(cmd)) run_subprocess(cmd, env=env, verbose=logger.level)
def run_group_average_source(*, cfg, subject='average'): """Run group average in source space""" if not config.run_source_estimation: msg = ' … skipping: run_source_estimation is set to False.' logger.info(**gen_log_kwargs(message=msg)) return mne.datasets.fetch_fsaverage(subjects_dir=config.get_fs_subjects_dir()) parallel, run_func, _ = parallel_func(morph_stc, n_jobs=config.get_n_jobs()) all_morphed_stcs = parallel( run_func(cfg=cfg, subject=subject, fs_subject=config.get_fs_subject(subject), session=session) for subject, session in itertools.product( config.get_subjects(), config.get_sessions())) mean_morphed_stcs = np.array(all_morphed_stcs).mean(axis=0) # XXX to fix sessions = config.get_sessions() if sessions: session = sessions[0] else: session = None run_average(cfg=cfg, session=session, mean_morphed_stcs=mean_morphed_stcs)
def main(): """Run group average in source space""" msg = 'Running Step 13: Grand-average source estimates' logger.info(gen_log_message(step=13, message=msg)) if not config.run_source_estimation: msg = ' … skipping: run_source_estimation is set to False.' logger.info(gen_log_message(step=13, message=msg)) return mne.datasets.fetch_fsaverage(subjects_dir=config.get_fs_subjects_dir()) parallel, run_func, _ = parallel_func(morph_stc, n_jobs=config.N_JOBS) all_morphed_stcs = parallel(run_func(subject, session) for subject, session in itertools.product(config.get_subjects(), config.get_sessions())) all_morphed_stcs = [morphed_stcs for morphed_stcs, subject in zip(all_morphed_stcs, config.get_subjects())] mean_morphed_stcs = map(sum, zip(*all_morphed_stcs)) subject = 'average' # XXX to fix if config.get_sessions(): session = config.get_sessions()[0] else: session = None bids_path = BIDSPath(subject=subject, session=session, task=config.get_task(), acquisition=config.acq, run=None, processing=config.proc, recording=config.rec, space=config.space, datatype=config.get_datatype(), root=config.deriv_root, check=False) if isinstance(config.conditions, dict): conditions = list(config.conditions.keys()) else: conditions = config.conditions for condition, this_stc in zip(conditions, mean_morphed_stcs): this_stc /= len(all_morphed_stcs) method = config.inverse_method cond_str = config.sanitize_cond_name(condition) inverse_str = method hemi_str = 'hemi' # MNE will auto-append '-lh' and '-rh'. morph_str = 'morph2fsaverage' fname_stc_avg = bids_path.copy().update( suffix=f'{cond_str}+{inverse_str}+{morph_str}+{hemi_str}') this_stc.save(fname_stc_avg) msg = 'Completed Step 13: Grand-average source estimates' logger.info(gen_log_message(step=13, message=msg))
def main(): # Ensure we're also processing fsaverage if present subjects = config.get_subjects() if (Path(config.get_fs_subjects_dir()) / 'fsaverage').exists(): subjects.append('fsaverage') parallel, run_func, _ = parallel_func(make_coreg_surfaces, n_jobs=config.get_n_jobs()) parallel(run_func(get_config(), subject) for subject in subjects)
def get_config(subject: Optional[str] = None, session: Optional[str] = None) -> BunchConst: cfg = BunchConst( fs_subject=config.get_fs_subject(subject=subject), fs_subjects_dir=config.get_fs_subjects_dir(), recreate_bem=config.recreate_bem, bem_mri_images=config.bem_mri_images, recreate_scalp_surface=config.recreate_scalp_surface, interactive=config.interactive, ) return cfg
def main(): """Run grp ave.""" msg = 'Running Step 13: Grand-average source estimates' logger.info(gen_log_message(step=13, message=msg)) mne.datasets.fetch_fsaverage(subjects_dir=config.get_fs_subjects_dir()) parallel, run_func, _ = parallel_func(morph_stc, n_jobs=config.N_JOBS) all_morphed_stcs = parallel(run_func(subject, session) for subject, session in itertools.product(config.get_subjects(), config.get_sessions())) all_morphed_stcs = [morphed_stcs for morphed_stcs, subject in zip(all_morphed_stcs, config.get_subjects())] mean_morphed_stcs = map(sum, zip(*all_morphed_stcs)) subject = 'average' # XXX to fix if config.get_sessions(): session = config.get_sessions()[0] else: session = None deriv_path = config.get_subject_deriv_path(subject=subject, session=session, kind=config.get_kind()) bids_basename = BIDSPath(subject=subject, session=session, task=config.get_task(), acquisition=config.acq, run=None, processing=config.proc, recording=config.rec, space=config.space, prefix=deriv_path, check=False) for condition, this_stc in zip(config.conditions, mean_morphed_stcs): this_stc /= len(all_morphed_stcs) method = config.inverse_method cond_str = condition.replace(op.sep, '').replace('_', '') inverse_str = method hemi_str = 'hemi' # MNE will auto-append '-lh' and '-rh'. morph_str = 'morph2fsaverage' fname_stc_avg = bids_basename.copy().update( kind=f'{cond_str}+{inverse_str}+{morph_str}+{hemi_str}') this_stc.save(fname_stc_avg) msg = 'Completed Step 13: Grand-average source estimates' logger.info(gen_log_message(step=13, message=msg))
def get_config(subject: Optional[str] = None, session: Optional[str] = None) -> SimpleNamespace: # Deal with configurations where `deriv_root` was specified, but not # `fs_subjects_dir`. We normally raise an exception in this case in # `get_fs_subjects_dir()`. However, in situations where users only run the # sensor-space scripts, we never call this function, so everything works # totally fine at first (which is expected). Yet, when creating the # reports, the pipeline would fail with an exception – which is # unjustified, as it would not make sense to force users to provide an # `fs_subjects_dir` if they don't care about source analysis anyway! So # simply assign a dummy value in such cases. # `get_fs_subject()` calls `get_fs_subjects_dir()`, so take care of this # too. try: fs_subjects_dir = config.get_fs_subjects_dir() except ValueError: fs_subjects_dir = None fs_subject = None else: fs_subject = config.get_fs_subject(subject=subject) cfg = SimpleNamespace( task=config.get_task(), runs=config.get_runs(subject=subject), datatype=config.get_datatype(), acq=config.acq, rec=config.rec, space=config.space, proc=config.proc, analyze_channels=config.analyze_channels, process_er=config.process_er, find_noisy_channels_meg=config.find_noisy_channels_meg, h_freq=config.h_freq, spatial_filter=config.spatial_filter, ica_reject=config.ica_reject, conditions=config.conditions, contrasts=config.contrasts, time_frequency_conditions=config.time_frequency_conditions, decode=config.decode, decoding_metric=config.decoding_metric, n_boot=config.n_boot, inverse_method=config.inverse_method, fs_subject=fs_subject, fs_subjects_dir=fs_subjects_dir, deriv_root=config.get_deriv_root(), bids_root=config.get_bids_root(), use_template_mri=config.use_template_mri, interactive=config.interactive, plot_psd_for_runs=config.plot_psd_for_runs, ) return cfg
def get_config() -> BunchConst: cfg = BunchConst( task=config.get_task(), datatype=config.get_datatype(), acq=config.acq, rec=config.rec, space=config.space, proc=config.proc, conditions=config.conditions, inverse_method=config.inverse_method, fs_subjects_dir=config.get_fs_subjects_dir(), deriv_root=config.get_deriv_root(), ) return cfg
def main() -> None: """Run freesurfer recon-all command on BIDS dataset. The script allows to run the freesurfer recon-all command on all subjects of your BIDS dataset. It can run in parallel with the --n_jobs parameter. It is built on top of the FreeSurfer BIDS app: https://github.com/BIDS-Apps/freesurfer and the MNE BIDS Pipeline https://mne.tools/mne-bids-pipeline You must have freesurfer available on your system. Run via the MNE BIDS Pipeline's `run.py`: python run.py --steps=freesurfer --config=your_pipeline_config.py """ # noqa logger.info('Running FreeSurfer') subjects = config.get_subjects() root_dir = config.get_bids_root() subjects_dir = Path(config.get_fs_subjects_dir()) subjects_dir.mkdir(parents=True, exist_ok=True) with config.get_parallel_backend(): n_jobs = config.get_n_jobs() parallel, run_func, _ = parallel_func(run_recon, n_jobs=n_jobs) parallel(run_func(root_dir, subject, fs_bids_app) for subject in subjects) # Handle fsaverage fsaverage_dir = subjects_dir / 'fsaverage' if fsaverage_dir.exists(): if fsaverage_dir.is_symlink(): fsaverage_dir.unlink() else: shutil.rmtree(fsaverage_dir) env = os.environ shutil.copytree(f"{env['FREESURFER_HOME']}/subjects/fsaverage", subjects_dir / 'fsaverage')
def morph_stc(subject, session=None): bids_path = BIDSPath(subject=subject, session=session, task=config.get_task(), acquisition=config.acq, run=None, recording=config.rec, space=config.space, datatype=config.get_datatype(), root=config.deriv_root, check=False) fs_subject = config.get_fs_subject(subject) fs_subjects_dir = config.get_fs_subjects_dir() morphed_stcs = [] if isinstance(config.conditions, dict): conditions = list(config.conditions.keys()) else: conditions = config.conditions for condition in conditions: method = config.inverse_method cond_str = config.sanitize_cond_name(condition) inverse_str = method hemi_str = 'hemi' # MNE will auto-append '-lh' and '-rh'. morph_str = 'morph2fsaverage' fname_stc = bids_path.copy().update( suffix=f'{cond_str}+{inverse_str}+{hemi_str}') fname_stc_fsaverage = bids_path.copy().update( suffix=f'{cond_str}+{inverse_str}+{morph_str}+{hemi_str}') stc = mne.read_source_estimate(fname_stc) morph = mne.compute_source_morph(stc, subject_from=fs_subject, subject_to='fsaverage', subjects_dir=fs_subjects_dir) stc_fsaverage = morph.apply(stc) stc_fsaverage.save(fname_stc_fsaverage) morphed_stcs.append(stc_fsaverage) del fname_stc, fname_stc_fsaverage return morphed_stcs
def make_bem(subject): fs_subject = config.get_fs_subject(subject) fs_subjects_dir = config.get_fs_subjects_dir() mri_dir = Path(fs_subjects_dir) / fs_subject / 'mri' bem_dir = Path(fs_subjects_dir) / fs_subject / 'bem' watershed_bem_dir = bem_dir / 'watershed' flash_bem_dir = bem_dir / 'flash' flash_dir = mri_dir / 'flash' / 'parameter_maps' show = True if config.interactive else False if config.bem_mri_images == 'FLASH' and not flash_dir.exists(): raise RuntimeError('Cannot locate FLASH MRI images.') elif config.bem_mri_images == 'FLASH': mri_images = 'FLASH' elif config.bem_mri_images == 'auto' and flash_dir.exists(): mri_images = 'FLASH' else: mri_images = 'T1' if ((mri_images == 'FLASH' and flash_bem_dir.exists()) or (mri_images == 'T1' and watershed_bem_dir.exists())): msg = 'Found existing BEM surfaces. ' if config.recreate_bem: msg += 'Overwriting as requested in configuration.' logger.info(gen_log_message(step=10, message=msg)) else: msg = 'Skipping surface extraction as requested in configuration.' logger.info(gen_log_message(step=10, message=msg)) return if mri_images == 'FLASH': msg = 'Creating BEM surfaces from FLASH MRI images' bem_func = mne.bem.make_flash_bem else: msg = ('Creating BEM surfaces from T1-weighted MRI images using ' 'watershed algorithm') bem_func = mne.bem.make_watershed_bem logger.info(gen_log_message(step=10, message=msg)) bem_func(subject=fs_subject, subjects_dir=fs_subjects_dir, copy=True, overwrite=True, show=show)
def main(): """Run grp ave.""" msg = 'Running Step 13: Grand-average source estimates' logger.info(gen_log_message(step=13, message=msg)) mne.datasets.fetch_fsaverage(subjects_dir=config.get_fs_subjects_dir()) parallel, run_func, _ = parallel_func(morph_stc, n_jobs=config.N_JOBS) all_morphed_stcs = parallel( run_func(subject, session) for subject, session in itertools.product( config.get_subjects(), config.get_sessions())) all_morphed_stcs = [ morphed_stcs for morphed_stcs, subject in zip(all_morphed_stcs, config.get_subjects()) ] mean_morphed_stcs = map(sum, zip(*all_morphed_stcs)) deriv_path = config.deriv_root bids_basename = make_bids_basename(task=config.get_task(), acquisition=config.acq, run=None, processing=config.proc, recording=config.rec, space=config.space) for condition, this_stc in zip(config.conditions, mean_morphed_stcs): this_stc /= len(all_morphed_stcs) method = config.inverse_method cond_str = 'cond-%s' % condition.replace(op.sep, '') inverse_str = 'inverse-%s' % method hemi_str = 'hemi' # MNE will auto-append '-lh' and '-rh'. morph_str = 'morph-fsaverage' fname_stc_avg = op.join( deriv_path, '_'.join([ 'average', bids_basename, cond_str, inverse_str, morph_str, hemi_str ])) this_stc.save(fname_stc_avg) msg = 'Completed Step 13: Grand-average source estimates' logger.info(gen_log_message(step=13, message=msg))
def get_config(subject: Optional[str] = None, session: Optional[str] = None) -> SimpleNamespace: cfg = SimpleNamespace( task=config.get_task(), runs=config.get_runs(subject=subject), datatype=config.get_datatype(), acq=config.acq, rec=config.rec, space=config.space, mindist=config.mindist, spacing=config.spacing, use_template_mri=config.use_template_mri, source_info_path_update=config.source_info_path_update, ch_types=config.ch_types, fs_subject=config.get_fs_subject(subject=subject), fs_subjects_dir=config.get_fs_subjects_dir(), deriv_root=config.get_deriv_root(), bids_root=config.get_bids_root(), n_jobs=config.get_n_jobs()) return cfg
def morph_stc(subject, session=None): deriv_path = config.get_subject_deriv_path(subject=subject, session=session, kind=config.get_kind()) bids_basename = make_bids_basename(subject=subject, session=session, task=config.get_task(), acquisition=config.acq, run=None, processing=config.proc, recording=config.rec, space=config.space) morphed_stcs = [] for condition in config.conditions: method = config.inverse_method cond_str = 'cond-%s' % condition.replace(op.sep, '') inverse_str = 'inverse-%s' % method hemi_str = 'hemi' # MNE will auto-append '-lh' and '-rh'. morph_str = 'morph-fsaverage' fname_stc = op.join( deriv_path, '_'.join([bids_basename, cond_str, inverse_str, hemi_str])) fname_stc_fsaverage = op.join( deriv_path, '_'.join( [bids_basename, cond_str, inverse_str, morph_str, hemi_str])) stc = mne.read_source_estimate(fname_stc) morph = mne.compute_source_morph( stc, subject_from=subject, subject_to='fsaverage', subjects_dir=config.get_fs_subjects_dir()) stc_fsaverage = morph.apply(stc) stc_fsaverage.save(fname_stc_fsaverage) morphed_stcs.append(stc_fsaverage) del fname_stc, fname_stc_fsaverage return morphed_stcs
def morph_stc(subject, session=None): deriv_path = config.get_subject_deriv_path(subject=subject, session=session, kind=config.get_kind()) bids_basename = BIDSPath(subject=subject, session=session, task=config.get_task(), acquisition=config.acq, run=None, recording=config.rec, space=config.space, prefix=deriv_path, check=False) morphed_stcs = [] for condition in config.conditions: method = config.inverse_method cond_str = condition.replace(op.sep, '').replace('_', '') inverse_str = method hemi_str = 'hemi' # MNE will auto-append '-lh' and '-rh'. morph_str = 'morph2fsaverage' fname_stc = bids_basename.copy().update( kind=f'{cond_str}+{inverse_str}+{hemi_str}') fname_stc_fsaverage = bids_basename.copy().update( kind=f'{cond_str}+{inverse_str}+{morph_str}+{hemi_str}') stc = mne.read_source_estimate(fname_stc) morph = mne.compute_source_morph( stc, subject_from=subject, subject_to='fsaverage', subjects_dir=config.get_fs_subjects_dir()) stc_fsaverage = morph.apply(stc) stc_fsaverage.save(fname_stc_fsaverage) morphed_stcs.append(stc_fsaverage) del fname_stc, fname_stc_fsaverage return morphed_stcs
def get_config( subject: Optional[str] = None, session: Optional[str] = None ) -> BunchConst: cfg = BunchConst( task=config.get_task(), runs=config.get_runs(subject=subject), datatype=config.get_datatype(), acq=config.acq, rec=config.rec, space=config.space, mindist=config.mindist, spacing=config.spacing, use_template_mri=config.use_template_mri, ch_types=config.ch_types, fs_subject=config.get_fs_subject(subject=subject), fs_subjects_dir=config.get_fs_subjects_dir(), deriv_root=config.get_deriv_root(), bids_root=config.get_bids_root(), n_jobs=config.get_n_jobs() ) return cfg
def get_config() -> BunchConst: cfg = BunchConst(subjects_dir=config.get_fs_subjects_dir()) return cfg
def run_report(subject, session=None): bids_path = BIDSPath(subject=subject, session=session, task=config.get_task(), acquisition=config.acq, run=None, recording=config.rec, space=config.space, extension='.fif', datatype=config.get_datatype(), root=config.deriv_root, check=False) fname_ave = bids_path.copy().update(suffix='ave') fname_trans = bids_path.copy().update(suffix='trans') subjects_dir = config.get_fs_subjects_dir() params = dict(info_fname=fname_ave, raw_psd=True) if op.exists(fname_trans): params['subject'] = subject params['subjects_dir'] = subjects_dir rep = mne.Report(**params) rep.parse_folder(fname_ave.fpath.parent, verbose=True) # Visualize automated noisy channel detection. if config.find_noisy_channels_meg: figs, captions = plot_auto_scores(subject=subject, session=session) rep.add_figs_to_section(figs=figs, captions=captions, section='Data Quality') # Visualize events. events_fig = plot_events(subject=subject, session=session) rep.add_figs_to_section(figs=events_fig, captions='Events in filtered continuous data', section='Events') conditions = config.conditions.copy() conditions.extend(config.contrasts) evokeds = mne.read_evokeds(fname_ave) ########################################################################### # # Visualize evoked responses. # for condition, evoked in zip(conditions, evokeds): if condition in config.conditions: caption = f'Condition: {condition}' section = 'Evoked' else: # It's a contrast of two conditions. caption = f'Contrast: {condition[0]} – {condition[1]}' section = 'Contrast' fig = evoked.plot(show=False, gfp=True, spatial_colors=True) rep.add_figs_to_section(figs=fig, captions=caption, comments=evoked.comment, section=section) ########################################################################### # # Visualize the coregistration & inverse solutions. # if op.exists(fname_trans): # We can only plot the coregistration if we have a valid 3d backend. if mne.viz.get_3d_backend() is not None: fig = mne.viz.plot_alignment(evoked.info, fname_trans, subject=subject, subjects_dir=subjects_dir, meg=True, dig=True, eeg=True) rep.add_figs_to_section(figs=fig, captions='Coregistration', section='Coregistration') else: msg = ('Cannot render sensor alignment (coregistration) because ' 'no usable 3d backend was found.') logger.warning( gen_log_message(message=msg, step=99, subject=subject, session=session)) for evoked in evokeds: msg = f'Rendering inverse solution for {evoked.comment} …' logger.info( gen_log_message(message=msg, step=99, subject=subject, session=session)) if condition in config.conditions: caption = f'Condition: {condition}' else: # It's a contrast of two conditions. # XXX Will change once we process contrasts here too continue method = config.inverse_method cond_str = evoked.comment.replace(op.sep, '').replace('_', '') inverse_str = '%s' % method hemi_str = 'hemi' # MNE will auto-append '-lh' and '-rh'. fname_stc = bids_path.copy().update( suffix=f'{cond_str}+{inverse_str}+{hemi_str}') if op.exists(str(fname_stc) + "-lh.stc"): stc = mne.read_source_estimate(fname_stc, subject) _, peak_time = stc.get_peak() # Plot using 3d backend if available, and use Matplotlib # otherwise. if mne.viz.get_3d_backend() is not None: brain = stc.plot(views=['lat'], hemi='both', initial_time=peak_time, backend='mayavi') figs = brain._figures[0] comments = evoked.comment captions = caption else: import matplotlib.pyplot as plt fig_lh = plt.figure() fig_rh = plt.figure() brain_lh = stc.plot(views='lat', hemi='lh', initial_time=peak_time, backend='matplotlib', subjects_dir=subjects_dir, figure=fig_lh) brain_rh = stc.plot(views='lat', hemi='rh', initial_time=peak_time, subjects_dir=subjects_dir, backend='matplotlib', figure=fig_rh) figs = [brain_lh, brain_rh] comments = [ f'{evoked.comment} - left hemisphere', f'{evoked.comment} - right hemisphere' ] captions = [f'{caption} - left', f'{caption} - right'] rep.add_figs_to_section(figs=figs, captions=captions, comments=comments, section='Sources') del peak_time if config.process_er: fig_er_psd = plot_er_psd(subject=subject, session=session) rep.add_figs_to_section(figs=fig_er_psd, captions='Empty-Room Power Spectral Density ' '(after filtering)', section='Empty-Room') fname_report = bids_path.copy().update(suffix='report', extension='.html') rep.save(fname=fname_report, open_browser=False, overwrite=True)
def main(): """Make reports.""" msg = 'Running Step 99: Create reports' logger.info(gen_log_message(step=99, message=msg)) parallel, run_func, _ = parallel_func(run_report, n_jobs=config.N_JOBS) parallel( run_func(subject, session) for subject, session in itertools.product( config.get_subjects(), config.get_sessions())) # Group report subject = 'average' # XXX to fix if config.get_sessions(): session = config.get_sessions()[0] else: session = None evoked_fname = BIDSPath(subject=subject, session=session, task=config.get_task(), acquisition=config.acq, run=None, recording=config.rec, space=config.space, suffix='ave', extension='.fif', datatype=config.get_datatype(), root=config.deriv_root, check=False) rep = mne.Report(info_fname=evoked_fname, subject='fsaverage', subjects_dir=config.get_fs_subjects_dir()) evokeds = mne.read_evokeds(evoked_fname) subjects_dir = config.get_fs_subjects_dir() method = config.inverse_method inverse_str = method hemi_str = 'hemi' # MNE will auto-append '-lh' and '-rh'. morph_str = 'morph2fsaverage' conditions = config.conditions.copy() conditions.extend(config.contrasts) ########################################################################### # # Visualize evoked responses. # for condition, evoked in zip(conditions, evokeds): if condition in config.conditions: caption = f'Average: {condition}' section = 'Evoked' else: # It's a contrast of two conditions. caption = f'Average Contrast: {condition[0]} – {condition[1]}' section = 'Contrast' fig = evoked.plot(show=False, gfp=True, spatial_colors=True) fig = evoked.plot(spatial_colors=True, gfp=True, show=False) rep.add_figs_to_section(figs=fig, captions=caption, comments=evoked.comment, section=section) ########################################################################### # # Visualize inverse solutions. # for condition, evoked in zip(conditions, evokeds): if condition in config.conditions: caption = f'Average: {condition}' cond_str = condition.replace(op.sep, '').replace('_', '') else: # It's a contrast of two conditions. # XXX Will change once we process contrasts here too continue section = 'Source' fname_stc_avg = evoked_fname.copy().update( suffix=f'{cond_str}+{inverse_str}+{morph_str}+{hemi_str}', extension=None) if op.exists(str(fname_stc_avg) + "-lh.stc"): stc = mne.read_source_estimate(fname_stc_avg, subject='fsaverage') _, peak_time = stc.get_peak() # Plot using 3d backend if available, and use Matplotlib # otherwise. if mne.viz.get_3d_backend() is not None: brain = stc.plot(views=['lat'], hemi='both', initial_time=peak_time, backend='mayavi', subjects_dir=subjects_dir) figs = brain._figures[0] captions = caption else: import matplotlib.pyplot as plt fig_lh = plt.figure() fig_rh = plt.figure() brain_lh = stc.plot(views='lat', hemi='lh', initial_time=peak_time, backend='matplotlib', figure=fig_lh, subjects_dir=subjects_dir) brain_rh = stc.plot(views='lat', hemi='rh', initial_time=peak_time, backend='matplotlib', figure=fig_rh, subjects_dir=subjects_dir) figs = [brain_lh, brain_rh] captions = [f'{caption} - left', f'{caption} - right'] rep.add_figs_to_section(figs=figs, captions=captions, section='Sources') del peak_time fname_report = evoked_fname.copy().update(task=config.get_task(), suffix='report', extension='.html') rep.save(fname=fname_report, open_browser=False, overwrite=True) msg = 'Completed Step 99: Create reports' logger.info(gen_log_message(step=99, message=msg))
def run_report_average(session: str) -> None: # Group report import matplotlib.pyplot as plt # nested import to help joblib subject = 'average' evoked_fname = BIDSPath(subject=subject, session=session, task=config.get_task(), acquisition=config.acq, run=None, recording=config.rec, space=config.space, suffix='ave', extension='.fif', datatype=config.get_datatype(), root=config.deriv_root, check=False) rep = mne.Report(info_fname=evoked_fname, subject='fsaverage', subjects_dir=config.get_fs_subjects_dir()) evokeds = mne.read_evokeds(evoked_fname) if config.analyze_channels: for evoked in evokeds: evoked.pick(config.analyze_channels) fs_subjects_dir = config.get_fs_subjects_dir() method = config.inverse_method inverse_str = method hemi_str = 'hemi' # MNE will auto-append '-lh' and '-rh'. morph_str = 'morph2fsaverage' conditions: List[Condition_T] = list(config.conditions) conditions.extend(config.contrasts) ########################################################################### # # Add events end epochs drop log stats. # add_event_counts(report=rep, session=session) ########################################################################### # # Visualize evoked responses. # for condition, evoked in zip(conditions, evokeds): if condition in config.conditions: caption = f'Average: {condition}' section = 'Evoked' else: # It's a contrast of two conditions. caption = f'Average Contrast: {condition[0]} – {condition[1]}' section = 'Contrast' fig = evoked.plot(spatial_colors=True, gfp=True, show=False) rep.add_figs_to_section(figs=fig, captions=caption, comments=evoked.comment, section=section) ########################################################################### # # Visualize decoding results. # if config.decode: for contrast in config.contrasts: cond_1, cond_2 = contrast a_vs_b = f'{cond_1}-{cond_2}'.replace(op.sep, '') processing = f'{a_vs_b}+{config.decoding_metric}' processing = processing.replace('_', '-').replace('-', '') fname_decoding_ = (evoked_fname.copy().update( processing=processing, suffix='decoding', extension='.mat')) decoding_data = loadmat(fname_decoding_) del fname_decoding_, processing, a_vs_b fig = plot_decoding_scores_gavg(decoding_data) caption = f'Time-by-time Decoding: {cond_1} ./. {cond_2}' comment = (f'Based on N={decoding_data["N"].squeeze()} subjects. ' f'Standard error and confidence interval of the mean ' f'were bootstrapped with {config.n_boot} resamples.') rep.add_figs_to_section(figs=fig, captions=caption, comments=comment, section='Decoding') del decoding_data, cond_1, cond_2, caption, comment ########################################################################### # # Visualize inverse solutions. # for condition, evoked in zip(conditions, evokeds): if condition in config.conditions: caption = f'Average: {condition}' cond_str = config.sanitize_cond_name(condition) else: # It's a contrast of two conditions. # XXX Will change once we process contrasts here too continue section = 'Source' fname_stc_avg = evoked_fname.copy().update( suffix=f'{cond_str}+{inverse_str}+{morph_str}+{hemi_str}', extension=None) if op.exists(str(fname_stc_avg) + "-lh.stc"): stc = mne.read_source_estimate(fname_stc_avg, subject='fsaverage') _, peak_time = stc.get_peak() # Plot using 3d backend if available, and use Matplotlib # otherwise. if mne.viz.get_3d_backend() is not None: brain = stc.plot(views=['lat'], hemi='both', initial_time=peak_time, backend='pyvista', time_viewer=True, show_traces=True, subjects_dir=fs_subjects_dir) brain.toggle_interface() figs = brain._renderer.figure captions = caption else: fig_lh = plt.figure() fig_rh = plt.figure() brain_lh = stc.plot(views='lat', hemi='lh', initial_time=peak_time, backend='matplotlib', figure=fig_lh, subjects_dir=fs_subjects_dir) brain_rh = stc.plot(views='lat', hemi='rh', initial_time=peak_time, backend='matplotlib', figure=fig_rh, subjects_dir=fs_subjects_dir) figs = [brain_lh, brain_rh] captions = [f'{caption} - left', f'{caption} - right'] rep.add_figs_to_section(figs=figs, captions=captions, section='Sources') del peak_time fname_report = evoked_fname.copy().update(task=config.get_task(), suffix='report', extension='.html') rep.save(fname=fname_report, open_browser=False, overwrite=True) msg = 'Completed Step 99: Create reports' logger.info(gen_log_message(step=99, message=msg)) plt.close('all') # close all figures to save memory
def run_report(subject, session=None): bids_path = BIDSPath(subject=subject, session=session, task=config.get_task(), acquisition=config.acq, run=None, recording=config.rec, space=config.space, extension='.fif', datatype=config.get_datatype(), root=config.deriv_root, check=False) fname_ave = bids_path.copy().update(suffix='ave') fname_trans = bids_path.copy().update(suffix='trans') fname_epo = bids_path.copy().update(suffix='epo') fname_trans = bids_path.copy().update(suffix='trans') fname_ica = bids_path.copy().update(suffix='ica') fname_decoding = fname_epo.copy().update(suffix='decoding', extension='.mat') fs_subject = config.get_fs_subject(subject) fs_subjects_dir = config.get_fs_subjects_dir() params: Dict[str, Any] = dict(info_fname=fname_ave, raw_psd=True) if op.exists(fname_trans): params['subject'] = fs_subject params['subjects_dir'] = fs_subjects_dir rep = mne.Report(**params) rep_kwargs: Dict[str, Any] = dict(data_path=fname_ave.fpath.parent, verbose=False) if not op.exists(fname_trans): rep_kwargs['render_bem'] = False task = config.get_task() if task is not None: rep_kwargs['pattern'] = f'*_task-{task}*' if mne.viz.get_3d_backend() is not None: with mne.viz.use_3d_backend('pyvista'): rep.parse_folder(**rep_kwargs) else: rep.parse_folder(**rep_kwargs) # Visualize automated noisy channel detection. if config.find_noisy_channels_meg: figs, captions = plot_auto_scores(subject=subject, session=session) rep.add_figs_to_section(figs=figs, captions=captions, section='Data Quality') # Visualize events. events_fig = plot_events(subject=subject, session=session) rep.add_figs_to_section(figs=events_fig, captions='Events in filtered continuous data', section='Events') ########################################################################### # # Visualize effect of ICA artifact rejection. # if config.use_ica: epochs = mne.read_epochs(fname_epo) ica = mne.preprocessing.read_ica(fname_ica) fig = ica.plot_overlay(epochs.average(), show=False) rep.add_figs_to_section(fig, captions='Evoked response (across all epochs) ' 'before and after ICA', section='ICA') ########################################################################### # # Visualize evoked responses. # conditions: List[Condition_T] = list(config.conditions) conditions.extend(config.contrasts) evokeds = mne.read_evokeds(fname_ave) if config.analyze_channels: for evoked in evokeds: evoked.pick(config.analyze_channels) for condition, evoked in zip(conditions, evokeds): if condition in config.conditions: caption = f'Condition: {condition}' section = 'Evoked' else: # It's a contrast of two conditions. caption = f'Contrast: {condition[0]} – {condition[1]}' section = 'Contrast' fig = evoked.plot(spatial_colors=True, gfp=True, show=False) rep.add_figs_to_section(figs=fig, captions=caption, comments=evoked.comment, section=section) ########################################################################### # # Visualize decoding results. # if config.decode: epochs = mne.read_epochs(fname_epo) for contrast in config.contrasts: cond_1, cond_2 = contrast a_vs_b = f'{cond_1}-{cond_2}'.replace(op.sep, '') processing = f'{a_vs_b}+{config.decoding_metric}' processing = processing.replace('_', '-').replace('-', '') fname_decoding_ = (fname_decoding.copy().update( processing=processing)) decoding_data = loadmat(fname_decoding_) del fname_decoding_, processing, a_vs_b fig = plot_decoding_scores( times=epochs.times, cross_val_scores=decoding_data['scores'], metric=config.decoding_metric) caption = f'Time-by-time Decoding: {cond_1} ./. {cond_2}' comment = (f'{len(epochs[cond_1])} × {cond_1} ./. ' f'{len(epochs[cond_2])} × {cond_2}') rep.add_figs_to_section(figs=fig, captions=caption, comments=comment, section='Decoding') del decoding_data, cond_1, cond_2, caption, comment del epochs ########################################################################### # # Visualize the coregistration & inverse solutions. # evokeds = mne.read_evokeds(fname_ave) if op.exists(fname_trans): # We can only plot the coregistration if we have a valid 3d backend. if mne.viz.get_3d_backend() is not None: fig = mne.viz.plot_alignment(evoked.info, fname_trans, subject=fs_subject, subjects_dir=fs_subjects_dir, meg=True, dig=True, eeg=True) rep.add_figs_to_section(figs=fig, captions='Coregistration', section='Coregistration') else: msg = ('Cannot render sensor alignment (coregistration) because ' 'no usable 3d backend was found.') logger.warning( gen_log_message(message=msg, step=99, subject=subject, session=session)) for condition, evoked in zip(conditions, evokeds): msg = f'Rendering inverse solution for {evoked.comment} …' logger.info( gen_log_message(message=msg, step=99, subject=subject, session=session)) if condition in config.conditions: full_condition = config.sanitize_cond_name(evoked.comment) caption = f'Condition: {full_condition}' del full_condition else: # It's a contrast of two conditions. # XXX Will change once we process contrasts here too continue method = config.inverse_method cond_str = config.sanitize_cond_name(condition) inverse_str = method hemi_str = 'hemi' # MNE will auto-append '-lh' and '-rh'. fname_stc = bids_path.copy().update( suffix=f'{cond_str}+{inverse_str}+{hemi_str}', extension=None) if op.exists(str(fname_stc) + "-lh.stc"): stc = mne.read_source_estimate(fname_stc, subject=fs_subject) _, peak_time = stc.get_peak() # Plot using 3d backend if available, and use Matplotlib # otherwise. import matplotlib.pyplot as plt if mne.viz.get_3d_backend() is not None: brain = stc.plot(views=['lat'], hemi='split', initial_time=peak_time, backend='pyvista', time_viewer=True, subjects_dir=fs_subjects_dir) brain.toggle_interface() brain._renderer.plotter.reset_camera() brain._renderer.plotter.subplot(0, 0) brain._renderer.plotter.reset_camera() figs, ax = plt.subplots(figsize=(15, 10)) ax.imshow(brain.screenshot(time_viewer=True)) ax.axis('off') comments = evoked.comment captions = caption else: fig_lh = plt.figure() fig_rh = plt.figure() brain_lh = stc.plot(views='lat', hemi='lh', initial_time=peak_time, backend='matplotlib', subjects_dir=fs_subjects_dir, figure=fig_lh) brain_rh = stc.plot(views='lat', hemi='rh', initial_time=peak_time, subjects_dir=fs_subjects_dir, backend='matplotlib', figure=fig_rh) figs = [brain_lh, brain_rh] comments = [ f'{evoked.comment} - left hemisphere', f'{evoked.comment} - right hemisphere' ] captions = [f'{caption} - left', f'{caption} - right'] rep.add_figs_to_section(figs=figs, captions=captions, comments=comments, section='Sources') del peak_time if config.process_er: fig_er_psd = plot_er_psd(subject=subject, session=session) rep.add_figs_to_section(figs=fig_er_psd, captions='Empty-Room Power Spectral Density ' '(after filtering)', section='Empty-Room') fname_report = bids_path.copy().update(suffix='report', extension='.html') rep.save(fname=fname_report, open_browser=False, overwrite=True) import matplotlib.pyplot as plt # nested import to help joblib plt.close('all') # close all figures to save memory
def get_config() -> SimpleNamespace: cfg = SimpleNamespace(subjects_dir=config.get_fs_subjects_dir()) return cfg
def run_forward(subject, session=None): bids_path = BIDSPath(subject=subject, session=session, task=config.get_task(), acquisition=config.acq, run=None, recording=config.rec, space=config.space, extension='.fif', datatype=config.get_datatype(), root=config.deriv_root, check=False) fname_evoked = bids_path.copy().update(suffix='ave') fname_trans = bids_path.copy().update(suffix='trans') fname_fwd = bids_path.copy().update(suffix='fwd') # Generate a head ↔ MRI transformation matrix from the # electrophysiological and MRI sidecar files, and save it to an MNE # "trans" file in the derivatives folder. if config.mri_t1_path_generator is None: t1_bids_path = None else: t1_bids_path = BIDSPath(subject=bids_path.subject, session=bids_path.session, root=config.bids_root) t1_bids_path = config.mri_t1_path_generator(t1_bids_path.copy()) if t1_bids_path.suffix is None: t1_bids_path.update(suffix='T1w') if t1_bids_path.datatype is None: t1_bids_path.update(datatype='anat') msg = 'Estimating head ↔ MRI transform' logger.info( gen_log_message(message=msg, step=10, subject=subject, session=session)) trans = get_head_mri_trans(bids_path.copy().update( run=config.get_runs()[0], root=config.bids_root), t1_bids_path=t1_bids_path) mne.write_trans(fname_trans, trans) fs_subject = config.get_fs_subject(subject) fs_subjects_dir = config.get_fs_subjects_dir() # Create the source space. msg = 'Creating source space' logger.info( gen_log_message(message=msg, step=10, subject=subject, session=session)) src = mne.setup_source_space(subject=fs_subject, subjects_dir=fs_subjects_dir, spacing=config.spacing, add_dist=False, n_jobs=config.N_JOBS) # Calculate the BEM solution. # Here we only use a 3-layers BEM only if EEG is available. msg = 'Calculating BEM solution' logger.info( gen_log_message(message=msg, step=10, subject=subject, session=session)) if 'eeg' in config.ch_types: conductivity = (0.3, 0.006, 0.3) else: conductivity = (0.3, ) bem_model = mne.make_bem_model(subject=fs_subject, subjects_dir=fs_subjects_dir, ico=4, conductivity=conductivity) bem_sol = mne.make_bem_solution(bem_model) # Finally, calculate and save the forward solution. msg = 'Calculating forward solution' logger.info( gen_log_message(message=msg, step=10, subject=subject, session=session)) info = mne.io.read_info(fname_evoked) fwd = mne.make_forward_solution(info, trans=trans, src=src, bem=bem_sol, mindist=config.mindist) mne.write_forward_solution(fname_fwd, fwd, overwrite=True)