def init_bold_std_trans_wf( freesurfer, mem_gb, omp_nthreads, spaces, name='bold_std_trans_wf', use_compression=True, use_fieldwarp=False, ): """ Sample fMRI into standard space with a single-step resampling of the original BOLD series. .. important:: This workflow provides two outputnodes. One output node (with name ``poutputnode``) will be parameterized in a Nipype sense (see `Nipype iterables <https://miykael.github.io/nipype_tutorial/notebooks/basic_iteration.html>`__), and a second node (``outputnode``) will collapse the parameterized outputs into synchronous lists of the output fields listed below. Workflow Graph .. workflow:: :graph2use: colored :simple_form: yes from niworkflows.utils.spaces import SpatialReferences from fmriprep_rodents.workflows.bold import init_bold_std_trans_wf wf = init_bold_std_trans_wf( freesurfer=True, mem_gb=3, omp_nthreads=1, spaces=SpatialReferences( spaces=['MNI152Lin', ('MNIPediatricAsym', {'cohort': '6'})], checkpoint=True), ) Parameters ---------- freesurfer : :obj:`bool` Whether to generate FreeSurfer's aseg/aparc segmentations on BOLD space. mem_gb : :obj:`float` Size of BOLD file in GB omp_nthreads : :obj:`int` Maximum number of threads an individual process may use spaces : :py:class:`~niworkflows.utils.spaces.SpatialReferences` A container for storing, organizing, and parsing spatial normalizations. Composed of :py:class:`~niworkflows.utils.spaces.Reference` objects representing spatial references. Each ``Reference`` contains a space, which is a string of either TemplateFlow template IDs (e.g., ``MNI152Lin``, ``MNI152NLin6Asym``, ``MNIPediatricAsym``), nonstandard references (e.g., ``T1w`` or ``anat``, ``sbref``, ``run``, etc.), or a custom template located in the TemplateFlow root directory. Each ``Reference`` may also contain a spec, which is a dictionary with template specifications (e.g., a specification of ``{'resolution': 2}`` would lead to resampling on a 2mm resolution of the space). name : :obj:`str` Name of workflow (default: ``bold_std_trans_wf``) use_compression : :obj:`bool` Save registered BOLD series as ``.nii.gz`` use_fieldwarp : :obj:`bool` Include SDC warp in single-shot transform from BOLD to MNI Inputs ------ anat2std_xfm List of anatomical-to-standard space transforms generated during spatial normalization. bold_aparc FreeSurfer's ``aparc+aseg.mgz`` atlas projected into the T1w reference (only if ``recon-all`` was run). bold_aseg FreeSurfer's ``aseg.mgz`` atlas projected into the T1w reference (only if ``recon-all`` was run). bold_mask Skull-stripping mask of reference image bold_split Individual 3D volumes, not motion corrected fieldwarp a :abbr:`DFM (displacements field map)` in ITK format hmc_xforms List of affine transforms aligning each volume to ``ref_image`` in ITK format itk_bold_to_t1 Affine transform from ``ref_bold_brain`` to T1 space (ITK format) name_source BOLD series NIfTI file Used to recover original information lost during processing templates List of templates that were applied as targets during spatial normalization. Outputs ------- bold_std BOLD series, resampled to template space bold_std_ref Reference, contrast-enhanced summary of the BOLD series, resampled to template space bold_mask_std BOLD series mask in template space bold_aseg_std FreeSurfer's ``aseg.mgz`` atlas, in template space at the BOLD resolution (only if ``recon-all`` was run) bold_aparc_std FreeSurfer's ``aparc+aseg.mgz`` atlas, in template space at the BOLD resolution (only if ``recon-all`` was run) template Template identifiers synchronized correspondingly to previously described outputs. """ from niworkflows.engine.workflows import LiterateWorkflow as Workflow from niworkflows.func.util import init_bold_reference_wf from niworkflows.interfaces.fixes import FixHeaderApplyTransforms as ApplyTransforms from niworkflows.interfaces.itk import MultiApplyTransforms from niworkflows.interfaces.utility import KeySelect from niworkflows.interfaces.utils import GenerateSamplingReference from niworkflows.interfaces.nilearn import Merge from niworkflows.utils.spaces import format_reference workflow = Workflow(name=name) output_references = spaces.cached.get_spaces(nonstandard=False, dim=(3, )) std_vol_references = [(s.fullname, s.spec) for s in spaces.references if s.standard and s.dim == 3] if len(output_references) == 1: workflow.__desc__ = """\ The BOLD time-series were resampled into standard space, generating a *preprocessed BOLD run in {tpl} space*. """.format(tpl=output_references[0]) elif len(output_references) > 1: workflow.__desc__ = """\ The BOLD time-series were resampled into several standard spaces, correspondingly generating the following *spatially-normalized, preprocessed BOLD runs*: {tpl}. """.format(tpl=', '.join(output_references)) inputnode = pe.Node(niu.IdentityInterface(fields=[ 'anat2std_xfm', 'bold_aparc', 'bold_aseg', 'bold_mask', 'bold_split', 'fieldwarp', 'hmc_xforms', 'itk_bold_to_t1', 'name_source', 'templates', ]), name='inputnode') iterablesource = pe.Node(niu.IdentityInterface(fields=['std_target']), name='iterablesource') # Generate conversions for every template+spec at the input iterablesource.iterables = [('std_target', std_vol_references)] split_target = pe.Node(niu.Function( function=_split_spec, input_names=['in_target'], output_names=['space', 'template', 'spec']), run_without_submitting=True, name='split_target') select_std = pe.Node(KeySelect(fields=['anat2std_xfm']), name='select_std', run_without_submitting=True) select_tpl = pe.Node(niu.Function(function=_select_template), name='select_tpl', run_without_submitting=True) gen_ref = pe.Node(GenerateSamplingReference(), name='gen_ref', mem_gb=0.3) # 256x256x256 * 64 / 8 ~ 150MB) mask_std_tfm = pe.Node(ApplyTransforms(interpolation='MultiLabel'), name='mask_std_tfm', mem_gb=1) # Write corrected file in the designated output dir mask_merge_tfms = pe.Node(niu.Merge(2), name='mask_merge_tfms', run_without_submitting=True, mem_gb=DEFAULT_MEMORY_MIN_GB) nxforms = 3 + use_fieldwarp merge_xforms = pe.Node(niu.Merge(nxforms), name='merge_xforms', run_without_submitting=True, mem_gb=DEFAULT_MEMORY_MIN_GB) workflow.connect([(inputnode, merge_xforms, [('hmc_xforms', 'in%d' % nxforms)])]) if use_fieldwarp: workflow.connect([(inputnode, merge_xforms, [('fieldwarp', 'in3')])]) bold_to_std_transform = pe.Node(MultiApplyTransforms( interpolation="LanczosWindowedSinc", float=True, copy_dtype=True), name='bold_to_std_transform', mem_gb=mem_gb * 3 * omp_nthreads, n_procs=omp_nthreads) merge = pe.Node(Merge(compress=use_compression), name='merge', mem_gb=mem_gb * 3) # Generate a reference on the target standard space gen_final_ref = init_bold_reference_wf(omp_nthreads=omp_nthreads, pre_mask=True) workflow.connect([ (iterablesource, split_target, [('std_target', 'in_target')]), (iterablesource, select_tpl, [('std_target', 'template')]), (inputnode, select_std, [('anat2std_xfm', 'anat2std_xfm'), ('templates', 'keys')]), (inputnode, mask_std_tfm, [('bold_mask', 'input_image')]), (inputnode, gen_ref, [(('bold_split', _first), 'moving_image')]), (inputnode, merge_xforms, [(('itk_bold_to_t1', _aslist), 'in2')]), (inputnode, merge, [('name_source', 'header_source')]), (inputnode, mask_merge_tfms, [(('itk_bold_to_t1', _aslist), 'in2')]), (inputnode, bold_to_std_transform, [('bold_split', 'input_image')]), (split_target, select_std, [('space', 'key')]), (select_std, merge_xforms, [('anat2std_xfm', 'in1')]), (select_std, mask_merge_tfms, [('anat2std_xfm', 'in1')]), (split_target, gen_ref, [(('spec', _is_native), 'keep_native')]), (select_tpl, gen_ref, [('out', 'fixed_image')]), (merge_xforms, bold_to_std_transform, [('out', 'transforms')]), (gen_ref, bold_to_std_transform, [('out_file', 'reference_image')]), (gen_ref, mask_std_tfm, [('out_file', 'reference_image')]), (mask_merge_tfms, mask_std_tfm, [('out', 'transforms')]), (mask_std_tfm, gen_final_ref, [('output_image', 'inputnode.bold_mask') ]), (bold_to_std_transform, merge, [('out_files', 'in_files')]), (merge, gen_final_ref, [('out_file', 'inputnode.bold_file')]), ]) output_names = [ 'bold_mask_std', 'bold_std', 'bold_std_ref', 'spatial_reference', 'template', ] + freesurfer * ['bold_aseg_std', 'bold_aparc_std'] poutputnode = pe.Node(niu.IdentityInterface(fields=output_names), name='poutputnode') workflow.connect([ # Connecting outputnode (iterablesource, poutputnode, [(('std_target', format_reference), 'spatial_reference')]), (merge, poutputnode, [('out_file', 'bold_std')]), (gen_final_ref, poutputnode, [('outputnode.ref_image', 'bold_std_ref') ]), (mask_std_tfm, poutputnode, [('output_image', 'bold_mask_std')]), (select_std, poutputnode, [('key', 'template')]), ]) if freesurfer: # Sample the parcellation files to functional space aseg_std_tfm = pe.Node(ApplyTransforms(interpolation='MultiLabel'), name='aseg_std_tfm', mem_gb=1) aparc_std_tfm = pe.Node(ApplyTransforms(interpolation='MultiLabel'), name='aparc_std_tfm', mem_gb=1) workflow.connect([ (inputnode, aseg_std_tfm, [('bold_aseg', 'input_image')]), (inputnode, aparc_std_tfm, [('bold_aparc', 'input_image')]), (select_std, aseg_std_tfm, [('anat2std_xfm', 'transforms')]), (select_std, aparc_std_tfm, [('anat2std_xfm', 'transforms')]), (gen_ref, aseg_std_tfm, [('out_file', 'reference_image')]), (gen_ref, aparc_std_tfm, [('out_file', 'reference_image')]), (aseg_std_tfm, poutputnode, [('output_image', 'bold_aseg_std')]), (aparc_std_tfm, poutputnode, [('output_image', 'bold_aparc_std')]), ]) # Connect parametric outputs to a Join outputnode outputnode = pe.JoinNode(niu.IdentityInterface(fields=output_names), name='outputnode', joinsource='iterablesource') workflow.connect([ (poutputnode, outputnode, [(f, f) for f in output_names]), ]) 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 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_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.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 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 fmriprep_dir = str(config.execution.fmriprep_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( '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) # 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_sdc, force_syn=config.workflow.force_syn) elif config.workflow.use_syn_sdc 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")) # 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 outputnode = pe.Node(niu.IdentityInterface(fields=[ 'bold_t1', 'bold_t1_ref', 'bold2anat_xfm', 'anat2bold_xfm', '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"), 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 func_derivatives_wf = init_func_derivatives_wf( bids_root=layout.root, cifti_output=config.workflow.cifti_output, freesurfer=freesurfer, metadata=metadata, output_dir=fmriprep_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'), ('bold2anat_xfm', 'inputnode.bold2anat_xfm'), ('anat2bold_xfm', 'inputnode.anat2bold_xfm'), ('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'), ('acompcor_masks', 'inputnode.acompcor_masks'), ('tcompcor_mask', 'inputnode.tcompcor_mask'), ]), ]) # Generate a tentative boldref initial_boldref_wf = init_bold_reference_wf( name='initial_boldref_wf', omp_nthreads=omp_nthreads, bold_file=bold_file, sbref_files=sbref_files, multiecho=multiecho, ) initial_boldref_wf.inputs.inputnode.dummy_scans = config.workflow.dummy_scans # 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, use_compression=False, ) # 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) # 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=bool(fmaps), name='bold_bold_trans_wf') bold_bold_trans_wf.inputs.inputnode.name_source = ref_file # Generate a new BOLD reference # This BOLD references *does not use* single-band reference images. final_boldref_wf = init_bold_reference_wf( name='final_boldref_wf', omp_nthreads=omp_nthreads, multiecho=multiecho, ) final_boldref_wf.__desc__ = None # Unset description to avoid second appearance # 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([ (initial_boldref_wf, bold_stc_wf, [('outputnode.skip_vols', 'inputnode.skip_vols')]), (bold_stc_wf, boldbuffer, [('outputnode.stc_file', 'bold_file')]), ]) if not multiecho: workflow.connect([(initial_boldref_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([(initial_boldref_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.sloppy) # 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') # MAIN WORKFLOW STRUCTURE ####################################################### workflow.connect([ (inputnode, t1w_brain, [('t1w_preproc', 'in_file'), ('t1w_mask', 'in_mask')]), # BOLD buffer has slice-time corrected if it was run, original otherwise (boldbuffer, bold_split, [('bold_file', 'in_file')]), # HMC (initial_boldref_wf, bold_hmc_wf, [('outputnode.raw_ref_image', 'inputnode.raw_ref_image'), ('outputnode.bold_file', 'inputnode.bold_file')]), (initial_boldref_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')]), (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_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')]), (initial_boldref_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.epi_mask', 'inputnode.ref_bold_mask'), ('outputnode.epi_brain', 'inputnode.ref_bold_brain')]), (bold_sdc_wf, bold_bold_trans_wf, [('outputnode.out_warp', 'inputnode.fieldwarp'), ('outputnode.epi_mask', 'inputnode.bold_mask')]), (bold_sdc_wf, bold_reg_wf, [('outputnode.epi_brain', 'inputnode.ref_bold_brain')]), (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'), ('outputnode.rmsd_file', 'inputnode.rmsd_file')]), (bold_reg_wf, bold_confounds_wf, [('outputnode.itk_t1_to_bold', 'inputnode.t1_bold_xform')]), (initial_boldref_wf, bold_confounds_wf, [('outputnode.skip_vols', 'inputnode.skip_vols')]), (final_boldref_wf, bold_confounds_wf, [('outputnode.bold_mask', 'inputnode.bold_mask')]), (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: 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')]), (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' 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')]), (initial_boldref_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 # And ensure echo is dropped from report 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 = "" fmap_unwarp_report_wf.get_node( node).inputs.dismiss_entities = ("echo", ) 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 = "" bold_sdc_wf.get_node(node).inputs.dismiss_entities = ("echo", ) 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')]), (initial_boldref_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 # And ensure echo is dropped from report 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 = "" syn_unwarp_report_wf.get_node( node).inputs.dismiss_entities = ("echo", ) # 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')]), (final_boldref_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([ (final_boldref_wf, func_derivatives_wf, [('outputnode.ref_image', 'inputnode.bold_native_ref'), ('outputnode.bold_mask', 'inputnode.bold_mask_native')]), (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, ) 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')]), (final_boldref_wf, bold_std_trans_wf, [('outputnode.bold_mask', 'inputnode.bold_mask')]), (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: 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')]), (initial_boldref_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, [('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')]), (final_boldref_wf, carpetplot_wf, [('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 ############################################################ 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')]), (initial_boldref_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 = fmriprep_dir workflow.get_node(node).inputs.source_file = ref_file return workflow
def test_pepolar_wf1(bids_layouts, output_path, dataset, workdir): """Test preparation workflow.""" layout = bids_layouts[dataset] if dataset == 'testdata': bold = layout.get(suffix='bold', dir='LR', direction='LR', extension=['.nii.gz', '.nii'])[0] boldref = layout.get(suffix='boldref', dir='LR', direction='LR', desc='brain', extension=['.nii.gz', '.nii'])[0] elif dataset == 'ds001600': bold = layout.get(suffix='bold', acquisition='AP', extension=['.nii.gz', '.nii'])[0] epidata = layout.get(suffix='epi', desc=None, extension=['.nii.gz', '.nii']) matched_pe = check_pes( [(im.path, im.get_metadata()['PhaseEncodingDirection']) for im in epidata], bold.get_metadata()['PhaseEncodingDirection']) wf = init_pepolar_unwarp_wf(omp_nthreads=cpu_count(), matched_pe=matched_pe) wf.inputs.inputnode.fmaps_epi = [ (im.path, im.get_metadata()['PhaseEncodingDirection']) for im in epidata ] wf.inputs.inputnode.bold_pe_dir = bold.get_metadata( )['PhaseEncodingDirection'] if output_path: from nipype.interfaces import utility as niu from ..pepolar import Workflow from ...interfaces.reportlets import FieldmapReportlet boiler = Workflow(name='boiler_%s' % dataset) split_field = pe.Node(niu.Function(function=_split_field), name='split_field') if dataset == 'ds001600': from niworkflows.func.util import init_bold_reference_wf gen_ref = init_bold_reference_wf(omp_nthreads=cpu_count(), bold_file=bold.path) boiler.connect([(gen_ref, wf, [ ('outputnode.ref_image', 'inputnode.in_reference'), ('outputnode.ref_image_brain', 'inputnode.in_reference_brain') ])]) else: wf.inputs.inputnode.in_reference_brain = boldref.path wf.inputs.inputnode.in_reference = boldref.path rep = pe.Node(FieldmapReportlet(), 'simple_report') dsink = pe.Node(DerivativesDataSink(base_directory=str(output_path), keep_dtype=True, desc='pepolar'), name='dsink') dsink.interface.out_path_base = 'sdcflows' dsink.inputs.source_file = epidata[0].path boiler.connect([ (wf, split_field, [('inputnode.bold_pe_dir', 'pe_dir'), ('outputnode.out_warp', 'in_field')]), (split_field, rep, [('out', 'fieldmap')]), ( wf, rep, [ # ('outputnode.out_warp', 'fieldmap'), ('outputnode.out_reference_brain', 'reference'), ('outputnode.out_mask', 'mask') ]), (rep, dsink, [('out_report', 'in_file')]), ]) if workdir: boiler.base_dir = str(workdir) boiler.run(plugin='MultiProc', plugin_args={'n_proc': cpu_count()})
def init_bold_preproc_trans_wf(mem_gb, omp_nthreads, name='bold_preproc_trans_wf', use_compression=True, use_fieldwarp=False, split_file=False, interpolation='LanczosWindowedSinc'): """ Resample in native (original) space. This workflow resamples the input fMRI in its native (original) space in a "single shot" from the original BOLD series. Workflow Graph .. workflow:: :graph2use: colored :simple_form: yes from fmriprep.workflows.bold import init_bold_preproc_trans_wf wf = init_bold_preproc_trans_wf(mem_gb=3, omp_nthreads=1) Parameters ---------- mem_gb : :obj:`float` Size of BOLD file in GB omp_nthreads : :obj:`int` Maximum number of threads an individual process may use name : :obj:`str` Name of workflow (default: ``bold_std_trans_wf``) use_compression : :obj:`bool` Save registered BOLD series as ``.nii.gz`` use_fieldwarp : :obj:`bool` Include SDC warp in single-shot transform from BOLD to MNI split_file : :obj:`bool` Whether the input file should be splitted (it is a 4D file) or it is a list of 3D files (default ``False``, do not split) interpolation : :obj:`str` Interpolation type to be used by ANTs' ``applyTransforms`` (default ``'LanczosWindowedSinc'``) Inputs ------ bold_file Individual 3D volumes, not motion corrected bold_mask Skull-stripping mask of reference image name_source BOLD series NIfTI file Used to recover original information lost during processing hmc_xforms List of affine transforms aligning each volume to ``ref_image`` in ITK format fieldwarp a :abbr:`DFM (displacements field map)` in ITK format Outputs ------- bold BOLD series, resampled in native space, including all preprocessing bold_mask BOLD series mask calculated with the new time-series bold_ref BOLD reference image: an average-like 3D image of the time-series bold_ref_brain Same as ``bold_ref``, but once the brain mask has been applied """ workflow = Workflow(name=name) workflow.__desc__ = """\ The BOLD time-series (including slice-timing correction when applied) were resampled onto their original, native space by applying {transforms}. These resampled BOLD time-series will be referred to as *preprocessed BOLD in original space*, or just *preprocessed BOLD*. """.format(transforms="""\ a single, composite transform to correct for head-motion and susceptibility distortions""" if use_fieldwarp else """\ the transforms to correct for head-motion""") inputnode = pe.Node(niu.IdentityInterface(fields=[ 'name_source', 'bold_file', 'bold_mask', 'hmc_xforms', 'fieldwarp' ]), name='inputnode') outputnode = pe.Node(niu.IdentityInterface( fields=['bold', 'bold_mask', 'bold_ref', 'bold_ref_brain']), name='outputnode') bold_transform = pe.Node(MultiApplyTransforms(interpolation=interpolation, float=True, copy_dtype=True), name='bold_transform', mem_gb=mem_gb * 3 * omp_nthreads, n_procs=omp_nthreads) merge = pe.Node(Merge(compress=use_compression), name='merge', mem_gb=mem_gb * 3) # Generate a new BOLD reference bold_reference_wf = init_bold_reference_wf(omp_nthreads=omp_nthreads) bold_reference_wf.__desc__ = None # Unset description to avoid second appearance workflow.connect([ (inputnode, merge, [('name_source', 'header_source')]), (bold_transform, merge, [('out_files', 'in_files')]), (merge, bold_reference_wf, [('out_file', 'inputnode.bold_file')]), (merge, outputnode, [('out_file', 'bold')]), (bold_reference_wf, outputnode, [('outputnode.ref_image', 'bold_ref'), ('outputnode.ref_image_brain', 'bold_ref_brain'), ('outputnode.bold_mask', 'bold_mask')]), ]) # Input file is not splitted if split_file: bold_split = pe.Node(FSLSplit(dimension='t'), name='bold_split', mem_gb=mem_gb * 3) workflow.connect([(inputnode, bold_split, [('bold_file', 'in_file')]), (bold_split, bold_transform, [ ('out_files', 'input_image'), (('out_files', _first), 'reference_image'), ])]) else: workflow.connect([ (inputnode, bold_transform, [('bold_file', 'input_image'), (('bold_file', _first), 'reference_image')]), ]) if use_fieldwarp: merge_xforms = pe.Node(niu.Merge(2), name='merge_xforms', run_without_submitting=True, mem_gb=DEFAULT_MEMORY_MIN_GB) workflow.connect([ (inputnode, merge_xforms, [('fieldwarp', 'in1'), ('hmc_xforms', 'in2')]), (merge_xforms, bold_transform, [('out', 'transforms')]), ]) else: def _aslist(val): return [val] workflow.connect([ (inputnode, bold_transform, [(('hmc_xforms', _aslist), 'transforms')]), ]) return workflow
def init_bold_t1_trans_wf(freesurfer, mem_gb, omp_nthreads, use_compression=True, name='bold_t1_trans_wf'): """ Co-register the reference BOLD image to T1w-space. The workflow uses :abbr:`BBR (boundary-based registration)`. Workflow Graph .. workflow:: :graph2use: orig :simple_form: yes from fmriprep.workflows.bold.registration import init_bold_t1_trans_wf wf = init_bold_t1_trans_wf(freesurfer=True, mem_gb=3, omp_nthreads=1) Parameters ---------- freesurfer : :obj:`bool` Enable FreeSurfer functional registration (bbregister) mem_gb : :obj:`float` Size of BOLD file in GB omp_nthreads : :obj:`int` Maximum number of threads an individual process may use use_compression : :obj:`bool` Save registered BOLD series as ``.nii.gz`` name : :obj:`str` Name of workflow (default: ``bold_reg_wf``) Inputs ------ name_source BOLD series NIfTI file Used to recover original information lost during processing ref_bold_brain Reference image to which BOLD series is aligned If ``fieldwarp == True``, ``ref_bold_brain`` should be unwarped ref_bold_mask Skull-stripping mask of reference image t1w_brain Skull-stripped bias-corrected structural template image t1w_mask Mask of the skull-stripped template image t1w_aseg FreeSurfer's ``aseg.mgz`` atlas projected into the T1w reference (only if ``recon-all`` was run). t1w_aparc FreeSurfer's ``aparc+aseg.mgz`` atlas projected into the T1w reference (only if ``recon-all`` was run). bold_split Individual 3D BOLD volumes, not motion corrected hmc_xforms List of affine transforms aligning each volume to ``ref_image`` in ITK format itk_bold_to_t1 Affine transform from ``ref_bold_brain`` to T1 space (ITK format) fieldwarp a :abbr:`DFM (displacements field map)` in ITK format Outputs ------- bold_t1 Motion-corrected BOLD series in T1 space bold_t1_ref Reference, contrast-enhanced summary of the motion-corrected BOLD series in T1w space bold_mask_t1 BOLD mask in T1 space bold_aseg_t1 FreeSurfer's ``aseg.mgz`` atlas, in T1w-space at the BOLD resolution (only if ``recon-all`` was run). bold_aparc_t1 FreeSurfer's ``aparc+aseg.mgz`` atlas, in T1w-space at the BOLD resolution (only if ``recon-all`` was run). See also -------- * :py:func:`~fmriprep.workflows.bold.registration.init_bbreg_wf` * :py:func:`~fmriprep.workflows.bold.registration.init_fsl_bbr_wf` """ from niworkflows.engine.workflows import LiterateWorkflow as Workflow from niworkflows.func.util import init_bold_reference_wf from niworkflows.interfaces.fixes import FixHeaderApplyTransforms as ApplyTransforms from niworkflows.interfaces.itk import MultiApplyTransforms from niworkflows.interfaces.nibabel import GenerateSamplingReference from niworkflows.interfaces.nilearn import Merge workflow = Workflow(name=name) inputnode = pe.Node(niu.IdentityInterface(fields=[ 'name_source', 'ref_bold_brain', 'ref_bold_mask', 't1w_brain', 't1w_mask', 't1w_aseg', 't1w_aparc', 'bold_split', 'fieldwarp', 'hmc_xforms', 'itk_bold_to_t1' ]), name='inputnode') outputnode = pe.Node(niu.IdentityInterface(fields=[ 'bold_t1', 'bold_t1_ref', 'bold_mask_t1', 'bold_aseg_t1', 'bold_aparc_t1' ]), name='outputnode') gen_ref = pe.Node(GenerateSamplingReference(), name='gen_ref', mem_gb=0.3) # 256x256x256 * 64 / 8 ~ 150MB mask_t1w_tfm = pe.Node(ApplyTransforms(interpolation='MultiLabel'), name='mask_t1w_tfm', mem_gb=0.1) workflow.connect([ (inputnode, gen_ref, [('ref_bold_brain', 'moving_image'), ('t1w_brain', 'fixed_image'), ('t1w_mask', 'fov_mask')]), (inputnode, mask_t1w_tfm, [('ref_bold_mask', 'input_image')]), (gen_ref, mask_t1w_tfm, [('out_file', 'reference_image')]), (inputnode, mask_t1w_tfm, [('itk_bold_to_t1', 'transforms')]), (mask_t1w_tfm, outputnode, [('output_image', 'bold_mask_t1')]), ]) if freesurfer: # Resample aseg and aparc in T1w space (no transforms needed) aseg_t1w_tfm = pe.Node(ApplyTransforms(interpolation='MultiLabel', transforms='identity'), name='aseg_t1w_tfm', mem_gb=0.1) aparc_t1w_tfm = pe.Node(ApplyTransforms(interpolation='MultiLabel', transforms='identity'), name='aparc_t1w_tfm', mem_gb=0.1) workflow.connect([ (inputnode, aseg_t1w_tfm, [('t1w_aseg', 'input_image')]), (inputnode, aparc_t1w_tfm, [('t1w_aparc', 'input_image')]), (gen_ref, aseg_t1w_tfm, [('out_file', 'reference_image')]), (gen_ref, aparc_t1w_tfm, [('out_file', 'reference_image')]), (aseg_t1w_tfm, outputnode, [('output_image', 'bold_aseg_t1')]), (aparc_t1w_tfm, outputnode, [('output_image', 'bold_aparc_t1')]), ]) bold_to_t1w_transform = pe.Node(MultiApplyTransforms( interpolation="LanczosWindowedSinc", float=True, copy_dtype=True), name='bold_to_t1w_transform', mem_gb=mem_gb * 3 * omp_nthreads, n_procs=omp_nthreads) # merge 3D volumes into 4D timeseries merge = pe.Node(Merge(compress=use_compression), name='merge', mem_gb=mem_gb) # Generate a reference on the target T1w space gen_final_ref = init_bold_reference_wf(omp_nthreads, pre_mask=True) # Merge transforms placing the head motion correction last merge_xforms = pe.Node(niu.Merge(3), name='merge_xforms', run_without_submitting=True, mem_gb=DEFAULT_MEMORY_MIN_GB) workflow.connect([ (inputnode, merge, [('name_source', 'header_source')]), ( inputnode, merge_xforms, [ ('hmc_xforms', 'in3'), # May be 'identity' if HMC already applied ('fieldwarp', 'in2'), # May be 'identity' if SDC already applied ('itk_bold_to_t1', 'in1') ]), (inputnode, bold_to_t1w_transform, [('bold_split', 'input_image')]), (merge_xforms, bold_to_t1w_transform, [('out', 'transforms')]), (gen_ref, bold_to_t1w_transform, [('out_file', 'reference_image')]), (bold_to_t1w_transform, merge, [('out_files', 'in_files')]), (merge, gen_final_ref, [('out_file', 'inputnode.bold_file')]), (mask_t1w_tfm, gen_final_ref, [('output_image', 'inputnode.bold_mask') ]), (merge, outputnode, [('out_file', 'bold_t1')]), (gen_final_ref, outputnode, [('outputnode.ref_image', 'bold_t1_ref')]), ]) return workflow
def init_func_preproc_wf( workdir=None, name="func_preproc_wf", fmap_type=None, memcalc=MemoryCalculator() ): """ simplified from fmriprep/workflows/bold/base.py """ workflow = pe.Workflow(name=name) inputnode = pe.Node( niu.IdentityInterface( fields=["bold_file", "fmaps", "metadata", *in_attrs_from_anat_preproc_wf] ), name="inputnode", ) outputnode = pe.Node( niu.IdentityInterface( fields=[ *func_preproc_wf_out_attrs, "aroma_confounds", "aroma_metadata", "movpar_file", "rmsd_file", "skip_vols", ] ), name="outputnode", ) def get_repetition_time(dic): return dic.get("RepetitionTime") metadatanode = pe.Node(niu.IdentityInterface(fields=["repetition_time"]), name="metadatanode") workflow.connect( [(inputnode, metadatanode, [(("metadata", get_repetition_time), "repetition_time")],)] ) # Generate a brain-masked conversion of the t1w t1w_brain = pe.Node(ApplyMask(), name="t1w_brain") workflow.connect( [(inputnode, t1w_brain, [("t1w_preproc", "in_file"), ("t1w_mask", "in_mask")])] ) # Generate a tentative boldref bold_reference_wf = init_bold_reference_wf(omp_nthreads=config.nipype.omp_nthreads) bold_reference_wf.get_node("inputnode").inputs.dummy_scans = config.workflow.dummy_scans workflow.connect(inputnode, "bold_file", bold_reference_wf, "inputnode.bold_file") workflow.connect(bold_reference_wf, "outputnode.skip_vols", outputnode, "skip_vols") # SDC (SUSCEPTIBILITY DISTORTION CORRECTION) or bypass ########################## bold_sdc_wf = init_sdc_estimate_wf(fmap_type=fmap_type) workflow.connect( [ ( inputnode, bold_sdc_wf, [("fmaps", "inputnode.fmaps"), ("metadata", "inputnode.metadata")], ), ( 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"), ], ), ] ) # Top-level BOLD splitter bold_split = pe.Node(FSLSplit(dimension="t"), name="bold_split", mem_gb=memcalc.series_gb * 3) workflow.connect(inputnode, "bold_file", bold_split, "in_file") # HMC on the BOLD bold_hmc_wf = init_bold_hmc_wf( name="bold_hmc_wf", mem_gb=memcalc.series_gb, omp_nthreads=config.nipype.omp_nthreads, ) workflow.connect( [ ( bold_reference_wf, bold_hmc_wf, [ ("outputnode.raw_ref_image", "inputnode.raw_ref_image"), ("outputnode.bold_file", "inputnode.bold_file"), ], ), ( bold_hmc_wf, outputnode, [("outputnode.movpar_file", "movpar_file"), ("outputnode.rmsd_file", "rmsd_file")], ), ] ) # calculate BOLD registration to T1w bold_reg_wf = init_bold_reg_wf( freesurfer=config.workflow.run_reconall, use_bbr=config.workflow.use_bbr, bold2t1w_dof=config.workflow.bold2t1w_dof, bold2t1w_init=config.workflow.bold2t1w_init, mem_gb=memcalc.series_std_gb, omp_nthreads=config.nipype.omp_nthreads, use_compression=False, ) workflow.connect( [ (inputnode, bold_reg_wf, [("t1w_dseg", "inputnode.t1w_dseg")]), (t1w_brain, bold_reg_wf, [("out_file", "inputnode.t1w_brain")]), (bold_sdc_wf, bold_reg_wf, [("outputnode.epi_brain", "inputnode.ref_bold_brain")],), ] ) # apply BOLD registration to T1w bold_t1_trans_wf = init_bold_t1_trans_wf( name="bold_t1_trans_wf", freesurfer=config.workflow.run_reconall, use_fieldwarp=fmap_type is not None, multiecho=False, mem_gb=memcalc.series_std_gb, omp_nthreads=config.nipype.omp_nthreads, use_compression=False, ) workflow.connect( [ ( inputnode, bold_t1_trans_wf, [("bold_file", "inputnode.name_source"), ("t1w_mask", "inputnode.t1w_mask")], ), ( bold_sdc_wf, bold_t1_trans_wf, [ ("outputnode.epi_brain", "inputnode.ref_bold_brain"), ("outputnode.epi_mask", "inputnode.ref_bold_mask"), ("outputnode.out_warp", "inputnode.fieldwarp"), ], ), (t1w_brain, bold_t1_trans_wf, [("out_file", "inputnode.t1w_brain")]), (bold_split, bold_t1_trans_wf, [("out_files", "inputnode.bold_split")]), (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")], ), ] ) # 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=memcalc.series_std_gb, omp_nthreads=config.nipype.omp_nthreads, use_compression=not config.execution.low_mem, use_fieldwarp=True, name="bold_bold_trans_wf", ) # bold_bold_trans_wf.inputs.inputnode.name_source = ref_file workflow.connect( [ (inputnode, bold_bold_trans_wf, [("bold_file", "inputnode.name_source")]), (bold_split, bold_bold_trans_wf, [("out_files", "inputnode.bold_file")]), (bold_hmc_wf, bold_bold_trans_wf, [("outputnode.xforms", "inputnode.hmc_xforms")],), ( bold_sdc_wf, bold_bold_trans_wf, [ ("outputnode.out_warp", "inputnode.fieldwarp"), ("outputnode.epi_mask", "inputnode.bold_mask"), ], ), ] ) # 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=config.workflow.run_reconall, mem_gb=memcalc.series_std_gb, omp_nthreads=config.nipype.omp_nthreads, spaces=config.workflow.spaces, name="bold_std_trans_wf", use_compression=not config.execution.low_mem, use_fieldwarp=fmap_type is not None, ) workflow.connect( [ ( inputnode, bold_std_trans_wf, [ ("template", "inputnode.templates"), ("anat2std_xfm", "inputnode.anat2std_xfm"), ("bold_file", "inputnode.name_source"), ], ), (bold_split, bold_std_trans_wf, [("out_files", "inputnode.bold_split")]), (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, 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"), ], ), ] ) ica_aroma_wf = init_ica_aroma_wf( mem_gb=memcalc.series_std_gb, metadata={"RepetitionTime": np.nan}, omp_nthreads=config.nipype.omp_nthreads, use_fieldwarp=True, err_on_aroma_warn=config.workflow.aroma_err_on_warn, aroma_melodic_dim=config.workflow.aroma_melodic_dim, name="ica_aroma_wf", ) ica_aroma_wf.get_node("ica_aroma").inputs.denoise_type = "no" ica_aroma_wf.remove_nodes([ica_aroma_wf.get_node("add_nonsteady")]) workflow.connect( [ (inputnode, ica_aroma_wf, [("bold_file", "inputnode.name_source")]), ( metadatanode, ica_aroma_wf, [("repetition_time", "melodic.tr_sec",), ("repetition_time", "ica_aroma.TR")], ), (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_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"), ], ), ( ica_aroma_wf, outputnode, [ ("outputnode.aroma_noise_ics", "aroma_noise_ics"), ("outputnode.melodic_mix", "melodic_mix"), ("outputnode.nonaggr_denoised_file", "nonaggr_denoised_file"), ("outputnode.aroma_confounds", "aroma_confounds"), ("outputnode.aroma_metadata", "aroma_metadata"), ], ), ] ) bold_confounds_wf = init_bold_confs_wf( mem_gb=memcalc.series_std_gb, 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] workflow.connect( [ ( 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"), ("outputnode.rmsd_file", "inputnode.rmsd_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")], ), ( metadatanode, bold_confounds_wf, [ ("repetition_time", "acompcor.repetition_time"), ("repetition_time", "tcompcor.repetition_time"), ], ), ( bold_bold_trans_wf, bold_confounds_wf, [ ("outputnode.bold", "inputnode.bold"), ("outputnode.bold_mask", "inputnode.bold_mask"), ], ), ] ) carpetplot_wf = init_carpetplot_wf( mem_gb=memcalc.series_std_gb, metadata={"RepetitionTime": np.nan}, cifti_output=config.workflow.cifti_output, name="carpetplot_wf", ) 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, 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")], ), (metadatanode, carpetplot_wf, [("repetition_time", "conf_plot.tr")]), ] ) # Custom make_reportnode(workflow) assert workdir is not None make_reportnode_datasink(workflow, workdir) return workflow