def init_3dQwarp_wf(omp_nthreads=1, debug=False, name="pepolar_estimate_wf"): """ Create the PEPOLAR field estimation workflow based on AFNI's ``3dQwarp``. This workflow takes in two EPI files that MUST have opposed :abbr:`PE (phase-encoding)` direction. Therefore, EPIs with orthogonal PE directions are not supported. Workflow Graph .. workflow :: :graph2use: orig :simple_form: yes from sdcflows.workflows.fit.pepolar import init_3dQwarp_wf wf = init_3dQwarp_wf() Parameters ---------- debug : :obj:`bool` Whether a fast configuration of topup (less accurate) should be applied. name : :obj:`str` Name for this workflow omp_nthreads : :obj:`int` Parallelize internal tasks across the number of CPUs given by this option. Inputs ------ in_data : :obj:`list` of :obj:`str` A list of two EPI files, the first of which will be taken as reference. Outputs ------- fmap : :obj:`str` The path of the estimated fieldmap. fmap_ref : :obj:`str` The path of an unwarped conversion of the first element of ``in_data``. """ from nipype.interfaces import afni from niworkflows.interfaces.header import CopyHeader from niworkflows.interfaces.fixes import ( FixHeaderRegistration as Registration, FixHeaderApplyTransforms as ApplyTransforms, ) from niworkflows.interfaces.freesurfer import StructuralReference from niworkflows.func.util import init_enhance_and_skullstrip_bold_wf from ...utils.misc import front as _front, last as _last from ...interfaces.utils import Flatten, ConvertWarp workflow = Workflow(name=name) workflow.__desc__ = f"""{_PEPOLAR_DESC} \ with `3dQwarp` (@afni; AFNI {''.join(['%02d' % v for v in afni.Info().version() or []])}). """ inputnode = pe.Node(niu.IdentityInterface(fields=["in_data", "metadata"]), name="inputnode") outputnode = pe.Node(niu.IdentityInterface(fields=["fmap", "fmap_ref"]), name="outputnode") flatten = pe.Node(Flatten(), name="flatten") sort_pe = pe.Node( niu.Function(function=_sorted_pe, output_names=["sorted", "qwarp_args"]), name="sort_pe", run_without_submitting=True, ) merge_pes = pe.MapNode( StructuralReference( auto_detect_sensitivity=True, initial_timepoint=1, fixed_timepoint=True, # Align to first image intensity_scaling=True, # 7-DOF (rigid + intensity) no_iteration=True, subsample_threshold=200, out_file="template.nii.gz", ), name="merge_pes", iterfield=["in_files"], ) pe0_wf = init_enhance_and_skullstrip_bold_wf(omp_nthreads=omp_nthreads, name="pe0_wf") pe1_wf = init_enhance_and_skullstrip_bold_wf(omp_nthreads=omp_nthreads, name="pe1_wf") align_pes = pe.Node( Registration( from_file=_pkg_fname("sdcflows", "data/translation_rigid.json"), output_warped_image=True, ), name="align_pes", n_procs=omp_nthreads, ) qwarp = pe.Node( afni.QwarpPlusMinus( blur=[-1, -1], environ={"OMP_NUM_THREADS": f"{min(omp_nthreads, 4)}"}, minpatch=9, nopadWARP=True, noweight=True, pblur=[0.05, 0.05], ), name="qwarp", n_procs=min(omp_nthreads, 4), ) to_ants = pe.Node(ConvertWarp(), name="to_ants", mem_gb=0.01) cphdr_warp = pe.Node(CopyHeader(), name="cphdr_warp", mem_gb=0.01) unwarp_reference = pe.Node( ApplyTransforms( dimension=3, float=True, interpolation="LanczosWindowedSinc", ), name="unwarp_reference", ) # fmt: off workflow.connect([ (inputnode, flatten, [("in_data", "in_data"), ("metadata", "in_meta")]), (flatten, sort_pe, [("out_list", "inlist")]), (sort_pe, qwarp, [("qwarp_args", "args")]), (sort_pe, merge_pes, [("sorted", "in_files")]), (merge_pes, pe0_wf, [(("out_file", _front), "inputnode.in_file")]), (merge_pes, pe1_wf, [(("out_file", _last), "inputnode.in_file")]), (pe0_wf, align_pes, [("outputnode.skull_stripped_file", "fixed_image") ]), (pe1_wf, align_pes, [("outputnode.skull_stripped_file", "moving_image") ]), (pe0_wf, qwarp, [("outputnode.skull_stripped_file", "in_file")]), (align_pes, qwarp, [("warped_image", "base_file")]), (inputnode, cphdr_warp, [(("in_data", _front), "hdr_file")]), (qwarp, cphdr_warp, [("source_warp", "in_file")]), (cphdr_warp, to_ants, [("out_file", "in_file")]), (to_ants, unwarp_reference, [("out_file", "transforms")]), (inputnode, unwarp_reference, [("in_reference", "reference_image"), ("in_reference", "input_image")]), (unwarp_reference, outputnode, [("output_image", "fmap_ref")]), (to_ants, outputnode, [("out_file", "fmap")]), ]) # fmt: on return workflow
def init_prepare_epi_wf(omp_nthreads, matched_pe=False, name="prepare_epi_wf"): """ Prepare opposed-PE EPI images for PE-POLAR SDC. This workflow takes in a set of EPI files and returns two 3D volumes with matching and opposed PE directions, ready to be used in field distortion estimation. The procedure involves: estimating a robust template using FreeSurfer's ``mri_robust_template``, bias field correction using ANTs ``N4BiasFieldCorrection`` and AFNI ``3dUnifize``, skullstripping using FSL BET and AFNI ``3dAutomask``, and rigid coregistration to the reference using ANTs. .. workflow :: :graph2use: orig :simple_form: yes from sdcflows.workflows.pepolar import init_prepare_epi_wf wf = init_prepare_epi_wf(omp_nthreads=8) **Parameters**: matched_pe : bool Whether the input ``fmaps_epi`` will contain images with matched PE blips or not. Please use :func:`sdcflows.workflows.pepolar.check_pes` to determine whether they exist or not. name : str Name for this workflow omp_nthreads : int Parallelize internal tasks across the number of CPUs given by this option. **Inputs**: epi_pe : str Phase-encoding direction of the EPI image to be corrected. maps_pe : list of tuple(pathlike, str) list of 3D or 4D NIfTI images ref_brain coregistration reference (skullstripped and bias field corrected) **Outputs**: opposed_pe : pathlike single 3D NIfTI file matched_pe : pathlike single 3D NIfTI file """ inputnode = pe.Node( niu.IdentityInterface(fields=['epi_pe', 'maps_pe', 'ref_brain']), name='inputnode') outputnode = pe.Node( niu.IdentityInterface(fields=['opposed_pe', 'matched_pe']), name='outputnode') ants_settings = pkgr.resource_filename('sdcflows', 'data/translation_rigid.json') split = pe.Node(niu.Function(function=_split_epi_lists), name='split') merge_op = pe.Node( StructuralReference( auto_detect_sensitivity=True, initial_timepoint=1, fixed_timepoint=True, # Align to first image intensity_scaling=True, # 7-DOF (rigid + intensity) no_iteration=True, subsample_threshold=200, out_file='template.nii.gz'), name='merge_op') ref_op_wf = init_enhance_and_skullstrip_bold_wf(omp_nthreads=omp_nthreads, name='ref_op_wf') op2ref_reg = pe.Node(ants.Registration(from_file=ants_settings, output_warped_image=True), name='op2ref_reg', n_procs=omp_nthreads) workflow = Workflow(name=name) workflow.connect([ (inputnode, split, [('maps_pe', 'in_files'), ('epi_pe', 'pe_dir')]), (split, merge_op, [(('out', _front), 'in_files')]), (merge_op, ref_op_wf, [('out_file', 'inputnode.in_file')]), (ref_op_wf, op2ref_reg, [('outputnode.skull_stripped_file', 'moving_image')]), (inputnode, op2ref_reg, [('ref_brain', 'fixed_image')]), (op2ref_reg, outputnode, [('warped_image', 'opposed_pe')]), ]) if not matched_pe: workflow.connect([ (inputnode, outputnode, [('ref_brain', 'matched_pe')]), ]) return workflow merge_ma = pe.Node( StructuralReference( auto_detect_sensitivity=True, initial_timepoint=1, fixed_timepoint=True, # Align to first image intensity_scaling=True, # 7-DOF (rigid + intensity) no_iteration=True, subsample_threshold=200, out_file='template.nii.gz'), name='merge_ma') ref_ma_wf = init_enhance_and_skullstrip_bold_wf(omp_nthreads=omp_nthreads, name='ref_ma_wf') ma2ref_reg = pe.Node(ants.Registration(from_file=ants_settings, output_warped_image=True), name='ma2ref_reg', n_procs=omp_nthreads) workflow.connect([ (split, merge_ma, [(('out', _last), 'in_files')]), (merge_ma, ref_ma_wf, [('out_file', 'inputnode.in_file')]), (ref_ma_wf, ma2ref_reg, [('outputnode.skull_stripped_file', 'moving_image')]), (inputnode, ma2ref_reg, [('ref_brain', 'fixed_image')]), (ma2ref_reg, outputnode, [('warped_image', 'matched_pe')]), ]) return workflow
def init_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_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_prepare_epi_wf(omp_nthreads, name="prepare_epi_wf"): """ This workflow takes in a set of EPI files with with the same phase encoding direction and returns a single 3D volume ready to be used in field distortion estimation. The procedure involves: estimating a robust template using FreeSurfer's 'mri_robust_template', bias field correction using ANTs N4BiasFieldCorrection and AFNI 3dUnifize, skullstripping using FSL BET and AFNI 3dAutomask, and rigid coregistration to the reference using ANTs. .. workflow :: :graph2use: orig :simple_form: yes from fmriprep.workflows.fieldmap.pepolar import init_prepare_epi_wf wf = init_prepare_epi_wf(omp_nthreads=8) Inputs fmaps list of 3D or 4D NIfTI images ref_brain coregistration reference (skullstripped and bias field corrected) Outputs out_file single 3D NIfTI file """ inputnode = pe.Node(niu.IdentityInterface(fields=['fmaps', 'ref_brain']), name='inputnode') outputnode = pe.Node(niu.IdentityInterface(fields=['out_file']), name='outputnode') split = pe.MapNode(fsl.Split(dimension='t'), iterfield='in_file', name='split') merge = pe.Node( StructuralReference(auto_detect_sensitivity=True, initial_timepoint=1, fixed_timepoint=True, # Align to first image intensity_scaling=True, # 7-DOF (rigid + intensity) no_iteration=True, subsample_threshold=200, out_file='template.nii.gz'), name='merge') enhance_and_skullstrip_bold_wf = init_enhance_and_skullstrip_bold_wf( omp_nthreads=omp_nthreads) ants_settings = pkgr.resource_filename('fmriprep', 'data/translation_rigid.json') fmap2ref_reg = pe.Node(ants.Registration(from_file=ants_settings, output_warped_image=True), name='fmap2ref_reg', n_procs=omp_nthreads) workflow = Workflow(name=name) def _flatten(l): from nipype.utils.filemanip import filename_to_list return [item for sublist in l for item in filename_to_list(sublist)] workflow.connect([ (inputnode, split, [('fmaps', 'in_file')]), (split, merge, [(('out_files', _flatten), 'in_files')]), (merge, enhance_and_skullstrip_bold_wf, [('out_file', 'inputnode.in_file')]), (enhance_and_skullstrip_bold_wf, fmap2ref_reg, [ ('outputnode.skull_stripped_file', 'moving_image')]), (inputnode, fmap2ref_reg, [('ref_brain', 'fixed_image')]), (fmap2ref_reg, outputnode, [('warped_image', 'out_file')]), ]) 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