def init_surface_recon_wf(omp_nthreads, hires, name='surface_recon_wf'): r""" Reconstruct anatomical surfaces using FreeSurfer's ``recon-all``. Reconstruction is performed in three phases. The first phase initializes the subject with T1w and T2w (if available) structural images and performs basic reconstruction (``autorecon1``) with the exception of skull-stripping. For example, a subject with only one session with T1w and T2w images would be processed by the following command:: $ recon-all -sd <output dir>/freesurfer -subjid sub-<subject_label> \ -i <bids-root>/sub-<subject_label>/anat/sub-<subject_label>_T1w.nii.gz \ -T2 <bids-root>/sub-<subject_label>/anat/sub-<subject_label>_T2w.nii.gz \ -autorecon1 \ -noskullstrip The second phase imports an externally computed skull-stripping mask. This workflow refines the external brainmask using the internal mask implicit the the FreeSurfer's ``aseg.mgz`` segmentation, to reconcile ANTs' and FreeSurfer's brain masks. First, the ``aseg.mgz`` mask from FreeSurfer is refined in two steps, using binary morphological operations: 1. With a binary closing operation the sulci are included into the mask. This results in a smoother brain mask that does not exclude deep, wide sulci. 2. Fill any holes (typically, there could be a hole next to the pineal gland and the corpora quadrigemina if the great cerebral brain is segmented out). Second, the brain mask is grown, including pixels that have a high likelihood to the GM tissue distribution: 3. Dilate and substract the brain mask, defining the region to search for candidate pixels that likely belong to cortical GM. 4. Pixels found in the search region that are labeled as GM by ANTs (during ``antsBrainExtraction.sh``) are directly added to the new mask. 5. Otherwise, estimate GM tissue parameters locally in patches of ``ww`` size, and test the likelihood of the pixel to belong in the GM distribution. This procedure is inspired on mindboggle's solution to the problem: https://github.com/nipy/mindboggle/blob/7f91faaa7664d820fe12ccc52ebaf21d679795e2/mindboggle/guts/segment.py#L1660 The final phase resumes reconstruction, using the T2w image to assist in finding the pial surface, if available. See :py:func:`~smriprep.workflows.surfaces.init_autorecon_resume_wf` for details. Memory annotations for FreeSurfer are based off `their documentation <https://surfer.nmr.mgh.harvard.edu/fswiki/SystemRequirements>`_. They specify an allocation of 4GB per subject. Here we define 5GB to have a certain margin. .. workflow:: :graph2use: orig :simple_form: yes from smriprep.workflows.surfaces import init_surface_recon_wf wf = init_surface_recon_wf(omp_nthreads=1, hires=True) **Parameters** omp_nthreads : int Maximum number of threads an individual process may use hires : bool Enable sub-millimeter preprocessing in FreeSurfer **Inputs** t1w List of T1-weighted structural images t2w List of T2-weighted structural images (only first used) flair List of FLAIR images skullstripped_t1 Skull-stripped T1-weighted image (or mask of image) ants_segs Brain tissue segmentation from ANTS ``antsBrainExtraction.sh`` corrected_t1 INU-corrected, merged T1-weighted image subjects_dir FreeSurfer SUBJECTS_DIR subject_id FreeSurfer subject ID **Outputs** subjects_dir FreeSurfer SUBJECTS_DIR subject_id FreeSurfer subject ID t1w2fsnative_xfm LTA-style affine matrix translating from T1w to FreeSurfer-conformed subject space fsnative2t1w_xfm LTA-style affine matrix translating from FreeSurfer-conformed subject space to T1w surfaces GIFTI surfaces for gray/white matter boundary, pial surface, midthickness (or graymid) surface, and inflated surfaces out_brainmask Refined brainmask, derived from FreeSurfer's ``aseg`` volume out_aseg FreeSurfer's aseg segmentation, in native T1w space out_aparc FreeSurfer's aparc+aseg segmentation, in native T1w space **Subworkflows** * :py:func:`~smriprep.workflows.surfaces.init_autorecon_resume_wf` * :py:func:`~smriprep.workflows.surfaces.init_gifti_surface_wf` """ workflow = Workflow(name=name) workflow.__desc__ = """\ Brain surfaces were reconstructed using `recon-all` [FreeSurfer {fs_ver}, RRID:SCR_001847, @fs_reconall], and the brain mask estimated previously was refined with a custom variation of the method to reconcile ANTs-derived and FreeSurfer-derived segmentations of the cortical gray-matter of Mindboggle [RRID:SCR_002438, @mindboggle]. """.format(fs_ver=fs.Info().looseversion() or '<ver>') inputnode = pe.Node(niu.IdentityInterface(fields=[ 't1w', 't2w', 'flair', 'skullstripped_t1', 'corrected_t1', 'ants_segs', 'subjects_dir', 'subject_id' ]), name='inputnode') outputnode = pe.Node(niu.IdentityInterface(fields=[ 'subjects_dir', 'subject_id', 't1w2fsnative_xfm', 'fsnative2t1w_xfm', 'surfaces', 'out_brainmask', 'out_aseg', 'out_aparc' ]), name='outputnode') recon_config = pe.Node(FSDetectInputs(hires_enabled=hires), name='recon_config') fov_check = pe.Node(niu.Function(function=_check_cw256), name='fov_check') autorecon1 = pe.Node(fs.ReconAll(directive='autorecon1', openmp=omp_nthreads), name='autorecon1', n_procs=omp_nthreads, mem_gb=5) autorecon1.interface._can_resume = False autorecon1.interface._always_run = True skull_strip_extern = pe.Node(FSInjectBrainExtracted(), name='skull_strip_extern') fsnative2t1w_xfm = pe.Node(RobustRegister(auto_sens=True, est_int_scale=True), name='fsnative2t1w_xfm') t1w2fsnative_xfm = pe.Node(LTAConvert(out_lta=True, invert=True), name='t1w2fsnative_xfm') autorecon_resume_wf = init_autorecon_resume_wf(omp_nthreads=omp_nthreads) gifti_surface_wf = init_gifti_surface_wf() aseg_to_native_wf = init_segs_to_native_wf() aparc_to_native_wf = init_segs_to_native_wf(segmentation='aparc_aseg') refine = pe.Node(RefineBrainMask(), name='refine') workflow.connect([ # Configuration (inputnode, recon_config, [('t1w', 't1w_list'), ('t2w', 't2w_list'), ('flair', 'flair_list')]), # Passing subjects_dir / subject_id enforces serial order (inputnode, autorecon1, [('subjects_dir', 'subjects_dir'), ('subject_id', 'subject_id')]), (autorecon1, skull_strip_extern, [('subjects_dir', 'subjects_dir'), ('subject_id', 'subject_id')]), (skull_strip_extern, autorecon_resume_wf, [('subjects_dir', 'inputnode.subjects_dir'), ('subject_id', 'inputnode.subject_id')]), (autorecon_resume_wf, gifti_surface_wf, [('outputnode.subjects_dir', 'inputnode.subjects_dir'), ('outputnode.subject_id', 'inputnode.subject_id')]), # Reconstruction phases (inputnode, autorecon1, [('t1w', 'T1_files')]), (inputnode, fov_check, [('t1w', 'in_files')]), (fov_check, autorecon1, [('out', 'flags')]), ( recon_config, autorecon1, [ ('t2w', 'T2_file'), ('flair', 'FLAIR_file'), ('hires', 'hires'), # First run only (recon-all saves expert options) ('mris_inflate', 'mris_inflate') ]), (inputnode, skull_strip_extern, [('skullstripped_t1', 'in_brain')]), (recon_config, autorecon_resume_wf, [('use_t2w', 'inputnode.use_T2'), ('use_flair', 'inputnode.use_FLAIR')]), # Construct transform from FreeSurfer conformed image to sMRIPrep # reoriented image (inputnode, fsnative2t1w_xfm, [('t1w', 'target_file')]), (autorecon1, fsnative2t1w_xfm, [('T1', 'source_file')]), (fsnative2t1w_xfm, gifti_surface_wf, [('out_reg_file', 'inputnode.fsnative2t1w_xfm')]), (fsnative2t1w_xfm, t1w2fsnative_xfm, [('out_reg_file', 'in_lta')]), # Refine ANTs mask, deriving new mask from FS' aseg (inputnode, refine, [('corrected_t1', 'in_anat'), ('ants_segs', 'in_ants')]), (inputnode, aseg_to_native_wf, [('corrected_t1', 'inputnode.in_file') ]), (autorecon_resume_wf, aseg_to_native_wf, [('outputnode.subjects_dir', 'inputnode.subjects_dir'), ('outputnode.subject_id', 'inputnode.subject_id')]), (inputnode, aparc_to_native_wf, [('corrected_t1', 'inputnode.in_file') ]), (autorecon_resume_wf, aparc_to_native_wf, [('outputnode.subjects_dir', 'inputnode.subjects_dir'), ('outputnode.subject_id', 'inputnode.subject_id')]), (aseg_to_native_wf, refine, [('outputnode.out_file', 'in_aseg')]), # Output (autorecon_resume_wf, outputnode, [('outputnode.subjects_dir', 'subjects_dir'), ('outputnode.subject_id', 'subject_id')]), (gifti_surface_wf, outputnode, [('outputnode.surfaces', 'surfaces')]), (t1w2fsnative_xfm, outputnode, [('out_lta', 't1w2fsnative_xfm')]), (fsnative2t1w_xfm, outputnode, [('out_reg_file', 'fsnative2t1w_xfm')]), (refine, outputnode, [('out_file', 'out_brainmask')]), (aseg_to_native_wf, outputnode, [('outputnode.out_file', 'out_aseg')]), (aparc_to_native_wf, outputnode, [('outputnode.out_file', 'out_aparc') ]), ]) return workflow
def init_bold_surf_wf(mem_gb, output_spaces, medial_surface_nan, name='bold_surf_wf'): """ This workflow samples functional images to FreeSurfer surfaces For each vertex, the cortical ribbon is sampled at six points (spaced 20% of thickness apart) and averaged. Outputs are in GIFTI format. .. workflow:: :graph2use: colored :simple_form: yes from fmriprep.workflows.bold import init_bold_surf_wf wf = init_bold_surf_wf(mem_gb=0.1, output_spaces=['T1w', 'fsnative', 'template', 'fsaverage5'], medial_surface_nan=False) **Parameters** output_spaces : list List of output spaces functional images are to be resampled to Target spaces beginning with ``fs`` will be selected for resampling, such as ``fsaverage`` or related template spaces If the list contains ``fsnative``, images will be resampled to the individual subject's native surface medial_surface_nan : bool Replace medial wall values with NaNs on functional GIFTI files **Inputs** source_file Motion-corrected BOLD series in T1 space t1_preproc Bias-corrected structural template image subjects_dir FreeSurfer SUBJECTS_DIR subject_id FreeSurfer subject ID t1_2_fsnative_forward_transform LTA-style affine matrix translating from T1w to FreeSurfer-conformed subject space **Outputs** surfaces BOLD series, resampled to FreeSurfer surfaces """ # Ensure volumetric spaces do not sneak into this workflow spaces = [space for space in output_spaces if space.startswith('fs')] workflow = Workflow(name=name) if spaces: workflow.__desc__ = """\ The BOLD time-series, were resampled to surfaces on the following spaces: {out_spaces}. """.format(out_spaces=', '.join(['*%s*' % s for s in spaces])) inputnode = pe.Node(niu.IdentityInterface(fields=[ 'source_file', 't1_preproc', 'subject_id', 'subjects_dir', 't1_2_fsnative_forward_transform' ]), name='inputnode') outputnode = pe.Node(niu.IdentityInterface(fields=['surfaces']), name='outputnode') def select_target(subject_id, space): """ Given a source subject ID and a target space, get the target subject ID """ return subject_id if space == 'fsnative' else space targets = pe.MapNode(niu.Function(function=select_target), iterfield=['space'], name='targets', mem_gb=DEFAULT_MEMORY_MIN_GB) targets.inputs.space = spaces # Rename the source file to the output space to simplify naming later rename_src = pe.MapNode(niu.Rename(format_string='%(subject)s', keep_ext=True), iterfield='subject', name='rename_src', run_without_submitting=True, mem_gb=DEFAULT_MEMORY_MIN_GB) rename_src.inputs.subject = spaces resampling_xfm = pe.Node(LTAConvert(in_lta='identity.nofile', out_lta=True), name='resampling_xfm') set_xfm_source = pe.Node(ConcatenateLTA(out_type='RAS2RAS'), name='set_xfm_source') sampler = pe.MapNode(fs.SampleToSurface(sampling_method='average', sampling_range=(0, 1, 0.2), sampling_units='frac', interp_method='trilinear', cortex_mask=True, override_reg_subj=True, out_type='gii'), iterfield=['source_file', 'target_subject'], iterables=('hemi', ['lh', 'rh']), name='sampler', mem_gb=mem_gb * 3) medial_nans = pe.MapNode(MedialNaNs(), iterfield=['in_file', 'target_subject'], name='medial_nans', mem_gb=DEFAULT_MEMORY_MIN_GB) merger = pe.JoinNode(niu.Merge(1, ravel_inputs=True), name='merger', joinsource='sampler', joinfield=['in1'], run_without_submitting=True, mem_gb=DEFAULT_MEMORY_MIN_GB) update_metadata = pe.MapNode(GiftiSetAnatomicalStructure(), iterfield='in_file', name='update_metadata', mem_gb=DEFAULT_MEMORY_MIN_GB) workflow.connect([ (inputnode, targets, [('subject_id', 'subject_id')]), (inputnode, rename_src, [('source_file', 'in_file')]), (inputnode, resampling_xfm, [('source_file', 'source_file'), ('t1_preproc', 'target_file')]), (inputnode, set_xfm_source, [('t1_2_fsnative_forward_transform', 'in_lta2')]), (resampling_xfm, set_xfm_source, [('out_lta', 'in_lta1')]), (inputnode, sampler, [('subjects_dir', 'subjects_dir'), ('subject_id', 'subject_id')]), (set_xfm_source, sampler, [('out_file', 'reg_file')]), (targets, sampler, [('out', 'target_subject')]), (rename_src, sampler, [('out_file', 'source_file')]), (merger, update_metadata, [('out', 'in_file')]), (update_metadata, outputnode, [('out_file', 'surfaces')]), ]) if medial_surface_nan: workflow.connect([ (inputnode, medial_nans, [('subjects_dir', 'subjects_dir')]), (sampler, medial_nans, [('out_file', 'in_file')]), (targets, medial_nans, [('out', 'target_subject')]), (medial_nans, merger, [('out_file', 'in1')]), ]) else: workflow.connect(sampler, 'out_file', merger, 'in1') return workflow
def init_bbreg_wf(use_bbr, bold2t1w_dof, bold2t1w_init, omp_nthreads, name='bbreg_wf'): """ Build a workflow to run FreeSurfer's ``bbregister``. This workflow uses FreeSurfer's ``bbregister`` to register a BOLD image to a T1-weighted structural image. It is a counterpart to :py:func:`~fmriprep.workflows.bold.registration.init_fsl_bbr_wf`, which performs the same task using FSL's FLIRT with a BBR cost function. The ``use_bbr`` option permits a high degree of control over registration. If ``False``, standard, affine coregistration will be performed using FreeSurfer's ``mri_coreg`` tool. If ``True``, ``bbregister`` will be seeded with the initial transform found by ``mri_coreg`` (equivalent to running ``bbregister --init-coreg``). If ``None``, after ``bbregister`` is run, the resulting affine transform will be compared to the initial transform found by ``mri_coreg``. Excessive deviation will result in rejecting the BBR refinement and accepting the original, affine registration. Workflow Graph .. workflow :: :graph2use: orig :simple_form: yes from fmriprep.workflows.bold.registration import init_bbreg_wf wf = init_bbreg_wf(use_bbr=True, bold2t1w_dof=9, bold2t1w_init='register', omp_nthreads=1) Parameters ---------- use_bbr : :obj:`bool` or None Enable/disable boundary-based registration refinement. If ``None``, test BBR result for distortion before accepting. bold2t1w_dof : 6, 9 or 12 Degrees-of-freedom for BOLD-T1w registration bold2t1w_init : str, 'header' or 'register' If ``'header'``, use header information for initialization of BOLD and T1 images. If ``'register'``, align volumes by their centers. name : :obj:`str`, optional Workflow name (default: bbreg_wf) Inputs ------ in_file Reference BOLD image to be registered fsnative2t1w_xfm FSL-style affine matrix translating from FreeSurfer T1.mgz to T1w subjects_dir FreeSurfer SUBJECTS_DIR subject_id FreeSurfer subject ID (must have folder in SUBJECTS_DIR) t1w_brain Unused (see :py:func:`~fmriprep.workflows.bold.registration.init_fsl_bbr_wf`) t1w_dseg Unused (see :py:func:`~fmriprep.workflows.bold.registration.init_fsl_bbr_wf`) Outputs ------- itk_bold_to_t1 Affine transform from ``ref_bold_brain`` to T1 space (ITK format) itk_t1_to_bold Affine transform from T1 space to BOLD space (ITK format) out_report Reportlet for assessing registration quality fallback Boolean indicating whether BBR was rejected (mri_coreg registration returned) """ from niworkflows.engine.workflows import LiterateWorkflow as Workflow # See https://github.com/nipreps/fmriprep/issues/768 from niworkflows.interfaces.freesurfer import ( PatchedBBRegisterRPT as BBRegisterRPT, PatchedMRICoregRPT as MRICoregRPT, PatchedLTAConvert as LTAConvert) from niworkflows.interfaces.nitransforms import ConcatenateXFMs workflow = Workflow(name=name) workflow.__desc__ = """\ The BOLD reference was then co-registered to the T1w reference using `bbregister` (FreeSurfer) which implements boundary-based registration [@bbr]. Co-registration was configured with {dof} degrees of freedom{reason}. """.format(dof={ 6: 'six', 9: 'nine', 12: 'twelve' }[bold2t1w_dof], reason='' if bold2t1w_dof == 6 else 'to account for distortions remaining in the BOLD reference') inputnode = pe.Node( niu.IdentityInterface([ 'in_file', 'fsnative2t1w_xfm', 'subjects_dir', 'subject_id', # BBRegister 't1w_dseg', 't1w_brain' ]), # FLIRT BBR name='inputnode') outputnode = pe.Node(niu.IdentityInterface( ['itk_bold_to_t1', 'itk_t1_to_bold', 'out_report', 'fallback']), name='outputnode') if bold2t1w_init not in ("register", "header"): raise ValueError( f"Unknown BOLD-T1w initialization option: {bold2t1w_init}") # For now make BBR unconditional - in the future, we can fall back to identity, # but adding the flexibility without testing seems a bit dangerous if bold2t1w_init == "header": if use_bbr is False: raise ValueError("Cannot disable BBR and use header registration") if use_bbr is None: LOGGER.warning( "Initializing BBR with header; affine fallback disabled") use_bbr = True merge_ltas = pe.Node(niu.Merge(2), name='merge_ltas', run_without_submitting=True) concat_xfm = pe.Node(ConcatenateXFMs(inverse=True), name='concat_xfm') workflow.connect([ # Output ITK transforms (inputnode, merge_ltas, [('fsnative2t1w_xfm', 'in2')]), (merge_ltas, concat_xfm, [('out', 'in_xfms')]), (concat_xfm, outputnode, [('out_xfm', 'itk_bold_to_t1')]), (concat_xfm, outputnode, [('out_inv', 'itk_t1_to_bold')]), ]) # Define both nodes, but only connect conditionally mri_coreg = pe.Node(MRICoregRPT(dof=bold2t1w_dof, sep=[4], ftol=0.0001, linmintol=0.01, generate_report=not use_bbr), name='mri_coreg', n_procs=omp_nthreads, mem_gb=5) bbregister = pe.Node(BBRegisterRPT(dof=bold2t1w_dof, contrast_type='t2', registered_file=True, out_lta_file=True, generate_report=True), name='bbregister', mem_gb=12) # Use mri_coreg if bold2t1w_init == "register": workflow.connect([ (inputnode, mri_coreg, [('subjects_dir', 'subjects_dir'), ('subject_id', 'subject_id'), ('in_file', 'source_file')]), ]) # Short-circuit workflow building, use initial registration if use_bbr is False: workflow.connect([ (mri_coreg, outputnode, [('out_report', 'out_report')]), (mri_coreg, merge_ltas, [('out_lta_file', 'in1')]) ]) outputnode.inputs.fallback = True return workflow # Use bbregister workflow.connect([ (inputnode, bbregister, [('subjects_dir', 'subjects_dir'), ('subject_id', 'subject_id'), ('in_file', 'source_file')]), ]) if bold2t1w_init == "header": bbregister.inputs.init = "header" else: workflow.connect([(mri_coreg, bbregister, [('out_lta_file', 'init_reg_file')])]) # Short-circuit workflow building, use boundary-based registration if use_bbr is True: workflow.connect([(bbregister, outputnode, [('out_report', 'out_report')]), (bbregister, merge_ltas, [('out_lta_file', 'in1')])]) outputnode.inputs.fallback = False return workflow # Only reach this point if bold2t1w_init is "register" and use_bbr is None transforms = pe.Node(niu.Merge(2), run_without_submitting=True, name='transforms') reports = pe.Node(niu.Merge(2), run_without_submitting=True, name='reports') lta_ras2ras = pe.MapNode(LTAConvert(out_lta=True), iterfield=['in_lta'], name='lta_ras2ras', mem_gb=2) compare_transforms = pe.Node(niu.Function(function=compare_xforms), name='compare_transforms') select_transform = pe.Node(niu.Select(), run_without_submitting=True, name='select_transform') select_report = pe.Node(niu.Select(), run_without_submitting=True, name='select_report') workflow.connect([ (bbregister, transforms, [('out_lta_file', 'in1')]), (mri_coreg, transforms, [('out_lta_file', 'in2')]), # Normalize LTA transforms to RAS2RAS (inputs are VOX2VOX) and compare (transforms, lta_ras2ras, [('out', 'in_lta')]), (lta_ras2ras, compare_transforms, [('out_lta', 'lta_list')]), (compare_transforms, outputnode, [('out', 'fallback')]), # Select output transform (transforms, select_transform, [('out', 'inlist')]), (compare_transforms, select_transform, [('out', 'index')]), (select_transform, merge_ltas, [('out', 'in1')]), # Select output report (bbregister, reports, [('out_report', 'in1')]), (mri_coreg, reports, [('out_report', 'in2')]), (reports, select_report, [('out', 'inlist')]), (compare_transforms, select_report, [('out', 'index')]), (select_report, outputnode, [('out', 'out_report')]), ]) return workflow
def init_fsl_bbr_wf(use_bbr, bold2t1w_dof, bold2t1w_init, sloppy=False, name='fsl_bbr_wf'): """ Build a workflow to run FSL's ``flirt``. This workflow uses FSL FLIRT to register a BOLD image to a T1-weighted structural image, using a boundary-based registration (BBR) cost function. It is a counterpart to :py:func:`~fmriprep.workflows.bold.registration.init_bbreg_wf`, which performs the same task using FreeSurfer's ``bbregister``. The ``use_bbr`` option permits a high degree of control over registration. If ``False``, standard, rigid coregistration will be performed by FLIRT. If ``True``, FLIRT-BBR will be seeded with the initial transform found by the rigid coregistration. If ``None``, after FLIRT-BBR is run, the resulting affine transform will be compared to the initial transform found by FLIRT. Excessive deviation will result in rejecting the BBR refinement and accepting the original, affine registration. Workflow Graph .. workflow :: :graph2use: orig :simple_form: yes from fmriprep.workflows.bold.registration import init_fsl_bbr_wf wf = init_fsl_bbr_wf(use_bbr=True, bold2t1w_dof=9, bold2t1w_init='register') Parameters ---------- use_bbr : :obj:`bool` or None Enable/disable boundary-based registration refinement. If ``None``, test BBR result for distortion before accepting. bold2t1w_dof : 6, 9 or 12 Degrees-of-freedom for BOLD-T1w registration bold2t1w_init : str, 'header' or 'register' If ``'header'``, use header information for initialization of BOLD and T1 images. If ``'register'``, align volumes by their centers. name : :obj:`str`, optional Workflow name (default: fsl_bbr_wf) Inputs ------ in_file Reference BOLD image to be registered t1w_brain Skull-stripped T1-weighted structural image t1w_dseg FAST segmentation of ``t1w_brain`` fsnative2t1w_xfm Unused (see :py:func:`~fmriprep.workflows.bold.registration.init_bbreg_wf`) subjects_dir Unused (see :py:func:`~fmriprep.workflows.bold.registration.init_bbreg_wf`) subject_id Unused (see :py:func:`~fmriprep.workflows.bold.registration.init_bbreg_wf`) Outputs ------- itk_bold_to_t1 Affine transform from ``ref_bold_brain`` to T1w space (ITK format) itk_t1_to_bold Affine transform from T1 space to BOLD space (ITK format) out_report Reportlet for assessing registration quality fallback Boolean indicating whether BBR was rejected (rigid FLIRT registration returned) """ from niworkflows.engine.workflows import LiterateWorkflow as Workflow from niworkflows.utils.images import dseg_label as _dseg_label from niworkflows.interfaces.freesurfer import PatchedLTAConvert as LTAConvert from niworkflows.interfaces.registration import FLIRTRPT workflow = Workflow(name=name) workflow.__desc__ = """\ The BOLD reference was then co-registered to the T1w reference using `flirt` [FSL {fsl_ver}, @flirt] with the boundary-based registration [@bbr] cost-function. Co-registration was configured with nine degrees of freedom to account for distortions remaining in the BOLD reference. """.format(fsl_ver=FLIRTRPT().version or '<ver>') inputnode = pe.Node( niu.IdentityInterface([ 'in_file', 'fsnative2t1w_xfm', 'subjects_dir', 'subject_id', # BBRegister 't1w_dseg', 't1w_brain' ]), # FLIRT BBR name='inputnode') outputnode = pe.Node(niu.IdentityInterface( ['itk_bold_to_t1', 'itk_t1_to_bold', 'out_report', 'fallback']), name='outputnode') wm_mask = pe.Node(niu.Function(function=_dseg_label), name='wm_mask') wm_mask.inputs.label = 2 # BIDS default is WM=2 flt_bbr_init = pe.Node(FLIRTRPT(dof=6, generate_report=not use_bbr, uses_qform=True), name='flt_bbr_init') if bold2t1w_init not in ("register", "header"): raise ValueError( f"Unknown BOLD-T1w initialization option: {bold2t1w_init}") if bold2t1w_init == "header": raise NotImplementedError( "Header-based registration initialization not supported for FSL") invt_bbr = pe.Node(fsl.ConvertXFM(invert_xfm=True), name='invt_bbr', mem_gb=DEFAULT_MEMORY_MIN_GB) # BOLD to T1 transform matrix is from fsl, using c3 tools to convert to # something ANTs will like. fsl2itk_fwd = pe.Node(c3.C3dAffineTool(fsl2ras=True, itk_transform=True), name='fsl2itk_fwd', mem_gb=DEFAULT_MEMORY_MIN_GB) fsl2itk_inv = pe.Node(c3.C3dAffineTool(fsl2ras=True, itk_transform=True), name='fsl2itk_inv', mem_gb=DEFAULT_MEMORY_MIN_GB) workflow.connect([ (inputnode, flt_bbr_init, [('in_file', 'in_file'), ('t1w_brain', 'reference')]), (inputnode, fsl2itk_fwd, [('t1w_brain', 'reference_file'), ('in_file', 'source_file')]), (inputnode, fsl2itk_inv, [('in_file', 'reference_file'), ('t1w_brain', 'source_file')]), (invt_bbr, fsl2itk_inv, [('out_file', 'transform_file')]), (fsl2itk_fwd, outputnode, [('itk_transform', 'itk_bold_to_t1')]), (fsl2itk_inv, outputnode, [('itk_transform', 'itk_t1_to_bold')]), ]) # Short-circuit workflow building, use rigid registration if use_bbr is False: workflow.connect([ (flt_bbr_init, invt_bbr, [('out_matrix_file', 'in_file')]), (flt_bbr_init, fsl2itk_fwd, [('out_matrix_file', 'transform_file') ]), (flt_bbr_init, outputnode, [('out_report', 'out_report')]), ]) outputnode.inputs.fallback = True return workflow flt_bbr = pe.Node(FLIRTRPT(cost_func='bbr', dof=bold2t1w_dof, generate_report=True), name='flt_bbr') FSLDIR = os.getenv('FSLDIR') if FSLDIR: flt_bbr.inputs.schedule = op.join(FSLDIR, 'etc/flirtsch/bbr.sch') else: # Should mostly be hit while building docs LOGGER.warning("FSLDIR unset - using packaged BBR schedule") flt_bbr.inputs.schedule = pkgr.resource_filename( 'fmriprep', 'data/flirtsch/bbr.sch') workflow.connect([ (inputnode, wm_mask, [('t1w_dseg', 'in_seg')]), (inputnode, flt_bbr, [('in_file', 'in_file')]), (flt_bbr_init, flt_bbr, [('out_matrix_file', 'in_matrix_file')]), ]) if sloppy is True: downsample = pe.Node(niu.Function( function=_conditional_downsampling, output_names=["out_file", "out_mask"]), name='downsample') workflow.connect([ (inputnode, downsample, [("t1w_brain", "in_file")]), (wm_mask, downsample, [("out", "in_mask")]), (downsample, flt_bbr, [('out_file', 'reference'), ('out_mask', 'wm_seg')]), ]) else: workflow.connect([ (inputnode, flt_bbr, [('t1w_brain', 'reference')]), (wm_mask, flt_bbr, [('out', 'wm_seg')]), ]) # Short-circuit workflow building, use boundary-based registration if use_bbr is True: workflow.connect([ (flt_bbr, invt_bbr, [('out_matrix_file', 'in_file')]), (flt_bbr, fsl2itk_fwd, [('out_matrix_file', 'transform_file')]), (flt_bbr, outputnode, [('out_report', 'out_report')]), ]) outputnode.inputs.fallback = False return workflow transforms = pe.Node(niu.Merge(2), run_without_submitting=True, name='transforms') reports = pe.Node(niu.Merge(2), run_without_submitting=True, name='reports') compare_transforms = pe.Node(niu.Function(function=compare_xforms), name='compare_transforms') select_transform = pe.Node(niu.Select(), run_without_submitting=True, name='select_transform') select_report = pe.Node(niu.Select(), run_without_submitting=True, name='select_report') fsl_to_lta = pe.MapNode(LTAConvert(out_lta=True), iterfield=['in_fsl'], name='fsl_to_lta') workflow.connect([ (flt_bbr, transforms, [('out_matrix_file', 'in1')]), (flt_bbr_init, transforms, [('out_matrix_file', 'in2')]), # Convert FSL transforms to LTA (RAS2RAS) transforms and compare (inputnode, fsl_to_lta, [('in_file', 'source_file'), ('t1w_brain', 'target_file')]), (transforms, fsl_to_lta, [('out', 'in_fsl')]), (fsl_to_lta, compare_transforms, [('out_lta', 'lta_list')]), (compare_transforms, outputnode, [('out', 'fallback')]), # Select output transform (transforms, select_transform, [('out', 'inlist')]), (compare_transforms, select_transform, [('out', 'index')]), (select_transform, invt_bbr, [('out', 'in_file')]), (select_transform, fsl2itk_fwd, [('out', 'transform_file')]), (flt_bbr, reports, [('out_report', 'in1')]), (flt_bbr_init, reports, [('out_report', 'in2')]), (reports, select_report, [('out', 'inlist')]), (compare_transforms, select_report, [('out', 'index')]), (select_report, outputnode, [('out', 'out_report')]), ]) return workflow
def init_anat_template_wf(longitudinal, omp_nthreads, num_t1w, name='anat_template_wf'): """ Generate a canonically-oriented, structural average from all input T1w images. Workflow Graph .. workflow:: :graph2use: orig :simple_form: yes from smriprep.workflows.anatomical import init_anat_template_wf wf = init_anat_template_wf( longitudinal=False, omp_nthreads=1, num_t1w=1) Parameters ---------- longitudinal : bool Create unbiased structural average, regardless of number of inputs (may increase runtime) omp_nthreads : int Maximum number of threads an individual process may use num_t1w : int Number of T1w images name : str, optional Workflow name (default: anat_template_wf) Inputs ------ t1w List of T1-weighted structural images Outputs ------- t1w_ref Structural reference averaging input T1w images, defining the T1w space. t1w_realign_xfm List of affine transforms to realign input T1w images out_report Conformation report """ workflow = Workflow(name=name) if num_t1w > 1: workflow.__desc__ = """\ A T1w-reference map was computed after registration of {num_t1w} T1w images (after INU-correction) using `mri_robust_template` [FreeSurfer {fs_ver}, @fs_template]. """.format(num_t1w=num_t1w, fs_ver=fs.Info().looseversion() or '<ver>') inputnode = pe.Node(niu.IdentityInterface(fields=['t1w']), name='inputnode') outputnode = pe.Node(niu.IdentityInterface( fields=['t1w_ref', 't1w_valid_list', 't1w_realign_xfm', 'out_report']), name='outputnode') # 0. Reorient T1w image(s) to RAS and resample to common voxel space t1w_ref_dimensions = pe.Node(TemplateDimensions(), name='t1w_ref_dimensions') t1w_conform = pe.MapNode(Conform(), iterfield='in_file', name='t1w_conform') workflow.connect([ (inputnode, t1w_ref_dimensions, [('t1w', 't1w_list')]), (t1w_ref_dimensions, t1w_conform, [('t1w_valid_list', 'in_file'), ('target_zooms', 'target_zooms'), ('target_shape', 'target_shape')]), (t1w_ref_dimensions, outputnode, [('out_report', 'out_report'), ('t1w_valid_list', 't1w_valid_list') ]), ]) if num_t1w == 1: get1st = pe.Node(niu.Select(index=[0]), name='get1st') outputnode.inputs.t1w_realign_xfm = [ pkgr('smriprep', 'data/itkIdentityTransform.txt') ] workflow.connect([ (t1w_conform, get1st, [('out_file', 'inlist')]), (get1st, outputnode, [('out', 't1w_ref')]), ]) return workflow t1w_conform_xfm = pe.MapNode(LTAConvert(in_lta='identity.nofile', out_lta=True), iterfield=['source_file', 'target_file'], name='t1w_conform_xfm') # 1. Template (only if several T1w images) # 1a. Correct for bias field: the bias field is an additive factor # in log-transformed intensity units. Therefore, it is not a linear # combination of fields and N4 fails with merged images. # 1b. Align and merge if several T1w images are provided n4_correct = pe.MapNode(N4BiasFieldCorrection(dimension=3, copy_header=True), iterfield='input_image', name='n4_correct', n_procs=1) # n_procs=1 for reproducibility # StructuralReference is fs.RobustTemplate if > 1 volume, copying otherwise t1w_merge = pe.Node( StructuralReference( auto_detect_sensitivity=True, initial_timepoint=1, # For deterministic behavior intensity_scaling=True, # 7-DOF (rigid + intensity) subsample_threshold=200, fixed_timepoint=not longitudinal, no_iteration=not longitudinal, transform_outputs=True, ), mem_gb=2 * num_t1w - 1, name='t1w_merge') # 2. Reorient template to RAS, if needed (mri_robust_template may set to LIA) t1w_reorient = pe.Node(image.Reorient(), name='t1w_reorient') concat_affines = pe.MapNode(ConcatenateLTA(out_type='RAS2RAS', invert_out=True), iterfield=['in_lta1', 'in_lta2'], name='concat_affines') lta_to_itk = pe.MapNode(LTAConvert(out_itk=True), iterfield=['in_lta'], name='lta_to_itk') def _set_threads(in_list, maximum): return min(len(in_list), maximum) workflow.connect([ (t1w_ref_dimensions, t1w_conform_xfm, [('t1w_valid_list', 'source_file')]), (t1w_conform, t1w_conform_xfm, [('out_file', 'target_file')]), (t1w_conform, n4_correct, [('out_file', 'input_image')]), (t1w_conform, t1w_merge, [(('out_file', _set_threads, omp_nthreads), 'num_threads'), (('out_file', add_suffix, '_template'), 'out_file')]), (n4_correct, t1w_merge, [('output_image', 'in_files')]), (t1w_merge, t1w_reorient, [('out_file', 'in_file')]), # Combine orientation and template transforms (t1w_conform_xfm, concat_affines, [('out_lta', 'in_lta1')]), (t1w_merge, concat_affines, [('transform_outputs', 'in_lta2')]), (concat_affines, lta_to_itk, [('out_file', 'in_lta')]), # Output (t1w_reorient, outputnode, [('out_file', 't1w_ref')]), (lta_to_itk, outputnode, [('out_itk', 't1w_realign_xfm')]), ]) return workflow
def init_anat_average_wf( *, bspline_fitting_distance=200, longitudinal=False, name="anat_average_wf", num_maps=1, omp_nthreads=None, sloppy=False, ): """ Create an average from several images of the same modality. Each image undergoes a clipping step, removing background noise and high-intensity outliers, which is required by INU correction with the N4 algorithm. Then INU correction is performed for each of the inputs and the range of the image clipped again to fit within uint8. Finally, each image is reoriented to have RAS+ data matrix and, if more than one inputs, aligned and averaged with FreeSurfer's ``mri_robust_template``. Parameters ---------- bspline_fitting_distance : :obj:`float` Distance in mm between B-Spline control points for N4 INU estimation. longitudinal : :obj:`bool` Whether an unbiased middle point should be calculated. name : :obj:`str` This particular workflow's unique name (Nipype requirement). num_maps : :obj:`int` Then number of input 3D volumes to be averaged. omp_nthreads : :obj:`int` The number of threads for individual processes in this workflow. sloppy : :obj:`bool` Run in *sloppy* mode. Inputs ------ in_files : :obj:`list` A list of one or more input files. They can be 3D or 4D. Outputs ------- out_file : :obj:`str` The output averaged reference file. valid_list : :obj:`list` A list of accepted/discarded volumes from the input list. realign_xfms : :obj:`list` List of rigid-body transformation matrices that bring every volume into alignment with the average reference. out_report : :obj:`str` Path to a reportlet summarizing what happened in this workflow. """ from pkg_resources import resource_filename as pkgr from nipype.interfaces.ants import N4BiasFieldCorrection from nipype.interfaces.image import Reorient from niworkflows.engine.workflows import LiterateWorkflow as Workflow from niworkflows.interfaces.header import ValidateImage from niworkflows.interfaces.nibabel import IntensityClip, SplitSeries from niworkflows.interfaces.freesurfer import ( StructuralReference, PatchedLTAConvert as LTAConvert, ) from niworkflows.interfaces.images import TemplateDimensions, Conform from niworkflows.interfaces.nitransforms import ConcatenateXFMs from niworkflows.utils.misc import add_suffix wf = Workflow(name=name) inputnode = pe.Node(niu.IdentityInterface(fields=["in_files"]), name="inputnode") outputnode = pe.Node( niu.IdentityInterface( fields=["out_file", "valid_list", "realign_xfms", "out_report"]), name="outputnode", ) # 1. Validate each of the input images validate = pe.MapNode( ValidateImage(), iterfield="in_file", name="validate", run_without_submitting=True, ) # 2. Ensure we don't have two timepoints and implicitly squeeze image split = pe.MapNode(SplitSeries(), iterfield="in_file", name="split") # 3. INU correction of all independent volumes clip_preinu = pe.MapNode(IntensityClip(p_min=50), iterfield="in_file", name="clip_preinu") correct_inu = pe.MapNode( N4BiasFieldCorrection( dimension=3, save_bias=False, copy_header=True, n_iterations=[50] * (5 - 2 * sloppy), convergence_threshold=1e-7, shrink_factor=4, bspline_fitting_distance=bspline_fitting_distance, ), iterfield="input_image", n_procs=omp_nthreads, name="correct_inu", ) clip_postinu = pe.MapNode(IntensityClip(p_min=10.0, p_max=99.5), iterfield="in_file", name="clip_postinu") # 4. Reorient T2w image(s) to RAS and resample to common voxel space ref_dimensions = pe.Node(TemplateDimensions(), name="ref_dimensions") conform = pe.MapNode(Conform(), iterfield="in_file", name="conform") # fmt:off wf.connect([ (inputnode, ref_dimensions, [("in_files", "t1w_list")]), (inputnode, validate, [("in_files", "in_file")]), (validate, split, [("out_file", "in_file")]), (split, clip_preinu, [(("out_files", _flatten), "in_file")]), (clip_preinu, correct_inu, [("out_file", "input_image")]), (correct_inu, clip_postinu, [("output_image", "in_file")]), (ref_dimensions, conform, [("t1w_valid_list", "in_file"), ("target_zooms", "target_zooms"), ("target_shape", "target_shape")]), (ref_dimensions, outputnode, [("out_report", "out_report"), ("t1w_valid_list", "valid_list")]), ]) # fmt:on # 5. Reorient template to RAS, if needed (mri_robust_template may set to LIA) ensure_ras = pe.Node(Reorient(), name="ensure_ras") if num_maps == 1: get1st = pe.Node(niu.Select(index=[0]), name="get1st") outputnode.inputs.realign_xfms = [ pkgr("smriprep", "data/itkIdentityTransform.txt") ] # fmt:off wf.connect([ (conform, get1st, [("out_file", "inlist")]), (get1st, ensure_ras, [("out", "in_file")]), (ensure_ras, outputnode, [("out_file", "out_file")]), ]) # fmt:on return wf from nipype.interfaces import freesurfer as fs wf.__desc__ = f"""\ An anatomical reference-map was computed after registration of {num_maps} images (after INU-correction) using `mri_robust_template` [FreeSurfer {fs.Info().looseversion() or "<ver>"}, @fs_template]. """ conform_xfm = pe.MapNode( LTAConvert(in_lta="identity.nofile", out_lta=True), iterfield=["source_file", "target_file"], name="conform_xfm", ) # 6. StructuralReference is fs.RobustTemplate if > 1 volume, copying otherwise merge = pe.Node( StructuralReference( auto_detect_sensitivity=True, initial_timepoint=1, # For deterministic behavior intensity_scaling=True, # 7-DOF (rigid + intensity) subsample_threshold=200, fixed_timepoint=not longitudinal, no_iteration=not longitudinal, transform_outputs=True, ), mem_gb=2 * num_maps - 1, name="merge", ) # 7. Final intensity equalization/conformation clip_final = pe.Node(IntensityClip(p_min=2.0, p_max=99.9), name="clip_final") merge_xfm = pe.MapNode( niu.Merge(2), name="merge_xfm", iterfield=["in1", "in2"], run_without_submitting=True, ) concat_xfms = pe.MapNode( ConcatenateXFMs(inverse=True), name="concat_xfms", iterfield=["in_xfms"], run_without_submitting=True, ) def _set_threads(in_list, maximum): return min(len(in_list), maximum) # fmt:off wf.connect([ (ref_dimensions, conform_xfm, [("t1w_valid_list", "source_file")]), (conform, conform_xfm, [("out_file", "target_file")]), (conform, merge, [("out_file", "in_files"), (("out_file", _set_threads, omp_nthreads), "num_threads"), (("out_file", add_suffix, "_template"), "out_file")]), (merge, ensure_ras, [("out_file", "in_file")]), # Combine orientation and template transforms (conform_xfm, merge_xfm, [("out_lta", "in1")]), (merge, merge_xfm, [("transform_outputs", "in2")]), (merge_xfm, concat_xfms, [("out", "in_xfms")]), # Output (ensure_ras, clip_final, [("out_file", "in_file")]), (clip_final, outputnode, [("out_file", "out_file")]), (concat_xfms, outputnode, [("out_xfm", "realign_xfms")]), ]) # fmt:on return wf
def init_bbreg_wf(use_bbr, bold2t1w_dof, omp_nthreads, name='bbreg_wf'): """ Build a workflow to run FreeSurfer's ``bbregister``. This workflow uses FreeSurfer's ``bbregister`` to register a BOLD image to a T1-weighted structural image. It is a counterpart to :py:func:`~fmriprep.workflows.bold.registration.init_fsl_bbr_wf`, which performs the same task using FSL's FLIRT with a BBR cost function. The ``use_bbr`` option permits a high degree of control over registration. If ``False``, standard, affine coregistration will be performed using FreeSurfer's ``mri_coreg`` tool. If ``True``, ``bbregister`` will be seeded with the initial transform found by ``mri_coreg`` (equivalent to running ``bbregister --init-coreg``). If ``None``, after ``bbregister`` is run, the resulting affine transform will be compared to the initial transform found by ``mri_coreg``. Excessive deviation will result in rejecting the BBR refinement and accepting the original, affine registration. Workflow Graph .. workflow :: :graph2use: orig :simple_form: yes from fmriprep.workflows.bold.registration import init_bbreg_wf wf = init_bbreg_wf(use_bbr=True, bold2t1w_dof=9, omp_nthreads=1) Parameters ---------- use_bbr : bool or None Enable/disable boundary-based registration refinement. If ``None``, test BBR result for distortion before accepting. bold2t1w_dof : 6, 9 or 12 Degrees-of-freedom for BOLD-T1w registration name : str, optional Workflow name (default: bbreg_wf) Inputs ------ in_file Reference BOLD image to be registered fsnative2t1w_xfm FSL-style affine matrix translating from FreeSurfer T1.mgz to T1w subjects_dir FreeSurfer SUBJECTS_DIR subject_id FreeSurfer subject ID (must have folder in SUBJECTS_DIR) t1w_brain Unused (see :py:func:`~fmriprep.workflows.bold.registration.init_fsl_bbr_wf`) t1w_dseg Unused (see :py:func:`~fmriprep.workflows.bold.registration.init_fsl_bbr_wf`) Outputs ------- itk_bold_to_t1 Affine transform from ``ref_bold_brain`` to T1 space (ITK format) itk_t1_to_bold Affine transform from T1 space to BOLD space (ITK format) out_report Reportlet for assessing registration quality fallback Boolean indicating whether BBR was rejected (mri_coreg registration returned) """ workflow = Workflow(name=name) workflow.__desc__ = """\ The BOLD reference was then co-registered to the T1w reference using `bbregister` (FreeSurfer) which implements boundary-based registration [@bbr]. Co-registration was configured with {dof} degrees of freedom{reason}. """.format(dof={6: 'six', 9: 'nine', 12: 'twelve'}[bold2t1w_dof], reason='' if bold2t1w_dof == 6 else 'to account for distortions remaining in the BOLD reference') inputnode = pe.Node( niu.IdentityInterface([ 'in_file', 'fsnative2t1w_xfm', 'subjects_dir', 'subject_id', # BBRegister 't1w_dseg', 't1w_brain']), # FLIRT BBR name='inputnode') outputnode = pe.Node( niu.IdentityInterface(['itk_bold_to_t1', 'itk_t1_to_bold', 'out_report', 'fallback']), name='outputnode') mri_coreg = pe.Node( MRICoregRPT(dof=bold2t1w_dof, sep=[4], ftol=0.0001, linmintol=0.01, generate_report=not use_bbr), name='mri_coreg', n_procs=omp_nthreads, mem_gb=5) lta_concat = pe.Node(ConcatenateLTA(out_file='out.lta'), name='lta_concat') # XXX LTA-FSL-ITK may ultimately be able to be replaced with a straightforward # LTA-ITK transform, but right now the translation parameters are off. lta2fsl_fwd = pe.Node(LTAConvert(out_fsl=True), name='lta2fsl_fwd') lta2fsl_inv = pe.Node(LTAConvert(out_fsl=True, invert=True), name='lta2fsl_inv') fsl2itk_fwd = pe.Node(c3.C3dAffineTool(fsl2ras=True, itk_transform=True), name='fsl2itk_fwd', mem_gb=DEFAULT_MEMORY_MIN_GB) fsl2itk_inv = pe.Node(c3.C3dAffineTool(fsl2ras=True, itk_transform=True), name='fsl2itk_inv', mem_gb=DEFAULT_MEMORY_MIN_GB) workflow.connect([ (inputnode, mri_coreg, [('subjects_dir', 'subjects_dir'), ('subject_id', 'subject_id'), ('in_file', 'source_file')]), # Output ITK transforms (inputnode, lta_concat, [('fsnative2t1w_xfm', 'in_lta2')]), (lta_concat, lta2fsl_fwd, [('out_file', 'in_lta')]), (lta_concat, lta2fsl_inv, [('out_file', 'in_lta')]), (inputnode, fsl2itk_fwd, [('t1w_brain', 'reference_file'), ('in_file', 'source_file')]), (inputnode, fsl2itk_inv, [('in_file', 'reference_file'), ('t1w_brain', 'source_file')]), (lta2fsl_fwd, fsl2itk_fwd, [('out_fsl', 'transform_file')]), (lta2fsl_inv, fsl2itk_inv, [('out_fsl', 'transform_file')]), (fsl2itk_fwd, outputnode, [('itk_transform', 'itk_bold_to_t1')]), (fsl2itk_inv, outputnode, [('itk_transform', 'itk_t1_to_bold')]), ]) # Short-circuit workflow building, use initial registration if use_bbr is False: workflow.connect([ (mri_coreg, outputnode, [('out_report', 'out_report')]), (mri_coreg, lta_concat, [('out_lta_file', 'in_lta1')])]) outputnode.inputs.fallback = True return workflow bbregister = pe.Node( BBRegisterRPT(dof=bold2t1w_dof, contrast_type='t2', registered_file=True, out_lta_file=True, generate_report=True), name='bbregister', mem_gb=12) workflow.connect([ (inputnode, bbregister, [('subjects_dir', 'subjects_dir'), ('subject_id', 'subject_id'), ('in_file', 'source_file')]), (mri_coreg, bbregister, [('out_lta_file', 'init_reg_file')]), ]) # Short-circuit workflow building, use boundary-based registration if use_bbr is True: workflow.connect([ (bbregister, outputnode, [('out_report', 'out_report')]), (bbregister, lta_concat, [('out_lta_file', 'in_lta1')])]) outputnode.inputs.fallback = False return workflow transforms = pe.Node(niu.Merge(2), run_without_submitting=True, name='transforms') reports = pe.Node(niu.Merge(2), run_without_submitting=True, name='reports') lta_ras2ras = pe.MapNode(LTAConvert(out_lta=True), iterfield=['in_lta'], name='lta_ras2ras', mem_gb=2) compare_transforms = pe.Node(niu.Function(function=compare_xforms), name='compare_transforms') select_transform = pe.Node(niu.Select(), run_without_submitting=True, name='select_transform') select_report = pe.Node(niu.Select(), run_without_submitting=True, name='select_report') workflow.connect([ (bbregister, transforms, [('out_lta_file', 'in1')]), (mri_coreg, transforms, [('out_lta_file', 'in2')]), # Normalize LTA transforms to RAS2RAS (inputs are VOX2VOX) and compare (transforms, lta_ras2ras, [('out', 'in_lta')]), (lta_ras2ras, compare_transforms, [('out_lta', 'lta_list')]), (compare_transforms, outputnode, [('out', 'fallback')]), # Select output transform (transforms, select_transform, [('out', 'inlist')]), (compare_transforms, select_transform, [('out', 'index')]), (select_transform, lta_concat, [('out', 'in_lta1')]), # Select output report (bbregister, reports, [('out_report', 'in1')]), (mri_coreg, reports, [('out_report', 'in2')]), (reports, select_report, [('out', 'inlist')]), (compare_transforms, select_report, [('out', 'index')]), (select_report, outputnode, [('out', 'out_report')]), ]) return workflow
def init_anat_derivatives_wf(bids_root, freesurfer, num_t1w, output_dir, name='anat_derivatives_wf'): """Set up a battery of datasinks to store derivatives in the right location.""" workflow = Workflow(name=name) inputnode = pe.Node(niu.IdentityInterface(fields=[ 'template', 'source_files', 't1w_ref_xfms', 't1w_preproc', 't1w_mask', 't1w_dseg', 't1w_tpms', 'anat2std_xfm', 'std2anat_xfm', 'std_t1w', 'std_mask', 'std_dseg', 'std_tpms', 't1w2fsnative_xfm', 'fsnative2t1w_xfm', 'surfaces', 't1w_fs_aseg', 't1w_fs_aparc' ]), name='inputnode') t1w_name = pe.Node(niu.Function(function=fix_multi_T1w_source_name), name='t1w_name') 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', keep_dtype=True, 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' lut_t1w_dseg = pe.Node(niu.Function(function=_apply_default_bids_lut), name='lut_t1w_dseg') 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.extra_values = ['label-CSF', 'label-GM', 'label-WM'] ds_t1w_tpl = pe.Node(DerivativesDataSink(base_directory=output_dir, desc='preproc', keep_dtype=True, compress=True), name='ds_t1w_tpl', run_without_submitting=True) ds_t1w_tpl.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' lut_std_dseg = pe.Node(niu.Function(function=_apply_default_bids_lut), name='lut_std_dseg') 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) ds_std_tpms.inputs.extra_values = ['label-CSF', 'label-GM', 'label-WM'] # Transforms ds_t1w_tpl_inv_warp = pe.Node(DerivativesDataSink( base_directory=output_dir, allowed_entities=['from', 'to', 'mode'], to='T1w', mode='image', suffix='xfm'), name='ds_t1w_tpl_inv_warp', run_without_submitting=True) ds_t1w_tpl_warp = pe.Node(DerivativesDataSink( base_directory=output_dir, allowed_entities=['from', 'to', 'mode'], mode='image', suffix='xfm', **{'from': 'T1w'}), name='ds_t1w_tpl_warp', run_without_submitting=True) workflow.connect([ (inputnode, t1w_name, [('source_files', 'in_files')]), (inputnode, raw_sources, [('source_files', 'in_files')]), (inputnode, ds_t1w_preproc, [('t1w_preproc', 'in_file')]), (inputnode, ds_t1w_mask, [('t1w_mask', 'in_file')]), (inputnode, lut_t1w_dseg, [('t1w_dseg', 'in_file')]), (inputnode, ds_t1w_tpms, [('t1w_tpms', 'in_file')]), (lut_t1w_dseg, ds_t1w_dseg, [('out', 'in_file')]), (t1w_name, ds_t1w_preproc, [('out', 'source_file')]), (t1w_name, ds_t1w_mask, [('out', 'source_file')]), (t1w_name, ds_t1w_dseg, [('out', 'source_file')]), (t1w_name, ds_t1w_tpms, [('out', 'source_file')]), (raw_sources, ds_t1w_mask, [('out', 'RawSources')]), # Template (inputnode, ds_t1w_tpl_warp, [('anat2std_xfm', 'in_file'), ('template', 'to')]), (inputnode, ds_t1w_tpl_inv_warp, [('std2anat_xfm', 'in_file'), ('template', 'from')]), (inputnode, ds_t1w_tpl, [('std_t1w', 'in_file'), ('template', 'space')]), (inputnode, ds_std_mask, [('std_mask', 'in_file'), ('template', 'space'), (('template', _rawsources), 'RawSources')]), (inputnode, ds_std_dseg, [('template', 'space')]), (inputnode, lut_std_dseg, [('std_dseg', 'in_file')]), (lut_std_dseg, ds_std_dseg, [('out', 'in_file')]), (inputnode, ds_std_tpms, [('std_tpms', 'in_file'), ('template', 'space')]), (t1w_name, ds_t1w_tpl_warp, [('out', 'source_file')]), (t1w_name, ds_t1w_tpl_inv_warp, [('out', 'source_file')]), (t1w_name, ds_t1w_tpl, [('out', 'source_file')]), (t1w_name, ds_std_mask, [('out', 'source_file')]), (t1w_name, ds_std_dseg, [('out', 'source_file')]), (t1w_name, ds_std_tpms, [('out', '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, allowed_entities=['from', 'to', 'mode'], to='T1w', mode='image', suffix='xfm', **{'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')]), ]) if not freesurfer: return workflow # FS native space transforms lta2itk_fwd = pe.Node(LTAConvert(out_itk=True), name='lta2itk_fwd') lta2itk_inv = pe.Node(LTAConvert(out_itk=True), name='lta2itk_inv') ds_t1w_fsnative = pe.Node(DerivativesDataSink( base_directory=output_dir, allowed_entities=['from', 'to', 'mode'], mode='image', to='fsnative', suffix='xfm', **{'from': 'T1w'}), name='ds_t1w_fsnative', run_without_submitting=True) ds_fsnative_t1w = pe.Node(DerivativesDataSink( base_directory=output_dir, allowed_entities=['from', 'to', 'mode'], mode='image', to='T1w', suffix='xfm', **{'from': 'fsnative'}), name='ds_fsnative_t1w', run_without_submitting=True) # Surfaces name_surfs = pe.MapNode(GiftiNameSource( pattern=r'(?P<LR>[lr])h.(?P<surf>.+)_converted.gii', template='hemi-{LR}_{surf}.surf'), iterfield='in_file', name='name_surfs', run_without_submitting=True) ds_surfs = pe.MapNode(DerivativesDataSink(base_directory=output_dir), iterfield=['in_file', '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) workflow.connect([ (inputnode, lta2itk_fwd, [('t1w2fsnative_xfm', 'in_lta')]), (inputnode, lta2itk_inv, [('fsnative2t1w_xfm', 'in_lta')]), (t1w_name, ds_t1w_fsnative, [('out', 'source_file')]), (lta2itk_fwd, ds_t1w_fsnative, [('out_itk', 'in_file')]), (t1w_name, ds_fsnative_t1w, [('out', 'source_file')]), (lta2itk_inv, ds_fsnative_t1w, [('out_itk', 'in_file')]), (inputnode, name_surfs, [('surfaces', 'in_file')]), (inputnode, ds_surfs, [('surfaces', 'in_file')]), (t1w_name, ds_surfs, [('out', 'source_file')]), (name_surfs, ds_surfs, [('out_name', 'suffix')]), (inputnode, ds_t1w_fsaseg, [('t1w_fs_aseg', 'in_file')]), (inputnode, ds_t1w_fsparc, [('t1w_fs_aparc', 'in_file')]), (t1w_name, ds_t1w_fsaseg, [('out', 'source_file')]), (t1w_name, ds_t1w_fsparc, [('out', 'source_file')]), ]) return workflow
def init_bold_surf_wf(mem_gb, surface_spaces, medial_surface_nan, name='bold_surf_wf'): """ Sample functional images to FreeSurfer surfaces. For each vertex, the cortical ribbon is sampled at six points (spaced 20% of thickness apart) and averaged. Outputs are in GIFTI format. Workflow Graph .. workflow:: :graph2use: colored :simple_form: yes from fmriprep.workflows.bold import init_bold_surf_wf wf = init_bold_surf_wf(mem_gb=0.1, surface_spaces=['fsnative', 'fsaverage5'], medial_surface_nan=False) Parameters ---------- surface_spaces : :obj:`list` List of FreeSurfer surface-spaces (either ``fsaverage{3,4,5,6,}`` or ``fsnative``) the functional images are to be resampled to. For ``fsnative``, images will be resampled to the individual subject's native surface. medial_surface_nan : :obj:`bool` Replace medial wall values with NaNs on functional GIFTI files Inputs ------ source_file Motion-corrected BOLD series in T1 space t1w_preproc Bias-corrected structural template image subjects_dir FreeSurfer SUBJECTS_DIR subject_id FreeSurfer subject ID t1w2fsnative_xfm LTA-style affine matrix translating from T1w to FreeSurfer-conformed subject space Outputs ------- surfaces BOLD series, resampled to FreeSurfer surfaces """ workflow = Workflow(name=name) workflow.__desc__ = """\ The BOLD time-series were resampled onto the following surfaces (FreeSurfer reconstruction nomenclature): {out_spaces}. """.format(out_spaces=', '.join(['*%s*' % s for s in surface_spaces])) inputnode = pe.Node(niu.IdentityInterface(fields=[ 'source_file', 't1w_preproc', 'subject_id', 'subjects_dir', 't1w2fsnative_xfm' ]), name='inputnode') itersource = pe.Node(niu.IdentityInterface(fields=['target']), name='itersource') itersource.iterables = [('target', surface_spaces)] def select_target(subject_id, space): """Get the target subject ID, given a source subject ID and a target space.""" return subject_id if space == 'fsnative' else space targets = pe.Node(niu.Function(function=select_target), name='targets', run_without_submitting=True, mem_gb=DEFAULT_MEMORY_MIN_GB) # Rename the source file to the output space to simplify naming later rename_src = pe.Node(niu.Rename(format_string='%(subject)s', keep_ext=True), name='rename_src', run_without_submitting=True, mem_gb=DEFAULT_MEMORY_MIN_GB) resampling_xfm = pe.Node(LTAConvert(in_lta='identity.nofile', out_lta=True), name='resampling_xfm') set_xfm_source = pe.Node(ConcatenateLTA(out_type='RAS2RAS'), name='set_xfm_source') sampler = pe.MapNode(fs.SampleToSurface( cortex_mask=True, interp_method='trilinear', out_type='gii', override_reg_subj=True, sampling_method='average', sampling_range=(0, 1, 0.2), sampling_units='frac', ), iterfield=['hemi'], name='sampler', mem_gb=mem_gb * 3) sampler.inputs.hemi = ['lh', 'rh'] update_metadata = pe.MapNode(GiftiSetAnatomicalStructure(), iterfield=['in_file'], name='update_metadata', mem_gb=DEFAULT_MEMORY_MIN_GB) outputnode = pe.JoinNode( niu.IdentityInterface(fields=['surfaces', 'target']), joinsource='itersource', name='outputnode') workflow.connect([ (inputnode, targets, [('subject_id', 'subject_id')]), (inputnode, rename_src, [('source_file', 'in_file')]), (inputnode, resampling_xfm, [('source_file', 'source_file'), ('t1w_preproc', 'target_file')]), (inputnode, set_xfm_source, [('t1w2fsnative_xfm', 'in_lta2')]), (inputnode, sampler, [('subjects_dir', 'subjects_dir'), ('subject_id', 'subject_id')]), (itersource, targets, [('target', 'space')]), (itersource, rename_src, [('target', 'subject')]), (resampling_xfm, set_xfm_source, [('out_lta', 'in_lta1')]), (set_xfm_source, sampler, [('out_file', 'reg_file')]), (targets, sampler, [('out', 'target_subject')]), (rename_src, sampler, [('out_file', 'source_file')]), (update_metadata, outputnode, [('out_file', 'surfaces')]), (itersource, outputnode, [('target', 'target')]), ]) if not medial_surface_nan: workflow.connect(sampler, 'out_file', update_metadata, 'in_file') return workflow # Refine if medial vertices should be NaNs medial_nans = pe.MapNode(MedialNaNs(), iterfield=['in_file'], name='medial_nans', mem_gb=DEFAULT_MEMORY_MIN_GB) workflow.connect([ (inputnode, medial_nans, [('subjects_dir', 'subjects_dir')]), (sampler, medial_nans, [('out_file', 'in_file')]), (medial_nans, update_metadata, [('out_file', 'in_file')]), ]) return workflow
def init_infant_surface_recon_wf(*, age_months, use_aseg=False, name="infant_surface_recon_wf"): wf = pe.Workflow(name=name) inputnode = pe.Node( niu.IdentityInterface(fields=[ "subjects_dir", "subject_id", "anat_orig", "anat_skullstripped", "anat_preproc", "anat_aseg", "t2w", ], ), name="inputnode", ) outputnode = pe.Node( niu.IdentityInterface(fields=[ "subjects_dir", "subject_id", "anat2fsnative_xfm", "fsnative2anat_xfm", "surfaces", "anat_aseg", "anat_aparc", ]), name="outputnode", ) gen_recon_outdir = pe.Node(niu.Function(function=_gen_recon_dir), name='gen_recon_outdir') # inject the intensity-normalized skull-stripped t1w from the brain extraction workflow recon = pe.Node(InfantReconAll(age=age_months), name="reconall") # these files are created by babyFS, but transforms are for masked anatomicals # https://github.com/freesurfer/freesurfer/blob/ # 8b40551f096294cc6603ce928317b8df70bce23e/infant/infant_recon_all#L744 # TODO: calculate full anat -> fsnative transform? get_tal_lta = pe.Node( niu.Function(function=_get_talairch_lta), name="get_tal_xfm", ) fsnative2anat_xfm = pe.Node( LTAConvert(out_lta=True, invert=True), name="fsnative2anat_xfm", ) # convert generated surfaces to GIFTIs gifti_surface_wf = init_gifti_surface_wf() get_aseg = pe.Node(niu.Function(function=_get_aseg), name='get_aseg') get_aparc = pe.Node(niu.Function(function=_get_aparc), name="get_aparc") aparc2nii = pe.Node(fs.MRIConvert(out_type="niigz"), name="aparc2nii") if use_aseg: # TODO: Add precomputed segmentation upon new babyFS rel wf.connect(inputnode, 'anat_aseg', recon, 'aseg_file') # fmt: off wf.connect([ (inputnode, gen_recon_outdir, [ ('subjects_dir', 'subjects_dir'), ('subject_id', 'subject_id'), ]), (inputnode, recon, [ ('anat_skullstripped', 'mask_file'), ('subject_id', 'subject_id'), ]), (gen_recon_outdir, recon, [ ('out', 'outdir'), ]), (recon, outputnode, [ ('subject_id', 'subject_id'), (('outdir', _parent), 'subjects_dir'), ]), (recon, gifti_surface_wf, [ ('subject_id', 'inputnode.subject_id'), (('outdir', _parent), 'inputnode.subjects_dir'), ]), (recon, get_aparc, [ ('outdir', 'fs_subject_dir'), ]), (recon, get_aseg, [ ('outdir', 'fs_subject_dir'), ]), (get_aseg, outputnode, [ ('out', 'anat_aseg'), ]), (get_aparc, aparc2nii, [ ('out', 'in_file'), ]), (aparc2nii, outputnode, [ ('out_file', 'anat_aparc'), ]), (recon, get_tal_lta, [ ('outdir', 'fs_subject_dir'), ]), (get_tal_lta, outputnode, [ ('out', 'anat2fsnative_xfm'), ]), (get_tal_lta, fsnative2anat_xfm, [ ('out', 'in_lta'), ]), (fsnative2anat_xfm, outputnode, [ ('out_lta', 'fsnative2anat_xfm'), ]), (fsnative2anat_xfm, gifti_surface_wf, [('out_lta', 'inputnode.fsnative2t1w_xfm')]), (gifti_surface_wf, outputnode, [ ('outputnode.surfaces', 'surfaces'), ]), ]) # fmt: on return wf
def init_bold_surf_wf(mem_gb, output_spaces, medial_surface_nan, fslr_density=None, name='bold_surf_wf'): """ Sample functional images to FreeSurfer surfaces. For each vertex, the cortical ribbon is sampled at six points (spaced 20% of thickness apart) and averaged. Outputs are in GIFTI format. Workflow Graph .. workflow:: :graph2use: colored :simple_form: yes from fmriprep.workflows.bold import init_bold_surf_wf wf = init_bold_surf_wf(mem_gb=0.1, output_spaces=['T1w', 'fsnative', 'template', 'fsaverage5'], medial_surface_nan=False) Parameters ---------- output_spaces : list List of output spaces functional images are to be resampled to Target spaces beginning with ``fs`` will be selected for resampling, such as ``fsaverage`` or related template spaces If the list contains ``fsnative``, images will be resampled to the individual subject's native surface If the list contains ``fsLR``, images will be resampled twice; first to ``fsaverage`` and then to ``fsLR``. medial_surface_nan : bool Replace medial wall values with NaNs on functional GIFTI files fslr_density : str, optional Density of fsLR surface (32k or 59k) Inputs ------ source_file Motion-corrected BOLD series in T1 space t1w_preproc Bias-corrected structural template image subjects_dir FreeSurfer SUBJECTS_DIR subject_id FreeSurfer subject ID t1w2fsnative_xfm LTA-style affine matrix translating from T1w to FreeSurfer-conformed subject space Outputs ------- surfaces BOLD series, resampled to FreeSurfer surfaces """ # Ensure volumetric spaces do not sneak into this workflow spaces = [space for space in output_spaces if space.startswith('fs')] workflow = Workflow(name=name) if spaces: workflow.__desc__ = """\ The BOLD time-series, were resampled to surfaces on the following spaces: {out_spaces}. """.format(out_spaces=', '.join(['*%s*' % s for s in spaces])) inputnode = pe.Node(niu.IdentityInterface(fields=[ 'source_file', 't1w_preproc', 'subject_id', 'subjects_dir', 't1w2fsnative_xfm' ]), name='inputnode') to_fslr = False if 'fsLR' in output_spaces: to_fslr = 'fsaverage' in output_spaces and fslr_density spaces.pop(spaces.index('fsLR')) outputnode = pe.Node(niu.IdentityInterface(fields=['surfaces']), name='outputnode') def select_target(subject_id, space): """Get the target subject ID, given a source subject ID and a target space.""" return subject_id if space == 'fsnative' else space targets = pe.MapNode(niu.Function(function=select_target), iterfield=['space'], name='targets', mem_gb=DEFAULT_MEMORY_MIN_GB) targets.inputs.space = spaces # Rename the source file to the output space to simplify naming later rename_src = pe.MapNode(niu.Rename(format_string='%(subject)s', keep_ext=True), iterfield='subject', name='rename_src', run_without_submitting=True, mem_gb=DEFAULT_MEMORY_MIN_GB) rename_src.inputs.subject = spaces resampling_xfm = pe.Node(LTAConvert(in_lta='identity.nofile', out_lta=True), name='resampling_xfm') set_xfm_source = pe.Node(ConcatenateLTA(out_type='RAS2RAS'), name='set_xfm_source') sampler = pe.MapNode(fs.SampleToSurface(sampling_method='average', sampling_range=(0, 1, 0.2), sampling_units='frac', interp_method='trilinear', cortex_mask=True, override_reg_subj=True, out_type='gii'), iterfield=['source_file', 'target_subject'], iterables=('hemi', ['lh', 'rh']), name='sampler', mem_gb=mem_gb * 3) if to_fslr: filter_fsavg = pe.Node(niu.Function( function=_select_fsaverage_hemi, output_names=['fsaverage_bold', 'hemi']), name='filter_fsavg', mem_gb=DEFAULT_MEMORY_MIN_GB, run_without_submitting=True) rename_fslr = pe.Node(niu.Rename(format_string="%(hemi)s.fsLR", keep_ext=True, parse_string=r'^(?P<hemi>\w+)'), name='rename_fslr', mem_gb=DEFAULT_MEMORY_MIN_GB, run_without_submitting=True) fetch_fslr_tpls = pe.Node(niu.Function(function=_fetch_fslr_templates, output_names=[ 'fsaverage_sphere', 'fslr_sphere', 'fsaverage_midthick', 'fslr_midthick' ]), name='fetch_fslr_tpls', mem_gb=DEFAULT_MEMORY_MIN_GB, overwrite=True) fetch_fslr_tpls.inputs.den = fslr_density resample_fslr = pe.Node(wb.MetricResample(method='ADAP_BARY_AREA', area_metrics=True), name='resample_fslr') merge_fslr = pe.Node(niu.Merge(2), name='merge_fslr', mem_gb=DEFAULT_MEMORY_MIN_GB, run_without_submitting=True) def _basename(in_file): import os return os.path.basename(in_file) workflow.connect([ (sampler, filter_fsavg, [('out_file', 'in_files')]), (filter_fsavg, fetch_fslr_tpls, [('hemi', 'hemi')]), (filter_fsavg, rename_fslr, [('fsaverage_bold', 'in_file')]), (rename_fslr, resample_fslr, [('out_file', 'in_file')]), (rename_fslr, resample_fslr, [(('out_file', _basename), 'out_file') ]), (fetch_fslr_tpls, resample_fslr, [('fsaverage_sphere', 'current_sphere'), ('fslr_sphere', 'new_sphere'), ('fsaverage_midthick', 'current_area'), ('fslr_midthick', 'new_area')]), (sampler, merge_fslr, [('out_file', 'in1')]), (resample_fslr, merge_fslr, [('out_file', 'in2')]), ]) merger = pe.JoinNode(niu.Merge(1, ravel_inputs=True), name='merger', joinsource='sampler', joinfield=['in1'], run_without_submitting=True, mem_gb=DEFAULT_MEMORY_MIN_GB) if medial_surface_nan: medial_nans = pe.MapNode(MedialNaNs(), iterfield=['in_file'], name='medial_nans', mem_gb=DEFAULT_MEMORY_MIN_GB) workflow.connect([ (inputnode, medial_nans, [('subjects_dir', 'subjects_dir')]), (medial_nans, merger, [('out_file', 'in1')]), ]) update_metadata = pe.MapNode(GiftiSetAnatomicalStructure(), iterfield='in_file', name='update_metadata', mem_gb=DEFAULT_MEMORY_MIN_GB) workflow.connect([ (inputnode, targets, [('subject_id', 'subject_id')]), (inputnode, rename_src, [('source_file', 'in_file')]), (inputnode, resampling_xfm, [('source_file', 'source_file'), ('t1w_preproc', 'target_file')]), (inputnode, set_xfm_source, [('t1w2fsnative_xfm', 'in_lta2')]), (resampling_xfm, set_xfm_source, [('out_lta', 'in_lta1')]), (inputnode, sampler, [('subjects_dir', 'subjects_dir'), ('subject_id', 'subject_id')]), (set_xfm_source, sampler, [('out_file', 'reg_file')]), (targets, sampler, [('out', 'target_subject')]), (rename_src, sampler, [('out_file', 'source_file')]), (merger, update_metadata, [('out', 'in_file')]), (update_metadata, outputnode, [('out_file', 'surfaces')]), ]) if to_fslr and medial_surface_nan: medial_nans.inputs.density = fslr_density workflow.connect(merge_fslr, 'out', medial_nans, 'in_file') elif to_fslr: workflow.connect(merge_fslr, 'out', merger, 'in1') elif medial_surface_nan: workflow.connect(sampler, 'out_file', medial_nans, 'in_file') else: workflow.connect(sampler, 'out_file', merger, 'in1') return workflow
def init_t2w_template_wf(longitudinal, omp_nthreads, num_t2w, name="anat_t2w_template_wf"): """ Adapts :py:func:`~smriprep.workflows.anatomical.init_anat_template_wf` for T2w image reference """ from pkg_resources import resource_filename as pkgr from nipype.interfaces import freesurfer as fs, image, ants from niworkflows.engine.workflows import LiterateWorkflow as Workflow from niworkflows.interfaces.freesurfer import ( StructuralReference, PatchedLTAConvert as LTAConvert, ) from niworkflows.interfaces.images import TemplateDimensions, Conform, ValidateImage from niworkflows.interfaces.nitransforms import ConcatenateXFMs from niworkflows.utils.misc import add_suffix wf = Workflow(name=name) inputnode = pe.Node(niu.IdentityInterface(fields=["t2w"]), name="inputnode") outputnode = pe.Node( niu.IdentityInterface(fields=[ "t2w_ref", "t2w_valid_list", "t2_realign_xfm", "out_report" ]), name="outputnode", ) # 0. Reorient T2w image(s) to RAS and resample to common voxel space t2w_ref_dimensions = pe.Node(TemplateDimensions(), name='t2w_ref_dimensions') t2w_conform = pe.MapNode(Conform(), iterfield='in_file', name='t2w_conform') wf.connect([ (inputnode, t2w_ref_dimensions, [('t2w', 't1w_list')]), (t2w_ref_dimensions, t2w_conform, [('t1w_valid_list', 'in_file'), ('target_zooms', 'target_zooms'), ('target_shape', 'target_shape')]), (t2w_ref_dimensions, outputnode, [('out_report', 'out_report'), ('t1w_valid_list', 't2w_valid_list') ]), ]) if num_t2w == 1: get1st = pe.Node(niu.Select(index=[0]), name='get1st') outputnode.inputs.t2w_realign_xfm = [ pkgr('smriprep', 'data/itkIdentityTransform.txt') ] wf.connect([ (t2w_conform, get1st, [('out_file', 'inlist')]), (get1st, outputnode, [('out', 't2w_ref')]), ]) return wf wf.__desc__ = f"""\ A T2w-reference map was computed after registration of {num_t2w} T2w images (after INU-correction) using `mri_robust_template` [FreeSurfer {fs.Info().looseversion() or "<ver>"}, @fs_template]. """ t2w_conform_xfm = pe.MapNode(LTAConvert(in_lta='identity.nofile', out_lta=True), iterfield=['source_file', 'target_file'], name='t2w_conform_xfm') # 1a. Correct for bias field: the bias field is an additive factor # in log-transformed intensity units. Therefore, it is not a linear # combination of fields and N4 fails with merged images. # 1b. Align and merge if several T1w images are provided n4_correct = pe.MapNode(ants.N4BiasFieldCorrection(dimension=3, copy_header=True), iterfield='input_image', name='n4_correct', n_procs=1) # n_procs=1 for reproducibility # StructuralReference is fs.RobustTemplate if > 1 volume, copying otherwise t2w_merge = pe.Node( StructuralReference( auto_detect_sensitivity=True, initial_timepoint=1, # For deterministic behavior intensity_scaling=True, # 7-DOF (rigid + intensity) subsample_threshold=200, fixed_timepoint=not longitudinal, no_iteration=not longitudinal, transform_outputs=True, ), mem_gb=2 * num_t2w - 1, name='t2w_merge') # 2. Reorient template to RAS, if needed (mri_robust_template may set to LIA) t2w_reorient = pe.Node(image.Reorient(), name='t2w_reorient') merge_xfm = pe.MapNode(niu.Merge(2), name='merge_xfm', iterfield=['in1', 'in2'], run_without_submitting=True) concat_xfms = pe.MapNode(ConcatenateXFMs(inverse=True), name="concat_xfms", iterfield=['in_xfms'], run_without_submitting=True) def _set_threads(in_list, maximum): return min(len(in_list), maximum) wf.connect([ (t2w_ref_dimensions, t2w_conform_xfm, [('t1w_valid_list', 'source_file')]), (t2w_conform, t2w_conform_xfm, [('out_file', 'target_file')]), (t2w_conform, n4_correct, [('out_file', 'input_image')]), (t2w_conform, t2w_merge, [(('out_file', _set_threads, omp_nthreads), 'num_threads'), (('out_file', add_suffix, '_template'), 'out_file')]), (n4_correct, t2w_merge, [('output_image', 'in_files')]), (t2w_merge, t2w_reorient, [('out_file', 'in_file')]), # Combine orientation and template transforms (t2w_conform_xfm, merge_xfm, [('out_lta', 'in1')]), (t2w_merge, merge_xfm, [('transform_outputs', 'in2')]), (merge_xfm, concat_xfms, [('out', 'in_xfms')]), # Output (t2w_reorient, outputnode, [('out_file', 't2w_ref')]), (concat_xfms, outputnode, [('out_xfm', 't2w_realign_xfm')]), ]) return wf
def init_anat_derivatives_wf(bids_root, freesurfer, output_dir, template, name='anat_derivatives_wf'): """ Set up a battery of datasinks to store derivatives in the right location """ workflow = Workflow(name=name) inputnode = pe.Node(niu.IdentityInterface(fields=[ 'source_files', 't1_template_transforms', 't1_preproc', 't1_mask', 't1_seg', 't1_tpms', 't1_2_mni_forward_transform', 't1_2_mni_reverse_transform', 't1_2_mni', 'mni_mask', 'mni_seg', 'mni_tpms', 't1_2_fsnative_forward_transform', 'surfaces', 't1_fs_aseg', 't1_fs_aparc' ]), name='inputnode') t1_name = pe.Node(niu.Function(function=fix_multi_T1w_source_name), name='t1_name') raw_sources = pe.Node(niu.Function(function=_bids_relative), name='raw_sources') raw_sources.inputs.bids_root = bids_root ds_t1_preproc = pe.Node(DerivativesDataSink(base_directory=output_dir, desc='preproc', keep_dtype=True), name='ds_t1_preproc', run_without_submitting=True) ds_t1_preproc.inputs.SkullStripped = False ds_t1_mask = pe.Node(DerivativesDataSink(base_directory=output_dir, desc='brain', suffix='mask'), name='ds_t1_mask', run_without_submitting=True) ds_t1_mask.inputs.Type = 'Brain' lut_t1_seg = pe.Node(niu.Function(function=_apply_default_bids_lut), name='lut_t1_seg') ds_t1_seg = pe.Node(DerivativesDataSink(base_directory=output_dir, suffix='dseg'), name='ds_t1_seg', run_without_submitting=True) ds_t1_tpms = pe.Node(DerivativesDataSink(base_directory=output_dir, suffix='probseg'), name='ds_t1_tpms', run_without_submitting=True) ds_t1_tpms.inputs.extra_values = ['label-CSF', 'label-GM', 'label-WM'] ds_t1_mni = pe.Node(DerivativesDataSink(base_directory=output_dir, space=template, desc='preproc', keep_dtype=True), name='ds_t1_mni', run_without_submitting=True) ds_t1_mni.inputs.SkullStripped = True ds_mni_mask = pe.Node(DerivativesDataSink(base_directory=output_dir, space=template, desc='brain', suffix='mask'), name='ds_mni_mask', run_without_submitting=True) ds_mni_mask.inputs.Type = 'Brain' ds_mni_mask.inputs.RawSources = 'tpl-{0}/tpl-{0}_res-01_desc-brain_mask.nii.gz'.format( template) lut_mni_seg = pe.Node(niu.Function(function=_apply_default_bids_lut), name='lut_mni_seg') ds_mni_seg = pe.Node(DerivativesDataSink(base_directory=output_dir, space=template, suffix='dseg'), name='ds_mni_seg', run_without_submitting=True) ds_mni_tpms = pe.Node(DerivativesDataSink(base_directory=output_dir, space=template, suffix='probseg'), name='ds_mni_tpms', run_without_submitting=True) ds_mni_tpms.inputs.extra_values = ['label-CSF', 'label-GM', 'label-WM'] # Transforms suffix_fmt = 'from-{}_to-{}_mode-image_xfm'.format ds_t1_mni_inv_warp = pe.Node(DerivativesDataSink(base_directory=output_dir, suffix=suffix_fmt( template, 'T1w')), name='ds_t1_mni_inv_warp', run_without_submitting=True) ds_t1_template_transforms = pe.MapNode( DerivativesDataSink(base_directory=output_dir, suffix=suffix_fmt('orig', 'T1w')), iterfield=['source_file', 'in_file'], name='ds_t1_template_transforms', run_without_submitting=True) ds_t1_mni_warp = pe.Node(DerivativesDataSink(base_directory=output_dir, suffix=suffix_fmt( 'T1w', template)), name='ds_t1_mni_warp', run_without_submitting=True) lta_2_itk = pe.Node(LTAConvert(out_itk=True), name='lta_2_itk') ds_t1_fsnative = pe.Node(DerivativesDataSink(base_directory=output_dir, suffix=suffix_fmt( 'T1w', 'fsnative')), name='ds_t1_fsnative', run_without_submitting=True) name_surfs = pe.MapNode(GiftiNameSource( pattern=r'(?P<LR>[lr])h.(?P<surf>.+)_converted.gii', template='hemi-{LR}_{surf}.surf'), iterfield='in_file', name='name_surfs', run_without_submitting=True) ds_surfs = pe.MapNode(DerivativesDataSink(base_directory=output_dir), iterfield=['in_file', 'suffix'], name='ds_surfs', run_without_submitting=True) workflow.connect([ (inputnode, t1_name, [('source_files', 'in_files')]), (inputnode, raw_sources, [('source_files', 'in_files')]), (inputnode, ds_t1_template_transforms, [('source_files', 'source_file'), ('t1_template_transforms', 'in_file')]), (inputnode, ds_t1_preproc, [('t1_preproc', 'in_file')]), (inputnode, ds_t1_mask, [('t1_mask', 'in_file')]), (inputnode, lut_t1_seg, [('t1_seg', 'in_file')]), (inputnode, ds_t1_tpms, [('t1_tpms', 'in_file')]), (lut_t1_seg, ds_t1_seg, [('out', 'in_file')]), (t1_name, ds_t1_preproc, [('out', 'source_file')]), (t1_name, ds_t1_mask, [('out', 'source_file')]), (t1_name, ds_t1_seg, [('out', 'source_file')]), (t1_name, ds_t1_tpms, [('out', 'source_file')]), (raw_sources, ds_t1_mask, [('out', 'RawSources')]), # Template (inputnode, ds_t1_mni_warp, [('t1_2_mni_forward_transform', 'in_file')] ), (inputnode, ds_t1_mni_inv_warp, [('t1_2_mni_reverse_transform', 'in_file')]), (inputnode, ds_t1_mni, [('t1_2_mni', 'in_file')]), (inputnode, ds_mni_mask, [('mni_mask', 'in_file')]), (inputnode, lut_mni_seg, [('mni_seg', 'in_file')]), (lut_mni_seg, ds_mni_seg, [('out', 'in_file')]), (inputnode, ds_mni_tpms, [('mni_tpms', 'in_file')]), (t1_name, ds_t1_mni_warp, [('out', 'source_file')]), (t1_name, ds_t1_mni_inv_warp, [('out', 'source_file')]), (t1_name, ds_t1_mni, [('out', 'source_file')]), (t1_name, ds_mni_mask, [('out', 'source_file')]), (t1_name, ds_mni_seg, [('out', 'source_file')]), (t1_name, ds_mni_tpms, [('out', 'source_file')]), ]) if freesurfer: ds_t1_fsaseg = pe.Node(DerivativesDataSink(base_directory=output_dir, desc='aseg', suffix='dseg'), name='ds_t1_fsaseg', run_without_submitting=True) ds_t1_fsparc = pe.Node(DerivativesDataSink(base_directory=output_dir, desc='aparcaseg', suffix='dseg'), name='ds_t1_fsparc', run_without_submitting=True) ds_t1_fsparc = pe.Node(DerivativesDataSink(base_directory=output_dir, desc='aparcaseg', suffix='dseg'), name='ds_t1_fsparc', run_without_submitting=True) workflow.connect([ (inputnode, lta_2_itk, [('t1_2_fsnative_forward_transform', 'in_lta')]), (t1_name, ds_t1_fsnative, [('out', 'source_file')]), (lta_2_itk, ds_t1_fsnative, [('out_itk', 'in_file')]), (inputnode, name_surfs, [('surfaces', 'in_file')]), (inputnode, ds_surfs, [('surfaces', 'in_file')]), (t1_name, ds_surfs, [('out', 'source_file')]), (name_surfs, ds_surfs, [('out_name', 'suffix')]), (inputnode, ds_t1_fsaseg, [('t1_fs_aseg', 'in_file')]), (inputnode, ds_t1_fsparc, [('t1_fs_aparc', 'in_file')]), (t1_name, ds_t1_fsaseg, [('out', 'source_file')]), (t1_name, ds_t1_fsparc, [('out', 'source_file')]), ]) return workflow