def init_func_preproc_wf(bold_file): """ This workflow controls the functional preprocessing stages of *fMRIPrep*. Workflow Graph .. workflow:: :graph2use: orig :simple_form: yes from fmriprep.workflows.tests import mock_config from fmriprep import config from fmriprep.workflows.bold.base import init_func_preproc_wf with mock_config(): bold_file = config.execution.bids_dir / 'sub-01' / 'func' \ / 'sub-01_task-mixedgamblestask_run-01_bold.nii.gz' wf = init_func_preproc_wf(str(bold_file)) Parameters ---------- bold_file BOLD series NIfTI file Inputs ------ bold_file BOLD series NIfTI file t1w_preproc Bias-corrected structural template image t1w_mask Mask of the skull-stripped template image t1w_dseg Segmentation of preprocessed structural image, including gray-matter (GM), white-matter (WM) and cerebrospinal fluid (CSF) t1w_asec Segmentation of structural image, done with FreeSurfer. t1w_aparc Parcellation of structural image, done with FreeSurfer. t1w_tpms List of tissue probability maps in T1w space template List of templates to target anat2std_xfm List of transform files, collated with templates std2anat_xfm List of inverse transform files, collated with templates 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 Outputs ------- bold_t1 BOLD series, resampled to T1w space bold_mask_t1 BOLD series mask in T1w space bold_std BOLD series, resampled to template space bold_mask_std 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` See Also -------- * :py:func:`~niworkflows.func.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_std_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:`~sdcflows.workflows.fmap.init_fmap_wf` * :py:func:`~sdcflows.workflows.pepolar.init_pepolar_unwarp_wf` * :py:func:`~sdcflows.workflows.phdiff.init_phdiff_wf` * :py:func:`~sdcflows.workflows.syn.init_syn_sdc_wf` * :py:func:`~sdcflows.workflows.unwarp.init_sdc_unwarp_wf` """ from niworkflows.engine.workflows import LiterateWorkflow as Workflow from niworkflows.func.util import init_bold_reference_wf from niworkflows.interfaces.nibabel import ApplyMask from niworkflows.interfaces.utility import KeySelect from niworkflows.interfaces.utils import DictMerge from sdcflows.workflows.base import init_sdc_estimate_wf, fieldmap_wrangler ref_file = bold_file mem_gb = {'filesize': 1, 'resampled': 1, 'largemem': 1} bold_tlen = 10 multiecho = isinstance(bold_file, list) # Have some options handy layout = config.execution.layout omp_nthreads = config.nipype.omp_nthreads freesurfer = config.workflow.run_reconall spaces = config.workflow.spaces 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) config.loggers.workflow.debug( '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 # Find associated sbref, if possible entities = layout.parse_file_entities(ref_file) entities['suffix'] = 'sbref' entities['extension'] = ['nii', 'nii.gz'] # Overwrite extensions files = layout.get(return_type='file', **entities) refbase = os.path.basename(ref_file) if 'sbref' in config.workflow.ignore: config.loggers.workflow.info("Single-band reference files ignored.") elif files and multiecho: config.loggers.workflow.warning( "Single-band reference found, but not supported in " "multi-echo workflows at this time. Ignoring.") elif files: sbref_file = files[0] sbbase = os.path.basename(sbref_file) if len(files) > 1: config.loggers.workflow.warning( "Multiple single-band reference files found for {}; using " "{}".format(refbase, sbbase)) else: config.loggers.workflow.info( "Using single-band reference file %s.", sbbase) else: config.loggers.workflow.info("No single-band-reference found for %s.", refbase) metadata = layout.get_metadata(ref_file) # Find fieldmaps. Options: (phase1|phase2|phasediff|epi|fieldmap|syn) fmaps = None if 'fieldmaps' not in config.workflow.ignore: fmaps = fieldmap_wrangler(layout, ref_file, use_syn=config.workflow.use_syn, force_syn=config.workflow.force_syn) elif config.workflow.use_syn or config.workflow.force_syn: # If fieldmaps are not enabled, activate SyN-SDC in unforced (False) mode fmaps = {'syn': False} # Short circuits: (True and True and (False or 'TooShort')) == 'TooShort' run_stc = (bool(metadata.get("SliceTiming")) and 'slicetiming' not in config.workflow.ignore and (_get_series_len(ref_file) > 4 or "TooShort")) # Check if MEEPI for T2* coregistration target if config.workflow.t2s_coreg and not multiecho: config.loggers.workflow.warning( "No multiecho BOLD images found for T2* coregistration. " "Using standard EPI-T1 coregistration.") config.workflow.t2s_coreg = False # By default, force-bbr for t2s_coreg unless user specifies otherwise if config.workflow.t2s_coreg and config.workflow.use_bbr is None: config.workflow.use_bbr = True # Build workflow workflow = Workflow(name=wf_name) 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 output 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', 'subjects_dir', 'subject_id', 't1w_preproc', 't1w_mask', 't1w_dseg', 't1w_tpms', 't1w_aseg', 't1w_aparc', 'anat2std_xfm', 'std2anat_xfm', 'template', 't1w2fsnative_xfm', 'fsnative2t1w_xfm' ]), name='inputnode') inputnode.inputs.bold_file = bold_file if sbref_file is not None: from niworkflows.interfaces.images import ValidateImage val_sbref = pe.Node(ValidateImage(in_file=sbref_file), name='val_sbref') outputnode = pe.Node(niu.IdentityInterface(fields=[ 'bold_t1', 'bold_t1_ref', 'bold_mask_t1', 'bold_aseg_t1', 'bold_aparc_t1', 'bold_std', 'bold_std_ref', 'bold_mask_std', 'bold_aseg_std', 'bold_aparc_std', 'bold_native', 'bold_cifti', 'cifti_variant', 'cifti_metadata', 'cifti_density', 'surfaces', 'confounds', 'aroma_noise_ics', 'melodic_mix', 'nonaggr_denoised_file', 'confounds_metadata' ]), name='outputnode') # Generate a brain-masked conversion of the t1w t1w_brain = pe.Node(ApplyMask(), name='t1w_brain') # 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( slice_timing=run_stc, registration=('FSL', 'FreeSurfer')[freesurfer], registration_dof=config.workflow.bold2t1w_dof, registration_init=config.workflow.bold2t1w_init, pe_direction=metadata.get("PhaseEncodingDirection"), tr=metadata.get("RepetitionTime")), name='summary', mem_gb=config.DEFAULT_MEMORY_MIN_GB, run_without_submitting=True) summary.inputs.dummy_scans = config.workflow.dummy_scans func_derivatives_wf = init_func_derivatives_wf( bids_root=layout.root, cifti_output=config.workflow.cifti_output, freesurfer=freesurfer, metadata=metadata, output_dir=str(config.execution.output_dir), spaces=spaces, use_aroma=config.workflow.use_aroma, ) 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_native', 'inputnode.bold_native'), ('confounds', 'inputnode.confounds'), ('surfaces', 'inputnode.surf_files'), ('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_metadata', 'inputnode.cifti_metadata'), ('cifti_density', 'inputnode.cifti_density'), ('confounds_metadata', 'inputnode.confounds_metadata'), ]), ]) # Generate a tentative boldref bold_reference_wf = init_bold_reference_wf(omp_nthreads=omp_nthreads) bold_reference_wf.inputs.inputnode.dummy_scans = config.workflow.dummy_scans if sbref_file is not None: workflow.connect([ (val_sbref, bold_reference_wf, [('out_file', 'inputnode.sbref_file')]), ]) # 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=config.workflow.use_bbr, bold2t1w_dof=config.workflow.bold2t1w_dof, bold2t1w_init=config.workflow.bold2t1w_init, 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=bool(fmaps), 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, regressors_all_comps=config.workflow.regressors_all_comps, regressors_fd_th=config.workflow.regressors_fd_th, regressors_dvars_th=config.workflow.regressors_dvars_th, 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 config.execution.low_mem, use_fieldwarp=bool(fmaps), 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_estimate_wf(fmaps, metadata, omp_nthreads=omp_nthreads, debug=config.execution.debug) # MULTI-ECHO EPI DATA ############################################# if multiecho: from niworkflows.func.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=config.workflow.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([ (inputnode, t1w_brain, [('t1w_preproc', 'in_file'), ('t1w_mask', 'in_mask')]), # Generate early reference (inputnode, bold_reference_wf, [('bold_file', 'inputnode.bold_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')]), (bold_reference_wf, summary, [('outputnode.algo_dummy_scans', 'algo_dummy_scans')]), # EPI-T1 registration workflow ( inputnode, bold_reg_wf, [ ('t1w_dseg', 'inputnode.t1w_dseg'), # Undefined if --fs-no-reconall, but this is safe ('subjects_dir', 'inputnode.subjects_dir'), ('subject_id', 'inputnode.subject_id'), ('fsnative2t1w_xfm', 'inputnode.fsnative2t1w_xfm') ]), (t1w_brain, bold_reg_wf, [('out_file', 'inputnode.t1w_brain')]), (inputnode, bold_t1_trans_wf, [('bold_file', 'inputnode.name_source'), ('t1w_mask', 'inputnode.t1w_mask'), ('t1w_aseg', 'inputnode.t1w_aseg'), ('t1w_aparc', 'inputnode.t1w_aparc')]), (t1w_brain, bold_t1_trans_wf, [('out_file', 'inputnode.t1w_brain')]), # 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) (t1w_brain, bold_sdc_wf, [('out_file', 'inputnode.t1w_brain')]), (bold_reference_wf, bold_sdc_wf, [('outputnode.ref_image', 'inputnode.epi_file'), ('outputnode.ref_image_brain', 'inputnode.epi_brain'), ('outputnode.bold_mask', 'inputnode.epi_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.epi_mask', 'inputnode.bold_mask')]), (bold_sdc_wf, summary, [('outputnode.method', 'distortion_correction') ]), # Connect bold_confounds_wf (inputnode, bold_confounds_wf, [('t1w_tpms', 'inputnode.t1w_tpms'), ('t1w_mask', 'inputnode.t1w_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'), ]), (bold_confounds_wf, outputnode, [ ('outputnode.confounds_metadata', 'confounds_metadata'), ]), # 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')]), ]) if not config.workflow.t2s_coreg: workflow.connect([ (bold_sdc_wf, bold_reg_wf, [('outputnode.epi_brain', 'inputnode.ref_bold_brain')]), (bold_sdc_wf, bold_t1_trans_wf, [('outputnode.epi_brain', 'inputnode.ref_bold_brain'), ('outputnode.epi_mask', 'inputnode.ref_bold_mask')]), ]) else: workflow.connect([ # For t2s_coreg, replace EPI-to-T1w registration inputs (bold_t2s_wf, bold_reg_wf, [('outputnode.bold_ref_brain', 'inputnode.ref_bold_brain')]), (bold_t2s_wf, bold_t1_trans_wf, [('outputnode.bold_ref_brain', 'inputnode.ref_bold_brain'), ('outputnode.bold_mask', 'inputnode.ref_bold_mask')]), ]) # 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 sdcflows.workflows.outputs import init_sdc_unwarp_report_wf # Report on BOLD correction fmap_unwarp_report_wf = init_sdc_unwarp_report_wf() workflow.connect([ (inputnode, fmap_unwarp_report_wf, [('t1w_dseg', '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.epi_corrected', 'inputnode.in_post')]), ]) # Overwrite ``out_path_base`` of unwarping DataSinks for node in fmap_unwarp_report_wf.list_node_names(): if node.split('.')[-1].startswith('ds_'): fmap_unwarp_report_wf.get_node( node).interface.out_path_base = 'fmriprep' for node in bold_sdc_wf.list_node_names(): if node.split('.')[-1].startswith('ds_'): bold_sdc_wf.get_node(node).interface.out_path_base = 'fmriprep' if 'syn' in fmaps: sdc_select_std = pe.Node(KeySelect(fields=['std2anat_xfm']), name='sdc_select_std', run_without_submitting=True) sdc_select_std.inputs.key = 'MNI152NLin2009cAsym' workflow.connect([ (inputnode, sdc_select_std, [('std2anat_xfm', 'std2anat_xfm'), ('template', 'keys')]), (sdc_select_std, bold_sdc_wf, [('std2anat_xfm', 'inputnode.std2anat_xfm')]), ]) if fmaps.get('syn') is True: # SyN forced syn_unwarp_report_wf = init_sdc_unwarp_report_wf( name='syn_unwarp_report_wf', forcedsyn=True) workflow.connect([ (inputnode, syn_unwarp_report_wf, [('t1w_dseg', '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_ref', 'inputnode.in_post')]), ]) # Overwrite ``out_path_base`` of unwarping DataSinks for node in syn_unwarp_report_wf.list_node_names(): if node.split('.')[-1].startswith('ds_'): syn_unwarp_report_wf.get_node( node).interface.out_path_base = 'fmriprep' # Map final BOLD mask into T1w space (if required) nonstd_spaces = set(spaces.get_nonstandard()) if nonstd_spaces.intersection(('T1w', 'anat')): 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 nonstd_spaces.intersection(('func', 'run', 'bold', 'boldref', 'sbref')): workflow.connect([ (bold_bold_trans_wf, outputnode, [('outputnode.bold', 'bold_native')]), (bold_bold_trans_wf, func_derivatives_wf, [('outputnode.bold_ref', 'inputnode.bold_native_ref'), ('outputnode.bold_mask', 'inputnode.bold_mask_native')]), ]) if spaces.get_spaces(nonstandard=False, dim=(3, )): # Apply transforms in 1 shot # Only use uncompressed output if AROMA is to be run bold_std_trans_wf = init_bold_std_trans_wf( freesurfer=freesurfer, mem_gb=mem_gb['resampled'], omp_nthreads=omp_nthreads, spaces=spaces, name='bold_std_trans_wf', use_compression=not config.execution.low_mem, use_fieldwarp=bool(fmaps), ) workflow.connect([ (inputnode, bold_std_trans_wf, [('template', 'inputnode.templates'), ('anat2std_xfm', 'inputnode.anat2std_xfm'), ('bold_file', 'inputnode.name_source'), ('t1w_aseg', 'inputnode.bold_aseg'), ('t1w_aparc', 'inputnode.bold_aparc')]), (bold_hmc_wf, bold_std_trans_wf, [('outputnode.xforms', 'inputnode.hmc_xforms')]), (bold_reg_wf, bold_std_trans_wf, [('outputnode.itk_bold_to_t1', 'inputnode.itk_bold_to_t1')]), (bold_bold_trans_wf if not multiecho else bold_t2s_wf, bold_std_trans_wf, [('outputnode.bold_mask', 'inputnode.bold_mask')]), (bold_sdc_wf, bold_std_trans_wf, [('outputnode.out_warp', 'inputnode.fieldwarp')]), (bold_std_trans_wf, outputnode, [('outputnode.bold_std', 'bold_std'), ('outputnode.bold_std_ref', 'bold_std_ref'), ('outputnode.bold_mask_std', 'bold_mask_std')]), ]) if freesurfer: workflow.connect([ (bold_std_trans_wf, func_derivatives_wf, [ ('outputnode.bold_aseg_std', 'inputnode.bold_aseg_std'), ('outputnode.bold_aparc_std', 'inputnode.bold_aparc_std'), ]), (bold_std_trans_wf, outputnode, [('outputnode.bold_aseg_std', 'bold_aseg_std'), ('outputnode.bold_aparc_std', 'bold_aparc_std')]), ]) if not multiecho: workflow.connect([(bold_split, bold_std_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_std_trans_wf, [('out_files', 'inputnode.bold_split')])]) # func_derivatives_wf internally parametrizes over snapshotted spaces. workflow.connect([ (bold_std_trans_wf, func_derivatives_wf, [ ('outputnode.template', 'inputnode.template'), ('outputnode.spatial_reference', 'inputnode.spatial_reference'), ('outputnode.bold_std_ref', 'inputnode.bold_std_ref'), ('outputnode.bold_std', 'inputnode.bold_std'), ('outputnode.bold_mask_std', 'inputnode.bold_mask_std'), ]), ]) if config.workflow.use_aroma: # ICA-AROMA workflow from .confounds import init_ica_aroma_wf ica_aroma_wf = init_ica_aroma_wf( mem_gb=mem_gb['resampled'], metadata=metadata, omp_nthreads=omp_nthreads, use_fieldwarp=bool(fmaps), err_on_aroma_warn=config.workflow.aroma_err_on_warn, aroma_melodic_dim=config.workflow.aroma_melodic_dim, name='ica_aroma_wf') join = pe.Node(niu.Function(output_names=["out_file"], function=_to_join), name='aroma_confounds') mrg_conf_metadata = pe.Node(niu.Merge(2), name='merge_confound_metadata', run_without_submitting=True) mrg_conf_metadata2 = pe.Node(DictMerge(), name='merge_confound_metadata2', run_without_submitting=True) workflow.disconnect([ (bold_confounds_wf, outputnode, [ ('outputnode.confounds_file', 'confounds'), ]), (bold_confounds_wf, outputnode, [ ('outputnode.confounds_metadata', 'confounds_metadata'), ]), ]) workflow.connect([ (inputnode, ica_aroma_wf, [('bold_file', 'inputnode.name_source')]), (bold_hmc_wf, ica_aroma_wf, [('outputnode.movpar_file', 'inputnode.movpar_file')]), (bold_reference_wf, ica_aroma_wf, [('outputnode.skip_vols', 'inputnode.skip_vols')]), (bold_confounds_wf, join, [('outputnode.confounds_file', 'in_file')]), (bold_confounds_wf, mrg_conf_metadata, [('outputnode.confounds_metadata', 'in1')]), (ica_aroma_wf, join, [('outputnode.aroma_confounds', 'join_file')]), (ica_aroma_wf, mrg_conf_metadata, [('outputnode.aroma_metadata', 'in2')]), (mrg_conf_metadata, mrg_conf_metadata2, [('out', 'in_dicts')]), (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')]), (mrg_conf_metadata2, outputnode, [('out_dict', 'confounds_metadata')]), (bold_std_trans_wf, ica_aroma_wf, [('outputnode.bold_std', 'inputnode.bold_std'), ('outputnode.bold_mask_std', 'inputnode.bold_mask_std'), ('outputnode.spatial_reference', 'inputnode.spatial_reference')]), ]) # SURFACES ################################################################################## # Freesurfer freesurfer_spaces = spaces.get_fs_spaces() if freesurfer and freesurfer_spaces: config.loggers.workflow.debug( 'Creating BOLD surface-sampling workflow.') bold_surf_wf = init_bold_surf_wf( mem_gb=mem_gb['resampled'], surface_spaces=freesurfer_spaces, medial_surface_nan=config.workflow.medial_surface_nan, name='bold_surf_wf') workflow.connect([ (inputnode, bold_surf_wf, [('t1w_preproc', 'inputnode.t1w_preproc'), ('subjects_dir', 'inputnode.subjects_dir'), ('subject_id', 'inputnode.subject_id'), ('t1w2fsnative_xfm', 'inputnode.t1w2fsnative_xfm')]), (bold_t1_trans_wf, bold_surf_wf, [('outputnode.bold_t1', 'inputnode.source_file')]), (bold_surf_wf, outputnode, [('outputnode.surfaces', 'surfaces')]), (bold_surf_wf, func_derivatives_wf, [('outputnode.target', 'inputnode.surf_refs')]), ]) # CIFTI output if config.workflow.cifti_output: from .resampling import init_bold_grayords_wf bold_grayords_wf = init_bold_grayords_wf( grayord_density=config.workflow.cifti_output, mem_gb=mem_gb['resampled'], repetition_time=metadata['RepetitionTime']) workflow.connect([ (inputnode, bold_grayords_wf, [('subjects_dir', 'inputnode.subjects_dir')]), (bold_std_trans_wf, bold_grayords_wf, [('outputnode.bold_std', 'inputnode.bold_std'), ('outputnode.spatial_reference', 'inputnode.spatial_reference')]), (bold_surf_wf, bold_grayords_wf, [ ('outputnode.surfaces', 'inputnode.surf_files'), ('outputnode.target', 'inputnode.surf_refs'), ]), (bold_grayords_wf, outputnode, [('outputnode.cifti_bold', 'bold_cifti'), ('outputnode.cifti_variant', 'cifti_variant'), ('outputnode.cifti_metadata', 'cifti_metadata'), ('outputnode.cifti_density', 'cifti_density')]), ]) if spaces.get_spaces(nonstandard=False, dim=(3, )): carpetplot_wf = init_carpetplot_wf( mem_gb=mem_gb['resampled'], metadata=metadata, cifti_output=config.workflow.cifti_output, name='carpetplot_wf') if config.workflow.cifti_output: workflow.connect(bold_grayords_wf, 'outputnode.cifti_bold', carpetplot_wf, 'inputnode.cifti_bold') else: # Xform to 'MNI152NLin2009cAsym' is always computed. carpetplot_select_std = pe.Node(KeySelect( fields=['std2anat_xfm'], key='MNI152NLin2009cAsym'), name='carpetplot_select_std', run_without_submitting=True) workflow.connect([ (inputnode, carpetplot_select_std, [('std2anat_xfm', 'std2anat_xfm'), ('template', 'keys')]), (carpetplot_select_std, carpetplot_wf, [('std2anat_xfm', 'inputnode.std2anat_xfm')]), (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')]), ]) workflow.connect([(bold_confounds_wf, carpetplot_wf, [ ('outputnode.confounds_file', 'inputnode.confounds_file') ])]) # REPORTING ############################################################ reportlets_dir = str(config.execution.work_dir / 'reportlets') ds_report_summary = pe.Node(DerivativesDataSink(desc='summary', keep_dtype=True), name='ds_report_summary', run_without_submitting=True, mem_gb=config.DEFAULT_MEMORY_MIN_GB) ds_report_validation = pe.Node(DerivativesDataSink( base_directory=reportlets_dir, desc='validation', keep_dtype=True), name='ds_report_validation', run_without_submitting=True, mem_gb=config.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_func_preproc_wf( aroma_melodic_dim, bold2t1w_dof, bold_file, cifti_output, debug, dummy_scans, err_on_aroma_warn, fmap_bspline, fmap_demean, force_syn, freesurfer, ignore, low_mem, medial_surface_nan, omp_nthreads, output_dir, output_spaces, regressors_all_comps, regressors_dvars_th, regressors_fd_th, reportlets_dir, t2s_coreg, use_aroma, use_bbr, use_syn, 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 from collections import namedtuple, OrderedDict BIDSLayout = namedtuple('BIDSLayout', ['root']) wf = init_func_preproc_wf( aroma_melodic_dim=-200, bold2t1w_dof=9, bold_file='/completely/made/up/path/sub-01_task-nback_bold.nii.gz', cifti_output=False, debug=False, dummy_scans=None, err_on_aroma_warn=False, fmap_bspline=True, fmap_demean=True, force_syn=True, freesurfer=True, ignore=[], low_mem=False, medial_surface_nan=False, omp_nthreads=1, output_dir='.', output_spaces=OrderedDict([ ('MNI152Lin', {}), ('fsaverage', {'density': '10k'}), ('T1w', {}), ('fsnative', {})]), regressors_all_comps=False, regressors_dvars_th=1.5, regressors_fd_th=0.5, reportlets_dir='.', t2s_coreg=False, use_aroma=False, use_bbr=True, use_syn=True, layout=BIDSLayout('.'), num_bold=1, ) **Parameters** aroma_melodic_dim : int Maximum number of components identified by MELODIC within ICA-AROMA (default is -200, ie. no limitation). bold2t1w_dof : 6, 9 or 12 Degrees-of-freedom for BOLD-T1w registration bold_file : str BOLD series NIfTI file cifti_output : bool Generate bold CIFTI file in output spaces debug : bool Enable debugging outputs dummy_scans : int or None Number of volumes to consider as non steady state err_on_aroma_warn : bool Do not crash on ICA-AROMA errors fmap_bspline : bool **Experimental**: Fit B-Spline field using least-squares fmap_demean : bool Demean voxel-shift map during unwarp force_syn : bool **Temporary**: Always run SyN-based SDC freesurfer : bool Enable FreeSurfer functional registration (bbregister) and resampling BOLD series to FreeSurfer surface meshes. ignore : list Preprocessing steps to skip (may include "slicetiming", "fieldmaps") low_mem : bool Write uncompressed .nii files in some cases to reduce memory usage medial_surface_nan : bool Replace medial wall values with NaNs on functional GIFTI files omp_nthreads : int Maximum number of threads an individual process may use output_dir : str Directory in which to save derivatives output_spaces : OrderedDict Ordered dictionary where keys are TemplateFlow ID strings (e.g. ``MNI152Lin``, ``MNI152NLin6Asym``, ``MNI152NLin2009cAsym``, or ``fsLR``) strings designating nonstandard references (e.g. ``T1w`` or ``anat``, ``sbref``, ``run``, etc.), 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). regressors_all_comps Return all CompCor component time series instead of the top fraction regressors_dvars_th Criterion for flagging DVARS outliers regressors_fd_th Criterion for flagging framewise displacement outliers reportlets_dir : str Absolute path of a directory in which reportlets will be temporarily stored t2s_coreg : bool For multiecho EPI, use the calculated T2*-map for T2*-driven coregistration use_aroma : bool Perform ICA-AROMA on MNI-resampled functional series 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. use_syn : bool **Experimental**: Enable ANTs SyN-based susceptibility distortion correction (SDC). If fieldmaps are present and enabled, this is not run, by default. 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 anat2std_xfm ANTs-compatible affine-and-warp transform file std2anat_xfm 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_std BOLD series, resampled to template space bold_mask_std 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_std_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 .resampling import NONSTANDARD_REFERENCES from ..fieldmap.base import init_sdc_wf # Avoid circular dependency (#1066) # Filter out standard spaces to a separate dict std_spaces = OrderedDict([(key, modifiers) for key, modifiers in output_spaces.items() if key not in NONSTANDARD_REFERENCES]) volume_std_spaces = OrderedDict([(key, modifiers) for key, modifiers in std_spaces.items() if not key.startswith('fs')]) 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 not hasattr(layout, 'parse_file_entities'): 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 = [{ 'suffix': '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['suffix'] = 'sbref' entities['extension'] = ['nii', 'nii.gz'] # Overwrite extensions files = layout.get(return_type='file', **entities) 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] 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: for fmap in layout.get_fieldmap(ref_file, return_list=True): if fmap['suffix'] == 'phase': LOGGER.warning("""\ Found phase1/2 type of fieldmaps, which are not currently supported. \ fMRIPrep will discard them for susceptibility distortion correction. \ Please, follow up on this issue at \ https://github.com/poldracklab/fmriprep/issues/1655.""") else: fmap['metadata'] = layout.get_metadata( fmap[fmap['suffix']]) fmaps.append(fmap) # Run SyN if forced or in the absence of fieldmap correction if force_syn or (use_syn and not fmaps): fmaps.append({'suffix': '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 output 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', 'subjects_dir', 'subject_id', 't1_preproc', 't1_brain', 't1_mask', 't1_seg', 't1_tpms', 't1_aseg', 't1_aparc', 'anat2std_xfm', 'std2anat_xfm', 'template', 'joint_anat2std_xfm', 'joint_std2anat_xfm', 'joint_template', 't1_2_fsnative_forward_transform', 't1_2_fsnative_reverse_transform' ]), name='inputnode') inputnode.inputs.bold_file = bold_file if sbref_file is not None: from niworkflows.interfaces.images import ValidateImage val_sbref = pe.Node(ValidateImage(in_file=sbref_file), name='val_sbref') outputnode = pe.Node(niu.IdentityInterface(fields=[ 'bold_t1', 'bold_t1_ref', 'bold_mask_t1', 'bold_aseg_t1', 'bold_aparc_t1', 'bold_std', 'bold_std_ref' 'bold_mask_std', 'bold_aseg_std', 'bold_aparc_std', 'bold_native', 'bold_cifti', 'cifti_variant', 'cifti_variant_key', 'surfaces', 'confounds', 'aroma_noise_ics', 'melodic_mix', 'nonaggr_denoised_file', 'confounds_metadata' ]), 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( slice_timing=run_stc, registration=('FSL', 'FreeSurfer')[freesurfer], registration_dof=bold2t1w_dof, pe_direction=metadata.get("PhaseEncodingDirection"), tr=metadata.get("RepetitionTime")), name='summary', mem_gb=DEFAULT_MEMORY_MIN_GB, run_without_submitting=True) summary.inputs.dummy_scans = dummy_scans # CIfTI output: currently, we only support fsaverage{5,6} cifti_spaces = set(s for s in output_spaces.keys() if s in ('fsaverage5', 'fsaverage6')) fsaverage_den = output_spaces.get('fsaverage', {}).get('den') if fsaverage_den: cifti_spaces.add(FSAVERAGE_DENSITY[fsaverage_den]) cifti_output = cifti_output and cifti_spaces func_derivatives_wf = init_func_derivatives_wf( bids_root=layout.root, cifti_output=cifti_output, freesurfer=freesurfer, metadata=metadata, output_dir=output_dir, output_spaces=output_spaces, standard_spaces=list(std_spaces.keys()), use_aroma=use_aroma, ) 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_native', 'inputnode.bold_native'), ('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'), ('confounds_metadata', 'inputnode.confounds_metadata'), ]), ]) # Generate a tentative boldref bold_reference_wf = init_bold_reference_wf(omp_nthreads=omp_nthreads) bold_reference_wf.inputs.inputnode.dummy_scans = dummy_scans if sbref_file is not None: workflow.connect([ (val_sbref, bold_reference_wf, [('out_file', 'inputnode.sbref_file')]), ]) # 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, regressors_all_comps=regressors_all_comps, regressors_fd_th=regressors_fd_th, regressors_dvars_th=regressors_dvars_th, 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) # If no standard space is given, use the default for SyN-SDC if not volume_std_spaces or 'MNI152NLin2009cAsym' in volume_std_spaces: bold_sdc_wf.inputs.inputnode.template = 'MNI152NLin2009cAsym' else: bold_sdc_wf.inputs.inputnode.template = next(iter(volume_std_spaces)) if not fmaps: LOGGER.warning('SDC: no fieldmaps found or they were ignored (%s).', ref_file) elif fmaps[0]['suffix'] == '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]['suffix'], ref_file) # Overwrite ``out_path_base`` of sdcflows' DataSinks for node in bold_sdc_wf.list_node_names(): if node.split('.')[-1].startswith('ds_'): bold_sdc_wf.get_node(node).interface.out_path_base = 'fmriprep' # 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')]), # 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')]), (bold_reference_wf, summary, [('outputnode.algo_dummy_scans', 'algo_dummy_scans')]), # 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, [('joint_template', 'inputnode.templates'), ('joint_std2anat_xfm', 'inputnode.std2anat_xfm')]), (inputnode, bold_sdc_wf, [('t1_brain', 'inputnode.t1_brain')]), (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'), ]), (bold_confounds_wf, outputnode, [ ('outputnode.confounds_metadata', 'confounds_metadata'), ]), # 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 # Report on BOLD correction fmap_unwarp_report_wf = init_fmap_unwarp_report_wf() 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')]), ]) # Overwrite ``out_path_base`` of unwarping DataSinks for node in fmap_unwarp_report_wf.list_node_names(): if node.split('.')[-1].startswith('ds_'): fmap_unwarp_report_wf.get_node( node).interface.out_path_base = 'fmriprep' if force_syn and fmaps[0]['suffix'] != 'syn': syn_unwarp_report_wf = init_fmap_unwarp_report_wf( name='syn_unwarp_report_wf', forcedsyn=True) 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')]), ]) # Overwrite ``out_path_base`` of unwarping DataSinks for node in syn_unwarp_report_wf.list_node_names(): if node.split('.')[-1].startswith('ds_'): syn_unwarp_report_wf.get_node( node).interface.out_path_base = 'fmriprep' # Map final BOLD mask into T1w space (if required) if 'T1w' in output_spaces or 'anat' 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 set(['func', 'run', 'bold', 'boldref', 'sbref']).intersection(output_spaces): workflow.connect([ (bold_bold_trans_wf, outputnode, [('outputnode.bold', 'bold_native')]), (bold_bold_trans_wf, func_derivatives_wf, [('outputnode.bold_ref', 'inputnode.bold_native_ref'), ('outputnode.bold_mask', 'inputnode.bold_mask_native')]), ]) if volume_std_spaces: # Apply transforms in 1 shot # Only use uncompressed output if AROMA is to be run bold_std_trans_wf = init_bold_std_trans_wf( freesurfer=freesurfer, mem_gb=mem_gb['resampled'], omp_nthreads=omp_nthreads, standard_spaces=volume_std_spaces, name='bold_std_trans_wf', use_compression=not low_mem, use_fieldwarp=fmaps is not None, ) workflow.connect([ (inputnode, bold_std_trans_wf, [('joint_template', 'inputnode.templates'), ('joint_anat2std_xfm', 'inputnode.anat2std_xfm'), ('bold_file', 'inputnode.name_source'), ('t1_aseg', 'inputnode.bold_aseg'), ('t1_aparc', 'inputnode.bold_aparc')]), (bold_hmc_wf, bold_std_trans_wf, [('outputnode.xforms', 'inputnode.hmc_xforms')]), (bold_reg_wf, bold_std_trans_wf, [('outputnode.itk_bold_to_t1', 'inputnode.itk_bold_to_t1')]), (bold_bold_trans_wf if not multiecho else bold_t2s_wf, bold_std_trans_wf, [('outputnode.bold_mask', 'inputnode.bold_mask')]), (bold_sdc_wf, bold_std_trans_wf, [('outputnode.out_warp', 'inputnode.fieldwarp')]), (bold_std_trans_wf, outputnode, [('outputnode.bold_std', 'bold_std'), ('outputnode.bold_std_ref', 'bold_std_ref'), ('outputnode.bold_mask_std', 'bold_mask_std')]), ]) if freesurfer: workflow.connect([ (bold_std_trans_wf, func_derivatives_wf, [ ('poutputnode.bold_aseg_std', 'inputnode.bold_aseg_std'), ('poutputnode.bold_aparc_std', 'inputnode.bold_aparc_std'), ]), (bold_std_trans_wf, outputnode, [('outputnode.bold_aseg_std', 'bold_aseg_std'), ('outputnode.bold_aparc_std', 'bold_aparc_std')]), ]) if 'MNI152NLin2009cAsym' in std_spaces: carpetplot_wf = init_carpetplot_wf(standard_spaces=std_spaces, mem_gb=mem_gb['resampled'], metadata=metadata, name='carpetplot_wf') workflow.connect([ (inputnode, carpetplot_wf, [('joint_std2anat_xfm', 'inputnode.std2anat_xfm')]), (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_std_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_std_trans_wf, [('out_files', 'inputnode.bold_split')])]) # Artifacts resampled in MNI space can only be sinked if they # were actually generated. See #1348. # Uses the parameterized outputnode to generate all outputs workflow.connect([ (bold_std_trans_wf, func_derivatives_wf, [ ('poutputnode.templates', 'inputnode.template'), ('poutputnode.bold_std_ref', 'inputnode.bold_std_ref'), ('poutputnode.bold_std', 'inputnode.bold_std'), ('poutputnode.bold_mask_std', 'inputnode.bold_mask_std'), ]), ]) if use_aroma and 'MNI152NLin6Asym' in std_spaces: # ICA-AROMA workflow from .confounds import init_ica_aroma_wf ica_aroma_wf = init_ica_aroma_wf( metadata=metadata, mem_gb=mem_gb['resampled'], omp_nthreads=omp_nthreads, use_fieldwarp=fmaps is not None, err_on_aroma_warn=err_on_aroma_warn, aroma_melodic_dim=aroma_melodic_dim, name='ica_aroma_wf') join = pe.Node(niu.Function(output_names=["out_file"], function=_to_join), name='aroma_confounds') mrg_conf_metadata = pe.Node(niu.Merge(2), name='merge_confound_metadata', run_without_submitting=True) mrg_conf_metadata2 = pe.Node(DictMerge(), name='merge_confound_metadata2', run_without_submitting=True) workflow.disconnect([ (bold_confounds_wf, outputnode, [ ('outputnode.confounds_file', 'confounds'), ]), (bold_confounds_wf, outputnode, [ ('outputnode.confounds_metadata', 'confounds_metadata'), ]), ]) workflow.connect([ (bold_std_trans_wf, ica_aroma_wf, [('outputnode.bold_std', 'inputnode.bold_std'), ('outputnode.bold_mask_std', 'inputnode.bold_mask_std'), ('outputnode.templates', 'inputnode.templates')]), (inputnode, ica_aroma_wf, [('bold_file', 'inputnode.name_source')]), (bold_hmc_wf, ica_aroma_wf, [('outputnode.movpar_file', 'inputnode.movpar_file')]), (bold_reference_wf, ica_aroma_wf, [('outputnode.skip_vols', 'inputnode.skip_vols')]), (bold_confounds_wf, join, [('outputnode.confounds_file', 'in_file')]), (bold_confounds_wf, mrg_conf_metadata, [('outputnode.confounds_metadata', 'in1')]), (ica_aroma_wf, join, [('outputnode.aroma_confounds', 'join_file')]), (ica_aroma_wf, mrg_conf_metadata, [('outputnode.aroma_metadata', 'in2')]), (mrg_conf_metadata, mrg_conf_metadata2, [('out', 'in_dicts')]), (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')]), (mrg_conf_metadata2, outputnode, [('out_dict', 'confounds_metadata')]), ]) # SURFACES ################################################################################## surface_spaces = [ space for space in output_spaces.keys() 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')]), ]) if cifti_output: from niworkflows.interfaces.utility import KeySelect bold_surf_wf.__desc__ += """\ *Grayordinates* files [@hcppipelines], which combine surface-sampled data and volume-sampled data, were also generated. """ select_std = pe.Node(KeySelect(fields=['bold_std']), name='select_std', run_without_submitting=True) select_std.inputs.key = 'MNI152NLin2009cAsym' 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 = list(cifti_spaces) workflow.connect([ (bold_std_trans_wf, select_std, [('outputnode.templates', 'keys'), ('outputnode.bold_std', 'bold_std')]), (bold_surf_wf, gen_cifti, [('outputnode.surfaces', 'gifti_files')]), (inputnode, gen_cifti, [('subjects_dir', 'subjects_dir')]), (select_std, gen_cifti, [('bold_std', 'bold_file')]), (gen_cifti, outputnode, [('out_file', 'bold_cifti'), ('variant', 'cifti_variant'), ('variant_key', 'cifti_variant_key') ]), ]) # REPORTING ############################################################ ds_report_summary = pe.Node(DerivativesDataSink(desc='summary', keep_dtype=True), name='ds_report_summary', run_without_submitting=True, mem_gb=DEFAULT_MEMORY_MIN_GB) ds_report_validation = pe.Node(DerivativesDataSink( base_directory=reportlets_dir, desc='validation', keep_dtype=True), 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_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_type = 'phase1' if fmap['type'] == 'phase' else fmap['type'] 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_func_preproc_wf(bold_file, has_fieldmap=False): """ This workflow controls the functional preprocessing stages of *fMRIPrep*. Workflow Graph .. workflow:: :graph2use: orig :simple_form: yes from fmriprep.workflows.tests import mock_config from fmriprep import config from fmriprep.workflows.bold.base import init_func_preproc_wf with mock_config(): bold_file = config.execution.bids_dir / 'sub-01' / 'func' \ / 'sub-01_task-mixedgamblestask_run-01_bold.nii.gz' wf = init_func_preproc_wf(str(bold_file)) Parameters ---------- bold_file BOLD series NIfTI file has_fieldmap Signals the workflow to use inputnode fieldmap files Inputs ------ bold_file BOLD series NIfTI file t1w_preproc Bias-corrected structural template image t1w_mask Mask of the skull-stripped template image t1w_dseg Segmentation of preprocessed structural image, including gray-matter (GM), white-matter (WM) and cerebrospinal fluid (CSF) t1w_asec Segmentation of structural image, done with FreeSurfer. t1w_aparc Parcellation of structural image, done with FreeSurfer. t1w_tpms List of tissue probability maps in T1w space template List of templates to target anat2std_xfm List of transform files, collated with templates std2anat_xfm List of inverse transform files, collated with templates 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 bold_ref BOLD reference file bold_ref_xfm Transform file in LTA format from bold to reference n_dummy_scans Number of nonsteady states at the beginning of the BOLD run Outputs ------- bold_t1 BOLD series, resampled to T1w space bold_mask_t1 BOLD series mask in T1w space bold_std BOLD series, resampled to template space bold_mask_std 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` See Also -------- * :py:func:`~niworkflows.func.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_confs_wf` * :py:func:`~fmriprep.workflows.bold.confounds.init_ica_aroma_wf` * :py:func:`~fmriprep.workflows.bold.resampling.init_bold_std_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:`~sdcflows.workflows.fmap.init_fmap_wf` * :py:func:`~sdcflows.workflows.pepolar.init_pepolar_unwarp_wf` * :py:func:`~sdcflows.workflows.phdiff.init_phdiff_wf` * :py:func:`~sdcflows.workflows.syn.init_syn_sdc_wf` * :py:func:`~sdcflows.workflows.unwarp.init_sdc_unwarp_wf` """ from niworkflows.engine.workflows import LiterateWorkflow as Workflow from niworkflows.interfaces.utility import DictMerge mem_gb = {'filesize': 1, 'resampled': 1, 'largemem': 1} bold_tlen = 10 # Have some options handy omp_nthreads = config.nipype.omp_nthreads freesurfer = config.workflow.run_reconall spaces = config.workflow.spaces nibabies_dir = str(config.execution.nibabies_dir) # Extract BIDS entities and metadata from BOLD file(s) entities = extract_entities(bold_file) layout = config.execution.layout # Take first file as reference ref_file = pop_file(bold_file) metadata = layout.get_metadata(ref_file) # get original image orientation ref_orientation = get_img_orientation(ref_file) echo_idxs = listify(entities.get("echo", [])) multiecho = len(echo_idxs) > 2 if len(echo_idxs) == 1: config.loggers.workflow.warning( f"Running a single echo <{ref_file}> from a seemingly multi-echo dataset." ) bold_file = ref_file # Just in case - drop the list if len(echo_idxs) == 2: raise RuntimeError( "Multi-echo processing requires at least three different echos (found two)." ) if multiecho: # Drop echo entity for future queries, have a boolean shorthand entities.pop("echo", None) # reorder echoes from shortest to largest tes, bold_file = zip(*sorted([(layout.get_metadata(bf)["EchoTime"], bf) for bf in bold_file])) ref_file = bold_file[0] # Reset reference to be the shortest TE if os.path.isfile(ref_file): bold_tlen, mem_gb = _create_mem_gb(ref_file) wf_name = _get_wf_name(ref_file) config.loggers.workflow.debug( f'Creating bold processing workflow for <{ref_file}> ({mem_gb["filesize"]:.2f} GB ' f'/ {bold_tlen} TRs). Memory resampled/largemem={mem_gb["resampled"]:.2f}' f'/{mem_gb["largemem"]:.2f} GB.') # Find associated sbref, if possible entities['suffix'] = 'sbref' entities['extension'] = ['.nii', '.nii.gz'] # Overwrite extensions sbref_files = layout.get(return_type='file', **entities) sbref_msg = f"No single-band-reference found for {os.path.basename(ref_file)}." if sbref_files and 'sbref' in config.workflow.ignore: sbref_msg = "Single-band reference file(s) found and ignored." elif sbref_files: sbref_msg = "Using single-band reference file(s) {}.".format(','.join( [os.path.basename(sbf) for sbf in sbref_files])) config.loggers.workflow.info(sbref_msg) if has_fieldmap: # Search for intended fieldmap from pathlib import Path import re from sdcflows.fieldmaps import get_identifier bold_rel = re.sub(r"^sub-[a-zA-Z0-9]*/", "", str(Path(bold_file).relative_to(layout.root))) estimator_key = get_identifier(bold_rel) if not estimator_key: has_fieldmap = False config.loggers.workflow.critical( f"None of the available B0 fieldmaps are associated to <{bold_rel}>" ) # Short circuits: (True and True and (False or 'TooShort')) == 'TooShort' run_stc = (bool(metadata.get("SliceTiming")) and 'slicetiming' not in config.workflow.ignore and (_get_series_len(ref_file) > 4 or "TooShort")) # Build workflow workflow = Workflow(name=wf_name) 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 output 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', # from smriprep 'anat_preproc', 'anat_brain', 'anat_mask', 'anat_dseg', 'anat_tpms', 'anat_aseg', 'anat_aparc', 'anat2std_xfm', 'std2anat_xfm', 'template', # from bold reference workflow 'bold_ref', 'bold_ref_xfm', 'n_dummy_scans', # from sdcflows (optional) 'fmap', 'fmap_ref', 'fmap_coeff', 'fmap_mask', 'fmap_id', # if reconstructing with FreeSurfer (optional) 'anat2fsnative_xfm', 'fsnative2anat_xfm', 'subject_id', 'subjects_dir', ]), name='inputnode') inputnode.inputs.bold_file = bold_file outputnode = pe.Node(niu.IdentityInterface(fields=[ 'bold_anat', 'bold_anat_ref', 'bold2anat_xfm', 'anat2bold_xfm', 'bold_mask_anat', 'bold_aseg_anat', 'bold_aparc_anat', 'bold_std', 'bold_std_ref', 'bold_mask_std', 'bold_aseg_std', 'bold_aparc_std', 'bold_native', 'bold_cifti', 'cifti_variant', 'cifti_metadata', 'cifti_density', 'surfaces', 'confounds', 'aroma_noise_ics', 'melodic_mix', 'nonaggr_denoised_file', 'confounds_metadata' ]), 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( slice_timing=run_stc, registration=('FSL', 'FreeSurfer')[freesurfer], registration_dof=config.workflow.bold2t1w_dof, registration_init=config.workflow.bold2t1w_init, pe_direction=metadata.get("PhaseEncodingDirection"), echo_idx=echo_idxs, tr=metadata.get("RepetitionTime"), orientation=ref_orientation), name='summary', mem_gb=config.DEFAULT_MEMORY_MIN_GB, run_without_submitting=True) summary.inputs.dummy_scans = config.workflow.dummy_scans # TODO: SDC: make dynamic summary.inputs.distortion_correction = 'None' if not has_fieldmap else 'TOPUP' func_derivatives_wf = init_func_derivatives_wf( bids_root=layout.root, cifti_output=config.workflow.cifti_output, freesurfer=freesurfer, metadata=metadata, output_dir=nibabies_dir, spaces=spaces, use_aroma=config.workflow.use_aroma, debug=config.execution.debug, ) workflow.connect([ (outputnode, func_derivatives_wf, [ ('bold_anat', 'inputnode.bold_t1'), ('bold_anat_ref', 'inputnode.bold_t1_ref'), ('bold2anat_xfm', 'inputnode.bold2anat_xfm'), ('anat2bold_xfm', 'inputnode.anat2bold_xfm'), ('bold_aseg_anat', 'inputnode.bold_aseg_t1'), ('bold_aparc_anat', 'inputnode.bold_aparc_t1'), ('bold_mask_anat', 'inputnode.bold_mask_t1'), ('bold_native', 'inputnode.bold_native'), ('confounds', 'inputnode.confounds'), ('surfaces', 'inputnode.surf_files'), ('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_metadata', 'inputnode.cifti_metadata'), ('cifti_density', 'inputnode.cifti_density'), ('confounds_metadata', 'inputnode.confounds_metadata'), ('acompcor_masks', 'inputnode.acompcor_masks'), ('tcompcor_mask', 'inputnode.tcompcor_mask'), ]), ]) # Extract BOLD validation from init_bold_reference_wf val_bold = pe.MapNode( ValidateImage(), name="val_bold", mem_gb=config.DEFAULT_MEMORY_MIN_GB, iterfield=["in_file"], ) val_bold.inputs.in_file = listify(bold_file) # 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( bold2t1w_dof=config.workflow.bold2t1w_dof, bold2t1w_init=config.workflow.bold2t1w_init, freesurfer=freesurfer, mem_gb=mem_gb['resampled'], name='bold_reg_wf', omp_nthreads=omp_nthreads, sloppy=config.execution.sloppy, use_bbr=config.workflow.use_bbr, ) # apply BOLD registration to T1w bold_t1_trans_wf = init_bold_t1_trans_wf(name='bold_t1_trans_wf', freesurfer=freesurfer, mem_gb=mem_gb['resampled'], omp_nthreads=omp_nthreads, use_compression=False) if not has_fieldmap: bold_t1_trans_wf.inputs.inputnode.fieldwarp = 'identity' # get confounds bold_confounds_wf = init_bold_confs_wf( mem_gb=mem_gb['largemem'], metadata=metadata, freesurfer=freesurfer, regressors_all_comps=config.workflow.regressors_all_comps, regressors_fd_th=config.workflow.regressors_fd_th, regressors_dvars_th=config.workflow.regressors_dvars_th, 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 config.execution.low_mem, use_fieldwarp=False, # TODO: Fieldwarp is already applied in new sdcflow 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([ (inputnode, bold_stc_wf, [('n_dummy_scans', 'inputnode.skip_vols') ]), (bold_stc_wf, boldbuffer, [('outputnode.stc_file', 'bold_file')]), ]) if not multiecho: workflow.connect([(val_bold, bold_stc_wf, [ (("out_file", pop_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([(val_bold, boldbuffer, [(("out_file", pop_file), 'bold_file')])]) else: # for meepi, iterate over all meepi echos to boldbuffer boldbuffer.iterables = ('bold_file', bold_file) # MULTI-ECHO EPI DATA ############################################# if multiecho: # instantiate relevant interfaces, imports from niworkflows.func.util import init_skullstrip_bold_wf skullstrip_bold_wf = init_skullstrip_bold_wf(name='skullstrip_bold_wf') split_opt_comb = bold_split.clone(name='split_opt_comb') inputnode.inputs.bold_file = ref_file # Replace reference w first echo join_echos = pe.JoinNode( niu.IdentityInterface( fields=['bold_files', 'skullstripped_bold_files']), joinsource=('meepi_echos' if run_stc is True else 'boldbuffer'), joinfield=['bold_files', 'skullstripped_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, name='bold_t2smap_wf') # Mask BOLD reference image final_boldref_masker = pe.Node(BrainExtraction(), name='final_boldref_masker') # MAIN WORKFLOW STRUCTURE ####################################################### workflow.connect([ # BOLD buffer has slice-time corrected if it was run, original otherwise (boldbuffer, bold_split, [('bold_file', 'in_file')]), # HMC (inputnode, bold_hmc_wf, [('bold_ref', 'inputnode.raw_ref_image')]), (inputnode, final_boldref_masker, [('bold_ref', 'in_file')]), (val_bold, bold_hmc_wf, [(("out_file", pop_file), 'inputnode.bold_file')]), (inputnode, summary, [('n_dummy_scans', 'algo_dummy_scans')]), # EPI-T1 registration workflow ( inputnode, bold_reg_wf, [ ('anat_dseg', 'inputnode.t1w_dseg'), # Undefined if --fs-no-reconall, but this is safe ('subjects_dir', 'inputnode.subjects_dir'), ('subject_id', 'inputnode.subject_id'), ('fsnative2anat_xfm', 'inputnode.fsnative2t1w_xfm') ]), (inputnode, bold_reg_wf, [('anat_brain', 'inputnode.t1w_brain')]), (inputnode, bold_t1_trans_wf, [('bold_file', 'inputnode.name_source'), ('anat_mask', 'inputnode.t1w_mask'), ('anat_brain', 'inputnode.t1w_brain'), ('anat_aseg', 'inputnode.t1w_aseg'), ('anat_aparc', 'inputnode.t1w_aparc')]), (bold_reg_wf, outputnode, [('outputnode.itk_bold_to_t1', 'bold2anat_xfm'), ('outputnode.itk_t1_to_bold', 'anat2bold_xfm')]), (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_anat'), ('outputnode.bold_t1_ref', 'bold_anat_ref'), ('outputnode.bold_aseg_t1', 'bold_aseg_anat'), ('outputnode.bold_aparc_t1', 'bold_aparc_anat')]), (bold_reg_wf, summary, [('outputnode.fallback', 'fallback')]), # Connect bold_confounds_wf (inputnode, bold_confounds_wf, [('anat_tpms', 'inputnode.t1w_tpms'), ('anat_mask', 'inputnode.t1w_mask')]), (bold_hmc_wf, bold_confounds_wf, [('outputnode.movpar_file', 'inputnode.movpar_file'), ('outputnode.rmsd_file', 'inputnode.rmsd_file')]), (bold_reg_wf, bold_confounds_wf, [('outputnode.itk_t1_to_bold', 'inputnode.t1_bold_xform')]), (inputnode, bold_confounds_wf, [('n_dummy_scans', 'inputnode.skip_vols')]), (bold_confounds_wf, outputnode, [ ('outputnode.confounds_file', 'confounds'), ('outputnode.confounds_metadata', 'confounds_metadata'), ('outputnode.acompcor_masks', 'acompcor_masks'), ('outputnode.tcompcor_mask', 'tcompcor_mask'), ]), # 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: # TODO: Add SDC workflow.connect([ (inputnode, func_derivatives_wf, [('bold_file', 'inputnode.source_file')]), (bold_bold_trans_wf, bold_confounds_wf, [('outputnode.bold', 'inputnode.bold')]), # (bold_bold_trans_wf, final_boldref_wf, [ # ('outputnode.bold', 'inputnode.bold_file')]), (bold_split, bold_t1_trans_wf, [('out_files', 'inputnode.bold_split')]), (bold_hmc_wf, bold_t1_trans_wf, [('outputnode.xforms', 'inputnode.hmc_xforms')]), # (bold_sdc_wf, bold_t1_trans_wf, [ # ('outputnode.out_warp', 'inputnode.fieldwarp')]) ]) else: # for meepi, 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, join_echos, [('outputnode.bold', 'bold_files') ]), # (join_echos, final_boldref_wf, [ # ('bold_files', 'inputnode.bold_file')]), # TODO: Check with multi-echo data (bold_bold_trans_wf, skullstrip_bold_wf, [('outputnode.bold', 'inputnode.in_file')]), (skullstrip_bold_wf, join_echos, [('outputnode.skull_stripped_file', 'skullstripped_bold_files')]), (join_echos, bold_t2s_wf, [('skullstripped_bold_files', 'inputnode.bold_file')]), (bold_t2s_wf, bold_confounds_wf, [('outputnode.bold', 'inputnode.bold')]), (bold_t2s_wf, split_opt_comb, [('outputnode.bold', 'in_file')]), (split_opt_comb, bold_t1_trans_wf, [('out_files', 'inputnode.bold_split')]), ]) # Already applied in bold_bold_trans_wf, which inputs to bold_t2s_wf bold_t1_trans_wf.inputs.inputnode.fieldwarp = 'identity' bold_t1_trans_wf.inputs.inputnode.hmc_xforms = 'identity' # Map final BOLD mask into T1w space (if required) nonstd_spaces = set(spaces.get_nonstandard()) if nonstd_spaces.intersection(('T1w', 'anat')): from niworkflows.interfaces.fixes import (FixHeaderApplyTransforms as ApplyTransforms) boldmask_to_t1w = pe.Node(ApplyTransforms(interpolation='MultiLabel'), 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')]), (boldmask_to_t1w, outputnode, [('output_image', 'bold_mask_anat') ]), ]) if nonstd_spaces.intersection(('func', 'run', 'bold', 'boldref', 'sbref')): workflow.connect([ (inputnode, func_derivatives_wf, [ ('bold_ref', 'inputnode.bold_native_ref'), ]), (bold_bold_trans_wf if not multiecho else bold_t2s_wf, outputnode, [('outputnode.bold', 'bold_native')]) ]) if spaces.get_spaces(nonstandard=False, dim=(3, )): # Apply transforms in 1 shot # Only use uncompressed output if AROMA is to be run bold_std_trans_wf = init_bold_std_trans_wf( freesurfer=freesurfer, mem_gb=mem_gb['resampled'], omp_nthreads=omp_nthreads, spaces=spaces, name='bold_std_trans_wf', use_compression=not config.execution.low_mem, ) if not has_fieldmap: bold_std_trans_wf.inputs.inputnode.fieldwarp = 'identity' workflow.connect([ (inputnode, bold_std_trans_wf, [('template', 'inputnode.templates'), ('anat2std_xfm', 'inputnode.anat2std_xfm'), ('bold_file', 'inputnode.name_source'), ('anat_aseg', 'inputnode.bold_aseg'), ('anat_aparc', 'inputnode.bold_aparc')]), (bold_reg_wf, bold_std_trans_wf, [('outputnode.itk_bold_to_t1', 'inputnode.itk_bold_to_t1')]), (bold_std_trans_wf, outputnode, [('outputnode.bold_std', 'bold_std'), ('outputnode.bold_std_ref', 'bold_std_ref'), ('outputnode.bold_mask_std', 'bold_mask_std')]), ]) if freesurfer: workflow.connect([ (bold_std_trans_wf, func_derivatives_wf, [ ('outputnode.bold_aseg_std', 'inputnode.bold_aseg_std'), ('outputnode.bold_aparc_std', 'inputnode.bold_aparc_std'), ]), (bold_std_trans_wf, outputnode, [('outputnode.bold_aseg_std', 'bold_aseg_std'), ('outputnode.bold_aparc_std', 'bold_aparc_std')]), ]) if not multiecho: # TODO: Add SDC workflow.connect([ (bold_split, bold_std_trans_wf, [('out_files', 'inputnode.bold_split')]), # (bold_sdc_wf, bold_std_trans_wf, [ # ('outputnode.out_warp', 'inputnode.fieldwarp')]), (bold_hmc_wf, bold_std_trans_wf, [('outputnode.xforms', 'inputnode.hmc_xforms')]), ]) else: workflow.connect([(split_opt_comb, bold_std_trans_wf, [('out_files', 'inputnode.bold_split')])]) # Already applied in bold_bold_trans_wf, which inputs to bold_t2s_wf bold_std_trans_wf.inputs.inputnode.fieldwarp = 'identity' bold_std_trans_wf.inputs.inputnode.hmc_xforms = 'identity' # func_derivatives_wf internally parametrizes over snapshotted spaces. workflow.connect([ (bold_std_trans_wf, func_derivatives_wf, [ ('outputnode.template', 'inputnode.template'), ('outputnode.spatial_reference', 'inputnode.spatial_reference'), ('outputnode.bold_std_ref', 'inputnode.bold_std_ref'), ('outputnode.bold_std', 'inputnode.bold_std'), ('outputnode.bold_mask_std', 'inputnode.bold_mask_std'), ]), ]) if config.workflow.use_aroma: # ICA-AROMA workflow from .confounds import init_ica_aroma_wf ica_aroma_wf = init_ica_aroma_wf( mem_gb=mem_gb['resampled'], metadata=metadata, omp_nthreads=omp_nthreads, err_on_aroma_warn=config.workflow.aroma_err_on_warn, aroma_melodic_dim=config.workflow.aroma_melodic_dim, name='ica_aroma_wf') join = pe.Node(niu.Function(output_names=["out_file"], function=_to_join), name='aroma_confounds') mrg_conf_metadata = pe.Node(niu.Merge(2), name='merge_confound_metadata', run_without_submitting=True) mrg_conf_metadata2 = pe.Node(DictMerge(), name='merge_confound_metadata2', run_without_submitting=True) workflow.disconnect([ (bold_confounds_wf, outputnode, [ ('outputnode.confounds_file', 'confounds'), ]), (bold_confounds_wf, outputnode, [ ('outputnode.confounds_metadata', 'confounds_metadata'), ]), ]) workflow.connect([ (inputnode, ica_aroma_wf, [('bold_file', 'inputnode.name_source')]), (bold_hmc_wf, ica_aroma_wf, [('outputnode.movpar_file', 'inputnode.movpar_file')]), (inputnode, ica_aroma_wf, [('n_dummy_scans', 'inputnode.skip_vols')]), (bold_confounds_wf, join, [('outputnode.confounds_file', 'in_file')]), (bold_confounds_wf, mrg_conf_metadata, [('outputnode.confounds_metadata', 'in1')]), (ica_aroma_wf, join, [('outputnode.aroma_confounds', 'join_file')]), (ica_aroma_wf, mrg_conf_metadata, [('outputnode.aroma_metadata', 'in2')]), (mrg_conf_metadata, mrg_conf_metadata2, [('out', 'in_dicts')]), (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')]), (mrg_conf_metadata2, outputnode, [('out_dict', 'confounds_metadata')]), (bold_std_trans_wf, ica_aroma_wf, [('outputnode.bold_std', 'inputnode.bold_std'), ('outputnode.bold_mask_std', 'inputnode.bold_mask_std'), ('outputnode.spatial_reference', 'inputnode.spatial_reference')]), ]) # SURFACES ################################################################################## # Freesurfer freesurfer_spaces = spaces.get_fs_spaces() if freesurfer and freesurfer_spaces: config.loggers.workflow.debug( 'Creating BOLD surface-sampling workflow.') bold_surf_wf = init_bold_surf_wf( mem_gb=mem_gb['resampled'], surface_spaces=freesurfer_spaces, medial_surface_nan=config.workflow.medial_surface_nan, name='bold_surf_wf') workflow.connect([ (inputnode, bold_surf_wf, [('subjects_dir', 'inputnode.subjects_dir'), ('subject_id', 'inputnode.subject_id'), ('anat2fsnative_xfm', 'inputnode.t1w2fsnative_xfm')]), (bold_t1_trans_wf, bold_surf_wf, [('outputnode.bold_t1', 'inputnode.source_file')]), (bold_surf_wf, outputnode, [('outputnode.surfaces', 'surfaces')]), (bold_surf_wf, func_derivatives_wf, [('outputnode.target', 'inputnode.surf_refs')]), ]) # CIFTI output if config.workflow.cifti_output: from .resampling import init_bold_grayords_wf bold_grayords_wf = init_bold_grayords_wf( grayord_density=config.workflow.cifti_output, mem_gb=mem_gb['resampled'], repetition_time=metadata['RepetitionTime']) workflow.connect([ (inputnode, bold_grayords_wf, [('subjects_dir', 'inputnode.subjects_dir')]), (bold_std_trans_wf, bold_grayords_wf, [('outputnode.bold_std', 'inputnode.bold_std'), ('outputnode.spatial_reference', 'inputnode.spatial_reference')]), (bold_surf_wf, bold_grayords_wf, [ ('outputnode.surfaces', 'inputnode.surf_files'), ('outputnode.target', 'inputnode.surf_refs'), ]), (bold_grayords_wf, outputnode, [('outputnode.cifti_bold', 'bold_cifti'), ('outputnode.cifti_variant', 'cifti_variant'), ('outputnode.cifti_metadata', 'cifti_metadata'), ('outputnode.cifti_density', 'cifti_density')]), ]) if spaces.get_spaces(nonstandard=False, dim=(3, )): if not config.workflow.cifti_output: config.loggers.workflow.critical( "The carpetplot requires CIFTI outputs") else: carpetplot_wf = init_carpetplot_wf( mem_gb=mem_gb['resampled'], metadata=metadata, cifti_output=bool(config.workflow.cifti_output), name='carpetplot_wf') workflow.connect([ (bold_grayords_wf, carpetplot_wf, [('outputnode.cifti_bold', 'inputnode.cifti_bold')]), (bold_confounds_wf, carpetplot_wf, [('outputnode.confounds_file', 'inputnode.confounds_file')]), ]) # REPORTING ############################################################ ds_report_summary = pe.Node(DerivativesDataSink( desc='summary', datatype="figures", dismiss_entities=("echo", )), name='ds_report_summary', run_without_submitting=True, mem_gb=config.DEFAULT_MEMORY_MIN_GB) ds_report_validation = pe.Node(DerivativesDataSink( desc='validation', datatype="figures", dismiss_entities=("echo", )), name='ds_report_validation', run_without_submitting=True, mem_gb=config.DEFAULT_MEMORY_MIN_GB) workflow.connect([ (summary, ds_report_summary, [('out_report', 'in_file')]), (val_bold, ds_report_validation, [(("out_report", pop_file), '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 = nibabies_dir workflow.get_node(node).inputs.source_file = ref_file # Distortion correction if not has_fieldmap: # fmt: off # Finalize workflow with fieldmap-less connections workflow.connect([ (inputnode, final_boldref_masker, [('bold_ref', 'in_file')]), (final_boldref_masker, bold_t1_trans_wf, [ ('out_mask', 'inputnode.ref_bold_mask'), ('out_file', 'inputnode.ref_bold_brain'), ]), (final_boldref_masker, bold_reg_wf, [ ('out_file', 'inputnode.ref_bold_brain'), ]), (final_boldref_masker, bold_confounds_wf, [('out_mask', 'inputnode.bold_mask')]), ]) if nonstd_spaces.intersection(('T1w', 'anat')): workflow.connect([ (final_boldref_masker, boldmask_to_t1w, [('out_mask', 'input_image')]), ]) # (final_boldref_wf, boldmask_to_t1w, [('outputnode.bold_mask', 'input_image')]), # ]) if nonstd_spaces.intersection( ('func', 'run', 'bold', 'boldref', 'sbref')): workflow.connect([ (final_boldref_masker, func_derivatives_wf, [('out_file', 'inputnode.bold_native_ref'), ('out_mask', 'inputnode.bold_mask_native')]), ]) # (final_boldref_wf, func_derivatives_wf, [ # ('outputnode.ref_image', 'inputnode.bold_native_ref'), # ('outputnode.bold_mask', 'inputnode.bold_mask_native')]), # ]) if spaces.get_spaces(nonstandard=False, dim=(3, )): workflow.connect([ (final_boldref_masker, bold_std_trans_wf, [('out_mask', 'inputnode.bold_mask')]), ]) # (final_boldref_wf, bold_std_trans_wf, [ # ('outputnode.bold_mask', 'inputnode.bold_mask')]), # ]) # fmt: on return workflow from niworkflows.interfaces.reportlets.registration import ( SimpleBeforeAfterRPT as SimpleBeforeAfter, ) from niworkflows.interfaces.utility import KeySelect from sdcflows.workflows.apply.registration import init_coeff2epi_wf from sdcflows.workflows.apply.correction import init_unwarp_wf coeff2epi_wf = init_coeff2epi_wf( debug="fieldmaps" in config.execution.debug, omp_nthreads=config.nipype.omp_nthreads, write_coeff=True, ) unwarp_wf = init_unwarp_wf(debug="fieldmaps" in config.execution.debug, omp_nthreads=config.nipype.omp_nthreads) unwarp_wf.inputs.inputnode.metadata = layout.get_metadata(str(bold_file)) output_select = pe.Node( KeySelect(fields=["fmap", "fmap_ref", "fmap_coeff", "fmap_mask"]), name="output_select", run_without_submitting=True, ) output_select.inputs.key = estimator_key[0] if len(estimator_key) > 1: config.loggers.workflow.warning( f"Several fieldmaps <{', '.join(estimator_key)}> are " f"'IntendedFor' <{bold_file}>, using {estimator_key[0]}") sdc_report = pe.Node( SimpleBeforeAfter(before_label="Distorted", after_label="Corrected"), name="sdc_report", mem_gb=0.1, ) ds_report_sdc = pe.Node( DerivativesDataSink(base_directory=nibabies_dir, desc="sdc", suffix="bold", datatype="figures", dismiss_entities=("echo", )), name="ds_report_sdc", run_without_submitting=True, ) unwarp_masker = pe.Node(BrainExtraction(), name='unwarp_masker') # fmt: off workflow.connect([ (inputnode, output_select, [("fmap", "fmap"), ("fmap_ref", "fmap_ref"), ("fmap_coeff", "fmap_coeff"), ("fmap_mask", "fmap_mask"), ("fmap_id", "keys")]), (output_select, coeff2epi_wf, [("fmap_ref", "inputnode.fmap_ref"), ("fmap_coeff", "inputnode.fmap_coeff"), ("fmap_mask", "inputnode.fmap_mask")]), (inputnode, coeff2epi_wf, [("bold_ref", "inputnode.target_ref")]), (final_boldref_masker, coeff2epi_wf, [("out_file", "inputnode.target_mask")]), (inputnode, unwarp_wf, [("bold_ref", "inputnode.distorted")]), (coeff2epi_wf, unwarp_wf, [("outputnode.fmap_coeff", "inputnode.fmap_coeff")]), (inputnode, sdc_report, [("bold_ref", "before")]), (unwarp_wf, sdc_report, [("outputnode.corrected", "after"), ("outputnode.corrected_mask", "wm_seg")]), (inputnode, ds_report_sdc, [("bold_file", "source_file")]), (sdc_report, ds_report_sdc, [("out_report", "in_file")]), # remaining workflow connections (unwarp_wf, unwarp_masker, [('outputnode.corrected', 'in_file')]), (unwarp_masker, bold_confounds_wf, [('out_mask', 'inputnode.bold_mask') ]), (unwarp_masker, bold_t1_trans_wf, [('out_mask', 'inputnode.ref_bold_mask'), ('out_file', 'inputnode.ref_bold_brain')]), # (unwarp_masker, bold_bold_trans_wf, [ # ('out_mask', 'inputnode.bold_mask')]), # Not used within workflow (unwarp_masker, bold_reg_wf, [('out_file', 'inputnode.ref_bold_brain')] ), # TODO: Add distortion correction method to sdcflow outputs? # (bold_sdc_wf, summary, [('outputnode.method', 'distortion_correction')]), ]) if nonstd_spaces.intersection(('T1w', 'anat')): workflow.connect([ (unwarp_masker, boldmask_to_t1w, [('out_mask', 'input_image')]), ]) if nonstd_spaces.intersection(('func', 'run', 'bold', 'boldref', 'sbref')): workflow.connect([ (unwarp_masker, func_derivatives_wf, [('out_file', 'inputnode.bold_native_ref'), ('out_mask', 'inputnode.bold_mask_native')]), ]) if spaces.get_spaces(nonstandard=False, dim=(3, )): workflow.connect([ (unwarp_masker, bold_std_trans_wf, [('out_mask', 'inputnode.bold_mask')]), ]) # fmt: on # if not multiecho: # (bold_sdc_wf, bold_t1_trans_wf, [ # ('outputnode.out_warp', 'inputnode.fieldwarp')]) # (bold_sdc_wf, bold_std_trans_wf, [ # ('outputnode.out_warp', 'inputnode.fieldwarp')]), # ]) return workflow
def init_func_preproc_wf(bold_file): """ This workflow controls the functional preprocessing stages of *fMRIPrep*. Workflow Graph .. workflow:: :graph2use: orig :simple_form: yes from fprodents.workflows.tests import mock_config from fprodents import config from fprodents.workflows.bold.base import init_func_preproc_wf with mock_config(): bold_file = config.execution.bids_dir / 'sub-01' / 'func' \ / 'sub-01_task-mixedgamblestask_run-01_bold.nii.gz' wf = init_func_preproc_wf(str(bold_file)) Inputs ------ bold_file BOLD series NIfTI file t1w_preproc Bias-corrected structural template image t1w_mask Mask of the skull-stripped template image anat_dseg Segmentation of preprocessed structural image, including gray-matter (GM), white-matter (WM) and cerebrospinal fluid (CSF) t1w_asec Segmentation of structural image, done with FreeSurfer. t1w_aparc Parcellation of structural image, done with FreeSurfer. anat_tpms List of tissue probability maps in T1w space template List of templates to target anat2std_xfm List of transform files, collated with templates std2anat_xfm List of inverse transform files, collated with templates 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 Outputs ------- bold_t1 BOLD series, resampled to T1w space bold_mask_t1 BOLD series mask in T1w space bold_std BOLD series, resampled to template space bold_mask_std 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 See Also -------- * :py:func:`~fprodents.workflows.bold.stc.init_bold_stc_wf` * :py:func:`~fprodents.workflows.bold.hmc.init_bold_hmc_wf` * :py:func:`~fprodents.workflows.bold.t2s.init_bold_t2s_wf` * :py:func:`~fprodents.workflows.bold.registration.init_bold_t1_trans_wf` * :py:func:`~fprodents.workflows.bold.registration.init_bold_reg_wf` * :py:func:`~fprodents.workflows.bold.confounds.init_bold_confounds_wf` * :py:func:`~fprodents.workflows.bold.confounds.init_ica_aroma_wf` * :py:func:`~fprodents.workflows.bold.resampling.init_bold_std_trans_wf` * :py:func:`~fprodents.workflows.bold.resampling.init_bold_preproc_trans_wf` * :py:func:`~fprodents.workflows.bold.resampling.init_bold_surf_wf` """ from niworkflows.engine.workflows import LiterateWorkflow as Workflow from niworkflows.interfaces.fixes import FixHeaderApplyTransforms as ApplyTransforms from niworkflows.interfaces.nibabel import ApplyMask from niworkflows.interfaces.utility import KeySelect, DictMerge from nipype.interfaces.freesurfer.utils import LTAConvert from ...patch.utils import extract_entities mem_gb = {"filesize": 1, "resampled": 1, "largemem": 1} bold_tlen = 10 # Have some options handy omp_nthreads = config.nipype.omp_nthreads spaces = config.workflow.spaces output_dir = str(config.execution.output_dir) # Extract BIDS entities and metadata from BOLD file(s) entities = extract_entities(bold_file) layout = config.execution.layout # Take first file as reference ref_file = pop_file(bold_file) metadata = layout.get_metadata(ref_file) echo_idxs = listify(entities.get("echo", [])) multiecho = len(echo_idxs) > 2 if len(echo_idxs) == 1: config.loggers.warning( f"Running a single echo <{ref_file}> from a seemingly multi-echo dataset." ) bold_file = ref_file # Just in case - drop the list if len(echo_idxs) == 2: raise RuntimeError( "Multi-echo processing requires at least three different echos (found two)." ) if multiecho: # Drop echo entity for future queries, have a boolean shorthand entities.pop("echo", None) # reorder echoes from shortest to largest tes, bold_file = zip(*sorted([(layout.get_metadata(bf)["EchoTime"], bf) for bf in bold_file])) ref_file = bold_file[0] # Reset reference to be the shortest TE if os.path.isfile(ref_file): bold_tlen, mem_gb = _create_mem_gb(ref_file) wf_name = _get_wf_name(ref_file) config.loggers.workflow.debug( "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"], ) # Find associated sbref, if possible entities["suffix"] = "sbref" entities["extension"] = ["nii", "nii.gz"] # Overwrite extensions sbref_files = layout.get(return_type="file", **entities) sbref_msg = f"No single-band-reference found for {os.path.basename(ref_file)}." if sbref_files and "sbref" in config.workflow.ignore: sbref_msg = "Single-band reference file(s) found and ignored." elif sbref_files: sbref_msg = "Using single-band reference file(s) {}.".format(",".join( [os.path.basename(sbf) for sbf in sbref_files])) config.loggers.workflow.info(sbref_msg) # Check whether STC must/can be run run_stc = (bool(metadata.get("SliceTiming")) and 'slicetiming' not in config.workflow.ignore) # Build workflow workflow = Workflow(name=wf_name) 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 output 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", "ref_file", "bold_ref_xfm", "n_dummy_scans", "validation_report", "subjects_dir", "subject_id", "anat_preproc", "anat_mask", "anat_dseg", "anat_tpms", "anat2std_xfm", "std2anat_xfm", "template", "anat2fsnative_xfm", "fsnative2anat_xfm", ]), name="inputnode", ) inputnode.inputs.bold_file = bold_file outputnode = pe.Node( niu.IdentityInterface(fields=[ "bold_mask", "bold_t1", "bold_t1_ref", "bold_mask_t1", "bold_std", "bold_std_ref", "bold_mask_std", "bold_native", "surfaces", "confounds", "aroma_noise_ics", "melodic_mix", "nonaggr_denoised_file", "confounds_metadata", ]), name="outputnode", ) # Generate a brain-masked conversion of the t1w and bold reference images t1w_brain = pe.Node(ApplyMask(), name="t1w_brain") lta_convert = pe.Node(LTAConvert(out_fsl=True, out_itk=True), name="lta_convert") # 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( slice_timing=run_stc, registration="FSL", registration_dof=config.workflow.bold2t1w_dof, registration_init=config.workflow.bold2t1w_init, pe_direction=metadata.get("PhaseEncodingDirection"), echo_idx=echo_idxs, tr=metadata.get("RepetitionTime"), distortion_correction="<not implemented>", ), name="summary", mem_gb=config.DEFAULT_MEMORY_MIN_GB, run_without_submitting=True, ) summary.inputs.dummy_scans = config.workflow.dummy_scans func_derivatives_wf = init_func_derivatives_wf( bids_root=layout.root, metadata=metadata, output_dir=output_dir, spaces=spaces, use_aroma=config.workflow.use_aroma, ) # fmt:off workflow.connect([ (outputnode, func_derivatives_wf, [ ('bold_t1', 'inputnode.bold_t1'), ('bold_t1_ref', 'inputnode.bold_t1_ref'), ('bold_mask_t1', 'inputnode.bold_mask_t1'), ('bold_native', 'inputnode.bold_native'), ('confounds', 'inputnode.confounds'), ('surfaces', 'inputnode.surf_files'), ('aroma_noise_ics', 'inputnode.aroma_noise_ics'), ('melodic_mix', 'inputnode.melodic_mix'), ('nonaggr_denoised_file', 'inputnode.nonaggr_denoised_file'), ('confounds_metadata', 'inputnode.confounds_metadata'), ]), ]) # fmt:on # Top-level BOLD splitter bold_split = pe.Node(FSLSplit(dimension="t"), name="bold_split", mem_gb=mem_gb["filesize"] * 3) # calculate BOLD registration to T1w bold_reg_wf = init_bold_reg_wf( bold2t1w_dof=config.workflow.bold2t1w_dof, bold2t1w_init=config.workflow.bold2t1w_init, mem_gb=mem_gb["resampled"], name="bold_reg_wf", 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", use_fieldwarp=False, multiecho=multiecho, mem_gb=mem_gb["resampled"], omp_nthreads=omp_nthreads, use_compression=False, ) t1w_mask_bold_tfm = pe.Node(ApplyTransforms(interpolation="MultiLabel"), name="t1w_mask_bold_tfm", mem_gb=0.1) # get confounds bold_confounds_wf = init_bold_confs_wf( mem_gb=mem_gb["largemem"], metadata=metadata, regressors_all_comps=config.workflow.regressors_all_comps, regressors_fd_th=config.workflow.regressors_fd_th, regressors_dvars_th=config.workflow.regressors_dvars_th, 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 config.execution.low_mem, use_fieldwarp=False, name="bold_bold_trans_wf", ) bold_bold_trans_wf.inputs.inputnode.name_source = ref_file # SLICE-TIME CORRECTION (or bypass) ############################################# if run_stc: bold_stc_wf = init_bold_stc_wf(name="bold_stc_wf", metadata=metadata) # fmt:off workflow.connect([ (inputnode, bold_stc_wf, [("n_dummy_scans", "inputnode.skip_vols") ]), (bold_stc_wf, boldbuffer, [("outputnode.stc_file", "bold_file")]), ]) # fmt:on if not multiecho: # fmt:off workflow.connect([(inputnode, bold_stc_wf, [('bold_file', 'inputnode.bold_file')])]) # fmt:on else: # for meepi, iterate through stc_wf for all workflows meepi_echos = boldbuffer.clone(name="meepi_echos") meepi_echos.iterables = ("bold_file", bold_file) # fmt:off workflow.connect([(meepi_echos, bold_stc_wf, [('bold_file', 'inputnode.bold_file')])]) # fmt:on elif not multiecho: # STC is too short or False # bypass STC from original BOLD to the splitter through boldbuffer # fmt:off workflow.connect([(inputnode, boldbuffer, [('bold_file', 'bold_file')]) ]) # fmt:on else: # for meepi, iterate over all meepi echos to boldbuffer boldbuffer.iterables = ("bold_file", bold_file) # MULTI-ECHO EPI DATA ############################################# if multiecho: from niworkflows.func.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, name="bold_t2smap_wf", ) # fmt:off workflow.connect([ (skullstrip_bold_wf, join_echos, [('outputnode.skull_stripped_file', 'bold_files')]), (join_echos, bold_t2s_wf, [('bold_files', 'inputnode.bold_file')]), ]) # fmt:on # MAIN WORKFLOW STRUCTURE ####################################################### # fmt:off workflow.connect([ (inputnode, bold_reg_wf, [('anat_preproc', 'inputnode.t1w_brain'), ('ref_file', 'inputnode.ref_bold_brain')]), (inputnode, t1w_brain, [('anat_preproc', 'in_file'), ('anat_mask', 'in_mask')]), # convert bold reference LTA transform to other formats (inputnode, lta_convert, [('bold_ref_xfm', 'in_lta')]), # BOLD buffer has slice-time corrected if it was run, original otherwise (boldbuffer, bold_split, [('bold_file', 'in_file')]), (inputnode, summary, [('n_dummy_scans', 'algo_dummy_scans')]), # EPI-T1 registration workflow (inputnode, bold_t1_trans_wf, [('bold_file', 'inputnode.name_source'), ('anat_mask', 'inputnode.t1w_mask'), ('ref_file', 'inputnode.ref_bold_brain') ]), (t1w_brain, bold_t1_trans_wf, [('out_file', 'inputnode.t1w_brain')]), (lta_convert, bold_t1_trans_wf, [('out_itk', 'inputnode.hmc_xforms')]), (bold_reg_wf, bold_t1_trans_wf, [('outputnode.bold2anat', 'inputnode.bold2anat')]), (bold_t1_trans_wf, outputnode, [('outputnode.bold_t1', 'bold_t1'), ('outputnode.bold_t1_ref', 'bold_t1_ref')]), # transform T1 mask to BOLD (inputnode, t1w_mask_bold_tfm, [('anat_mask', 'input_image'), ('ref_file', 'reference_image')]), (bold_reg_wf, t1w_mask_bold_tfm, [('outputnode.anat2bold', 'transforms')]), (t1w_mask_bold_tfm, outputnode, [('output_image', 'bold_mask')]), # Connect bold_confounds_wf (inputnode, bold_confounds_wf, [('anat_tpms', 'inputnode.anat_tpms'), ('anat_mask', 'inputnode.t1w_mask')]), (lta_convert, bold_confounds_wf, [('out_fsl', 'inputnode.movpar_file') ]), (bold_reg_wf, bold_confounds_wf, [('outputnode.anat2bold', 'inputnode.anat2bold')]), (inputnode, bold_confounds_wf, [('n_dummy_scans', 'inputnode.skip_vols')]), (t1w_mask_bold_tfm, bold_confounds_wf, [('output_image', 'inputnode.bold_mask')]), (bold_confounds_wf, outputnode, [('outputnode.confounds_file', 'confounds')]), (bold_confounds_wf, outputnode, [('outputnode.confounds_metadata', 'confounds_metadata')]), # Connect bold_bold_trans_wf (inputnode, bold_bold_trans_wf, [('ref_file', 'inputnode.bold_ref')]), (t1w_mask_bold_tfm, bold_bold_trans_wf, [('output_image', 'inputnode.bold_mask')]), (bold_split, bold_bold_trans_wf, [('out_files', 'inputnode.bold_file') ]), (lta_convert, bold_bold_trans_wf, [('out_itk', 'inputnode.hmc_xforms') ]), # Summary (outputnode, summary, [('confounds', 'confounds_file')]), ]) # fmt:on # for standard EPI data, pass along correct file if not multiecho: # fmt:off workflow.connect([ (inputnode, func_derivatives_wf, [('bold_file', 'inputnode.source_file')]), (bold_bold_trans_wf, bold_confounds_wf, [('outputnode.bold', 'inputnode.bold')]), (bold_split, bold_t1_trans_wf, [('out_files', 'inputnode.bold_split')]), ]) # fmt:on else: # for meepi, create and use optimal combination # fmt:off 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')]), (bold_t2s_wf, bold_t1_trans_wf, [('outputnode.bold', 'inputnode.bold_split')]), ]) # fmt:on # Map final BOLD mask into T1w space (if required) nonstd_spaces = set(spaces.get_nonstandard()) if nonstd_spaces.intersection(("T1w", "anat")): from niworkflows.interfaces.fixes import ( FixHeaderApplyTransforms as ApplyTransforms, ) boldmask_to_t1w = pe.Node( ApplyTransforms(interpolation="MultiLabel"), name="boldmask_to_t1w", mem_gb=0.1, ) # fmt:off workflow.connect([ (bold_reg_wf, boldmask_to_t1w, [('outputnode.bold2anat', 'transforms')]), (bold_t1_trans_wf, boldmask_to_t1w, [('outputnode.bold_mask_t1', 'reference_image')]), (t1w_mask_bold_tfm, boldmask_to_t1w, [('output_image', 'input_image')]), (boldmask_to_t1w, outputnode, [('output_image', 'bold_mask_t1')]), ]) # fmt:on if nonstd_spaces.intersection(("func", "run", "bold", "boldref", "sbref")): # fmt:off workflow.connect([ (bold_bold_trans_wf, outputnode, [('outputnode.bold', 'bold_native')]), (bold_bold_trans_wf, func_derivatives_wf, [('outputnode.bold_ref', 'inputnode.bold_native_ref'), ('outputnode.bold_mask', 'inputnode.bold_mask_native')]), ]) # fmt:on if spaces.get_spaces(nonstandard=False, dim=(3, )): # Apply transforms in 1 shot # Only use uncompressed output if AROMA is to be run bold_std_trans_wf = init_bold_std_trans_wf( mem_gb=mem_gb["resampled"], omp_nthreads=omp_nthreads, spaces=spaces, name="bold_std_trans_wf", use_compression=not config.execution.low_mem, use_fieldwarp=False, ) # fmt:off workflow.connect([ (inputnode, bold_std_trans_wf, [('template', 'inputnode.templates'), ('anat2std_xfm', 'inputnode.anat2std_xfm'), ('bold_file', 'inputnode.name_source')]), (t1w_mask_bold_tfm, bold_std_trans_wf, [('output_image', 'inputnode.bold_mask')]), (lta_convert, bold_std_trans_wf, [('out_itk', 'inputnode.hmc_xforms')]), (bold_reg_wf, bold_std_trans_wf, [('outputnode.bold2anat', 'inputnode.bold2anat')]), (bold_std_trans_wf, outputnode, [('outputnode.bold_std', 'bold_std'), ('outputnode.bold_std_ref', 'bold_std_ref'), ('outputnode.bold_mask_std', 'bold_mask_std')]), ]) # fmt:on if not multiecho: # fmt:off workflow.connect([ (bold_split, bold_std_trans_wf, [("out_files", "inputnode.bold_split")]), ]) # fmt:on else: split_opt_comb = bold_split.clone(name="split_opt_comb") # fmt:off workflow.connect([(bold_t2s_wf, split_opt_comb, [('outputnode.bold', 'in_file')]), (split_opt_comb, bold_std_trans_wf, [('out_files', 'inputnode.bold_split')])]) # fmt:on # func_derivatives_wf internally parametrizes over snapshotted spaces. # fmt:off workflow.connect([ (bold_std_trans_wf, func_derivatives_wf, [ ('outputnode.template', 'inputnode.template'), ('outputnode.spatial_reference', 'inputnode.spatial_reference'), ('outputnode.bold_std_ref', 'inputnode.bold_std_ref'), ('outputnode.bold_std', 'inputnode.bold_std'), ('outputnode.bold_mask_std', 'inputnode.bold_mask_std'), ]), ]) # fmt:on if config.workflow.use_aroma: # ICA-AROMA workflow from .confounds import init_ica_aroma_wf ica_aroma_wf = init_ica_aroma_wf( mem_gb=mem_gb["resampled"], metadata=metadata, omp_nthreads=omp_nthreads, use_fieldwarp=False, err_on_aroma_warn=config.workflow.aroma_err_on_warn, aroma_melodic_dim=config.workflow.aroma_melodic_dim, name="ica_aroma_wf", ) join = pe.Node( niu.Function(output_names=["out_file"], function=_to_join), name="aroma_confounds", ) mrg_conf_metadata = pe.Node( niu.Merge(2), name="merge_confound_metadata", run_without_submitting=True, ) mrg_conf_metadata2 = pe.Node( DictMerge(), name="merge_confound_metadata2", run_without_submitting=True, ) # fmt:off workflow.disconnect([ (bold_confounds_wf, outputnode, [ ('outputnode.confounds_file', 'confounds'), ]), (bold_confounds_wf, outputnode, [ ('outputnode.confounds_metadata', 'confounds_metadata'), ]), ]) workflow.connect([ (inputnode, ica_aroma_wf, [('bold_file', 'inputnode.name_source'), ('n_dummy_scans', 'inputnode.skip_vols')]), (lta_convert, ica_aroma_wf, [('out_fsl', 'inputnode.movpar_file')]), (bold_confounds_wf, join, [('outputnode.confounds_file', 'in_file')]), (bold_confounds_wf, mrg_conf_metadata, [('outputnode.confounds_metadata', 'in1')]), (ica_aroma_wf, join, [('outputnode.aroma_confounds', 'join_file')]), (ica_aroma_wf, mrg_conf_metadata, [('outputnode.aroma_metadata', 'in2')]), (mrg_conf_metadata, mrg_conf_metadata2, [('out', 'in_dicts')]), (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')]), (mrg_conf_metadata2, outputnode, [('out_dict', 'confounds_metadata')]), (bold_std_trans_wf, ica_aroma_wf, [('outputnode.bold_std', 'inputnode.bold_std'), ('outputnode.bold_mask_std', 'inputnode.bold_mask_std'), ('outputnode.spatial_reference', 'inputnode.spatial_reference')]), ]) # fmt:on if spaces.get_spaces(nonstandard=False, dim=(3, )): carpetplot_wf = init_carpetplot_wf( mem_gb=mem_gb["resampled"], metadata=metadata, name="carpetplot_wf", ) # Xform to 'Fischer344' is always computed. carpetplot_select_std = pe.Node( KeySelect(fields=["std2anat_xfm"], key="Fischer344"), name="carpetplot_select_std", run_without_submitting=True, ) # fmt:off workflow.connect([ (inputnode, carpetplot_select_std, [('std2anat_xfm', 'std2anat_xfm'), ('template', 'keys')]), (carpetplot_select_std, carpetplot_wf, [('std2anat_xfm', 'inputnode.std2anat_xfm')]), (bold_bold_trans_wf if not multiecho else bold_t2s_wf, carpetplot_wf, [('outputnode.bold', 'inputnode.bold')]), (t1w_mask_bold_tfm, carpetplot_wf, [('output_image', 'inputnode.bold_mask')]), (bold_reg_wf, carpetplot_wf, [('outputnode.anat2bold', 'inputnode.anat2bold')]), (bold_confounds_wf, carpetplot_wf, [('outputnode.confounds_file', 'inputnode.confounds_file')]), ]) # fmt:on # REPORTING ############################################################ ds_report_summary = pe.Node( DerivativesDataSink(desc="summary", datatype="figures", dismiss_entities=("echo", )), name="ds_report_summary", run_without_submitting=True, mem_gb=config.DEFAULT_MEMORY_MIN_GB, ) ds_report_validation = pe.Node( DerivativesDataSink( base_directory=output_dir, desc="validation", datatype="figures", dismiss_entities=("echo", ), ), name="ds_report_validation", run_without_submitting=True, mem_gb=config.DEFAULT_MEMORY_MIN_GB, ) # fmt:off workflow.connect([ (summary, ds_report_summary, [('out_report', 'in_file')]), (inputnode, ds_report_validation, [('validation_report', 'in_file')]), ]) # fmt:on # 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 = output_dir workflow.get_node(node).inputs.source_file = ref_file 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