def init_fmriprep_adapter_wf( name: str = "fmriprep_adapter_wf", memcalc: MemoryCalculator = MemoryCalculator.default(), ): workflow = pe.Workflow(name=name) inputnode = pe.Node( niu.IdentityInterface( fields=[ "bold_std", "bold_mask_std", "spatial_reference", "confounds", "vals", ] ), name="inputnode", ) outputnode = pe.Node( niu.IdentityInterface(fields=["files", "mask", "vals"]), name="outputnode", ) select_std = pe.Node( KeySelect(fields=["bold_std", "bold_mask_std"]), name="select_std", run_without_submitting=True, nohash=True, ) select_std.inputs.key = f"{constants.reference_space}_res-{constants.reference_res}" workflow.connect(inputnode, "bold_std", select_std, "bold_std") workflow.connect(inputnode, "bold_mask_std", select_std, "bold_mask_std") workflow.connect(inputnode, "spatial_reference", select_std, "keys") # applymask = pe.Node( interface=fsl.ApplyMask(), name="applymask", mem_gb=memcalc.series_std_gb, ) workflow.connect(select_std, "bold_std", applymask, "in_file") workflow.connect(select_std, "bold_mask_std", applymask, "mask_file") # merge = pe.Node(niu.Merge(2), name="merge") workflow.connect(applymask, "out_file", merge, "in1") workflow.connect(inputnode, "confounds", merge, "in2") # workflow.connect(merge, "out", outputnode, "files") workflow.connect(select_std, "bold_mask_std", outputnode, "mask") workflow.connect(inputnode, "vals", outputnode, "vals") return workflow
def __call__(self, parentworkflow, out_wf, in_wf, out_nodename="outputnode", in_nodename="inputnode"): if out_nodename is None: out_nodename = "" else: out_nodename = f"{out_nodename}." if in_nodename is None: in_nodename = "" else: in_nodename = f"{in_nodename}." parentworkflow.connect([( out_wf, in_wf, [(f"{out_nodename}{attr}", f"{in_nodename}{attr}") for attr in self._attrs], )]) if self._keyAttr is not None: assert self._keyVal is not None assert self._keySelectAttrs is not None and len( self._keySelectAttrs) > 0 hexval = hexdigest( (self._keyAttr, self._keyVal, self._keySelectAttrs)) selectnodename = f"keyselect_from_{out_wf.name}_{hexval}" select = parentworkflow.get_node(selectnodename) if select is None: select = pe.Node( interface=KeySelect(fields=self._keySelectAttrs, key=self._keyVal), name=selectnodename, run_without_submitting=True, ) parentworkflow.connect(out_wf, self._keyAttr, select, "keys") parentworkflow.connect([ ( out_wf, select, [(f"{out_nodename}{attr}", attr) for attr in self._keySelectAttrs], ), ]) parentworkflow.connect([ ( select, in_wf, [(attr, f"{in_nodename}{attr}") for attr in self._keySelectAttrs], ), ])
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_anat_preproc_wf( *, bids_root, freesurfer, hires, longitudinal, t1w, omp_nthreads, output_dir, skull_strip_mode, skull_strip_template, spaces, debug=False, existing_derivatives=None, name='anat_preproc_wf', skull_strip_fixed_seed=False, ): """ Stage the anatomical preprocessing steps of *sMRIPrep*. This includes: - T1w reference: realigning and then averaging T1w images. - Brain extraction and INU (bias field) correction. - Brain tissue segmentation. - Spatial normalization to standard spaces. - Surface reconstruction with FreeSurfer_. .. include:: ../links.rst Workflow Graph .. workflow:: :graph2use: orig :simple_form: yes from niworkflows.utils.spaces import SpatialReferences, Reference from smriprep.workflows.anatomical import init_anat_preproc_wf wf = init_anat_preproc_wf( bids_root='.', freesurfer=True, hires=True, longitudinal=False, t1w=['t1w.nii.gz'], omp_nthreads=1, output_dir='.', skull_strip_mode='force', skull_strip_template=Reference('OASIS30ANTs'), spaces=SpatialReferences(spaces=['MNI152NLin2009cAsym', 'fsaverage5']), ) Parameters ---------- bids_root : :obj:`str` Path of the input BIDS dataset root existing_derivatives : :obj:`dict` or None Dictionary mapping output specification attribute names and paths to corresponding derivatives. freesurfer : :obj:`bool` Enable FreeSurfer surface reconstruction (increases runtime by 6h, at the very least) hires : :obj:`bool` Enable sub-millimeter preprocessing in FreeSurfer longitudinal : :obj:`bool` Create unbiased structural template, regardless of number of inputs (may increase runtime) t1w : :obj:`list` List of T1-weighted structural images. omp_nthreads : :obj:`int` Maximum number of threads an individual process may use output_dir : :obj:`str` Directory in which to save derivatives skull_strip_template : :py:class:`~niworkflows.utils.spaces.Reference` Spatial reference to use in atlas-based brain extraction. spaces : :py:class:`~niworkflows.utils.spaces.SpatialReferences` Object containing standard and nonstandard space specifications. debug : :obj:`bool` Enable debugging outputs name : :obj:`str`, optional Workflow name (default: anat_preproc_wf) skull_strip_mode : :obj:`str` Determiner for T1-weighted skull stripping (`force` ensures skull stripping, `skip` ignores skull stripping, and `auto` automatically ignores skull stripping if pre-stripped brains are detected). skull_strip_fixed_seed : :obj:`bool` Do not use a random seed for skull-stripping - will ensure run-to-run replicability when used with --omp-nthreads 1 (default: ``False``). Inputs ------ t1w List of T1-weighted structural images t2w List of T2-weighted structural images roi A mask to exclude regions during standardization flair List of FLAIR images subjects_dir FreeSurfer SUBJECTS_DIR subject_id FreeSurfer subject ID Outputs ------- t1w_preproc The T1w reference map, which is calculated as the average of bias-corrected and preprocessed T1w images, defining the anatomical space. t1w_brain Skull-stripped ``t1w_preproc`` t1w_mask Brain (binary) mask estimated by brain extraction. t1w_dseg Brain tissue segmentation of the preprocessed structural image, including gray-matter (GM), white-matter (WM) and cerebrospinal fluid (CSF). t1w_tpms List of tissue probability maps corresponding to ``t1w_dseg``. std_preproc T1w reference resampled in one or more standard spaces. std_mask Mask of skull-stripped template, in MNI space std_dseg Segmentation, resampled into MNI space std_tpms List of tissue probability maps in MNI space subjects_dir FreeSurfer SUBJECTS_DIR anat2std_xfm Nonlinear spatial transform to resample imaging data given in anatomical space into standard space. std2anat_xfm Inverse transform of the above. subject_id FreeSurfer subject ID t1w2fsnative_xfm LTA-style affine matrix translating from T1w to FreeSurfer-conformed subject space fsnative2t1w_xfm LTA-style affine matrix translating from FreeSurfer-conformed subject space to T1w surfaces GIFTI surfaces (gray/white boundary, midthickness, pial, inflated) See Also -------- * :py:func:`~niworkflows.anat.ants.init_brain_extraction_wf` * :py:func:`~smriprep.workflows.surfaces.init_surface_recon_wf` """ workflow = Workflow(name=name) num_t1w = len(t1w) desc = """Anatomical data preprocessing : """ desc += """\ A total of {num_t1w} T1-weighted (T1w) images were found within the input BIDS dataset.""".format(num_t1w=num_t1w) inputnode = pe.Node(niu.IdentityInterface( fields=['t1w', 't2w', 'roi', 'flair', 'subjects_dir', 'subject_id']), name='inputnode') outputnode = pe.Node(niu.IdentityInterface( fields=['template', 'subjects_dir', 'subject_id'] + get_outputnode_spec()), name='outputnode') # Connect reportlets workflows anat_reports_wf = init_anat_reports_wf( freesurfer=freesurfer, output_dir=output_dir, ) workflow.connect([ (outputnode, anat_reports_wf, [('t1w_preproc', 'inputnode.t1w_preproc'), ('t1w_mask', 'inputnode.t1w_mask'), ('t1w_dseg', 'inputnode.t1w_dseg')]), ]) if existing_derivatives is not None: LOGGER.log( 25, "Anatomical workflow will reuse prior derivatives found in the " "output folder (%s).", output_dir) desc += """ Anatomical preprocessing was reused from previously existing derivative objects.\n""" workflow.__desc__ = desc templates = existing_derivatives.pop('template') templatesource = pe.Node(niu.IdentityInterface(fields=['template']), name='templatesource') templatesource.iterables = [('template', templates)] outputnode.inputs.template = templates for field, value in existing_derivatives.items(): setattr(outputnode.inputs, field, value) anat_reports_wf.inputs.inputnode.source_file = fix_multi_T1w_source_name( [existing_derivatives['t1w_preproc']]) stdselect = pe.Node(KeySelect(fields=['std_preproc', 'std_mask'], keys=templates), name='stdselect', run_without_submitting=True) workflow.connect([ (inputnode, outputnode, [('subjects_dir', 'subjects_dir'), ('subject_id', 'subject_id')]), (inputnode, anat_reports_wf, [('subjects_dir', 'inputnode.subjects_dir'), ('subject_id', 'inputnode.subject_id')]), (templatesource, stdselect, [('template', 'key')]), (outputnode, stdselect, [('std_preproc', 'std_preproc'), ('std_mask', 'std_mask')]), (stdselect, anat_reports_wf, [ ('key', 'inputnode.template'), ('std_preproc', 'inputnode.std_t1w'), ('std_mask', 'inputnode.std_mask'), ]), ]) return workflow # The workflow is not cached. desc += """ All of them were corrected for intensity non-uniformity (INU) """ if num_t1w > 1 else """\ The T1-weighted (T1w) image was corrected for intensity non-uniformity (INU) """ desc += """\ with `N4BiasFieldCorrection` [@n4], distributed with ANTs {ants_ver} \ [@ants, RRID:SCR_004757]""" desc += '.\n' if num_t1w > 1 else ", and used as T1w-reference throughout the workflow.\n" desc += """\ The T1w-reference was then skull-stripped with a *Nipype* implementation of the `antsBrainExtraction.sh` workflow (from ANTs), using {skullstrip_tpl} as target template. Brain tissue segmentation of cerebrospinal fluid (CSF), white-matter (WM) and gray-matter (GM) was performed on the brain-extracted T1w using `fast` [FSL {fsl_ver}, RRID:SCR_002823, @fsl_fast]. """ workflow.__desc__ = desc.format( ants_ver=ANTsInfo.version() or '(version unknown)', fsl_ver=fsl.FAST().version or '(version unknown)', num_t1w=num_t1w, skullstrip_tpl=skull_strip_template.fullname, ) buffernode = pe.Node( niu.IdentityInterface(fields=['t1w_brain', 't1w_mask']), name='buffernode') # 1. Anatomical reference generation - average input T1w images. anat_template_wf = init_anat_template_wf(longitudinal=longitudinal, omp_nthreads=omp_nthreads, num_t1w=num_t1w) anat_validate = pe.Node(ValidateImage(), name='anat_validate', run_without_submitting=True) # 2. Brain-extraction and INU (bias field) correction. if skull_strip_mode == 'auto': import numpy as np import nibabel as nb def _is_skull_stripped(imgs): """Check if T1w images are skull-stripped.""" def _check_img(img): data = np.abs(nb.load(img).get_fdata(dtype=np.float32)) sidevals = data[0, :, :].sum() + data[-1, :, :].sum() + \ data[:, 0, :].sum() + data[:, -1, :].sum() + \ data[:, :, 0].sum() + data[:, :, -1].sum() return sidevals < 10 return all(_check_img(img) for img in imgs) skull_strip_mode = _is_skull_stripped(t1w) if skull_strip_mode in (True, 'skip'): brain_extraction_wf = init_n4_only_wf( omp_nthreads=omp_nthreads, atropos_use_random_seed=not skull_strip_fixed_seed, ) else: brain_extraction_wf = init_brain_extraction_wf( in_template=skull_strip_template.space, template_spec=skull_strip_template.spec, atropos_use_random_seed=not skull_strip_fixed_seed, omp_nthreads=omp_nthreads, normalization_quality='precise' if not debug else 'testing') # 4. Spatial normalization anat_norm_wf = init_anat_norm_wf( debug=debug, omp_nthreads=omp_nthreads, templates=spaces.get_spaces(nonstandard=False, dim=(3, )), ) workflow.connect([ # Step 1. (inputnode, anat_template_wf, [('t1w', 'inputnode.t1w')]), (anat_template_wf, anat_validate, [('outputnode.t1w_ref', 'in_file')]), (anat_validate, brain_extraction_wf, [('out_file', 'inputnode.in_files')]), (brain_extraction_wf, outputnode, [(('outputnode.bias_corrected', _pop), 't1w_preproc')]), (anat_template_wf, outputnode, [('outputnode.t1w_realign_xfm', 't1w_ref_xfms')]), (buffernode, outputnode, [('t1w_brain', 't1w_brain'), ('t1w_mask', 't1w_mask')]), # Steps 2, 3 and 4 (inputnode, anat_norm_wf, [(('t1w', fix_multi_T1w_source_name), 'inputnode.orig_t1w'), ('roi', 'inputnode.lesion_mask')]), (brain_extraction_wf, anat_norm_wf, [(('outputnode.bias_corrected', _pop), 'inputnode.moving_image')]), (buffernode, anat_norm_wf, [('t1w_mask', 'inputnode.moving_mask')]), (anat_norm_wf, outputnode, [ ('poutputnode.standardized', 'std_preproc'), ('poutputnode.std_mask', 'std_mask'), ('poutputnode.std_dseg', 'std_dseg'), ('poutputnode.std_tpms', 'std_tpms'), ('outputnode.template', 'template'), ('outputnode.anat2std_xfm', 'anat2std_xfm'), ('outputnode.std2anat_xfm', 'std2anat_xfm'), ]), ]) # Change LookUp Table - BIDS wants: 0 (bg), 1 (gm), 2 (wm), 3 (csf) lut_t1w_dseg = pe.Node(niu.Function(function=_apply_bids_lut), name='lut_t1w_dseg') workflow.connect([ (lut_t1w_dseg, anat_norm_wf, [('out', 'inputnode.moving_segmentation') ]), (lut_t1w_dseg, outputnode, [('out', 't1w_dseg')]), ]) # Connect reportlets workflow.connect([ (inputnode, anat_reports_wf, [(('t1w', fix_multi_T1w_source_name), 'inputnode.source_file')]), (outputnode, anat_reports_wf, [ ('std_preproc', 'inputnode.std_t1w'), ('std_mask', 'inputnode.std_mask'), ]), (anat_template_wf, anat_reports_wf, [('outputnode.out_report', 'inputnode.t1w_conform_report')]), (anat_norm_wf, anat_reports_wf, [('poutputnode.template', 'inputnode.template')]), ]) # Write outputs ############################################3 anat_derivatives_wf = init_anat_derivatives_wf( bids_root=bids_root, freesurfer=freesurfer, num_t1w=num_t1w, output_dir=output_dir, ) workflow.connect([ # Connect derivatives (anat_template_wf, anat_derivatives_wf, [('outputnode.t1w_valid_list', 'inputnode.source_files')]), (anat_norm_wf, anat_derivatives_wf, [('poutputnode.template', 'inputnode.template'), ('poutputnode.anat2std_xfm', 'inputnode.anat2std_xfm'), ('poutputnode.std2anat_xfm', 'inputnode.std2anat_xfm')]), (outputnode, anat_derivatives_wf, [ ('std_preproc', 'inputnode.std_t1w'), ('t1w_ref_xfms', 'inputnode.t1w_ref_xfms'), ('t1w_preproc', 'inputnode.t1w_preproc'), ('t1w_mask', 'inputnode.t1w_mask'), ('t1w_dseg', 'inputnode.t1w_dseg'), ('t1w_tpms', 'inputnode.t1w_tpms'), ('std_mask', 'inputnode.std_mask'), ('std_dseg', 'inputnode.std_dseg'), ('std_tpms', 'inputnode.std_tpms'), ]), ]) if not freesurfer: # Flag --fs-no-reconall is set - return # Brain tissue segmentation - FAST produces: 0 (bg), 1 (wm), 2 (csf), 3 (gm) t1w_dseg = pe.Node(fsl.FAST(segments=True, no_bias=True, probability_maps=True), name='t1w_dseg', mem_gb=3) lut_t1w_dseg.inputs.lut = (0, 3, 1, 2 ) # Maps: 0 -> 0, 3 -> 1, 1 -> 2, 2 -> 3. fast2bids = pe.Node(niu.Function(function=_probseg_fast2bids), name="fast2bids", run_without_submitting=True) workflow.connect([ (brain_extraction_wf, buffernode, [(('outputnode.out_file', _pop), 't1w_brain'), ('outputnode.out_mask', 't1w_mask')]), (buffernode, t1w_dseg, [('t1w_brain', 'in_files')]), (t1w_dseg, lut_t1w_dseg, [('partial_volume_map', 'in_dseg')]), (t1w_dseg, fast2bids, [('partial_volume_files', 'inlist')]), (fast2bids, anat_norm_wf, [('out', 'inputnode.moving_tpms')]), (fast2bids, outputnode, [('out', 't1w_tpms')]), ]) return workflow # Map FS' aseg labels onto three-tissue segmentation lut_t1w_dseg.inputs.lut = _aseg_to_three() split_seg = pe.Node(niu.Function(function=_split_segments), name='split_seg') # check for older IsRunning files and remove accordingly fs_isrunning = pe.Node(niu.Function(function=_fs_isRunning), overwrite=True, name='fs_isrunning') fs_isrunning.inputs.logger = LOGGER # 5. Surface reconstruction (--fs-no-reconall not set) surface_recon_wf = init_surface_recon_wf(name='surface_recon_wf', omp_nthreads=omp_nthreads, hires=hires) applyrefined = pe.Node(fsl.ApplyMask(), name='applyrefined') workflow.connect([ (inputnode, fs_isrunning, [('subjects_dir', 'subjects_dir'), ('subject_id', 'subject_id')]), (inputnode, surface_recon_wf, [('t2w', 'inputnode.t2w'), ('flair', 'inputnode.flair'), ('subject_id', 'inputnode.subject_id') ]), (fs_isrunning, surface_recon_wf, [('out', 'inputnode.subjects_dir')]), (anat_validate, surface_recon_wf, [('out_file', 'inputnode.t1w')]), (brain_extraction_wf, surface_recon_wf, [(('outputnode.out_file', _pop), 'inputnode.skullstripped_t1'), ('outputnode.out_segm', 'inputnode.ants_segs'), (('outputnode.bias_corrected', _pop), 'inputnode.corrected_t1')]), (brain_extraction_wf, applyrefined, [(('outputnode.bias_corrected', _pop), 'in_file')]), (surface_recon_wf, applyrefined, [('outputnode.out_brainmask', 'mask_file')]), (surface_recon_wf, lut_t1w_dseg, [('outputnode.out_aseg', 'in_dseg')]), (lut_t1w_dseg, split_seg, [('out', 'in_file')]), (split_seg, anat_norm_wf, [('out', 'inputnode.moving_tpms')]), (split_seg, outputnode, [('out', 't1w_tpms')]), (surface_recon_wf, outputnode, [('outputnode.subjects_dir', 'subjects_dir'), ('outputnode.subject_id', 'subject_id'), ('outputnode.t1w2fsnative_xfm', 't1w2fsnative_xfm'), ('outputnode.fsnative2t1w_xfm', 'fsnative2t1w_xfm'), ('outputnode.surfaces', 'surfaces'), ('outputnode.out_aseg', 't1w_aseg'), ('outputnode.out_aparc', 't1w_aparc')]), (applyrefined, buffernode, [('out_file', 't1w_brain')]), (surface_recon_wf, buffernode, [('outputnode.out_brainmask', 't1w_mask')]), (surface_recon_wf, anat_reports_wf, [('outputnode.subject_id', 'inputnode.subject_id'), ('outputnode.subjects_dir', 'inputnode.subjects_dir')]), (surface_recon_wf, anat_derivatives_wf, [ ('outputnode.out_aseg', 'inputnode.t1w_fs_aseg'), ('outputnode.out_aparc', 'inputnode.t1w_fs_aparc'), ]), (outputnode, anat_derivatives_wf, [ ('t1w2fsnative_xfm', 'inputnode.t1w2fsnative_xfm'), ('fsnative2t1w_xfm', 'inputnode.fsnative2t1w_xfm'), ('surfaces', 'inputnode.surfaces'), ]), ]) return workflow
def init_ica_aroma_wf( mem_gb, metadata, omp_nthreads, aroma_melodic_dim=-200, err_on_aroma_warn=False, name='ica_aroma_wf', susan_fwhm=6.0, ): """ Build a workflow that runs `ICA-AROMA`_. This workflow wraps `ICA-AROMA`_ to identify and remove motion-related independent components from a BOLD time series. The following steps are performed: #. Remove non-steady state volumes from the bold series. #. Smooth data using FSL `susan`, with a kernel width FWHM=6.0mm. #. Run FSL `melodic` outside of ICA-AROMA to generate the report #. Run ICA-AROMA #. Aggregate identified motion components (aggressive) to TSV #. Return ``classified_motion_ICs`` and ``melodic_mix`` for user to complete non-aggressive denoising in T1w space #. Calculate ICA-AROMA-identified noise components (columns named ``AROMAAggrCompXX``) Additionally, non-aggressive denoising is performed on the BOLD series resampled into MNI space. There is a current discussion on whether other confounds should be extracted before or after denoising `here <http://nbviewer.jupyter.org/github/poldracklab/fmriprep-notebooks/blob/922e436429b879271fa13e76767a6e73443e74d9/issue-817_aroma_confounds.ipynb>`__. .. _ICA-AROMA: https://github.com/maartenmennes/ICA-AROMA Workflow Graph .. workflow:: :graph2use: orig :simple_form: yes from fmriprep.workflows.bold.confounds import init_ica_aroma_wf wf = init_ica_aroma_wf( mem_gb=3, metadata={'RepetitionTime': 1.0}, omp_nthreads=1) Parameters ---------- metadata : :obj:`dict` BIDS metadata for BOLD file 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_tpl_trans_wf``) susan_fwhm : :obj:`float` Kernel width (FWHM in mm) for the smoothing step with FSL ``susan`` (default: 6.0mm) err_on_aroma_warn : :obj:`bool` Do not fail on ICA-AROMA errors aroma_melodic_dim : :obj:`int` Set the dimensionality of the MELODIC ICA decomposition. Negative numbers set a maximum on automatic dimensionality estimation. Positive numbers set an exact number of components to extract. (default: -200, i.e., estimate <=200 components) Inputs ------ itk_bold_to_t1 Affine transform from ``ref_bold_brain`` to T1 space (ITK format) anat2std_xfm ANTs-compatible affine-and-warp transform file name_source BOLD series NIfTI file Used to recover original information lost during processing skip_vols number of non steady state volumes bold_split Individual 3D BOLD volumes, not motion corrected bold_mask BOLD series mask in template space hmc_xforms List of affine transforms aligning each volume to ``ref_image`` in ITK format movpar_file SPM-formatted motion parameters file Outputs ------- aroma_confounds TSV of confounds identified as noise by ICA-AROMA aroma_noise_ics CSV of noise components identified by ICA-AROMA melodic_mix FSL MELODIC mixing matrix nonaggr_denoised_file BOLD series with non-aggressive ICA-AROMA denoising applied """ from niworkflows.engine.workflows import LiterateWorkflow as Workflow from niworkflows.interfaces.segmentation import ICA_AROMARPT from niworkflows.interfaces.utility import KeySelect from niworkflows.interfaces.utils import TSV2JSON workflow = Workflow(name=name) workflow.__postdesc__ = """\ Automatic removal of motion artifacts using independent component analysis [ICA-AROMA, @aroma] was performed on the *preprocessed BOLD on MNI space* time-series after removal of non-steady state volumes and spatial smoothing with an isotropic, Gaussian kernel of 6mm FWHM (full-width half-maximum). Corresponding "non-aggresively" denoised runs were produced after such smoothing. Additionally, the "aggressive" noise-regressors were collected and placed in the corresponding confounds file. """ inputnode = pe.Node(niu.IdentityInterface(fields=[ 'bold_std', 'bold_mask_std', 'movpar_file', 'name_source', 'skip_vols', 'spatial_reference', ]), name='inputnode') outputnode = pe.Node(niu.IdentityInterface(fields=[ 'aroma_confounds', 'aroma_noise_ics', 'melodic_mix', 'nonaggr_denoised_file', 'aroma_metadata' ]), name='outputnode') # extract out to BOLD base select_std = pe.Node(KeySelect(fields=['bold_mask_std', 'bold_std']), name='select_std', run_without_submitting=True) select_std.inputs.key = 'MNI152NLin6Asym_res-2' rm_non_steady_state = pe.Node(niu.Function(function=_remove_volumes, output_names=['bold_cut']), name='rm_nonsteady') calc_median_val = pe.Node(fsl.ImageStats(op_string='-k %s -p 50'), name='calc_median_val') calc_bold_mean = pe.Node(fsl.MeanImage(), name='calc_bold_mean') def _getusans_func(image, thresh): return [tuple([image, thresh])] getusans = pe.Node(niu.Function(function=_getusans_func, output_names=['usans']), name='getusans', mem_gb=0.01) smooth = pe.Node(fsl.SUSAN(fwhm=susan_fwhm), name='smooth') # melodic node melodic = pe.Node(fsl.MELODIC(no_bet=True, tr_sec=float(metadata['RepetitionTime']), mm_thresh=0.5, out_stats=True, dim=aroma_melodic_dim), name="melodic") # ica_aroma node ica_aroma = pe.Node(ICA_AROMARPT(denoise_type='nonaggr', generate_report=True, TR=metadata['RepetitionTime'], args='-np'), name='ica_aroma') add_non_steady_state = pe.Node(niu.Function(function=_add_volumes, output_names=['bold_add']), name='add_nonsteady') # extract the confound ICs from the results ica_aroma_confound_extraction = pe.Node( ICAConfounds(err_on_aroma_warn=err_on_aroma_warn), name='ica_aroma_confound_extraction') ica_aroma_metadata_fmt = pe.Node(TSV2JSON(index_column='IC', output=None, enforce_case=True, additional_metadata={ 'Method': { 'Name': 'ICA-AROMA', 'Version': getenv( 'AROMA_VERSION', 'n/a') } }), name='ica_aroma_metadata_fmt') ds_report_ica_aroma = pe.Node(DerivativesDataSink( desc='aroma', datatype="figures", dismiss_entities=("echo", )), name='ds_report_ica_aroma', run_without_submitting=True, mem_gb=DEFAULT_MEMORY_MIN_GB) def _getbtthresh(medianval): return 0.75 * medianval # connect the nodes workflow.connect([ (inputnode, select_std, [('spatial_reference', 'keys'), ('bold_std', 'bold_std'), ('bold_mask_std', 'bold_mask_std')]), (inputnode, ica_aroma, [('movpar_file', 'motion_parameters')]), (inputnode, rm_non_steady_state, [('skip_vols', 'skip_vols')]), (select_std, rm_non_steady_state, [('bold_std', 'bold_file')]), (select_std, calc_median_val, [('bold_mask_std', 'mask_file')]), (rm_non_steady_state, calc_median_val, [('bold_cut', 'in_file')]), (rm_non_steady_state, calc_bold_mean, [('bold_cut', 'in_file')]), (calc_bold_mean, getusans, [('out_file', 'image')]), (calc_median_val, getusans, [('out_stat', 'thresh')]), # Connect input nodes to complete smoothing (rm_non_steady_state, smooth, [('bold_cut', 'in_file')]), (getusans, smooth, [('usans', 'usans')]), (calc_median_val, smooth, [(('out_stat', _getbtthresh), 'brightness_threshold')]), # connect smooth to melodic (smooth, melodic, [('smoothed_file', 'in_files')]), (select_std, melodic, [('bold_mask_std', 'mask')]), # connect nodes to ICA-AROMA (smooth, ica_aroma, [('smoothed_file', 'in_file')]), (select_std, ica_aroma, [('bold_mask_std', 'report_mask'), ('bold_mask_std', 'mask')]), (melodic, ica_aroma, [('out_dir', 'melodic_dir')]), # generate tsvs from ICA-AROMA (ica_aroma, ica_aroma_confound_extraction, [('out_dir', 'in_directory') ]), (inputnode, ica_aroma_confound_extraction, [('skip_vols', 'skip_vols') ]), (ica_aroma_confound_extraction, ica_aroma_metadata_fmt, [('aroma_metadata', 'in_file')]), # output for processing and reporting (ica_aroma_confound_extraction, outputnode, [('aroma_confounds', 'aroma_confounds'), ('aroma_noise_ics', 'aroma_noise_ics'), ('melodic_mix', 'melodic_mix')]), (ica_aroma_metadata_fmt, outputnode, [('output', 'aroma_metadata')]), (ica_aroma, add_non_steady_state, [('nonaggr_denoised_file', 'bold_cut_file')]), (select_std, add_non_steady_state, [('bold_std', 'bold_file')]), (inputnode, add_non_steady_state, [('skip_vols', 'skip_vols')]), (add_non_steady_state, outputnode, [('bold_add', 'nonaggr_denoised_file')]), (ica_aroma, ds_report_ica_aroma, [('out_report', 'in_file')]), ]) return workflow
def init_anat_derivatives_wf( *, bids_root, freesurfer, num_t1w, output_dir, spaces, name='anat_derivatives_wf', tpm_labels=("GM", "WM", "CSF"), ): """ Set up a battery of datasinks to store derivatives in the right location. Parameters ---------- bids_root : :obj:`str` Root path of BIDS dataset freesurfer : :obj:`bool` FreeSurfer was enabled num_t1w : :obj:`int` Number of T1w images output_dir : :obj:`str` Directory in which to save derivatives name : :obj:`str` Workflow name (default: anat_derivatives_wf) tpm_labels : :obj:`tuple` Tissue probability maps in order Inputs ------ template Template space and specifications source_files List of input T1w images t1w_ref_xfms List of affine transforms to realign input T1w images t1w_preproc The T1w reference map, which is calculated as the average of bias-corrected and preprocessed T1w images, defining the anatomical space. t1w_mask Mask of the ``t1w_preproc`` t1w_dseg Segmentation in T1w space t1w_tpms Tissue probability maps in T1w space anat2std_xfm Nonlinear spatial transform to resample imaging data given in anatomical space into standard space. std2anat_xfm Inverse transform of ``anat2std_xfm`` std_t1w T1w reference resampled in one or more standard spaces. std_mask Mask of skull-stripped template, in standard space std_dseg Segmentation, resampled into standard space std_tpms Tissue probability maps in standard space t1w2fsnative_xfm LTA-style affine matrix translating from T1w to FreeSurfer-conformed subject space fsnative2t1w_xfm LTA-style affine matrix translating from FreeSurfer-conformed subject space to T1w surfaces GIFTI surfaces (gray/white boundary, midthickness, pial, inflated) t1w_fs_aseg FreeSurfer's aseg segmentation, in native T1w space t1w_fs_aparc FreeSurfer's aparc+aseg segmentation, in native T1w space """ from niworkflows.interfaces.utility import KeySelect from smriprep.interfaces import DerivativesDataSink from smriprep.workflows.outputs import ( _bids_relative, _combine_cohort, _is_native, _drop_path ) workflow = Workflow(name=name) inputnode = pe.Node( niu.IdentityInterface( fields=['template', 'source_files', 't1w_ref_xfms', 't1w_preproc', 't1w_mask', 't1w_dseg', 't1w_tpms', 'anat2std_xfm', 'std2anat_xfm', 't1w2fsnative_xfm', 'fsnative2t1w_xfm', 'surfaces', 't1w_fs_aseg', 't1w_fs_aparc']), name='inputnode') raw_sources = pe.Node(niu.Function(function=_bids_relative), name='raw_sources') raw_sources.inputs.bids_root = bids_root ds_t1w_preproc = pe.Node( DerivativesDataSink(base_directory=output_dir, desc='preproc', compress=True), name='ds_t1w_preproc', run_without_submitting=True) ds_t1w_preproc.inputs.SkullStripped = False ds_t1w_mask = pe.Node( DerivativesDataSink(base_directory=output_dir, desc='brain', suffix='mask', compress=True), name='ds_t1w_mask', run_without_submitting=True) ds_t1w_mask.inputs.Type = 'Brain' ds_t1w_dseg = pe.Node( DerivativesDataSink(base_directory=output_dir, suffix='dseg', compress=True), name='ds_t1w_dseg', run_without_submitting=True) ds_t1w_tpms = pe.Node( DerivativesDataSink(base_directory=output_dir, suffix='probseg', compress=True), name='ds_t1w_tpms', run_without_submitting=True) ds_t1w_tpms.inputs.label = tpm_labels workflow.connect([ (inputnode, raw_sources, [('source_files', 'in_files')]), (inputnode, ds_t1w_preproc, [('t1w_preproc', 'in_file'), ('source_files', 'source_file')]), (inputnode, ds_t1w_mask, [('t1w_mask', 'in_file'), ('source_files', 'source_file')]), (inputnode, ds_t1w_tpms, [('t1w_tpms', 'in_file'), ('source_files', 'source_file')]), (inputnode, ds_t1w_dseg, [('t1w_dseg', 'in_file'), ('source_files', 'source_file')]), (raw_sources, ds_t1w_mask, [('out', 'RawSources')]), ]) # Transforms if spaces.get_spaces(nonstandard=False, dim=(3,)): ds_std2t1w_xfm = pe.MapNode( DerivativesDataSink(base_directory=output_dir, to='T1w', mode='image', suffix='xfm'), iterfield=('in_file', 'from'), name='ds_std2t1w_xfm', run_without_submitting=True) ds_t1w2std_xfm = pe.MapNode( DerivativesDataSink(base_directory=output_dir, mode='image', suffix='xfm', **{'from': 'T1w'}), iterfield=('in_file', 'to'), name='ds_t1w2std_xfm', run_without_submitting=True) workflow.connect([ (inputnode, ds_t1w2std_xfm, [ ('anat2std_xfm', 'in_file'), (('template', _combine_cohort), 'to'), ('source_files', 'source_file')]), (inputnode, ds_std2t1w_xfm, [ ('std2anat_xfm', 'in_file'), (('template', _combine_cohort), 'from'), ('source_files', 'source_file')]), ]) if num_t1w > 1: # Please note the dictionary unpacking to provide the from argument. # It is necessary because from is a protected keyword (not allowed as argument name). ds_t1w_ref_xfms = pe.MapNode( DerivativesDataSink(base_directory=output_dir, to='T1w', mode='image', suffix='xfm', extension='txt', **{'from': 'orig'}), iterfield=['source_file', 'in_file'], name='ds_t1w_ref_xfms', run_without_submitting=True) workflow.connect([ (inputnode, ds_t1w_ref_xfms, [('source_files', 'source_file'), ('t1w_ref_xfms', 'in_file')]), ]) # Write derivatives in standard spaces specified by --output-spaces if getattr(spaces, '_cached') is not None and spaces.cached.references: from niworkflows.interfaces.space import SpaceDataSource from niworkflows.interfaces.utils import GenerateSamplingReference from niworkflows.interfaces.fixes import FixHeaderApplyTransforms as ApplyTransforms spacesource = pe.Node(SpaceDataSource(), name='spacesource', run_without_submitting=True) spacesource.iterables = ('in_tuple', [ (s.fullname, s.spec) for s in spaces.cached.get_standard(dim=(3,)) ]) gen_tplid = pe.Node(niu.Function(function=_fmt_cohort), name="gen_tplid", run_without_submitting=True) select_xfm = pe.Node(KeySelect( fields=['anat2std_xfm']), name='select_xfm', run_without_submitting=True) select_tpl = pe.Node(TemplateFlowSelect(), name='select_tpl', run_without_submitting=True) gen_ref = pe.Node(GenerateSamplingReference(), name='gen_ref', mem_gb=0.01) # Resample T1w-space inputs anat2std_t1w = pe.Node(ApplyTransforms( dimension=3, default_value=0, float=True, interpolation='LanczosWindowedSinc'), name='anat2std_t1w') anat2std_mask = pe.Node(ApplyTransforms( interpolation='MultiLabel'), name='anat2std_mask' ) anat2std_dseg = pe.Node(ApplyTransforms( interpolation='MultiLabel'), name='anat2std_dseg' ) anat2std_tpms = pe.MapNode(ApplyTransforms( dimension=3, default_value=0, float=True, interpolation='Gaussian'), iterfield=['input_image'], name='anat2std_tpms' ) ds_std_t1w = pe.Node( DerivativesDataSink(base_directory=output_dir, desc='preproc', keep_dtype=True, compress=True), name='ds_std_t1w', run_without_submitting=True) ds_std_t1w.inputs.SkullStripped = True ds_std_mask = pe.Node( DerivativesDataSink(base_directory=output_dir, desc='brain', suffix='mask', compress=True), name='ds_std_mask', run_without_submitting=True) ds_std_mask.inputs.Type = 'Brain' ds_std_dseg = pe.Node( DerivativesDataSink(base_directory=output_dir, suffix='dseg', compress=True), name='ds_std_dseg', run_without_submitting=True) ds_std_tpms = pe.Node( DerivativesDataSink(base_directory=output_dir, suffix='probseg', compress=True), name='ds_std_tpms', run_without_submitting=True) # CRITICAL: the sequence of labels here (CSF-GM-WM) is that of the output of FSL-FAST # (intensity mean, per tissue). This order HAS to be matched also by the ``tpms`` # output in the data/io_spec.json file. ds_std_tpms.inputs.label = tpm_labels workflow.connect([ (inputnode, anat2std_t1w, [('t1w_preproc', 'input_image')]), (inputnode, anat2std_mask, [('t1w_mask', 'input_image')]), (inputnode, anat2std_dseg, [('t1w_dseg', 'input_image')]), (inputnode, anat2std_tpms, [('t1w_tpms', 'input_image')]), (inputnode, gen_ref, [('t1w_preproc', 'moving_image')]), (inputnode, select_xfm, [ ('anat2std_xfm', 'anat2std_xfm'), ('template', 'keys')]), (spacesource, gen_tplid, [('space', 'template'), ('cohort', 'cohort')]), (gen_tplid, select_xfm, [('out', 'key')]), (spacesource, select_tpl, [('space', 'template'), ('cohort', 'cohort'), (('resolution', _no_native), 'resolution')]), (spacesource, gen_ref, [(('resolution', _is_native), 'keep_native')]), (select_tpl, gen_ref, [('t2w_file', 'fixed_image')]), (anat2std_t1w, ds_std_t1w, [('output_image', 'in_file')]), (anat2std_mask, ds_std_mask, [('output_image', 'in_file')]), (anat2std_dseg, ds_std_dseg, [('output_image', 'in_file')]), (anat2std_tpms, ds_std_tpms, [('output_image', 'in_file')]), (select_tpl, ds_std_mask, [(('brain_mask', _drop_path), 'RawSources')]), ]) workflow.connect( # Connect apply transforms nodes [ (gen_ref, n, [('out_file', 'reference_image')]) for n in (anat2std_t1w, anat2std_mask, anat2std_dseg, anat2std_tpms) ] + [ (select_xfm, n, [('anat2std_xfm', 'transforms')]) for n in (anat2std_t1w, anat2std_mask, anat2std_dseg, anat2std_tpms) ] # Connect the source_file input of these datasinks + [ (inputnode, n, [('source_files', 'source_file')]) for n in (ds_std_t1w, ds_std_mask, ds_std_dseg, ds_std_tpms) ] # Connect the space input of these datasinks + [ (spacesource, n, [ ('space', 'space'), ('cohort', 'cohort'), ('resolution', 'resolution') ]) for n in (ds_std_t1w, ds_std_mask, ds_std_dseg, ds_std_tpms) ] ) 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_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_derivatives_wf( bids_root, metadata, output_dir, spaces, use_aroma, name="func_derivatives_wf", ): """ Set up a battery of datasinks to store derivatives in the right location. Parameters ---------- bids_root : :obj:`str` Original BIDS dataset path. metadata : :obj:`dict` Metadata dictionary associated to the BOLD run. output_dir : :obj:`str` Where derivatives should be written out to. 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). use_aroma : :obj:`bool` Whether ``--use-aroma`` flag was set. name : :obj:`str` This workflow's identifier (default: ``func_derivatives_wf``). """ from niworkflows.engine.workflows import LiterateWorkflow as Workflow from niworkflows.interfaces.utility import KeySelect from smriprep.workflows.outputs import _bids_relative nonstd_spaces = set(spaces.get_nonstandard()) workflow = Workflow(name=name) inputnode = pe.Node( niu.IdentityInterface(fields=[ "aroma_noise_ics", "bold_mask_std", "bold_mask_t1", "bold_std", "bold_std_ref", "bold_t1", "bold_t1_ref", "bold_native", "bold_native_ref", "bold_mask_native", "confounds", "confounds_metadata", "melodic_mix", "nonaggr_denoised_file", "source_file", "surf_files", "surf_refs", "template", "spatial_reference", ]), name="inputnode", ) raw_sources = pe.Node(niu.Function(function=_bids_relative), name="raw_sources") raw_sources.inputs.bids_root = bids_root ds_confounds = pe.Node( DerivativesDataSink( base_directory=output_dir, desc="confounds", suffix="regressors", dismiss_entities=("echo", ), ), name="ds_confounds", run_without_submitting=True, mem_gb=DEFAULT_MEMORY_MIN_GB, ) # fmt:off workflow.connect([ (inputnode, raw_sources, [('source_file', 'in_files')]), (inputnode, ds_confounds, [('source_file', 'source_file'), ('confounds', 'in_file'), ('confounds_metadata', 'meta_dict')]), ]) # fmt:on if nonstd_spaces.intersection(("func", "run", "bold", "boldref", "sbref")): ds_bold_native = pe.Node( DerivativesDataSink( base_directory=output_dir, desc="preproc", compress=True, SkullStripped=False, RepetitionTime=metadata.get("RepetitionTime"), TaskName=metadata.get("TaskName"), dismiss_entities=("echo", ), ), name="ds_bold_native", run_without_submitting=True, mem_gb=DEFAULT_MEMORY_MIN_GB, ) ds_bold_native_ref = pe.Node( DerivativesDataSink( base_directory=output_dir, suffix="boldref", compress=True, dismiss_entities=("echo", ), ), name="ds_bold_native_ref", run_without_submitting=True, mem_gb=DEFAULT_MEMORY_MIN_GB, ) ds_bold_mask_native = pe.Node( DerivativesDataSink( base_directory=output_dir, desc="brain", suffix="mask", compress=True, dismiss_entities=("echo", ), ), name="ds_bold_mask_native", run_without_submitting=True, mem_gb=DEFAULT_MEMORY_MIN_GB, ) # fmt:off workflow.connect([ (inputnode, ds_bold_native, [('source_file', 'source_file'), ('bold_native', 'in_file')]), (inputnode, ds_bold_native_ref, [('source_file', 'source_file'), ('bold_native_ref', 'in_file')]), (inputnode, ds_bold_mask_native, [('source_file', 'source_file'), ('bold_mask_native', 'in_file') ]), (raw_sources, ds_bold_mask_native, [('out', 'RawSources')]), ]) # fmt:on # Resample to T1w space if nonstd_spaces.intersection(("T1w", "anat")): ds_bold_t1 = pe.Node( DerivativesDataSink( base_directory=output_dir, space="T1w", desc="preproc", compress=True, SkullStripped=False, RepetitionTime=metadata.get("RepetitionTime"), TaskName=metadata.get("TaskName"), dismiss_entities=("echo", ), ), name="ds_bold_t1", run_without_submitting=True, mem_gb=DEFAULT_MEMORY_MIN_GB, ) ds_bold_t1_ref = pe.Node( DerivativesDataSink( base_directory=output_dir, space="T1w", suffix="boldref", compress=True, dismiss_entities=("echo", ), ), name="ds_bold_t1_ref", run_without_submitting=True, mem_gb=DEFAULT_MEMORY_MIN_GB, ) ds_bold_mask_t1 = pe.Node( DerivativesDataSink( base_directory=output_dir, space="T1w", desc="brain", suffix="mask", compress=True, dismiss_entities=("echo", ), ), name="ds_bold_mask_t1", run_without_submitting=True, mem_gb=DEFAULT_MEMORY_MIN_GB, ) # fmt:off workflow.connect([ (inputnode, ds_bold_t1, [('source_file', 'source_file'), ('bold_t1', 'in_file')]), (inputnode, ds_bold_t1_ref, [('source_file', 'source_file'), ('bold_t1_ref', 'in_file')]), (inputnode, ds_bold_mask_t1, [('source_file', 'source_file'), ('bold_mask_t1', 'in_file')]), (raw_sources, ds_bold_mask_t1, [('out', 'RawSources')]), ]) # fmt:on if use_aroma: ds_aroma_noise_ics = pe.Node( DerivativesDataSink( base_directory=output_dir, suffix="AROMAnoiseICs", dismiss_entities=("echo", ), ), name="ds_aroma_noise_ics", run_without_submitting=True, mem_gb=DEFAULT_MEMORY_MIN_GB, ) ds_melodic_mix = pe.Node( DerivativesDataSink( base_directory=output_dir, desc="MELODIC", suffix="mixing", dismiss_entities=("echo", ), ), name="ds_melodic_mix", run_without_submitting=True, mem_gb=DEFAULT_MEMORY_MIN_GB, ) ds_aroma_std = pe.Node( DerivativesDataSink( base_directory=output_dir, space="MNI152NLin6Asym", desc="smoothAROMAnonaggr", compress=True, ), name="ds_aroma_std", run_without_submitting=True, mem_gb=DEFAULT_MEMORY_MIN_GB, ) # fmt:off workflow.connect([ (inputnode, ds_aroma_noise_ics, [('source_file', 'source_file'), ('aroma_noise_ics', 'in_file')]), (inputnode, ds_melodic_mix, [('source_file', 'source_file'), ('melodic_mix', 'in_file')]), (inputnode, ds_aroma_std, [('source_file', 'source_file'), ('nonaggr_denoised_file', 'in_file')]), ]) # fmt:on if getattr(spaces, "_cached") is None: return workflow # Store resamplings in standard spaces when listed in --output-spaces if spaces.cached.references: from niworkflows.interfaces.space import SpaceDataSource spacesource = pe.Node(SpaceDataSource(), name="spacesource", run_without_submitting=True) spacesource.iterables = ( "in_tuple", [(s.fullname, s.spec) for s in spaces.cached.get_standard(dim=(3, ))], ) select_std = pe.Node( KeySelect(fields=[ "template", "bold_std", "bold_std_ref", "bold_mask_std" ]), name="select_std", run_without_submitting=True, mem_gb=DEFAULT_MEMORY_MIN_GB, ) ds_bold_std = pe.Node( DerivativesDataSink( base_directory=output_dir, desc="preproc", compress=True, SkullStripped=False, RepetitionTime=metadata.get("RepetitionTime"), TaskName=metadata.get("TaskName"), dismiss_entities=("echo", ), ), name="ds_bold_std", run_without_submitting=True, mem_gb=DEFAULT_MEMORY_MIN_GB, ) ds_bold_std_ref = pe.Node( DerivativesDataSink( base_directory=output_dir, suffix="boldref", compress=True, dismiss_entities=("echo", ), ), name="ds_bold_std_ref", run_without_submitting=True, mem_gb=DEFAULT_MEMORY_MIN_GB, ) ds_bold_mask_std = pe.Node( DerivativesDataSink( base_directory=output_dir, desc="brain", suffix="mask", compress=True, dismiss_entities=("echo", ), ), name="ds_bold_mask_std", run_without_submitting=True, mem_gb=DEFAULT_MEMORY_MIN_GB, ) # fmt:off workflow.connect([ (inputnode, ds_bold_std, [('source_file', 'source_file')]), (inputnode, ds_bold_std_ref, [('source_file', 'source_file')]), (inputnode, ds_bold_mask_std, [('source_file', 'source_file')]), (inputnode, select_std, [('bold_std', 'bold_std'), ('bold_std_ref', 'bold_std_ref'), ('bold_mask_std', 'bold_mask_std'), ('template', 'template'), ('spatial_reference', 'keys')]), (spacesource, select_std, [('uid', 'key')]), (select_std, ds_bold_std, [('bold_std', 'in_file')]), (spacesource, ds_bold_std, [('space', 'space'), ('cohort', 'cohort'), ('resolution', 'resolution'), ('density', 'density')]), (select_std, ds_bold_std_ref, [('bold_std_ref', 'in_file')]), (spacesource, ds_bold_std_ref, [('space', 'space'), ('cohort', 'cohort'), ('resolution', 'resolution'), ('density', 'density')]), (select_std, ds_bold_mask_std, [('bold_mask_std', 'in_file')]), (spacesource, ds_bold_mask_std, [('space', 'space'), ('cohort', 'cohort'), ('resolution', 'resolution'), ('density', 'density')]), (raw_sources, ds_bold_mask_std, [('out', 'RawSources')]), ]) # fmt:on 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_anat_derivatives_wf( *, bids_root, freesurfer, num_t1w, output_dir, spaces, name="anat_derivatives_wf", tpm_labels=BIDS_TISSUE_ORDER, ): """ Set up a battery of datasinks to store derivatives in the right location. Parameters ---------- bids_root : :obj:`str` Root path of BIDS dataset freesurfer : :obj:`bool` FreeSurfer was enabled num_t1w : :obj:`int` Number of T1w images output_dir : :obj:`str` Directory in which to save derivatives name : :obj:`str` Workflow name (default: anat_derivatives_wf) tpm_labels : :obj:`tuple` Tissue probability maps in order Inputs ------ template Template space and specifications source_files List of input T1w images t1w_ref_xfms List of affine transforms to realign input T1w images t1w_preproc The T1w reference map, which is calculated as the average of bias-corrected and preprocessed T1w images, defining the anatomical space. t1w_mask Mask of the ``t1w_preproc`` t1w_dseg Segmentation in T1w space t1w_tpms Tissue probability maps in T1w space anat2std_xfm Nonlinear spatial transform to resample imaging data given in anatomical space into standard space. std2anat_xfm Inverse transform of ``anat2std_xfm`` std_t1w T1w reference resampled in one or more standard spaces. std_mask Mask of skull-stripped template, in standard space std_dseg Segmentation, resampled into standard space std_tpms Tissue probability maps in standard space t1w2fsnative_xfm LTA-style affine matrix translating from T1w to FreeSurfer-conformed subject space fsnative2t1w_xfm LTA-style affine matrix translating from FreeSurfer-conformed subject space to T1w surfaces GIFTI surfaces (gray/white boundary, midthickness, pial, inflated) t1w_fs_aseg FreeSurfer's aseg segmentation, in native T1w space t1w_fs_aparc FreeSurfer's aparc+aseg segmentation, in native T1w space """ from niworkflows.interfaces.utility import KeySelect workflow = Workflow(name=name) inputnode = pe.Node( niu.IdentityInterface(fields=[ "template", "source_files", "t1w_ref_xfms", "t1w_preproc", "t1w_mask", "t1w_dseg", "t1w_tpms", "anat2std_xfm", "std2anat_xfm", "t1w2fsnative_xfm", "fsnative2t1w_xfm", "surfaces", "t1w_fs_aseg", "t1w_fs_aparc", ]), name="inputnode", ) raw_sources = pe.Node(niu.Function(function=_bids_relative), name="raw_sources") raw_sources.inputs.bids_root = bids_root ds_t1w_preproc = pe.Node( DerivativesDataSink(base_directory=output_dir, desc="preproc", compress=True), name="ds_t1w_preproc", run_without_submitting=True, ) ds_t1w_preproc.inputs.SkullStripped = False ds_t1w_mask = pe.Node( DerivativesDataSink(base_directory=output_dir, desc="brain", suffix="mask", compress=True), name="ds_t1w_mask", run_without_submitting=True, ) ds_t1w_mask.inputs.Type = "Brain" ds_t1w_dseg = pe.Node( DerivativesDataSink(base_directory=output_dir, suffix="dseg", compress=True), name="ds_t1w_dseg", run_without_submitting=True, ) ds_t1w_tpms = pe.Node( DerivativesDataSink(base_directory=output_dir, suffix="probseg", compress=True), name="ds_t1w_tpms", run_without_submitting=True, ) ds_t1w_tpms.inputs.label = tpm_labels # fmt:off workflow.connect([ (inputnode, raw_sources, [('source_files', 'in_files')]), (inputnode, ds_t1w_preproc, [('t1w_preproc', 'in_file'), ('source_files', 'source_file')]), (inputnode, ds_t1w_mask, [('t1w_mask', 'in_file'), ('source_files', 'source_file')]), (inputnode, ds_t1w_tpms, [('t1w_tpms', 'in_file'), ('source_files', 'source_file')]), (inputnode, ds_t1w_dseg, [('t1w_dseg', 'in_file'), ('source_files', 'source_file')]), (raw_sources, ds_t1w_mask, [('out', 'RawSources')]), ]) # fmt:on # Transforms if spaces.get_spaces(nonstandard=False, dim=(3, )): ds_std2t1w_xfm = pe.MapNode( DerivativesDataSink(base_directory=output_dir, to="T1w", mode="image", suffix="xfm"), iterfield=("in_file", "from"), name="ds_std2t1w_xfm", run_without_submitting=True, ) ds_t1w2std_xfm = pe.MapNode( DerivativesDataSink(base_directory=output_dir, mode="image", suffix="xfm", **{"from": "T1w"}), iterfield=("in_file", "to"), name="ds_t1w2std_xfm", run_without_submitting=True, ) # fmt:off workflow.connect([ (inputnode, ds_t1w2std_xfm, [('anat2std_xfm', 'in_file'), (('template', _combine_cohort), 'to'), ('source_files', 'source_file')]), (inputnode, ds_std2t1w_xfm, [('std2anat_xfm', 'in_file'), (('template', _combine_cohort), 'from'), ('source_files', 'source_file')]), ]) # fmt:on if num_t1w > 1: # Please note the dictionary unpacking to provide the from argument. # It is necessary because from is a protected keyword (not allowed as argument name). ds_t1w_ref_xfms = pe.MapNode( DerivativesDataSink( base_directory=output_dir, to="T1w", mode="image", suffix="xfm", extension="txt", **{"from": "orig"}, ), iterfield=["source_file", "in_file"], name="ds_t1w_ref_xfms", run_without_submitting=True, ) # fmt:off workflow.connect([ (inputnode, ds_t1w_ref_xfms, [('source_files', 'source_file'), ('t1w_ref_xfms', 'in_file')]), ]) # fmt:on # Write derivatives in standard spaces specified by --output-spaces if getattr(spaces, "_cached") is not None and spaces.cached.references: from niworkflows.interfaces.space import SpaceDataSource from niworkflows.interfaces.nibabel import GenerateSamplingReference from niworkflows.interfaces.fixes import ( FixHeaderApplyTransforms as ApplyTransforms, ) from ..interfaces.templateflow import TemplateFlowSelect spacesource = pe.Node(SpaceDataSource(), name="spacesource", run_without_submitting=True) spacesource.iterables = ( "in_tuple", [(s.fullname, s.spec) for s in spaces.cached.get_standard(dim=(3, ))], ) gen_tplid = pe.Node( niu.Function(function=_fmt_cohort), name="gen_tplid", run_without_submitting=True, ) select_xfm = pe.Node( KeySelect(fields=["anat2std_xfm"]), name="select_xfm", run_without_submitting=True, ) select_tpl = pe.Node(TemplateFlowSelect(), name="select_tpl", run_without_submitting=True) gen_ref = pe.Node(GenerateSamplingReference(), name="gen_ref", mem_gb=0.01) # Mask T1w preproc images mask_t1w = pe.Node(ApplyMask(), name='mask_t1w') # Resample T1w-space inputs anat2std_t1w = pe.Node( ApplyTransforms( dimension=3, default_value=0, float=True, interpolation="LanczosWindowedSinc", ), name="anat2std_t1w", ) anat2std_mask = pe.Node(ApplyTransforms(interpolation="MultiLabel"), name="anat2std_mask") anat2std_dseg = pe.Node(ApplyTransforms(interpolation="MultiLabel"), name="anat2std_dseg") anat2std_tpms = pe.MapNode( ApplyTransforms(dimension=3, default_value=0, float=True, interpolation="Gaussian"), iterfield=["input_image"], name="anat2std_tpms", ) ds_std_t1w = pe.Node( DerivativesDataSink( base_directory=output_dir, desc="preproc", compress=True, ), name="ds_std_t1w", run_without_submitting=True, ) ds_std_t1w.inputs.SkullStripped = True ds_std_mask = pe.Node( DerivativesDataSink(base_directory=output_dir, desc="brain", suffix="mask", compress=True), name="ds_std_mask", run_without_submitting=True, ) ds_std_mask.inputs.Type = "Brain" ds_std_dseg = pe.Node( DerivativesDataSink(base_directory=output_dir, suffix="dseg", compress=True), name="ds_std_dseg", run_without_submitting=True, ) ds_std_tpms = pe.Node( DerivativesDataSink(base_directory=output_dir, suffix="probseg", compress=True), name="ds_std_tpms", run_without_submitting=True, ) # CRITICAL: the sequence of labels here (CSF-GM-WM) is that of the output of FSL-FAST # (intensity mean, per tissue). This order HAS to be matched also by the ``tpms`` # output in the data/io_spec.json file. ds_std_tpms.inputs.label = tpm_labels # fmt:off workflow.connect([ (inputnode, mask_t1w, [('t1w_preproc', 'in_file'), ('t1w_mask', 'in_mask')]), (mask_t1w, anat2std_t1w, [('out_file', 'input_image')]), (inputnode, anat2std_mask, [('t1w_mask', 'input_image')]), (inputnode, anat2std_dseg, [('t1w_dseg', 'input_image')]), (inputnode, anat2std_tpms, [('t1w_tpms', 'input_image')]), (inputnode, gen_ref, [('t1w_preproc', 'moving_image')]), (inputnode, select_xfm, [('anat2std_xfm', 'anat2std_xfm'), ('template', 'keys')]), (spacesource, gen_tplid, [('space', 'template'), ('cohort', 'cohort')]), (gen_tplid, select_xfm, [('out', 'key')]), (spacesource, select_tpl, [('space', 'template'), ('cohort', 'cohort'), (('resolution', _no_native), 'resolution')]), (spacesource, gen_ref, [(('resolution', _is_native), 'keep_native') ]), (select_tpl, gen_ref, [('t1w_file', 'fixed_image')]), (anat2std_t1w, ds_std_t1w, [('output_image', 'in_file')]), (anat2std_mask, ds_std_mask, [('output_image', 'in_file')]), (anat2std_dseg, ds_std_dseg, [('output_image', 'in_file')]), (anat2std_tpms, ds_std_tpms, [('output_image', 'in_file')]), (select_tpl, ds_std_mask, [(('brain_mask', _drop_path), 'RawSources')]), ]) workflow.connect( # Connect apply transforms nodes [(gen_ref, n, [('out_file', 'reference_image')]) for n in (anat2std_t1w, anat2std_mask, anat2std_dseg, anat2std_tpms)] + [(select_xfm, n, [('anat2std_xfm', 'transforms')]) for n in (anat2std_t1w, anat2std_mask, anat2std_dseg, anat2std_tpms)] # Connect the source_file input of these datasinks + [(inputnode, n, [('source_files', 'source_file')]) for n in (ds_std_t1w, ds_std_mask, ds_std_dseg, ds_std_tpms)] # Connect the space input of these datasinks + [(spacesource, n, [('space', 'space'), ('cohort', 'cohort'), ('resolution', 'resolution')]) for n in (ds_std_t1w, ds_std_mask, ds_std_dseg, ds_std_tpms)]) # fmt:on if not freesurfer: return workflow from niworkflows.interfaces.nitransforms import ConcatenateXFMs from niworkflows.interfaces.surf import Path2BIDS # FS native space transforms lta2itk_fwd = pe.Node(ConcatenateXFMs(), name="lta2itk_fwd", run_without_submitting=True) lta2itk_inv = pe.Node(ConcatenateXFMs(), name="lta2itk_inv", run_without_submitting=True) ds_t1w_fsnative = pe.Node( DerivativesDataSink( base_directory=output_dir, mode="image", to="fsnative", suffix="xfm", extension="txt", **{"from": "T1w"}, ), name="ds_t1w_fsnative", run_without_submitting=True, ) ds_fsnative_t1w = pe.Node( DerivativesDataSink( base_directory=output_dir, mode="image", to="T1w", suffix="xfm", extension="txt", **{"from": "fsnative"}, ), name="ds_fsnative_t1w", run_without_submitting=True, ) # Surfaces name_surfs = pe.MapNode(Path2BIDS(), iterfield="in_file", name="name_surfs", run_without_submitting=True) ds_surfs = pe.MapNode( DerivativesDataSink(base_directory=output_dir, extension=".surf.gii"), iterfield=["in_file", "hemi", "suffix"], name="ds_surfs", run_without_submitting=True, ) # Parcellations ds_t1w_fsaseg = pe.Node( DerivativesDataSink(base_directory=output_dir, desc="aseg", suffix="dseg", compress=True), name="ds_t1w_fsaseg", run_without_submitting=True, ) ds_t1w_fsparc = pe.Node( DerivativesDataSink(base_directory=output_dir, desc="aparcaseg", suffix="dseg", compress=True), name="ds_t1w_fsparc", run_without_submitting=True, ) # fmt:off workflow.connect([ (inputnode, lta2itk_fwd, [('t1w2fsnative_xfm', 'in_xfms')]), (inputnode, lta2itk_inv, [('fsnative2t1w_xfm', 'in_xfms')]), (inputnode, ds_t1w_fsnative, [('source_files', 'source_file')]), (lta2itk_fwd, ds_t1w_fsnative, [('out_xfm', 'in_file')]), (inputnode, ds_fsnative_t1w, [('source_files', 'source_file')]), (lta2itk_inv, ds_fsnative_t1w, [('out_xfm', 'in_file')]), (inputnode, name_surfs, [('surfaces', 'in_file')]), (inputnode, ds_surfs, [('surfaces', 'in_file'), ('source_files', 'source_file')]), (name_surfs, ds_surfs, [('hemi', 'hemi'), ('suffix', 'suffix')]), (inputnode, ds_t1w_fsaseg, [('t1w_fs_aseg', 'in_file'), ('source_files', 'source_file')]), (inputnode, ds_t1w_fsparc, [('t1w_fs_aparc', 'in_file'), ('source_files', 'source_file')]), ]) # fmt:on return workflow
def add_templates_by_composing_transforms(workflow, templates=["MNI152NLin6Asym"]): anat_norm_wf = workflow.get_node("anat_norm_wf") outputnode = workflow.get_node("outputnode") if len(templates) == 0: workflow.connect([ ( anat_norm_wf, outputnode, [ ("outputnode.standardized", "std_preproc"), ("outputnode.std_mask", "std_mask"), ("outputnode.std_dseg", "std_dseg"), ("outputnode.std_tpms", "std_tpms"), ("outputnode.template", "template"), ("outputnode.anat2std_xfm", "anat2std_xfm"), ("outputnode.std2anat_xfm", "std2anat_xfm"), ], ), ]) return templitersrc = pe.Node( niu.IdentityInterface(fields=["template"]), iterables=[("template", templates)], name="templitersrc", ) movingsrc = pe.Node( niu.IdentityInterface(fields=[ "moving_image", "moving_mask", "moving_segmentation", "moving_tpms" ]), name="movingsrc", ) brain_extraction_wf = workflow.get_node("brain_extraction_wf") if brain_extraction_wf is None: brain_extraction_wf = workflow.get_node("n4_only_wf") workflow.connect([ ( brain_extraction_wf, movingsrc, [(("outputnode.bias_corrected", first), "moving_image")], ), (workflow.get_node("buffernode"), movingsrc, [("t1w_mask", "moving_mask")]), ( workflow.get_node("t1w_dseg"), movingsrc, [("tissue_class_map", "moving_segmentation")], ), (workflow.get_node("t1w_dseg"), movingsrc, [("probability_maps", "moving_tpms")]), ]) # gather inputs def select_xfm_to_compose(base_template_list=None, out_template=None): from pipeline import resources for base_template in base_template_list: xfm_file = resources.get( f"tpl_{out_template}_from_{base_template}_mode_image_xfm.h5") inv_xfm_file = resources.get( f"tpl_{base_template}_from_{out_template}_mode_image_xfm.h5") if xfm_file is not None and inv_xfm_file is not None: return base_template, xfm_file, inv_xfm_file raise ValueError("No xfm available") selectcompxfm = pe.Node( interface=niu.Function( input_names=["base_template_list", "out_template"], output_names=["base_template", "xfm", "inv_xfm"], function=select_xfm_to_compose, ), name="selectcompxfm", ) workflow.connect(anat_norm_wf, "outputnode.template", selectcompxfm, "base_template_list") workflow.connect(templitersrc, "template", selectcompxfm, "out_template") selectbasexfm = pe.Node( KeySelect(fields=["anat2std_xfm", "std2anat_xfm"]), name="selectbasexfm", run_without_submitting=True, ) workflow.connect(selectcompxfm, "base_template", selectbasexfm, "key") workflow.connect(anat_norm_wf, "outputnode.template", selectbasexfm, "keys") workflow.connect(anat_norm_wf, "outputnode.anat2std_xfm", selectbasexfm, "anat2std_xfm") workflow.connect(anat_norm_wf, "outputnode.std2anat_xfm", selectbasexfm, "std2anat_xfm") tf_select = pe.Node(TemplateFlowSelect(resolution=1), name="tf_select", run_without_submitting=True) workflow.connect(templitersrc, "template", tf_select, "template") # compose xfms mergexfm = pe.Node(niu.Merge(numinputs=2), name="mergexfm", run_without_submitting=True) workflow.connect(selectbasexfm, "anat2std_xfm", mergexfm, "in1") workflow.connect(selectcompxfm, "xfm", mergexfm, "in2") compxfm = pe.Node( ApplyTransforms( dimension=3, print_out_composite_warp_file=True, output_image="ants_t1_to_mniComposite.nii.gz", ), name="compxfm", ) workflow.connect(tf_select, "t1w_file", compxfm, "reference_image") workflow.connect(mergexfm, "out", compxfm, "transforms") mergeinvxfm = pe.Node(niu.Merge(numinputs=2), name="mergeinvxfm", run_without_submitting=True) workflow.connect(selectcompxfm, "inv_xfm", mergeinvxfm, "in1") workflow.connect(selectbasexfm, "std2anat_xfm", mergeinvxfm, "in2") compinvxfm = pe.Node( ApplyTransforms( dimension=3, print_out_composite_warp_file=True, output_image="ants_t1_to_mniInverseComposite.nii.gz", ), name="compinvxfm", ) workflow.connect(movingsrc, "moving_image", compinvxfm, "reference_image") workflow.connect(mergeinvxfm, "out", compinvxfm, "transforms") # apply xfms tpl_moving = pe.Node( ApplyTransforms(dimension=3, default_value=0, float=True, interpolation="LanczosWindowedSinc"), name="tpl_moving", ) workflow.connect(movingsrc, "moving_image", tpl_moving, "input_image") workflow.connect(tf_select, "t1w_file", tpl_moving, "reference_image") workflow.connect(compxfm, "output_image", tpl_moving, "transforms") std_mask = pe.Node( ApplyTransforms(dimension=3, default_value=0, float=True, interpolation="MultiLabel"), name="std_mask", ) workflow.connect(movingsrc, "moving_mask", std_mask, "input_image") workflow.connect(tf_select, "t1w_file", std_mask, "reference_image") workflow.connect(compxfm, "output_image", std_mask, "transforms") std_dseg = pe.Node( ApplyTransforms(dimension=3, default_value=0, float=True, interpolation="MultiLabel"), name="std_dseg", ) workflow.connect(movingsrc, "moving_segmentation", std_dseg, "input_image") workflow.connect(tf_select, "t1w_file", std_dseg, "reference_image") workflow.connect(compxfm, "output_image", std_dseg, "transforms") std_tpms = pe.MapNode( ApplyTransforms(dimension=3, default_value=0, float=True, interpolation="Gaussian"), iterfield=["input_image"], name="std_tpms", ) workflow.connect(movingsrc, "moving_tpms", std_tpms, "input_image") workflow.connect(tf_select, "t1w_file", std_tpms, "reference_image") workflow.connect(compxfm, "output_image", std_tpms, "transforms") # merge mergefields = [ "template", "standardized", "std_mask", "std_dseg", "std_tpms", "anat2std_xfm", "std2anat_xfm", ] aliases = {"standardized": "std_preproc"} joinnode = pe.JoinNode( niu.IdentityInterface(fields=mergefields), name="joinnode", joinsource="templitersrc", joinfield="template", ) workflow.connect(templitersrc, "template", joinnode, "template") workflow.connect(compxfm, "output_image", joinnode, "anat2std_xfm") workflow.connect(compinvxfm, "output_image", joinnode, "std2anat_xfm") workflow.connect(tpl_moving, "output_image", joinnode, "standardized") workflow.connect(std_mask, "output_image", joinnode, "std_mask") workflow.connect(std_dseg, "output_image", joinnode, "std_dseg") workflow.connect(std_tpms, "output_image", joinnode, "std_tpms") for field in mergefields: merge = pe.Node(niu.Merge(numinputs=2), name=f"merge{field}", run_without_submitting=True) workflow.connect(anat_norm_wf, f"outputnode.{field}", merge, "in1") workflow.connect(joinnode, field, merge, "in2") workflow.connect(merge, "out", outputnode, aliases[field] if field in aliases else field)
def init_dwi_preproc_wf(dwi_file, has_fieldmap=False): """ Build a preprocessing workflow for one DWI run. Workflow Graph .. workflow:: :graph2use: orig :simple_form: yes from dmriprep.config.testing import mock_config from dmriprep import config from dmriprep.workflows.dwi.base import init_dwi_preproc_wf with mock_config(): wf = init_dwi_preproc_wf( f"{config.execution.layout.root}/" "sub-THP0005/dwi/sub-THP0005_dwi.nii.gz" ) Parameters ---------- dwi_file : :obj:`os.PathLike` One diffusion MRI dataset to be processed. has_fieldmap : :obj:`bool` Build the workflow with a path to register a fieldmap to the DWI. Inputs ------ dwi_file dwi NIfTI file in_bvec File path of the b-vectors in_bval File path of the b-values fmap File path of the fieldmap fmap_ref File path of the fieldmap reference fmap_coeff File path of the fieldmap coefficients fmap_mask File path of the fieldmap mask fmap_id The BIDS modality label of the fieldmap being used Outputs ------- dwi_reference A 3D :math:`b = 0` reference, before susceptibility distortion correction. dwi_mask A 3D, binary mask of the ``dwi_reference`` above. gradients_rasb A *RASb* (RAS+ coordinates, scaled b-values, normalized b-vectors, BIDS-compatible) gradient table. See Also -------- * :py:func:`~dmriprep.workflows.dwi.util.init_dwi_reference_wf` * :py:func:`~dmriprep.workflows.dwi.outputs.init_dwi_derivatives_wf` * :py:func:`~dmriprep.workflows.dwi.outputs.init_reportlets_wf` """ from niworkflows.interfaces.reportlets.registration import ( SimpleBeforeAfterRPT as SimpleBeforeAfter, ) from ...interfaces.vectors import CheckGradientTable from .util import init_dwi_reference_wf from .outputs import init_dwi_derivatives_wf, init_reportlets_wf from .eddy import init_eddy_wf layout = config.execution.layout dwi_file = Path(dwi_file) config.loggers.workflow.debug( f"Creating DWI preprocessing workflow for <{dwi_file.name}>") if has_fieldmap: import re from sdcflows.fieldmaps import get_identifier dwi_rel = re.sub(r"^sub-[a-zA-Z0-9]*/", "", str(dwi_file.relative_to(layout.root))) estimator_key = get_identifier(dwi_rel) if not estimator_key: has_fieldmap = False config.loggers.workflow.critical( f"None of the available B0 fieldmaps are associated to <{dwi_rel}>" ) # Build workflow workflow = Workflow(name=_get_wf_name(dwi_file.name)) inputnode = pe.Node( niu.IdentityInterface(fields=[ # DWI "dwi_file", "in_bvec", "in_bval", # From SDCFlows "fmap", "fmap_ref", "fmap_coeff", "fmap_mask", "fmap_id", # From anatomical "t1w_preproc", "t1w_mask", "t1w_dseg", "t1w_aseg", "t1w_aparc", "t1w_tpms", "template", "anat2std_xfm", "std2anat_xfm", "subjects_dir", "subject_id", "t1w2fsnative_xfm", "fsnative2t1w_xfm", ]), name="inputnode", ) inputnode.inputs.dwi_file = str(dwi_file.absolute()) inputnode.inputs.in_bvec = str(layout.get_bvec(dwi_file)) inputnode.inputs.in_bval = str(layout.get_bval(dwi_file)) outputnode = pe.Node( niu.IdentityInterface( fields=["dwi_reference", "dwi_mask", "gradients_rasb"]), name="outputnode", ) gradient_table = pe.Node(CheckGradientTable(), name="gradient_table") dwi_reference_wf = init_dwi_reference_wf( mem_gb=config.DEFAULT_MEMORY_MIN_GB, omp_nthreads=config.nipype.omp_nthreads) dwi_derivatives_wf = init_dwi_derivatives_wf( output_dir=str(config.execution.output_dir)) # MAIN WORKFLOW STRUCTURE # fmt: off workflow.connect([ (inputnode, gradient_table, [("dwi_file", "dwi_file"), ("in_bvec", "in_bvec"), ("in_bval", "in_bval")]), (inputnode, dwi_reference_wf, [("dwi_file", "inputnode.dwi_file")]), (inputnode, dwi_derivatives_wf, [("dwi_file", "inputnode.source_file") ]), (gradient_table, dwi_reference_wf, [("b0_ixs", "inputnode.b0_ixs")]), (gradient_table, outputnode, [("out_rasb", "gradients_rasb")]), (outputnode, dwi_derivatives_wf, [ ("dwi_reference", "inputnode.dwi_ref"), ("dwi_mask", "inputnode.dwi_mask"), ]), ]) # fmt: on if config.workflow.run_reconall: from niworkflows.interfaces.nibabel import ApplyMask from niworkflows.anat.coregistration import init_bbreg_wf from ...utils.misc import sub_prefix as _prefix # Mask the T1w t1w_brain = pe.Node(ApplyMask(), name="t1w_brain") bbr_wf = init_bbreg_wf( debug=config.execution.debug, epi2t1w_init=config.workflow.dwi2t1w_init, omp_nthreads=config.nipype.omp_nthreads, ) ds_report_reg = pe.Node( DerivativesDataSink( base_directory=str(config.execution.output_dir), datatype="figures", ), name="ds_report_reg", run_without_submitting=True, ) def _bold_reg_suffix(fallback): return "coreg" if fallback else "bbregister" # fmt: off workflow.connect([ (inputnode, bbr_wf, [ ("fsnative2t1w_xfm", "inputnode.fsnative2t1w_xfm"), (("subject_id", _prefix), "inputnode.subject_id"), ("subjects_dir", "inputnode.subjects_dir"), ]), # T1w Mask (inputnode, t1w_brain, [("t1w_preproc", "in_file"), ("t1w_mask", "in_mask")]), (inputnode, ds_report_reg, [("dwi_file", "source_file")]), # BBRegister (dwi_reference_wf, bbr_wf, [("outputnode.ref_image", "inputnode.in_file")]), (bbr_wf, ds_report_reg, [('outputnode.out_report', 'in_file'), (('outputnode.fallback', _bold_reg_suffix), 'desc')]), ]) # fmt: on if "eddy" not in config.workflow.ignore: # Eddy distortion correction eddy_wf = init_eddy_wf(debug=config.execution.debug) eddy_wf.inputs.inputnode.metadata = layout.get_metadata(str(dwi_file)) ds_report_eddy = pe.Node( DerivativesDataSink( base_directory=str(config.execution.output_dir), desc="eddy", datatype="figures", ), name="ds_report_eddy", run_without_submitting=True, ) eddy_report = pe.Node( SimpleBeforeAfter( before_label="Distorted", after_label="Eddy Corrected", ), name="eddy_report", mem_gb=0.1, ) # fmt:off workflow.connect([ (dwi_reference_wf, eddy_wf, [ ("outputnode.dwi_file", "inputnode.dwi_file"), ("outputnode.dwi_mask", "inputnode.dwi_mask"), ]), (inputnode, eddy_wf, [("in_bvec", "inputnode.in_bvec"), ("in_bval", "inputnode.in_bval")]), (dwi_reference_wf, eddy_report, [("outputnode.ref_image", "before") ]), (eddy_wf, eddy_report, [('outputnode.eddy_ref_image', 'after')]), (dwi_reference_wf, ds_report_eddy, [("outputnode.dwi_file", "source_file")]), (eddy_report, ds_report_eddy, [("out_report", "in_file")]), ]) # fmt:on # REPORTING ############################################################ reportlets_wf = init_reportlets_wf( str(config.execution.output_dir), sdc_report=has_fieldmap, ) # fmt: off workflow.connect([ (inputnode, reportlets_wf, [("dwi_file", "inputnode.source_file")]), (dwi_reference_wf, reportlets_wf, [ ("outputnode.validation_report", "inputnode.validation_report"), ]), (outputnode, reportlets_wf, [ ("dwi_reference", "inputnode.dwi_ref"), ("dwi_mask", "inputnode.dwi_mask"), ]), ]) # fmt: on if not has_fieldmap: # fmt: off workflow.connect([ (dwi_reference_wf, outputnode, [("outputnode.ref_image", "dwi_reference"), ("outputnode.dwi_mask", "dwi_mask")]), ]) # fmt: on return workflow 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=config.execution.debug, omp_nthreads=config.nipype.omp_nthreads, write_coeff=True, ) unwarp_wf = init_unwarp_wf(debug=config.execution.debug, omp_nthreads=config.nipype.omp_nthreads) unwarp_wf.inputs.inputnode.metadata = layout.get_metadata(str(dwi_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' <{dwi_file}>, using {estimator_key[0]}") sdc_report = pe.Node( SimpleBeforeAfter( before_label="Distorted", after_label="Corrected", ), name="sdc_report", mem_gb=0.1, ) # 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")]), (dwi_reference_wf, coeff2epi_wf, [("outputnode.ref_image", "inputnode.target_ref"), ("outputnode.dwi_mask", "inputnode.target_mask")]), (dwi_reference_wf, unwarp_wf, [("outputnode.ref_image", "inputnode.distorted")]), (coeff2epi_wf, unwarp_wf, [("outputnode.fmap_coeff", "inputnode.fmap_coeff")]), (dwi_reference_wf, sdc_report, [("outputnode.ref_image", "before")]), (unwarp_wf, sdc_report, [("outputnode.corrected", "after"), ("outputnode.corrected_mask", "wm_seg")]), (sdc_report, reportlets_wf, [("out_report", "inputnode.sdc_report")]), (unwarp_wf, outputnode, [("outputnode.corrected", "dwi_reference"), ("outputnode.corrected_mask", "dwi_mask")]), ]) # fmt: on 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
def init_anat_preproc_wf( *, bids_root, longitudinal, t2w, omp_nthreads, output_dir, skull_strip_mode, skull_strip_template, spaces, debug=False, existing_derivatives=None, name="anat_preproc_wf", skull_strip_fixed_seed=False, ): """ Stage the anatomical preprocessing steps of *sMRIPrep*. This includes: - T1w reference: realigning and then averaging T1w images. - Brain extraction and INU (bias field) correction. - Brain tissue segmentation. - Spatial normalization to standard spaces. .. include:: ../links.rst Workflow Graph .. workflow:: :graph2use: orig :simple_form: yes from niworkflows.utils.spaces import SpatialReferences, Reference from smriprep.workflows.anatomical import init_anat_preproc_wf wf = init_anat_preproc_wf( bids_root='.', longitudinal=False, t2w=['t2w.nii.gz'], omp_nthreads=1, output_dir='.', skull_strip_mode='force', skull_strip_template=Reference('OASIS30ANTs'), spaces=SpatialReferences(spaces=['Fischer344']), ) Parameters ---------- bids_root : :obj:`str` Path of the input BIDS dataset root existing_derivatives : :obj:`dict` or None Dictionary mapping output specification attribute names and paths to corresponding derivatives. longitudinal : :obj:`bool` Create unbiased structural template, regardless of number of inputs (may increase runtime) t1w : :obj:`list` List of T1-weighted structural images. omp_nthreads : :obj:`int` Maximum number of threads an individual process may use output_dir : :obj:`str` Directory in which to save derivatives skull_strip_template : :py:class:`~niworkflows.utils.spaces.Reference` Spatial reference to use in atlas-based brain extraction. spaces : :py:class:`~niworkflows.utils.spaces.SpatialReferences` Object containing standard and nonstandard space specifications. debug : :obj:`bool` Enable debugging outputs name : :obj:`str`, optional Workflow name (default: anat_preproc_wf) skull_strip_mode : :obj:`str` Determiner for T1-weighted skull stripping (`force` ensures skull stripping, `skip` ignores skull stripping, and `auto` automatically ignores skull stripping if pre-stripped brains are detected). skull_strip_fixed_seed : :obj:`bool` Do not use a random seed for skull-stripping - will ensure run-to-run replicability when used with --omp-nthreads 1 (default: ``False``). Inputs ------ t1w List of T1-weighted structural images t2w List of T2-weighted structural images roi A mask to exclude regions during standardization flair List of FLAIR images Outputs ------- t1w_preproc The T1w reference map, which is calculated as the average of bias-corrected and preprocessed T1w images, defining the anatomical space. t1w_brain Skull-stripped ``t1w_preproc`` t1w_mask Brain (binary) mask estimated by brain extraction. anat_dseg Brain tissue segmentation of the preprocessed structural image, including gray-matter (GM), white-matter (WM) and cerebrospinal fluid (CSF). anat_tpms List of tissue probability maps corresponding to ``anat_dseg``. std_preproc T1w reference resampled in one or more standard spaces. std_mask Mask of skull-stripped template, in MNI space std_dseg Segmentation, resampled into MNI space std_tpms List of tissue probability maps in MNI space subjects_dir FreeSurfer SUBJECTS_DIR anat2std_xfm Nonlinear spatial transform to resample imaging data given in anatomical space into standard space. std2anat_xfm Inverse transform of the above. See Also -------- * :py:func:`~niworkflows.anat.ants.init_brain_extraction_wf` * :py:func:`~smriprep.workflows.surfaces.init_surface_recon_wf` """ workflow = Workflow(name=name) num_t2w = len(t2w) desc = """Anatomical data preprocessing : """ desc += """\ A total of {num_t2w} T2-weighted (T2w) images were found within the input BIDS dataset.""" inputnode = pe.Node(niu.IdentityInterface(fields=["t2w", "roi"]), name="inputnode") outputnode = pe.Node( niu.IdentityInterface( fields=[ "t2w_preproc", "t2w_mask", "t2w_dseg", "t2w_tpms", "std_preproc", "std_mask", "std_dseg", "std_tpms", "anat2std_xfm", "std2anat_xfm", "template", ] ), name="outputnode", ) # Connect reportlets workflows anat_reports_wf = init_anat_reports_wf(output_dir=output_dir,) workflow.connect( [ ( outputnode, anat_reports_wf, [ ("t2w_preproc", "inputnode.t1w_preproc"), ("t2w_mask", "inputnode.t1w_mask"), ("t2w_dseg", "inputnode.anat_dseg"), ], ), ] ) if existing_derivatives is not None: LOGGER.log( 25, "Anatomical workflow will reuse prior derivatives found in the " "output folder (%s).", output_dir, ) desc += """ Anatomical preprocessing was reused from previously existing derivative objects.\n""" workflow.__desc__ = desc templates = existing_derivatives.pop("template") templatesource = pe.Node( niu.IdentityInterface(fields=["template"]), name="templatesource" ) templatesource.iterables = [("template", templates)] outputnode.inputs.template = templates for field, value in existing_derivatives.items(): setattr(outputnode.inputs, field, value) anat_reports_wf.inputs.inputnode.source_file = fix_multi_source_name( [existing_derivatives["t2w_preproc"]], modality="T2w" ) stdselect = pe.Node( KeySelect(fields=["std_preproc", "std_mask"], keys=templates), name="stdselect", run_without_submitting=True, ) workflow.connect( [ ( inputnode, outputnode, [("subjects_dir", "subjects_dir"), ("subject_id", "subject_id")], ), ( inputnode, anat_reports_wf, [ ("subjects_dir", "inputnode.subjects_dir"), ("subject_id", "inputnode.subject_id"), ], ), (templatesource, stdselect, [("template", "key")]), ( outputnode, stdselect, [("std_preproc", "std_preproc"), ("std_mask", "std_mask")], ), ( stdselect, anat_reports_wf, [ ("key", "inputnode.template"), ("std_preproc", "inputnode.std_t1w"), ("std_mask", "inputnode.std_mask"), ], ), ] ) return workflow # The workflow is not cached. desc += ( """ All of them were corrected for intensity non-uniformity (INU) """ if num_t2w > 1 else """\ The T2-weighted (T2w) image was corrected for intensity non-uniformity (INU) """ ) desc += """\ with `N4BiasFieldCorrection` [@n4], distributed with ANTs {ants_ver} \ [@ants, RRID:SCR_004757]""" desc += ( ".\n" if num_t2w > 1 else ", and used as T2w-reference throughout the workflow.\n" ) desc += """\ The T2w-reference was then skull-stripped with a *Nipype* implementation of the `antsBrainExtraction.sh` workflow (from ANTs), using {skullstrip_tpl} as target template. Brain tissue segmentation of cerebrospinal fluid (CSF), white-matter (WM) and gray-matter (GM) was performed on the brain-extracted T1w using `fast` [FSL {fsl_ver}, RRID:SCR_002823, @fsl_fast]. """ workflow.__desc__ = desc.format( ants_ver=ANTsInfo.version() or "(version unknown)", fsl_ver=fsl.FAST().version or "(version unknown)", num_t2w=num_t2w, skullstrip_tpl=skull_strip_template.fullname, ) buffernode = pe.Node( niu.IdentityInterface(fields=["t2w_brain", "t2w_mask"]), name="buffernode" ) # 1. Anatomical reference generation - average input T1w images. anat_template_wf = init_anat_template_wf( longitudinal=longitudinal, omp_nthreads=omp_nthreads, num_t1w=num_t2w ) anat_validate = pe.Node( ValidateImage(), name="anat_validate", run_without_submitting=True ) # 2. Brain-extraction and INU (bias field) correction. if skull_strip_mode == "auto": import numpy as np import nibabel as nb def _is_skull_stripped(imgs): """Check if T1w images are skull-stripped.""" def _check_img(img): data = np.abs(nb.load(img).get_fdata(dtype=np.float32)) sidevals = ( data[0, :, :].sum() + data[-1, :, :].sum() + data[:, 0, :].sum() + data[:, -1, :].sum() + data[:, :, 0].sum() + data[:, :, -1].sum() ) return sidevals < 10 return all(_check_img(img) for img in imgs) skull_strip_mode = _is_skull_stripped(t2w) if skull_strip_mode in (True, "skip"): raise NotImplementedError("Cannot run on already skull-stripped images.") else: # ants_affine_init? brain_extraction_wf = init_rodent_brain_extraction_wf( template_id=skull_strip_template.space, omp_nthreads=omp_nthreads, debug=debug ) # 3. Spatial normalization anat_norm_wf = init_anat_norm_wf( debug=debug, omp_nthreads=omp_nthreads, templates=spaces.get_spaces(nonstandard=False, dim=(3,)), ) # fmt:off workflow.connect([ # Step 1. (inputnode, anat_template_wf, [('t2w', 'inputnode.t1w')]), (anat_template_wf, anat_validate, [ ('outputnode.t1w_ref', 'in_file')]), (anat_validate, brain_extraction_wf, [ ('out_file', 'inputnode.in_files')]), (brain_extraction_wf, outputnode, [ (('outputnode.out_corrected', _pop), 't2w_preproc')]), (anat_template_wf, outputnode, [ ('outputnode.t1w_realign_xfm', 't2w_ref_xfms')]), (buffernode, outputnode, [('t2w_brain', 't2w_brain'), ('t2w_mask', 't2w_mask')]), # Steps 2 and 3 (inputnode, anat_norm_wf, [ (('t2w', fix_multi_source_name), 'inputnode.orig_t1w'), ('roi', 'inputnode.lesion_mask')]), (brain_extraction_wf, anat_norm_wf, [ (('outputnode.out_corrected', _pop), 'inputnode.moving_image')]), (buffernode, anat_norm_wf, [('t2w_mask', 'inputnode.moving_mask')]), (anat_norm_wf, outputnode, [ ('poutputnode.standardized', 'std_preproc'), ('poutputnode.std_mask', 'std_mask'), ('outputnode.template', 'template'), ('outputnode.anat2std_xfm', 'anat2std_xfm'), ('outputnode.std2anat_xfm', 'std2anat_xfm'), ]), # Connect reportlets (inputnode, anat_reports_wf, [ (('t2w', fix_multi_source_name), 'inputnode.source_file')]), (outputnode, anat_reports_wf, [ ('std_preproc', 'inputnode.std_t1w'), ('std_mask', 'inputnode.std_mask'), ]), (anat_template_wf, anat_reports_wf, [ ('outputnode.out_report', 'inputnode.t1w_conform_report')]), (anat_norm_wf, anat_reports_wf, [ ('poutputnode.template', 'inputnode.template')]), ]) # fmt:off # Write outputs ############################################3 anat_derivatives_wf = init_anat_derivatives_wf( bids_root=bids_root, num_t1w=num_t2w, output_dir=output_dir, spaces=spaces, ) # fmt:off workflow.connect([ # Connect derivatives (anat_template_wf, anat_derivatives_wf, [ ('outputnode.t1w_valid_list', 'inputnode.source_files')]), (anat_norm_wf, anat_derivatives_wf, [ ('outputnode.template', 'inputnode.template'), ('outputnode.anat2std_xfm', 'inputnode.anat2std_xfm'), ('outputnode.std2anat_xfm', 'inputnode.std2anat_xfm') ]), (outputnode, anat_derivatives_wf, [ ('t2w_ref_xfms', 'inputnode.t1w_ref_xfms'), ('t2w_preproc', 'inputnode.t1w_preproc'), ('t2w_mask', 'inputnode.t1w_mask'), ]), ]) # fmt:on # 4. Brain tissue segmentation - FAST produces: 0 (bg), 1 (wm), 2 (csf), 3 (gm) gm_tpm = get("Fischer344", label="GM", suffix="probseg") wm_tpm = get("Fischer344", label="WM", suffix="probseg") csf_tpm = get("Fischer344", label="CSF", suffix="probseg") xfm_gm = pe.Node( ApplyTransforms(input_image=_pop(gm_tpm), interpolation="MultiLabel"), name="xfm_gm", ) xfm_wm = pe.Node( ApplyTransforms(input_image=_pop(wm_tpm), interpolation="MultiLabel"), name="xfm_wm", ) xfm_csf = pe.Node( ApplyTransforms(input_image=_pop(csf_tpm), interpolation="MultiLabel"), name="xfm_csf", ) mrg_tpms = pe.Node(niu.Merge(3), name="mrg_tpms") anat_dseg = pe.Node( FAST( segments=True, probability_maps=True, bias_iters=0, no_bias=True, ), name="anat_dseg", mem_gb=3, ) # Change LookUp Table - BIDS wants: 0 (bg), 1 (gm), 2 (wm), 3 (csf) lut_anat_dseg = pe.Node( niu.Function(function=_apply_bids_lut), name="lut_anat_dseg" ) lut_anat_dseg.inputs.lut = (0, 3, 2, 1) # Maps: 0 -> 0, 3 -> 1, 2 -> 2, 1 -> 3 fast2bids = pe.Node( niu.Function(function=_probseg_fast2bids), name="fast2bids", run_without_submitting=True, ) # 5. Move native dseg & tpms back to standard space xfm_dseg = pe.Node(ApplyTransforms(interpolation="MultiLabel"), name="xfm_dseg") xfm_tpms = pe.MapNode( ApplyTransforms( dimension=3, default_value=0, float=True, interpolation="Gaussian" ), iterfield=["input_image"], name="xfm_tpms", ) # fmt:off workflow.connect([ # step 4 (brain_extraction_wf, buffernode, [ (('outputnode.out_brain', _pop), 't2w_brain'), ('outputnode.out_mask', 't2w_mask')]), (buffernode, anat_dseg, [('t2w_brain', 'in_files')]), (brain_extraction_wf, xfm_gm, [( ('outputnode.out_corrected', _pop), 'reference_image')]), (brain_extraction_wf, xfm_wm, [( ('outputnode.out_corrected', _pop), 'reference_image')]), (brain_extraction_wf, xfm_csf, [( ('outputnode.out_corrected', _pop), 'reference_image')]), (anat_norm_wf, xfm_gm, [( 'outputnode.std2anat_xfm', 'transforms')]), (anat_norm_wf, xfm_wm, [( 'outputnode.std2anat_xfm', 'transforms')]), (anat_norm_wf, xfm_csf, [( 'outputnode.std2anat_xfm', 'transforms')]), (xfm_gm, mrg_tpms, [('output_image', 'in1')]), (xfm_wm, mrg_tpms, [('output_image', 'in2')]), (xfm_csf, mrg_tpms, [('output_image', 'in3')]), (mrg_tpms, anat_dseg, [('out', 'other_priors')]), (anat_dseg, lut_anat_dseg, [('partial_volume_map', 'in_dseg')]), (lut_anat_dseg, outputnode, [('out', 't2w_dseg')]), (anat_dseg, fast2bids, [('partial_volume_files', 'inlist')]), (fast2bids, outputnode, [('out', 't2w_tpms')]), (outputnode, anat_derivatives_wf, [ ('t2w_tpms', 'inputnode.anat_tpms'), ('t2w_dseg', 'inputnode.anat_dseg') ]), # step 5 (anat_norm_wf, xfm_dseg, [('poutputnode.standardized', 'reference_image')]), (lut_anat_dseg, xfm_dseg, [('out', 'input_image')]), (anat_norm_wf, xfm_dseg, [('poutputnode.anat2std_xfm', 'transforms')]), (anat_norm_wf, xfm_tpms, [('poutputnode.standardized', 'reference_image')]), (fast2bids, xfm_tpms, [('out', 'input_image')]), (anat_norm_wf, xfm_tpms, [('poutputnode.anat2std_xfm', 'transforms')]), (xfm_dseg, outputnode, [('output_image', 'std_dseg')]), (xfm_tpms, outputnode, [('output_image', 'std_tpms')]), (outputnode, anat_derivatives_wf, [ ('std_dseg', 'inputnode.std_dseg'), ('std_tpms', 'inputnode.std_tpms') ]), ]) # fmt:on 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_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_bold_grayords_wf(grayord_density, mem_gb, repetition_time, name='bold_grayords_wf'): """ Sample Grayordinates files onto the fsLR atlas. Outputs are in CIFTI2 format. Workflow Graph .. workflow:: :graph2use: colored :simple_form: yes from fmriprep_rodents.workflows.bold import init_bold_grayords_wf wf = init_bold_grayords_wf(mem_gb=0.1, grayord_density='91k') Parameters ---------- grayord_density : :obj:`str` Either `91k` or `170k`, representing the total of vertices or *grayordinates*. mem_gb : :obj:`float` Size of BOLD file in GB name : :obj:`str` Unique name for the subworkflow (default: ``'bold_grayords_wf'``) Inputs ------ bold_std : :obj:`str` List of BOLD conversions to standard spaces. spatial_reference :obj:`str` List of unique identifiers corresponding to the BOLD standard-conversions. subjects_dir : :obj:`str` FreeSurfer's subjects directory. surf_files : :obj:`str` List of BOLD files resampled on the fsaverage (ico7) surfaces. surf_refs : List of unique identifiers corresponding to the BOLD surface-conversions. Outputs ------- cifti_bold : :obj:`str` List of BOLD grayordinates files - (L)eft and (R)ight. cifti_variant : :obj:`str` Only ``'HCP Grayordinates'`` is currently supported. cifti_metadata : :obj:`str` Path of metadata files corresponding to ``cifti_bold``. cifti_density : :obj:`str` Density (i.e., either `91k` or `170k`) of ``cifti_bold``. """ import templateflow.api as tf from niworkflows.engine.workflows import LiterateWorkflow as Workflow from niworkflows.interfaces.cifti import GenerateCifti from niworkflows.interfaces.utility import KeySelect workflow = Workflow(name=name) workflow.__desc__ = """\ *Grayordinates* files [@hcppipelines] containing {density} samples were also generated using the highest-resolution ``fsaverage`` as intermediate standardized surface space. """.format(density=grayord_density) fslr_density, mni_density = ('32k', '2') if grayord_density == '91k' else ('59k', '1') inputnode = pe.Node(niu.IdentityInterface(fields=[ 'bold_std', 'spatial_reference', 'subjects_dir', 'surf_files', 'surf_refs', ]), name='inputnode') outputnode = pe.Node(niu.IdentityInterface(fields=[ 'cifti_bold', 'cifti_variant', 'cifti_metadata', 'cifti_density', ]), name='outputnode') # extract out to BOLD base select_std = pe.Node(KeySelect(fields=['bold_std']), name='select_std', run_without_submitting=True, nohash=True) select_std.inputs.key = 'MNI152NLin6Asym_res-%s' % mni_density select_fs_surf = pe.Node(KeySelect(fields=['surf_files']), name='select_fs_surf', run_without_submitting=True, mem_gb=DEFAULT_MEMORY_MIN_GB) select_fs_surf.inputs.key = 'fsaverage' # Setup Workbench command. LR ordering for hemi can be assumed, as it is imposed # by the iterfield of the MapNode in the surface sampling workflow above. resample = pe.MapNode(wb.MetricResample(method='ADAP_BARY_AREA', area_metrics=True), name='resample', iterfield=[ 'in_file', 'out_file', 'new_sphere', 'new_area', 'current_sphere', 'current_area' ]) resample.inputs.current_sphere = [ str( tf.get('fsaverage', hemi=hemi, density='164k', desc='std', suffix='sphere')) for hemi in 'LR' ] resample.inputs.current_area = [ str( tf.get('fsaverage', hemi=hemi, density='164k', desc='vaavg', suffix='midthickness')) for hemi in 'LR' ] resample.inputs.new_sphere = [ str( tf.get('fsLR', space='fsaverage', hemi=hemi, density=fslr_density, suffix='sphere')) for hemi in 'LR' ] resample.inputs.new_area = [ str( tf.get('fsLR', hemi=hemi, density=fslr_density, desc='vaavg', suffix='midthickness')) for hemi in 'LR' ] resample.inputs.out_file = [ 'space-fsLR_hemi-%s_den-%s_bold.gii' % (h, grayord_density) for h in 'LR' ] gen_cifti = pe.Node(GenerateCifti( volume_target='MNI152NLin6Asym', surface_target='fsLR', TR=repetition_time, surface_density=fslr_density, ), name="gen_cifti") workflow.connect([ (inputnode, gen_cifti, [('subjects_dir', 'subjects_dir')]), (inputnode, select_std, [('bold_std', 'bold_std'), ('spatial_reference', 'keys')]), (inputnode, select_fs_surf, [('surf_files', 'surf_files'), ('surf_refs', 'keys')]), (select_fs_surf, resample, [('surf_files', 'in_file')]), (select_std, gen_cifti, [('bold_std', 'bold_file')]), (resample, gen_cifti, [('out_file', 'surface_bolds')]), (gen_cifti, outputnode, [('out_file', 'cifti_bold'), ('variant', 'cifti_variant'), ('out_metadata', 'cifti_metadata'), ('density', 'cifti_density')]), ]) return workflow
def init_carpetplot_wf(standard_spaces, mem_gb, metadata, name="bold_carpet_wf"): """ Resamples the MNI parcellation (ad-hoc parcellation derived from the Harvard-Oxford template and others). **Parameters** mem_gb : float Size of BOLD file in GB - please note that this size should be calculated after resamplings that may extend the FoV metadata : dict BIDS metadata for BOLD file name : str Name of workflow (default: ``bold_carpet_wf``) **Inputs** bold BOLD image, after the prescribed corrections (STC, HMC and SDC) when available. bold_mask BOLD series mask confounds_file TSV of all aggregated confounds t1_bold_xform Affine matrix that maps the T1w space into alignment with the native BOLD space std2anat_xfm ANTs-compatible affine-and-warp transform file **Outputs** out_carpetplot Path of the generated SVG file """ inputnode = pe.Node(niu.IdentityInterface(fields=[ 'bold', 'bold_mask', 'confounds_file', 't1_bold_xform', 'std2anat_xfm' ]), name='inputnode') outputnode = pe.Node(niu.IdentityInterface(fields=['out_carpetplot']), name='outputnode') select_std = pe.Node(KeySelect(keys=list(standard_spaces.keys()), fields=['std2anat_xfm']), name='select_std', run_without_submitting=True) select_std.inputs.key = 'MNI152NLin2009cAsym' # List transforms mrg_xfms = pe.Node(niu.Merge(2), name='mrg_xfms') # Warp segmentation into EPI space resample_parc = pe.Node(ApplyTransforms( float=True, input_image=str( get_template('MNI152NLin2009cAsym', resolution=1, desc='carpet', suffix='dseg', extensions=['.nii', '.nii.gz'])), dimension=3, default_value=0, interpolation='MultiLabel'), name='resample_parc') # Carpetplot and confounds plot conf_plot = pe.Node(FMRISummary(tr=metadata['RepetitionTime'], confounds_list=[ ('global_signal', None, 'GS'), ('csf', None, 'GSCSF'), ('white_matter', None, 'GSWM'), ('std_dvars', None, 'DVARS'), ('framewise_displacement', 'mm', 'FD') ]), name='conf_plot', mem_gb=mem_gb) ds_report_bold_conf = pe.Node(DerivativesDataSink(desc='carpetplot', keep_dtype=True), name='ds_report_bold_conf', run_without_submitting=True, mem_gb=DEFAULT_MEMORY_MIN_GB) workflow = Workflow(name=name) workflow.connect([ (inputnode, select_std, [('std2anat_xfm', 'std2anat_xfm')]), (inputnode, mrg_xfms, [('t1_bold_xform', 'in1')]), (inputnode, resample_parc, [('bold_mask', 'reference_image')]), (select_std, mrg_xfms, [('std2anat_xfm', 'in2')]), (mrg_xfms, resample_parc, [('out', 'transforms')]), # Carpetplot (inputnode, conf_plot, [('bold', 'in_func'), ('bold_mask', 'in_mask'), ('confounds_file', 'confounds_file')]), (resample_parc, conf_plot, [('output_image', 'in_segm')]), (conf_plot, ds_report_bold_conf, [('out_file', 'in_file')]), (conf_plot, outputnode, [('out_file', 'out_carpetplot')]), ]) return workflow
def init_sdc_wf(fmaps, bold_meta, omp_nthreads=1, debug=False, fmap_bspline=False, fmap_demean=True): """ This workflow implements the heuristics to choose a :abbr:`SDC (susceptibility distortion correction)` strategy. When no field map information is present within the BIDS inputs, the EXPERIMENTAL "fieldmap-less SyN" can be performed, using the ``--use-syn`` argument. When ``--force-syn`` is specified, then the "fieldmap-less SyN" is always executed and reported despite of other fieldmaps available with higher priority. In the latter case (some sort of fieldmap(s) is available and ``--force-syn`` is requested), then the :abbr:`SDC (susceptibility distortion correction)` method applied is that with the highest priority. .. workflow:: :graph2use: orig :simple_form: yes from fmriprep.workflows.fieldmap import init_sdc_wf wf = init_sdc_wf( fmaps=[{ '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', }], bold_meta={ 'RepetitionTime': 2.0, 'SliceTiming': [0.0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9], 'PhaseEncodingDirection': 'j', }, ) **Parameters** fmaps : list of pybids dicts A list of dictionaries with the available fieldmaps (and their metadata using the key ``'metadata'`` for the case of *epi* fieldmaps) bold_meta : dict BIDS metadata dictionary corresponding to the BOLD run omp_nthreads : int Maximum number of threads an individual process may use fmap_bspline : bool **Experimental**: Fit B-Spline field using least-squares fmap_demean : bool Demean voxel-shift map during unwarp debug : bool Enable debugging outputs **Inputs** bold_ref A BOLD reference calculated at a previous stage bold_ref_brain Same as above, but brain-masked bold_mask Brain mask for the BOLD run t1_brain T1w image, brain-masked, for the fieldmap-less SyN method std2anat_xfm List of standard-to-T1w transforms generated during spatial normalization (only for the fieldmap-less SyN method). template : str Name of template from which prior knowledge will be mapped into the subject's T1w reference (only for the fieldmap-less SyN method) templates : str Name of templates that index the ``std2anat_xfm`` input list (only for the fieldmap-less SyN method). **Outputs** bold_ref An unwarped BOLD reference bold_mask The corresponding new mask after unwarping bold_ref_brain Brain-extracted, unwarped BOLD reference out_warp The deformation field to unwarp the susceptibility distortions syn_bold_ref If ``--force-syn``, an unwarped BOLD reference with this method (for reporting purposes) """ # TODO: To be removed (filter out unsupported fieldmaps): fmaps = [fmap for fmap in fmaps if fmap['suffix'] in FMAP_PRIORITY] workflow = Workflow(name='sdc_wf' if fmaps else 'sdc_bypass_wf') inputnode = pe.Node(niu.IdentityInterface(fields=[ 'bold_ref', 'bold_ref_brain', 'bold_mask', 't1_brain', 'std2anat_xfm', 'template', 'templates' ]), name='inputnode') outputnode = pe.Node(niu.IdentityInterface(fields=[ 'bold_ref', 'bold_mask', 'bold_ref_brain', 'out_warp', 'syn_bold_ref', 'method' ]), name='outputnode') # No fieldmaps - forward inputs to outputs if not fmaps: outputnode.inputs.method = 'None' workflow.connect([ (inputnode, outputnode, [('bold_ref', 'bold_ref'), ('bold_mask', 'bold_mask'), ('bold_ref_brain', 'bold_ref_brain')]), ]) return workflow workflow.__postdesc__ = """\ Based on the estimated susceptibility distortion, an unwarped BOLD reference was calculated for a more accurate co-registration with the anatomical reference. """ # In case there are multiple fieldmaps prefer EPI fmaps.sort(key=lambda fmap: FMAP_PRIORITY[fmap['suffix']]) fmap = fmaps[0] # PEPOLAR path if fmap['suffix'] == 'epi': outputnode.inputs.method = 'PEB/PEPOLAR (phase-encoding based / PE-POLARity)' # Get EPI polarities and their metadata epi_fmaps = [(fmap_['epi'], fmap_['metadata']["PhaseEncodingDirection"]) for fmap_ in fmaps if fmap_['suffix'] == 'epi'] sdc_unwarp_wf = init_pepolar_unwarp_wf(bold_meta=bold_meta, epi_fmaps=epi_fmaps, omp_nthreads=omp_nthreads, name='pepolar_unwarp_wf') workflow.connect([ (inputnode, sdc_unwarp_wf, [('bold_ref', 'inputnode.in_reference'), ('bold_mask', 'inputnode.in_mask'), ('bold_ref_brain', 'inputnode.in_reference_brain')]), ]) # FIELDMAP path if fmap['suffix'] in ['fieldmap', 'phasediff']: outputnode.inputs.method = 'FMB (%s-based)' % fmap['suffix'] # Import specific workflows here, so we don't break everything with one # unused workflow. if fmap['suffix'] == 'fieldmap': from .fmap import init_fmap_wf fmap_estimator_wf = init_fmap_wf(omp_nthreads=omp_nthreads, fmap_bspline=fmap_bspline) # set inputs fmap_estimator_wf.inputs.inputnode.fieldmap = fmap['fieldmap'] fmap_estimator_wf.inputs.inputnode.magnitude = fmap['magnitude'] if fmap['suffix'] == 'phasediff': from .phdiff import init_phdiff_wf fmap_estimator_wf = init_phdiff_wf(omp_nthreads=omp_nthreads) # set inputs fmap_estimator_wf.inputs.inputnode.phasediff = fmap['phasediff'] fmap_estimator_wf.inputs.inputnode.magnitude = [ fmap_ for key, fmap_ in sorted(fmap.items()) if key.startswith("magnitude") ] sdc_unwarp_wf = init_sdc_unwarp_wf(omp_nthreads=omp_nthreads, fmap_demean=fmap_demean, debug=debug, name='sdc_unwarp_wf') sdc_unwarp_wf.inputs.inputnode.metadata = bold_meta workflow.connect([ (inputnode, sdc_unwarp_wf, [('bold_ref', 'inputnode.in_reference'), ('bold_ref_brain', 'inputnode.in_reference_brain'), ('bold_mask', 'inputnode.in_mask')]), (fmap_estimator_wf, sdc_unwarp_wf, [('outputnode.fmap', 'inputnode.fmap'), ('outputnode.fmap_ref', 'inputnode.fmap_ref'), ('outputnode.fmap_mask', 'inputnode.fmap_mask')]), ]) # FIELDMAP-less path if any(fm['suffix'] == 'syn' for fm in fmaps): # Select template sdc_select_std = pe.Node(KeySelect(fields=['std2anat_xfm']), name='sdc_select_std', run_without_submitting=True) syn_sdc_wf = init_syn_sdc_wf(bold_pe=bold_meta.get( 'PhaseEncodingDirection', None), omp_nthreads=omp_nthreads) workflow.connect([ (inputnode, sdc_select_std, [('template', 'key'), ('templates', 'keys'), ('std2anat_xfm', 'std2anat_xfm')]), (sdc_select_std, syn_sdc_wf, [('std2anat_xfm', 'inputnode.std2anat_xfm')]), (inputnode, syn_sdc_wf, [('t1_brain', 'inputnode.t1_brain'), ('bold_ref', 'inputnode.bold_ref'), ('bold_ref_brain', 'inputnode.bold_ref_brain'), ('template', 'inputnode.template')]), ]) # XXX Eliminate branch when forcing isn't an option if fmap['suffix'] == 'syn': # No fieldmaps, but --use-syn outputnode.inputs.method = 'FLB ("fieldmap-less", SyN-based)' sdc_unwarp_wf = syn_sdc_wf else: # --force-syn was called when other fieldmap was present sdc_unwarp_wf.__desc__ = None workflow.connect([ (syn_sdc_wf, outputnode, [('outputnode.out_reference', 'syn_bold_ref')]), ]) workflow.connect([ (sdc_unwarp_wf, outputnode, [('outputnode.out_warp', 'out_warp'), ('outputnode.out_reference', 'bold_ref'), ('outputnode.out_reference_brain', 'bold_ref_brain'), ('outputnode.out_mask', 'bold_mask')]), ]) return workflow
def init_func_derivatives_wf( bids_root, cifti_output, freesurfer, metadata, output_dir, spaces, use_aroma, name='func_derivatives_wf', ): """ Set up a battery of datasinks to store derivatives in the right location. Parameters ---------- bids_root : :obj:`str` Original BIDS dataset path. cifti_output : :obj:`bool` Whether the ``--cifti-output`` flag was set. freesurfer : :obj:`bool` Whether FreeSurfer anatomical processing was run. metadata : :obj:`dict` Metadata dictionary associated to the BOLD run. output_dir : :obj:`str` Where derivatives should be written out to. 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). use_aroma : :obj:`bool` Whether ``--use-aroma`` flag was set. name : :obj:`str` This workflow's identifier (default: ``func_derivatives_wf``). """ from niworkflows.engine.workflows import LiterateWorkflow as Workflow from niworkflows.interfaces.utility import KeySelect from smriprep.workflows.outputs import _bids_relative nonstd_spaces = set(spaces.get_nonstandard()) workflow = Workflow(name=name) inputnode = pe.Node(niu.IdentityInterface(fields=[ 'aroma_noise_ics', 'bold_aparc_std', 'bold_aparc_t1', 'bold_aseg_std', 'bold_aseg_t1', 'bold_cifti', 'bold_mask_std', 'bold_mask_t1', 'bold_std', 'bold_std_ref', 'bold_t1', 'bold_t1_ref', 'bold_native', 'bold_native_ref', 'bold_mask_native', 'cifti_variant', 'cifti_metadata', 'cifti_density', 'confounds', 'confounds_metadata', 'melodic_mix', 'nonaggr_denoised_file', 'source_file', 'surf_files', 'surf_refs', 'template', 'spatial_reference']), name='inputnode') raw_sources = pe.Node(niu.Function(function=_bids_relative), name='raw_sources') raw_sources.inputs.bids_root = bids_root ds_confounds = pe.Node(DerivativesDataSink( base_directory=output_dir, desc='confounds', suffix='regressors', dismiss_entities=("echo",)), name="ds_confounds", run_without_submitting=True, mem_gb=DEFAULT_MEMORY_MIN_GB) workflow.connect([ (inputnode, raw_sources, [('source_file', 'in_files')]), (inputnode, ds_confounds, [('source_file', 'source_file'), ('confounds', 'in_file'), ('confounds_metadata', 'meta_dict')]), ]) if nonstd_spaces.intersection(('func', 'run', 'bold', 'boldref', 'sbref')): ds_bold_native = pe.Node( DerivativesDataSink( base_directory=output_dir, desc='preproc', compress=True, SkullStripped=False, RepetitionTime=metadata.get('RepetitionTime'), TaskName=metadata.get('TaskName'), dismiss_entities=("echo",)), name='ds_bold_native', run_without_submitting=True, mem_gb=DEFAULT_MEMORY_MIN_GB) ds_bold_native_ref = pe.Node( DerivativesDataSink(base_directory=output_dir, suffix='boldref', compress=True, dismiss_entities=("echo",)), name='ds_bold_native_ref', run_without_submitting=True, mem_gb=DEFAULT_MEMORY_MIN_GB) ds_bold_mask_native = pe.Node( DerivativesDataSink(base_directory=output_dir, desc='brain', suffix='mask', compress=True, dismiss_entities=("echo",)), name='ds_bold_mask_native', run_without_submitting=True, mem_gb=DEFAULT_MEMORY_MIN_GB) workflow.connect([ (inputnode, ds_bold_native, [('source_file', 'source_file'), ('bold_native', 'in_file')]), (inputnode, ds_bold_native_ref, [('source_file', 'source_file'), ('bold_native_ref', 'in_file')]), (inputnode, ds_bold_mask_native, [('source_file', 'source_file'), ('bold_mask_native', 'in_file')]), (raw_sources, ds_bold_mask_native, [('out', 'RawSources')]), ]) # Resample to T1w space if nonstd_spaces.intersection(('T1w', 'anat')): ds_bold_t1 = pe.Node( DerivativesDataSink( base_directory=output_dir, space='T1w', desc='preproc', compress=True, SkullStripped=False, RepetitionTime=metadata.get('RepetitionTime'), TaskName=metadata.get('TaskName'), dismiss_entities=("echo",)), name='ds_bold_t1', run_without_submitting=True, mem_gb=DEFAULT_MEMORY_MIN_GB) ds_bold_t1_ref = pe.Node( DerivativesDataSink(base_directory=output_dir, space='T1w', suffix='boldref', compress=True, dismiss_entities=("echo",)), name='ds_bold_t1_ref', run_without_submitting=True, mem_gb=DEFAULT_MEMORY_MIN_GB) ds_bold_mask_t1 = pe.Node( DerivativesDataSink(base_directory=output_dir, space='T1w', desc='brain', suffix='mask', compress=True, dismiss_entities=("echo",)), name='ds_bold_mask_t1', run_without_submitting=True, mem_gb=DEFAULT_MEMORY_MIN_GB) workflow.connect([ (inputnode, ds_bold_t1, [('source_file', 'source_file'), ('bold_t1', 'in_file')]), (inputnode, ds_bold_t1_ref, [('source_file', 'source_file'), ('bold_t1_ref', 'in_file')]), (inputnode, ds_bold_mask_t1, [('source_file', 'source_file'), ('bold_mask_t1', 'in_file')]), (raw_sources, ds_bold_mask_t1, [('out', 'RawSources')]), ]) if freesurfer: ds_bold_aseg_t1 = pe.Node(DerivativesDataSink( base_directory=output_dir, space='T1w', desc='aseg', suffix='dseg', compress=True, dismiss_entities=("echo",)), name='ds_bold_aseg_t1', run_without_submitting=True, mem_gb=DEFAULT_MEMORY_MIN_GB) ds_bold_aparc_t1 = pe.Node(DerivativesDataSink( base_directory=output_dir, space='T1w', desc='aparcaseg', suffix='dseg', compress=True, dismiss_entities=("echo",)), name='ds_bold_aparc_t1', run_without_submitting=True, mem_gb=DEFAULT_MEMORY_MIN_GB) workflow.connect([ (inputnode, ds_bold_aseg_t1, [('source_file', 'source_file'), ('bold_aseg_t1', 'in_file')]), (inputnode, ds_bold_aparc_t1, [('source_file', 'source_file'), ('bold_aparc_t1', 'in_file')]), ]) if use_aroma: ds_aroma_noise_ics = pe.Node(DerivativesDataSink( base_directory=output_dir, suffix='AROMAnoiseICs', dismiss_entities=("echo",)), name="ds_aroma_noise_ics", run_without_submitting=True, mem_gb=DEFAULT_MEMORY_MIN_GB) ds_melodic_mix = pe.Node(DerivativesDataSink( base_directory=output_dir, desc='MELODIC', suffix='mixing', dismiss_entities=("echo",)), name="ds_melodic_mix", run_without_submitting=True, mem_gb=DEFAULT_MEMORY_MIN_GB) ds_aroma_std = pe.Node( DerivativesDataSink( base_directory=output_dir, space='MNI152NLin6Asym', desc='smoothAROMAnonaggr', compress=True), name='ds_aroma_std', run_without_submitting=True, mem_gb=DEFAULT_MEMORY_MIN_GB) workflow.connect([ (inputnode, ds_aroma_noise_ics, [('source_file', 'source_file'), ('aroma_noise_ics', 'in_file')]), (inputnode, ds_melodic_mix, [('source_file', 'source_file'), ('melodic_mix', 'in_file')]), (inputnode, ds_aroma_std, [('source_file', 'source_file'), ('nonaggr_denoised_file', 'in_file')]), ]) if getattr(spaces, '_cached') is None: return workflow # Store resamplings in standard spaces when listed in --output-spaces if spaces.cached.references: from niworkflows.interfaces.space import SpaceDataSource spacesource = pe.Node(SpaceDataSource(), name='spacesource', run_without_submitting=True) spacesource.iterables = ('in_tuple', [ (s.fullname, s.spec) for s in spaces.cached.get_standard(dim=(3,)) ]) select_std = pe.Node(KeySelect( fields=['template', 'bold_std', 'bold_std_ref', 'bold_mask_std']), name='select_std', run_without_submitting=True, mem_gb=DEFAULT_MEMORY_MIN_GB) ds_bold_std = pe.Node( DerivativesDataSink( base_directory=output_dir, desc='preproc', compress=True, SkullStripped=False, RepetitionTime=metadata.get('RepetitionTime'), TaskName=metadata.get('TaskName'), dismiss_entities=("echo",)), name='ds_bold_std', run_without_submitting=True, mem_gb=DEFAULT_MEMORY_MIN_GB) ds_bold_std_ref = pe.Node( DerivativesDataSink(base_directory=output_dir, suffix='boldref', compress=True, dismiss_entities=("echo",)), name='ds_bold_std_ref', run_without_submitting=True, mem_gb=DEFAULT_MEMORY_MIN_GB) ds_bold_mask_std = pe.Node( DerivativesDataSink(base_directory=output_dir, desc='brain', suffix='mask', compress=True, dismiss_entities=("echo",)), name='ds_bold_mask_std', run_without_submitting=True, mem_gb=DEFAULT_MEMORY_MIN_GB) workflow.connect([ (inputnode, ds_bold_std, [('source_file', 'source_file')]), (inputnode, ds_bold_std_ref, [('source_file', 'source_file')]), (inputnode, ds_bold_mask_std, [('source_file', 'source_file')]), (inputnode, select_std, [('bold_std', 'bold_std'), ('bold_std_ref', 'bold_std_ref'), ('bold_mask_std', 'bold_mask_std'), ('template', 'template'), ('spatial_reference', 'keys')]), (spacesource, select_std, [('uid', 'key')]), (select_std, ds_bold_std, [('bold_std', 'in_file')]), (spacesource, ds_bold_std, [('space', 'space'), ('cohort', 'cohort'), ('resolution', 'resolution'), ('density', 'density')]), (select_std, ds_bold_std_ref, [('bold_std_ref', 'in_file')]), (spacesource, ds_bold_std_ref, [('space', 'space'), ('cohort', 'cohort'), ('resolution', 'resolution'), ('density', 'density')]), (select_std, ds_bold_mask_std, [('bold_mask_std', 'in_file')]), (spacesource, ds_bold_mask_std, [('space', 'space'), ('cohort', 'cohort'), ('resolution', 'resolution'), ('density', 'density')]), (raw_sources, ds_bold_mask_std, [('out', 'RawSources')]), ]) if freesurfer: select_fs_std = pe.Node(KeySelect( fields=['bold_aseg_std', 'bold_aparc_std', 'template']), name='select_fs_std', run_without_submitting=True, mem_gb=DEFAULT_MEMORY_MIN_GB) ds_bold_aseg_std = pe.Node(DerivativesDataSink( base_directory=output_dir, desc='aseg', suffix='dseg', compress=True, dismiss_entities=("echo",)), name='ds_bold_aseg_std', run_without_submitting=True, mem_gb=DEFAULT_MEMORY_MIN_GB) ds_bold_aparc_std = pe.Node(DerivativesDataSink( base_directory=output_dir, desc='aparcaseg', suffix='dseg', compress=True, dismiss_entities=("echo",)), name='ds_bold_aparc_std', run_without_submitting=True, mem_gb=DEFAULT_MEMORY_MIN_GB) workflow.connect([ (spacesource, select_fs_std, [('uid', 'key')]), (inputnode, select_fs_std, [('bold_aseg_std', 'bold_aseg_std'), ('bold_aparc_std', 'bold_aparc_std'), ('template', 'template'), ('spatial_reference', 'keys')]), (select_fs_std, ds_bold_aseg_std, [('bold_aseg_std', 'in_file')]), (spacesource, ds_bold_aseg_std, [('space', 'space'), ('cohort', 'cohort'), ('resolution', 'resolution'), ('density', 'density')]), (select_fs_std, ds_bold_aparc_std, [('bold_aparc_std', 'in_file')]), (spacesource, ds_bold_aparc_std, [('space', 'space'), ('cohort', 'cohort'), ('resolution', 'resolution'), ('density', 'density')]), (inputnode, ds_bold_aseg_std, [('source_file', 'source_file')]), (inputnode, ds_bold_aparc_std, [('source_file', 'source_file')]) ]) fs_outputs = spaces.cached.get_fs_spaces() if freesurfer and fs_outputs: from niworkflows.interfaces.surf import Path2BIDS select_fs_surf = pe.Node(KeySelect( fields=['surfaces', 'surf_kwargs']), name='select_fs_surf', run_without_submitting=True, mem_gb=DEFAULT_MEMORY_MIN_GB) select_fs_surf.iterables = [('key', fs_outputs)] select_fs_surf.inputs.surf_kwargs = [{'space': s} for s in fs_outputs] name_surfs = pe.MapNode(Path2BIDS(pattern=r'(?P<hemi>[lr])h.\w+'), iterfield='in_file', name='name_surfs', run_without_submitting=True) ds_bold_surfs = pe.MapNode(DerivativesDataSink( base_directory=output_dir, extension="func.gii", dismiss_entities=("echo",)), iterfield=['in_file', 'hemi'], name='ds_bold_surfs', run_without_submitting=True, mem_gb=DEFAULT_MEMORY_MIN_GB) workflow.connect([ (inputnode, select_fs_surf, [ ('surf_files', 'surfaces'), ('surf_refs', 'keys')]), (select_fs_surf, name_surfs, [('surfaces', 'in_file')]), (inputnode, ds_bold_surfs, [('source_file', 'source_file')]), (select_fs_surf, ds_bold_surfs, [('surfaces', 'in_file'), ('key', 'space')]), (name_surfs, ds_bold_surfs, [('hemi', 'hemi')]), ]) # CIFTI output if cifti_output: ds_bold_cifti = pe.Node(DerivativesDataSink( base_directory=output_dir, suffix='bold', compress=False, dismiss_entities=("echo",)), name='ds_bold_cifti', run_without_submitting=True, mem_gb=DEFAULT_MEMORY_MIN_GB) workflow.connect([ (inputnode, ds_bold_cifti, [(('bold_cifti', _unlist), 'in_file'), ('source_file', 'source_file'), (('cifti_metadata', _get_surface), 'space'), ('cifti_density', 'density'), (('cifti_metadata', _read_json), 'meta_dict')]) ]) return workflow
def init_bold_std_trans_wf(freesurfer, mem_gb, omp_nthreads, standard_spaces, name='bold_std_trans_wf', use_compression=True, use_fieldwarp=False): """ This workflow samples functional images into standard space with a single resampling of the original BOLD series. .. workflow:: :graph2use: colored :simple_form: yes from collections import OrderedDict from fmriprep.workflows.bold import init_bold_std_trans_wf wf = init_bold_std_trans_wf( freesurfer=True, mem_gb=3, omp_nthreads=1, standard_spaces=OrderedDict([('MNI152Lin', {}), ('fsaverage', {'density': '10k'})]), ) **Parameters** freesurfer : bool Whether to generate FreeSurfer's aseg/aparc segmentations on BOLD space. mem_gb : float Size of BOLD file in GB omp_nthreads : int Maximum number of threads an individual process may use standard_spaces : OrderedDict Ordered dictionary where keys are TemplateFlow ID strings (e.g., ``MNI152Lin``, ``MNI152NLin6Asym``, ``MNI152NLin2009cAsym``, or ``fsLR``), or paths pointing to custom templates organized in a TemplateFlow-like structure. Values of the dictionary aggregate modifiers (e.g., the value for the key ``MNI152Lin`` could be ``{'resolution': 2}`` if one wants the resampling to be done on the 2mm resolution version of the selected template). name : str Name of workflow (default: ``bold_std_trans_wf``) use_compression : bool Save registered BOLD series as ``.nii.gz`` use_fieldwarp : bool Include SDC warp in single-shot transform from BOLD to MNI **Inputs** anat2std_xfm List of anatomical-to-standard space transforms generated during spatial normalization. bold_aparc FreeSurfer's ``aparc+aseg.mgz`` atlas projected into the T1w reference (only if ``recon-all`` was run). bold_aseg FreeSurfer's ``aseg.mgz`` atlas projected into the T1w reference (only if ``recon-all`` was run). bold_mask Skull-stripping mask of reference image bold_split Individual 3D volumes, not motion corrected fieldwarp a :abbr:`DFM (displacements field map)` in ITK format hmc_xforms List of affine transforms aligning each volume to ``ref_image`` in ITK format itk_bold_to_t1 Affine transform from ``ref_bold_brain`` to T1 space (ITK format) name_source BOLD series NIfTI file Used to recover original information lost during processing templates List of templates that were applied as targets during spatial normalization. **Outputs** - Two outputnodes are available. One output node (with name ``poutputnode``) will be parameterized in a Nipype sense (see `Nipype iterables <https://miykael.github.io/nipype_tutorial/notebooks/basic_iteration.html>`__), and a second node (``outputnode``) will collapse the parameterized outputs into synchronous lists of the following fields: bold_std BOLD series, resampled to template space bold_std_ref Reference, contrast-enhanced summary of the BOLD series, resampled to template space bold_mask_std BOLD series mask in template space bold_aseg_std FreeSurfer's ``aseg.mgz`` atlas, in template space at the BOLD resolution (only if ``recon-all`` was run) bold_aparc_std FreeSurfer's ``aparc+aseg.mgz`` atlas, in template space at the BOLD resolution (only if ``recon-all`` was run) templates Template identifiers synchronized correspondingly to previously described outputs. """ # Filter ``standard_spaces`` vol_std_spaces = [ k for k in standard_spaces.keys() if not k.startswith('fs') ] workflow = Workflow(name=name) if len(vol_std_spaces) == 1: workflow.__desc__ = """\ The BOLD time-series were resampled into standard space, generating a *preprocessed BOLD run in {tpl} space*. """.format(tpl=vol_std_spaces) else: workflow.__desc__ = """\ The BOLD time-series were resampled into several standard spaces, correspondingly generating the following *spatially-normalized, preprocessed BOLD runs*: {tpl}. """.format(tpl=', '.join(vol_std_spaces)) inputnode = pe.Node(niu.IdentityInterface(fields=[ 'anat2std_xfm', 'bold_aparc', 'bold_aseg', 'bold_mask', 'bold_split', 'fieldwarp', 'hmc_xforms', 'itk_bold_to_t1', 'name_source', 'templates', ]), name='inputnode') select_std = pe.Node(KeySelect(fields=['resolution', 'anat2std_xfm']), name='select_std', run_without_submitting=True) select_std.inputs.resolution = [ v.get('resolution') or v.get('res') or 'native' for k, v in list(standard_spaces.items()) if k in vol_std_spaces ] select_std.iterables = ('key', vol_std_spaces) select_tpl = pe.Node(niu.Function(function=_select_template), name='select_tpl', run_without_submitting=True) select_tpl.inputs.template_specs = standard_spaces gen_ref = pe.Node(GenerateSamplingReference(), name='gen_ref', mem_gb=0.3) # 256x256x256 * 64 / 8 ~ 150MB) mask_std_tfm = pe.Node(ApplyTransforms(interpolation='MultiLabel', float=True), name='mask_std_tfm', mem_gb=1) # Write corrected file in the designated output dir mask_merge_tfms = pe.Node(niu.Merge(2), name='mask_merge_tfms', run_without_submitting=True, mem_gb=DEFAULT_MEMORY_MIN_GB) workflow.connect([ (inputnode, select_std, [('templates', 'keys'), ('anat2std_xfm', 'anat2std_xfm')]), (inputnode, mask_std_tfm, [('bold_mask', 'input_image')]), (inputnode, gen_ref, [(('bold_split', _first), 'moving_image')]), (inputnode, mask_merge_tfms, [(('itk_bold_to_t1', _aslist), 'in2')]), (select_std, select_tpl, [('key', 'template')]), (select_std, mask_merge_tfms, [('anat2std_xfm', 'in1')]), (select_std, gen_ref, [(('resolution', _is_native), 'keep_native')]), (select_tpl, gen_ref, [('out', 'fixed_image')]), (mask_merge_tfms, mask_std_tfm, [('out', 'transforms')]), (gen_ref, mask_std_tfm, [('out_file', 'reference_image')]), ]) nxforms = 4 if use_fieldwarp else 3 merge_xforms = pe.Node(niu.Merge(nxforms), name='merge_xforms', run_without_submitting=True, mem_gb=DEFAULT_MEMORY_MIN_GB) workflow.connect([(inputnode, merge_xforms, [('hmc_xforms', 'in%d' % nxforms)])]) if use_fieldwarp: workflow.connect([(inputnode, merge_xforms, [('fieldwarp', 'in3')])]) bold_to_std_transform = pe.Node(MultiApplyTransforms( interpolation="LanczosWindowedSinc", float=True, copy_dtype=True), name='bold_to_std_transform', mem_gb=mem_gb * 3 * omp_nthreads, n_procs=omp_nthreads) merge = pe.Node(Merge(compress=use_compression), name='merge', mem_gb=mem_gb * 3) # Generate a reference on the target T1w space gen_final_ref = init_bold_reference_wf(omp_nthreads=omp_nthreads, pre_mask=True) workflow.connect([ (inputnode, merge_xforms, [(('itk_bold_to_t1', _aslist), 'in2')]), (inputnode, merge, [('name_source', 'header_source')]), (inputnode, bold_to_std_transform, [('bold_split', 'input_image')]), (select_std, merge_xforms, [('anat2std_xfm', 'in1')]), (merge_xforms, bold_to_std_transform, [('out', 'transforms')]), (gen_ref, bold_to_std_transform, [('out_file', 'reference_image')]), (bold_to_std_transform, merge, [('out_files', 'in_files')]), (merge, gen_final_ref, [('out_file', 'inputnode.bold_file')]), (mask_std_tfm, gen_final_ref, [('output_image', 'inputnode.bold_mask') ]), ]) # Connect output nodes output_names = ['bold_std', 'bold_std_ref', 'bold_mask_std', 'templates'] if freesurfer: output_names += ['bold_aseg_std', 'bold_aparc_std'] # poutputnode - parametric output node poutputnode = pe.Node(niu.IdentityInterface(fields=output_names), name='poutputnode') workflow.connect([ (gen_final_ref, poutputnode, [('outputnode.ref_image', 'bold_std_ref') ]), (merge, poutputnode, [('out_file', 'bold_std')]), (mask_std_tfm, poutputnode, [('output_image', 'bold_mask_std')]), (select_std, poutputnode, [('key', 'templates')]), ]) if freesurfer: # Sample the parcellation files to functional space aseg_std_tfm = pe.Node(ApplyTransforms(interpolation='MultiLabel', float=True), name='aseg_std_tfm', mem_gb=1) aparc_std_tfm = pe.Node(ApplyTransforms(interpolation='MultiLabel', float=True), name='aparc_std_tfm', mem_gb=1) workflow.connect([ (inputnode, aseg_std_tfm, [('bold_aseg', 'input_image')]), (inputnode, aparc_std_tfm, [('bold_aparc', 'input_image')]), (select_std, aseg_std_tfm, [('anat2std_xfm', 'transforms')]), (select_std, aparc_std_tfm, [('anat2std_xfm', 'transforms')]), (gen_ref, aseg_std_tfm, [('out_file', 'reference_image')]), (gen_ref, aparc_std_tfm, [('out_file', 'reference_image')]), (aseg_std_tfm, poutputnode, [('output_image', 'bold_aseg_std')]), (aparc_std_tfm, poutputnode, [('output_image', 'bold_aparc_std')]), ]) # Connect outputnode to the parameterized outputnode outputnode = pe.JoinNode(niu.IdentityInterface(fields=output_names), name='outputnode', joinsource='select_std') workflow.connect([(poutputnode, outputnode, [(f, f) for f in output_names])]) return workflow
def init_anat_report_wf(workdir=None, name="anat_report_wf", memcalc=MemoryCalculator.default()): workflow = pe.Workflow(name=name) fmriprepreports = ["t1w_dseg_mask", "std_t1w"] fmriprepreportdatasinks = [f"ds_{fr}_report" for fr in fmriprepreports] inputnode = pe.Node( niu.IdentityInterface(fields=[ "standardized", "std_mask", "template", "t1w_preproc", "t1w_mask", "t1w_dseg", *fmriprepreportdatasinks, "tags", ]), name="inputnode", ) select_std = pe.Node( KeySelect(fields=["standardized", "std_mask"]), name="select_std", run_without_submitting=True, nohash=True, ) select_std.inputs.key = constants.reference_space workflow.connect(inputnode, "standardized", select_std, "standardized") workflow.connect(inputnode, "std_mask", select_std, "std_mask") workflow.connect(inputnode, "template", select_std, "keys") # make_resultdicts = pe.Node( MakeResultdicts( reportkeys=["skull_strip_report", "t1_norm_rpt", *fmriprepreports ]), name="make_resultdicts", ) workflow.connect(inputnode, "tags", make_resultdicts, "tags") # resultdict_datasink = pe.Node(ResultdictDatasink(base_directory=workdir), name="resultdict_datasink") workflow.connect(make_resultdicts, "resultdicts", resultdict_datasink, "indicts") # for fr, frd in zip(fmriprepreports, fmriprepreportdatasinks): workflow.connect(inputnode, frd, make_resultdicts, fr) # T1w segmentation skull_strip_report = pe.Node(SimpleShowMaskRPT(), name="skull_strip_report") workflow.connect(inputnode, "t1w_preproc", skull_strip_report, "background_file") workflow.connect(inputnode, "t1w_mask", skull_strip_report, "mask_file") workflow.connect(skull_strip_report, "out_report", make_resultdicts, "skull_strip_report") # T1 -> mni spaces = config.workflow.spaces assert isinstance(spaces, SpatialReferences) t1_norm_rpt = pe.Node( PlotRegistration(template=spaces.get_spaces()[0]), name="t1_norm_rpt", mem_gb=memcalc.min_gb, ) workflow.connect(select_std, "standardized", t1_norm_rpt, "in_file") workflow.connect(select_std, "std_mask", t1_norm_rpt, "mask_file") workflow.connect(t1_norm_rpt, "out_report", make_resultdicts, "t1_norm_rpt") return workflow