def init_scale_wf(mem_gb, omp_nthreads, n_dummy=None, scale_stat='mean', name='scale'): """ Run afni's voxel level mean scaling Parameters ---------- mem_gb : :obj:`float` Size of BOLD file in GB omp_nthreads : :obj:`int` Maximum number of threads an individual process may use n_dummy: :obj: `int` Number of dummy scans at the begining of the bold to discard when calculating the mean scale_stat : :obj:`str` Name of the flag for the statistic to scale relative to (defaul: ``mean``) name : :obj:`str` Name of workflow (default: ``bold_std_trans_wf``) Inputs ------ bold_file bold image to scale, should probably be head motion corrected first Outputs ------- scaled scaled bold time series """ from niworkflows.engine.workflows import LiterateWorkflow as Workflow from niworkflows.interfaces.fixes import FixHeaderApplyTransforms as ApplyTransforms from nipype.interfaces.afni import Calc from ..interfaces.afni import TStat workflow = Workflow(name=name) inputnode = pe.Node(niu.IdentityInterface(fields=['bold_file']), name='inputnode') outputnode = pe.Node(niu.IdentityInterface(fields=['scaled']), name='outputnode') scale_ref = pe.Node(TStat(args=f'-{scale_stat}', index=f'[{n_dummy}..$]', outputtype='NIFTI_GZ'), name='scale_ref', mem_gb=mem_gb, n_procs=omp_nthreads) scale = pe.Node(Calc(outputtype='NIFTI_GZ', expr='min(200, a/b*100)*step(a)*step(b)'), name='scale', mem_gb=mem_gb, n_procs=omp_nthreads) workflow.connect([(inputnode, scale_ref, [('bold_file', 'in_file')]), (inputnode, scale, [('bold_file', 'in_file_a')]), (scale_ref, scale, [('out_file', 'in_file_b')]), (scale, outputnode, [('out_file', 'scaled')])]) return workflow
def init_reportlets_wf(reportlets_dir, name='reportlets_wf'): """Set up a battery of datasinks to store reports in the right location.""" from niworkflows.interfaces.masks import SimpleShowMaskRPT workflow = Workflow(name=name) inputnode = pe.Node(niu.IdentityInterface( fields=['source_file', 'dwi_ref', 'dwi_mask', 'validation_report']), name='inputnode') mask_reportlet = pe.Node(SimpleShowMaskRPT(), name='mask_reportlet') ds_report_mask = pe.Node( DerivativesDataSink(base_directory=reportlets_dir, desc='brain', suffix='mask'), name='ds_report_mask', run_without_submitting=True) ds_report_validation = pe.Node( DerivativesDataSink(base_directory=reportlets_dir, desc='validation', keep_dtype=True), name='ds_report_validation', run_without_submitting=True) workflow.connect([ (inputnode, mask_reportlet, [('dwi_ref', 'background_file'), ('dwi_mask', 'mask_file')]), (inputnode, ds_report_validation, [('source_file', 'source_file')]), (inputnode, ds_report_mask, [('source_file', 'source_file')]), (inputnode, ds_report_validation, [('validation_report', 'in_file')]), (mask_reportlet, ds_report_mask, [('out_report', 'in_file')]), ]) return workflow
def init_fmriprep_wf(): """ Build *fMRIPrep*'s pipeline. This workflow organizes the execution of FMRIPREP, with a sub-workflow for each subject. If FreeSurfer's ``recon-all`` is to be run, a corresponding folder is created and populated with any needed template subjects under the derivatives folder. Workflow Graph .. workflow:: :graph2use: orig :simple_form: yes from fmriprep.workflows.tests import mock_config from fmriprep.workflows.base import init_fmriprep_wf with mock_config(): wf = init_fmriprep_wf() """ from niworkflows.engine.workflows import LiterateWorkflow as Workflow from niworkflows.interfaces.bids import BIDSFreeSurferDir fmriprep_wf = Workflow(name='fmriprep_wf') fmriprep_wf.base_dir = config.execution.work_dir freesurfer = config.workflow.run_reconall if freesurfer: fsdir = pe.Node( BIDSFreeSurferDir(derivatives=config.execution.output_dir, freesurfer_home=os.getenv('FREESURFER_HOME'), spaces=config.workflow.spaces.get_fs_spaces()), name='fsdir_run_%s' % config.execution.run_uuid.replace('-', '_'), run_without_submitting=True) if config.execution.fs_subjects_dir is not None: fsdir.inputs.subjects_dir = str( config.execution.fs_subjects_dir.absolute()) for subject_id in config.execution.participant_label: single_subject_wf = init_single_subject_wf(subject_id) single_subject_wf.config['execution']['crashdump_dir'] = str( config.execution.output_dir / "fmriprep" / "-".join( ("sub", subject_id)) / "log" / config.execution.run_uuid) for node in single_subject_wf._get_all_nodes(): node.config = deepcopy(single_subject_wf.config) if freesurfer: fmriprep_wf.connect(fsdir, 'subjects_dir', single_subject_wf, 'inputnode.subjects_dir') else: fmriprep_wf.add_nodes([single_subject_wf]) # Dump a copy of the config file into the log directory log_dir = config.execution.output_dir / 'fmriprep' / 'sub-{}'.format(subject_id) \ / 'log' / config.execution.run_uuid log_dir.mkdir(exist_ok=True, parents=True) config.to_filename(log_dir / 'fmriprep.toml') return fmriprep_wf
def init_reportlets_wf(output_dir, sdc_report=False, name="reportlets_wf"): """Set up a battery of datasinks to store reports in the right location.""" from niworkflows.interfaces.reportlets.masks import SimpleShowMaskRPT workflow = Workflow(name=name) inputnode = pe.Node( niu.IdentityInterface( fields=[ "source_file", "dwi_ref", "dwi_mask", "validation_report", "sdc_report", ] ), name="inputnode", ) mask_reportlet = pe.Node(SimpleShowMaskRPT(), name="mask_reportlet") ds_report_mask = pe.Node( DerivativesDataSink( base_directory=output_dir, desc="brain", suffix="mask", datatype="figures" ), name="ds_report_mask", run_without_submitting=True, ) ds_report_validation = pe.Node( DerivativesDataSink( base_directory=output_dir, desc="validation", datatype="figures" ), name="ds_report_validation", run_without_submitting=True, ) # fmt:off workflow.connect([ (inputnode, mask_reportlet, [("dwi_ref", "background_file"), ("dwi_mask", "mask_file")]), (inputnode, ds_report_validation, [("source_file", "source_file")]), (inputnode, ds_report_mask, [("source_file", "source_file")]), (inputnode, ds_report_validation, [("validation_report", "in_file")]), (mask_reportlet, ds_report_mask, [("out_report", "in_file")]), ]) # fmt:on if sdc_report: ds_report_sdc = pe.Node( DerivativesDataSink( base_directory=output_dir, desc="sdc", suffix="dwi", datatype="figures" ), name="ds_report_sdc", run_without_submitting=True, ) # fmt:off workflow.connect([ (inputnode, ds_report_sdc, [("source_file", "source_file"), ("sdc_report", "in_file")]), ]) # fmt:on return workflow
def init_dmriprep_wf(): """ Create the base workflow. This workflow organizes the execution of *dMRIPrep*, with a sub-workflow for each subject. If FreeSurfer's recon-all is to be run, a FreeSurfer derivatives folder is created and populated with any needed template subjects. Workflow Graph .. workflow:: :graph2use: orig :simple_form: yes from dmriprep.config.testing import mock_config from dmriprep.workflows.base import init_dmriprep_wf with mock_config(): wf = init_dmriprep_wf() """ dmriprep_wf = Workflow(name="dmriprep_wf") dmriprep_wf.base_dir = config.execution.work_dir freesurfer = config.workflow.run_reconall if freesurfer: fsdir = pe.Node( BIDSFreeSurferDir( derivatives=config.execution.output_dir, freesurfer_home=os.getenv("FREESURFER_HOME"), spaces=config.workflow.spaces.get_fs_spaces(), ), name=f"fsdir_run_{config.execution.run_uuid.replace('-', '_')}", run_without_submitting=True, ) if config.execution.fs_subjects_dir is not None: fsdir.inputs.subjects_dir = str( config.execution.fs_subjects_dir.absolute()) for subject_id in config.execution.participant_label: single_subject_wf = init_single_subject_wf(subject_id) single_subject_wf.config["execution"]["crashdump_dir"] = str( config.execution.output_dir / "dmriprep" / f"sub-{subject_id}" / "log" / config.execution.run_uuid) for node in single_subject_wf._get_all_nodes(): node.config = deepcopy(single_subject_wf.config) if freesurfer: dmriprep_wf.connect(fsdir, "subjects_dir", single_subject_wf, "fsinputnode.subjects_dir") else: dmriprep_wf.add_nodes([single_subject_wf]) # Dump a copy of the config file into the log directory log_dir = (config.execution.output_dir / "dmriprep" / f"sub-{subject_id}" / "log" / config.execution.run_uuid) log_dir.mkdir(exist_ok=True, parents=True) config.to_filename(log_dir / "dmriprep.toml") return dmriprep_wf
def init_faux_bold_wf(bids_layout, base_dir=None, name="faux_bold"): # create workflow wf = Workflow(name=name, base_dir=base_dir) input_node = pe.Node( niu.IdentityInterface([ 'base_bold_list', 'second_bold_list', 'task_name', 'num_discard', 'num_interp']), name='input_node', ) output_node = pe.Node( niu.IdentityInterface([ 'faux_bold_files']), name='output_node') combine_bold = pe.MapNode(CombineRestBold(return_type="file"), iterfield=['base_bold', 'second_bold'], name='combine_node') participants = bids_layout.get_subjects() base_bold_list = [] second_bold_list = [] for participant in participants: for run, lst in zip([1, 2], [base_bold_list, second_bold_list]): try: bold_file = bids_layout.get( task="rest", extension=".nii.gz", run=run, subject=participant, return_type='file', )[0] except IndexError: raise IndexError(f"{participant} does not have rest run: {run}") lst.append(bold_file) input_node.inputs.base_bold_list = base_bold_list input_node.inputs.second_bold_list = second_bold_list wf.connect([ (input_node, combine_bold, [('base_bold_list', 'base_bold'), ('second_bold_list', 'second_bold'), ('task_name', 'task_name'), ('num_discard', 'num_discard'), ('num_interp', 'num_interp')]), (combine_bold, output_node, [('faux_bold', 'faux_bold_files')]) ]) return wf
def init_qwarp_inversion_wf(omp_nthreads=1, name="qwarp_invert_wf"): """ Invert a warp produced by 3dqwarp and convert it to an ANTS formatted warp Workflow Graph .. workflow :: :graph2use: orig :simple_form: yes from sdcflows.workflows.base import init_qwarp_inversion_wf wf = init_qwarp_inversion_wf() Parameters ---------- name : str Name for this workflow omp_nthreads : int Parallelize internal tasks across the number of CPUs given by this option. Inputs ------ warp : pathlike The warp you want to invert. in_reference : pathlike The baseline reference image (must correspond to ``epi_pe_dir``). Outputs ------- out_warp : pathlike The corresponding inverted :abbr:`DFM (displacements field map)` compatible with ANTs. """ from ..interfaces.afni import InvertWarp workflow = Workflow(name=name) workflow.__desc__ = """\ A warp produced by 3dQwarp was inverted by `3dNwarpCat` @afni (AFNI {afni_ver}). """.format(afni_ver=''.join(['%02d' % v for v in afni.Info().version() or []])) inputnode = pe.Node(niu.IdentityInterface(fields=['warp', 'in_reference']), name='inputnode') outputnode = pe.Node(niu.IdentityInterface(fields=['out_warp']), name='outputnode') invert = pe.Node(InvertWarp(), name='invert', n_procs=omp_nthreads) invert.inputs.outputtype = 'NIFTI_GZ' to_ants = pe.Node(niu.Function(function=_fix_hdr), name='to_ants', mem_gb=0.01) cphdr_warp = pe.Node(CopyHeader(), name='cphdr_warp', mem_gb=0.01) workflow.connect([ (inputnode, invert, [('warp', 'in_file')]), (invert, cphdr_warp, [('out_file', 'in_file')]), (inputnode, cphdr_warp, [('in_reference', 'hdr_file')]), (cphdr_warp, to_ants, [('out_file', 'in_file')]), (to_ants, outputnode, [('out', 'out_warp')]), ]) return workflow
def init_anat_reports_wf(reportlets_dir, template, freesurfer, name='anat_reports_wf'): """ Set up a battery of datasinks to store reports in the right location """ workflow = Workflow(name=name) inputnode = pe.Node(niu.IdentityInterface(fields=[ 'source_file', 't1_conform_report', 'seg_report', 't1_2_mni_report', 'recon_report' ]), name='inputnode') ds_t1_conform_report = pe.Node(DerivativesDataSink( base_directory=reportlets_dir, suffix='conform'), name='ds_t1_conform_report', run_without_submitting=True) ds_t1_2_mni_report = pe.Node(DerivativesDataSink( base_directory=reportlets_dir, suffix='t1_2_mni'), name='ds_t1_2_mni_report', run_without_submitting=True) ds_t1_seg_mask_report = pe.Node(DerivativesDataSink( base_directory=reportlets_dir, suffix='seg_brainmask'), name='ds_t1_seg_mask_report', run_without_submitting=True) workflow.connect([ (inputnode, ds_t1_conform_report, [('source_file', 'source_file'), ('t1_conform_report', 'in_file')]), (inputnode, ds_t1_seg_mask_report, [('source_file', 'source_file'), ('seg_report', 'in_file')]), (inputnode, ds_t1_2_mni_report, [('source_file', 'source_file'), ('t1_2_mni_report', 'in_file')]) ]) if freesurfer: ds_recon_report = pe.Node(DerivativesDataSink( base_directory=reportlets_dir, suffix='reconall'), name='ds_recon_report', run_without_submitting=True) workflow.connect([(inputnode, ds_recon_report, [('source_file', 'source_file'), ('recon_report', 'in_file')])]) return workflow
def init_pepolar_estimate_wf(debug=False, generate_report=True, name="pepolar_estimate_wf"): """Initialize a barebones TOPUP implementation.""" from nipype.interfaces.afni import Automask from nipype.interfaces.fsl.epi import TOPUP from niworkflows.interfaces.nibabel import MergeSeries from sdcflows.interfaces.fmap import get_trt from ...interfaces.images import RescaleB0 wf = Workflow(name=name) inputnode = pe.Node(niu.IdentityInterface(fields=["metadata", "in_data"]), name="inputnode") outputnode = pe.Node(niu.IdentityInterface(fields=["fieldmap", "corrected", "corrected_mask"]), name="outputnode") concat_blips = pe.Node(MergeSeries(), name="concat_blips") readout_time = pe.MapNode(niu.Function( input_names=["in_meta", "in_file"], function=get_trt), name="readout_time", iterfield=["in_meta", "in_file"], run_without_submitting=True ) topup = pe.Node(TOPUP(config=_pkg_fname( "dmriprep", f"data/flirtsch/b02b0{'_quick' * debug}.cnf")), name="topup") pre_mask = pe.Node(Automask(dilate=1, outputtype="NIFTI_GZ"), name="pre_mask") rescale_corrected = pe.Node(RescaleB0(), name="rescale_corrected") post_mask = pe.Node(Automask(outputtype="NIFTI_GZ"), name="post_mask") wf.connect([ (inputnode, concat_blips, [("in_data", "in_files")]), (inputnode, readout_time, [("in_data", "in_file"), ("metadata", "in_meta")]), (inputnode, topup, [(("metadata", _get_pedir), "encoding_direction")]), (readout_time, topup, [("out", "readout_times")]), (concat_blips, topup, [("out_file", "in_file")]), (topup, pre_mask, [("out_corrected", "in_file")]), (pre_mask, rescale_corrected, [("out_file", "mask_file")]), (topup, rescale_corrected, [("out_corrected", "in_file")]), (topup, outputnode, [("out_field", "fieldmap")]), (rescale_corrected, post_mask, [("out_ref", "in_file")]), (rescale_corrected, outputnode, [("out_ref", "corrected")]), (post_mask, outputnode, [("out_file", "corrected_mask")]), ]) return wf
def init_fmap_wf(omp_nthreads, fmap_bspline, name='fmap_wf'): """ Fieldmap workflow - when we have a sequence that directly measures the fieldmap we just need to mask it (using the corresponding magnitude image) to remove the noise in the surrounding air region, and ensure that units are Hz. .. workflow :: :graph2use: orig :simple_form: yes from fmriprep.workflows.fieldmap.fmap import init_fmap_wf wf = init_fmap_wf(omp_nthreads=6, fmap_bspline=False) """ workflow = Workflow(name=name) inputnode = pe.Node(niu.IdentityInterface( fields=['magnitude', 'fieldmap']), name='inputnode') outputnode = pe.Node(niu.IdentityInterface(fields=['fmap', 'fmap_ref', 'fmap_mask']), name='outputnode') # Merge input magnitude images magmrg = pe.Node(IntraModalMerge(), name='magmrg') # Merge input fieldmap images fmapmrg = pe.Node(IntraModalMerge(zero_based_avg=False, hmc=False), name='fmapmrg') # de-gradient the fields ("bias/illumination artifact") n4_correct = pe.Node(ants.N4BiasFieldCorrection(dimension=3, copy_header=True), name='n4_correct', n_procs=omp_nthreads) bet = pe.Node(BETRPT(generate_report=True, frac=0.6, mask=True), name='bet') ds_fmap_mask = pe.Node(DerivativesDataSink(suffix='fmap_mask'), name='ds_report_fmap_mask', run_without_submitting=True) workflow.connect([ (inputnode, magmrg, [('magnitude', 'in_files')]), (inputnode, fmapmrg, [('fieldmap', 'in_files')]), (magmrg, n4_correct, [('out_file', 'input_image')]), (n4_correct, bet, [('output_image', 'in_file')]), (bet, outputnode, [('mask_file', 'fmap_mask'), ('out_file', 'fmap_ref')]), (inputnode, ds_fmap_mask, [('fieldmap', 'source_file')]), (bet, ds_fmap_mask, [('out_report', 'in_file')]), ]) if fmap_bspline: # despike_threshold=1.0, mask_erode=1), fmapenh = pe.Node(FieldEnhance(unwrap=False, despike=False), name='fmapenh', mem_gb=4, n_procs=omp_nthreads) workflow.connect([ (bet, fmapenh, [('mask_file', 'in_mask'), ('out_file', 'in_magnitude')]), (fmapmrg, fmapenh, [('out_file', 'in_file')]), (fmapenh, outputnode, [('out_file', 'fmap')]), ]) else: torads = pe.Node(FieldToRadS(), name='torads') prelude = pe.Node(fsl.PRELUDE(), name='prelude') tohz = pe.Node(FieldToHz(), name='tohz') denoise = pe.Node(fsl.SpatialFilter(operation='median', kernel_shape='sphere', kernel_size=3), name='denoise') demean = pe.Node(niu.Function(function=demean_image), name='demean') cleanup_wf = cleanup_edge_pipeline(name='cleanup_wf') applymsk = pe.Node(fsl.ApplyMask(), name='applymsk') workflow.connect([ (bet, prelude, [('mask_file', 'mask_file'), ('out_file', 'magnitude_file')]), (fmapmrg, torads, [('out_file', 'in_file')]), (torads, tohz, [('fmap_range', 'range_hz')]), (torads, prelude, [('out_file', 'phase_file')]), (prelude, tohz, [('unwrapped_phase_file', 'in_file')]), (tohz, denoise, [('out_file', 'in_file')]), (denoise, demean, [('out_file', 'in_file')]), (demean, cleanup_wf, [('out', 'inputnode.in_file')]), (bet, cleanup_wf, [('mask_file', 'inputnode.in_mask')]), (cleanup_wf, applymsk, [('outputnode.out_file', 'in_file')]), (bet, applymsk, [('mask_file', 'mask_file')]), (applymsk, outputnode, [('out_file', 'fmap')]), ]) return workflow
def init_single_subject_wf( layout, subject_id, task_id, echo_idx, name, reportlets_dir, output_dir, ignore, debug, low_mem, anat_only, longitudinal, t2s_coreg, omp_nthreads, skull_strip_template, skull_strip_fixed_seed, freesurfer, output_spaces, template, medial_surface_nan, cifti_output, hires, use_bbr, bold2t1w_dof, fmap_bspline, fmap_demean, use_syn, force_syn, template_out_grid, use_aroma, aroma_melodic_dim, err_on_aroma_warn): """ This workflow organizes the preprocessing pipeline for a single subject. It collects and reports information about the subject, and prepares sub-workflows to perform anatomical and functional preprocessing. Anatomical preprocessing is performed in a single workflow, regardless of the number of sessions. Functional preprocessing is performed using a separate workflow for each individual BOLD series. .. workflow:: :graph2use: orig :simple_form: yes from fmriprep.workflows.base import init_single_subject_wf from collections import namedtuple BIDSLayout = namedtuple('BIDSLayout', ['root'], defaults='.') wf = init_single_subject_wf(layout=BIDSLayout(), subject_id='test', task_id='', echo_idx=None, name='single_subject_wf', reportlets_dir='.', output_dir='.', ignore=[], debug=False, low_mem=False, anat_only=False, longitudinal=False, t2s_coreg=False, omp_nthreads=1, skull_strip_template='OASIS30ANTs', skull_strip_fixed_seed=False, freesurfer=True, template='MNI152NLin2009cAsym', output_spaces=['T1w', 'fsnative', 'template', 'fsaverage5'], medial_surface_nan=False, cifti_output=False, hires=True, use_bbr=True, bold2t1w_dof=9, fmap_bspline=False, fmap_demean=True, use_syn=True, force_syn=True, template_out_grid='native', use_aroma=False, aroma_melodic_dim=-200, err_on_aroma_warn=False) Parameters layout : BIDSLayout object BIDS dataset layout subject_id : str List of subject labels task_id : str or None Task ID of BOLD series to preprocess, or ``None`` to preprocess all echo_idx : int or None Index of echo to preprocess in multiecho BOLD series, or ``None`` to preprocess all name : str Name of workflow ignore : list Preprocessing steps to skip (may include "slicetiming", "fieldmaps") debug : bool Enable debugging outputs low_mem : bool Write uncompressed .nii files in some cases to reduce memory usage anat_only : bool Disable functional workflows longitudinal : bool Treat multiple sessions as longitudinal (may increase runtime) See sub-workflows for specific differences t2s_coreg : bool For multi-echo EPI, use the calculated T2*-map for T2*-driven coregistration omp_nthreads : int Maximum number of threads an individual process may use skull_strip_template : str Name of ANTs skull-stripping template ('OASIS30ANTs' or 'NKI') skull_strip_fixed_seed : bool Do not use a random seed for skull-stripping - will ensure run-to-run replicability when used with --omp-nthreads 1 reportlets_dir : str Directory in which to save reportlets output_dir : str Directory in which to save derivatives freesurfer : bool Enable FreeSurfer surface reconstruction (may increase runtime) output_spaces : list List of output spaces functional images are to be resampled to. Some parts of pipeline will only be instantiated for some output spaces. Valid spaces: - T1w - template - fsnative - fsaverage (or other pre-existing FreeSurfer templates) template : str Name of template targeted by ``template`` output space medial_surface_nan : bool Replace medial wall values with NaNs on functional GIFTI files cifti_output : bool Generate bold CIFTI file in output spaces hires : bool Enable sub-millimeter preprocessing in FreeSurfer use_bbr : bool or None Enable/disable boundary-based registration refinement. If ``None``, test BBR result for distortion before accepting. bold2t1w_dof : 6, 9 or 12 Degrees-of-freedom for BOLD-T1w registration fmap_bspline : bool **Experimental**: Fit B-Spline field using least-squares fmap_demean : bool Demean voxel-shift map during unwarp use_syn : bool **Experimental**: Enable ANTs SyN-based susceptibility distortion correction (SDC). If fieldmaps are present and enabled, this is not run, by default. force_syn : bool **Temporary**: Always run SyN-based SDC template_out_grid : str Keyword ('native', '1mm' or '2mm') or path of custom reference image for normalization use_aroma : bool Perform ICA-AROMA on MNI-resampled functional series err_on_aroma_warn : bool Do not fail on ICA-AROMA errors Inputs subjects_dir FreeSurfer SUBJECTS_DIR """ if name in ('single_subject_wf', 'single_subject_fmripreptest_wf'): # for documentation purposes subject_data = { 't1w': ['/completely/made/up/path/sub-01_T1w.nii.gz'], 'bold': ['/completely/made/up/path/sub-01_task-nback_bold.nii.gz'] } else: subject_data = collect_data(layout, subject_id, task_id, echo_idx)[0] # Make sure we always go through these two checks if not anat_only and subject_data['bold'] == []: raise Exception("No BOLD images found for participant {} and task {}. " "All workflows require BOLD images.".format( subject_id, task_id if task_id else '<all>')) if not subject_data['t1w']: raise Exception("No T1w images found for participant {}. " "All workflows require T1w images.".format(subject_id)) workflow = Workflow(name=name) workflow.__desc__ = """ Results included in this manuscript come from preprocessing performed using *fMRIPrep* {fmriprep_ver} (@fmriprep1; @fmriprep2; RRID:SCR_016216), which is based on *Nipype* {nipype_ver} (@nipype1; @nipype2; RRID:SCR_002502). """.format(fmriprep_ver=__version__, nipype_ver=nipype_ver) workflow.__postdesc__ = """ Many internal operations of *fMRIPrep* use *Nilearn* {nilearn_ver} [@nilearn, RRID:SCR_001362], mostly within the functional processing workflow. For more details of the pipeline, see [the section corresponding to workflows in *fMRIPrep*'s documentation]\ (https://fmriprep.readthedocs.io/en/latest/workflows.html \ "FMRIPrep's documentation"). ### References """.format(nilearn_ver=nilearn_ver) inputnode = pe.Node(niu.IdentityInterface(fields=['subjects_dir']), name='inputnode') bidssrc = pe.Node(BIDSDataGrabber(subject_data=subject_data, anat_only=anat_only), name='bidssrc') bids_info = pe.Node(BIDSInfo(bids_dir=layout.root, bids_validate=False), name='bids_info') summary = pe.Node(SubjectSummary(output_spaces=output_spaces, template=template), name='summary', run_without_submitting=True) about = pe.Node(AboutSummary(version=__version__, command=' '.join(sys.argv)), name='about', run_without_submitting=True) ds_report_summary = pe.Node(DerivativesDataSink( base_directory=reportlets_dir, suffix='summary'), name='ds_report_summary', run_without_submitting=True) ds_report_about = pe.Node(DerivativesDataSink( base_directory=reportlets_dir, suffix='about'), name='ds_report_about', run_without_submitting=True) # Preprocessing of T1w (includes registration to MNI) anat_preproc_wf = init_anat_preproc_wf( bids_root=layout.root, debug=debug, freesurfer=freesurfer, fs_spaces=output_spaces, hires=hires, longitudinal=longitudinal, name="anat_preproc_wf", num_t1w=len(subject_data['t1w']), omp_nthreads=omp_nthreads, output_dir=output_dir, reportlets_dir=reportlets_dir, skull_strip_fixed_seed=skull_strip_fixed_seed, skull_strip_template=skull_strip_template, template=template, ) workflow.connect([ (inputnode, anat_preproc_wf, [('subjects_dir', 'inputnode.subjects_dir')]), (bidssrc, bids_info, [(('t1w', fix_multi_T1w_source_name), 'in_file') ]), (inputnode, summary, [('subjects_dir', 'subjects_dir')]), (bidssrc, summary, [('t1w', 't1w'), ('t2w', 't2w'), ('bold', 'bold')]), (bids_info, summary, [('subject', 'subject_id')]), (bids_info, anat_preproc_wf, [(('subject', _prefix), 'inputnode.subject_id')]), (bidssrc, anat_preproc_wf, [('t1w', 'inputnode.t1w'), ('t2w', 'inputnode.t2w'), ('roi', 'inputnode.roi'), ('flair', 'inputnode.flair')]), (bidssrc, ds_report_summary, [(('t1w', fix_multi_T1w_source_name), 'source_file')]), (summary, ds_report_summary, [('out_report', 'in_file')]), (bidssrc, ds_report_about, [(('t1w', fix_multi_T1w_source_name), 'source_file')]), (about, ds_report_about, [('out_report', 'in_file')]), ]) # Overwrite ``out_path_base`` of smriprep's DataSinks for node in workflow.list_node_names(): if node.split('.')[-1].startswith('ds_'): workflow.get_node(node).interface.out_path_base = 'fmriprep' if anat_only: return workflow for bold_file in subject_data['bold']: func_preproc_wf = init_func_preproc_wf( bold_file=bold_file, layout=layout, ignore=ignore, freesurfer=freesurfer, use_bbr=use_bbr, t2s_coreg=t2s_coreg, bold2t1w_dof=bold2t1w_dof, reportlets_dir=reportlets_dir, output_spaces=output_spaces, template=template, medial_surface_nan=medial_surface_nan, cifti_output=cifti_output, output_dir=output_dir, omp_nthreads=omp_nthreads, low_mem=low_mem, fmap_bspline=fmap_bspline, fmap_demean=fmap_demean, use_syn=use_syn, force_syn=force_syn, debug=debug, template_out_grid=template_out_grid, use_aroma=use_aroma, aroma_melodic_dim=aroma_melodic_dim, err_on_aroma_warn=err_on_aroma_warn, num_bold=len(subject_data['bold'])) workflow.connect([ ( anat_preproc_wf, func_preproc_wf, [ (('outputnode.t1_preproc', _pop), 'inputnode.t1_preproc'), ('outputnode.t1_brain', 'inputnode.t1_brain'), ('outputnode.t1_mask', 'inputnode.t1_mask'), ('outputnode.t1_seg', 'inputnode.t1_seg'), ('outputnode.t1_aseg', 'inputnode.t1_aseg'), ('outputnode.t1_aparc', 'inputnode.t1_aparc'), ('outputnode.t1_tpms', 'inputnode.t1_tpms'), ('outputnode.t1_2_mni_forward_transform', 'inputnode.t1_2_mni_forward_transform'), ('outputnode.t1_2_mni_reverse_transform', 'inputnode.t1_2_mni_reverse_transform'), # Undefined if --no-freesurfer, but this is safe ('outputnode.subjects_dir', 'inputnode.subjects_dir'), ('outputnode.subject_id', 'inputnode.subject_id'), ('outputnode.t1_2_fsnative_forward_transform', 'inputnode.t1_2_fsnative_forward_transform'), ('outputnode.t1_2_fsnative_reverse_transform', 'inputnode.t1_2_fsnative_reverse_transform') ]), ]) return workflow
def init_3dQwarp_wf(omp_nthreads=1, debug=False, name="pepolar_estimate_wf"): """ Create the PEPOLAR field estimation workflow based on AFNI's ``3dQwarp``. This workflow takes in two EPI files that MUST have opposed :abbr:`PE (phase-encoding)` direction. Therefore, EPIs with orthogonal PE directions are not supported. Workflow Graph .. workflow :: :graph2use: orig :simple_form: yes from sdcflows.workflows.fit.pepolar import init_3dQwarp_wf wf = init_3dQwarp_wf() Parameters ---------- debug : :obj:`bool` Whether a fast configuration of topup (less accurate) should be applied. name : :obj:`str` Name for this workflow omp_nthreads : :obj:`int` Parallelize internal tasks across the number of CPUs given by this option. Inputs ------ in_data : :obj:`list` of :obj:`str` A list of two EPI files, the first of which will be taken as reference. Outputs ------- fmap : :obj:`str` The path of the estimated fieldmap. fmap_ref : :obj:`str` The path of an unwarped conversion of the first element of ``in_data``. """ from nipype.interfaces import afni from niworkflows.interfaces.header import CopyHeader from niworkflows.interfaces.fixes import ( FixHeaderRegistration as Registration, FixHeaderApplyTransforms as ApplyTransforms, ) from niworkflows.interfaces.freesurfer import StructuralReference from niworkflows.func.util import init_enhance_and_skullstrip_bold_wf from ...utils.misc import front as _front, last as _last from ...interfaces.utils import Flatten, ConvertWarp workflow = Workflow(name=name) workflow.__desc__ = f"""{_PEPOLAR_DESC} \ with `3dQwarp` (@afni; AFNI {''.join(['%02d' % v for v in afni.Info().version() or []])}). """ inputnode = pe.Node(niu.IdentityInterface(fields=["in_data", "metadata"]), name="inputnode") outputnode = pe.Node(niu.IdentityInterface(fields=["fmap", "fmap_ref"]), name="outputnode") flatten = pe.Node(Flatten(), name="flatten") sort_pe = pe.Node( niu.Function(function=_sorted_pe, output_names=["sorted", "qwarp_args"]), name="sort_pe", run_without_submitting=True, ) merge_pes = pe.MapNode( StructuralReference( auto_detect_sensitivity=True, initial_timepoint=1, fixed_timepoint=True, # Align to first image intensity_scaling=True, # 7-DOF (rigid + intensity) no_iteration=True, subsample_threshold=200, out_file="template.nii.gz", ), name="merge_pes", iterfield=["in_files"], ) pe0_wf = init_enhance_and_skullstrip_bold_wf(omp_nthreads=omp_nthreads, name="pe0_wf") pe1_wf = init_enhance_and_skullstrip_bold_wf(omp_nthreads=omp_nthreads, name="pe1_wf") align_pes = pe.Node( Registration( from_file=_pkg_fname("sdcflows", "data/translation_rigid.json"), output_warped_image=True, ), name="align_pes", n_procs=omp_nthreads, ) qwarp = pe.Node( afni.QwarpPlusMinus( blur=[-1, -1], environ={"OMP_NUM_THREADS": f"{min(omp_nthreads, 4)}"}, minpatch=9, nopadWARP=True, noweight=True, pblur=[0.05, 0.05], ), name="qwarp", n_procs=min(omp_nthreads, 4), ) to_ants = pe.Node(ConvertWarp(), name="to_ants", mem_gb=0.01) cphdr_warp = pe.Node(CopyHeader(), name="cphdr_warp", mem_gb=0.01) unwarp_reference = pe.Node( ApplyTransforms( dimension=3, float=True, interpolation="LanczosWindowedSinc", ), name="unwarp_reference", ) # fmt: off workflow.connect([ (inputnode, flatten, [("in_data", "in_data"), ("metadata", "in_meta")]), (flatten, sort_pe, [("out_list", "inlist")]), (sort_pe, qwarp, [("qwarp_args", "args")]), (sort_pe, merge_pes, [("sorted", "in_files")]), (merge_pes, pe0_wf, [(("out_file", _front), "inputnode.in_file")]), (merge_pes, pe1_wf, [(("out_file", _last), "inputnode.in_file")]), (pe0_wf, align_pes, [("outputnode.skull_stripped_file", "fixed_image") ]), (pe1_wf, align_pes, [("outputnode.skull_stripped_file", "moving_image") ]), (pe0_wf, qwarp, [("outputnode.skull_stripped_file", "in_file")]), (align_pes, qwarp, [("warped_image", "base_file")]), (inputnode, cphdr_warp, [(("in_data", _front), "hdr_file")]), (qwarp, cphdr_warp, [("source_warp", "in_file")]), (cphdr_warp, to_ants, [("out_file", "in_file")]), (to_ants, unwarp_reference, [("out_file", "transforms")]), (inputnode, unwarp_reference, [("in_reference", "reference_image"), ("in_reference", "input_image")]), (unwarp_reference, outputnode, [("output_image", "fmap_ref")]), (to_ants, outputnode, [("out_file", "fmap")]), ]) # fmt: on return workflow
def init_prepare_epi_wf(omp_nthreads, matched_pe=False, name="prepare_epi_wf"): """ Prepare opposed-PE EPI images for PE-POLAR SDC. This workflow takes in a set of EPI files and returns two 3D volumes with matching and opposed PE directions, ready to be used in field distortion estimation. The procedure involves: estimating a robust template using FreeSurfer's ``mri_robust_template``, bias field correction using ANTs ``N4BiasFieldCorrection`` and AFNI ``3dUnifize``, skullstripping using FSL BET and AFNI ``3dAutomask``, and rigid coregistration to the reference using ANTs. .. workflow :: :graph2use: orig :simple_form: yes from sdcflows.workflows.pepolar import init_prepare_epi_wf wf = init_prepare_epi_wf(omp_nthreads=8) **Parameters**: matched_pe : bool Whether the input ``fmaps_epi`` will contain images with matched PE blips or not. Please use :func:`sdcflows.workflows.pepolar.check_pes` to determine whether they exist or not. name : str Name for this workflow omp_nthreads : int Parallelize internal tasks across the number of CPUs given by this option. **Inputs**: epi_pe : str Phase-encoding direction of the EPI image to be corrected. maps_pe : list of tuple(pathlike, str) list of 3D or 4D NIfTI images ref_brain coregistration reference (skullstripped and bias field corrected) **Outputs**: opposed_pe : pathlike single 3D NIfTI file matched_pe : pathlike single 3D NIfTI file """ inputnode = pe.Node( niu.IdentityInterface(fields=['epi_pe', 'maps_pe', 'ref_brain']), name='inputnode') outputnode = pe.Node( niu.IdentityInterface(fields=['opposed_pe', 'matched_pe']), name='outputnode') ants_settings = pkgr.resource_filename('sdcflows', 'data/translation_rigid.json') split = pe.Node(niu.Function(function=_split_epi_lists), name='split') merge_op = pe.Node( StructuralReference( auto_detect_sensitivity=True, initial_timepoint=1, fixed_timepoint=True, # Align to first image intensity_scaling=True, # 7-DOF (rigid + intensity) no_iteration=True, subsample_threshold=200, out_file='template.nii.gz'), name='merge_op') ref_op_wf = init_enhance_and_skullstrip_bold_wf(omp_nthreads=omp_nthreads, name='ref_op_wf') op2ref_reg = pe.Node(ants.Registration(from_file=ants_settings, output_warped_image=True), name='op2ref_reg', n_procs=omp_nthreads) workflow = Workflow(name=name) workflow.connect([ (inputnode, split, [('maps_pe', 'in_files'), ('epi_pe', 'pe_dir')]), (split, merge_op, [(('out', _front), 'in_files')]), (merge_op, ref_op_wf, [('out_file', 'inputnode.in_file')]), (ref_op_wf, op2ref_reg, [('outputnode.skull_stripped_file', 'moving_image')]), (inputnode, op2ref_reg, [('ref_brain', 'fixed_image')]), (op2ref_reg, outputnode, [('warped_image', 'opposed_pe')]), ]) if not matched_pe: workflow.connect([ (inputnode, outputnode, [('ref_brain', 'matched_pe')]), ]) return workflow merge_ma = pe.Node( StructuralReference( auto_detect_sensitivity=True, initial_timepoint=1, fixed_timepoint=True, # Align to first image intensity_scaling=True, # 7-DOF (rigid + intensity) no_iteration=True, subsample_threshold=200, out_file='template.nii.gz'), name='merge_ma') ref_ma_wf = init_enhance_and_skullstrip_bold_wf(omp_nthreads=omp_nthreads, name='ref_ma_wf') ma2ref_reg = pe.Node(ants.Registration(from_file=ants_settings, output_warped_image=True), name='ma2ref_reg', n_procs=omp_nthreads) workflow.connect([ (split, merge_ma, [(('out', _last), 'in_files')]), (merge_ma, ref_ma_wf, [('out_file', 'inputnode.in_file')]), (ref_ma_wf, ma2ref_reg, [('outputnode.skull_stripped_file', 'moving_image')]), (inputnode, ma2ref_reg, [('ref_brain', 'fixed_image')]), (ma2ref_reg, outputnode, [('warped_image', 'matched_pe')]), ]) return workflow
def init_smriprep_wf( debug, fast_track, freesurfer, fs_subjects_dir, hires, layout, longitudinal, low_mem, omp_nthreads, output_dir, run_uuid, skull_strip_mode, skull_strip_fixed_seed, skull_strip_template, spaces, subject_list, work_dir, bids_filters, ): """ Create the execution graph of *sMRIPrep*, with a sub-workflow for each subject. If FreeSurfer's ``recon-all`` is to be run, a FreeSurfer derivatives folder is created and populated with any needed template subjects. Workflow Graph .. workflow:: :graph2use: orig :simple_form: yes import os from collections import namedtuple BIDSLayout = namedtuple('BIDSLayout', ['root']) os.environ['FREESURFER_HOME'] = os.getcwd() from smriprep.workflows.base import init_smriprep_wf from niworkflows.utils.spaces import SpatialReferences, Reference wf = init_smriprep_wf( debug=False, fast_track=False, freesurfer=True, fs_subjects_dir=None, hires=True, layout=BIDSLayout('.'), longitudinal=False, low_mem=False, omp_nthreads=1, output_dir='.', run_uuid='testrun', skull_strip_fixed_seed=False, skull_strip_mode='force', skull_strip_template=Reference('OASIS30ANTs'), spaces=SpatialReferences(spaces=['MNI152NLin2009cAsym', 'fsaverage5']), subject_list=['smripreptest'], work_dir='.', bids_filters=None, ) Parameters ---------- debug : :obj:`bool` Enable debugging outputs fast_track : :obj:`bool` Fast-track the workflow by searching for existing derivatives. freesurfer : :obj:`bool` Enable FreeSurfer surface reconstruction (may increase runtime) fs_subjects_dir : os.PathLike or None Use existing FreeSurfer subjects directory if provided hires : :obj:`bool` Enable sub-millimeter preprocessing in FreeSurfer layout : BIDSLayout object BIDS dataset layout longitudinal : :obj:`bool` Treat multiple sessions as longitudinal (may increase runtime) See sub-workflows for specific differences low_mem : :obj:`bool` Write uncompressed .nii files in some cases to reduce memory usage omp_nthreads : :obj:`int` Maximum number of threads an individual process may use output_dir : :obj:`str` Directory in which to save derivatives run_uuid : :obj:`str` Unique identifier for execution instance skull_strip_fixed_seed : :obj:`bool` Do not use a random seed for skull-stripping - will ensure run-to-run replicability when used with --omp-nthreads 1 skull_strip_mode : :obj:`str` Determiner for T1-weighted skull stripping (`force` ensures skull stripping, `skip` ignores skull stripping, and `auto` automatically ignores skull stripping if pre-stripped brains are detected). skull_strip_template : :py:class:`~niworkflows.utils.spaces.Reference` Spatial reference to use in atlas-based brain extraction. spaces : :py:class:`~niworkflows.utils.spaces.SpatialReferences` Object containing standard and nonstandard space specifications. subject_list : :obj:`list` List of subject labels work_dir : :obj:`str` Directory in which to store workflow execution state and temporary files bids_filters : dict Provides finer specification of the pipeline input files through pybids entities filters. A dict with the following structure {<suffix>:{<entity>:<filter>,...},...} """ smriprep_wf = Workflow(name='smriprep_wf') smriprep_wf.base_dir = work_dir if freesurfer: fsdir = pe.Node(BIDSFreeSurferDir( derivatives=output_dir, freesurfer_home=os.getenv('FREESURFER_HOME'), spaces=spaces.get_fs_spaces()), name='fsdir_run_%s' % run_uuid.replace('-', '_'), run_without_submitting=True) if fs_subjects_dir is not None: fsdir.inputs.subjects_dir = str(fs_subjects_dir.absolute()) for subject_id in subject_list: single_subject_wf = init_single_subject_wf( debug=debug, freesurfer=freesurfer, fast_track=fast_track, hires=hires, layout=layout, longitudinal=longitudinal, low_mem=low_mem, name="single_subject_%s_wf" % subject_id, omp_nthreads=omp_nthreads, output_dir=output_dir, skull_strip_fixed_seed=skull_strip_fixed_seed, skull_strip_mode=skull_strip_mode, skull_strip_template=skull_strip_template, spaces=spaces, subject_id=subject_id, bids_filters=bids_filters, ) single_subject_wf.config['execution']['crashdump_dir'] = (os.path.join( output_dir, "smriprep", "sub-" + subject_id, 'log', run_uuid)) for node in single_subject_wf._get_all_nodes(): node.config = deepcopy(single_subject_wf.config) if freesurfer: smriprep_wf.connect(fsdir, 'subjects_dir', single_subject_wf, 'inputnode.subjects_dir') else: smriprep_wf.add_nodes([single_subject_wf]) return smriprep_wf
def init_bold_t1_trans_wf(freesurfer, mem_gb, omp_nthreads, multiecho=False, use_fieldwarp=False, use_compression=True, name='bold_t1_trans_wf'): """ This workflow registers the reference BOLD image to T1-space, using a boundary-based registration (BBR) cost function. .. workflow:: :graph2use: orig :simple_form: yes from fmriprep.workflows.bold.registration import init_bold_t1_trans_wf wf = init_bold_t1_trans_wf(freesurfer=True, mem_gb=3, omp_nthreads=1) **Parameters** freesurfer : bool Enable FreeSurfer functional registration (bbregister) use_fieldwarp : bool Include SDC warp in single-shot transform from BOLD to T1 multiecho : bool If multiecho data was supplied, HMC already performed mem_gb : float Size of BOLD file in GB omp_nthreads : int Maximum number of threads an individual process may use use_compression : bool Save registered BOLD series as ``.nii.gz`` name : str Name of workflow (default: ``bold_reg_wf``) **Inputs** name_source BOLD series NIfTI file Used to recover original information lost during processing ref_bold_brain Reference image to which BOLD series is aligned If ``fieldwarp == True``, ``ref_bold_brain`` should be unwarped ref_bold_mask Skull-stripping mask of reference image t1_brain Skull-stripped bias-corrected structural template image t1_mask Mask of the skull-stripped template image t1_aseg FreeSurfer's ``aseg.mgz`` atlas projected into the T1w reference (only if ``recon-all`` was run). t1_aparc FreeSurfer's ``aparc+aseg.mgz`` atlas projected into the T1w reference (only if ``recon-all`` was run). bold_split Individual 3D BOLD volumes, not motion corrected hmc_xforms List of affine transforms aligning each volume to ``ref_image`` in ITK format itk_bold_to_t1 Affine transform from ``ref_bold_brain`` to T1 space (ITK format) fieldwarp a :abbr:`DFM (displacements field map)` in ITK format **Outputs** bold_t1 Motion-corrected BOLD series in T1 space bold_t1_ref Reference, contrast-enhanced summary of the motion-corrected BOLD series in T1w space bold_mask_t1 BOLD mask in T1 space bold_aseg_t1 FreeSurfer's ``aseg.mgz`` atlas, in T1w-space at the BOLD resolution (only if ``recon-all`` was run). bold_aparc_t1 FreeSurfer's ``aparc+aseg.mgz`` atlas, in T1w-space at the BOLD resolution (only if ``recon-all`` was run). **Subworkflows** * :py:func:`~fmriprep.workflows.bold.registration.init_bbreg_wf` * :py:func:`~fmriprep.workflows.bold.registration.init_fsl_bbr_wf` """ from .util import init_bold_reference_wf workflow = Workflow(name=name) inputnode = pe.Node( niu.IdentityInterface( fields=['name_source', 'ref_bold_brain', 'ref_bold_mask', 't1_brain', 't1_mask', 't1_aseg', 't1_aparc', 'bold_split', 'fieldwarp', 'hmc_xforms', 'itk_bold_to_t1']), name='inputnode' ) outputnode = pe.Node( niu.IdentityInterface(fields=[ 'bold_t1', 'bold_t1_ref', 'bold_mask_t1', 'bold_aseg_t1', 'bold_aparc_t1']), name='outputnode' ) gen_ref = pe.Node(GenerateSamplingReference(), name='gen_ref', mem_gb=0.3) # 256x256x256 * 64 / 8 ~ 150MB mask_t1w_tfm = pe.Node( ApplyTransforms(interpolation='MultiLabel', float=True), name='mask_t1w_tfm', mem_gb=0.1 ) workflow.connect([ (inputnode, gen_ref, [('ref_bold_brain', 'moving_image'), ('t1_brain', 'fixed_image'), ('t1_mask', 'fov_mask')]), (inputnode, mask_t1w_tfm, [('ref_bold_mask', 'input_image')]), (gen_ref, mask_t1w_tfm, [('out_file', 'reference_image')]), (inputnode, mask_t1w_tfm, [('itk_bold_to_t1', 'transforms')]), (mask_t1w_tfm, outputnode, [('output_image', 'bold_mask_t1')]), ]) if freesurfer: # Resample aseg and aparc in T1w space (no transforms needed) aseg_t1w_tfm = pe.Node( ApplyTransforms(interpolation='MultiLabel', transforms='identity', float=True), name='aseg_t1w_tfm', mem_gb=0.1) aparc_t1w_tfm = pe.Node( ApplyTransforms(interpolation='MultiLabel', transforms='identity', float=True), name='aparc_t1w_tfm', mem_gb=0.1) workflow.connect([ (inputnode, aseg_t1w_tfm, [('t1_aseg', 'input_image')]), (inputnode, aparc_t1w_tfm, [('t1_aparc', 'input_image')]), (gen_ref, aseg_t1w_tfm, [('out_file', 'reference_image')]), (gen_ref, aparc_t1w_tfm, [('out_file', 'reference_image')]), (aseg_t1w_tfm, outputnode, [('output_image', 'bold_aseg_t1')]), (aparc_t1w_tfm, outputnode, [('output_image', 'bold_aparc_t1')]), ]) bold_to_t1w_transform = pe.Node( MultiApplyTransforms(interpolation="LanczosWindowedSinc", float=True, copy_dtype=True), name='bold_to_t1w_transform', mem_gb=mem_gb * 3 * omp_nthreads, n_procs=omp_nthreads) # merge 3D volumes into 4D timeseries merge = pe.Node(Merge(compress=use_compression), name='merge', mem_gb=mem_gb) # Generate a reference on the target T1w space gen_final_ref = init_bold_reference_wf(omp_nthreads, pre_mask=True) if not multiecho: # Merge transforms placing the head motion correction last nforms = 2 + int(use_fieldwarp) merge_xforms = pe.Node(niu.Merge(nforms), name='merge_xforms', run_without_submitting=True, mem_gb=DEFAULT_MEMORY_MIN_GB) if use_fieldwarp: workflow.connect([ (inputnode, merge_xforms, [('fieldwarp', 'in2')]) ]) workflow.connect([ # merge transforms (inputnode, merge_xforms, [ ('hmc_xforms', 'in%d' % nforms), ('itk_bold_to_t1', 'in1')]), (merge_xforms, bold_to_t1w_transform, [('out', 'transforms')]), (inputnode, bold_to_t1w_transform, [('bold_split', 'input_image')]), ]) else: from nipype.interfaces.fsl import Split as FSLSplit bold_split = pe.Node(FSLSplit(dimension='t'), name='bold_split', mem_gb=DEFAULT_MEMORY_MIN_GB) workflow.connect([ (inputnode, bold_split, [('bold_split', 'in_file')]), (bold_split, bold_to_t1w_transform, [('out_files', 'input_image')]), (inputnode, bold_to_t1w_transform, [('itk_bold_to_t1', 'transforms')]), ]) workflow.connect([ (inputnode, merge, [('name_source', 'header_source')]), (gen_ref, bold_to_t1w_transform, [('out_file', 'reference_image')]), (bold_to_t1w_transform, merge, [('out_files', 'in_files')]), (merge, gen_final_ref, [('out_file', 'inputnode.bold_file')]), (mask_t1w_tfm, gen_final_ref, [('output_image', 'inputnode.bold_mask')]), (merge, outputnode, [('out_file', 'bold_t1')]), (gen_final_ref, outputnode, [('outputnode.ref_image', 'bold_t1_ref')]), ]) return workflow
def init_bold_std_trans_wf(freesurfer, mem_gb, omp_nthreads, standard_spaces, name='bold_std_trans_wf', use_compression=True, use_fieldwarp=False): """ This workflow samples functional images into standard space with a single resampling of the original BOLD series. .. workflow:: :graph2use: colored :simple_form: yes from collections import OrderedDict from fmriprep.workflows.bold import init_bold_std_trans_wf wf = init_bold_std_trans_wf( freesurfer=True, mem_gb=3, omp_nthreads=1, standard_spaces=OrderedDict([('MNI152Lin', {}), ('fsaverage', {'density': '10k'})]), ) **Parameters** freesurfer : bool Whether to generate FreeSurfer's aseg/aparc segmentations on BOLD space. mem_gb : float Size of BOLD file in GB omp_nthreads : int Maximum number of threads an individual process may use standard_spaces : OrderedDict Ordered dictionary where keys are TemplateFlow ID strings (e.g., ``MNI152Lin``, ``MNI152NLin6Asym``, ``MNI152NLin2009cAsym``, or ``fsLR``), or paths pointing to custom templates organized in a TemplateFlow-like structure. Values of the dictionary aggregate modifiers (e.g., the value for the key ``MNI152Lin`` could be ``{'resolution': 2}`` if one wants the resampling to be done on the 2mm resolution version of the selected template). name : str Name of workflow (default: ``bold_std_trans_wf``) use_compression : bool Save registered BOLD series as ``.nii.gz`` use_fieldwarp : bool Include SDC warp in single-shot transform from BOLD to MNI **Inputs** anat2std_xfm List of anatomical-to-standard space transforms generated during spatial normalization. bold_aparc FreeSurfer's ``aparc+aseg.mgz`` atlas projected into the T1w reference (only if ``recon-all`` was run). bold_aseg FreeSurfer's ``aseg.mgz`` atlas projected into the T1w reference (only if ``recon-all`` was run). bold_mask Skull-stripping mask of reference image bold_split Individual 3D volumes, not motion corrected fieldwarp a :abbr:`DFM (displacements field map)` in ITK format hmc_xforms List of affine transforms aligning each volume to ``ref_image`` in ITK format itk_bold_to_t1 Affine transform from ``ref_bold_brain`` to T1 space (ITK format) name_source BOLD series NIfTI file Used to recover original information lost during processing templates List of templates that were applied as targets during spatial normalization. **Outputs** - Two outputnodes are available. One output node (with name ``poutputnode``) will be parameterized in a Nipype sense (see `Nipype iterables <https://miykael.github.io/nipype_tutorial/notebooks/basic_iteration.html>`__), and a second node (``outputnode``) will collapse the parameterized outputs into synchronous lists of the following fields: bold_std BOLD series, resampled to template space bold_std_ref Reference, contrast-enhanced summary of the BOLD series, resampled to template space bold_mask_std BOLD series mask in template space bold_aseg_std FreeSurfer's ``aseg.mgz`` atlas, in template space at the BOLD resolution (only if ``recon-all`` was run) bold_aparc_std FreeSurfer's ``aparc+aseg.mgz`` atlas, in template space at the BOLD resolution (only if ``recon-all`` was run) templates Template identifiers synchronized correspondingly to previously described outputs. """ # Filter ``standard_spaces`` vol_std_spaces = [ k for k in standard_spaces.keys() if not k.startswith('fs') ] workflow = Workflow(name=name) if len(vol_std_spaces) == 1: workflow.__desc__ = """\ The BOLD time-series were resampled into standard space, generating a *preprocessed BOLD run in {tpl} space*. """.format(tpl=vol_std_spaces) else: workflow.__desc__ = """\ The BOLD time-series were resampled into several standard spaces, correspondingly generating the following *spatially-normalized, preprocessed BOLD runs*: {tpl}. """.format(tpl=', '.join(vol_std_spaces)) inputnode = pe.Node(niu.IdentityInterface(fields=[ 'anat2std_xfm', 'bold_aparc', 'bold_aseg', 'bold_mask', 'bold_split', 'fieldwarp', 'hmc_xforms', 'itk_bold_to_t1', 'name_source', 'templates', ]), name='inputnode') select_std = pe.Node(KeySelect(fields=['resolution', 'anat2std_xfm']), name='select_std', run_without_submitting=True) select_std.inputs.resolution = [ v.get('resolution') or v.get('res') or 'native' for k, v in list(standard_spaces.items()) if k in vol_std_spaces ] select_std.iterables = ('key', vol_std_spaces) select_tpl = pe.Node(niu.Function(function=_select_template), name='select_tpl', run_without_submitting=True) select_tpl.inputs.template_specs = standard_spaces gen_ref = pe.Node(GenerateSamplingReference(), name='gen_ref', mem_gb=0.3) # 256x256x256 * 64 / 8 ~ 150MB) mask_std_tfm = pe.Node(ApplyTransforms(interpolation='MultiLabel', float=True), name='mask_std_tfm', mem_gb=1) # Write corrected file in the designated output dir mask_merge_tfms = pe.Node(niu.Merge(2), name='mask_merge_tfms', run_without_submitting=True, mem_gb=DEFAULT_MEMORY_MIN_GB) workflow.connect([ (inputnode, select_std, [('templates', 'keys'), ('anat2std_xfm', 'anat2std_xfm')]), (inputnode, mask_std_tfm, [('bold_mask', 'input_image')]), (inputnode, gen_ref, [(('bold_split', _first), 'moving_image')]), (inputnode, mask_merge_tfms, [(('itk_bold_to_t1', _aslist), 'in2')]), (select_std, select_tpl, [('key', 'template')]), (select_std, mask_merge_tfms, [('anat2std_xfm', 'in1')]), (select_std, gen_ref, [(('resolution', _is_native), 'keep_native')]), (select_tpl, gen_ref, [('out', 'fixed_image')]), (mask_merge_tfms, mask_std_tfm, [('out', 'transforms')]), (gen_ref, mask_std_tfm, [('out_file', 'reference_image')]), ]) nxforms = 4 if use_fieldwarp else 3 merge_xforms = pe.Node(niu.Merge(nxforms), name='merge_xforms', run_without_submitting=True, mem_gb=DEFAULT_MEMORY_MIN_GB) workflow.connect([(inputnode, merge_xforms, [('hmc_xforms', 'in%d' % nxforms)])]) if use_fieldwarp: workflow.connect([(inputnode, merge_xforms, [('fieldwarp', 'in3')])]) bold_to_std_transform = pe.Node(MultiApplyTransforms( interpolation="LanczosWindowedSinc", float=True, copy_dtype=True), name='bold_to_std_transform', mem_gb=mem_gb * 3 * omp_nthreads, n_procs=omp_nthreads) merge = pe.Node(Merge(compress=use_compression), name='merge', mem_gb=mem_gb * 3) # Generate a reference on the target T1w space gen_final_ref = init_bold_reference_wf(omp_nthreads=omp_nthreads, pre_mask=True) workflow.connect([ (inputnode, merge_xforms, [(('itk_bold_to_t1', _aslist), 'in2')]), (inputnode, merge, [('name_source', 'header_source')]), (inputnode, bold_to_std_transform, [('bold_split', 'input_image')]), (select_std, merge_xforms, [('anat2std_xfm', 'in1')]), (merge_xforms, bold_to_std_transform, [('out', 'transforms')]), (gen_ref, bold_to_std_transform, [('out_file', 'reference_image')]), (bold_to_std_transform, merge, [('out_files', 'in_files')]), (merge, gen_final_ref, [('out_file', 'inputnode.bold_file')]), (mask_std_tfm, gen_final_ref, [('output_image', 'inputnode.bold_mask') ]), ]) # Connect output nodes output_names = ['bold_std', 'bold_std_ref', 'bold_mask_std', 'templates'] if freesurfer: output_names += ['bold_aseg_std', 'bold_aparc_std'] # poutputnode - parametric output node poutputnode = pe.Node(niu.IdentityInterface(fields=output_names), name='poutputnode') workflow.connect([ (gen_final_ref, poutputnode, [('outputnode.ref_image', 'bold_std_ref') ]), (merge, poutputnode, [('out_file', 'bold_std')]), (mask_std_tfm, poutputnode, [('output_image', 'bold_mask_std')]), (select_std, poutputnode, [('key', 'templates')]), ]) if freesurfer: # Sample the parcellation files to functional space aseg_std_tfm = pe.Node(ApplyTransforms(interpolation='MultiLabel', float=True), name='aseg_std_tfm', mem_gb=1) aparc_std_tfm = pe.Node(ApplyTransforms(interpolation='MultiLabel', float=True), name='aparc_std_tfm', mem_gb=1) workflow.connect([ (inputnode, aseg_std_tfm, [('bold_aseg', 'input_image')]), (inputnode, aparc_std_tfm, [('bold_aparc', 'input_image')]), (select_std, aseg_std_tfm, [('anat2std_xfm', 'transforms')]), (select_std, aparc_std_tfm, [('anat2std_xfm', 'transforms')]), (gen_ref, aseg_std_tfm, [('out_file', 'reference_image')]), (gen_ref, aparc_std_tfm, [('out_file', 'reference_image')]), (aseg_std_tfm, poutputnode, [('output_image', 'bold_aseg_std')]), (aparc_std_tfm, poutputnode, [('output_image', 'bold_aparc_std')]), ]) # Connect outputnode to the parameterized outputnode outputnode = pe.JoinNode(niu.IdentityInterface(fields=output_names), name='outputnode', joinsource='select_std') workflow.connect([(poutputnode, outputnode, [(f, f) for f in output_names])]) return workflow
def init_bold_stc_wf(metadata, name='bold_stc_wf'): """ This workflow performs :abbr:`STC (slice-timing correction)` over the input :abbr:`BOLD (blood-oxygen-level dependent)` image. .. workflow:: :graph2use: orig :simple_form: yes from fmriprep.workflows.bold import init_bold_stc_wf wf = init_bold_stc_wf( metadata={"RepetitionTime": 2.0, "SliceTiming": [0.0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9]}, ) **Parameters** metadata : dict BIDS metadata for BOLD file name : str Name of workflow (default: ``bold_stc_wf``) **Inputs** bold_file BOLD series NIfTI file skip_vols Number of non-steady-state volumes detected at beginning of ``bold_file`` **Outputs** stc_file Slice-timing corrected BOLD series NIfTI file """ workflow = Workflow(name=name) workflow.__desc__ = """\ BOLD runs were slice-time corrected using `3dTshift` from AFNI {afni_ver} [@afni, RRID:SCR_005927]. """.format(afni_ver=''.join(['%02d' % v for v in afni.Info().version() or []])) inputnode = pe.Node(niu.IdentityInterface(fields=['bold_file', 'skip_vols']), name='inputnode') outputnode = pe.Node(niu.IdentityInterface(fields=['stc_file']), name='outputnode') LOGGER.log(25, 'Slice-timing correction will be included.') # It would be good to fingerprint memory use of afni.TShift slice_timing_correction = pe.Node( afni.TShift(outputtype='NIFTI_GZ', tr='{}s'.format(metadata["RepetitionTime"]), slice_timing=metadata['SliceTiming'], slice_encoding_direction=metadata.get('SliceEncodingDirection', 'k')), name='slice_timing_correction') copy_xform = pe.Node(CopyXForm(), name='copy_xform', mem_gb=0.1) workflow.connect([ (inputnode, slice_timing_correction, [('bold_file', 'in_file'), ('skip_vols', 'ignore')]), (slice_timing_correction, copy_xform, [('out_file', 'in_file')]), (inputnode, copy_xform, [('bold_file', 'hdr_file')]), (copy_xform, outputnode, [('out_file', 'stc_file')]), ]) return workflow
def init_sdc_wf(fmaps, bold_meta, omp_nthreads=1, debug=False, fmap_bspline=False, fmap_demean=True): """ This workflow implements the heuristics to choose a :abbr:`SDC (susceptibility distortion correction)` strategy. When no field map information is present within the BIDS inputs, the EXPERIMENTAL "fieldmap-less SyN" can be performed, using the ``--use-syn`` argument. When ``--force-syn`` is specified, then the "fieldmap-less SyN" is always executed and reported despite of other fieldmaps available with higher priority. In the latter case (some sort of fieldmap(s) is available and ``--force-syn`` is requested), then the :abbr:`SDC (susceptibility distortion correction)` method applied is that with the highest priority. .. workflow:: :graph2use: orig :simple_form: yes from fmriprep.workflows.fieldmap import init_sdc_wf wf = init_sdc_wf( fmaps=[{ 'type': 'phasediff', 'phasediff': 'sub-03/ses-2/fmap/sub-03_ses-2_run-1_phasediff.nii.gz', 'magnitude1': 'sub-03/ses-2/fmap/sub-03_ses-2_run-1_magnitude1.nii.gz', 'magnitude2': 'sub-03/ses-2/fmap/sub-03_ses-2_run-1_magnitude2.nii.gz', }], bold_meta={ 'RepetitionTime': 2.0, 'SliceTiming': [0.0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9], 'PhaseEncodingDirection': 'j', }, ) **Parameters** fmaps : list of pybids dicts A list of dictionaries with the available fieldmaps (and their metadata using the key ``'metadata'`` for the case of *epi* fieldmaps) bold_meta : dict BIDS metadata dictionary corresponding to the BOLD run omp_nthreads : int Maximum number of threads an individual process may use fmap_bspline : bool **Experimental**: Fit B-Spline field using least-squares fmap_demean : bool Demean voxel-shift map during unwarp debug : bool Enable debugging outputs **Inputs** bold_ref A BOLD reference calculated at a previous stage bold_ref_brain Same as above, but brain-masked bold_mask Brain mask for the BOLD run t1_brain T1w image, brain-masked, for the fieldmap-less SyN method t1_2_mni_reverse_transform MNI-to-T1w transform to map prior knowledge to the T1w fo the fieldmap-less SyN method template : str Name of template targeted by ``template`` output space **Outputs** bold_ref An unwarped BOLD reference bold_mask The corresponding new mask after unwarping bold_ref_brain Brain-extracted, unwarped BOLD reference out_warp The deformation field to unwarp the susceptibility distortions syn_bold_ref If ``--force-syn``, an unwarped BOLD reference with this method (for reporting purposes) """ # TODO: To be removed (filter out unsupported fieldmaps): fmaps = [fmap for fmap in fmaps if fmap['type'] in FMAP_PRIORITY] workflow = Workflow(name='sdc_wf' if fmaps else 'sdc_bypass_wf') inputnode = pe.Node(niu.IdentityInterface( fields=['bold_ref', 'bold_ref_brain', 'bold_mask', 't1_brain', 't1_2_mni_reverse_transform', 'template']), name='inputnode') outputnode = pe.Node(niu.IdentityInterface( fields=['bold_ref', 'bold_mask', 'bold_ref_brain', 'out_warp', 'syn_bold_ref', 'method']), name='outputnode') # No fieldmaps - forward inputs to outputs if not fmaps: outputnode.inputs.method = 'None' workflow.connect([ (inputnode, outputnode, [('bold_ref', 'bold_ref'), ('bold_mask', 'bold_mask'), ('bold_ref_brain', 'bold_ref_brain')]), ]) return workflow workflow.__postdesc__ = """\ Based on the estimated susceptibility distortion, an unwarped BOLD reference was calculated for a more accurate co-registration with the anatomical reference. """ # In case there are multiple fieldmaps prefer EPI fmaps.sort(key=lambda fmap: FMAP_PRIORITY[fmap['type']]) fmap = fmaps[0] # PEPOLAR path if fmap['type'] == 'epi': outputnode.inputs.method = 'PEB/PEPOLAR (phase-encoding based / PE-POLARity)' # Get EPI polarities and their metadata epi_fmaps = [(fmap_['epi'], fmap_['metadata']["PhaseEncodingDirection"]) for fmap_ in fmaps if fmap_['type'] == 'epi'] sdc_unwarp_wf = init_pepolar_unwarp_wf( bold_meta=bold_meta, epi_fmaps=epi_fmaps, omp_nthreads=omp_nthreads, name='pepolar_unwarp_wf') workflow.connect([ (inputnode, sdc_unwarp_wf, [ ('bold_ref', 'inputnode.in_reference'), ('bold_mask', 'inputnode.in_mask'), ('bold_ref_brain', 'inputnode.in_reference_brain')]), ]) # FIELDMAP path if fmap['type'] in ['fieldmap', 'phasediff']: outputnode.inputs.method = 'FMB (%s-based)' % fmap['type'] # Import specific workflows here, so we don't break everything with one # unused workflow. if fmap['type'] == 'fieldmap': from .fmap import init_fmap_wf fmap_estimator_wf = init_fmap_wf( omp_nthreads=omp_nthreads, fmap_bspline=fmap_bspline) # set inputs fmap_estimator_wf.inputs.inputnode.fieldmap = fmap['fieldmap'] fmap_estimator_wf.inputs.inputnode.magnitude = fmap['magnitude'] if fmap['type'] == 'phasediff': from .phdiff import init_phdiff_wf fmap_estimator_wf = init_phdiff_wf(omp_nthreads=omp_nthreads) # set inputs fmap_estimator_wf.inputs.inputnode.phasediff = fmap['phasediff'] fmap_estimator_wf.inputs.inputnode.magnitude = [ fmap_ for key, fmap_ in sorted(fmap.items()) if key.startswith("magnitude") ] sdc_unwarp_wf = init_sdc_unwarp_wf( omp_nthreads=omp_nthreads, fmap_demean=fmap_demean, debug=debug, name='sdc_unwarp_wf') sdc_unwarp_wf.inputs.inputnode.metadata = bold_meta workflow.connect([ (inputnode, sdc_unwarp_wf, [ ('bold_ref', 'inputnode.in_reference'), ('bold_ref_brain', 'inputnode.in_reference_brain'), ('bold_mask', 'inputnode.in_mask')]), (fmap_estimator_wf, sdc_unwarp_wf, [ ('outputnode.fmap', 'inputnode.fmap'), ('outputnode.fmap_ref', 'inputnode.fmap_ref'), ('outputnode.fmap_mask', 'inputnode.fmap_mask')]), ]) # FIELDMAP-less path if any(fm['type'] == 'syn' for fm in fmaps): syn_sdc_wf = init_syn_sdc_wf( bold_pe=bold_meta.get('PhaseEncodingDirection', None), omp_nthreads=omp_nthreads) workflow.connect([ (inputnode, syn_sdc_wf, [ ('t1_brain', 'inputnode.t1_brain'), ('t1_2_mni_reverse_transform', 'inputnode.t1_2_mni_reverse_transform'), ('bold_ref', 'inputnode.bold_ref'), ('bold_ref_brain', 'inputnode.bold_ref_brain'), ('template', 'inputnode.template')]), ]) # XXX Eliminate branch when forcing isn't an option if fmap['type'] == 'syn': # No fieldmaps, but --use-syn outputnode.inputs.method = 'FLB ("fieldmap-less", SyN-based)' sdc_unwarp_wf = syn_sdc_wf else: # --force-syn was called when other fieldmap was present sdc_unwarp_wf.__desc__ = None workflow.connect([ (syn_sdc_wf, outputnode, [ ('outputnode.out_reference', 'syn_bold_ref')]), ]) workflow.connect([ (sdc_unwarp_wf, outputnode, [ ('outputnode.out_warp', 'out_warp'), ('outputnode.out_reference', 'bold_ref'), ('outputnode.out_reference_brain', 'bold_ref_brain'), ('outputnode.out_mask', 'bold_mask')]), ]) return workflow
def init_enhance_and_skullstrip_bold_wf( name='enhance_and_skullstrip_bold_wf', pre_mask=False, omp_nthreads=1): """ This workflow takes in a :abbr:`BOLD (blood-oxygen level-dependant)` :abbr:`fMRI (functional MRI)` average/summary (e.g. a reference image averaging non-steady-state timepoints), and sharpens the histogram with the application of the N4 algorithm for removing the :abbr:`INU (intensity non-uniformity)` bias field and calculates a signal mask. Steps of this workflow are: 1. Calculate a tentative mask by registering (9-parameters) to *fMRIPrep*'s :abbr:`EPI (echo-planar imaging)` -*boldref* template, which is in MNI space. The tentative mask is obtained by resampling the MNI template's brainmask into *boldref*-space. 2. Binary dilation of the tentative mask with a sphere of 3mm diameter. 3. Run ANTs' ``N4BiasFieldCorrection`` on the input :abbr:`BOLD (blood-oxygen level-dependant)` average, using the mask generated in 1) instead of the internal Otsu thresholding. 4. Calculate a loose mask using FSL's ``bet``, with one mathematical morphology dilation of one iteration and a sphere of 6mm as structuring element. 5. Mask the :abbr:`INU (intensity non-uniformity)`-corrected image with the latest mask calculated in 3), then use AFNI's ``3dUnifize`` to *standardize* the T2* contrast distribution. 6. Calculate a mask using AFNI's ``3dAutomask`` after the contrast enhancement of 4). 7. Calculate a final mask as the intersection of 4) and 6). 8. Apply final mask on the enhanced reference. Step 1 can be skipped if the ``pre_mask`` argument is set to ``True`` and a tentative mask is passed in to the workflow throught the ``pre_mask`` Nipype input. .. workflow :: :graph2use: orig :simple_form: yes from fmriprep.workflows.bold.util import init_enhance_and_skullstrip_bold_wf wf = init_enhance_and_skullstrip_bold_wf(omp_nthreads=1) **Parameters** name : str Name of workflow (default: ``enhance_and_skullstrip_bold_wf``) pre_mask : bool Indicates whether the ``pre_mask`` input will be set (and thus, step 1 should be skipped). omp_nthreads : int number of threads available to parallel nodes **Inputs** in_file BOLD image (single volume) pre_mask : bool A tentative brain mask to initialize the workflow (requires ``pre_mask`` parameter set ``True``). **Outputs** bias_corrected_file the ``in_file`` after `N4BiasFieldCorrection`_ skull_stripped_file the ``bias_corrected_file`` after skull-stripping mask_file mask of the skull-stripped input file out_report reportlet for the skull-stripping .. _N4BiasFieldCorrection: https://hdl.handle.net/10380/3053 """ workflow = Workflow(name=name) inputnode = pe.Node(niu.IdentityInterface(fields=['in_file', 'pre_mask']), name='inputnode') outputnode = pe.Node(niu.IdentityInterface(fields=[ 'mask_file', 'skull_stripped_file', 'bias_corrected_file']), name='outputnode') # Dilate pre_mask pre_dilate = pe.Node(fsl.DilateImage( operation='max', kernel_shape='sphere', kernel_size=3.0, internal_datatype='char'), name='pre_mask_dilate') # Ensure mask's header matches reference's check_hdr = pe.Node(MatchHeader(), name='check_hdr', run_without_submitting=True) # Run N4 normally, force num_threads=1 for stability (images are small, no need for >1) n4_correct = pe.Node(ants.N4BiasFieldCorrection(dimension=3, copy_header=True), name='n4_correct', n_procs=1) # Create a generous BET mask out of the bias-corrected EPI skullstrip_first_pass = pe.Node(fsl.BET(frac=0.2, mask=True), name='skullstrip_first_pass') bet_dilate = pe.Node(fsl.DilateImage( operation='max', kernel_shape='sphere', kernel_size=6.0, internal_datatype='char'), name='skullstrip_first_dilate') bet_mask = pe.Node(fsl.ApplyMask(), name='skullstrip_first_mask') # Use AFNI's unifize for T2 constrast & fix header unifize = pe.Node(afni.Unifize( t2=True, outputtype='NIFTI_GZ', # Default -clfrac is 0.1, 0.4 was too conservative # -rbt because I'm a Jedi AFNI Master (see 3dUnifize's documentation) args='-clfrac 0.2 -rbt 18.3 65.0 90.0', out_file="uni.nii.gz"), name='unifize') fixhdr_unifize = pe.Node(CopyXForm(), name='fixhdr_unifize', mem_gb=0.1) # Run ANFI's 3dAutomask to extract a refined brain mask skullstrip_second_pass = pe.Node(afni.Automask(dilate=1, outputtype='NIFTI_GZ'), name='skullstrip_second_pass') fixhdr_skullstrip2 = pe.Node(CopyXForm(), name='fixhdr_skullstrip2', mem_gb=0.1) # Take intersection of both masks combine_masks = pe.Node(fsl.BinaryMaths(operation='mul'), name='combine_masks') # Compute masked brain apply_mask = pe.Node(fsl.ApplyMask(), name='apply_mask') if not pre_mask: bold_template = get_template('fMRIPrep') / 'tpl-fMRIPrep_space-MNI_res-02_boldref.nii.gz' brain_mask = get_template('MNI152NLin2009cAsym') / \ 'tpl-MNI152NLin2009cAsym_space-MNI_res-02_brainmask.nii.gz' # Initialize transforms with antsAI init_aff = pe.Node(AI( fixed_image=str(bold_template), fixed_image_mask=str(brain_mask), metric=('Mattes', 32, 'Regular', 0.2), transform=('Affine', 0.1), search_factor=(20, 0.12), principal_axes=False, convergence=(10, 1e-6, 10), verbose=True), name='init_aff', n_procs=omp_nthreads) # Registration().version may be None if parseversion(Registration().version or '0.0.0') > Version('2.2.0'): init_aff.inputs.search_grid = (40, (0, 40, 40)) # Set up spatial normalization norm = pe.Node(Registration( from_file=pkgr_fn( 'fmriprep.data', 'epi_atlasbased_brainmask.json')), name='norm', n_procs=omp_nthreads) norm.inputs.fixed_image = str(bold_template) map_brainmask = pe.Node( ApplyTransforms(interpolation='MultiLabel', float=True, input_image=str(brain_mask)), name='map_brainmask' ) workflow.connect([ (inputnode, init_aff, [('in_file', 'moving_image')]), (inputnode, map_brainmask, [('in_file', 'reference_image')]), (inputnode, norm, [('in_file', 'moving_image')]), (init_aff, norm, [('output_transform', 'initial_moving_transform')]), (norm, map_brainmask, [ ('reverse_invert_flags', 'invert_transform_flags'), ('reverse_transforms', 'transforms')]), (map_brainmask, pre_dilate, [('output_image', 'in_file')]), ]) else: workflow.connect([ (inputnode, pre_dilate, [('pre_mask', 'in_file')]), ]) workflow.connect([ (inputnode, check_hdr, [('in_file', 'reference')]), (pre_dilate, check_hdr, [('out_file', 'in_file')]), (check_hdr, n4_correct, [('out_file', 'mask_image')]), (inputnode, n4_correct, [('in_file', 'input_image')]), (inputnode, fixhdr_unifize, [('in_file', 'hdr_file')]), (inputnode, fixhdr_skullstrip2, [('in_file', 'hdr_file')]), (n4_correct, skullstrip_first_pass, [('output_image', 'in_file')]), (skullstrip_first_pass, bet_dilate, [('mask_file', 'in_file')]), (bet_dilate, bet_mask, [('out_file', 'mask_file')]), (skullstrip_first_pass, bet_mask, [('out_file', 'in_file')]), (bet_mask, unifize, [('out_file', 'in_file')]), (unifize, fixhdr_unifize, [('out_file', 'in_file')]), (fixhdr_unifize, skullstrip_second_pass, [('out_file', 'in_file')]), (skullstrip_first_pass, combine_masks, [('mask_file', 'in_file')]), (skullstrip_second_pass, fixhdr_skullstrip2, [('out_file', 'in_file')]), (fixhdr_skullstrip2, combine_masks, [('out_file', 'operand_file')]), (fixhdr_unifize, apply_mask, [('out_file', 'in_file')]), (combine_masks, apply_mask, [('out_file', 'mask_file')]), (combine_masks, outputnode, [('out_file', 'mask_file')]), (apply_mask, outputnode, [('out_file', 'skull_stripped_file')]), (n4_correct, outputnode, [('output_image', 'bias_corrected_file')]), ]) return workflow
def init_fsl_bbr_wf(use_bbr, bold2t1w_dof, name='fsl_bbr_wf'): """ This workflow uses FSL FLIRT to register a BOLD image to a T1-weighted structural image, using a boundary-based registration (BBR) cost function. It is a counterpart to :py:func:`~fmriprep.workflows.bold.registration.init_bbreg_wf`, which performs the same task using FreeSurfer's ``bbregister``. The ``use_bbr`` option permits a high degree of control over registration. If ``False``, standard, rigid coregistration will be performed by FLIRT. If ``True``, FLIRT-BBR will be seeded with the initial transform found by the rigid coregistration. If ``None``, after FLIRT-BBR is run, the resulting affine transform will be compared to the initial transform found by FLIRT. Excessive deviation will result in rejecting the BBR refinement and accepting the original, affine registration. .. workflow :: :graph2use: orig :simple_form: yes from fmriprep.workflows.bold.registration import init_fsl_bbr_wf wf = init_fsl_bbr_wf(use_bbr=True, bold2t1w_dof=9) Parameters use_bbr : bool or None Enable/disable boundary-based registration refinement. If ``None``, test BBR result for distortion before accepting. bold2t1w_dof : 6, 9 or 12 Degrees-of-freedom for BOLD-T1w registration name : str, optional Workflow name (default: fsl_bbr_wf) Inputs in_file Reference BOLD image to be registered t1_brain Skull-stripped T1-weighted structural image t1_seg FAST segmentation of ``t1_brain`` t1_2_fsnative_reverse_transform Unused (see :py:func:`~fmriprep.workflows.bold.registration.init_bbreg_wf`) subjects_dir Unused (see :py:func:`~fmriprep.workflows.bold.registration.init_bbreg_wf`) subject_id Unused (see :py:func:`~fmriprep.workflows.bold.registration.init_bbreg_wf`) Outputs itk_bold_to_t1 Affine transform from ``ref_bold_brain`` to T1w space (ITK format) itk_t1_to_bold Affine transform from T1 space to BOLD space (ITK format) out_report Reportlet for assessing registration quality fallback Boolean indicating whether BBR was rejected (rigid FLIRT registration returned) """ workflow = Workflow(name=name) workflow.__desc__ = """\ The BOLD reference was then co-registered to the T1w reference using `flirt` [FSL {fsl_ver}, @flirt] with the boundary-based registration [@bbr] cost-function. Co-registration was configured with nine degrees of freedom to account for distortions remaining in the BOLD reference. """.format(fsl_ver=FLIRTRPT().version or '<ver>') inputnode = pe.Node( niu.IdentityInterface([ 'in_file', 't1_2_fsnative_reverse_transform', 'subjects_dir', 'subject_id', # BBRegister 't1_seg', 't1_brain']), # FLIRT BBR name='inputnode') outputnode = pe.Node( niu.IdentityInterface(['itk_bold_to_t1', 'itk_t1_to_bold', 'out_report', 'fallback']), name='outputnode') wm_mask = pe.Node(niu.Function(function=extract_wm), name='wm_mask') flt_bbr_init = pe.Node(FLIRTRPT(dof=6, generate_report=not use_bbr, uses_qform=True), name='flt_bbr_init') invt_bbr = pe.Node(fsl.ConvertXFM(invert_xfm=True), name='invt_bbr', mem_gb=DEFAULT_MEMORY_MIN_GB) # BOLD to T1 transform matrix is from fsl, using c3 tools to convert to # something ANTs will like. fsl2itk_fwd = pe.Node(c3.C3dAffineTool(fsl2ras=True, itk_transform=True), name='fsl2itk_fwd', mem_gb=DEFAULT_MEMORY_MIN_GB) fsl2itk_inv = pe.Node(c3.C3dAffineTool(fsl2ras=True, itk_transform=True), name='fsl2itk_inv', mem_gb=DEFAULT_MEMORY_MIN_GB) workflow.connect([ (inputnode, flt_bbr_init, [('in_file', 'in_file'), ('t1_brain', 'reference')]), (inputnode, fsl2itk_fwd, [('t1_brain', 'reference_file'), ('in_file', 'source_file')]), (inputnode, fsl2itk_inv, [('in_file', 'reference_file'), ('t1_brain', 'source_file')]), (invt_bbr, fsl2itk_inv, [('out_file', 'transform_file')]), (fsl2itk_fwd, outputnode, [('itk_transform', 'itk_bold_to_t1')]), (fsl2itk_inv, outputnode, [('itk_transform', 'itk_t1_to_bold')]), ]) # Short-circuit workflow building, use rigid registration if use_bbr is False: workflow.connect([ (flt_bbr_init, invt_bbr, [('out_matrix_file', 'in_file')]), (flt_bbr_init, fsl2itk_fwd, [('out_matrix_file', 'transform_file')]), (flt_bbr_init, outputnode, [('out_report', 'out_report')]), ]) outputnode.inputs.fallback = True return workflow flt_bbr = pe.Node( FLIRTRPT(cost_func='bbr', dof=bold2t1w_dof, generate_report=True), name='flt_bbr') FSLDIR = os.getenv('FSLDIR') if FSLDIR: flt_bbr.inputs.schedule = op.join(FSLDIR, 'etc/flirtsch/bbr.sch') else: # Should mostly be hit while building docs LOGGER.warning("FSLDIR unset - using packaged BBR schedule") flt_bbr.inputs.schedule = pkgr.resource_filename('fmriprep', 'data/flirtsch/bbr.sch') workflow.connect([ (inputnode, wm_mask, [('t1_seg', 'in_seg')]), (inputnode, flt_bbr, [('in_file', 'in_file'), ('t1_brain', 'reference')]), (flt_bbr_init, flt_bbr, [('out_matrix_file', 'in_matrix_file')]), (wm_mask, flt_bbr, [('out', 'wm_seg')]), ]) # Short-circuit workflow building, use boundary-based registration if use_bbr is True: workflow.connect([ (flt_bbr, invt_bbr, [('out_matrix_file', 'in_file')]), (flt_bbr, fsl2itk_fwd, [('out_matrix_file', 'transform_file')]), (flt_bbr, outputnode, [('out_report', 'out_report')]), ]) outputnode.inputs.fallback = False return workflow transforms = pe.Node(niu.Merge(2), run_without_submitting=True, name='transforms') reports = pe.Node(niu.Merge(2), run_without_submitting=True, name='reports') compare_transforms = pe.Node(niu.Function(function=compare_xforms), name='compare_transforms') select_transform = pe.Node(niu.Select(), run_without_submitting=True, name='select_transform') select_report = pe.Node(niu.Select(), run_without_submitting=True, name='select_report') fsl_to_lta = pe.MapNode(LTAConvert(out_lta=True), iterfield=['in_fsl'], name='fsl_to_lta') workflow.connect([ (flt_bbr, transforms, [('out_matrix_file', 'in1')]), (flt_bbr_init, transforms, [('out_matrix_file', 'in2')]), # Convert FSL transforms to LTA (RAS2RAS) transforms and compare (inputnode, fsl_to_lta, [('in_file', 'source_file'), ('t1_brain', 'target_file')]), (transforms, fsl_to_lta, [('out', 'in_fsl')]), (fsl_to_lta, compare_transforms, [('out_lta', 'lta_list')]), (compare_transforms, outputnode, [('out', 'fallback')]), # Select output transform (transforms, select_transform, [('out', 'inlist')]), (compare_transforms, select_transform, [('out', 'index')]), (select_transform, invt_bbr, [('out', 'in_file')]), (select_transform, fsl2itk_fwd, [('out', 'transform_file')]), (flt_bbr, reports, [('out_report', 'in1')]), (flt_bbr_init, reports, [('out_report', 'in2')]), (reports, select_report, [('out', 'inlist')]), (compare_transforms, select_report, [('out', 'index')]), (select_report, outputnode, [('out', 'out_report')]), ]) return workflow
def init_bold_reg_wf(freesurfer, use_bbr, bold2t1w_dof, mem_gb, omp_nthreads, use_compression=True, write_report=True, name='bold_reg_wf'): """ Calculates the registration between a reference BOLD image and T1-space using a boundary-based registration (BBR) cost function. If FreeSurfer-based preprocessing is enabled, the ``bbregister`` utility is used to align the BOLD images to the reconstructed subject, and the resulting transform is adjusted to target the T1 space. If FreeSurfer-based preprocessing is disabled, FSL FLIRT is used with the BBR cost function to directly target the T1 space. .. workflow:: :graph2use: orig :simple_form: yes from fmriprep.workflows.bold.registration import init_bold_reg_wf wf = init_bold_reg_wf(freesurfer=True, mem_gb=3, omp_nthreads=1, use_bbr=True, bold2t1w_dof=9) **Parameters** freesurfer : bool Enable FreeSurfer functional registration (bbregister) use_bbr : bool or None Enable/disable boundary-based registration refinement. If ``None``, test BBR result for distortion before accepting. bold2t1w_dof : 6, 9 or 12 Degrees-of-freedom for BOLD-T1w registration mem_gb : float Size of BOLD file in GB omp_nthreads : int Maximum number of threads an individual process may use name : str Name of workflow (default: ``bold_reg_wf``) use_compression : bool Save registered BOLD series as ``.nii.gz`` use_fieldwarp : bool Include SDC warp in single-shot transform from BOLD to T1 write_report : bool Whether a reportlet should be stored **Inputs** ref_bold_brain Reference image to which BOLD series is aligned If ``fieldwarp == True``, ``ref_bold_brain`` should be unwarped t1_brain Skull-stripped ``t1_preproc`` t1_seg Segmentation of preprocessed structural image, including gray-matter (GM), white-matter (WM) and cerebrospinal fluid (CSF) subjects_dir FreeSurfer SUBJECTS_DIR subject_id FreeSurfer subject ID t1_2_fsnative_reverse_transform LTA-style affine matrix translating from FreeSurfer-conformed subject space to T1w **Outputs** itk_bold_to_t1 Affine transform from ``ref_bold_brain`` to T1 space (ITK format) itk_t1_to_bold Affine transform from T1 space to BOLD space (ITK format) fallback Boolean indicating whether BBR was rejected (mri_coreg registration returned) **Subworkflows** * :py:func:`~fmriprep.workflows.bold.registration.init_bbreg_wf` * :py:func:`~fmriprep.workflows.bold.registration.init_fsl_bbr_wf` """ workflow = Workflow(name=name) inputnode = pe.Node( niu.IdentityInterface( fields=['ref_bold_brain', 't1_brain', 't1_seg', 'subjects_dir', 'subject_id', 't1_2_fsnative_reverse_transform']), name='inputnode' ) outputnode = pe.Node( niu.IdentityInterface(fields=[ 'itk_bold_to_t1', 'itk_t1_to_bold', 'fallback']), name='outputnode' ) if freesurfer: bbr_wf = init_bbreg_wf(use_bbr=use_bbr, bold2t1w_dof=bold2t1w_dof, omp_nthreads=omp_nthreads) else: bbr_wf = init_fsl_bbr_wf(use_bbr=use_bbr, bold2t1w_dof=bold2t1w_dof) workflow.connect([ (inputnode, bbr_wf, [ ('ref_bold_brain', 'inputnode.in_file'), ('t1_2_fsnative_reverse_transform', 'inputnode.t1_2_fsnative_reverse_transform'), ('subjects_dir', 'inputnode.subjects_dir'), ('subject_id', 'inputnode.subject_id'), ('t1_seg', 'inputnode.t1_seg'), ('t1_brain', 'inputnode.t1_brain')]), (bbr_wf, outputnode, [('outputnode.itk_bold_to_t1', 'itk_bold_to_t1'), ('outputnode.itk_t1_to_bold', 'itk_t1_to_bold'), ('outputnode.fallback', 'fallback')]), ]) if write_report: ds_report_reg = pe.Node( DerivativesDataSink(), name='ds_report_reg', run_without_submitting=True, mem_gb=DEFAULT_MEMORY_MIN_GB) def _bold_reg_suffix(fallback, freesurfer): if fallback: return 'coreg' if freesurfer else 'flirtnobbr' return 'bbregister' if freesurfer else 'flirtbbr' workflow.connect([ (bbr_wf, ds_report_reg, [ ('outputnode.out_report', 'in_file'), (('outputnode.fallback', _bold_reg_suffix, freesurfer), 'suffix')]), ]) return workflow
def init_bbreg_wf(use_bbr, bold2t1w_dof, omp_nthreads, name='bbreg_wf'): """ This workflow uses FreeSurfer's ``bbregister`` to register a BOLD image to a T1-weighted structural image. It is a counterpart to :py:func:`~fmriprep.workflows.bold.registration.init_fsl_bbr_wf`, which performs the same task using FSL's FLIRT with a BBR cost function. The ``use_bbr`` option permits a high degree of control over registration. If ``False``, standard, affine coregistration will be performed using FreeSurfer's ``mri_coreg`` tool. If ``True``, ``bbregister`` will be seeded with the initial transform found by ``mri_coreg`` (equivalent to running ``bbregister --init-coreg``). If ``None``, after ``bbregister`` is run, the resulting affine transform will be compared to the initial transform found by ``mri_coreg``. Excessive deviation will result in rejecting the BBR refinement and accepting the original, affine registration. .. workflow :: :graph2use: orig :simple_form: yes from fmriprep.workflows.bold.registration import init_bbreg_wf wf = init_bbreg_wf(use_bbr=True, bold2t1w_dof=9, omp_nthreads=1) Parameters use_bbr : bool or None Enable/disable boundary-based registration refinement. If ``None``, test BBR result for distortion before accepting. bold2t1w_dof : 6, 9 or 12 Degrees-of-freedom for BOLD-T1w registration name : str, optional Workflow name (default: bbreg_wf) Inputs in_file Reference BOLD image to be registered t1_2_fsnative_reverse_transform FSL-style affine matrix translating from FreeSurfer T1.mgz to T1w subjects_dir FreeSurfer SUBJECTS_DIR subject_id FreeSurfer subject ID (must have folder in SUBJECTS_DIR) t1_brain Unused (see :py:func:`~fmriprep.workflows.bold.registration.init_fsl_bbr_wf`) t1_seg Unused (see :py:func:`~fmriprep.workflows.bold.registration.init_fsl_bbr_wf`) Outputs itk_bold_to_t1 Affine transform from ``ref_bold_brain`` to T1 space (ITK format) itk_t1_to_bold Affine transform from T1 space to BOLD space (ITK format) out_report Reportlet for assessing registration quality fallback Boolean indicating whether BBR was rejected (mri_coreg registration returned) """ workflow = Workflow(name=name) workflow.__desc__ = """\ The BOLD reference was then co-registered to the T1w reference using `bbregister` (FreeSurfer) which implements boundary-based registration [@bbr]. Co-registration was configured with nine degrees of freedom to account for distortions remaining in the BOLD reference. """ inputnode = pe.Node( niu.IdentityInterface([ 'in_file', 't1_2_fsnative_reverse_transform', 'subjects_dir', 'subject_id', # BBRegister 't1_seg', 't1_brain']), # FLIRT BBR name='inputnode') outputnode = pe.Node( niu.IdentityInterface(['itk_bold_to_t1', 'itk_t1_to_bold', 'out_report', 'fallback']), name='outputnode') mri_coreg = pe.Node( MRICoregRPT(dof=bold2t1w_dof, sep=[4], ftol=0.0001, linmintol=0.01, generate_report=not use_bbr), name='mri_coreg', n_procs=omp_nthreads, mem_gb=5) lta_concat = pe.Node(ConcatenateLTA(out_file='out.lta'), name='lta_concat') # XXX LTA-FSL-ITK may ultimately be able to be replaced with a straightforward # LTA-ITK transform, but right now the translation parameters are off. lta2fsl_fwd = pe.Node(LTAConvert(out_fsl=True), name='lta2fsl_fwd') lta2fsl_inv = pe.Node(LTAConvert(out_fsl=True, invert=True), name='lta2fsl_inv') fsl2itk_fwd = pe.Node(c3.C3dAffineTool(fsl2ras=True, itk_transform=True), name='fsl2itk_fwd', mem_gb=DEFAULT_MEMORY_MIN_GB) fsl2itk_inv = pe.Node(c3.C3dAffineTool(fsl2ras=True, itk_transform=True), name='fsl2itk_inv', mem_gb=DEFAULT_MEMORY_MIN_GB) workflow.connect([ (inputnode, mri_coreg, [('subjects_dir', 'subjects_dir'), ('subject_id', 'subject_id'), ('in_file', 'source_file')]), # Output ITK transforms (inputnode, lta_concat, [('t1_2_fsnative_reverse_transform', 'in_lta2')]), (lta_concat, lta2fsl_fwd, [('out_file', 'in_lta')]), (lta_concat, lta2fsl_inv, [('out_file', 'in_lta')]), (inputnode, fsl2itk_fwd, [('t1_brain', 'reference_file'), ('in_file', 'source_file')]), (inputnode, fsl2itk_inv, [('in_file', 'reference_file'), ('t1_brain', 'source_file')]), (lta2fsl_fwd, fsl2itk_fwd, [('out_fsl', 'transform_file')]), (lta2fsl_inv, fsl2itk_inv, [('out_fsl', 'transform_file')]), (fsl2itk_fwd, outputnode, [('itk_transform', 'itk_bold_to_t1')]), (fsl2itk_inv, outputnode, [('itk_transform', 'itk_t1_to_bold')]), ]) # Short-circuit workflow building, use initial registration if use_bbr is False: workflow.connect([ (mri_coreg, outputnode, [('out_report', 'out_report')]), (mri_coreg, lta_concat, [('out_lta_file', 'in_lta1')])]) outputnode.inputs.fallback = True return workflow bbregister = pe.Node( BBRegisterRPT(dof=bold2t1w_dof, contrast_type='t2', registered_file=True, out_lta_file=True, generate_report=True), name='bbregister', mem_gb=12) workflow.connect([ (inputnode, bbregister, [('subjects_dir', 'subjects_dir'), ('subject_id', 'subject_id'), ('in_file', 'source_file')]), (mri_coreg, bbregister, [('out_lta_file', 'init_reg_file')]), ]) # Short-circuit workflow building, use boundary-based registration if use_bbr is True: workflow.connect([ (bbregister, outputnode, [('out_report', 'out_report')]), (bbregister, lta_concat, [('out_lta_file', 'in_lta1')])]) outputnode.inputs.fallback = False return workflow transforms = pe.Node(niu.Merge(2), run_without_submitting=True, name='transforms') reports = pe.Node(niu.Merge(2), run_without_submitting=True, name='reports') lta_ras2ras = pe.MapNode(LTAConvert(out_lta=True), iterfield=['in_lta'], name='lta_ras2ras', mem_gb=2) compare_transforms = pe.Node(niu.Function(function=compare_xforms), name='compare_transforms') select_transform = pe.Node(niu.Select(), run_without_submitting=True, name='select_transform') select_report = pe.Node(niu.Select(), run_without_submitting=True, name='select_report') workflow.connect([ (bbregister, transforms, [('out_lta_file', 'in1')]), (mri_coreg, transforms, [('out_lta_file', 'in2')]), # Normalize LTA transforms to RAS2RAS (inputs are VOX2VOX) and compare (transforms, lta_ras2ras, [('out', 'in_lta')]), (lta_ras2ras, compare_transforms, [('out_lta', 'lta_list')]), (compare_transforms, outputnode, [('out', 'fallback')]), # Select output transform (transforms, select_transform, [('out', 'inlist')]), (compare_transforms, select_transform, [('out', 'index')]), (select_transform, lta_concat, [('out', 'in_lta1')]), # Select output report (bbregister, reports, [('out_report', 'in1')]), (mri_coreg, reports, [('out_report', 'in2')]), (reports, select_report, [('out', 'inlist')]), (compare_transforms, select_report, [('out', 'index')]), (select_report, outputnode, [('out', 'out_report')]), ]) return workflow
def init_surface_recon_wf(omp_nthreads, hires, name='surface_recon_wf'): r""" Reconstruct anatomical surfaces using FreeSurfer's ``recon-all``. Reconstruction is performed in three phases. The first phase initializes the subject with T1w and T2w (if available) structural images and performs basic reconstruction (``autorecon1``) with the exception of skull-stripping. For example, a subject with only one session with T1w and T2w images would be processed by the following command:: $ recon-all -sd <output dir>/freesurfer -subjid sub-<subject_label> \ -i <bids-root>/sub-<subject_label>/anat/sub-<subject_label>_T1w.nii.gz \ -T2 <bids-root>/sub-<subject_label>/anat/sub-<subject_label>_T2w.nii.gz \ -autorecon1 \ -noskullstrip The second phase imports an externally computed skull-stripping mask. This workflow refines the external brainmask using the internal mask implicit the the FreeSurfer's ``aseg.mgz`` segmentation, to reconcile ANTs' and FreeSurfer's brain masks. First, the ``aseg.mgz`` mask from FreeSurfer is refined in two steps, using binary morphological operations: 1. With a binary closing operation the sulci are included into the mask. This results in a smoother brain mask that does not exclude deep, wide sulci. 2. Fill any holes (typically, there could be a hole next to the pineal gland and the corpora quadrigemina if the great cerebral brain is segmented out). Second, the brain mask is grown, including pixels that have a high likelihood to the GM tissue distribution: 3. Dilate and substract the brain mask, defining the region to search for candidate pixels that likely belong to cortical GM. 4. Pixels found in the search region that are labeled as GM by ANTs (during ``antsBrainExtraction.sh``) are directly added to the new mask. 5. Otherwise, estimate GM tissue parameters locally in patches of ``ww`` size, and test the likelihood of the pixel to belong in the GM distribution. This procedure is inspired on mindboggle's solution to the problem: https://github.com/nipy/mindboggle/blob/7f91faaa7664d820fe12ccc52ebaf21d679795e2/mindboggle/guts/segment.py#L1660 The final phase resumes reconstruction, using the T2w image to assist in finding the pial surface, if available. See :py:func:`~smriprep.workflows.surfaces.init_autorecon_resume_wf` for details. Memory annotations for FreeSurfer are based off `their documentation <https://surfer.nmr.mgh.harvard.edu/fswiki/SystemRequirements>`_. They specify an allocation of 4GB per subject. Here we define 5GB to have a certain margin. .. workflow:: :graph2use: orig :simple_form: yes from smriprep.workflows.surfaces import init_surface_recon_wf wf = init_surface_recon_wf(omp_nthreads=1, hires=True) **Parameters** omp_nthreads : int Maximum number of threads an individual process may use hires : bool Enable sub-millimeter preprocessing in FreeSurfer **Inputs** t1w List of T1-weighted structural images t2w List of T2-weighted structural images (only first used) flair List of FLAIR images skullstripped_t1 Skull-stripped T1-weighted image (or mask of image) ants_segs Brain tissue segmentation from ANTS ``antsBrainExtraction.sh`` corrected_t1 INU-corrected, merged T1-weighted image subjects_dir FreeSurfer SUBJECTS_DIR subject_id FreeSurfer subject ID **Outputs** subjects_dir FreeSurfer SUBJECTS_DIR subject_id FreeSurfer subject ID t1w2fsnative_xfm LTA-style affine matrix translating from T1w to FreeSurfer-conformed subject space fsnative2t1w_xfm LTA-style affine matrix translating from FreeSurfer-conformed subject space to T1w surfaces GIFTI surfaces for gray/white matter boundary, pial surface, midthickness (or graymid) surface, and inflated surfaces out_brainmask Refined brainmask, derived from FreeSurfer's ``aseg`` volume out_aseg FreeSurfer's aseg segmentation, in native T1w space out_aparc FreeSurfer's aparc+aseg segmentation, in native T1w space **Subworkflows** * :py:func:`~smriprep.workflows.surfaces.init_autorecon_resume_wf` * :py:func:`~smriprep.workflows.surfaces.init_gifti_surface_wf` """ workflow = Workflow(name=name) workflow.__desc__ = """\ Brain surfaces were reconstructed using `recon-all` [FreeSurfer {fs_ver}, RRID:SCR_001847, @fs_reconall], and the brain mask estimated previously was refined with a custom variation of the method to reconcile ANTs-derived and FreeSurfer-derived segmentations of the cortical gray-matter of Mindboggle [RRID:SCR_002438, @mindboggle]. """.format(fs_ver=fs.Info().looseversion() or '<ver>') inputnode = pe.Node(niu.IdentityInterface(fields=[ 't1w', 't2w', 'flair', 'skullstripped_t1', 'corrected_t1', 'ants_segs', 'subjects_dir', 'subject_id' ]), name='inputnode') outputnode = pe.Node(niu.IdentityInterface(fields=[ 'subjects_dir', 'subject_id', 't1w2fsnative_xfm', 'fsnative2t1w_xfm', 'surfaces', 'out_brainmask', 'out_aseg', 'out_aparc' ]), name='outputnode') recon_config = pe.Node(FSDetectInputs(hires_enabled=hires), name='recon_config') fov_check = pe.Node(niu.Function(function=_check_cw256), name='fov_check') autorecon1 = pe.Node(fs.ReconAll(directive='autorecon1', openmp=omp_nthreads), name='autorecon1', n_procs=omp_nthreads, mem_gb=5) autorecon1.interface._can_resume = False autorecon1.interface._always_run = True skull_strip_extern = pe.Node(FSInjectBrainExtracted(), name='skull_strip_extern') fsnative2t1w_xfm = pe.Node(RobustRegister(auto_sens=True, est_int_scale=True), name='fsnative2t1w_xfm') t1w2fsnative_xfm = pe.Node(LTAConvert(out_lta=True, invert=True), name='t1w2fsnative_xfm') autorecon_resume_wf = init_autorecon_resume_wf(omp_nthreads=omp_nthreads) gifti_surface_wf = init_gifti_surface_wf() aseg_to_native_wf = init_segs_to_native_wf() aparc_to_native_wf = init_segs_to_native_wf(segmentation='aparc_aseg') refine = pe.Node(RefineBrainMask(), name='refine') workflow.connect([ # Configuration (inputnode, recon_config, [('t1w', 't1w_list'), ('t2w', 't2w_list'), ('flair', 'flair_list')]), # Passing subjects_dir / subject_id enforces serial order (inputnode, autorecon1, [('subjects_dir', 'subjects_dir'), ('subject_id', 'subject_id')]), (autorecon1, skull_strip_extern, [('subjects_dir', 'subjects_dir'), ('subject_id', 'subject_id')]), (skull_strip_extern, autorecon_resume_wf, [('subjects_dir', 'inputnode.subjects_dir'), ('subject_id', 'inputnode.subject_id')]), (autorecon_resume_wf, gifti_surface_wf, [('outputnode.subjects_dir', 'inputnode.subjects_dir'), ('outputnode.subject_id', 'inputnode.subject_id')]), # Reconstruction phases (inputnode, autorecon1, [('t1w', 'T1_files')]), (inputnode, fov_check, [('t1w', 'in_files')]), (fov_check, autorecon1, [('out', 'flags')]), ( recon_config, autorecon1, [ ('t2w', 'T2_file'), ('flair', 'FLAIR_file'), ('hires', 'hires'), # First run only (recon-all saves expert options) ('mris_inflate', 'mris_inflate') ]), (inputnode, skull_strip_extern, [('skullstripped_t1', 'in_brain')]), (recon_config, autorecon_resume_wf, [('use_t2w', 'inputnode.use_T2'), ('use_flair', 'inputnode.use_FLAIR')]), # Construct transform from FreeSurfer conformed image to sMRIPrep # reoriented image (inputnode, fsnative2t1w_xfm, [('t1w', 'target_file')]), (autorecon1, fsnative2t1w_xfm, [('T1', 'source_file')]), (fsnative2t1w_xfm, gifti_surface_wf, [('out_reg_file', 'inputnode.fsnative2t1w_xfm')]), (fsnative2t1w_xfm, t1w2fsnative_xfm, [('out_reg_file', 'in_lta')]), # Refine ANTs mask, deriving new mask from FS' aseg (inputnode, refine, [('corrected_t1', 'in_anat'), ('ants_segs', 'in_ants')]), (inputnode, aseg_to_native_wf, [('corrected_t1', 'inputnode.in_file') ]), (autorecon_resume_wf, aseg_to_native_wf, [('outputnode.subjects_dir', 'inputnode.subjects_dir'), ('outputnode.subject_id', 'inputnode.subject_id')]), (inputnode, aparc_to_native_wf, [('corrected_t1', 'inputnode.in_file') ]), (autorecon_resume_wf, aparc_to_native_wf, [('outputnode.subjects_dir', 'inputnode.subjects_dir'), ('outputnode.subject_id', 'inputnode.subject_id')]), (aseg_to_native_wf, refine, [('outputnode.out_file', 'in_aseg')]), # Output (autorecon_resume_wf, outputnode, [('outputnode.subjects_dir', 'subjects_dir'), ('outputnode.subject_id', 'subject_id')]), (gifti_surface_wf, outputnode, [('outputnode.surfaces', 'surfaces')]), (t1w2fsnative_xfm, outputnode, [('out_lta', 't1w2fsnative_xfm')]), (fsnative2t1w_xfm, outputnode, [('out_reg_file', 'fsnative2t1w_xfm')]), (refine, outputnode, [('out_file', 'out_brainmask')]), (aseg_to_native_wf, outputnode, [('outputnode.out_file', 'out_aseg')]), (aparc_to_native_wf, outputnode, [('outputnode.out_file', 'out_aparc') ]), ]) return workflow
def init_segs_to_native_wf(name='segs_to_native', segmentation='aseg'): """ Get a segmentation from FreeSurfer conformed space into native T1w space. .. workflow:: :graph2use: orig :simple_form: yes from smriprep.workflows.surfaces import init_segs_to_native_wf wf = init_segs_to_native_wf() **Parameters** segmentation The name of a segmentation ('aseg' or 'aparc_aseg' or 'wmparc') **Inputs** in_file Anatomical, merged T1w image after INU correction subjects_dir FreeSurfer SUBJECTS_DIR subject_id FreeSurfer subject ID **Outputs** out_file The selected segmentation, after resampling in native space """ workflow = Workflow(name='%s_%s' % (name, segmentation)) inputnode = pe.Node(niu.IdentityInterface( ['in_file', 'subjects_dir', 'subject_id']), name='inputnode') outputnode = pe.Node(niu.IdentityInterface(['out_file']), name='outputnode') # Extract the aseg and aparc+aseg outputs fssource = pe.Node(nio.FreeSurferSource(), name='fs_datasource') tonative = pe.Node(fs.Label2Vol(), name='tonative') tonii = pe.Node(fs.MRIConvert(out_type='niigz', resample_type='nearest'), name='tonii') if segmentation.startswith('aparc'): if segmentation == 'aparc_aseg': def _sel(x): return [parc for parc in x if 'aparc+' in parc][0] elif segmentation == 'aparc_a2009s': def _sel(x): return [parc for parc in x if 'a2009s+' in parc][0] elif segmentation == 'aparc_dkt': def _sel(x): return [parc for parc in x if 'DKTatlas+' in parc][0] segmentation = (segmentation, _sel) workflow.connect([ (inputnode, fssource, [('subjects_dir', 'subjects_dir'), ('subject_id', 'subject_id')]), (inputnode, tonii, [('in_file', 'reslice_like')]), (fssource, tonative, [(segmentation, 'seg_file'), ('rawavg', 'template_file'), ('aseg', 'reg_header')]), (tonative, tonii, [('vol_label_file', 'in_file')]), (tonii, outputnode, [('out_file', 'out_file')]), ]) return workflow
def init_skullstrip_bold_wf(name='skullstrip_bold_wf'): """ This workflow applies skull-stripping to a BOLD image. It is intended to be used on an image that has previously been bias-corrected with :py:func:`~fmriprep.workflows.bold.util.init_enhance_and_skullstrip_bold_wf` .. workflow :: :graph2use: orig :simple_form: yes from fmriprep.workflows.bold.util import init_skullstrip_bold_wf wf = init_skullstrip_bold_wf() Inputs in_file BOLD image (single volume) Outputs skull_stripped_file the ``in_file`` after skull-stripping mask_file mask of the skull-stripped input file out_report reportlet for the skull-stripping """ workflow = Workflow(name=name) inputnode = pe.Node(niu.IdentityInterface(fields=['in_file']), name='inputnode') outputnode = pe.Node(niu.IdentityInterface(fields=['mask_file', 'skull_stripped_file', 'out_report']), name='outputnode') skullstrip_first_pass = pe.Node(fsl.BET(frac=0.2, mask=True), name='skullstrip_first_pass') skullstrip_second_pass = pe.Node(afni.Automask(dilate=1, outputtype='NIFTI_GZ'), name='skullstrip_second_pass') combine_masks = pe.Node(fsl.BinaryMaths(operation='mul'), name='combine_masks') apply_mask = pe.Node(fsl.ApplyMask(), name='apply_mask') mask_reportlet = pe.Node(SimpleShowMaskRPT(), name='mask_reportlet') workflow.connect([ (inputnode, skullstrip_first_pass, [('in_file', 'in_file')]), (skullstrip_first_pass, skullstrip_second_pass, [('out_file', 'in_file')]), (skullstrip_first_pass, combine_masks, [('mask_file', 'in_file')]), (skullstrip_second_pass, combine_masks, [('out_file', 'operand_file')]), (combine_masks, outputnode, [('out_file', 'mask_file')]), # Masked file (inputnode, apply_mask, [('in_file', 'in_file')]), (combine_masks, apply_mask, [('out_file', 'mask_file')]), (apply_mask, outputnode, [('out_file', 'skull_stripped_file')]), # Reportlet (inputnode, mask_reportlet, [('in_file', 'background_file')]), (combine_masks, mask_reportlet, [('out_file', 'mask_file')]), (mask_reportlet, outputnode, [('out_report', 'out_report')]), ]) return workflow
def init_bold_preproc_trans_wf(mem_gb, omp_nthreads, name='bold_preproc_trans_wf', use_compression=True, use_fieldwarp=False, split_file=False, interpolation='LanczosWindowedSinc'): """ This workflow resamples the input fMRI in its native (original) space in a "single shot" from the original BOLD series. .. workflow:: :graph2use: colored :simple_form: yes from fmriprep.workflows.bold import init_bold_preproc_trans_wf wf = init_bold_preproc_trans_wf(mem_gb=3, omp_nthreads=1) **Parameters** mem_gb : float Size of BOLD file in GB omp_nthreads : int Maximum number of threads an individual process may use name : str Name of workflow (default: ``bold_std_trans_wf``) use_compression : bool Save registered BOLD series as ``.nii.gz`` use_fieldwarp : bool Include SDC warp in single-shot transform from BOLD to MNI split_file : bool Whether the input file should be splitted (it is a 4D file) or it is a list of 3D files (default ``False``, do not split) interpolation : str Interpolation type to be used by ANTs' ``applyTransforms`` (default ``'LanczosWindowedSinc'``) **Inputs** bold_file Individual 3D volumes, not motion corrected bold_mask Skull-stripping mask of reference image name_source BOLD series NIfTI file Used to recover original information lost during processing hmc_xforms List of affine transforms aligning each volume to ``ref_image`` in ITK format fieldwarp a :abbr:`DFM (displacements field map)` in ITK format **Outputs** bold BOLD series, resampled in native space, including all preprocessing bold_mask BOLD series mask calculated with the new time-series bold_ref BOLD reference image: an average-like 3D image of the time-series bold_ref_brain Same as ``bold_ref``, but once the brain mask has been applied """ workflow = Workflow(name=name) workflow.__desc__ = """\ The BOLD time-series (including slice-timing correction when applied) were resampled onto their original, native space by applying {transforms}. These resampled BOLD time-series will be referred to as *preprocessed BOLD in original space*, or just *preprocessed BOLD*. """.format(transforms="""\ a single, composite transform to correct for head-motion and susceptibility distortions""" if use_fieldwarp else """\ the transforms to correct for head-motion""") inputnode = pe.Node(niu.IdentityInterface(fields=[ 'name_source', 'bold_file', 'bold_mask', 'hmc_xforms', 'fieldwarp' ]), name='inputnode') outputnode = pe.Node(niu.IdentityInterface( fields=['bold', 'bold_mask', 'bold_ref', 'bold_ref_brain']), name='outputnode') bold_transform = pe.Node(MultiApplyTransforms(interpolation=interpolation, float=True, copy_dtype=True), name='bold_transform', mem_gb=mem_gb * 3 * omp_nthreads, n_procs=omp_nthreads) merge = pe.Node(Merge(compress=use_compression), name='merge', mem_gb=mem_gb * 3) # Generate a new BOLD reference bold_reference_wf = init_bold_reference_wf(omp_nthreads=omp_nthreads) bold_reference_wf.__desc__ = None # Unset description to avoid second appearance workflow.connect([ (inputnode, merge, [('name_source', 'header_source')]), (bold_transform, merge, [('out_files', 'in_files')]), (merge, bold_reference_wf, [('out_file', 'inputnode.bold_file')]), (merge, outputnode, [('out_file', 'bold')]), (bold_reference_wf, outputnode, [('outputnode.ref_image', 'bold_ref'), ('outputnode.ref_image_brain', 'bold_ref_brain'), ('outputnode.bold_mask', 'bold_mask')]), ]) # Input file is not splitted if split_file: bold_split = pe.Node(FSLSplit(dimension='t'), name='bold_split', mem_gb=mem_gb * 3) workflow.connect([(inputnode, bold_split, [('bold_file', 'in_file')]), (bold_split, bold_transform, [ ('out_files', 'input_image'), (('out_files', _first), 'reference_image'), ])]) else: workflow.connect([ (inputnode, bold_transform, [('bold_file', 'input_image'), (('bold_file', _first), 'reference_image')]), ]) if use_fieldwarp: merge_xforms = pe.Node(niu.Merge(2), name='merge_xforms', run_without_submitting=True, mem_gb=DEFAULT_MEMORY_MIN_GB) workflow.connect([ (inputnode, merge_xforms, [('fieldwarp', 'in1'), ('hmc_xforms', 'in2')]), (merge_xforms, bold_transform, [('out', 'transforms')]), ]) else: def _aslist(val): return [val] workflow.connect([ (inputnode, bold_transform, [(('hmc_xforms', _aslist), 'transforms')]), ]) # Code ready to generate a pre/post processing report # bold_bold_report_wf = init_bold_preproc_report_wf( # mem_gb=mem_gb['resampled'], # reportlets_dir=reportlets_dir # ) # workflow.connect([ # (inputnode, bold_bold_report_wf, [ # ('bold_file', 'inputnode.name_source'), # ('bold_file', 'inputnode.in_pre')]), # This should be after STC # (bold_bold_trans_wf, bold_bold_report_wf, [ # ('outputnode.bold', 'inputnode.in_post')]), # ]) return workflow
def init_phdiff_wf(omp_nthreads, phasetype='phasediff', name='phdiff_wf'): """ Estimates the fieldmap using a phase-difference image and one or more magnitude images corresponding to two or more :abbr:`GRE (Gradient Echo sequence)` acquisitions. The `original code was taken from nipype <https://github.com/nipy/nipype/blob/master/nipype/workflows/dmri/fsl/artifacts.py#L514>`_. .. workflow :: :graph2use: orig :simple_form: yes from fmriprep.workflows.fieldmap.phdiff import init_phdiff_wf wf = init_phdiff_wf(omp_nthreads=1) Outputs:: outputnode.fmap_ref - The average magnitude image, skull-stripped outputnode.fmap_mask - The brain mask applied to the fieldmap outputnode.fmap - The estimated fieldmap in Hz """ workflow = Workflow(name=name) workflow.__desc__ = """\ A deformation field to correct for susceptibility distortions was estimated based on a field map that was co-registered to the BOLD reference, using a custom workflow of *fMRIPrep* derived from D. Greve's `epidewarp.fsl` [script](http://www.nmr.mgh.harvard.edu/~greve/fbirn/b0/epidewarp.fsl) and further improvements of HCP Pipelines [@hcppipelines]. """ inputnode = pe.Node( niu.IdentityInterface(fields=['magnitude', 'phasediff']), name='inputnode') outputnode = pe.Node( niu.IdentityInterface(fields=['fmap', 'fmap_ref', 'fmap_mask']), name='outputnode') def _pick1st(inlist): return inlist[0] # Read phasediff echo times meta = pe.Node(ReadSidecarJSON(), name='meta', mem_gb=0.01, run_without_submitting=True) # Merge input magnitude images magmrg = pe.Node(IntraModalMerge(), name='magmrg') # de-gradient the fields ("bias/illumination artifact") n4 = pe.Node(ants.N4BiasFieldCorrection(dimension=3, copy_header=True), name='n4', n_procs=omp_nthreads) bet = pe.Node(BETRPT(generate_report=True, frac=0.6, mask=True), name='bet') ds_report_fmap_mask = pe.Node(DerivativesDataSink(desc='brain', suffix='mask'), name='ds_report_fmap_mask', mem_gb=0.01, run_without_submitting=True) # uses mask from bet; outputs a mask # dilate = pe.Node(fsl.maths.MathsCommand( # nan2zeros=True, args='-kernel sphere 5 -dilM'), name='MskDilate') # phase diff -> radians pha2rads = pe.Node(niu.Function(function=siemens2rads), name='pha2rads') # FSL PRELUDE will perform phase-unwrapping prelude = pe.Node(fsl.PRELUDE(), name='prelude') denoise = pe.Node(fsl.SpatialFilter(operation='median', kernel_shape='sphere', kernel_size=5), name='denoise') demean = pe.Node(niu.Function(function=demean_image), name='demean') cleanup_wf = cleanup_edge_pipeline(name="cleanup_wf") compfmap = pe.Node(Phasediff2Fieldmap(), name='compfmap') # The phdiff2fmap interface is equivalent to: # rad2rsec (using rads2radsec from nipype.workflows.dmri.fsl.utils) # pre_fugue = pe.Node(fsl.FUGUE(save_fmap=True), name='ComputeFieldmapFUGUE') # rsec2hz (divide by 2pi) if phasetype == "phasediff": # Read phasediff echo times meta = pe.Node(ReadSidecarJSON(), name='meta', mem_gb=0.01) # phase diff -> radians pha2rads = pe.Node(niu.Function(function=siemens2rads), name='pha2rads') # Read phasediff echo times meta = pe.Node(ReadSidecarJSON(), name='meta', mem_gb=0.01, run_without_submitting=True) workflow.connect([ (meta, compfmap, [('out_dict', 'metadata')]), (inputnode, pha2rads, [('phasediff', 'in_file')]), (pha2rads, prelude, [('out', 'phase_file')]), (inputnode, ds_report_fmap_mask, [('phasediff', 'source_file')]), ]) elif phasetype == "phase": workflow.__desc__ += """\ The phase difference used for unwarping was calculated using two separate phase measurements [@pncprocessing]. """ # Special case for phase1, phase2 images meta = pe.MapNode(ReadSidecarJSON(), name='meta', mem_gb=0.01, run_without_submitting=True, iterfield=['in_file']) phases2fmap = pe.Node(Phases2Fieldmap(), name='phases2fmap') workflow.connect([ (meta, phases2fmap, [('out_dict', 'metadatas')]), (inputnode, phases2fmap, [('phasediff', 'phase_files')]), (phases2fmap, prelude, [('out_file', 'phase_file')]), (phases2fmap, compfmap, [('phasediff_metadata', 'metadata')]), (phases2fmap, ds_report_fmap_mask, [('out_file', 'source_file')]) ]) workflow.connect([ (inputnode, meta, [('phasediff', 'in_file')]), (inputnode, magmrg, [('magnitude', 'in_files')]), (magmrg, n4, [('out_avg', 'input_image')]), (n4, prelude, [('output_image', 'magnitude_file')]), (n4, bet, [('output_image', 'in_file')]), (bet, prelude, [('mask_file', 'mask_file')]), (prelude, denoise, [('unwrapped_phase_file', 'in_file')]), (denoise, demean, [('out_file', 'in_file')]), (demean, cleanup_wf, [('out', 'inputnode.in_file')]), (bet, cleanup_wf, [('mask_file', 'inputnode.in_mask')]), (cleanup_wf, compfmap, [('outputnode.out_file', 'in_file')]), (compfmap, outputnode, [('out_file', 'fmap')]), (bet, outputnode, [('mask_file', 'fmap_mask'), ('out_file', 'fmap_ref')]), (bet, ds_report_fmap_mask, [('out_report', 'in_file')]), ]) return workflow
def init_fsl_bbr_wf(use_bbr, bold2t1w_dof, bold2t1w_init, sloppy=False, name='fsl_bbr_wf'): """ Build a workflow to run FSL's ``flirt``. This workflow uses FSL FLIRT to register a BOLD image to a T1-weighted structural image, using a boundary-based registration (BBR) cost function. It is a counterpart to :py:func:`~fmriprep.workflows.bold.registration.init_bbreg_wf`, which performs the same task using FreeSurfer's ``bbregister``. The ``use_bbr`` option permits a high degree of control over registration. If ``False``, standard, rigid coregistration will be performed by FLIRT. If ``True``, FLIRT-BBR will be seeded with the initial transform found by the rigid coregistration. If ``None``, after FLIRT-BBR is run, the resulting affine transform will be compared to the initial transform found by FLIRT. Excessive deviation will result in rejecting the BBR refinement and accepting the original, affine registration. Workflow Graph .. workflow :: :graph2use: orig :simple_form: yes from fmriprep.workflows.bold.registration import init_fsl_bbr_wf wf = init_fsl_bbr_wf(use_bbr=True, bold2t1w_dof=9, bold2t1w_init='register') Parameters ---------- use_bbr : :obj:`bool` or None Enable/disable boundary-based registration refinement. If ``None``, test BBR result for distortion before accepting. bold2t1w_dof : 6, 9 or 12 Degrees-of-freedom for BOLD-T1w registration bold2t1w_init : str, 'header' or 'register' If ``'header'``, use header information for initialization of BOLD and T1 images. If ``'register'``, align volumes by their centers. name : :obj:`str`, optional Workflow name (default: fsl_bbr_wf) Inputs ------ in_file Reference BOLD image to be registered t1w_brain Skull-stripped T1-weighted structural image t1w_dseg FAST segmentation of ``t1w_brain`` fsnative2t1w_xfm Unused (see :py:func:`~fmriprep.workflows.bold.registration.init_bbreg_wf`) subjects_dir Unused (see :py:func:`~fmriprep.workflows.bold.registration.init_bbreg_wf`) subject_id Unused (see :py:func:`~fmriprep.workflows.bold.registration.init_bbreg_wf`) Outputs ------- itk_bold_to_t1 Affine transform from ``ref_bold_brain`` to T1w space (ITK format) itk_t1_to_bold Affine transform from T1 space to BOLD space (ITK format) out_report Reportlet for assessing registration quality fallback Boolean indicating whether BBR was rejected (rigid FLIRT registration returned) """ from niworkflows.engine.workflows import LiterateWorkflow as Workflow from niworkflows.utils.images import dseg_label as _dseg_label from niworkflows.interfaces.freesurfer import PatchedLTAConvert as LTAConvert from niworkflows.interfaces.registration import FLIRTRPT workflow = Workflow(name=name) workflow.__desc__ = """\ The BOLD reference was then co-registered to the T1w reference using `flirt` [FSL {fsl_ver}, @flirt] with the boundary-based registration [@bbr] cost-function. Co-registration was configured with nine degrees of freedom to account for distortions remaining in the BOLD reference. """.format(fsl_ver=FLIRTRPT().version or '<ver>') inputnode = pe.Node( niu.IdentityInterface([ 'in_file', 'fsnative2t1w_xfm', 'subjects_dir', 'subject_id', # BBRegister 't1w_dseg', 't1w_brain' ]), # FLIRT BBR name='inputnode') outputnode = pe.Node(niu.IdentityInterface( ['itk_bold_to_t1', 'itk_t1_to_bold', 'out_report', 'fallback']), name='outputnode') wm_mask = pe.Node(niu.Function(function=_dseg_label), name='wm_mask') wm_mask.inputs.label = 2 # BIDS default is WM=2 flt_bbr_init = pe.Node(FLIRTRPT(dof=6, generate_report=not use_bbr, uses_qform=True), name='flt_bbr_init') if bold2t1w_init not in ("register", "header"): raise ValueError( f"Unknown BOLD-T1w initialization option: {bold2t1w_init}") if bold2t1w_init == "header": raise NotImplementedError( "Header-based registration initialization not supported for FSL") invt_bbr = pe.Node(fsl.ConvertXFM(invert_xfm=True), name='invt_bbr', mem_gb=DEFAULT_MEMORY_MIN_GB) # BOLD to T1 transform matrix is from fsl, using c3 tools to convert to # something ANTs will like. fsl2itk_fwd = pe.Node(c3.C3dAffineTool(fsl2ras=True, itk_transform=True), name='fsl2itk_fwd', mem_gb=DEFAULT_MEMORY_MIN_GB) fsl2itk_inv = pe.Node(c3.C3dAffineTool(fsl2ras=True, itk_transform=True), name='fsl2itk_inv', mem_gb=DEFAULT_MEMORY_MIN_GB) workflow.connect([ (inputnode, flt_bbr_init, [('in_file', 'in_file'), ('t1w_brain', 'reference')]), (inputnode, fsl2itk_fwd, [('t1w_brain', 'reference_file'), ('in_file', 'source_file')]), (inputnode, fsl2itk_inv, [('in_file', 'reference_file'), ('t1w_brain', 'source_file')]), (invt_bbr, fsl2itk_inv, [('out_file', 'transform_file')]), (fsl2itk_fwd, outputnode, [('itk_transform', 'itk_bold_to_t1')]), (fsl2itk_inv, outputnode, [('itk_transform', 'itk_t1_to_bold')]), ]) # Short-circuit workflow building, use rigid registration if use_bbr is False: workflow.connect([ (flt_bbr_init, invt_bbr, [('out_matrix_file', 'in_file')]), (flt_bbr_init, fsl2itk_fwd, [('out_matrix_file', 'transform_file') ]), (flt_bbr_init, outputnode, [('out_report', 'out_report')]), ]) outputnode.inputs.fallback = True return workflow flt_bbr = pe.Node(FLIRTRPT(cost_func='bbr', dof=bold2t1w_dof, generate_report=True), name='flt_bbr') FSLDIR = os.getenv('FSLDIR') if FSLDIR: flt_bbr.inputs.schedule = op.join(FSLDIR, 'etc/flirtsch/bbr.sch') else: # Should mostly be hit while building docs LOGGER.warning("FSLDIR unset - using packaged BBR schedule") flt_bbr.inputs.schedule = pkgr.resource_filename( 'fmriprep', 'data/flirtsch/bbr.sch') workflow.connect([ (inputnode, wm_mask, [('t1w_dseg', 'in_seg')]), (inputnode, flt_bbr, [('in_file', 'in_file')]), (flt_bbr_init, flt_bbr, [('out_matrix_file', 'in_matrix_file')]), ]) if sloppy is True: downsample = pe.Node(niu.Function( function=_conditional_downsampling, output_names=["out_file", "out_mask"]), name='downsample') workflow.connect([ (inputnode, downsample, [("t1w_brain", "in_file")]), (wm_mask, downsample, [("out", "in_mask")]), (downsample, flt_bbr, [('out_file', 'reference'), ('out_mask', 'wm_seg')]), ]) else: workflow.connect([ (inputnode, flt_bbr, [('t1w_brain', 'reference')]), (wm_mask, flt_bbr, [('out', 'wm_seg')]), ]) # Short-circuit workflow building, use boundary-based registration if use_bbr is True: workflow.connect([ (flt_bbr, invt_bbr, [('out_matrix_file', 'in_file')]), (flt_bbr, fsl2itk_fwd, [('out_matrix_file', 'transform_file')]), (flt_bbr, outputnode, [('out_report', 'out_report')]), ]) outputnode.inputs.fallback = False return workflow transforms = pe.Node(niu.Merge(2), run_without_submitting=True, name='transforms') reports = pe.Node(niu.Merge(2), run_without_submitting=True, name='reports') compare_transforms = pe.Node(niu.Function(function=compare_xforms), name='compare_transforms') select_transform = pe.Node(niu.Select(), run_without_submitting=True, name='select_transform') select_report = pe.Node(niu.Select(), run_without_submitting=True, name='select_report') fsl_to_lta = pe.MapNode(LTAConvert(out_lta=True), iterfield=['in_fsl'], name='fsl_to_lta') workflow.connect([ (flt_bbr, transforms, [('out_matrix_file', 'in1')]), (flt_bbr_init, transforms, [('out_matrix_file', 'in2')]), # Convert FSL transforms to LTA (RAS2RAS) transforms and compare (inputnode, fsl_to_lta, [('in_file', 'source_file'), ('t1w_brain', 'target_file')]), (transforms, fsl_to_lta, [('out', 'in_fsl')]), (fsl_to_lta, compare_transforms, [('out_lta', 'lta_list')]), (compare_transforms, outputnode, [('out', 'fallback')]), # Select output transform (transforms, select_transform, [('out', 'inlist')]), (compare_transforms, select_transform, [('out', 'index')]), (select_transform, invt_bbr, [('out', 'in_file')]), (select_transform, fsl2itk_fwd, [('out', 'transform_file')]), (flt_bbr, reports, [('out_report', 'in1')]), (flt_bbr_init, reports, [('out_report', 'in2')]), (reports, select_report, [('out', 'inlist')]), (compare_transforms, select_report, [('out', 'index')]), (select_report, outputnode, [('out', 'out_report')]), ]) return workflow
def init_single_subject_wf(subject_id, task_id, echo_idx, name, reportlets_dir, output_dir, bids_dir, ignore, debug, low_mem, anat_only, longitudinal, t2s_coreg, omp_nthreads, skull_strip_template, skull_strip_fixed_seed, freesurfer, output_spaces, template, medial_surface_nan, cifti_output, hires, use_bbr, bold2t1w_dof, fmap_bspline, fmap_demean, use_syn, force_syn, template_out_grid, use_aroma, aroma_melodic_dim, ignore_aroma_err): """ This workflow organizes the preprocessing pipeline for a single subject. It collects and reports information about the subject, and prepares sub-workflows to perform anatomical and functional preprocessing. Anatomical preprocessing is performed in a single workflow, regardless of the number of sessions. Functional preprocessing is performed using a separate workflow for each individual BOLD series. .. workflow:: :graph2use: orig :simple_form: yes from fmriprep.workflows.base import init_single_subject_wf wf = init_single_subject_wf(subject_id='test', task_id='', echo_idx=None, name='single_subject_wf', reportlets_dir='.', output_dir='.', bids_dir='.', ignore=[], debug=False, low_mem=False, anat_only=False, longitudinal=False, t2s_coreg=False, omp_nthreads=1, skull_strip_template='OASIS', skull_strip_fixed_seed=False, freesurfer=True, template='MNI152NLin2009cAsym', output_spaces=['T1w', 'fsnative', 'template', 'fsaverage5'], medial_surface_nan=False, cifti_output=False, hires=True, use_bbr=True, bold2t1w_dof=9, fmap_bspline=False, fmap_demean=True, use_syn=True, force_syn=True, template_out_grid='native', use_aroma=False, aroma_melodic_dim=-200, ignore_aroma_err=False) Parameters subject_id : str List of subject labels task_id : str or None Task ID of BOLD series to preprocess, or ``None`` to preprocess all echo_idx : int or None Index of echo to preprocess in multiecho BOLD series, or ``None`` to preprocess all name : str Name of workflow ignore : list Preprocessing steps to skip (may include "slicetiming", "fieldmaps") debug : bool Enable debugging outputs low_mem : bool Write uncompressed .nii files in some cases to reduce memory usage anat_only : bool Disable functional workflows longitudinal : bool Treat multiple sessions as longitudinal (may increase runtime) See sub-workflows for specific differences t2s_coreg : bool For multi-echo EPI, use the calculated T2*-map for T2*-driven coregistration omp_nthreads : int Maximum number of threads an individual process may use skull_strip_template : str Name of ANTs skull-stripping template ('OASIS' or 'NKI') skull_strip_fixed_seed : bool Do not use a random seed for skull-stripping - will ensure run-to-run replicability when used with --omp-nthreads 1 reportlets_dir : str Directory in which to save reportlets output_dir : str Directory in which to save derivatives bids_dir : str Root directory of BIDS dataset freesurfer : bool Enable FreeSurfer surface reconstruction (may increase runtime) output_spaces : list List of output spaces functional images are to be resampled to. Some parts of pipeline will only be instantiated for some output spaces. Valid spaces: - T1w - template - fsnative - fsaverage (or other pre-existing FreeSurfer templates) template : str Name of template targeted by ``template`` output space medial_surface_nan : bool Replace medial wall values with NaNs on functional GIFTI files cifti_output : bool Generate bold CIFTI file in output spaces hires : bool Enable sub-millimeter preprocessing in FreeSurfer use_bbr : bool or None Enable/disable boundary-based registration refinement. If ``None``, test BBR result for distortion before accepting. bold2t1w_dof : 6, 9 or 12 Degrees-of-freedom for BOLD-T1w registration fmap_bspline : bool **Experimental**: Fit B-Spline field using least-squares fmap_demean : bool Demean voxel-shift map during unwarp use_syn : bool **Experimental**: Enable ANTs SyN-based susceptibility distortion correction (SDC). If fieldmaps are present and enabled, this is not run, by default. force_syn : bool **Temporary**: Always run SyN-based SDC template_out_grid : str Keyword ('native', '1mm' or '2mm') or path of custom reference image for normalization use_aroma : bool Perform ICA-AROMA on MNI-resampled functional series ignore_aroma_err : bool Do not fail on ICA-AROMA errors Inputs subjects_dir FreeSurfer SUBJECTS_DIR """ if name in ('single_subject_wf', 'single_subject_fmripreptest_wf'): # for documentation purposes subject_data = { 't1w': ['/completely/made/up/path/sub-01_T1w.nii.gz'], 'bold': ['/completely/made/up/path/sub-01_task-nback_bold.nii.gz'] } layout = None else: subject_data, layout = collect_data(bids_dir, subject_id, task_id, echo_idx) # Make sure we always go through these two checks if not anat_only and subject_data['bold'] == []: raise Exception("No BOLD images found for participant {} and task {}. " "All workflows require BOLD images.".format( subject_id, task_id if task_id else '<all>')) if not subject_data['t1w']: raise Exception("No T1w images found for participant {}. " "All workflows require T1w images.".format(subject_id)) workflow = Workflow(name=name) workflow.__desc__ = """ Results included in this manuscript come from preprocessing performed using *fMRIPprep* {fmriprep_ver} (@fmriprep1; @fmriprep2; RRID:SCR_016216), which is based on *Nipype* {nipype_ver} (@nipype1; @nipype2; RRID:SCR_002502). """.format(fmriprep_ver=__version__, nipype_ver=nipype_ver) workflow.__postdesc__ = """ Many internal operations of *fMRIPrep* use *Nilearn* {nilearn_ver} [@nilearn, RRID:SCR_001362], mostly within the functional processing workflow. For more details of the pipeline, see [the section corresponding to workflows in *fMRIPrep*'s documentation]\ (https://fmriprep.readthedocs.io/en/latest/workflows.html \ "FMRIPrep's documentation"). ### References """.format(nilearn_ver=nilearn_ver) inputnode = pe.Node(niu.IdentityInterface(fields=['subjects_dir']), name='inputnode') bidssrc = pe.Node(BIDSDataGrabber(subject_data=subject_data, anat_only=anat_only), name='bidssrc') bids_info = pe.Node(BIDSInfo(), name='bids_info', run_without_submitting=True) summary = pe.Node(SubjectSummary(output_spaces=output_spaces, template=template), name='summary', run_without_submitting=True) about = pe.Node(AboutSummary(version=__version__, command=' '.join(sys.argv)), name='about', run_without_submitting=True) ds_report_summary = pe.Node( DerivativesDataSink(base_directory=reportlets_dir, suffix='summary'), name='ds_report_summary', run_without_submitting=True) ds_report_about = pe.Node( DerivativesDataSink(base_directory=reportlets_dir, suffix='about'), name='ds_report_about', run_without_submitting=True) # Preprocessing of T1w (includes registration to MNI) anat_preproc_wf = init_anat_preproc_wf(name="anat_preproc_wf", skull_strip_template=skull_strip_template, skull_strip_fixed_seed=skull_strip_fixed_seed, output_spaces=output_spaces, template=template, debug=debug, longitudinal=longitudinal, omp_nthreads=omp_nthreads, freesurfer=freesurfer, hires=hires, reportlets_dir=reportlets_dir, output_dir=output_dir, num_t1w=len(subject_data['t1w'])) workflow.connect([ (inputnode, anat_preproc_wf, [('subjects_dir', 'inputnode.subjects_dir')]), (bidssrc, bids_info, [(('t1w', fix_multi_T1w_source_name), 'in_file')]), (inputnode, summary, [('subjects_dir', 'subjects_dir')]), (bidssrc, summary, [('t1w', 't1w'), ('t2w', 't2w'), ('bold', 'bold')]), (bids_info, summary, [('subject_id', 'subject_id')]), (bidssrc, anat_preproc_wf, [('t1w', 'inputnode.t1w'), ('t2w', 'inputnode.t2w'), ('roi', 'inputnode.roi'), ('flair', 'inputnode.flair')]), (summary, anat_preproc_wf, [('subject_id', 'inputnode.subject_id')]), (bidssrc, ds_report_summary, [(('t1w', fix_multi_T1w_source_name), 'source_file')]), (summary, ds_report_summary, [('out_report', 'in_file')]), (bidssrc, ds_report_about, [(('t1w', fix_multi_T1w_source_name), 'source_file')]), (about, ds_report_about, [('out_report', 'in_file')]), ]) if anat_only: return workflow for bold_file in subject_data['bold']: func_preproc_wf = init_func_preproc_wf(bold_file=bold_file, layout=layout, ignore=ignore, freesurfer=freesurfer, use_bbr=use_bbr, t2s_coreg=t2s_coreg, bold2t1w_dof=bold2t1w_dof, reportlets_dir=reportlets_dir, output_spaces=output_spaces, template=template, medial_surface_nan=medial_surface_nan, cifti_output=cifti_output, output_dir=output_dir, omp_nthreads=omp_nthreads, low_mem=low_mem, fmap_bspline=fmap_bspline, fmap_demean=fmap_demean, use_syn=use_syn, force_syn=force_syn, debug=debug, template_out_grid=template_out_grid, use_aroma=use_aroma, aroma_melodic_dim=aroma_melodic_dim, ignore_aroma_err=ignore_aroma_err, num_bold=len(subject_data['bold'])) workflow.connect([ (anat_preproc_wf, func_preproc_wf, [('outputnode.t1_preproc', 'inputnode.t1_preproc'), ('outputnode.t1_brain', 'inputnode.t1_brain'), ('outputnode.t1_mask', 'inputnode.t1_mask'), ('outputnode.t1_seg', 'inputnode.t1_seg'), ('outputnode.t1_aseg', 'inputnode.t1_aseg'), ('outputnode.t1_aparc', 'inputnode.t1_aparc'), ('outputnode.t1_tpms', 'inputnode.t1_tpms'), ('outputnode.t1_2_mni_forward_transform', 'inputnode.t1_2_mni_forward_transform'), ('outputnode.t1_2_mni_reverse_transform', 'inputnode.t1_2_mni_reverse_transform'), # Undefined if --no-freesurfer, but this is safe ('outputnode.subjects_dir', 'inputnode.subjects_dir'), ('outputnode.subject_id', 'inputnode.subject_id'), ('outputnode.t1_2_fsnative_forward_transform', 'inputnode.t1_2_fsnative_forward_transform'), ('outputnode.t1_2_fsnative_reverse_transform', 'inputnode.t1_2_fsnative_reverse_transform')]), ]) return workflow
def init_single_subject_wf( debug, freesurfer, fast_track, hires, layout, longitudinal, low_mem, name, omp_nthreads, output_dir, skull_strip_fixed_seed, skull_strip_mode, skull_strip_template, spaces, subject_id, bids_filters, ): """ Create a single subject workflow. This workflow organizes the preprocessing pipeline for a single subject. It collects and reports information about the subject, and prepares sub-workflows to perform anatomical and functional preprocessing. Anatomical preprocessing is performed in a single workflow, regardless of the number of sessions. Functional preprocessing is performed using a separate workflow for each individual BOLD series. Workflow Graph .. workflow:: :graph2use: orig :simple_form: yes from collections import namedtuple from niworkflows.utils.spaces import SpatialReferences, Reference from smriprep.workflows.base import init_single_subject_wf BIDSLayout = namedtuple('BIDSLayout', ['root']) wf = init_single_subject_wf( debug=False, freesurfer=True, fast_track=False, hires=True, layout=BIDSLayout('.'), longitudinal=False, low_mem=False, name='single_subject_wf', omp_nthreads=1, output_dir='.', skull_strip_fixed_seed=False, skull_strip_mode='force', skull_strip_template=Reference('OASIS30ANTs'), spaces=SpatialReferences(spaces=['MNI152NLin2009cAsym', 'fsaverage5']), subject_id='test', bids_filters=None, ) Parameters ---------- debug : :obj:`bool` Enable debugging outputs freesurfer : :obj:`bool` Enable FreeSurfer surface reconstruction (may increase runtime) fast_track : :obj:`bool` If ``True``, attempt to collect previously run derivatives. hires : :obj:`bool` Enable sub-millimeter preprocessing in FreeSurfer layout : BIDSLayout object BIDS dataset layout longitudinal : :obj:`bool` Treat multiple sessions as longitudinal (may increase runtime) See sub-workflows for specific differences low_mem : :obj:`bool` Write uncompressed .nii files in some cases to reduce memory usage name : :obj:`str` Name of workflow omp_nthreads : :obj:`int` Maximum number of threads an individual process may use output_dir : :obj:`str` Directory in which to save derivatives skull_strip_fixed_seed : :obj:`bool` Do not use a random seed for skull-stripping - will ensure run-to-run replicability when used with --omp-nthreads 1 skull_strip_mode : :obj:`str` Determiner for T1-weighted skull stripping (`force` ensures skull stripping, `skip` ignores skull stripping, and `auto` automatically ignores skull stripping if pre-stripped brains are detected). skull_strip_template : :py:class:`~niworkflows.utils.spaces.Reference` Spatial reference to use in atlas-based brain extraction. spaces : :py:class:`~niworkflows.utils.spaces.SpatialReferences` Object containing standard and nonstandard space specifications. subject_id : :obj:`str` List of subject labels bids_filters : dict Provides finer specification of the pipeline input files through pybids entities filters. A dict with the following structure {<suffix>:{<entity>:<filter>,...},...} Inputs ------ subjects_dir FreeSurfer SUBJECTS_DIR """ from ..interfaces.reports import AboutSummary, SubjectSummary if name in ('single_subject_wf', 'single_subject_smripreptest_wf'): # for documentation purposes subject_data = { 't1w': ['/completely/made/up/path/sub-01_T1w.nii.gz'], } else: subject_data = collect_data(layout, subject_id, bids_filters=bids_filters)[0] if not subject_data['t1w']: raise Exception("No T1w images found for participant {}. " "All workflows require T1w images.".format(subject_id)) workflow = Workflow(name=name) workflow.__desc__ = """ Results included in this manuscript come from preprocessing performed using *sMRIPprep* {smriprep_ver} (@fmriprep1; @fmriprep2; RRID:SCR_016216), which is based on *Nipype* {nipype_ver} (@nipype1; @nipype2; RRID:SCR_002502). """.format(smriprep_ver=__version__, nipype_ver=nipype_ver) workflow.__postdesc__ = """ For more details of the pipeline, see [the section corresponding to workflows in *sMRIPrep*'s documentation]\ (https://smriprep.readthedocs.io/en/latest/workflows.html \ "sMRIPrep's documentation"). ### References """ deriv_cache = None if fast_track: from ..utils.bids import collect_derivatives std_spaces = spaces.get_spaces(nonstandard=False, dim=(3, )) deriv_cache = collect_derivatives( Path(output_dir) / 'smriprep', subject_id, std_spaces, freesurfer) inputnode = pe.Node(niu.IdentityInterface(fields=['subjects_dir']), name='inputnode') bidssrc = pe.Node(BIDSDataGrabber(subject_data=subject_data, anat_only=True), name='bidssrc') bids_info = pe.Node(BIDSInfo(bids_dir=layout.root), name='bids_info', run_without_submitting=True) summary = pe.Node( SubjectSummary(output_spaces=spaces.get_spaces(nonstandard=False)), name='summary', run_without_submitting=True) about = pe.Node(AboutSummary(version=__version__, command=' '.join(sys.argv)), name='about', run_without_submitting=True) ds_report_summary = pe.Node(DerivativesDataSink( base_directory=output_dir, dismiss_entities=("session", ), desc='summary', datatype="figures"), name='ds_report_summary', run_without_submitting=True) ds_report_about = pe.Node(DerivativesDataSink( base_directory=output_dir, dismiss_entities=("session", ), desc='about', datatype="figures"), name='ds_report_about', run_without_submitting=True) # Preprocessing of T1w (includes registration to MNI) anat_preproc_wf = init_anat_preproc_wf( bids_root=layout.root, debug=debug, existing_derivatives=deriv_cache, freesurfer=freesurfer, hires=hires, longitudinal=longitudinal, name="anat_preproc_wf", t1w=subject_data['t1w'], omp_nthreads=omp_nthreads, output_dir=output_dir, skull_strip_fixed_seed=skull_strip_fixed_seed, skull_strip_mode=skull_strip_mode, skull_strip_template=skull_strip_template, spaces=spaces, ) workflow.connect([ (inputnode, anat_preproc_wf, [('subjects_dir', 'inputnode.subjects_dir')]), (bidssrc, bids_info, [(('t1w', fix_multi_T1w_source_name), 'in_file') ]), (inputnode, summary, [('subjects_dir', 'subjects_dir')]), (bidssrc, summary, [('t1w', 't1w'), ('t2w', 't2w')]), (bids_info, summary, [('subject', 'subject_id')]), (bids_info, anat_preproc_wf, [(('subject', _prefix), 'inputnode.subject_id')]), (bidssrc, anat_preproc_wf, [('t1w', 'inputnode.t1w'), ('t2w', 'inputnode.t2w'), ('roi', 'inputnode.roi'), ('flair', 'inputnode.flair')]), (bidssrc, ds_report_summary, [(('t1w', fix_multi_T1w_source_name), 'source_file')]), (summary, ds_report_summary, [('out_report', 'in_file')]), (bidssrc, ds_report_about, [(('t1w', fix_multi_T1w_source_name), 'source_file')]), (about, ds_report_about, [('out_report', 'in_file')]), ]) return workflow
def init_fmriprep_wf(subject_list, task_id, echo_idx, run_uuid, work_dir, output_dir, bids_dir, ignore, debug, low_mem, anat_only, longitudinal, t2s_coreg, omp_nthreads, skull_strip_template, skull_strip_fixed_seed, freesurfer, output_spaces, template, medial_surface_nan, cifti_output, hires, use_bbr, bold2t1w_dof, fmap_bspline, fmap_demean, use_syn, force_syn, use_aroma, ignore_aroma_err, aroma_melodic_dim, template_out_grid): """ This workflow organizes the execution of FMRIPREP, with a sub-workflow for each subject. If FreeSurfer's recon-all is to be run, a FreeSurfer derivatives folder is created and populated with any needed template subjects. .. workflow:: :graph2use: orig :simple_form: yes import os os.environ['FREESURFER_HOME'] = os.getcwd() from fmriprep.workflows.base import init_fmriprep_wf wf = init_fmriprep_wf(subject_list=['fmripreptest'], task_id='', echo_idx=None, run_uuid='X', work_dir='.', output_dir='.', bids_dir='.', ignore=[], debug=False, low_mem=False, anat_only=False, longitudinal=False, t2s_coreg=False, omp_nthreads=1, skull_strip_template='OASIS', skull_strip_fixed_seed=False, freesurfer=True, output_spaces=['T1w', 'fsnative', 'template', 'fsaverage5'], template='MNI152NLin2009cAsym', medial_surface_nan=False, cifti_output=False, hires=True, use_bbr=True, bold2t1w_dof=9, fmap_bspline=False, fmap_demean=True, use_syn=True, force_syn=True, use_aroma=False, ignore_aroma_err=False, aroma_melodic_dim=-200, template_out_grid='native') Parameters subject_list : list List of subject labels task_id : str or None Task ID of BOLD series to preprocess, or ``None`` to preprocess all echo_idx : int or None Index of echo to preprocess in multiecho BOLD series, or ``None`` to preprocess all run_uuid : str Unique identifier for execution instance work_dir : str Directory in which to store workflow execution state and temporary files output_dir : str Directory in which to save derivatives bids_dir : str Root directory of BIDS dataset ignore : list Preprocessing steps to skip (may include "slicetiming", "fieldmaps") debug : bool Enable debugging outputs low_mem : bool Write uncompressed .nii files in some cases to reduce memory usage anat_only : bool Disable functional workflows longitudinal : bool Treat multiple sessions as longitudinal (may increase runtime) See sub-workflows for specific differences t2s_coreg : bool For multi-echo EPI, use the calculated T2*-map for T2*-driven coregistration omp_nthreads : int Maximum number of threads an individual process may use skull_strip_template : str Name of ANTs skull-stripping template ('OASIS' or 'NKI') skull_strip_fixed_seed : bool Do not use a random seed for skull-stripping - will ensure run-to-run replicability when used with --omp-nthreads 1 freesurfer : bool Enable FreeSurfer surface reconstruction (may increase runtime) output_spaces : list List of output spaces functional images are to be resampled to. Some parts of pipeline will only be instantiated for some output spaces. Valid spaces: - T1w - template - fsnative - fsaverage (or other pre-existing FreeSurfer templates) template : str Name of template targeted by ``template`` output space medial_surface_nan : bool Replace medial wall values with NaNs on functional GIFTI files cifti_output : bool Generate bold CIFTI file in output spaces hires : bool Enable sub-millimeter preprocessing in FreeSurfer use_bbr : bool or None Enable/disable boundary-based registration refinement. If ``None``, test BBR result for distortion before accepting. bold2t1w_dof : 6, 9 or 12 Degrees-of-freedom for BOLD-T1w registration fmap_bspline : bool **Experimental**: Fit B-Spline field using least-squares fmap_demean : bool Demean voxel-shift map during unwarp use_syn : bool **Experimental**: Enable ANTs SyN-based susceptibility distortion correction (SDC). If fieldmaps are present and enabled, this is not run, by default. force_syn : bool **Temporary**: Always run SyN-based SDC use_aroma : bool Perform ICA-AROMA on MNI-resampled functional series ignore_aroma_err : bool Do not fail on ICA-AROMA errors template_out_grid : str Keyword ('native', '1mm' or '2mm') or path of custom reference image for normalization """ fmriprep_wf = Workflow(name='fmriprep_wf') fmriprep_wf.base_dir = work_dir if freesurfer: fsdir = pe.Node( BIDSFreeSurferDir( derivatives=output_dir, freesurfer_home=os.getenv('FREESURFER_HOME'), spaces=output_spaces), name='fsdir_run_' + run_uuid.replace('-', '_'), run_without_submitting=True) reportlets_dir = os.path.join(work_dir, 'reportlets') for subject_id in subject_list: single_subject_wf = init_single_subject_wf( subject_id=subject_id, task_id=task_id, echo_idx=echo_idx, name="single_subject_" + subject_id + "_wf", reportlets_dir=reportlets_dir, output_dir=output_dir, bids_dir=bids_dir, ignore=ignore, debug=debug, low_mem=low_mem, anat_only=anat_only, longitudinal=longitudinal, t2s_coreg=t2s_coreg, omp_nthreads=omp_nthreads, skull_strip_template=skull_strip_template, skull_strip_fixed_seed=skull_strip_fixed_seed, freesurfer=freesurfer, output_spaces=output_spaces, template=template, medial_surface_nan=medial_surface_nan, cifti_output=cifti_output, hires=hires, use_bbr=use_bbr, bold2t1w_dof=bold2t1w_dof, fmap_bspline=fmap_bspline, fmap_demean=fmap_demean, use_syn=use_syn, force_syn=force_syn, template_out_grid=template_out_grid, use_aroma=use_aroma, aroma_melodic_dim=aroma_melodic_dim, ignore_aroma_err=ignore_aroma_err, ) single_subject_wf.config['execution']['crashdump_dir'] = ( os.path.join(output_dir, "fmriprep", "sub-" + subject_id, 'log', run_uuid) ) for node in single_subject_wf._get_all_nodes(): node.config = deepcopy(single_subject_wf.config) if freesurfer: fmriprep_wf.connect(fsdir, 'subjects_dir', single_subject_wf, 'inputnode.subjects_dir') else: fmriprep_wf.add_nodes([single_subject_wf]) return fmriprep_wf
def init_topup_wf(omp_nthreads=1, debug=False, name="pepolar_estimate_wf"): """ Create the PEPOLAR field estimation workflow based on FSL's ``topup``. Workflow Graph .. workflow :: :graph2use: orig :simple_form: yes from sdcflows.workflows.fit.pepolar import init_topup_wf wf = init_topup_wf() Parameters ---------- debug : :obj:`bool` Whether a fast configuration of topup (less accurate) should be applied. name : :obj:`str` Name for this workflow omp_nthreads : :obj:`int` Parallelize internal tasks across the number of CPUs given by this option. Inputs ------ in_data : :obj:`list` of :obj:`str` A list of EPI files that will be fed into TOPUP. metadata : :obj:`list` of :obj:`dict` A list of dictionaries containing the metadata corresponding to each file in ``in_data``. Outputs ------- fmap : :obj:`str` The path of the estimated fieldmap. fmap_ref : :obj:`str` The path of an unwarped conversion of files in ``in_data``. fmap_mask : :obj:`str` The path of mask corresponding to the ``fmap_ref`` output. fmap_coeff : :obj:`str` or :obj:`list` of :obj:`str` The path(s) of the B-Spline coefficients supporting the fieldmap. """ from nipype.interfaces.fsl.epi import TOPUP from niworkflows.interfaces.nibabel import MergeSeries from niworkflows.interfaces.images import IntraModalMerge from ...interfaces.epi import GetReadoutTime from ...interfaces.utils import Flatten from ...interfaces.bspline import TOPUPCoeffReorient from ..ancillary import init_brainextraction_wf workflow = Workflow(name=name) workflow.__postdesc__ = f"""\ {_PEPOLAR_DESC} with `topup` (@topup; FSL {TOPUP().version}). """ inputnode = pe.Node(niu.IdentityInterface(fields=INPUT_FIELDS), name="inputnode") outputnode = pe.Node( niu.IdentityInterface(fields=[ "fmap", "fmap_ref", "fmap_coeff", "fmap_mask", "jacobians", "xfms", "out_warps", ]), name="outputnode", ) flatten = pe.Node(Flatten(), name="flatten") concat_blips = pe.Node(MergeSeries(), name="concat_blips") readout_time = pe.MapNode( GetReadoutTime(), name="readout_time", iterfield=["metadata", "in_file"], run_without_submitting=True, ) topup = pe.Node( TOPUP(config=_pkg_fname("sdcflows", f"data/flirtsch/b02b0{'_quick' * debug}.cnf")), name="topup", ) merge_corrected = pe.Node(IntraModalMerge(hmc=False, to_ras=False), name="merge_corrected") fix_coeff = pe.Node(TOPUPCoeffReorient(), name="fix_coeff", run_without_submitting=True) brainextraction_wf = init_brainextraction_wf() # fmt: off workflow.connect([ (inputnode, flatten, [("in_data", "in_data"), ("metadata", "in_meta")]), (flatten, readout_time, [("out_data", "in_file"), ("out_meta", "metadata")]), (flatten, concat_blips, [("out_data", "in_files")]), (flatten, topup, [(("out_meta", _pe2fsl), "encoding_direction")]), (readout_time, topup, [("readout_time", "readout_times")]), (concat_blips, topup, [("out_file", "in_file")]), (topup, merge_corrected, [("out_corrected", "in_files")]), (topup, fix_coeff, [("out_fieldcoef", "in_coeff"), ("out_corrected", "fmap_ref")]), (topup, outputnode, [("out_field", "fmap"), ("out_jacs", "jacobians"), ("out_mats", "xfms"), ("out_warps", "out_warps")]), (merge_corrected, brainextraction_wf, [("out_avg", "inputnode.in_file") ]), (merge_corrected, outputnode, [("out_avg", "fmap_ref")]), (brainextraction_wf, outputnode, [("outputnode.out_mask", "fmap_mask") ]), (fix_coeff, outputnode, [("out_coeff", "fmap_coeff")]), ]) # fmt: on return workflow
def init_func_derivatives_wf(output_dir, output_spaces, template, freesurfer, use_aroma, cifti_output, name='func_derivatives_wf'): """ Set up a battery of datasinks to store derivatives in the right location """ workflow = Workflow(name=name) inputnode = pe.Node( niu.IdentityInterface( fields=['source_file', 'bold_t1', 'bold_t1_ref', 'bold_mask_t1', 'bold_mni', 'bold_mni_ref', 'bold_mask_mni', 'bold_aseg_t1', 'bold_aparc_t1', 'bold_aseg_mni', 'bold_aparc_mni', 'cifti_variant_key', 'confounds', 'surfaces', 'aroma_noise_ics', 'melodic_mix', 'nonaggr_denoised_file', 'bold_cifti', 'cifti_variant']), name='inputnode') ds_confounds = pe.Node(DerivativesDataSink( base_directory=output_dir, desc='confounds', suffix='regressors'), name="ds_confounds", run_without_submitting=True, mem_gb=DEFAULT_MEMORY_MIN_GB) workflow.connect([ (inputnode, ds_confounds, [('source_file', 'source_file'), ('confounds', 'in_file')]), ]) # Resample to T1w space if 'T1w' in output_spaces: ds_bold_t1 = pe.Node( DerivativesDataSink(base_directory=output_dir, space='T1w', desc='preproc', keep_dtype=True, compress=True), name='ds_bold_t1', run_without_submitting=True, mem_gb=DEFAULT_MEMORY_MIN_GB) ds_bold_t1_ref = pe.Node( DerivativesDataSink(base_directory=output_dir, space='T1w', suffix='boldref'), name='ds_bold_t1_ref', run_without_submitting=True, mem_gb=DEFAULT_MEMORY_MIN_GB) ds_bold_mask_t1 = pe.Node( DerivativesDataSink(base_directory=output_dir, space='T1w', desc='brain', suffix='mask'), name='ds_bold_mask_t1', run_without_submitting=True, mem_gb=DEFAULT_MEMORY_MIN_GB) workflow.connect([ (inputnode, ds_bold_t1, [('source_file', 'source_file'), ('bold_t1', 'in_file')]), (inputnode, ds_bold_t1_ref, [('source_file', 'source_file'), ('bold_t1_ref', 'in_file')]), (inputnode, ds_bold_mask_t1, [('source_file', 'source_file'), ('bold_mask_t1', 'in_file')]), ]) if freesurfer: ds_bold_aseg_t1 = pe.Node(DerivativesDataSink( base_directory=output_dir, space='T1w', desc='aseg', suffix='dseg'), name='ds_bold_aseg_t1', run_without_submitting=True, mem_gb=DEFAULT_MEMORY_MIN_GB) ds_bold_aparc_t1 = pe.Node(DerivativesDataSink( base_directory=output_dir, space='T1w', desc='aparcaseg', suffix='dseg'), name='ds_bold_aparc_t1', run_without_submitting=True, mem_gb=DEFAULT_MEMORY_MIN_GB) workflow.connect([ (inputnode, ds_bold_aseg_t1, [('source_file', 'source_file'), ('bold_aseg_t1', 'in_file')]), (inputnode, ds_bold_aparc_t1, [('source_file', 'source_file'), ('bold_aparc_t1', 'in_file')]), ]) # Resample to template (default: MNI) if 'template' in output_spaces: ds_bold_mni = pe.Node( DerivativesDataSink(base_directory=output_dir, space=template, desc='preproc', keep_dtype=True, compress=True), name='ds_bold_mni', run_without_submitting=True, mem_gb=DEFAULT_MEMORY_MIN_GB) ds_bold_mni_ref = pe.Node( DerivativesDataSink(base_directory=output_dir, space=template, suffix='boldref'), name='ds_bold_mni_ref', run_without_submitting=True, mem_gb=DEFAULT_MEMORY_MIN_GB) ds_bold_mask_mni = pe.Node( DerivativesDataSink(base_directory=output_dir, space=template, desc='brain', suffix='mask'), name='ds_bold_mask_mni', run_without_submitting=True, mem_gb=DEFAULT_MEMORY_MIN_GB) workflow.connect([ (inputnode, ds_bold_mni, [('source_file', 'source_file'), ('bold_mni', 'in_file')]), (inputnode, ds_bold_mni_ref, [('source_file', 'source_file'), ('bold_mni_ref', 'in_file')]), (inputnode, ds_bold_mask_mni, [('source_file', 'source_file'), ('bold_mask_mni', 'in_file')]), ]) if freesurfer: ds_bold_aseg_mni = pe.Node(DerivativesDataSink( base_directory=output_dir, space=template, desc='aseg', suffix='dseg'), name='ds_bold_aseg_mni', run_without_submitting=True, mem_gb=DEFAULT_MEMORY_MIN_GB) ds_bold_aparc_mni = pe.Node(DerivativesDataSink( base_directory=output_dir, space=template, desc='aparcaseg', suffix='dseg'), name='ds_bold_aparc_mni', run_without_submitting=True, mem_gb=DEFAULT_MEMORY_MIN_GB) workflow.connect([ (inputnode, ds_bold_aseg_mni, [('source_file', 'source_file'), ('bold_aseg_mni', 'in_file')]), (inputnode, ds_bold_aparc_mni, [('source_file', 'source_file'), ('bold_aparc_mni', 'in_file')]), ]) # fsaverage space if freesurfer and any(space.startswith('fs') for space in output_spaces): name_surfs = pe.MapNode(GiftiNameSource( pattern=r'(?P<LR>[lr])h.(?P<space>\w+).gii', template='space-{space}_hemi-{LR}.func'), iterfield='in_file', name='name_surfs', mem_gb=DEFAULT_MEMORY_MIN_GB, run_without_submitting=True) ds_bold_surfs = pe.MapNode(DerivativesDataSink(base_directory=output_dir), iterfield=['in_file', 'suffix'], name='ds_bold_surfs', run_without_submitting=True, mem_gb=DEFAULT_MEMORY_MIN_GB) workflow.connect([ (inputnode, name_surfs, [('surfaces', 'in_file')]), (inputnode, ds_bold_surfs, [('source_file', 'source_file'), ('surfaces', 'in_file')]), (name_surfs, ds_bold_surfs, [('out_name', 'suffix')]), ]) # CIFTI output if cifti_output and 'template' in output_spaces: name_cifti = pe.MapNode( CiftiNameSource(), iterfield=['variant'], name='name_cifti', mem_gb=DEFAULT_MEMORY_MIN_GB, run_without_submitting=True) cifti_bolds = pe.MapNode( DerivativesDataSink(base_directory=output_dir, compress=False), iterfield=['in_file', 'suffix'], name='cifti_bolds', run_without_submitting=True, mem_gb=DEFAULT_MEMORY_MIN_GB) cifti_key = pe.MapNode(DerivativesDataSink( base_directory=output_dir), iterfield=['in_file', 'suffix'], name='cifti_key', run_without_submitting=True, mem_gb=DEFAULT_MEMORY_MIN_GB) workflow.connect([ (inputnode, name_cifti, [('cifti_variant', 'variant')]), (inputnode, cifti_bolds, [('bold_cifti', 'in_file'), ('source_file', 'source_file')]), (name_cifti, cifti_bolds, [('out_name', 'suffix')]), (name_cifti, cifti_key, [('out_name', 'suffix')]), (inputnode, cifti_key, [('source_file', 'source_file'), ('cifti_variant_key', 'in_file')]), ]) if use_aroma: ds_aroma_noise_ics = pe.Node(DerivativesDataSink( base_directory=output_dir, suffix='AROMAnoiseICs'), name="ds_aroma_noise_ics", run_without_submitting=True, mem_gb=DEFAULT_MEMORY_MIN_GB) ds_melodic_mix = pe.Node(DerivativesDataSink( base_directory=output_dir, desc='MELODIC', suffix='mixing'), name="ds_melodic_mix", run_without_submitting=True, mem_gb=DEFAULT_MEMORY_MIN_GB) ds_aroma_mni = pe.Node( DerivativesDataSink(base_directory=output_dir, space=template, desc='smoothAROMAnonaggr', keep_dtype=True), name='ds_aroma_mni', run_without_submitting=True, mem_gb=DEFAULT_MEMORY_MIN_GB) workflow.connect([ (inputnode, ds_aroma_noise_ics, [('source_file', 'source_file'), ('aroma_noise_ics', 'in_file')]), (inputnode, ds_melodic_mix, [('source_file', 'source_file'), ('melodic_mix', 'in_file')]), (inputnode, ds_aroma_mni, [('source_file', 'source_file'), ('nonaggr_denoised_file', 'in_file')]), ]) return workflow
def init_pepolar_unwarp_wf(omp_nthreads=1, matched_pe=False, name="pepolar_unwarp_wf"): """ Create the PE-Polar field estimation workflow. This workflow takes in a set of EPI files with opposite phase encoding direction than the target file and calculates a displacements field (in other words, an ANTs-compatible warp file). This procedure works if there is only one '_epi' file is present (as long as it has the opposite phase encoding direction to the target file). The target file will be used to estimate the field distortion. However, if there is another '_epi' file present with a matching phase encoding direction to the target it will be used instead. Currently, different phase encoding dimension in the target file and the '_epi' file(s) (for example 'i' and 'j') is not supported. The warp field correcting for the distortions is estimated using AFNI's 3dQwarp, with displacement estimation limited to the target file phase encoding direction. It also calculates a new mask for the input dataset that takes into account the distortions. .. workflow :: :graph2use: orig :simple_form: yes from sdcflows.workflows.pepolar import init_pepolar_unwarp_wf wf = init_pepolar_unwarp_wf() **Parameters**: matched_pe : bool Whether the input ``fmaps_epi`` will contain images with matched PE blips or not. Please use :func:`sdcflows.workflows.pepolar.check_pes` to determine whether they exist or not. name : str Name for this workflow omp_nthreads : int Parallelize internal tasks across the number of CPUs given by this option. **Inputs**: fmaps_epi : list of tuple(pathlike, str) The list of EPI images that will be used in PE-Polar correction, and their corresponding ``PhaseEncodingDirection`` metadata. The workflow will use the ``bold_pe_dir`` input to separate out those EPI acquisitions with opposed PE blips and those with matched PE blips (the latter could be none, and ``in_reference_brain`` would then be used). The workflow raises a ``ValueError`` when no images with opposed PE blips are found. bold_pe_dir : str The baseline PE direction. in_reference : pathlike The baseline reference image (must correspond to ``bold_pe_dir``). in_reference_brain : pathlike The reference image above, but skullstripped. in_mask : pathlike Not used, present only for consistency across fieldmap estimation workflows. **Outputs**: out_reference : pathlike The ``in_reference`` after unwarping out_reference_brain : pathlike The ``in_reference`` after unwarping and skullstripping out_warp : pathlike The corresponding :abbr:`DFM (displacements field map)` compatible with ANTs. out_mask : pathlike Mask of the unwarped input file """ workflow = Workflow(name=name) workflow.__desc__ = """\ A deformation field to correct for susceptibility distortions was estimated based on two echo-planar imaging (EPI) references with opposing phase-encoding directions, using `3dQwarp` @afni (AFNI {afni_ver}). """.format(afni_ver=''.join(['%02d' % v for v in afni.Info().version() or []])) inputnode = pe.Node(niu.IdentityInterface(fields=[ 'fmaps_epi', 'in_reference', 'in_reference_brain', 'in_mask', 'bold_pe_dir' ]), name='inputnode') outputnode = pe.Node(niu.IdentityInterface(fields=[ 'out_reference', 'out_reference_brain', 'out_warp', 'out_mask' ]), name='outputnode') prepare_epi_wf = init_prepare_epi_wf(omp_nthreads=omp_nthreads, matched_pe=matched_pe, name="prepare_epi_wf") qwarp = pe.Node(afni.QwarpPlusMinus( pblur=[0.05, 0.05], blur=[-1, -1], noweight=True, minpatch=9, nopadWARP=True, environ={'OMP_NUM_THREADS': '%d' % omp_nthreads}), name='qwarp', n_procs=omp_nthreads) to_ants = pe.Node(niu.Function(function=_fix_hdr), name='to_ants', mem_gb=0.01) cphdr_warp = pe.Node(CopyHeader(), name='cphdr_warp', mem_gb=0.01) unwarp_reference = pe.Node(ANTSApplyTransformsRPT( dimension=3, generate_report=False, float=True, interpolation='LanczosWindowedSinc'), name='unwarp_reference') enhance_and_skullstrip_bold_wf = init_enhance_and_skullstrip_bold_wf( omp_nthreads=omp_nthreads) workflow.connect([ (inputnode, qwarp, [(('bold_pe_dir', _qwarp_args), 'args')]), (inputnode, cphdr_warp, [('in_reference', 'hdr_file')]), (inputnode, prepare_epi_wf, [('fmaps_epi', 'inputnode.maps_pe'), ('bold_pe_dir', 'inputnode.epi_pe'), ('in_reference_brain', 'inputnode.ref_brain')]), (prepare_epi_wf, qwarp, [('outputnode.opposed_pe', 'base_file'), ('outputnode.matched_pe', 'in_file')]), (qwarp, cphdr_warp, [('source_warp', 'in_file')]), (cphdr_warp, to_ants, [('out_file', 'in_file')]), (to_ants, unwarp_reference, [('out', 'transforms')]), (inputnode, unwarp_reference, [('in_reference', 'reference_image'), ('in_reference', 'input_image')]), (unwarp_reference, enhance_and_skullstrip_bold_wf, [('output_image', 'inputnode.in_file')]), (unwarp_reference, outputnode, [('output_image', 'out_reference')]), (enhance_and_skullstrip_bold_wf, outputnode, [('outputnode.mask_file', 'out_mask'), ('outputnode.skull_stripped_file', 'out_reference_brain')]), (to_ants, outputnode, [('out', 'out_warp')]), ]) return workflow
def init_carpetplot_wf(mem_gb, metadata, name="bold_carpet_wf"): """ Resamples the MNI parcellation (ad-hoc parcellation derived from the Harvard-Oxford template and others). **Parameters** mem_gb : float Size of BOLD file in GB - please note that this size should be calculated after resamplings that may extend the FoV metadata : dict BIDS metadata for BOLD file name : str Name of workflow (default: ``bold_carpet_wf``) **Inputs** bold BOLD image, after the prescribed corrections (STC, HMC and SDC) when available. bold_mask BOLD series mask confounds_file TSV of all aggregated confounds t1_bold_xform Affine matrix that maps the T1w space into alignment with the native BOLD space t1_2_mni_reverse_transform ANTs-compatible affine-and-warp transform file **Outputs** out_carpetplot Path of the generated SVG file """ inputnode = pe.Node(niu.IdentityInterface( fields=['bold', 'bold_mask', 'confounds_file', 't1_bold_xform', 't1_2_mni_reverse_transform']), name='inputnode') outputnode = pe.Node(niu.IdentityInterface( fields=['out_carpetplot']), name='outputnode') # List transforms mrg_xfms = pe.Node(niu.Merge(2), name='mrg_xfms') # Warp segmentation into EPI space resample_parc = pe.Node(ApplyTransforms( float=True, input_image=str( get_template('MNI152NLin2009cAsym') / 'tpl-MNI152NLin2009cAsym_space-MNI_res-01_label-carpet_atlas.nii.gz'), dimension=3, default_value=0, interpolation='MultiLabel'), name='resample_parc') # Carpetplot and confounds plot conf_plot = pe.Node(FMRISummary( tr=metadata['RepetitionTime'], confounds_list=[ ('global_signal', None, 'GS'), ('csf', None, 'GSCSF'), ('white_matter', None, 'GSWM'), ('std_dvars', None, 'DVARS'), ('framewise_displacement', 'mm', 'FD')]), name='conf_plot', mem_gb=mem_gb) ds_report_bold_conf = pe.Node( DerivativesDataSink(suffix='carpetplot'), name='ds_report_bold_conf', run_without_submitting=True, mem_gb=DEFAULT_MEMORY_MIN_GB) workflow = Workflow(name=name) workflow.connect([ (inputnode, mrg_xfms, [('t1_bold_xform', 'in1'), ('t1_2_mni_reverse_transform', 'in2')]), (inputnode, resample_parc, [('bold_mask', 'reference_image')]), (mrg_xfms, resample_parc, [('out', 'transforms')]), # Carpetplot (inputnode, conf_plot, [ ('bold', 'in_func'), ('bold_mask', 'in_mask'), ('confounds_file', 'confounds_file')]), (resample_parc, conf_plot, [('output_image', 'in_segm')]), (conf_plot, ds_report_bold_conf, [('out_file', 'in_file')]), (conf_plot, outputnode, [('out_file', 'out_carpetplot')]), ]) return workflow
def init_topup_wf( grid_reference=0, omp_nthreads=1, sloppy=False, debug=False, name="pepolar_estimate_wf", ): """ Create the PEPOLAR field estimation workflow based on FSL's ``topup``. Workflow Graph .. workflow :: :graph2use: orig :simple_form: yes from sdcflows.workflows.fit.pepolar import init_topup_wf wf = init_topup_wf() Parameters ---------- grid_reference : :obj:`int` Index of the volume (after flattening) that will be taken for gridding reference. sloppy : :obj:`bool` Whether a fast configuration of topup (less accurate) should be applied. debug : :obj:`bool` Run in debug mode name : :obj:`str` Name for this workflow omp_nthreads : :obj:`int` Parallelize internal tasks across the number of CPUs given by this option. Inputs ------ in_data : :obj:`list` of :obj:`str` A list of EPI files that will be fed into TOPUP. metadata : :obj:`list` of :obj:`dict` A list of dictionaries containing the metadata corresponding to each file in ``in_data``. Outputs ------- fmap : :obj:`str` The path of the estimated fieldmap. fmap_ref : :obj:`str` The path of an unwarped conversion of files in ``in_data``. fmap_mask : :obj:`str` The path of mask corresponding to the ``fmap_ref`` output. fmap_coeff : :obj:`str` or :obj:`list` of :obj:`str` The path(s) of the B-Spline coefficients supporting the fieldmap. method: :obj:`str` Short description of the estimation method that was run. """ from nipype.interfaces.fsl.epi import TOPUP from niworkflows.interfaces.nibabel import MergeSeries from niworkflows.interfaces.images import RobustAverage from ...utils.misc import front as _front from ...interfaces.epi import GetReadoutTime from ...interfaces.utils import Flatten, UniformGrid, PadSlices from ...interfaces.bspline import TOPUPCoeffReorient from ..ancillary import init_brainextraction_wf workflow = Workflow(name=name) workflow.__desc__ = f"""\ {_PEPOLAR_DESC} with `topup` (@topup; FSL {TOPUP().version}). """ inputnode = pe.Node(niu.IdentityInterface(fields=INPUT_FIELDS), name="inputnode") outputnode = pe.Node( niu.IdentityInterface(fields=[ "fmap", "fmap_ref", "fmap_coeff", "fmap_mask", "jacobians", "xfms", "out_warps", "method", ]), name="outputnode", ) outputnode.inputs.method = "PEB/PEPOLAR (phase-encoding based / PE-POLARity)" flatten = pe.Node(Flatten(), name="flatten") regrid = pe.Node(UniformGrid(reference=grid_reference), name="regrid") concat_blips = pe.Node(MergeSeries(), name="concat_blips") readout_time = pe.MapNode( GetReadoutTime(), name="readout_time", iterfield=["metadata", "in_file"], run_without_submitting=True, ) pad_blip_slices = pe.Node(PadSlices(), name="pad_blip_slices") pad_ref_slices = pe.Node(PadSlices(), name="pad_ref_slices") topup = pe.Node( TOPUP(config=_pkg_fname( "sdcflows", f"data/flirtsch/b02b0{'_quick' * sloppy}.cnf")), name="topup", ) ref_average = pe.Node(RobustAverage(), name="ref_average") fix_coeff = pe.Node(TOPUPCoeffReorient(), name="fix_coeff", run_without_submitting=True) brainextraction_wf = init_brainextraction_wf() # fmt: off workflow.connect([ (inputnode, flatten, [("in_data", "in_data"), ("metadata", "in_meta")]), (flatten, readout_time, [("out_data", "in_file"), ("out_meta", "metadata")]), (flatten, regrid, [("out_data", "in_data")]), (regrid, concat_blips, [("out_data", "in_files")]), (readout_time, topup, [("readout_time", "readout_times"), ("pe_dir_fsl", "encoding_direction")]), (regrid, pad_ref_slices, [("reference", "in_file")]), (pad_ref_slices, fix_coeff, [("out_file", "fmap_ref")]), (readout_time, fix_coeff, [(("pe_direction", _front), "pe_dir")]), (topup, fix_coeff, [("out_fieldcoef", "in_coeff")]), (topup, outputnode, [("out_jacs", "jacobians"), ("out_mats", "xfms")]), (ref_average, brainextraction_wf, [("out_file", "inputnode.in_file")]), (brainextraction_wf, outputnode, [("outputnode.out_file", "fmap_ref"), ("outputnode.out_mask", "fmap_mask") ]), (fix_coeff, outputnode, [("out_coeff", "fmap_coeff")]), ]) # fmt: on if not debug: # fmt: off workflow.connect([ (concat_blips, pad_blip_slices, [("out_file", "in_file")]), (pad_blip_slices, topup, [("out_file", "in_file")]), (topup, ref_average, [("out_corrected", "in_file")]), (topup, outputnode, [("out_field", "fmap"), ("out_warps", "out_warps")]), ]) # fmt: on return workflow from nipype.interfaces.afni.preprocess import Volreg from niworkflows.interfaces.nibabel import SplitSeries from ...interfaces.bspline import ApplyCoeffsField realign = pe.Node( Volreg(args=f"-base {grid_reference}", outputtype="NIFTI_GZ"), name="realign_blips", ) split_blips = pe.Node(SplitSeries(), name="split_blips") unwarp = pe.Node(ApplyCoeffsField(), name="unwarp") unwarp.interface._always_run = True concat_corrected = pe.Node(MergeSeries(), name="concat_corrected") # fmt:off workflow.connect([ (concat_blips, realign, [("out_file", "in_file")]), (realign, pad_blip_slices, [("out_file", "in_file")]), (pad_blip_slices, topup, [("out_file", "in_file")]), (fix_coeff, unwarp, [("out_coeff", "in_coeff")]), (realign, split_blips, [("out_file", "in_file")]), (split_blips, unwarp, [("out_files", "in_data")]), (readout_time, unwarp, [("readout_time", "ro_time"), ("pe_direction", "pe_dir")]), (unwarp, outputnode, [("out_warp", "out_warps"), ("out_field", "fmap")]), (unwarp, concat_corrected, [("out_corrected", "in_files")]), (concat_corrected, ref_average, [("out_file", "in_file")]), ]) # fmt:on return workflow
def init_bold_confs_wf(mem_gb, metadata, name="bold_confs_wf"): """ This workflow calculates confounds for a BOLD series, and aggregates them into a :abbr:`TSV (tab-separated value)` file, for use as nuisance regressors in a :abbr:`GLM (general linear model)`. The following confounds are calculated, with column headings in parentheses: #. Region-wise average signal (``csf``, ``white_matter``, ``global_signal``) #. DVARS - original and standardized variants (``dvars``, ``std_dvars``) #. Framewise displacement, based on head-motion parameters (``framewise_displacement``) #. Temporal CompCor (``t_comp_cor_XX``) #. Anatomical CompCor (``a_comp_cor_XX``) #. Cosine basis set for high-pass filtering w/ 0.008 Hz cut-off (``cosine_XX``) #. Non-steady-state volumes (``non_steady_state_XX``) #. Estimated head-motion parameters, in mm and rad (``trans_x``, ``trans_y``, ``trans_z``, ``rot_x``, ``rot_y``, ``rot_z``) Prior to estimating aCompCor and tCompCor, non-steady-state volumes are censored and high-pass filtered using a :abbr:`DCT (discrete cosine transform)` basis. The cosine basis, as well as one regressor per censored volume, are included for convenience. .. workflow:: :graph2use: orig :simple_form: yes from fmriprep.workflows.bold.confounds import init_bold_confs_wf wf = init_bold_confs_wf( mem_gb=1, metadata={}) **Parameters** mem_gb : float Size of BOLD file in GB - please note that this size should be calculated after resamplings that may extend the FoV metadata : dict BIDS metadata for BOLD file name : str Name of workflow (default: ``bold_confs_wf``) **Inputs** bold BOLD image, after the prescribed corrections (STC, HMC and SDC) when available. bold_mask BOLD series mask movpar_file SPM-formatted motion parameters file skip_vols number of non steady state volumes t1_mask Mask of the skull-stripped template image t1_tpms List of tissue probability maps in T1w space t1_bold_xform Affine matrix that maps the T1w space into alignment with the native BOLD space **Outputs** confounds_file TSV of all aggregated confounds rois_report Reportlet visualizing white-matter/CSF mask used for aCompCor, the ROI for tCompCor and the BOLD brain mask. """ workflow = Workflow(name=name) workflow.__desc__ = """\ Several confounding time-series were calculated based on the *preprocessed BOLD*: framewise displacement (FD), DVARS and three region-wise global signals. FD and DVARS are calculated for each functional run, both using their implementations in *Nipype* [following the definitions by @power_fd_dvars]. The three global signals are extracted within the CSF, the WM, and the whole-brain masks. Additionally, a set of physiological regressors were extracted to allow for component-based noise correction [*CompCor*, @compcor]. Principal components are estimated after high-pass filtering the *preprocessed BOLD* time-series (using a discrete cosine filter with 128s cut-off) for the two *CompCor* variants: temporal (tCompCor) and anatomical (aCompCor). Six tCompCor components are then calculated from the top 5% variable voxels within a mask covering the subcortical regions. This subcortical mask is obtained by heavily eroding the brain mask, which ensures it does not include cortical GM regions. For aCompCor, six components are calculated within the intersection of the aforementioned mask and the union of CSF and WM masks calculated in T1w space, after their projection to the native space of each functional run (using the inverse BOLD-to-T1w transformation). The head-motion estimates calculated in the correction step were also placed within the corresponding confounds file. """ inputnode = pe.Node(niu.IdentityInterface( fields=['bold', 'bold_mask', 'movpar_file', 'skip_vols', 't1_mask', 't1_tpms', 't1_bold_xform']), name='inputnode') outputnode = pe.Node(niu.IdentityInterface( fields=['confounds_file']), name='outputnode') # Get masks ready in T1w space acc_tpm = pe.Node(AddTPMs(indices=[0, 2]), name='tpms_add_csf_wm') # acc stands for aCompCor csf_roi = pe.Node(TPM2ROI(erode_mm=0, mask_erode_mm=30), name='csf_roi') wm_roi = pe.Node(TPM2ROI( erode_prop=0.6, mask_erode_prop=0.6**3), # 0.6 = radius; 0.6^3 = volume name='wm_roi') acc_roi = pe.Node(TPM2ROI( erode_prop=0.6, mask_erode_prop=0.6**3), # 0.6 = radius; 0.6^3 = volume name='acc_roi') # Map ROIs in T1w space into BOLD space csf_tfm = pe.Node(ApplyTransforms(interpolation='NearestNeighbor', float=True), name='csf_tfm', mem_gb=0.1) wm_tfm = pe.Node(ApplyTransforms(interpolation='NearestNeighbor', float=True), name='wm_tfm', mem_gb=0.1) acc_tfm = pe.Node(ApplyTransforms(interpolation='NearestNeighbor', float=True), name='acc_tfm', mem_gb=0.1) tcc_tfm = pe.Node(ApplyTransforms(interpolation='NearestNeighbor', float=True), name='tcc_tfm', mem_gb=0.1) # Ensure ROIs don't go off-limits (reduced FoV) csf_msk = pe.Node(niu.Function(function=_maskroi), name='csf_msk') wm_msk = pe.Node(niu.Function(function=_maskroi), name='wm_msk') acc_msk = pe.Node(niu.Function(function=_maskroi), name='acc_msk') tcc_msk = pe.Node(niu.Function(function=_maskroi), name='tcc_msk') # DVARS dvars = pe.Node(nac.ComputeDVARS(save_nstd=True, save_std=True, remove_zerovariance=True), name="dvars", mem_gb=mem_gb) # Frame displacement fdisp = pe.Node(nac.FramewiseDisplacement(parameter_source="SPM"), name="fdisp", mem_gb=mem_gb) # a/t-CompCor tcompcor = pe.Node( TCompCor(components_file='tcompcor.tsv', header_prefix='t_comp_cor_', pre_filter='cosine', save_pre_filter=True, percentile_threshold=.05), name="tcompcor", mem_gb=mem_gb) acompcor = pe.Node( ACompCor(components_file='acompcor.tsv', header_prefix='a_comp_cor_', pre_filter='cosine', save_pre_filter=True), name="acompcor", mem_gb=mem_gb) # Set TR if present if 'RepetitionTime' in metadata: tcompcor.inputs.repetition_time = metadata['RepetitionTime'] acompcor.inputs.repetition_time = metadata['RepetitionTime'] # Global and segment regressors mrg_lbl = pe.Node(niu.Merge(3), name='merge_rois', run_without_submitting=True) signals = pe.Node(SignalExtraction(class_labels=["csf", "white_matter", "global_signal"]), name="signals", mem_gb=mem_gb) # Arrange confounds add_dvars_header = pe.Node( AddTSVHeader(columns=["dvars"]), name="add_dvars_header", mem_gb=0.01, run_without_submitting=True) add_std_dvars_header = pe.Node( AddTSVHeader(columns=["std_dvars"]), name="add_std_dvars_header", mem_gb=0.01, run_without_submitting=True) add_motion_headers = pe.Node( AddTSVHeader(columns=["trans_x", "trans_y", "trans_z", "rot_x", "rot_y", "rot_z"]), name="add_motion_headers", mem_gb=0.01, run_without_submitting=True) concat = pe.Node(GatherConfounds(), name="concat", mem_gb=0.01, run_without_submitting=True) # Generate reportlet mrg_compcor = pe.Node(niu.Merge(2), name='merge_compcor', run_without_submitting=True) rois_plot = pe.Node(ROIsPlot(colors=['b', 'magenta'], generate_report=True), name='rois_plot', mem_gb=mem_gb) ds_report_bold_rois = pe.Node( DerivativesDataSink(suffix='rois'), name='ds_report_bold_rois', run_without_submitting=True, mem_gb=DEFAULT_MEMORY_MIN_GB) def _pick_csf(files): return files[0] def _pick_wm(files): return files[-1] workflow.connect([ # Massage ROIs (in T1w space) (inputnode, acc_tpm, [('t1_tpms', 'in_files')]), (inputnode, csf_roi, [(('t1_tpms', _pick_csf), 'in_tpm'), ('t1_mask', 'in_mask')]), (inputnode, wm_roi, [(('t1_tpms', _pick_wm), 'in_tpm'), ('t1_mask', 'in_mask')]), (inputnode, acc_roi, [('t1_mask', 'in_mask')]), (acc_tpm, acc_roi, [('out_file', 'in_tpm')]), # Map ROIs to BOLD (inputnode, csf_tfm, [('bold_mask', 'reference_image'), ('t1_bold_xform', 'transforms')]), (csf_roi, csf_tfm, [('roi_file', 'input_image')]), (inputnode, wm_tfm, [('bold_mask', 'reference_image'), ('t1_bold_xform', 'transforms')]), (wm_roi, wm_tfm, [('roi_file', 'input_image')]), (inputnode, acc_tfm, [('bold_mask', 'reference_image'), ('t1_bold_xform', 'transforms')]), (acc_roi, acc_tfm, [('roi_file', 'input_image')]), (inputnode, tcc_tfm, [('bold_mask', 'reference_image'), ('t1_bold_xform', 'transforms')]), (csf_roi, tcc_tfm, [('eroded_mask', 'input_image')]), # Mask ROIs with bold_mask (inputnode, csf_msk, [('bold_mask', 'in_mask')]), (inputnode, wm_msk, [('bold_mask', 'in_mask')]), (inputnode, acc_msk, [('bold_mask', 'in_mask')]), (inputnode, tcc_msk, [('bold_mask', 'in_mask')]), # connect inputnode to each non-anatomical confound node (inputnode, dvars, [('bold', 'in_file'), ('bold_mask', 'in_mask')]), (inputnode, fdisp, [('movpar_file', 'in_file')]), # tCompCor (inputnode, tcompcor, [('bold', 'realigned_file')]), (inputnode, tcompcor, [('skip_vols', 'ignore_initial_volumes')]), (tcc_tfm, tcc_msk, [('output_image', 'roi_file')]), (tcc_msk, tcompcor, [('out', 'mask_files')]), # aCompCor (inputnode, acompcor, [('bold', 'realigned_file')]), (inputnode, acompcor, [('skip_vols', 'ignore_initial_volumes')]), (acc_tfm, acc_msk, [('output_image', 'roi_file')]), (acc_msk, acompcor, [('out', 'mask_files')]), # Global signals extraction (constrained by anatomy) (inputnode, signals, [('bold', 'in_file')]), (csf_tfm, csf_msk, [('output_image', 'roi_file')]), (csf_msk, mrg_lbl, [('out', 'in1')]), (wm_tfm, wm_msk, [('output_image', 'roi_file')]), (wm_msk, mrg_lbl, [('out', 'in2')]), (inputnode, mrg_lbl, [('bold_mask', 'in3')]), (mrg_lbl, signals, [('out', 'label_files')]), # Collate computed confounds together (inputnode, add_motion_headers, [('movpar_file', 'in_file')]), (dvars, add_dvars_header, [('out_nstd', 'in_file')]), (dvars, add_std_dvars_header, [('out_std', 'in_file')]), (signals, concat, [('out_file', 'signals')]), (fdisp, concat, [('out_file', 'fd')]), (tcompcor, concat, [('components_file', 'tcompcor'), ('pre_filter_file', 'cos_basis')]), (acompcor, concat, [('components_file', 'acompcor')]), (add_motion_headers, concat, [('out_file', 'motion')]), (add_dvars_header, concat, [('out_file', 'dvars')]), (add_std_dvars_header, concat, [('out_file', 'std_dvars')]), # Set outputs (concat, outputnode, [('confounds_file', 'confounds_file')]), (inputnode, rois_plot, [('bold', 'in_file'), ('bold_mask', 'in_mask')]), (tcompcor, mrg_compcor, [('high_variance_masks', 'in1')]), (acc_msk, mrg_compcor, [('out', 'in2')]), (mrg_compcor, rois_plot, [('out', 'in_rois')]), (rois_plot, ds_report_bold_rois, [('out_report', 'in_file')]), ]) return workflow
def init_fmriprep_wf(layout, subject_list, task_id, echo_idx, run_uuid, work_dir, output_dir, ignore, debug, low_mem, anat_only, longitudinal, t2s_coreg, omp_nthreads, skull_strip_template, skull_strip_fixed_seed, freesurfer, output_spaces, template, medial_surface_nan, cifti_output, hires, use_bbr, bold2t1w_dof, fmap_bspline, fmap_demean, use_syn, force_syn, use_aroma, err_on_aroma_warn, aroma_melodic_dim, template_out_grid): """ This workflow organizes the execution of FMRIPREP, with a sub-workflow for each subject. If FreeSurfer's recon-all is to be run, a FreeSurfer derivatives folder is created and populated with any needed template subjects. .. workflow:: :graph2use: orig :simple_form: yes import os from collections import namedtuple BIDSLayout = namedtuple('BIDSLayout', ['root'], defaults='.') from fmriprep.workflows.base import init_fmriprep_wf os.environ['FREESURFER_HOME'] = os.getcwd() wf = init_fmriprep_wf(layout=BIDSLayout(), subject_list=['fmripreptest'], task_id='', echo_idx=None, run_uuid='X', work_dir='.', output_dir='.', ignore=[], debug=False, low_mem=False, anat_only=False, longitudinal=False, t2s_coreg=False, omp_nthreads=1, skull_strip_template='OASIS30ANTs', skull_strip_fixed_seed=False, freesurfer=True, output_spaces=['T1w', 'fsnative', 'template', 'fsaverage5'], template='MNI152NLin2009cAsym', medial_surface_nan=False, cifti_output=False, hires=True, use_bbr=True, bold2t1w_dof=9, fmap_bspline=False, fmap_demean=True, use_syn=True, force_syn=True, use_aroma=False, err_on_aroma_warn=False, aroma_melodic_dim=-200, template_out_grid='native') Parameters layout : BIDSLayout object BIDS dataset layout subject_list : list List of subject labels task_id : str or None Task ID of BOLD series to preprocess, or ``None`` to preprocess all echo_idx : int or None Index of echo to preprocess in multiecho BOLD series, or ``None`` to preprocess all run_uuid : str Unique identifier for execution instance work_dir : str Directory in which to store workflow execution state and temporary files output_dir : str Directory in which to save derivatives ignore : list Preprocessing steps to skip (may include "slicetiming", "fieldmaps") debug : bool Enable debugging outputs low_mem : bool Write uncompressed .nii files in some cases to reduce memory usage anat_only : bool Disable functional workflows longitudinal : bool Treat multiple sessions as longitudinal (may increase runtime) See sub-workflows for specific differences t2s_coreg : bool For multi-echo EPI, use the calculated T2*-map for T2*-driven coregistration omp_nthreads : int Maximum number of threads an individual process may use skull_strip_template : str Name of ANTs skull-stripping template ('OASIS30ANTs' or 'NKI') skull_strip_fixed_seed : bool Do not use a random seed for skull-stripping - will ensure run-to-run replicability when used with --omp-nthreads 1 freesurfer : bool Enable FreeSurfer surface reconstruction (may increase runtime) output_spaces : list List of output spaces functional images are to be resampled to. Some parts of pipeline will only be instantiated for some output spaces. Valid spaces: - T1w - template - fsnative - fsaverage (or other pre-existing FreeSurfer templates) template : str Name of template targeted by ``template`` output space medial_surface_nan : bool Replace medial wall values with NaNs on functional GIFTI files cifti_output : bool Generate bold CIFTI file in output spaces hires : bool Enable sub-millimeter preprocessing in FreeSurfer use_bbr : bool or None Enable/disable boundary-based registration refinement. If ``None``, test BBR result for distortion before accepting. bold2t1w_dof : 6, 9 or 12 Degrees-of-freedom for BOLD-T1w registration fmap_bspline : bool **Experimental**: Fit B-Spline field using least-squares fmap_demean : bool Demean voxel-shift map during unwarp use_syn : bool **Experimental**: Enable ANTs SyN-based susceptibility distortion correction (SDC). If fieldmaps are present and enabled, this is not run, by default. force_syn : bool **Temporary**: Always run SyN-based SDC use_aroma : bool Perform ICA-AROMA on MNI-resampled functional series err_on_aroma_warn : bool Do not fail on ICA-AROMA errors template_out_grid : str Keyword ('native', '1mm' or '2mm') or path of custom reference image for normalization """ fmriprep_wf = Workflow(name='fmriprep_wf') fmriprep_wf.base_dir = work_dir if freesurfer: fsdir = pe.Node(BIDSFreeSurferDir( derivatives=output_dir, freesurfer_home=os.getenv('FREESURFER_HOME'), spaces=output_spaces), name='fsdir_run_' + run_uuid.replace('-', '_'), run_without_submitting=True) reportlets_dir = os.path.join(work_dir, 'reportlets') for subject_id in subject_list: single_subject_wf = init_single_subject_wf( layout=layout, subject_id=subject_id, task_id=task_id, echo_idx=echo_idx, name="single_subject_" + subject_id + "_wf", reportlets_dir=reportlets_dir, output_dir=output_dir, ignore=ignore, debug=debug, low_mem=low_mem, anat_only=anat_only, longitudinal=longitudinal, t2s_coreg=t2s_coreg, omp_nthreads=omp_nthreads, skull_strip_template=skull_strip_template, skull_strip_fixed_seed=skull_strip_fixed_seed, freesurfer=freesurfer, output_spaces=output_spaces, template=template, medial_surface_nan=medial_surface_nan, cifti_output=cifti_output, hires=hires, use_bbr=use_bbr, bold2t1w_dof=bold2t1w_dof, fmap_bspline=fmap_bspline, fmap_demean=fmap_demean, use_syn=use_syn, force_syn=force_syn, template_out_grid=template_out_grid, use_aroma=use_aroma, aroma_melodic_dim=aroma_melodic_dim, err_on_aroma_warn=err_on_aroma_warn, ) single_subject_wf.config['execution']['crashdump_dir'] = (os.path.join( output_dir, "fmriprep", "sub-" + subject_id, 'log', run_uuid)) for node in single_subject_wf._get_all_nodes(): node.config = deepcopy(single_subject_wf.config) if freesurfer: fmriprep_wf.connect(fsdir, 'subjects_dir', single_subject_wf, 'inputnode.subjects_dir') else: fmriprep_wf.add_nodes([single_subject_wf]) return fmriprep_wf
def init_ica_aroma_wf(template, metadata, mem_gb, omp_nthreads, name='ica_aroma_wf', susan_fwhm=6.0, ignore_aroma_err=False, aroma_melodic_dim=-200, use_fieldwarp=True): """ This workflow wraps `ICA-AROMA`_ to identify and remove motion-related independent components from a BOLD time series. The following steps are performed: #. Remove non-steady state volumes from the bold series. #. Smooth data using FSL `susan`, with a kernel width FWHM=6.0mm. #. Run FSL `melodic` outside of ICA-AROMA to generate the report #. Run ICA-AROMA #. Aggregate identified motion components (aggressive) to TSV #. Return ``classified_motion_ICs`` and ``melodic_mix`` for user to complete non-aggressive denoising in T1w space #. Calculate ICA-AROMA-identified noise components (columns named ``AROMAAggrCompXX``) Additionally, non-aggressive denoising is performed on the BOLD series resampled into MNI space. There is a current discussion on whether other confounds should be extracted before or after denoising `here <http://nbviewer.jupyter.org/github/poldracklab/\ fmriprep-notebooks/blob/922e436429b879271fa13e76767a6e73443e74d9/issue-817_\ aroma_confounds.ipynb>`__. .. workflow:: :graph2use: orig :simple_form: yes from fmriprep.workflows.bold.confounds import init_ica_aroma_wf wf = init_ica_aroma_wf(template='MNI152NLin2009cAsym', metadata={'RepetitionTime': 1.0}, mem_gb=3, omp_nthreads=1) **Parameters** template : str Spatial normalization template used as target when that registration step was previously calculated with :py:func:`~fmriprep.workflows.bold.registration.init_bold_reg_wf`. The template must be one of the MNI templates (fMRIPrep uses ``MNI152NLin2009cAsym`` by default). metadata : dict BIDS metadata for BOLD file mem_gb : float Size of BOLD file in GB omp_nthreads : int Maximum number of threads an individual process may use name : str Name of workflow (default: ``bold_mni_trans_wf``) susan_fwhm : float Kernel width (FWHM in mm) for the smoothing step with FSL ``susan`` (default: 6.0mm) use_fieldwarp : bool Include SDC warp in single-shot transform from BOLD to MNI ignore_aroma_err : bool Do not fail on ICA-AROMA errors aroma_melodic_dim: int Set the dimensionality of the MELODIC ICA decomposition. Negative numbers set a maximum on automatic dimensionality estimation. Positive numbers set an exact number of components to extract. (default: -200, i.e., estimate <=200 components) **Inputs** itk_bold_to_t1 Affine transform from ``ref_bold_brain`` to T1 space (ITK format) t1_2_mni_forward_transform ANTs-compatible affine-and-warp transform file name_source BOLD series NIfTI file Used to recover original information lost during processing skip_vols number of non steady state volumes bold_split Individual 3D BOLD volumes, not motion corrected bold_mask BOLD series mask in template space hmc_xforms List of affine transforms aligning each volume to ``ref_image`` in ITK format fieldwarp a :abbr:`DFM (displacements field map)` in ITK format movpar_file SPM-formatted motion parameters file **Outputs** aroma_confounds TSV of confounds identified as noise by ICA-AROMA aroma_noise_ics CSV of noise components identified by ICA-AROMA melodic_mix FSL MELODIC mixing matrix nonaggr_denoised_file BOLD series with non-aggressive ICA-AROMA denoising applied .. _ICA-AROMA: https://github.com/maartenmennes/ICA-AROMA """ workflow = Workflow(name=name) workflow.__postdesc__ = """\ Automatic removal of motion artifacts using independent component analysis [ICA-AROMA, @aroma] was performed on the *preprocessed BOLD on MNI space* time-series after removal of non-steady state volumes and spatial smoothing with an isotropic, Gaussian kernel of 6mm FWHM (full-width half-maximum). Corresponding "non-aggresively" denoised runs were produced after such smoothing. Additionally, the "aggressive" noise-regressors were collected and placed in the corresponding confounds file. """ inputnode = pe.Node(niu.IdentityInterface( fields=[ 'itk_bold_to_t1', 't1_2_mni_forward_transform', 'name_source', 'skip_vols', 'bold_split', 'bold_mask', 'hmc_xforms', 'fieldwarp', 'movpar_file']), name='inputnode') outputnode = pe.Node(niu.IdentityInterface( fields=['aroma_confounds', 'aroma_noise_ics', 'melodic_mix', 'nonaggr_denoised_file']), name='outputnode') bold_mni_trans_wf = init_bold_mni_trans_wf( template=template, freesurfer=False, mem_gb=mem_gb, omp_nthreads=omp_nthreads, template_out_grid=str( get_template('MNI152Lin') / 'tpl-MNI152Lin_space-MNI_res-02_T1w.nii.gz'), use_compression=False, use_fieldwarp=use_fieldwarp, name='bold_mni_trans_wf' ) bold_mni_trans_wf.__desc__ = None rm_non_steady_state = pe.Node(niu.Function(function=_remove_volumes, output_names=['bold_cut']), name='rm_nonsteady') calc_median_val = pe.Node(fsl.ImageStats(op_string='-k %s -p 50'), name='calc_median_val') calc_bold_mean = pe.Node(fsl.MeanImage(), name='calc_bold_mean') def _getusans_func(image, thresh): return [tuple([image, thresh])] getusans = pe.Node(niu.Function(function=_getusans_func, output_names=['usans']), name='getusans', mem_gb=0.01) smooth = pe.Node(fsl.SUSAN(fwhm=susan_fwhm), name='smooth') # melodic node melodic = pe.Node(fsl.MELODIC( no_bet=True, tr_sec=float(metadata['RepetitionTime']), mm_thresh=0.5, out_stats=True, dim=aroma_melodic_dim), name="melodic") # ica_aroma node ica_aroma = pe.Node(ICA_AROMARPT( denoise_type='nonaggr', generate_report=True, TR=metadata['RepetitionTime']), name='ica_aroma') add_non_steady_state = pe.Node(niu.Function(function=_add_volumes, output_names=['bold_add']), name='add_nonsteady') # extract the confound ICs from the results ica_aroma_confound_extraction = pe.Node(ICAConfounds(ignore_aroma_err=ignore_aroma_err), name='ica_aroma_confound_extraction') ds_report_ica_aroma = pe.Node( DerivativesDataSink(suffix='ica_aroma'), name='ds_report_ica_aroma', run_without_submitting=True, mem_gb=DEFAULT_MEMORY_MIN_GB) def _getbtthresh(medianval): return 0.75 * medianval # connect the nodes workflow.connect([ (inputnode, bold_mni_trans_wf, [ ('name_source', 'inputnode.name_source'), ('bold_split', 'inputnode.bold_split'), ('bold_mask', 'inputnode.bold_mask'), ('hmc_xforms', 'inputnode.hmc_xforms'), ('itk_bold_to_t1', 'inputnode.itk_bold_to_t1'), ('t1_2_mni_forward_transform', 'inputnode.t1_2_mni_forward_transform'), ('fieldwarp', 'inputnode.fieldwarp')]), (inputnode, ica_aroma, [('movpar_file', 'motion_parameters')]), (inputnode, rm_non_steady_state, [ ('skip_vols', 'skip_vols')]), (bold_mni_trans_wf, rm_non_steady_state, [ ('outputnode.bold_mni', 'bold_file')]), (bold_mni_trans_wf, calc_median_val, [ ('outputnode.bold_mask_mni', 'mask_file')]), (rm_non_steady_state, calc_median_val, [ ('bold_cut', 'in_file')]), (rm_non_steady_state, calc_bold_mean, [ ('bold_cut', 'in_file')]), (calc_bold_mean, getusans, [('out_file', 'image')]), (calc_median_val, getusans, [('out_stat', 'thresh')]), # Connect input nodes to complete smoothing (rm_non_steady_state, smooth, [ ('bold_cut', 'in_file')]), (getusans, smooth, [('usans', 'usans')]), (calc_median_val, smooth, [(('out_stat', _getbtthresh), 'brightness_threshold')]), # connect smooth to melodic (smooth, melodic, [('smoothed_file', 'in_files')]), (bold_mni_trans_wf, melodic, [ ('outputnode.bold_mask_mni', 'mask')]), # connect nodes to ICA-AROMA (smooth, ica_aroma, [('smoothed_file', 'in_file')]), (bold_mni_trans_wf, ica_aroma, [ ('outputnode.bold_mask_mni', 'report_mask'), ('outputnode.bold_mask_mni', 'mask')]), (melodic, ica_aroma, [('out_dir', 'melodic_dir')]), # generate tsvs from ICA-AROMA (ica_aroma, ica_aroma_confound_extraction, [('out_dir', 'in_directory')]), (inputnode, ica_aroma_confound_extraction, [ ('skip_vols', 'skip_vols')]), # output for processing and reporting (ica_aroma_confound_extraction, outputnode, [('aroma_confounds', 'aroma_confounds'), ('aroma_noise_ics', 'aroma_noise_ics'), ('melodic_mix', 'melodic_mix')]), # TODO change melodic report to reflect noise and non-noise components (ica_aroma, add_non_steady_state, [ ('nonaggr_denoised_file', 'bold_cut_file')]), (bold_mni_trans_wf, add_non_steady_state, [ ('outputnode.bold_mni', 'bold_file')]), (inputnode, add_non_steady_state, [ ('skip_vols', 'skip_vols')]), (add_non_steady_state, outputnode, [('bold_add', 'nonaggr_denoised_file')]), (ica_aroma, ds_report_ica_aroma, [('out_report', 'in_file')]), ]) return workflow
def init_autorecon_resume_wf(omp_nthreads, name='autorecon_resume_wf'): r""" Resume recon-all execution, assuming the `-autorecon1` stage has been completed. In order to utilize resources efficiently, this is broken down into five sub-stages; after the first stage, the second and third stages may be run simultaneously, and the fourth and fifth stages may be run simultaneously, if resources permit:: $ recon-all -sd <output dir>/freesurfer -subjid sub-<subject_label> \ -autorecon2-volonly $ recon-all -sd <output dir>/freesurfer -subjid sub-<subject_label> \ -autorecon-hemi lh \ -noparcstats -nocortparc2 -noparcstats2 -nocortparc3 \ -noparcstats3 -nopctsurfcon -nohyporelabel -noaparc2aseg \ -noapas2aseg -nosegstats -nowmparc -nobalabels $ recon-all -sd <output dir>/freesurfer -subjid sub-<subject_label> \ -autorecon-hemi rh \ -noparcstats -nocortparc2 -noparcstats2 -nocortparc3 \ -noparcstats3 -nopctsurfcon -nohyporelabel -noaparc2aseg \ -noapas2aseg -nosegstats -nowmparc -nobalabels $ recon-all -sd <output dir>/freesurfer -subjid sub-<subject_label> \ -autorecon3 -hemi lh -T2pial $ recon-all -sd <output dir>/freesurfer -subjid sub-<subject_label> \ -autorecon3 -hemi rh -T2pial The excluded steps in the second and third stages (``-no<option>``) are not fully hemisphere independent, and are therefore postponed to the final two stages. .. workflow:: :graph2use: orig :simple_form: yes from smriprep.workflows.surfaces import init_autorecon_resume_wf wf = init_autorecon_resume_wf(omp_nthreads=1) **Inputs** subjects_dir FreeSurfer SUBJECTS_DIR subject_id FreeSurfer subject ID use_T2 Refine pial surface using T2w image use_FLAIR Refine pial surface using FLAIR image **Outputs** subjects_dir FreeSurfer SUBJECTS_DIR subject_id FreeSurfer subject ID """ workflow = Workflow(name=name) inputnode = pe.Node(niu.IdentityInterface( fields=['subjects_dir', 'subject_id', 'use_T2', 'use_FLAIR']), name='inputnode') outputnode = pe.Node( niu.IdentityInterface(fields=['subjects_dir', 'subject_id']), name='outputnode') autorecon2_vol = pe.Node(fs.ReconAll(directive='autorecon2-volonly', openmp=omp_nthreads), n_procs=omp_nthreads, mem_gb=5, name='autorecon2_vol') autorecon2_vol.interface._always_run = True autorecon_surfs = pe.MapNode(fs.ReconAll( directive='autorecon-hemi', flags=[ '-noparcstats', '-nocortparc2', '-noparcstats2', '-nocortparc3', '-noparcstats3', '-nopctsurfcon', '-nohyporelabel', '-noaparc2aseg', '-noapas2aseg', '-nosegstats', '-nowmparc', '-nobalabels' ], openmp=omp_nthreads), iterfield='hemi', n_procs=omp_nthreads, mem_gb=5, name='autorecon_surfs') autorecon_surfs.inputs.hemi = ['lh', 'rh'] autorecon_surfs.interface._always_run = True autorecon3 = pe.MapNode(fs.ReconAll(directive='autorecon3', openmp=omp_nthreads), iterfield='hemi', n_procs=omp_nthreads, mem_gb=5, name='autorecon3') autorecon3.inputs.hemi = ['lh', 'rh'] autorecon3.interface._always_run = True def _dedup(in_list): vals = set(in_list) if len(vals) > 1: raise ValueError( "Non-identical values can't be deduplicated:\n{!r}".format( in_list)) return vals.pop() workflow.connect([ (inputnode, autorecon3, [('use_T2', 'use_T2'), ('use_FLAIR', 'use_FLAIR')]), (inputnode, autorecon2_vol, [('subjects_dir', 'subjects_dir'), ('subject_id', 'subject_id')]), (autorecon2_vol, autorecon_surfs, [('subjects_dir', 'subjects_dir'), ('subject_id', 'subject_id')]), (autorecon_surfs, autorecon3, [(('subjects_dir', _dedup), 'subjects_dir'), (('subject_id', _dedup), 'subject_id')]), (autorecon3, outputnode, [(('subjects_dir', _dedup), 'subjects_dir'), (('subject_id', _dedup), 'subject_id')]), ]) return workflow
def init_bold_t2s_wf(echo_times, mem_gb, omp_nthreads, t2s_coreg=False, name='bold_t2s_wf'): """ This workflow wraps the `tedana`_ `T2* workflow`_ to optimally combine multiple echos and derive a T2* map for optional use as a coregistration target. The following steps are performed: #. :abbr:`HMC (head motion correction)` on individual echo files. #. Compute the T2* map #. Create an optimally combined ME-EPI time series **Parameters** echo_times list of TEs associated with each echo mem_gb : float Size of BOLD file in GB omp_nthreads : int Maximum number of threads an individual process may use t2s_coreg : bool Use the calculated T2*-map for T2*-driven coregistration name : str Name of workflow (default: ``bold_t2s_wf``) **Inputs** bold_file list of individual echo files **Outputs** bold the optimally combined time series for all supplied echos bold_mask the binarized, skull-stripped adaptive T2* map bold_ref_brain the adaptive T2* map .. _tedana: https://github.com/me-ica/tedana .. _`T2* workflow`: https://tedana.readthedocs.io/en/latest/generated/tedana.workflows.t2smap_workflow.html#tedana.workflows.t2smap_workflow # noqa """ workflow = Workflow(name=name) workflow.__desc__ = """\ A T2* map was estimated from the preprocessed BOLD by fitting to a monoexponential signal decay model with log-linear regression. For each voxel, the maximal number of echoes with reliable signal in that voxel were used to fit the model. The calculated T2* map was then used to optimally combine preprocessed BOLD across echoes following the method described in [@posse_t2s]. The optimally combined time series was carried forward as the *preprocessed BOLD*{}. """.format('' if not t2s_coreg else ', and the T2* map was also retained as the BOLD reference') inputnode = pe.Node(niu.IdentityInterface(fields=['bold_file']), name='inputnode') outputnode = pe.Node(niu.IdentityInterface(fields=['bold', 'bold_mask', 'bold_ref_brain']), name='outputnode') LOGGER.log(25, 'Generating T2* map and optimally combined ME-EPI time series.') t2smap_node = pe.Node(T2SMap(echo_times=echo_times), name='t2smap_node') skullstrip_t2smap_wf = init_skullstrip_bold_wf(name='skullstrip_t2smap_wf') workflow.connect([ (inputnode, t2smap_node, [('bold_file', 'in_files')]), (t2smap_node, outputnode, [('optimal_comb', 'bold')]), (t2smap_node, skullstrip_t2smap_wf, [('t2star_map', 'inputnode.in_file')]), (skullstrip_t2smap_wf, outputnode, [ ('outputnode.mask_file', 'bold_mask'), ('outputnode.skull_stripped_file', 'bold_ref_brain')]), ]) return workflow
def init_gifti_surface_wf(name='gifti_surface_wf'): r""" Prepare GIFTI surfaces from a FreeSurfer subjects directory. If midthickness (or graymid) surfaces do not exist, they are generated and saved to the subject directory as ``lh/rh.midthickness``. These, along with the gray/white matter boundary (``lh/rh.smoothwm``), pial sufaces (``lh/rh.pial``) and inflated surfaces (``lh/rh.inflated``) are converted to GIFTI files. Additionally, the vertex coordinates are :py:class:`recentered <smriprep.interfaces.NormalizeSurf>` to align with native T1w space. .. workflow:: :graph2use: orig :simple_form: yes from smriprep.workflows.surfaces import init_gifti_surface_wf wf = init_gifti_surface_wf() **Inputs** subjects_dir FreeSurfer SUBJECTS_DIR subject_id FreeSurfer subject ID fsnative2t1w_xfm LTA formatted affine transform file (inverse) **Outputs** surfaces GIFTI surfaces for gray/white matter boundary, pial surface, midthickness (or graymid) surface, and inflated surfaces """ workflow = Workflow(name=name) inputnode = pe.Node(niu.IdentityInterface( ['subjects_dir', 'subject_id', 'fsnative2t1w_xfm']), name='inputnode') outputnode = pe.Node(niu.IdentityInterface(['surfaces']), name='outputnode') get_surfaces = pe.Node(nio.FreeSurferSource(), name='get_surfaces') midthickness = pe.MapNode(MakeMidthickness(thickness=True, distance=0.5, out_name='midthickness'), iterfield='in_file', name='midthickness') save_midthickness = pe.Node(nio.DataSink(parameterization=False), name='save_midthickness') surface_list = pe.Node(niu.Merge(4, ravel_inputs=True), name='surface_list', run_without_submitting=True) fs2gii = pe.MapNode(fs.MRIsConvert(out_datatype='gii'), iterfield='in_file', name='fs2gii') fix_surfs = pe.MapNode(NormalizeSurf(), iterfield='in_file', name='fix_surfs') workflow.connect([ (inputnode, get_surfaces, [('subjects_dir', 'subjects_dir'), ('subject_id', 'subject_id')]), (inputnode, save_midthickness, [('subjects_dir', 'base_directory'), ('subject_id', 'container')]), # Generate midthickness surfaces and save to FreeSurfer derivatives (get_surfaces, midthickness, [('smoothwm', 'in_file'), ('graymid', 'graymid')]), (midthickness, save_midthickness, [('out_file', 'surf.@graymid')]), # Produce valid GIFTI surface files (dense mesh) (get_surfaces, surface_list, [('smoothwm', 'in1'), ('pial', 'in2'), ('inflated', 'in3')]), (save_midthickness, surface_list, [('out_file', 'in4')]), (surface_list, fs2gii, [('out', 'in_file')]), (fs2gii, fix_surfs, [('converted', 'in_file')]), (inputnode, fix_surfs, [('fsnative2t1w_xfm', 'transform_file')]), (fix_surfs, outputnode, [('out_file', 'surfaces')]), ]) return workflow
def init_func_preproc_wf(bold_file, ignore, freesurfer, use_bbr, t2s_coreg, bold2t1w_dof, reportlets_dir, output_spaces, template, output_dir, omp_nthreads, fmap_bspline, fmap_demean, use_syn, force_syn, use_aroma, ignore_aroma_err, aroma_melodic_dim, medial_surface_nan, cifti_output, debug, low_mem, template_out_grid, layout=None, num_bold=1): """ This workflow controls the functional preprocessing stages of FMRIPREP. .. workflow:: :graph2use: orig :simple_form: yes from fmriprep.workflows.bold import init_func_preproc_wf wf = init_func_preproc_wf('/completely/made/up/path/sub-01_task-nback_bold.nii.gz', omp_nthreads=1, ignore=[], freesurfer=True, reportlets_dir='.', output_dir='.', template='MNI152NLin2009cAsym', output_spaces=['T1w', 'fsnative', 'template', 'fsaverage5'], debug=False, use_bbr=True, t2s_coreg=False, bold2t1w_dof=9, fmap_bspline=True, fmap_demean=True, use_syn=True, force_syn=True, low_mem=False, template_out_grid='native', medial_surface_nan=False, cifti_output=False, use_aroma=False, ignore_aroma_err=False, aroma_melodic_dim=-200, num_bold=1) **Parameters** bold_file : str BOLD series NIfTI file ignore : list Preprocessing steps to skip (may include "slicetiming", "fieldmaps") freesurfer : bool Enable FreeSurfer functional registration (bbregister) and resampling BOLD series to FreeSurfer surface meshes. use_bbr : bool or None Enable/disable boundary-based registration refinement. If ``None``, test BBR result for distortion before accepting. When using ``t2s_coreg``, BBR will be enabled by default unless explicitly specified otherwise. t2s_coreg : bool For multiecho EPI, use the calculated T2*-map for T2*-driven coregistration bold2t1w_dof : 6, 9 or 12 Degrees-of-freedom for BOLD-T1w registration reportlets_dir : str Directory in which to save reportlets output_spaces : list List of output spaces functional images are to be resampled to. Some parts of pipeline will only be instantiated for some output spaces. Valid spaces: - T1w - template - fsnative - fsaverage (or other pre-existing FreeSurfer templates) template : str Name of template targeted by ``template`` output space output_dir : str Directory in which to save derivatives omp_nthreads : int Maximum number of threads an individual process may use fmap_bspline : bool **Experimental**: Fit B-Spline field using least-squares fmap_demean : bool Demean voxel-shift map during unwarp use_syn : bool **Experimental**: Enable ANTs SyN-based susceptibility distortion correction (SDC). If fieldmaps are present and enabled, this is not run, by default. force_syn : bool **Temporary**: Always run SyN-based SDC use_aroma : bool Perform ICA-AROMA on MNI-resampled functional series ignore_aroma_err : bool Do not fail on ICA-AROMA errors medial_surface_nan : bool Replace medial wall values with NaNs on functional GIFTI files cifti_output : bool Generate bold CIFTI file in output spaces debug : bool Enable debugging outputs low_mem : bool Write uncompressed .nii files in some cases to reduce memory usage template_out_grid : str Keyword ('native', '1mm' or '2mm') or path of custom reference image for normalization layout : BIDSLayout BIDSLayout structure to enable metadata retrieval num_bold : int Total number of BOLD files that have been set for preprocessing (default is 1) **Inputs** bold_file BOLD series NIfTI file t1_preproc Bias-corrected structural template image t1_brain Skull-stripped ``t1_preproc`` t1_mask Mask of the skull-stripped template image t1_seg Segmentation of preprocessed structural image, including gray-matter (GM), white-matter (WM) and cerebrospinal fluid (CSF) t1_tpms List of tissue probability maps in T1w space t1_2_mni_forward_transform ANTs-compatible affine-and-warp transform file t1_2_mni_reverse_transform ANTs-compatible affine-and-warp transform file (inverse) subjects_dir FreeSurfer SUBJECTS_DIR subject_id FreeSurfer subject ID t1_2_fsnative_forward_transform LTA-style affine matrix translating from T1w to FreeSurfer-conformed subject space t1_2_fsnative_reverse_transform LTA-style affine matrix translating from FreeSurfer-conformed subject space to T1w **Outputs** bold_t1 BOLD series, resampled to T1w space bold_mask_t1 BOLD series mask in T1w space bold_mni BOLD series, resampled to template space bold_mask_mni BOLD series mask in template space confounds TSV of confounds surfaces BOLD series, resampled to FreeSurfer surfaces aroma_noise_ics Noise components identified by ICA-AROMA melodic_mix FSL MELODIC mixing matrix bold_cifti BOLD CIFTI image cifti_variant combination of target spaces for `bold_cifti` **Subworkflows** * :py:func:`~fmriprep.workflows.bold.util.init_bold_reference_wf` * :py:func:`~fmriprep.workflows.bold.stc.init_bold_stc_wf` * :py:func:`~fmriprep.workflows.bold.hmc.init_bold_hmc_wf` * :py:func:`~fmriprep.workflows.bold.t2s.init_bold_t2s_wf` * :py:func:`~fmriprep.workflows.bold.registration.init_bold_t1_trans_wf` * :py:func:`~fmriprep.workflows.bold.registration.init_bold_reg_wf` * :py:func:`~fmriprep.workflows.bold.confounds.init_bold_confounds_wf` * :py:func:`~fmriprep.workflows.bold.confounds.init_ica_aroma_wf` * :py:func:`~fmriprep.workflows.bold.resampling.init_bold_mni_trans_wf` * :py:func:`~fmriprep.workflows.bold.resampling.init_bold_preproc_trans_wf` * :py:func:`~fmriprep.workflows.bold.resampling.init_bold_surf_wf` * :py:func:`~fmriprep.workflows.fieldmap.pepolar.init_pepolar_unwarp_wf` * :py:func:`~fmriprep.workflows.fieldmap.init_fmap_estimator_wf` * :py:func:`~fmriprep.workflows.fieldmap.init_sdc_unwarp_wf` * :py:func:`~fmriprep.workflows.fieldmap.init_nonlinear_sdc_wf` """ from ..fieldmap.base import init_sdc_wf # Avoid circular dependency (#1066) ref_file = bold_file mem_gb = {'filesize': 1, 'resampled': 1, 'largemem': 1} bold_tlen = 10 multiecho = isinstance(bold_file, list) if multiecho: tes = [layout.get_metadata(echo)['EchoTime'] for echo in bold_file] ref_file = dict(zip(tes, bold_file))[min(tes)] if os.path.isfile(ref_file): bold_tlen, mem_gb = _create_mem_gb(ref_file) wf_name = _get_wf_name(ref_file) LOGGER.log(25, ('Creating bold processing workflow for "%s" (%.2f GB / %d TRs). ' 'Memory resampled/largemem=%.2f/%.2f GB.'), ref_file, mem_gb['filesize'], bold_tlen, mem_gb['resampled'], mem_gb['largemem']) sbref_file = None # For doc building purposes if layout is None or bold_file == 'bold_preprocesing': LOGGER.log(25, 'No valid layout: building empty workflow.') metadata = { 'RepetitionTime': 2.0, 'SliceTiming': [0.0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9], 'PhaseEncodingDirection': 'j', } fmaps = [{ 'type': 'phasediff', 'phasediff': 'sub-03/ses-2/fmap/sub-03_ses-2_run-1_phasediff.nii.gz', 'magnitude1': 'sub-03/ses-2/fmap/sub-03_ses-2_run-1_magnitude1.nii.gz', 'magnitude2': 'sub-03/ses-2/fmap/sub-03_ses-2_run-1_magnitude2.nii.gz', }] run_stc = True multiecho = False else: # Find associated sbref, if possible entities = layout.parse_file_entities(ref_file) entities['type'] = 'sbref' files = layout.get(**entities, extensions=['nii', 'nii.gz']) refbase = os.path.basename(ref_file) if 'sbref' in ignore: LOGGER.info("Single-band reference files ignored.") elif files and multiecho: LOGGER.warning("Single-band reference found, but not supported in " "multi-echo workflows at this time. Ignoring.") elif files: sbref_file = files[0].filename sbbase = os.path.basename(sbref_file) if len(files) > 1: LOGGER.warning( "Multiple single-band reference files found for {}; using " "{}".format(refbase, sbbase)) else: LOGGER.log(25, "Using single-band reference file {}".format(sbbase)) else: LOGGER.log(25, "No single-band-reference found for {}".format(refbase)) metadata = layout.get_metadata(ref_file) # Find fieldmaps. Options: (phase1|phase2|phasediff|epi|fieldmap|syn) fmaps = [] if 'fieldmaps' not in ignore: fmaps = layout.get_fieldmap(ref_file, return_list=True) for fmap in fmaps: fmap['metadata'] = layout.get_metadata(fmap[fmap['type']]) # Run SyN if forced or in the absence of fieldmap correction if force_syn or (use_syn and not fmaps): fmaps.append({'type': 'syn'}) # Short circuits: (True and True and (False or 'TooShort')) == 'TooShort' run_stc = ("SliceTiming" in metadata and 'slicetiming' not in ignore and (_get_series_len(ref_file) > 4 or "TooShort")) # Check if MEEPI for T2* coregistration target if t2s_coreg and not multiecho: LOGGER.warning("No multiecho BOLD images found for T2* coregistration. " "Using standard EPI-T1 coregistration.") t2s_coreg = False # By default, force-bbr for t2s_coreg unless user specifies otherwise if t2s_coreg and use_bbr is None: use_bbr = True # Build workflow workflow = Workflow(name=wf_name) workflow.__desc__ = """ Functional data preprocessing : For each of the {num_bold} BOLD runs found per subject (across all tasks and sessions), the following preprocessing was performed. """.format(num_bold=num_bold) workflow.__postdesc__ = """\ All resamplings can be performed with *a single interpolation step* by composing all the pertinent transformations (i.e. head-motion transform matrices, susceptibility distortion correction when available, and co-registrations to anatomical and template spaces). Gridded (volumetric) resamplings were performed using `antsApplyTransforms` (ANTs), configured with Lanczos interpolation to minimize the smoothing effects of other kernels [@lanczos]. Non-gridded (surface) resamplings were performed using `mri_vol2surf` (FreeSurfer). """ inputnode = pe.Node(niu.IdentityInterface( fields=['bold_file', 'sbref_file', 'subjects_dir', 'subject_id', 't1_preproc', 't1_brain', 't1_mask', 't1_seg', 't1_tpms', 't1_aseg', 't1_aparc', 't1_2_mni_forward_transform', 't1_2_mni_reverse_transform', 't1_2_fsnative_forward_transform', 't1_2_fsnative_reverse_transform']), name='inputnode') inputnode.inputs.bold_file = bold_file if sbref_file is not None: inputnode.inputs.sbref_file = sbref_file outputnode = pe.Node(niu.IdentityInterface( fields=['bold_t1', 'bold_t1_ref', 'bold_mask_t1', 'bold_aseg_t1', 'bold_aparc_t1', 'bold_mni', 'bold_mni_ref' 'bold_mask_mni', 'bold_aseg_mni', 'bold_aparc_mni', 'bold_cifti', 'cifti_variant', 'cifti_variant_key', 'confounds', 'surfaces', 'aroma_noise_ics', 'melodic_mix', 'nonaggr_denoised_file']), name='outputnode') # BOLD buffer: an identity used as a pointer to either the original BOLD # or the STC'ed one for further use. boldbuffer = pe.Node(niu.IdentityInterface(fields=['bold_file']), name='boldbuffer') summary = pe.Node( FunctionalSummary(output_spaces=output_spaces, slice_timing=run_stc, registration='FreeSurfer' if freesurfer else 'FSL', registration_dof=bold2t1w_dof, pe_direction=metadata.get("PhaseEncodingDirection")), name='summary', mem_gb=DEFAULT_MEMORY_MIN_GB, run_without_submitting=True) func_derivatives_wf = init_func_derivatives_wf(output_dir=output_dir, output_spaces=output_spaces, template=template, freesurfer=freesurfer, use_aroma=use_aroma, cifti_output=cifti_output) workflow.connect([ (outputnode, func_derivatives_wf, [ ('bold_t1', 'inputnode.bold_t1'), ('bold_t1_ref', 'inputnode.bold_t1_ref'), ('bold_aseg_t1', 'inputnode.bold_aseg_t1'), ('bold_aparc_t1', 'inputnode.bold_aparc_t1'), ('bold_mask_t1', 'inputnode.bold_mask_t1'), ('bold_mni', 'inputnode.bold_mni'), ('bold_mni_ref', 'inputnode.bold_mni_ref'), ('bold_aseg_mni', 'inputnode.bold_aseg_mni'), ('bold_aparc_mni', 'inputnode.bold_aparc_mni'), ('bold_mask_mni', 'inputnode.bold_mask_mni'), ('confounds', 'inputnode.confounds'), ('surfaces', 'inputnode.surfaces'), ('aroma_noise_ics', 'inputnode.aroma_noise_ics'), ('melodic_mix', 'inputnode.melodic_mix'), ('nonaggr_denoised_file', 'inputnode.nonaggr_denoised_file'), ('bold_cifti', 'inputnode.bold_cifti'), ('cifti_variant', 'inputnode.cifti_variant'), ('cifti_variant_key', 'inputnode.cifti_variant_key') ]), ]) # Generate a tentative boldref bold_reference_wf = init_bold_reference_wf(omp_nthreads=omp_nthreads) # Top-level BOLD splitter bold_split = pe.Node(FSLSplit(dimension='t'), name='bold_split', mem_gb=mem_gb['filesize'] * 3) # HMC on the BOLD bold_hmc_wf = init_bold_hmc_wf(name='bold_hmc_wf', mem_gb=mem_gb['filesize'], omp_nthreads=omp_nthreads) # calculate BOLD registration to T1w bold_reg_wf = init_bold_reg_wf(name='bold_reg_wf', freesurfer=freesurfer, use_bbr=use_bbr, bold2t1w_dof=bold2t1w_dof, mem_gb=mem_gb['resampled'], omp_nthreads=omp_nthreads, use_compression=False) # apply BOLD registration to T1w bold_t1_trans_wf = init_bold_t1_trans_wf(name='bold_t1_trans_wf', freesurfer=freesurfer, use_fieldwarp=(fmaps is not None or use_syn), multiecho=multiecho, mem_gb=mem_gb['resampled'], omp_nthreads=omp_nthreads, use_compression=False) # get confounds bold_confounds_wf = init_bold_confs_wf( mem_gb=mem_gb['largemem'], metadata=metadata, name='bold_confounds_wf') bold_confounds_wf.get_node('inputnode').inputs.t1_transform_flags = [False] # Apply transforms in 1 shot # Only use uncompressed output if AROMA is to be run bold_bold_trans_wf = init_bold_preproc_trans_wf( mem_gb=mem_gb['resampled'], omp_nthreads=omp_nthreads, use_compression=not low_mem, use_fieldwarp=(fmaps is not None or use_syn), name='bold_bold_trans_wf' ) bold_bold_trans_wf.inputs.inputnode.name_source = ref_file # SLICE-TIME CORRECTION (or bypass) ############################################# if run_stc is True: # bool('TooShort') == True, so check True explicitly bold_stc_wf = init_bold_stc_wf(name='bold_stc_wf', metadata=metadata) workflow.connect([ (bold_reference_wf, bold_stc_wf, [ ('outputnode.skip_vols', 'inputnode.skip_vols')]), (bold_stc_wf, boldbuffer, [('outputnode.stc_file', 'bold_file')]), ]) if not multiecho: workflow.connect([ (bold_reference_wf, bold_stc_wf, [ ('outputnode.bold_file', 'inputnode.bold_file')])]) else: # for meepi, iterate through stc_wf for all workflows meepi_echos = boldbuffer.clone(name='meepi_echos') meepi_echos.iterables = ('bold_file', bold_file) workflow.connect([ (meepi_echos, bold_stc_wf, [('bold_file', 'inputnode.bold_file')])]) elif not multiecho: # STC is too short or False # bypass STC from original BOLD to the splitter through boldbuffer workflow.connect([ (bold_reference_wf, boldbuffer, [('outputnode.bold_file', 'bold_file')])]) else: # for meepi, iterate over all meepi echos to boldbuffer boldbuffer.iterables = ('bold_file', bold_file) # SDC (SUSCEPTIBILITY DISTORTION CORRECTION) or bypass ########################## bold_sdc_wf = init_sdc_wf( fmaps, metadata, omp_nthreads=omp_nthreads, debug=debug, fmap_demean=fmap_demean, fmap_bspline=fmap_bspline) bold_sdc_wf.inputs.inputnode.template = template if not fmaps: LOGGER.warning('SDC: no fieldmaps found or they were ignored (%s).', ref_file) elif fmaps[0]['type'] == 'syn': LOGGER.warning( 'SDC: no fieldmaps found or they were ignored. ' 'Using EXPERIMENTAL "fieldmap-less SyN" correction ' 'for dataset %s.', ref_file) else: LOGGER.log(25, 'SDC: fieldmap estimation of type "%s" intended for %s found.', fmaps[0]['type'], ref_file) # MULTI-ECHO EPI DATA ############################################# if multiecho: from .util import init_skullstrip_bold_wf skullstrip_bold_wf = init_skullstrip_bold_wf(name='skullstrip_bold_wf') inputnode.inputs.bold_file = ref_file # Replace reference w first echo join_echos = pe.JoinNode(niu.IdentityInterface(fields=['bold_files']), joinsource=('meepi_echos' if run_stc is True else 'boldbuffer'), joinfield=['bold_files'], name='join_echos') # create optimal combination, adaptive T2* map bold_t2s_wf = init_bold_t2s_wf(echo_times=tes, mem_gb=mem_gb['resampled'], omp_nthreads=omp_nthreads, t2s_coreg=t2s_coreg, name='bold_t2smap_wf') workflow.connect([ (skullstrip_bold_wf, join_echos, [ ('outputnode.skull_stripped_file', 'bold_files')]), (join_echos, bold_t2s_wf, [ ('bold_files', 'inputnode.bold_file')]), ]) # MAIN WORKFLOW STRUCTURE ####################################################### workflow.connect([ # Generate early reference (inputnode, bold_reference_wf, [('bold_file', 'inputnode.bold_file'), ('sbref_file', 'inputnode.sbref_file')]), # BOLD buffer has slice-time corrected if it was run, original otherwise (boldbuffer, bold_split, [('bold_file', 'in_file')]), # HMC (bold_reference_wf, bold_hmc_wf, [ ('outputnode.raw_ref_image', 'inputnode.raw_ref_image'), ('outputnode.bold_file', 'inputnode.bold_file')]), # EPI-T1 registration workflow (inputnode, bold_reg_wf, [ ('t1_brain', 'inputnode.t1_brain'), ('t1_seg', 'inputnode.t1_seg'), # Undefined if --no-freesurfer, but this is safe ('subjects_dir', 'inputnode.subjects_dir'), ('subject_id', 'inputnode.subject_id'), ('t1_2_fsnative_reverse_transform', 'inputnode.t1_2_fsnative_reverse_transform')]), (inputnode, bold_t1_trans_wf, [ ('bold_file', 'inputnode.name_source'), ('t1_brain', 'inputnode.t1_brain'), ('t1_mask', 'inputnode.t1_mask'), ('t1_aseg', 'inputnode.t1_aseg'), ('t1_aparc', 'inputnode.t1_aparc')]), # unused if multiecho, but this is safe (bold_hmc_wf, bold_t1_trans_wf, [('outputnode.xforms', 'inputnode.hmc_xforms')]), (bold_reg_wf, bold_t1_trans_wf, [ ('outputnode.itk_bold_to_t1', 'inputnode.itk_bold_to_t1')]), (bold_t1_trans_wf, outputnode, [('outputnode.bold_t1', 'bold_t1'), ('outputnode.bold_t1_ref', 'bold_t1_ref'), ('outputnode.bold_aseg_t1', 'bold_aseg_t1'), ('outputnode.bold_aparc_t1', 'bold_aparc_t1')]), (bold_reg_wf, summary, [('outputnode.fallback', 'fallback')]), # SDC (or pass-through workflow) (inputnode, bold_sdc_wf, [ ('t1_brain', 'inputnode.t1_brain'), ('t1_2_mni_reverse_transform', 'inputnode.t1_2_mni_reverse_transform')]), (bold_reference_wf, bold_sdc_wf, [ ('outputnode.ref_image', 'inputnode.bold_ref'), ('outputnode.ref_image_brain', 'inputnode.bold_ref_brain'), ('outputnode.bold_mask', 'inputnode.bold_mask')]), # For t2s_coreg, replace EPI-to-T1w registration inputs (bold_sdc_wf if not t2s_coreg else bold_t2s_wf, bold_reg_wf, [ ('outputnode.bold_ref_brain', 'inputnode.ref_bold_brain')]), (bold_sdc_wf if not t2s_coreg else bold_t2s_wf, bold_t1_trans_wf, [ ('outputnode.bold_ref_brain', 'inputnode.ref_bold_brain'), ('outputnode.bold_mask', 'inputnode.ref_bold_mask')]), (bold_sdc_wf, bold_t1_trans_wf, [ ('outputnode.out_warp', 'inputnode.fieldwarp')]), (bold_sdc_wf, bold_bold_trans_wf, [ ('outputnode.out_warp', 'inputnode.fieldwarp'), ('outputnode.bold_mask', 'inputnode.bold_mask')]), (bold_sdc_wf, summary, [('outputnode.method', 'distortion_correction')]), # Connect bold_confounds_wf (inputnode, bold_confounds_wf, [('t1_tpms', 'inputnode.t1_tpms'), ('t1_mask', 'inputnode.t1_mask')]), (bold_hmc_wf, bold_confounds_wf, [ ('outputnode.movpar_file', 'inputnode.movpar_file')]), (bold_reg_wf, bold_confounds_wf, [ ('outputnode.itk_t1_to_bold', 'inputnode.t1_bold_xform')]), (bold_reference_wf, bold_confounds_wf, [ ('outputnode.skip_vols', 'inputnode.skip_vols')]), (bold_confounds_wf, outputnode, [ ('outputnode.confounds_file', 'confounds'), ]), # Connect bold_bold_trans_wf (bold_split, bold_bold_trans_wf, [ ('out_files', 'inputnode.bold_file')]), (bold_hmc_wf, bold_bold_trans_wf, [ ('outputnode.xforms', 'inputnode.hmc_xforms')]), # Summary (outputnode, summary, [('confounds', 'confounds_file')]), ]) # for standard EPI data, pass along correct file if not multiecho: workflow.connect([ (inputnode, func_derivatives_wf, [ ('bold_file', 'inputnode.source_file')]), (bold_bold_trans_wf, bold_confounds_wf, [ ('outputnode.bold', 'inputnode.bold'), ('outputnode.bold_mask', 'inputnode.bold_mask')]), (bold_split, bold_t1_trans_wf, [ ('out_files', 'inputnode.bold_split')]), ]) else: # for meepi, create and use optimal combination workflow.connect([ # update name source for optimal combination (inputnode, func_derivatives_wf, [ (('bold_file', combine_meepi_source), 'inputnode.source_file')]), (bold_bold_trans_wf, skullstrip_bold_wf, [ ('outputnode.bold', 'inputnode.in_file')]), (bold_t2s_wf, bold_confounds_wf, [ ('outputnode.bold', 'inputnode.bold'), ('outputnode.bold_mask', 'inputnode.bold_mask')]), (bold_t2s_wf, bold_t1_trans_wf, [ ('outputnode.bold', 'inputnode.bold_split')]), ]) if fmaps: from ..fieldmap.unwarp import init_fmap_unwarp_report_wf sdc_type = fmaps[0]['type'] # Report on BOLD correction fmap_unwarp_report_wf = init_fmap_unwarp_report_wf( suffix='sdc_%s' % sdc_type) workflow.connect([ (inputnode, fmap_unwarp_report_wf, [ ('t1_seg', 'inputnode.in_seg')]), (bold_reference_wf, fmap_unwarp_report_wf, [ ('outputnode.ref_image', 'inputnode.in_pre')]), (bold_reg_wf, fmap_unwarp_report_wf, [ ('outputnode.itk_t1_to_bold', 'inputnode.in_xfm')]), (bold_sdc_wf, fmap_unwarp_report_wf, [ ('outputnode.bold_ref', 'inputnode.in_post')]), ]) if force_syn and sdc_type != 'syn': syn_unwarp_report_wf = init_fmap_unwarp_report_wf( suffix='forcedsyn', name='syn_unwarp_report_wf') workflow.connect([ (inputnode, syn_unwarp_report_wf, [ ('t1_seg', 'inputnode.in_seg')]), (bold_reference_wf, syn_unwarp_report_wf, [ ('outputnode.ref_image', 'inputnode.in_pre')]), (bold_reg_wf, syn_unwarp_report_wf, [ ('outputnode.itk_t1_to_bold', 'inputnode.in_xfm')]), (bold_sdc_wf, syn_unwarp_report_wf, [ ('outputnode.syn_bold_ref', 'inputnode.in_post')]), ]) # Map final BOLD mask into T1w space (if required) if 'T1w' in output_spaces: from niworkflows.interfaces.fixes import ( FixHeaderApplyTransforms as ApplyTransforms ) boldmask_to_t1w = pe.Node( ApplyTransforms(interpolation='MultiLabel', float=True), name='boldmask_to_t1w', mem_gb=0.1 ) workflow.connect([ (bold_reg_wf, boldmask_to_t1w, [ ('outputnode.itk_bold_to_t1', 'transforms')]), (bold_t1_trans_wf, boldmask_to_t1w, [ ('outputnode.bold_mask_t1', 'reference_image')]), (bold_bold_trans_wf if not multiecho else bold_t2s_wf, boldmask_to_t1w, [ ('outputnode.bold_mask', 'input_image')]), (boldmask_to_t1w, outputnode, [ ('output_image', 'bold_mask_t1')]), ]) if 'template' in output_spaces: # Apply transforms in 1 shot # Only use uncompressed output if AROMA is to be run bold_mni_trans_wf = init_bold_mni_trans_wf( template=template, freesurfer=freesurfer, mem_gb=mem_gb['resampled'], omp_nthreads=omp_nthreads, template_out_grid=template_out_grid, use_compression=not low_mem, use_fieldwarp=fmaps is not None, name='bold_mni_trans_wf' ) carpetplot_wf = init_carpetplot_wf( mem_gb=mem_gb['resampled'], metadata=metadata, name='carpetplot_wf') workflow.connect([ (inputnode, bold_mni_trans_wf, [ ('bold_file', 'inputnode.name_source'), ('t1_2_mni_forward_transform', 'inputnode.t1_2_mni_forward_transform'), ('t1_aseg', 'inputnode.bold_aseg'), ('t1_aparc', 'inputnode.bold_aparc')]), (bold_hmc_wf, bold_mni_trans_wf, [ ('outputnode.xforms', 'inputnode.hmc_xforms')]), (bold_reg_wf, bold_mni_trans_wf, [ ('outputnode.itk_bold_to_t1', 'inputnode.itk_bold_to_t1')]), (bold_bold_trans_wf if not multiecho else bold_t2s_wf, bold_mni_trans_wf, [ ('outputnode.bold_mask', 'inputnode.bold_mask')]), (bold_sdc_wf, bold_mni_trans_wf, [ ('outputnode.out_warp', 'inputnode.fieldwarp')]), (bold_mni_trans_wf, outputnode, [('outputnode.bold_mni', 'bold_mni'), ('outputnode.bold_mni_ref', 'bold_mni_ref'), ('outputnode.bold_mask_mni', 'bold_mask_mni'), ('outputnode.bold_aseg_mni', 'bold_aseg_mni'), ('outputnode.bold_aparc_mni', 'bold_aparc_mni')]), (inputnode, carpetplot_wf, [ ('t1_2_mni_reverse_transform', 'inputnode.t1_2_mni_reverse_transform')]), (bold_bold_trans_wf if not multiecho else bold_t2s_wf, carpetplot_wf, [ ('outputnode.bold', 'inputnode.bold'), ('outputnode.bold_mask', 'inputnode.bold_mask')]), (bold_reg_wf, carpetplot_wf, [ ('outputnode.itk_t1_to_bold', 'inputnode.t1_bold_xform')]), (bold_confounds_wf, carpetplot_wf, [ ('outputnode.confounds_file', 'inputnode.confounds_file')]), ]) if not multiecho: workflow.connect([ (bold_split, bold_mni_trans_wf, [ ('out_files', 'inputnode.bold_split')]) ]) else: split_opt_comb = bold_split.clone(name='split_opt_comb') workflow.connect([ (bold_t2s_wf, split_opt_comb, [ ('outputnode.bold', 'in_file')]), (split_opt_comb, bold_mni_trans_wf, [ ('out_files', 'inputnode.bold_split') ]) ]) if use_aroma: # ICA-AROMA workflow # Internally resamples to MNI152 Linear (2006) from .confounds import init_ica_aroma_wf from niworkflows.interfaces.utils import JoinTSVColumns ica_aroma_wf = init_ica_aroma_wf( template=template, metadata=metadata, mem_gb=mem_gb['resampled'], omp_nthreads=omp_nthreads, use_fieldwarp=fmaps is not None, ignore_aroma_err=ignore_aroma_err, aroma_melodic_dim=aroma_melodic_dim, name='ica_aroma_wf') join = pe.Node(JoinTSVColumns(), name='aroma_confounds') workflow.disconnect([ (bold_confounds_wf, outputnode, [ ('outputnode.confounds_file', 'confounds'), ]), ]) workflow.connect([ (inputnode, ica_aroma_wf, [ ('bold_file', 'inputnode.name_source'), ('t1_2_mni_forward_transform', 'inputnode.t1_2_mni_forward_transform')]), (bold_split, ica_aroma_wf, [ ('out_files', 'inputnode.bold_split')]), (bold_hmc_wf, ica_aroma_wf, [ ('outputnode.movpar_file', 'inputnode.movpar_file'), ('outputnode.xforms', 'inputnode.hmc_xforms')]), (bold_reg_wf, ica_aroma_wf, [ ('outputnode.itk_bold_to_t1', 'inputnode.itk_bold_to_t1')]), (bold_bold_trans_wf if not multiecho else bold_t2s_wf, ica_aroma_wf, [ ('outputnode.bold_mask', 'inputnode.bold_mask')]), (bold_sdc_wf, ica_aroma_wf, [ ('outputnode.out_warp', 'inputnode.fieldwarp')]), (bold_reference_wf, ica_aroma_wf, [ ('outputnode.skip_vols', 'inputnode.skip_vols')]), (bold_confounds_wf, join, [ ('outputnode.confounds_file', 'in_file')]), (ica_aroma_wf, join, [('outputnode.aroma_confounds', 'join_file')]), (ica_aroma_wf, outputnode, [('outputnode.aroma_noise_ics', 'aroma_noise_ics'), ('outputnode.melodic_mix', 'melodic_mix'), ('outputnode.nonaggr_denoised_file', 'nonaggr_denoised_file')]), (join, outputnode, [('out_file', 'confounds')]), ]) # SURFACES ################################################################################## surface_spaces = [space for space in output_spaces if space.startswith('fs')] if freesurfer and surface_spaces: LOGGER.log(25, 'Creating BOLD surface-sampling workflow.') bold_surf_wf = init_bold_surf_wf(mem_gb=mem_gb['resampled'], output_spaces=surface_spaces, medial_surface_nan=medial_surface_nan, name='bold_surf_wf') workflow.connect([ (inputnode, bold_surf_wf, [ ('t1_preproc', 'inputnode.t1_preproc'), ('subjects_dir', 'inputnode.subjects_dir'), ('subject_id', 'inputnode.subject_id'), ('t1_2_fsnative_forward_transform', 'inputnode.t1_2_fsnative_forward_transform')]), (bold_t1_trans_wf, bold_surf_wf, [('outputnode.bold_t1', 'inputnode.source_file')]), (bold_surf_wf, outputnode, [('outputnode.surfaces', 'surfaces')]), ]) # CIFTI output if cifti_output and surface_spaces: bold_surf_wf.__desc__ += """\ *Grayordinates* files [@hcppipelines], which combine surface-sampled data and volume-sampled data, were also generated. """ gen_cifti = pe.MapNode(GenerateCifti(), iterfield=["surface_target", "gifti_files"], name="gen_cifti") gen_cifti.inputs.TR = metadata.get("RepetitionTime") gen_cifti.inputs.surface_target = [s for s in surface_spaces if s.startswith('fsaverage')] workflow.connect([ (bold_surf_wf, gen_cifti, [ ('outputnode.surfaces', 'gifti_files')]), (inputnode, gen_cifti, [('subjects_dir', 'subjects_dir')]), (bold_mni_trans_wf, gen_cifti, [('outputnode.bold_mni', 'bold_file')]), (gen_cifti, outputnode, [('out_file', 'bold_cifti'), ('variant', 'cifti_variant'), ('variant_key', 'cifti_variant_key')]), ]) # REPORTING ############################################################ ds_report_summary = pe.Node( DerivativesDataSink(suffix='summary'), name='ds_report_summary', run_without_submitting=True, mem_gb=DEFAULT_MEMORY_MIN_GB) ds_report_validation = pe.Node( DerivativesDataSink(base_directory=reportlets_dir, suffix='validation'), name='ds_report_validation', run_without_submitting=True, mem_gb=DEFAULT_MEMORY_MIN_GB) workflow.connect([ (summary, ds_report_summary, [('out_report', 'in_file')]), (bold_reference_wf, ds_report_validation, [ ('outputnode.validation_report', 'in_file')]), ]) # Fill-in datasinks of reportlets seen so far for node in workflow.list_node_names(): if node.split('.')[-1].startswith('ds_report'): workflow.get_node(node).inputs.base_directory = reportlets_dir workflow.get_node(node).inputs.source_file = ref_file return workflow
def init_syn_sdc_wf(omp_nthreads, epi_pe=None, atlas_threshold=3, name='syn_sdc_wf'): """ Build the *fieldmap-less* susceptibility-distortion estimation workflow. This workflow takes a skull-stripped T1w image and reference BOLD image and estimates a susceptibility distortion correction warp, using ANTs symmetric normalization (SyN) and the average fieldmap atlas described in [Treiber2016]_. SyN deformation is restricted to the phase-encoding (PE) direction. If no PE direction is specified, anterior-posterior PE is assumed. SyN deformation is also restricted to regions that are expected to have a >3mm (approximately 1 voxel) warp, based on the fieldmap atlas. This technique is a variation on those developed in [Huntenburg2014]_ and [Wang2017]_. Workflow Graph .. workflow :: :graph2use: orig :simple_form: yes from sdcflows.workflows.syn import init_syn_sdc_wf wf = init_syn_sdc_wf( epi_pe='j', omp_nthreads=8) Inputs ------ in_reference reference image in_reference_brain skull-stripped reference image t1w_brain skull-stripped, bias-corrected structural image std2anat_xfm inverse registration transform of T1w image to MNI template Outputs ------- out_reference the ``in_reference`` image after unwarping out_reference_brain the ``in_reference_brain`` image after unwarping out_warp the corresponding :abbr:`DFM (displacements field map)` compatible with ANTs out_mask mask of the unwarped input file References ---------- .. [Treiber2016] Treiber, J. M. et al. (2016) Characterization and Correction of Geometric Distortions in 814 Diffusion Weighted Images, PLoS ONE 11(3): e0152472. doi:`10.1371/journal.pone.0152472 <https://doi.org/10.1371/journal.pone.0152472>`_. .. [Wang2017] Wang S, et al. (2017) Evaluation of Field Map and Nonlinear Registration Methods for Correction of Susceptibility Artifacts in Diffusion MRI. Front. Neuroinform. 11:17. doi:`10.3389/fninf.2017.00017 <https://doi.org/10.3389/fninf.2017.00017>`_. .. [Huntenburg2014] Huntenburg, J. M. (2014) Evaluating Nonlinear Coregistration of BOLD EPI and T1w Images. Berlin: Master Thesis, Freie Universität. `PDF <http://pubman.mpdl.mpg.de/pubman/item/escidoc:2327525:5/component/escidoc:2327523/master_thesis_huntenburg_4686947.pdf>`_. """ if epi_pe is None or epi_pe[0] not in ['i', 'j']: LOGGER.warning('Incorrect phase-encoding direction, assuming PA (posterior-to-anterior).') epi_pe = 'j' workflow = Workflow(name=name) workflow.__desc__ = """\ A deformation field to correct for susceptibility distortions was estimated based on *fMRIPrep*'s *fieldmap-less* approach. The deformation field is that resulting from co-registering the BOLD reference to the same-subject T1w-reference with its intensity inverted [@fieldmapless1; @fieldmapless2]. Registration is performed with `antsRegistration` (ANTs {ants_ver}), and the process regularized by constraining deformation to be nonzero only along the phase-encoding direction, and modulated with an average fieldmap template [@fieldmapless3]. """.format(ants_ver=Registration().version or '<ver>') inputnode = pe.Node( niu.IdentityInterface(['in_reference', 'in_reference_brain', 't1w_brain', 'std2anat_xfm']), name='inputnode') outputnode = pe.Node( niu.IdentityInterface(['out_reference', 'out_reference_brain', 'out_mask', 'out_warp']), name='outputnode') # Collect predefined data # Atlas image and registration affine atlas_img = resource_filename('sdcflows', 'data/fmap_atlas.nii.gz') # Registration specifications affine_transform = resource_filename('sdcflows', 'data/affine.json') syn_transform = resource_filename('sdcflows', 'data/susceptibility_syn.json') invert_t1w = pe.Node(Rescale(invert=True), name='invert_t1w', mem_gb=0.3) ref_2_t1 = pe.Node(Registration(from_file=affine_transform), name='ref_2_t1', n_procs=omp_nthreads) t1_2_ref = pe.Node(ApplyTransforms(invert_transform_flags=[True]), name='t1_2_ref', n_procs=omp_nthreads) # 1) BOLD -> T1; 2) MNI -> T1; 3) ATLAS -> MNI transform_list = pe.Node(niu.Merge(3), name='transform_list', mem_gb=DEFAULT_MEMORY_MIN_GB) transform_list.inputs.in3 = resource_filename( 'sdcflows', 'data/fmap_atlas_2_MNI152NLin2009cAsym_affine.mat') # Inverting (1), then applying in reverse order: # # ATLAS -> MNI -> T1 -> BOLD atlas_2_ref = pe.Node( ApplyTransforms(invert_transform_flags=[True, False, False]), name='atlas_2_ref', n_procs=omp_nthreads, mem_gb=0.3) atlas_2_ref.inputs.input_image = atlas_img threshold_atlas = pe.Node( fsl.maths.MathsCommand(args='-thr {:.8g} -bin'.format(atlas_threshold), output_datatype='char'), name='threshold_atlas', mem_gb=0.3) fixed_image_masks = pe.Node(niu.Merge(2), name='fixed_image_masks', mem_gb=DEFAULT_MEMORY_MIN_GB) fixed_image_masks.inputs.in1 = 'NULL' restrict = [[int(epi_pe[0] == 'i'), int(epi_pe[0] == 'j'), 0]] * 2 syn = pe.Node( Registration(from_file=syn_transform, restrict_deformation=restrict), name='syn', n_procs=omp_nthreads) unwarp_ref = pe.Node(ApplyTransforms( dimension=3, float=True, interpolation='LanczosWindowedSinc'), name='unwarp_ref') skullstrip_bold_wf = init_skullstrip_bold_wf() workflow.connect([ (inputnode, invert_t1w, [('t1w_brain', 'in_file'), ('in_reference', 'ref_file')]), (inputnode, ref_2_t1, [('in_reference_brain', 'moving_image')]), (invert_t1w, ref_2_t1, [('out_file', 'fixed_image')]), (inputnode, t1_2_ref, [('in_reference', 'reference_image')]), (invert_t1w, t1_2_ref, [('out_file', 'input_image')]), (ref_2_t1, t1_2_ref, [('forward_transforms', 'transforms')]), (ref_2_t1, transform_list, [('forward_transforms', 'in1')]), (inputnode, transform_list, [ ('std2anat_xfm', 'in2')]), (inputnode, atlas_2_ref, [('in_reference', 'reference_image')]), (transform_list, atlas_2_ref, [('out', 'transforms')]), (atlas_2_ref, threshold_atlas, [('output_image', 'in_file')]), (threshold_atlas, fixed_image_masks, [('out_file', 'in2')]), (inputnode, syn, [('in_reference_brain', 'moving_image')]), (t1_2_ref, syn, [('output_image', 'fixed_image')]), (fixed_image_masks, syn, [('out', 'fixed_image_masks')]), (syn, outputnode, [('forward_transforms', 'out_warp')]), (syn, unwarp_ref, [('forward_transforms', 'transforms')]), (inputnode, unwarp_ref, [('in_reference', 'reference_image'), ('in_reference', 'input_image')]), (unwarp_ref, skullstrip_bold_wf, [ ('output_image', 'inputnode.in_file')]), (unwarp_ref, outputnode, [('output_image', 'out_reference')]), (skullstrip_bold_wf, outputnode, [ ('outputnode.skull_stripped_file', 'out_reference_brain'), ('outputnode.mask_file', 'out_mask')]), ]) return workflow
def init_bold_hmc_wf(mem_gb, omp_nthreads, name='bold_hmc_wf'): """ This workflow estimates the motion parameters to perform :abbr:`HMC (head motion correction)` over the input :abbr:`BOLD (blood-oxygen-level dependent)` image. .. workflow:: :graph2use: orig :simple_form: yes from fmriprep.workflows.bold import init_bold_hmc_wf wf = init_bold_hmc_wf( mem_gb=3, omp_nthreads=1) **Parameters** mem_gb : float Size of BOLD file in GB omp_nthreads : int Maximum number of threads an individual process may use name : str Name of workflow (default: ``bold_hmc_wf``) **Inputs** bold_file BOLD series NIfTI file raw_ref_image Reference image to which BOLD series is motion corrected **Outputs** xforms ITKTransform file aligning each volume to ``ref_image`` movpar_file MCFLIRT motion parameters, normalized to SPM format (X, Y, Z, Rx, Ry, Rz) """ workflow = Workflow(name=name) workflow.__desc__ = """\ Head-motion parameters with respect to the BOLD reference (transformation matrices, and six corresponding rotation and translation parameters) are estimated before any spatiotemporal filtering using `mcflirt` [FSL {fsl_ver}, @mcflirt]. """.format(fsl_ver=fsl.Info().version() or '<ver>') inputnode = pe.Node(niu.IdentityInterface(fields=['bold_file', 'raw_ref_image']), name='inputnode') outputnode = pe.Node( niu.IdentityInterface(fields=['xforms', 'movpar_file']), name='outputnode') # Head motion correction (hmc) mcflirt = pe.Node(fsl.MCFLIRT(save_mats=True, save_plots=True), name='mcflirt', mem_gb=mem_gb * 3) fsl2itk = pe.Node(MCFLIRT2ITK(), name='fsl2itk', mem_gb=0.05, n_procs=omp_nthreads) normalize_motion = pe.Node(NormalizeMotionParams(format='FSL'), name="normalize_motion", mem_gb=DEFAULT_MEMORY_MIN_GB) workflow.connect([ (inputnode, mcflirt, [('raw_ref_image', 'ref_file'), ('bold_file', 'in_file')]), (inputnode, fsl2itk, [('raw_ref_image', 'in_source'), ('raw_ref_image', 'in_reference')]), (mcflirt, fsl2itk, [('mat_file', 'in_files')]), (mcflirt, normalize_motion, [('par_file', 'in_file')]), (fsl2itk, outputnode, [('out_file', 'xforms')]), (normalize_motion, outputnode, [('out_file', 'movpar_file')]), ]) return workflow
def init_bold_surf_wf(mem_gb, output_spaces, medial_surface_nan, name='bold_surf_wf'): """ This workflow samples functional images to FreeSurfer surfaces For each vertex, the cortical ribbon is sampled at six points (spaced 20% of thickness apart) and averaged. Outputs are in GIFTI format. .. workflow:: :graph2use: colored :simple_form: yes from fmriprep.workflows.bold import init_bold_surf_wf wf = init_bold_surf_wf(mem_gb=0.1, output_spaces=['T1w', 'fsnative', 'template', 'fsaverage5'], medial_surface_nan=False) **Parameters** output_spaces : list List of output spaces functional images are to be resampled to Target spaces beginning with ``fs`` will be selected for resampling, such as ``fsaverage`` or related template spaces If the list contains ``fsnative``, images will be resampled to the individual subject's native surface medial_surface_nan : bool Replace medial wall values with NaNs on functional GIFTI files **Inputs** source_file Motion-corrected BOLD series in T1 space t1_preproc Bias-corrected structural template image subjects_dir FreeSurfer SUBJECTS_DIR subject_id FreeSurfer subject ID t1_2_fsnative_forward_transform LTA-style affine matrix translating from T1w to FreeSurfer-conformed subject space **Outputs** surfaces BOLD series, resampled to FreeSurfer surfaces """ # Ensure volumetric spaces do not sneak into this workflow spaces = [space for space in output_spaces if space.startswith('fs')] workflow = Workflow(name=name) if spaces: workflow.__desc__ = """\ The BOLD time-series, were resampled to surfaces on the following spaces: {out_spaces}. """.format(out_spaces=', '.join(['*%s*' % s for s in spaces])) inputnode = pe.Node(niu.IdentityInterface(fields=[ 'source_file', 't1_preproc', 'subject_id', 'subjects_dir', 't1_2_fsnative_forward_transform' ]), name='inputnode') outputnode = pe.Node(niu.IdentityInterface(fields=['surfaces']), name='outputnode') def select_target(subject_id, space): """ Given a source subject ID and a target space, get the target subject ID """ return subject_id if space == 'fsnative' else space targets = pe.MapNode(niu.Function(function=select_target), iterfield=['space'], name='targets', mem_gb=DEFAULT_MEMORY_MIN_GB) targets.inputs.space = spaces # Rename the source file to the output space to simplify naming later rename_src = pe.MapNode(niu.Rename(format_string='%(subject)s', keep_ext=True), iterfield='subject', name='rename_src', run_without_submitting=True, mem_gb=DEFAULT_MEMORY_MIN_GB) rename_src.inputs.subject = spaces resampling_xfm = pe.Node(LTAConvert(in_lta='identity.nofile', out_lta=True), name='resampling_xfm') set_xfm_source = pe.Node(ConcatenateLTA(out_type='RAS2RAS'), name='set_xfm_source') sampler = pe.MapNode(fs.SampleToSurface(sampling_method='average', sampling_range=(0, 1, 0.2), sampling_units='frac', interp_method='trilinear', cortex_mask=True, override_reg_subj=True, out_type='gii'), iterfield=['source_file', 'target_subject'], iterables=('hemi', ['lh', 'rh']), name='sampler', mem_gb=mem_gb * 3) medial_nans = pe.MapNode(MedialNaNs(), iterfield=['in_file', 'target_subject'], name='medial_nans', mem_gb=DEFAULT_MEMORY_MIN_GB) merger = pe.JoinNode(niu.Merge(1, ravel_inputs=True), name='merger', joinsource='sampler', joinfield=['in1'], run_without_submitting=True, mem_gb=DEFAULT_MEMORY_MIN_GB) update_metadata = pe.MapNode(GiftiSetAnatomicalStructure(), iterfield='in_file', name='update_metadata', mem_gb=DEFAULT_MEMORY_MIN_GB) workflow.connect([ (inputnode, targets, [('subject_id', 'subject_id')]), (inputnode, rename_src, [('source_file', 'in_file')]), (inputnode, resampling_xfm, [('source_file', 'source_file'), ('t1_preproc', 'target_file')]), (inputnode, set_xfm_source, [('t1_2_fsnative_forward_transform', 'in_lta2')]), (resampling_xfm, set_xfm_source, [('out_lta', 'in_lta1')]), (inputnode, sampler, [('subjects_dir', 'subjects_dir'), ('subject_id', 'subject_id')]), (set_xfm_source, sampler, [('out_file', 'reg_file')]), (targets, sampler, [('out', 'target_subject')]), (rename_src, sampler, [('out_file', 'source_file')]), (merger, update_metadata, [('out', 'in_file')]), (update_metadata, outputnode, [('out_file', 'surfaces')]), ]) if medial_surface_nan: workflow.connect([ (inputnode, medial_nans, [('subjects_dir', 'subjects_dir')]), (sampler, medial_nans, [('out_file', 'in_file')]), (targets, medial_nans, [('out', 'target_subject')]), (medial_nans, merger, [('out_file', 'in1')]), ]) else: workflow.connect(sampler, 'out_file', merger, 'in1') return workflow
def init_bbreg_wf(use_bbr, bold2t1w_dof, bold2t1w_init, omp_nthreads, name='bbreg_wf'): """ Build a workflow to run FreeSurfer's ``bbregister``. This workflow uses FreeSurfer's ``bbregister`` to register a BOLD image to a T1-weighted structural image. It is a counterpart to :py:func:`~fmriprep.workflows.bold.registration.init_fsl_bbr_wf`, which performs the same task using FSL's FLIRT with a BBR cost function. The ``use_bbr`` option permits a high degree of control over registration. If ``False``, standard, affine coregistration will be performed using FreeSurfer's ``mri_coreg`` tool. If ``True``, ``bbregister`` will be seeded with the initial transform found by ``mri_coreg`` (equivalent to running ``bbregister --init-coreg``). If ``None``, after ``bbregister`` is run, the resulting affine transform will be compared to the initial transform found by ``mri_coreg``. Excessive deviation will result in rejecting the BBR refinement and accepting the original, affine registration. Workflow Graph .. workflow :: :graph2use: orig :simple_form: yes from fmriprep.workflows.bold.registration import init_bbreg_wf wf = init_bbreg_wf(use_bbr=True, bold2t1w_dof=9, bold2t1w_init='register', omp_nthreads=1) Parameters ---------- use_bbr : :obj:`bool` or None Enable/disable boundary-based registration refinement. If ``None``, test BBR result for distortion before accepting. bold2t1w_dof : 6, 9 or 12 Degrees-of-freedom for BOLD-T1w registration bold2t1w_init : str, 'header' or 'register' If ``'header'``, use header information for initialization of BOLD and T1 images. If ``'register'``, align volumes by their centers. name : :obj:`str`, optional Workflow name (default: bbreg_wf) Inputs ------ in_file Reference BOLD image to be registered fsnative2t1w_xfm FSL-style affine matrix translating from FreeSurfer T1.mgz to T1w subjects_dir FreeSurfer SUBJECTS_DIR subject_id FreeSurfer subject ID (must have folder in SUBJECTS_DIR) t1w_brain Unused (see :py:func:`~fmriprep.workflows.bold.registration.init_fsl_bbr_wf`) t1w_dseg Unused (see :py:func:`~fmriprep.workflows.bold.registration.init_fsl_bbr_wf`) Outputs ------- itk_bold_to_t1 Affine transform from ``ref_bold_brain`` to T1 space (ITK format) itk_t1_to_bold Affine transform from T1 space to BOLD space (ITK format) out_report Reportlet for assessing registration quality fallback Boolean indicating whether BBR was rejected (mri_coreg registration returned) """ from niworkflows.engine.workflows import LiterateWorkflow as Workflow # See https://github.com/nipreps/fmriprep/issues/768 from niworkflows.interfaces.freesurfer import ( PatchedBBRegisterRPT as BBRegisterRPT, PatchedMRICoregRPT as MRICoregRPT, PatchedLTAConvert as LTAConvert) from niworkflows.interfaces.nitransforms import ConcatenateXFMs workflow = Workflow(name=name) workflow.__desc__ = """\ The BOLD reference was then co-registered to the T1w reference using `bbregister` (FreeSurfer) which implements boundary-based registration [@bbr]. Co-registration was configured with {dof} degrees of freedom{reason}. """.format(dof={ 6: 'six', 9: 'nine', 12: 'twelve' }[bold2t1w_dof], reason='' if bold2t1w_dof == 6 else 'to account for distortions remaining in the BOLD reference') inputnode = pe.Node( niu.IdentityInterface([ 'in_file', 'fsnative2t1w_xfm', 'subjects_dir', 'subject_id', # BBRegister 't1w_dseg', 't1w_brain' ]), # FLIRT BBR name='inputnode') outputnode = pe.Node(niu.IdentityInterface( ['itk_bold_to_t1', 'itk_t1_to_bold', 'out_report', 'fallback']), name='outputnode') if bold2t1w_init not in ("register", "header"): raise ValueError( f"Unknown BOLD-T1w initialization option: {bold2t1w_init}") # For now make BBR unconditional - in the future, we can fall back to identity, # but adding the flexibility without testing seems a bit dangerous if bold2t1w_init == "header": if use_bbr is False: raise ValueError("Cannot disable BBR and use header registration") if use_bbr is None: LOGGER.warning( "Initializing BBR with header; affine fallback disabled") use_bbr = True merge_ltas = pe.Node(niu.Merge(2), name='merge_ltas', run_without_submitting=True) concat_xfm = pe.Node(ConcatenateXFMs(inverse=True), name='concat_xfm') workflow.connect([ # Output ITK transforms (inputnode, merge_ltas, [('fsnative2t1w_xfm', 'in2')]), (merge_ltas, concat_xfm, [('out', 'in_xfms')]), (concat_xfm, outputnode, [('out_xfm', 'itk_bold_to_t1')]), (concat_xfm, outputnode, [('out_inv', 'itk_t1_to_bold')]), ]) # Define both nodes, but only connect conditionally mri_coreg = pe.Node(MRICoregRPT(dof=bold2t1w_dof, sep=[4], ftol=0.0001, linmintol=0.01, generate_report=not use_bbr), name='mri_coreg', n_procs=omp_nthreads, mem_gb=5) bbregister = pe.Node(BBRegisterRPT(dof=bold2t1w_dof, contrast_type='t2', registered_file=True, out_lta_file=True, generate_report=True), name='bbregister', mem_gb=12) # Use mri_coreg if bold2t1w_init == "register": workflow.connect([ (inputnode, mri_coreg, [('subjects_dir', 'subjects_dir'), ('subject_id', 'subject_id'), ('in_file', 'source_file')]), ]) # Short-circuit workflow building, use initial registration if use_bbr is False: workflow.connect([ (mri_coreg, outputnode, [('out_report', 'out_report')]), (mri_coreg, merge_ltas, [('out_lta_file', 'in1')]) ]) outputnode.inputs.fallback = True return workflow # Use bbregister workflow.connect([ (inputnode, bbregister, [('subjects_dir', 'subjects_dir'), ('subject_id', 'subject_id'), ('in_file', 'source_file')]), ]) if bold2t1w_init == "header": bbregister.inputs.init = "header" else: workflow.connect([(mri_coreg, bbregister, [('out_lta_file', 'init_reg_file')])]) # Short-circuit workflow building, use boundary-based registration if use_bbr is True: workflow.connect([(bbregister, outputnode, [('out_report', 'out_report')]), (bbregister, merge_ltas, [('out_lta_file', 'in1')])]) outputnode.inputs.fallback = False return workflow # Only reach this point if bold2t1w_init is "register" and use_bbr is None transforms = pe.Node(niu.Merge(2), run_without_submitting=True, name='transforms') reports = pe.Node(niu.Merge(2), run_without_submitting=True, name='reports') lta_ras2ras = pe.MapNode(LTAConvert(out_lta=True), iterfield=['in_lta'], name='lta_ras2ras', mem_gb=2) compare_transforms = pe.Node(niu.Function(function=compare_xforms), name='compare_transforms') select_transform = pe.Node(niu.Select(), run_without_submitting=True, name='select_transform') select_report = pe.Node(niu.Select(), run_without_submitting=True, name='select_report') workflow.connect([ (bbregister, transforms, [('out_lta_file', 'in1')]), (mri_coreg, transforms, [('out_lta_file', 'in2')]), # Normalize LTA transforms to RAS2RAS (inputs are VOX2VOX) and compare (transforms, lta_ras2ras, [('out', 'in_lta')]), (lta_ras2ras, compare_transforms, [('out_lta', 'lta_list')]), (compare_transforms, outputnode, [('out', 'fallback')]), # Select output transform (transforms, select_transform, [('out', 'inlist')]), (compare_transforms, select_transform, [('out', 'index')]), (select_transform, merge_ltas, [('out', 'in1')]), # Select output report (bbregister, reports, [('out_report', 'in1')]), (mri_coreg, reports, [('out_report', 'in2')]), (reports, select_report, [('out', 'inlist')]), (compare_transforms, select_report, [('out', 'index')]), (select_report, outputnode, [('out', 'out_report')]), ]) return workflow
def init_bold_preproc_report_wf(mem_gb, reportlets_dir, name='bold_preproc_report_wf'): """ This workflow generates and saves a reportlet showing the effect of resampling the BOLD signal using the standard deviation maps. .. workflow:: :graph2use: orig :simple_form: yes from fmriprep.workflows.bold.resampling import init_bold_preproc_report_wf wf = init_bold_preproc_report_wf(mem_gb=1, reportlets_dir='.') **Parameters** mem_gb : float Size of BOLD file in GB reportlets_dir : str Directory in which to save reportlets name : str, optional Workflow name (default: bold_preproc_report_wf) **Inputs** in_pre BOLD time-series, before resampling in_post BOLD time-series, after resampling name_source BOLD series NIfTI file Used to recover original information lost during processing """ from nipype.algorithms.confounds import TSNR from niworkflows.interfaces import SimpleBeforeAfter workflow = Workflow(name=name) inputnode = pe.Node( niu.IdentityInterface(fields=['in_pre', 'in_post', 'name_source']), name='inputnode') pre_tsnr = pe.Node(TSNR(), name='pre_tsnr', mem_gb=mem_gb * 4.5) pos_tsnr = pe.Node(TSNR(), name='pos_tsnr', mem_gb=mem_gb * 4.5) bold_rpt = pe.Node(SimpleBeforeAfter(), name='bold_rpt', mem_gb=0.1) ds_report_bold = pe.Node(DerivativesDataSink(base_directory=reportlets_dir, desc='preproc', keep_dtype=True), name='ds_report_bold', mem_gb=DEFAULT_MEMORY_MIN_GB, run_without_submitting=True) workflow.connect([ (inputnode, ds_report_bold, [('name_source', 'source_file')]), (inputnode, pre_tsnr, [('in_pre', 'in_file')]), (inputnode, pos_tsnr, [('in_post', 'in_file')]), (pre_tsnr, bold_rpt, [('stddev_file', 'before')]), (pos_tsnr, bold_rpt, [('stddev_file', 'after')]), (bold_rpt, ds_report_bold, [('out_report', 'in_file')]), ]) return workflow
def init_bold_reference_wf(omp_nthreads, bold_file=None, pre_mask=False, name='bold_reference_wf', gen_report=False): """ This workflow generates reference BOLD images for a series The raw reference image is the target of :abbr:`HMC (head motion correction)`, and a contrast-enhanced reference is the subject of distortion correction, as well as boundary-based registration to T1w and template spaces. .. workflow:: :graph2use: orig :simple_form: yes from fmriprep.workflows.bold import init_bold_reference_wf wf = init_bold_reference_wf(omp_nthreads=1) **Parameters** bold_file : str BOLD series NIfTI file omp_nthreads : int Maximum number of threads an individual process may use name : str Name of workflow (default: ``bold_reference_wf``) gen_report : bool Whether a mask report node should be appended in the end enhance_t2 : bool Perform logarithmic transform of input BOLD image to improve contrast before calculating the preliminary mask **Inputs** bold_file BOLD series NIfTI file bold_mask : bool A tentative brain mask to initialize the workflow (requires ``pre_mask`` parameter set ``True``). **Outputs** bold_file Validated BOLD series NIfTI file raw_ref_image Reference image to which BOLD series is motion corrected skip_vols Number of non-steady-state volumes detected at beginning of ``bold_file`` ref_image Contrast-enhanced reference image ref_image_brain Skull-stripped reference image bold_mask Skull-stripping mask of reference image validation_report HTML reportlet indicating whether ``bold_file`` had a valid affine **Subworkflows** * :py:func:`~fmriprep.workflows.bold.util.init_enhance_and_skullstrip_wf` """ workflow = Workflow(name=name) workflow.__desc__ = """\ First, a reference volume and its skull-stripped version were generated using a custom methodology of *fMRIPrep*. """ inputnode = pe.Node(niu.IdentityInterface(fields=['bold_file', 'sbref_file', 'bold_mask']), name='inputnode') outputnode = pe.Node( niu.IdentityInterface(fields=['bold_file', 'raw_ref_image', 'skip_vols', 'ref_image', 'ref_image_brain', 'bold_mask', 'validation_report', 'mask_report']), name='outputnode') # Simplify manually setting input image if bold_file is not None: inputnode.inputs.bold_file = bold_file validate = pe.Node(ValidateImage(), name='validate', mem_gb=DEFAULT_MEMORY_MIN_GB) gen_ref = pe.Node(EstimateReferenceImage(), name="gen_ref", mem_gb=1) # OE: 128x128x128x50 * 64 / 8 ~ 900MB. # Re-run validation; no effect if no sbref; otherwise apply same validation to sbref as bold validate_ref = pe.Node(ValidateImage(), name='validate_ref', mem_gb=DEFAULT_MEMORY_MIN_GB) enhance_and_skullstrip_bold_wf = init_enhance_and_skullstrip_bold_wf( omp_nthreads=omp_nthreads, pre_mask=pre_mask) workflow.connect([ (inputnode, enhance_and_skullstrip_bold_wf, [('bold_mask', 'inputnode.pre_mask')]), (inputnode, validate, [('bold_file', 'in_file')]), (inputnode, gen_ref, [('sbref_file', 'sbref_file')]), (validate, gen_ref, [('out_file', 'in_file')]), (gen_ref, validate_ref, [('ref_image', 'in_file')]), (validate_ref, enhance_and_skullstrip_bold_wf, [('out_file', 'inputnode.in_file')]), (validate, outputnode, [('out_file', 'bold_file'), ('out_report', 'validation_report')]), (gen_ref, outputnode, [('n_volumes_to_discard', 'skip_vols')]), (validate_ref, outputnode, [('out_file', 'raw_ref_image')]), (enhance_and_skullstrip_bold_wf, outputnode, [ ('outputnode.bias_corrected_file', 'ref_image'), ('outputnode.mask_file', 'bold_mask'), ('outputnode.skull_stripped_file', 'ref_image_brain')]), ]) if gen_report: mask_reportlet = pe.Node(SimpleShowMaskRPT(), name='mask_reportlet') workflow.connect([ (enhance_and_skullstrip_bold_wf, mask_reportlet, [ ('outputnode.bias_corrected_file', 'background_file'), ('outputnode.mask_file', 'mask_file'), ]), ]) return workflow
def init_syn_sdc_wf(omp_nthreads, bold_pe=None, atlas_threshold=3, name='syn_sdc_wf'): """ This workflow takes a skull-stripped T1w image and reference BOLD image and estimates a susceptibility distortion correction warp, using ANTs symmetric normalization (SyN) and the average fieldmap atlas described in [Treiber2016]_. SyN deformation is restricted to the phase-encoding (PE) direction. If no PE direction is specified, anterior-posterior PE is assumed. SyN deformation is also restricted to regions that are expected to have a >3mm (approximately 1 voxel) warp, based on the fieldmap atlas. This technique is a variation on those developed in [Huntenburg2014]_ and [Wang2017]_. .. workflow :: :graph2use: orig :simple_form: yes from fmriprep.workflows.fieldmap.syn import init_syn_sdc_wf wf = init_syn_sdc_wf( bold_pe='j', omp_nthreads=8) **Inputs** bold_ref reference image bold_ref_brain skull-stripped reference image template : str Name of template targeted by ``template`` output space t1_brain skull-stripped, bias-corrected structural image t1_2_mni_reverse_transform inverse registration transform of T1w image to MNI template **Outputs** out_reference the ``bold_ref`` image after unwarping out_reference_brain the ``bold_ref_brain`` image after unwarping out_warp the corresponding :abbr:`DFM (displacements field map)` compatible with ANTs out_mask mask of the unwarped input file """ if bold_pe is None or bold_pe[0] not in ['i', 'j']: LOGGER.warning('Incorrect phase-encoding direction, assuming PA (posterior-to-anterior).') bold_pe = 'j' workflow = Workflow(name=name) workflow.__desc__ = """\ A deformation field to correct for susceptibility distortions was estimated based on *fMRIPrep*'s *fieldmap-less* approach. The deformation field is that resulting from co-registering the BOLD reference to the same-subject T1w-reference with its intensity inverted [@fieldmapless1; @fieldmapless2]. Registration is performed with `antsRegistration` (ANTs {ants_ver}), and the process regularized by constraining deformation to be nonzero only along the phase-encoding direction, and modulated with an average fieldmap template [@fieldmapless3]. """.format(ants_ver=Registration().version or '<ver>') inputnode = pe.Node( niu.IdentityInterface(['bold_ref', 'bold_ref_brain', 'template', 't1_brain', 't1_2_mni_reverse_transform']), name='inputnode') outputnode = pe.Node( niu.IdentityInterface(['out_reference', 'out_reference_brain', 'out_mask', 'out_warp']), name='outputnode') # Collect predefined data # Atlas image and registration affine atlas_img = pkgr.resource_filename('fmriprep', 'data/fmap_atlas.nii.gz') # Registration specifications affine_transform = pkgr.resource_filename('fmriprep', 'data/affine.json') syn_transform = pkgr.resource_filename('fmriprep', 'data/susceptibility_syn.json') invert_t1w = pe.Node(Rescale(invert=True), name='invert_t1w', mem_gb=0.3) ref_2_t1 = pe.Node(Registration(from_file=affine_transform), name='ref_2_t1', n_procs=omp_nthreads) t1_2_ref = pe.Node(ApplyTransforms(invert_transform_flags=[True]), name='t1_2_ref', n_procs=omp_nthreads) # 1) BOLD -> T1; 2) MNI -> T1; 3) ATLAS -> MNI transform_list = pe.Node(niu.Merge(3), name='transform_list', mem_gb=DEFAULT_MEMORY_MIN_GB) # Inverting (1), then applying in reverse order: # # ATLAS -> MNI -> T1 -> BOLD atlas_2_ref = pe.Node( ApplyTransforms(invert_transform_flags=[True, False, False]), name='atlas_2_ref', n_procs=omp_nthreads, mem_gb=0.3) atlas_2_ref.inputs.input_image = atlas_img threshold_atlas = pe.Node( fsl.maths.MathsCommand(args='-thr {:.8g} -bin'.format(atlas_threshold), output_datatype='char'), name='threshold_atlas', mem_gb=0.3) fixed_image_masks = pe.Node(niu.Merge(2), name='fixed_image_masks', mem_gb=DEFAULT_MEMORY_MIN_GB) fixed_image_masks.inputs.in1 = 'NULL' restrict = [[int(bold_pe[0] == 'i'), int(bold_pe[0] == 'j'), 0]] * 2 syn = pe.Node( Registration(from_file=syn_transform, restrict_deformation=restrict), name='syn', n_procs=omp_nthreads) unwarp_ref = pe.Node(ApplyTransforms( dimension=3, float=True, interpolation='LanczosWindowedSinc'), name='unwarp_ref') skullstrip_bold_wf = init_skullstrip_bold_wf() workflow.connect([ (inputnode, invert_t1w, [('t1_brain', 'in_file'), ('bold_ref', 'ref_file')]), (inputnode, ref_2_t1, [('bold_ref_brain', 'moving_image')]), (invert_t1w, ref_2_t1, [('out_file', 'fixed_image')]), (inputnode, t1_2_ref, [('bold_ref', 'reference_image')]), (invert_t1w, t1_2_ref, [('out_file', 'input_image')]), (ref_2_t1, t1_2_ref, [('forward_transforms', 'transforms')]), (ref_2_t1, transform_list, [('forward_transforms', 'in1')]), (inputnode, transform_list, [ ('t1_2_mni_reverse_transform', 'in2'), (('template', _prior_path), 'in3')]), (inputnode, atlas_2_ref, [('bold_ref', 'reference_image')]), (transform_list, atlas_2_ref, [('out', 'transforms')]), (atlas_2_ref, threshold_atlas, [('output_image', 'in_file')]), (threshold_atlas, fixed_image_masks, [('out_file', 'in2')]), (inputnode, syn, [('bold_ref_brain', 'moving_image')]), (t1_2_ref, syn, [('output_image', 'fixed_image')]), (fixed_image_masks, syn, [('out', 'fixed_image_masks')]), (syn, outputnode, [('forward_transforms', 'out_warp')]), (syn, unwarp_ref, [('forward_transforms', 'transforms')]), (inputnode, unwarp_ref, [('bold_ref', 'reference_image'), ('bold_ref', 'input_image')]), (unwarp_ref, skullstrip_bold_wf, [ ('output_image', 'inputnode.in_file')]), (unwarp_ref, outputnode, [('output_image', 'out_reference')]), (skullstrip_bold_wf, outputnode, [ ('outputnode.skull_stripped_file', 'out_reference_brain'), ('outputnode.mask_file', 'out_mask')]), ]) return workflow
def init_phdiff_wf(omp_nthreads, name='phdiff_wf'): """ Estimates the fieldmap using a phase-difference image and one or more magnitude images corresponding to two or more :abbr:`GRE (Gradient Echo sequence)` acquisitions. The `original code was taken from nipype <https://github.com/nipy/nipype/blob/master/nipype/workflows/dmri/fsl/artifacts.py#L514>`_. .. workflow :: :graph2use: orig :simple_form: yes from fmriprep.workflows.fieldmap.phdiff import init_phdiff_wf wf = init_phdiff_wf(omp_nthreads=1) Outputs:: outputnode.fmap_ref - The average magnitude image, skull-stripped outputnode.fmap_mask - The brain mask applied to the fieldmap outputnode.fmap - The estimated fieldmap in Hz """ workflow = Workflow(name=name) workflow.__desc__ = """\ A deformation field to correct for susceptibility distortions was estimated based on a field map that was co-registered to the BOLD reference, using a custom workflow of *fMRIPrep* derived from D. Greve's `epidewarp.fsl` [script](http://www.nmr.mgh.harvard.edu/~greve/fbirn/b0/epidewarp.fsl) and further improvements of HCP Pipelines [@hcppipelines]. """ inputnode = pe.Node(niu.IdentityInterface(fields=['magnitude', 'phasediff']), name='inputnode') outputnode = pe.Node(niu.IdentityInterface( fields=['fmap', 'fmap_ref', 'fmap_mask']), name='outputnode') def _pick1st(inlist): return inlist[0] # Read phasediff echo times meta = pe.Node(ReadSidecarJSON(), name='meta', mem_gb=0.01, run_without_submitting=True) # Merge input magnitude images magmrg = pe.Node(IntraModalMerge(), name='magmrg') # de-gradient the fields ("bias/illumination artifact") n4 = pe.Node(ants.N4BiasFieldCorrection(dimension=3, copy_header=True), name='n4', n_procs=omp_nthreads) bet = pe.Node(BETRPT(generate_report=True, frac=0.6, mask=True), name='bet') ds_fmap_mask = pe.Node(DerivativesDataSink(suffix='fmap_mask'), name='ds_report_fmap_mask', mem_gb=0.01, run_without_submitting=True) # uses mask from bet; outputs a mask # dilate = pe.Node(fsl.maths.MathsCommand( # nan2zeros=True, args='-kernel sphere 5 -dilM'), name='MskDilate') # phase diff -> radians pha2rads = pe.Node(niu.Function(function=siemens2rads), name='pha2rads') # FSL PRELUDE will perform phase-unwrapping prelude = pe.Node(fsl.PRELUDE(), name='prelude') denoise = pe.Node(fsl.SpatialFilter(operation='median', kernel_shape='sphere', kernel_size=3), name='denoise') demean = pe.Node(niu.Function(function=demean_image), name='demean') cleanup_wf = cleanup_edge_pipeline(name="cleanup_wf") compfmap = pe.Node(Phasediff2Fieldmap(), name='compfmap') # The phdiff2fmap interface is equivalent to: # rad2rsec (using rads2radsec from nipype.workflows.dmri.fsl.utils) # pre_fugue = pe.Node(fsl.FUGUE(save_fmap=True), name='ComputeFieldmapFUGUE') # rsec2hz (divide by 2pi) workflow.connect([ (inputnode, meta, [('phasediff', 'in_file')]), (inputnode, magmrg, [('magnitude', 'in_files')]), (magmrg, n4, [('out_avg', 'input_image')]), (n4, prelude, [('output_image', 'magnitude_file')]), (n4, bet, [('output_image', 'in_file')]), (bet, prelude, [('mask_file', 'mask_file')]), (inputnode, pha2rads, [('phasediff', 'in_file')]), (pha2rads, prelude, [('out', 'phase_file')]), (meta, compfmap, [('out_dict', 'metadata')]), (prelude, denoise, [('unwrapped_phase_file', 'in_file')]), (denoise, demean, [('out_file', 'in_file')]), (demean, cleanup_wf, [('out', 'inputnode.in_file')]), (bet, cleanup_wf, [('mask_file', 'inputnode.in_mask')]), (cleanup_wf, compfmap, [('outputnode.out_file', 'in_file')]), (compfmap, outputnode, [('out_file', 'fmap')]), (bet, outputnode, [('mask_file', 'fmap_mask'), ('out_file', 'fmap_ref')]), (inputnode, ds_fmap_mask, [('phasediff', 'source_file')]), (bet, ds_fmap_mask, [('out_report', 'in_file')]), ]) return workflow