Beispiel #1
0
def func_to_anat(func_filename, anat_filename, tr, write_dir, caching=False):
    """
    The functional volume is aligned to the anatomical, first with a rigid body
    registration and then on a per-slice basis (only a fine correction, this is
    mostly for correction of EPI distortion). This pipeline includes
    slice timing.
    """
    if caching:
        memory = Memory(write_dir)
        tshift = memory.cache(afni.TShift)
        clip_level = memory.cache(afni.ClipLevel)
        threshold = memory.cache(fsl.Threshold)
        volreg = memory.cache(afni.Volreg)
        allineate = memory.cache(afni.Allineate)
        copy_geom = memory.cache(fsl.CopyGeom)
        bias_correct = memory.cache(ants.N4BiasFieldCorrection)
        tstat = memory.cache(afni.TStat)
        rats = memory.cache(RatsMM)
        calc = memory.cache(afni.Calc)
        allineate = memory.cache(afni.Allineate)
        allineate2 = memory.cache(afni.Allineate)
        unifize = memory.cache(afni.Unifize)
        catmatvec = memory.cache(afni.CatMatvec)
        warp = memory.cache(afni.Warp)
        resample = memory.cache(afni.Resample)
        slicer = memory.cache(afni.ZCutUp)
        warp_apply = memory.cache(afni.NwarpApply)
        qwarp = memory.cache(afni.Qwarp)
        merge = memory.cache(fsl.Merge)
    else:
        tshift = afni.TShift().run
        clip_level = afni.ClipLevel().run
        threshold = fsl.Threshold().run
        volreg = afni.Volreg().run
        allineate = afni.Allineate().run
        allineate2 = afni.Allineate().run  # TODO: remove after fixed bug
        copy_geom = fsl.CopyGeom().run
        bias_correct = ants.N4BiasFieldCorrection().run
        tstat = afni.TStat().run
        rats = RatsMM().run
        calc = afni.Calc().run
        allineate = afni.Allineate().run
        unifize = afni.Unifize().run
        catmatvec = afni.CatMatvec().run
        warp = afni.Warp().run
        resample = afni.Resample().run
        slicer = afni.ZCutUp().run
        warp_apply = afni.NwarpApply().run
        qwarp = afni.Qwarp().run
        merge = fsl.Merge().run

    # Correct slice timing
    os.chdir(write_dir)
    out_tshift = tshift(in_file=func_filename,
                        outputtype='NIFTI_GZ',
                        tpattern='altplus',
                        tr=str(tr))
    tshifted_filename = out_tshift.outputs.out_file

    # Register to the first volume
    # XXX why do you need a thresholded image ?
    out_clip_level = clip_level(in_file=tshifted_filename)
    out_threshold = threshold(in_file=tshifted_filename,
                              thresh=out_clip_level.outputs.clip_val)
    thresholded_filename = out_threshold.outputs.out_file

    oned_filename = fname_presuffix(thresholded_filename,
                                    suffix='Vr.1Dfile.1D',
                                    use_ext=False)
    oned_matrix_filename = fname_presuffix(thresholded_filename,
                                           suffix='Vr.aff12.1D',
                                           use_ext=False)
    out_volreg = volreg(
        in_file=thresholded_filename,
        outputtype='NIFTI_GZ',
        oned_file=oned_filename,  # XXX dfile not saved
        oned_matrix_save=oned_matrix_filename)
    # XXX: bad output: up and down on y-axis

    # Apply the registration to the whole head
    allineated_filename = fname_presuffix(tshifted_filename, suffix='Av')
    out_allineate = allineate(in_file=tshifted_filename,
                              master=tshifted_filename,
                              in_matrix=out_volreg.outputs.oned_matrix_save,
                              out_file=allineated_filename)

    # 3dAllineate removes the obliquity. This is not a good way to readd it as
    # removes motion correction info in the header if it were an AFNI file...as
    # it happens it's NIfTI which does not store that so irrelevant!
    out_copy_geom = copy_geom(dest_file=out_allineate.outputs.out_file,
                              in_file=out_volreg.outputs.out_file)
    allineated_filename = out_copy_geom.outputs.out_file

    # XXX: bad output: up and down on y-axis

    # Create a (hopefully) nice mean image for use in the registration
    out_tstat = tstat(in_file=allineated_filename,
                      args='-mean',
                      outputtype='NIFTI_GZ')
    averaged_filename = out_tstat.outputs.out_file

    # Correct the functional average for intensities bias
    out_bias_correct = bias_correct(input_image=averaged_filename, dimension=3)
    unbiased_func_filename = out_bias_correct.outputs.output_image

    # Bias correct the antomical image
    out_unifize = unifize(in_file=anat_filename, outputtype='NIFTI_GZ')
    unbiased_anat_filename = out_unifize.outputs.out_file

    # Mask the mean functional volume outside the brain.
    out_clip_level = clip_level(in_file=unbiased_func_filename)
    # XXX bad: brain mask cut
    out_rats = rats(in_file=unbiased_func_filename,
                    volume_threshold=400,
                    intensity_threshold=int(out_clip_level.outputs.clip_val))
    out_cacl = calc(in_file_a=unbiased_func_filename,
                    in_file_b=out_rats.outputs.out_file,
                    expr='a*b',
                    outputtype='NIFTI_GZ')

    # Compute the transformation from the functional image to the anatomical
    # XXX: why in this sense
    out_allineate = allineate2(
        in_file=out_cacl.outputs.out_file,
        reference=unbiased_anat_filename,
        out_matrix=fname_presuffix(out_cacl.outputs.out_file,
                                   suffix='_shr.aff12.1D',
                                   use_ext=False),
        center_of_mass='',
        warp_type='shift_rotate',
        out_file=fname_presuffix(out_cacl.outputs.out_file, suffix='_shr'))
    rigid_transform_file = out_allineate.outputs.out_matrix

    # apply the inverse transformation to register to the anatomical volume
    catmatvec_out_file = fname_presuffix(rigid_transform_file, suffix='INV')
    if not os.path.isfile(catmatvec_out_file):
        _ = catmatvec(in_file=[(rigid_transform_file, 'I')],
                      oneline=True,
                      out_file=catmatvec_out_file)
        # XXX not cached I don't understand why
    out_allineate = allineate(in_file=unbiased_anat_filename,
                              master=unbiased_func_filename,
                              in_matrix=catmatvec_out_file,
                              out_file=fname_presuffix(
                                  unbiased_anat_filename,
                                  suffix='_shr_in_func_space'))

    # suppanatwarp="$base"_BmBe_shr.aff12.1D

    # Non-linear registration
    # XXX what is the difference between Warp and 3dQwarp?
    out_warp = warp(in_file=out_allineate.outputs.out_file,
                    oblique_parent=unbiased_func_filename,
                    interp='quintic',
                    gridset=unbiased_func_filename,
                    outputtype='NIFTI_GZ',
                    verbose=True)
    registered_anat_filename = out_warp.outputs.out_file
    mat_filename = fname_presuffix(registered_anat_filename,
                                   suffix='_warp.mat',
                                   use_ext=False)
    if not os.path.isfile(mat_filename):
        np.savetxt(mat_filename, [out_warp.runtime.stdout], fmt='%s')

    # 3dWarp doesn't put the obliquity in the header, so do it manually
    # This step generates one file per slice and per time point, so we are
    # making sure they are removed at the end
    registered_anat_oblique_filename = fix_obliquity(registered_anat_filename,
                                                     unbiased_func_filename,
                                                     overwrite=False)

    # Slice anatomical image
    anat_img = nibabel.load(registered_anat_oblique_filename)
    anat_n_slices = anat_img.header.get_data_shape()[2]
    sliced_registered_anat_filenames = []
    for slice_n in range(anat_n_slices):
        out_slicer = slicer(in_file=registered_anat_oblique_filename,
                            keep='{0} {1}'.format(slice_n, slice_n),
                            out_file=fname_presuffix(
                                registered_anat_oblique_filename,
                                suffix='Sl%d' % slice_n))
        sliced_registered_anat_filenames.append(out_slicer.outputs.out_file)

    # Slice mean functional
    sliced_bias_corrected_filenames = []
    img = nibabel.load(func_filename)
    n_slices = img.header.get_data_shape()[2]
    for slice_n in range(n_slices):
        out_slicer = slicer(in_file=unbiased_func_filename,
                            keep='{0} {1}'.format(slice_n, slice_n),
                            out_file=fname_presuffix(unbiased_func_filename,
                                                     suffix='Sl%d' % slice_n))
        sliced_bias_corrected_filenames.append(out_slicer.outputs.out_file)

    # Below line is to deal with slices where there is no signal (for example
    # rostral end of some anatomicals)

    # The inverse warp frequently fails, Resampling can help it work better
    voxel_size_z = anat_img.header.get_zooms()[2]
    resampled_registered_anat_filenames = []
    for sliced_registered_anat_filename in sliced_registered_anat_filenames:
        out_resample = resample(in_file=sliced_registered_anat_filename,
                                voxel_size=(.1, .1, voxel_size_z),
                                outputtype='NIFTI_GZ')
        resampled_registered_anat_filenames.append(
            out_resample.outputs.out_file)

    resampled_bias_corrected_filenames = []
    for sliced_bias_corrected_filename in sliced_bias_corrected_filenames:
        out_resample = resample(in_file=sliced_bias_corrected_filename,
                                voxel_size=(.1, .1, voxel_size_z),
                                outputtype='NIFTI_GZ')
        resampled_bias_corrected_filenames.append(
            out_resample.outputs.out_file)

    # single slice non-linear functional to anatomical registration
    warped_slices = []
    warp_filenames = []
    for (resampled_bias_corrected_filename,
         resampled_registered_anat_filename) in zip(
             resampled_bias_corrected_filenames,
             resampled_registered_anat_filenames):
        warped_slice = fname_presuffix(resampled_bias_corrected_filename,
                                       suffix='_qw')
        out_qwarp = qwarp(in_file=resampled_bias_corrected_filename,
                          base_file=resampled_registered_anat_filename,
                          iwarp=True,
                          noneg=True,
                          blur=[0],
                          nmi=True,
                          noXdis=True,
                          allineate=True,
                          allineate_opts='-parfix 1 0 -parfix 2 0 -parfix 3 0 '
                          '-parfix 4 0 -parfix 5 0 -parfix 6 0 '
                          '-parfix 7 0 -parfix 9 0 '
                          '-parfix 10 0 -parfix 12 0',
                          out_file=warped_slice)
        warped_slices.append(out_qwarp.outputs.warped_source)
        warp_filenames.append(out_qwarp.outputs.source_warp)

    # Resample the mean volume back to the initial resolution,
    voxel_size = nibabel.load(
        sliced_bias_corrected_filename).header.get_zooms()
    resampled_warped_slices = []
    for warped_slice in warped_slices:
        out_resample = resample(in_file=warped_slice,
                                voxel_size=voxel_size + (voxel_size[0], ),
                                outputtype='NIFTI_GZ')
        resampled_warped_slices.append(out_resample.outputs.out_file)

    # fix the obliquity
    for (resampled_registered_anat_filename,
         resampled_warped_slice) in zip(resampled_registered_anat_filenames,
                                        resampled_warped_slices):
        _ = fix_obliquity(resampled_warped_slice,
                          resampled_registered_anat_filename)

    # Merge all slices !
#    out_merge_mean = merge(in_files=resampled_warped_slices, dimension='z')

# slice functional
    sliced_func_filenames = []
    for slice_n in range(n_slices):
        out_slicer = slicer(in_file=allineated_filename,
                            keep='{0} {1}'.format(slice_n, slice_n),
                            out_file=fname_presuffix(allineated_filename,
                                                     suffix='Sl%d' % slice_n))
        sliced_func_filenames.append(out_slicer.outputs.out_file)

    # resample functional slices
    resampled_func_filenames = []
    for sliced_func_filename in sliced_func_filenames:
        out_resample = resample(in_file=sliced_func_filename,
                                voxel_size=(.1, .1, voxel_size_z),
                                outputtype='NIFTI_GZ')
        resampled_func_filenames.append(out_resample.outputs.out_file)

    # Apply the precomputed warp slice by slice
    warped_func_slices = []
    for (resampled_func_filename,
         warp_filename) in zip(resampled_func_filenames, warp_filenames):
        out_warp_apply = warp_apply(in_file=resampled_func_filename,
                                    warp=warp_filename,
                                    out_file=fname_presuffix(
                                        resampled_func_filename, suffix='_qw'))
        warped_func_slices.append(out_warp_apply.outputs.out_file)

    # Fix the obliquity
    # XXX why no resampling back before ?
    for (resampled_registered_anat_filename,
         warped_func_slice) in zip(resampled_registered_anat_filenames,
                                   warped_func_slices):
        _ = fix_obliquity(warped_func_slice,
                          resampled_registered_anat_filename)

    # Finally, merge all slices !
    out_merge_func = merge(in_files=warped_func_slices, dimension='z')
    out_merge_anat = merge(in_files=resampled_registered_anat_filenames,
                           dimension='z')
    return (out_merge_func.outputs.merged_file,
            out_merge_anat.outputs.merged_file)
Beispiel #2
0
            newpath=sammba_dir,
            prefix='bil2_transfo',
            suffix='_unifized_affine_general_warped_WARP')
        sammba_registered_atlas_file = fname_presuffix(atlas_id,
                                                       suffix='_allineated',
                                                       newpath=sammba_dir)
        allineate = afni.Allineate().run
        out_allineate = allineate(in_file=sammba_mouse_atlas_file,
                                  master=sammba_template_file,
                                  in_matrix=matrix_file,
                                  out_file=sammba_registered_atlas_file,
                                  final_interpolation='nearestneighbour',
                                  environ={'AFNI_DECONFLICT': 'OVERWRITE'})

        if os.path.isfile(warp_file):
            nwarp_apply = afni.NwarpApply().run
            transforms = [warp_file, matrix_file]
            warp = "'"
            warp += ' '.join(transforms)
            warp += "'"
            out_warp_apply = nwarp_apply(in_file=sammba_mouse_atlas_file,
                                         master=sammba_template_file,
                                         warp=warp,
                                         interp='nearestneighbor',
                                         out_file=fname_presuffix(
                                             sammba_registered_atlas_file,
                                             suffix='_warped'))
        spm_atlas_id = atlas_id.replace('_corrected', '')
        if mouse_id == '_C57_ab2_invivo_corrected.nii.gz':
            spm_atlas_id = '_Ab2_invivo.nii.gz'
        elif mouse_id == '_C57_y81_Invivo_corrected.nii.gz':
Beispiel #3
0
def _func_to_template(func_coreg_filename,
                      template_filename,
                      write_dir,
                      func_to_anat_oned_filename,
                      anat_to_template_oned_filename,
                      anat_to_template_warp_filename,
                      voxel_size=None,
                      caching=False,
                      verbose=True):
    """ Applies successive transforms to coregistered functional to put it in
    template space.

    Parameters
    ----------
    coreg_func_filename : str
        Path to functional volume, coregistered to a common space with the
        anatomical volume.

    template_filename : str
        Template to register the functional to.

    func_to_anat_oned_filename : str
        Path to the affine 1D transform from functional to coregistration
        space.

    anat_to_template_oned_filename : str
        Path to the affine 1D transform from anatomical to template space.

    anat_to_template_warp_filename : str
        Path to the warp transform from anatomical to template space.

    voxel_size : 3-tuple of floats, optional
        Voxel size of the registered functional, in mm.

    caching : bool, optional
        Wether or not to use caching.

    verbose : bool, optional
        If True, all steps are verbose. Note that caching implies some
        verbosity in any case.
    """
    environ = {}
    if verbose:
        terminal_output = 'allatonce'
    else:
        terminal_output = 'none'

    if caching:
        memory = Memory(write_dir)
        resample = memory.cache(afni.Resample)
        catmatvec = memory.cache(afni.CatMatvec)
        allineate = memory.cache(afni.Allineate)
        warp_apply = memory.cache(afni.NwarpApply)
        for step in [resample, allineate, warp_apply]:
            step.interface().set_default_terminal_output(terminal_output)
    else:
        resample = afni.Resample(terminal_output=terminal_output).run
        catmatvec = afni.CatMatvec().run
        allineate = afni.Allineate(terminal_output=terminal_output).run
        warp_apply = afni.NwarpApply(terminal_output=terminal_output).run
        environ['AFNI_DECONFLICT'] = 'OVERWRITE'

    current_dir = os.getcwd()
    os.chdir(write_dir)  # XXX to remove
    normalized_filename = fname_presuffix(func_coreg_filename,
                                          suffix='_normalized')
    if voxel_size is None:
        func_template_filename = template_filename
    else:
        out_resample = resample(in_file=template_filename,
                                voxel_size=voxel_size,
                                outputtype='NIFTI_GZ',
                                environ=environ)
        func_template_filename = out_resample.outputs.out_file

    if anat_to_template_warp_filename is None:
        affine_transform_filename = fname_presuffix(func_to_anat_oned_filename,
                                                    suffix='_to_template')
        _ = catmatvec(in_file=[(anat_to_template_oned_filename, 'ONELINE'),
                               (func_to_anat_oned_filename, 'ONELINE')],
                      oneline=True,
                      out_file=affine_transform_filename,
                      environ=environ)
        _ = allineate(in_file=func_coreg_filename,
                      master=func_template_filename,
                      in_matrix=affine_transform_filename,
                      out_file=normalized_filename,
                      environ=environ)
    else:
        warp = "'{0} {1} {2}'".format(anat_to_template_warp_filename,
                                      anat_to_template_oned_filename,
                                      func_to_anat_oned_filename)

        _ = warp_apply(in_file=func_coreg_filename,
                       master=func_template_filename,
                       warp=warp,
                       out_file=normalized_filename,
                       environ=environ)
    os.chdir(current_dir)
    return normalized_filename
Beispiel #4
0
def coregister_fmri_session(session_data,
                            t_r,
                            write_dir,
                            brain_volume,
                            use_rats_tool=True,
                            slice_timing=True,
                            prior_rigid_body_registration=False,
                            caching=False,
                            voxel_size_x=.1,
                            voxel_size_y=.1,
                            verbose=True,
                            **environ_kwargs):
    """
    Coregistration of the subject's functional and anatomical images.
    The functional volume is aligned to the anatomical, first with a rigid body
    registration and then on a per-slice basis (only a fine correction, this is
    mostly for correction of EPI distortion).


    Parameters
    ----------
    session_data : sammba.registration.SessionData
        Single animal data, giving paths to its functional and anatomical
        image, as well as it identifier.

    t_r : float
        Repetition time for the EPI, in seconds.

    write_dir : str
        Directory to save the output and temporary images.

    brain_volume : int
        Volume of the brain in mm3 used for brain extraction.
        Typically 400 for mouse and 1800 for rat.

    use_rats_tool : bool, optional
        If True, brain mask is computed using RATS Mathematical Morphology.
        Otherwise, a histogram-based brain segmentation is used.

    prior_rigid_body_registration : bool, optional
        If True, a rigid-body registration of the anat to the func is performed
        prior to the warp. Useful if the images headers have missing/wrong
        information.

    voxel_size_x : float, optional
        Resampling resolution for the x-axis, in mm.

    voxel_size_y : float, optional
        Resampling resolution for the y-axis, in mm.

    caching : bool, optional
        Wether or not to use caching.

    verbose : bool, optional
        If True, all steps are verbose. Note that caching implies some
        verbosity in any case.

    environ_kwargs : extra arguments keywords
        Extra arguments keywords, passed to interfaces environ variable.

    Returns
    -------
    the same sequence with each animal_data updated: the following attributes
    are added
        - `output_dir_` : str
                          Path to the output directory.
        - `coreg_func_` : str
                          Path to paths to the coregistered functional image.
        - `coreg_anat_` : str
                          Path to paths to the coregistered functional image.
        - `coreg_transform_` : str
                               Path to the transform from anat to func.

    Notes
    -----
    If `use_rats_tool` is turned on, RATS tool is used for brain extraction
    and has to be cited. For more information, see
    `RATS <http://www.iibi.uiowa.edu/content/rats-overview/>`_
    """
    func_filename = session_data.func
    anat_filename = session_data.anat

    environ = {'AFNI_DECONFLICT': 'OVERWRITE'}
    for (key, value) in environ_kwargs.items():
        environ[key] = value

    if verbose:
        terminal_output = 'allatonce'
    else:
        terminal_output = 'none'

    if use_rats_tool:
        if segmentation.interfaces.Info().version() is None:
            raise ValueError('Can not locate RATS')
        else:
            ComputeMask = segmentation.MathMorphoMask
    else:
        ComputeMask = segmentation.HistogramMask

    if ants.base.Info().version is None:
        raise ValueError('Can not locate ANTS')

    if caching:
        memory = Memory(write_dir)
        tshift = memory.cache(afni.TShift)
        clip_level = memory.cache(afni.ClipLevel)
        volreg = memory.cache(afni.Volreg)
        allineate = memory.cache(afni.Allineate)
        tstat = memory.cache(afni.TStat)
        compute_mask = memory.cache(ComputeMask)
        calc = memory.cache(afni.Calc)
        allineate = memory.cache(afni.Allineate)
        allineate2 = memory.cache(afni.Allineate)
        unifize = memory.cache(afni.Unifize)
        bias_correct = memory.cache(ants.N4BiasFieldCorrection)
        catmatvec = memory.cache(afni.CatMatvec)
        warp = memory.cache(afni.Warp)
        resample = memory.cache(afni.Resample)
        slicer = memory.cache(afni.ZCutUp)
        warp_apply = memory.cache(afni.NwarpApply)
        qwarp = memory.cache(afni.Qwarp)
        merge = memory.cache(afni.Zcat)
        copy_geom = memory.cache(fsl.CopyGeom)
        overwrite = False
        for step in [
                tshift, volreg, allineate, allineate2, tstat, compute_mask,
                calc, unifize, resample, slicer, warp_apply, qwarp, merge
        ]:
            step.interface().set_default_terminal_output(terminal_output)
    else:
        tshift = afni.TShift(terminal_output=terminal_output).run
        clip_level = afni.ClipLevel().run
        volreg = afni.Volreg(terminal_output=terminal_output).run
        allineate = afni.Allineate(terminal_output=terminal_output).run
        allineate2 = afni.Allineate(terminal_output=terminal_output
                                    ).run  # TODO: remove after fixed bug
        tstat = afni.TStat(terminal_output=terminal_output).run
        compute_mask = ComputeMask(terminal_output=terminal_output).run
        calc = afni.Calc(terminal_output=terminal_output).run
        unifize = afni.Unifize(terminal_output=terminal_output).run
        bias_correct = ants.N4BiasFieldCorrection(
            terminal_output=terminal_output).run
        catmatvec = afni.CatMatvec().run
        warp = afni.Warp().run
        resample = afni.Resample(terminal_output=terminal_output).run
        slicer = afni.ZCutUp(terminal_output=terminal_output).run
        warp_apply = afni.NwarpApply(terminal_output=terminal_output).run
        qwarp = afni.Qwarp(terminal_output=terminal_output).run
        merge = afni.Zcat(terminal_output=terminal_output).run
        copy_geom = fsl.CopyGeom(terminal_output=terminal_output).run
        overwrite = True

    session_data._check_inputs()
    output_dir = os.path.join(os.path.abspath(write_dir),
                              session_data.animal_id)
    session_data._set_output_dir_(output_dir)
    current_dir = os.getcwd()
    os.chdir(output_dir)
    output_files = []

    #######################################
    # Correct functional for slice timing #
    #######################################
    if slice_timing:
        out_tshift = tshift(in_file=func_filename,
                            outputtype='NIFTI_GZ',
                            tpattern='altplus',
                            tr=str(t_r),
                            environ=environ)
        func_filename = out_tshift.outputs.out_file
        output_files.append(func_filename)

    ################################################
    # Register functional volumes to the first one #
    ################################################
    # XXX why do you need a thresholded image ?
    out_clip_level = clip_level(in_file=func_filename)
    out_calc_threshold = calc(in_file_a=func_filename,
                              expr='ispositive(a-{0}) * a'.format(
                                  out_clip_level.outputs.clip_val),
                              outputtype='NIFTI_GZ')
    thresholded_filename = out_calc_threshold.outputs.out_file

    out_volreg = volreg(  # XXX dfile not saved
        in_file=thresholded_filename,
        outputtype='NIFTI_GZ',
        environ=environ,
        oned_file=fname_presuffix(thresholded_filename,
                                  suffix='Vr.1Dfile.1D',
                                  use_ext=False),
        oned_matrix_save=fname_presuffix(thresholded_filename,
                                         suffix='Vr.aff12.1D',
                                         use_ext=False))

    # Apply the registration to the whole head
    out_allineate = allineate(in_file=func_filename,
                              master=func_filename,
                              in_matrix=out_volreg.outputs.oned_matrix_save,
                              out_file=fname_presuffix(func_filename,
                                                       suffix='Av'),
                              environ=environ)

    # 3dAllineate removes the obliquity. This is not a good way to readd it as
    # removes motion correction info in the header if it were an AFNI file...as
    # it happens it's NIfTI which does not store that so irrelevant!
    out_copy_geom = copy_geom(dest_file=out_allineate.outputs.out_file,
                              in_file=out_volreg.outputs.out_file)

    allineated_filename = out_copy_geom.outputs.out_file

    # Create a (hopefully) nice mean image for use in the registration
    out_tstat = tstat(in_file=allineated_filename,
                      args='-mean',
                      outputtype='NIFTI_GZ',
                      environ=environ)

    # Update outputs
    output_files.extend([
        thresholded_filename, out_volreg.outputs.oned_matrix_save,
        out_volreg.outputs.out_file, out_volreg.outputs.md1d_file,
        allineated_filename, out_tstat.outputs.out_file
    ])

    ###########################################
    # Corret anat and func for intensity bias #
    ###########################################
    # Correct the functional average for intensities bias
    out_bias_correct = bias_correct(input_image=out_tstat.outputs.out_file)
    unbiased_func_filename = out_bias_correct.outputs.output_image

    # Bias correct the antomical image
    out_unifize = unifize(in_file=anat_filename,
                          outputtype='NIFTI_GZ',
                          environ=environ)
    unbiased_anat_filename = out_unifize.outputs.out_file

    # Update outputs
    output_files.extend([unbiased_func_filename, unbiased_anat_filename])

    #############################################
    # Rigid-body registration anat -> mean func #
    #############################################
    if prior_rigid_body_registration:
        # Mask the mean functional volume outside the brain.
        out_clip_level = clip_level(in_file=unbiased_func_filename)
        out_compute_mask_func = compute_mask(
            in_file=unbiased_func_filename,
            volume_threshold=brain_volume,
            intensity_threshold=int(out_clip_level.outputs.clip_val))
        out_cacl_func = calc(in_file_a=unbiased_func_filename,
                             in_file_b=out_compute_mask_func.outputs.out_file,
                             expr='a*b',
                             outputtype='NIFTI_GZ',
                             environ=environ)

        # Mask the anatomical volume outside the brain.
        out_clip_level = clip_level(in_file=unbiased_anat_filename)
        out_compute_mask_anat = compute_mask(
            in_file=unbiased_anat_filename,
            volume_threshold=brain_volume,
            intensity_threshold=int(out_clip_level.outputs.clip_val))
        out_cacl_anat = calc(in_file_a=unbiased_anat_filename,
                             in_file_b=out_compute_mask_anat.outputs.out_file,
                             expr='a*b',
                             outputtype='NIFTI_GZ',
                             environ=environ)

        # Compute the transformation from functional to anatomical brain
        # XXX: why in this sense
        out_allineate = allineate2(
            in_file=out_cacl_func.outputs.out_file,
            reference=out_cacl_anat.outputs.out_file,
            out_matrix=fname_presuffix(out_cacl_func.outputs.out_file,
                                       suffix='_shr.aff12.1D',
                                       use_ext=False),
            center_of_mass='',
            warp_type='shift_rotate',
            out_file=fname_presuffix(out_cacl_func.outputs.out_file,
                                     suffix='_shr'),
            environ=environ)
        rigid_transform_file = out_allineate.outputs.out_matrix
        output_files.extend([
            out_compute_mask_func.outputs.out_file,
            out_cacl_func.outputs.out_file,
            out_compute_mask_anat.outputs.out_file,
            out_cacl_anat.outputs.out_file, rigid_transform_file,
            out_allineate.outputs.out_file
        ])

        # apply the inverse transform to register the anatomical to the func
        catmatvec_out_file = fname_presuffix(rigid_transform_file,
                                             suffix='INV')
        out_catmatvec = catmatvec(in_file=[(rigid_transform_file, 'I')],
                                  oneline=True,
                                  out_file=catmatvec_out_file)
        output_files.append(out_catmatvec.outputs.out_file)
        out_allineate = allineate(in_file=unbiased_anat_filename,
                                  master=unbiased_func_filename,
                                  in_matrix=out_catmatvec.outputs.out_file,
                                  out_file=fname_presuffix(
                                      unbiased_anat_filename,
                                      suffix='_shr_in_func_space'),
                                  environ=environ)
        allineated_anat_filename = out_allineate.outputs.out_file
        output_files.append(allineated_anat_filename)
    else:
        allineated_anat_filename = unbiased_anat_filename

    ############################################
    # Nonlinear registration anat -> mean func #
    ############################################
    # 3dWarp doesn't put the obliquity in the header, so do it manually
    # This step generates one file per slice and per time point, so we are
    # making sure they are removed at the end
    out_warp = warp(in_file=allineated_anat_filename,
                    oblique_parent=unbiased_func_filename,
                    interp='quintic',
                    gridset=unbiased_func_filename,
                    outputtype='NIFTI_GZ',
                    verbose=True,
                    environ=environ)
    registered_anat_filename = out_warp.outputs.out_file
    registered_anat_oblique_filename = fix_obliquity(registered_anat_filename,
                                                     unbiased_func_filename,
                                                     verbose=verbose)

    # Concatenate all the anat to func tranforms
    mat_filename = fname_presuffix(registered_anat_filename,
                                   suffix='_warp.mat',
                                   use_ext=False)
    # XXX Handle this correctly according to caching
    if not os.path.isfile(mat_filename):
        np.savetxt(mat_filename, [out_warp.runtime.stdout], fmt='%s')
        output_files.append(mat_filename)

    transform_filename = fname_presuffix(registered_anat_filename,
                                         suffix='_anat_to_func.aff12.1D',
                                         use_ext=False)
    if prior_rigid_body_registration:
        _ = catmatvec(in_file=[(mat_filename, 'ONELINE'),
                               (rigid_transform_file, 'ONELINE')],
                      oneline=True,
                      out_file=transform_filename)
    else:
        _ = catmatvec(in_file=[(mat_filename, 'ONELINE')],
                      oneline=True,
                      out_file=transform_filename)

    ##################################################
    # Per-slice non-linear registration func -> anat #
    ##################################################
    # Slice anatomical image
    anat_img = nibabel.load(registered_anat_oblique_filename)
    anat_n_slices = anat_img.header.get_data_shape()[2]
    sliced_registered_anat_filenames = []
    for slice_n in range(anat_n_slices):
        out_slicer = slicer(in_file=registered_anat_oblique_filename,
                            keep='{0} {0}'.format(slice_n),
                            out_file=fname_presuffix(
                                registered_anat_oblique_filename,
                                suffix='Sl%d' % slice_n),
                            environ=environ)
        oblique_slice = fix_obliquity(out_slicer.outputs.out_file,
                                      registered_anat_oblique_filename,
                                      verbose=verbose)
        sliced_registered_anat_filenames.append(oblique_slice)

    # Slice mean functional
    sliced_bias_corrected_filenames = []
    img = nibabel.load(func_filename)
    n_slices = img.header.get_data_shape()[2]
    for slice_n in range(n_slices):
        out_slicer = slicer(in_file=unbiased_func_filename,
                            keep='{0} {0}'.format(slice_n),
                            out_file=fname_presuffix(unbiased_func_filename,
                                                     suffix='Sl%d' % slice_n),
                            environ=environ)
        oblique_slice = fix_obliquity(out_slicer.outputs.out_file,
                                      unbiased_func_filename,
                                      verbose=verbose)
        sliced_bias_corrected_filenames.append(oblique_slice)

    # Below line is to deal with slices where there is no signal (for example
    # rostral end of some anatomicals)

    # The inverse warp frequently fails, Resampling can help it work better
    # XXX why specifically .1 in voxel_size ?
    voxel_size_z = anat_img.header.get_zooms()[2]
    resampled_registered_anat_filenames = []
    for sliced_registered_anat_filename in sliced_registered_anat_filenames:
        out_resample = resample(in_file=sliced_registered_anat_filename,
                                voxel_size=(voxel_size_x, voxel_size_y,
                                            voxel_size_z),
                                outputtype='NIFTI_GZ',
                                environ=environ)
        resampled_registered_anat_filenames.append(
            out_resample.outputs.out_file)

    resampled_bias_corrected_filenames = []
    for sliced_bias_corrected_filename in sliced_bias_corrected_filenames:
        out_resample = resample(in_file=sliced_bias_corrected_filename,
                                voxel_size=(voxel_size_x, voxel_size_y,
                                            voxel_size_z),
                                outputtype='NIFTI_GZ',
                                environ=environ)
        resampled_bias_corrected_filenames.append(
            out_resample.outputs.out_file)

    # single slice non-linear functional to anatomical registration
    warped_slices = []
    warp_filenames = []
    for (resampled_bias_corrected_filename,
         resampled_registered_anat_filename) in zip(
             resampled_bias_corrected_filenames,
             resampled_registered_anat_filenames):
        warped_slice = fname_presuffix(resampled_bias_corrected_filename,
                                       suffix='_qw')
        out_qwarp = qwarp(
            in_file=resampled_bias_corrected_filename,
            base_file=resampled_registered_anat_filename,
            iwarp=True,  # XXX: is this necessary
            noneg=True,
            blur=[0],
            nmi=True,
            noXdis=True,
            allineate=True,
            allineate_opts='-parfix 1 0 -parfix 2 0 -parfix 3 0 '
            '-parfix 4 0 -parfix 5 0 -parfix 6 0 '
            '-parfix 7 0 -parfix 9 0 '
            '-parfix 10 0 -parfix 12 0',
            out_file=warped_slice,
            environ=environ)
        warped_slices.append(out_qwarp.outputs.warped_source)
        warp_filenames.append(out_qwarp.outputs.source_warp)
        output_files.append(out_qwarp.outputs.base_warp)
        # There are files geenrated by the allineate option
        output_files.extend([
            fname_presuffix(out_qwarp.outputs.warped_source, suffix='_Allin'),
            fname_presuffix(out_qwarp.outputs.warped_source,
                            suffix='_Allin.nii',
                            use_ext=False),
            fname_presuffix(out_qwarp.outputs.warped_source,
                            suffix='_Allin.aff12.1D',
                            use_ext=False)
        ])

    # Resample the mean volume back to the initial resolution,
    voxel_size = nibabel.load(unbiased_func_filename).header.get_zooms()
    resampled_warped_slices = []
    for warped_slice in warped_slices:
        out_resample = resample(in_file=warped_slice,
                                voxel_size=voxel_size,
                                outputtype='NIFTI_GZ',
                                environ=environ)
        resampled_warped_slices.append(out_resample.outputs.out_file)

    # fix the obliquity
    resampled_warped_slices_oblique = []
    for (sliced_registered_anat_filename,
         resampled_warped_slice) in zip(sliced_registered_anat_filenames,
                                        resampled_warped_slices):
        oblique_slice = fix_obliquity(resampled_warped_slice,
                                      sliced_registered_anat_filename,
                                      verbose=verbose)
        resampled_warped_slices_oblique.append(oblique_slice)

    # slice functional
    sliced_func_filenames = []
    for slice_n in range(n_slices):
        out_slicer = slicer(in_file=allineated_filename,
                            keep='{0} {0}'.format(slice_n),
                            out_file=fname_presuffix(allineated_filename,
                                                     suffix='Sl%d' % slice_n),
                            environ=environ)
        oblique_slice = fix_obliquity(out_slicer.outputs.out_file,
                                      allineated_filename,
                                      verbose=verbose)
        sliced_func_filenames.append(oblique_slice)

    # Apply the precomputed warp slice by slice
    warped_func_slices = []
    for (sliced_func_filename, warp_filename) in zip(sliced_func_filenames,
                                                     warp_filenames):
        out_warp_apply = warp_apply(in_file=sliced_func_filename,
                                    master=sliced_func_filename,
                                    warp=warp_filename,
                                    out_file=fname_presuffix(
                                        sliced_func_filename, suffix='_qw'),
                                    environ=environ)
        warped_func_slices.append(out_warp_apply.outputs.out_file)

    # Finally, merge all slices !
    out_merge_func = merge(in_files=warped_func_slices,
                           outputtype='NIFTI_GZ',
                           environ=environ)

    # Fix the obliquity
    merged_oblique = fix_obliquity(out_merge_func.outputs.out_file,
                                   allineated_filename,
                                   verbose=verbose)

    # Update the fmri data
    setattr(session_data, "coreg_func_", merged_oblique)
    setattr(session_data, "coreg_anat_", registered_anat_oblique_filename)
    setattr(session_data, "coreg_transform_", transform_filename)
    os.chdir(current_dir)

    # Collect the outputs
    output_files.extend(sliced_registered_anat_filenames +
                        sliced_bias_corrected_filenames +
                        resampled_registered_anat_filenames +
                        resampled_bias_corrected_filenames + warped_slices +
                        warp_filenames + resampled_warped_slices_oblique +
                        sliced_func_filenames + warped_func_slices)
    if not caching:
        for out_file in output_files:
            if os.path.isfile(out_file):
                os.remove(out_file)
Beispiel #5
0
def _func_to_template(func_coreg_filename,
                      template_filename,
                      write_dir,
                      func_to_anat_oned_filename,
                      anat_to_template_oned_filename,
                      anat_to_template_warp_filename,
                      voxel_size=None,
                      caching=False,
                      verbose=True):
    """ Applies successive transforms to coregistered functional to put it in
    template space.

    Parameters
    ----------
    coreg_func_filename : str
        Path to functional volume, coregistered to a common space with the
        anatomical volume.

    template_filename : str
        Template to register the functional to.

    func_to_anat_oned_filename : str
        Path to the affine 1D transform from functional to coregistration
        space.

    anat_to_template_oned_filename : str
        Path to the affine 1D transform from anatomical to template space.

    anat_to_template_warp_filename : str
        Path to the warp transform from anatomical to template space.

    voxel_size : 3-tuple of floats, optional
        Voxel size of the registered functional, in mm.

    caching : bool, optional
        Wether or not to use caching.

    verbose : bool, optional
        If True, all steps are verbose. Note that caching implies some
        verbosity in any case.
    """
    if verbose:
        terminal_output = 'allatonce'
    else:
        terminal_output = 'none'

    if caching:
        memory = Memory(write_dir)
        warp_apply = memory.cache(afni.NwarpApply)
        resample = memory.cache(afni.Resample)
        warp_apply.interface().set_default_terminal_output(terminal_output)
        resample.interface().set_default_terminal_output(terminal_output)
    else:
        warp_apply = afni.NwarpApply(terminal_output=terminal_output).run
        resample = afni.Resample(terminal_output=terminal_output).run

    current_dir = os.getcwd()
    os.chdir(write_dir)  # XXX to remove
    normalized_filename = fname_presuffix(func_coreg_filename,
                                          suffix='_normalized')
    if voxel_size is None:
        func_template_filename = template_filename
    else:
        out_resample = resample(in_file=template_filename,
                                voxel_size=voxel_size,
                                outputtype='NIFTI_GZ')
        func_template_filename = out_resample.outputs.out_file

    warp = "'{0} {1} {2}'".format(anat_to_template_warp_filename,
                                  anat_to_template_oned_filename,
                                  func_to_anat_oned_filename)

    _ = warp_apply(in_file=func_coreg_filename,
                   master=func_template_filename,
                   warp=warp,
                   out_file=normalized_filename)
    os.chdir(current_dir)
    return normalized_filename
Beispiel #6
0
def anats_to_common(t1_filenames, write_dir, brain_volume=400,
                    registration_kind='affine',
                    nonlinear_levels=[1, 2, 3],
                    nonlinear_minimal_patch=75,
                    convergence=0.005, caching=False, verbose=0):
    """ Create common template from native T1 weighted images and achieve
    their registration to it.

    Parameters
    ----------
    t1_filenames : list of str
        Paths to the T1 weighted images.

    write_dir : str
        Path to an existant directory to save output files to.

    brain_volume : float, optional
        Volumes of the brain as passed to Rats_MM brain extraction tool.
        Default to 400 for the mouse brain.

    registration_kind : one of {'rigid', 'affine', 'nonlinear'}, optional
        The allowed transform kind.

    nonlinear_levels : list of int, optional
        Maximal levels for each nonlinear warping iteration. Passed iteratively
        to sammba.externals.nipype.interfaces.afni.Qwarp

    nonlinear_minimal_patch : int, optional
        Minimal patch for the final nonlinear warp, passed to
        sammba.externals.nipype.interfaces.afni.Qwarp

    caching : bool, optional
        If True, caching is used for all the registration steps.

    convergence : float, optional
        Convergence limit, passed to

    verbose : bool, optional
        If True, all steps are verbose.

    Returns
    -------
    data : sklearn.datasets.base.Bunch
        Dictionary-like object, the interest attributes are :

        - 'registered' : list of str.
                         Paths to registered images. Note that
                         they have undergone a bias correction step before.
        - 'transforms' : list of str.
                         Paths to the transforms from the raw
                         images to the registered images.
    """
    registration_kinds = ['rigid', 'affine', 'nonlinear']
    if registration_kind not in registration_kinds:
        raise ValueError(
            'Registration kind must be one of {0}, you entered {1}'.format(
                registration_kinds, registration_kind))

    if verbose:
        terminal_output = 'allatonce'
    else:
        terminal_output = 'none'

    if caching:
        memory = Memory(write_dir)
        copy = memory.cache(afni.Copy)
        unifize = memory.cache(afni.Unifize)
        clip_level = memory.cache(afni.ClipLevel)
        rats = memory.cache(RatsMM)
        apply_mask = memory.cache(fsl.ApplyMask)
        center_mass = memory.cache(afni.CenterMass)
        refit = memory.cache(afni.Refit)
        tcat = memory.cache(afni.TCat)
        tstat = memory.cache(afni.TStat)
        undump = memory.cache(afni.Undump)
        resample = memory.cache(afni.Resample)
        allineate = memory.cache(afni.Allineate)
        allineate2 = memory.cache(afni.Allineate)
        mask_tool = memory.cache(afni.MaskTool)
        catmatvec = memory.cache(afni.CatMatvec)
        qwarp = memory.cache(afni.Qwarp)
        nwarp_cat = memory.cache(afni.NwarpCat)
        warp_apply = memory.cache(afni.NwarpApply)
        for func in [copy, unifize, rats, apply_mask, refit,
                     tcat, tstat, undump, resample, allineate, allineate2,
                     mask_tool, catmatvec, qwarp, nwarp_cat, warp_apply]:
            func.interface().set_default_terminal_output(terminal_output)
    else:
        copy = afni.Copy(terminal_output=terminal_output).run
        unifize = afni.Unifize(terminal_output=terminal_output).run
        clip_level = afni.ClipLevel().run  # XXX fix nipype bug with 'none'
        rats = RatsMM(terminal_output=terminal_output).run
        apply_mask = fsl.ApplyMask(terminal_output=terminal_output).run
        center_mass = afni.CenterMass().run  # XXX fix nipype bug with 'none'
        refit = afni.Refit(terminal_output=terminal_output).run
        tcat = afni.TCat(terminal_output=terminal_output).run
        tstat = afni.TStat(terminal_output=terminal_output).run
        undump = afni.Undump(terminal_output=terminal_output).run
        resample = afni.Resample(terminal_output=terminal_output).run
        allineate = afni.Allineate(terminal_output=terminal_output).run
        allineate2 = afni.Allineate(terminal_output=terminal_output).run
        mask_tool = afni.MaskTool(terminal_output=terminal_output).run
        catmatvec = afni.CatMatvec(terminal_output=terminal_output).run
        qwarp = afni.Qwarp(terminal_output=terminal_output).run
        nwarp_cat = afni.NwarpCat(terminal_output=terminal_output).run
        warp_apply = afni.NwarpApply(terminal_output=terminal_output).run

    current_dir = os.getcwd()
    os.chdir(write_dir)

    ###########################################################################
    # First copy anatomical files, to make sure they are never changed
    # and they have different names across individuals
    copied_t1_filenames = []
    for n, anat_file in enumerate(t1_filenames):
        suffixed_file = fname_presuffix(anat_file, suffix='_{}'.format(n))
        out_file = os.path.join(write_dir, os.path.basename(suffixed_file))
        out_copy = copy(in_file=anat_file, out_file=out_file)
        copied_t1_filenames.append(out_copy.outputs.out_file)

    ###########################################################################
    # Register using center of mass
    # -----------------------------
    # An initial coarse registration is done using brain centre of mass (CoM).
    #
    # First we loop through anatomical scans and correct intensities for bias.
    unifized_files = []
    for n, anat_file in enumerate(copied_t1_filenames):
        out_unifize = unifize(in_file=anat_file, outputtype='NIFTI_GZ')
        unifized_files.append(out_unifize.outputs.out_file)

    ###########################################################################
    # Second extract brains, aided by an approximate guessed brain volume,
    # and set the NIfTI image centre (as defined in the header) to the CoM
    # of the extracted brain.
    brain_files = []
    for unifized_file in unifized_files:
        out_clip_level = clip_level(in_file=unifized_file)
        out_rats = rats(
            in_file=unifized_file,
            volume_threshold=brain_volume,
            intensity_threshold=int(out_clip_level.outputs.clip_val),
            terminal_output=terminal_output)
        out_apply_mask = apply_mask(in_file=unifized_file,
                                    mask_file=out_rats.outputs.out_file)
        out_center_mass = center_mass(
            in_file=out_apply_mask.outputs.out_file,
            cm_file=fname_presuffix(unifized_file, suffix='_cm.txt',
                                    use_ext=False),
            set_cm=(0, 0, 0))
        brain_files.append(out_center_mass.outputs.out_file)

    ###########################################################################
    # Same header change, for head files.
    head_files = []
    for unifized_file, brain_file in zip(unifized_files, brain_files):
        out_refit = refit(in_file=unifized_file, duporigin_file=brain_file)
        head_files.append(out_refit.outputs.out_file)

    ###########################################################################
    # The brain files with new image center are concatenated to produce
    # a quality check video
    out_tcat = tcat(in_files=brain_files, outputtype='NIFTI_GZ',
                    terminal_output=terminal_output)

    ###########################################################################
    # and averaged
    out_tstat = tstat(in_file=out_tcat.outputs.out_file, outputtype='NIFTI_GZ')

    ###########################################################################
    # to create an empty template, with origin placed at CoM
    out_undump = undump(in_file=out_tstat.outputs.out_file,
                        outputtype='NIFTI_GZ')
    out_refit = refit(in_file=out_undump.outputs.out_file,
                      xorigin='cen', yorigin='cen', zorigin='cen')

    ###########################################################################
    # Finally, we shift heads and brains within the images to place the CoM at
    # the image center.
    centered_head_files = []
    for head_file in head_files:
        out_resample = resample(in_file=head_file,
                                master=out_refit.outputs.out_file,
                                outputtype='NIFTI_GZ')
        centered_head_files.append(out_resample.outputs.out_file)

    centered_brain_files = []
    for brain_file in brain_files:
        out_resample = resample(in_file=brain_file,
                                master=out_refit.outputs.out_file,
                                outputtype='NIFTI_GZ')
        centered_brain_files.append(out_resample.outputs.out_file)

    ###########################################################################
    # Quality check videos and average brain
    out_tcat = tcat(in_files=centered_brain_files,
                    out_file=os.path.join(write_dir, 'centered_brains.nii.gz'))
    out_tstat_centered_brain = tstat(in_file=out_tcat.outputs.out_file,
                                     outputtype='NIFTI_GZ')

    ###########################################################################
    # At this point, we achieved a translation-only registration of the raw
    # anatomical images to each other's brain's (as defined by the brain
    # extractor) CoMs.
    ###########################################################################
    # Shift rotate
    # ------------
    # Now we move to rigid-body registration of CoM brains, and application of
    # this registration to CoM heads. This registration requires a target
    #  template. Here we use mean of all bias-corrected, brain-extracted,
    # mass-centered images. Other possibilities include an externally-sourced
    # image or, more biased, a nicely-aligned individual.
    shift_rotated_brain_files = []
    rigid_transform_files = []
    for centered_brain_file in centered_brain_files:
        suffixed_matrix = fname_presuffix(centered_brain_file,
                                          suffix='_shr.aff12.1D',
                                          use_ext=False)
        out_matrix = os.path.join(write_dir, os.path.basename(suffixed_matrix))
        out_allineate = allineate(
            in_file=centered_brain_file,
            reference=out_tstat_centered_brain.outputs.out_file,
            out_matrix=out_matrix,
            convergence=convergence,
            two_blur=1,
            warp_type='shift_rotate',
            out_file=fname_presuffix(centered_brain_file, suffix='_shr'))
        rigid_transform_files.append(out_allineate.outputs.out_matrix)
        shift_rotated_brain_files.append(out_allineate.outputs.out_file)

    ###########################################################################
    # Application to the whole head image. can also be used for a good
    # demonstration of linear vs. non-linear registration quality
    shift_rotated_head_files = []
    for centered_head_file, rigid_transform_file in zip(centered_head_files,
                                                        rigid_transform_files):
        suffixed_file = fname_presuffix(centered_head_file, suffix='_shr')
        out_file = os.path.join(write_dir, os.path.basename(suffixed_file))
        out_allineate = allineate2(
            in_file=centered_head_file,
            master=out_tstat_centered_brain.outputs.out_file,
            in_matrix=rigid_transform_file,
            out_file=out_file)
        shift_rotated_head_files.append(out_allineate.outputs.out_file)

    ###########################################################################
    # Note that this rigid body registration may need to be run more than once.
    # Now we produce an average of rigid body registered heads
    out_tcat = tcat(
        in_files=shift_rotated_head_files,
        out_file=os.path.join(write_dir, 'rigid_body_registered_heads.nii.gz'))
    out_tstat_shr = tstat(in_file=out_tcat.outputs.out_file,
                          outputtype='NIFTI_GZ')

    if registration_kind == 'rigid':
        os.chdir(current_dir)
        return Bunch(registered=shift_rotated_head_files,
                     transforms=rigid_transform_files)

    ###########################################################################
    # Affine transform
    # ----------------
    # We begin by achieving an affine registration on aligned heads.
    # A weighting mask is used to ...
    out_mask_tool = mask_tool(in_file=out_tcat.outputs.out_file,
                              count=True,
                              outputtype='NIFTI_GZ')

    ###########################################################################
    # The count mask is also useful for looking at brain extraction efficiency
    # and differences in brain size.
    affine_transform_files = []
    for shift_rotated_head_file, rigid_transform_file in zip(
            shift_rotated_head_files, rigid_transform_files):
        out_allineate = allineate(
            in_file=shift_rotated_head_file,
            reference=out_tstat_shr.outputs.out_file,
            out_matrix=fname_presuffix(shift_rotated_head_file,
                                       suffix='_affine.aff12.1D',
                                       use_ext=False),
            convergence=convergence,
            two_blur=1,
            one_pass=True,
            weight=out_mask_tool.outputs.out_file,
            out_file=fname_presuffix(shift_rotated_head_file,
                                     suffix='_affine'))

        suffixed_matrix = fname_presuffix(shift_rotated_head_file,
                                          suffix='_affine_catenated.aff12.1D',
                                          use_ext=False)
        catmatvec_out_file = os.path.join(write_dir,
                                          os.path.basename(suffixed_matrix))
        out_catmatvec = catmatvec(in_file=[(rigid_transform_file, 'ONELINE'),
                                           (out_allineate.outputs.out_matrix,
                                            'ONELINE')],
                                  out_file=catmatvec_out_file)
        affine_transform_files.append(catmatvec_out_file)

    ###########################################################################
    # Each resulting registration matrix is concatenated to the corresponding
    # rigid bory registration matrix then directly applied to the CoM brain
    # and head, reducing reslice errors in the final result.
    allineated_brain_files = []
    for centered_brain_file, affine_transform_file in zip(
            centered_brain_files, affine_transform_files):
        out_allineate = allineate2(
            in_file=centered_brain_file,
            master=out_tstat_shr.outputs.out_file,
            in_matrix=affine_transform_file,
            out_file=fname_presuffix(centered_brain_file,
                                     suffix='_shr_affine_catenated'))
        allineated_brain_files.append(out_allineate.outputs.out_file)

    ###########################################################################
    # The application to the whole head image can also be used for a good
    # demonstration of linear vs. non-linear registration quality.
    allineated_head_files = []
    for centered_head_file, affine_transform_file in zip(
            centered_head_files, affine_transform_files):
        suffixed_file = fname_presuffix(centered_head_file,
                                        suffix='_shr_affine_catenated')
        out_file = os.path.join(write_dir, os.path.basename(suffixed_file))
        out_allineate = allineate2(
            in_file=centered_head_file,
            master=out_tstat_shr.outputs.out_file,
            in_matrix=affine_transform_file,
            out_file=out_file)
        allineated_head_files.append(out_allineate.outputs.out_file)

    ###########################################################################
    # Quality check videos and template
    out_tcat_head = tcat(
        in_files=allineated_head_files,
        out_file=os.path.join(write_dir, 'affine_registered_heads.nii.gz'))
    out_tstat_allineated_head = tstat(in_file=out_tcat_head.outputs.out_file,
                                      outputtype='NIFTI_GZ')

    if registration_kind == 'affine':
        os.chdir(current_dir)
        return Bunch(registered=allineated_head_files,
                     transforms=affine_transform_files)

    ###########################################################################
    # Non-linear registration
    # -----------------------
    # A weight mask that extends beyond the brain, incorporating some
    # surrounding tissue, is needed to help better define the brain head
    # boundary.
    out_mask_tool = mask_tool(in_file=out_tcat.outputs.out_file, count=True,
                              outputtype='NIFTI_GZ')
    out_mask_tool = mask_tool(in_file=out_tcat.outputs.out_file, union=True,
                              outputtype='NIFTI_GZ')
    out_mask_tool = mask_tool(in_file=out_mask_tool.outputs.out_file,
                              dilate_inputs='4',
                              outputtype='NIFTI_GZ')

    ###########################################################################
    # The input source images are initially transformed prior to registration,
    # to ensure that they are already quite well-aligned to the template.
    # To save time, we only achieve one refinement level per step
    if nonlinear_levels is None:
        nonlinear_levels = [1, 2, 3]

    warped_files = []
    warp_files = []
    for affine_transform_file, centered_head_file in zip(
            affine_transform_files, centered_head_files):
        out_qwarp = qwarp(
            in_file=centered_head_file,
            base_file=out_tstat_allineated_head.outputs.out_file,
            nmi=True,
            noneg=True,
            iwarp=True,
            weight=out_mask_tool.outputs.out_file,
            iniwarp=[affine_transform_file],
            inilev=0,
            maxlev=nonlinear_levels[0],
            out_file=fname_presuffix(centered_head_file, suffix='_warped1'))
        warp_files.append(out_qwarp.outputs.source_warp)
        warped_files.append(out_qwarp.outputs.warped_source)

    out_tcat = tcat(
        in_files=warped_files,
        out_file=os.path.join(write_dir, 'warped_1iter_heads.nii.gz'))
    out_tstat_warp_head = tstat(in_file=out_tcat.outputs.out_file,
                                outputtype='NIFTI_GZ')

    ###########################################################################
    # Then iterative registration from a given level to another is achieved.
    # Note that any level below a patch size of 25 will not be done (see
    # 3dQwarp help for further detail).
    # The input transform is the former warp and needs to be concatenated to
    # IDENT initially; I forget why, I think it is to avoid some weird bug.
    if len(nonlinear_levels) > 1:
        previous_warp_files = warp_files
        warped_files = []
        warp_files = []
        for warp_file, centered_head_file in zip(previous_warp_files,
                                                 centered_head_files):
            out_nwarp_cat = nwarp_cat(
                in_files=[('IDENT', out_tstat_warp_head.outputs.out_file),
                          warp_file], out_file='iniwarp.nii.gz')
            out_qwarp = qwarp(
                in_file=centered_head_file,
                base_file=out_tstat_warp_head.outputs.out_file,
                nmi=True,
                noneg=True,
                iwarp=True,
                weight=out_mask_tool.outputs.out_file,
                iniwarp=[out_nwarp_cat.outputs.out_file],
                inilev=nonlinear_levels[0],
                maxlev=nonlinear_levels[1],
                out_file=fname_presuffix(centered_head_file,
                                         suffix='_warped2'))
            warp_files.append(out_qwarp.outputs.source_warp)
            warped_files.append(out_qwarp.outputs.warped_source)

        out_tcat = tcat(in_files=warped_files,
                        out_file='warped_2iters_heads.nii.gz')
        out_tstat_warp_head = tstat(in_file=out_tcat.outputs.out_file,
                                    outputtype='NIFTI_GZ')

    ###########################################################################
    # Using previous files and concatenated transforms can be exploited to
    # avoid building up reslice errors.
    # Warp with mini-patch
    # In this particular case, minpatch=75 corresponds to a level of 4
    if len(nonlinear_levels) > 2:
        if nonlinear_minimal_patch is None:
            nonlinear_minimal_patch = 75

        for n_iter, inilev in enumerate(nonlinear_levels[2:]):
            previous_warp_files = warp_files
            warped_files = []
            warp_files = []
            for warp_file, centered_head_file in zip(previous_warp_files,
                                                     centered_head_files):
                suffixed_file = fname_presuffix(
                    centered_head_file,
                    suffix='_warped{}'.format(n_iter + 3))
                if n_iter == len(nonlinear_levels):
                    out_file = os.path.join(write_dir,
                                            os.path.basename(suffixed_file))
                else:
                    out_file = suffixed_file

                out_qwarp = qwarp(
                    in_file=centered_head_file,
                    base_file=out_tstat_warp_head.outputs.out_file,
                    nmi=True,
                    noneg=True,
                    iwarp=True,
                    weight=out_mask_tool.outputs.out_file,
                    iniwarp=[warp_file],
                    inilev=inilev,
                    minpatch=nonlinear_minimal_patch,
                    out_file=out_file)
                warped_files.append(out_qwarp.outputs.warped_source)
                warp_files.append(out_qwarp.outputs.source_warp)

            out_tcat = tcat(
                in_files=warped_files,
                out_file=os.path.join(
                    write_dir, 'warped_{0}iters_heads.nii.gz'.format(n_iter +
                                                                     3)))
            out_tstat_warp_head = tstat(in_file=out_tcat.outputs.out_file,
                                        outputtype='NIFTI_GZ')

    ###########################################################################
    # We can repeat this very last warp while using the last average until we
    # are satisfied with the template quality

    ###########################################################################
    # Register to template
    # --------------------
    # Apply non-linear registration results to uncorrected images
    warped_files = []
    for centered_head_file, warp_file in zip(centered_head_files, warp_files):
        suffixed_file = fname_presuffix(
            centered_head_file,
            suffix='affine_warp{}_catenated'.format(len(nonlinear_levels)))
        out_file = os.path.join(write_dir, os.path.basename(suffixed_file))
        out_warp_apply = warp_apply(
            in_file=centered_head_file,
            warp=warp_file,
            master=out_tstat_warp_head.outputs.out_file,
            out_file=out_file)
        warped_files.append(out_warp_apply.outputs.out_file)

    os.chdir(current_dir)
    return Bunch(registered=warped_files,
                 transforms=warp_files)
Beispiel #7
0
def anats_to_common(anat_filenames,
                    write_dir,
                    brain_volume,
                    registration_kind='affine',
                    use_rats_tool=True,
                    nonlinear_levels=[1, 2, 3],
                    nonlinear_minimal_patches=[75],
                    nonlinear_weight_file=None,
                    convergence=0.005,
                    blur_radius_coarse=1.1,
                    caching=False,
                    verbose=1,
                    unifize_kwargs=None,
                    brain_masking_unifize_kwargs=None):
    """ Create common template from native anatomical images and achieve
    their registration to it.

    Parameters
    ----------
    anat_filenames : list of str
        Paths to the anatomical images.

    write_dir : str
        Path to an existant directory to save output files to.

    brain_volume : int
        Volume of the brain used for brain extraction.
        Typically 400 for mouse and 1800 for rat.

    registration_kind : one of {'rigid', 'affine', 'nonlinear'}, optional
        The allowed transform kind.

    use_rats_tool : bool, optional
        If True, brain mask is computed using RATS Mathematical Morphology.
        Otherwise, a histogram-based brain segmentation is used.

    nonlinear_levels : list of int, optional
        Maximal levels for each nonlinear warping iteration. Passed iteratively
        to sammba.externals.nipype.interfaces.afni.Qwarp

    nonlinear_minimal_patches : list of int, optional
        Minimal patches for the final nonlinear warps, passed to
        sammba.externals.nipype.interfaces.afni.Qwarp
        
    nonlinear_weight_file : str, optional
        Path to a mask used to weight non-linear registration. Ideally should 
        include not just the whole brain but also extend in all directions to 
        include some amount of surrounding head tissue.
        
    convergence : float, optional
        Convergence limit, passed to
        sammba.externals.nipype.interfaces.afni.Allineate
        
    blur_radius_coarse : float, optional
        Radius passed to sammba.externals.nipype.interfaces.afni.Allineate for
        the "-twoblur" option

    caching : bool, optional
        If True, caching is used for all the registration steps.

    verbose : int, optional
        Verbosity level. Note that caching implies some
        verbosity in any case.

    unifize_kwargs : dict, optional
        Is passed to sammba.externals.nipype.interfaces.afni.Unifize, to
        control bias correction of the template.

    brain_masking_unifize_kwargs : dict, optional
        Is passed to sammba.externals.nipype.interfaces.afni.Unifize, to tune
        the seperate bias correction step done prior to brain masking.

    Returns
    -------
    data : sklearn.datasets.base.Bunch
        Dictionary-like object, the interest attributes are :

        - `registered` : list of str.
                         Paths to registered images. Note that
                         they have undergone a bias correction step before.
        - `transforms` : list of str.
                         Paths to the transforms from the raw
                         images to the registered images.
                         
    Notes
    -----    
    nonlinear_weight_file:

    Without this weight mask, for non-linear registration a mask is generated by 
    binarizing the mean of images that have been brain and affine-registered, 
    then evenly dilating it to include some surrounding head tissue. The 
    non-linear registration is then weighted to work only within this mask. This 
    substantially improves performance by 1) reducing the number of voxels to 
    analyse, and 2) avoiding other parts of the head where structures are highly 
    variable and signal often poor. However, this automatically-generated mask 
    is frequently sub-optimal, usually due to missing paraflocculi and 
    inappropriate dilation.
    
    Of course, it is impossible to know ahead of time where to weight the image 
    before the brains/heads have been registered to each other. So in practice a 
    first running of this script is done up until and including the affine stage 
    (the default). The user should then manually use 3dmask_tool or some other 
    software tool to create an intersect/frac/union mask of the 
    _Unifized_for_brain_extraction_masked_resample_shr_affine_catenated files. 
    These can then be dilated as needed to include some surrounding head tissue 
    (which helps to better distinguish the brain-non-brain boundary). Missing 
    regions can be added manually. This does not have to be done precisely, only 
    roughly, and it is better to include too much tissue than too little. The 
    procedure should then be rerun as non-linear, but using this weight mask.                     

    use_rats_tool:
    If `use_rats_tool` is turned on, RATS tool is used for brain extraction
    and has to be cited. For more information, see
    `RATS <http://www.iibi.uiowa.edu/content/rats-overview/>`_
    """
    registration_kinds = ['rigid', 'affine', 'nonlinear']
    if registration_kind not in registration_kinds:
        raise ValueError(
            'Registration kind must be one of {0}, you entered {1}'.format(
                registration_kinds, registration_kind))

    if registration_kind is 'nonlinear' and len(anat_filenames) < 5:
        raise ValueError('At least 5 input files are required to make a '
                         'template by non-linear \n registration. Only '
                         '{0} have been provided.'.format(len(anat_filenames)))

    if use_rats_tool:
        if segmentation.Info().version() is None:
            raise ValueError('Can not locate RATS')
        else:
            ComputeMask = segmentation.MathMorphoMask
    else:
        ComputeMask = segmentation.HistogramMask

    if verbose:
        terminal_output = 'stream'
        verbosity_kwargs = {'verb': verbose > 1}
        quietness_kwargs = {}
        verbosity_quietness_kwargs = {'verb': verbose > 2}
    else:
        terminal_output = 'none'
        verbosity_kwargs = {}
        quietness_kwargs = {'quiet': True}
        verbosity_quietness_kwargs = {'quiet': True}

    if caching:
        memory = Memory(write_dir)
        copy = memory.cache(afni.Copy)
        unifize = memory.cache(afni.Unifize)
        clip_level = memory.cache(afni.ClipLevel)
        compute_mask = memory.cache(ComputeMask)
        calc = memory.cache(afni.Calc)
        center_mass = memory.cache(afni.CenterMass)
        refit = memory.cache(afni.Refit)
        tcat = memory.cache(afni.TCat)
        tstat = memory.cache(afni.TStat)
        undump = memory.cache(afni.Undump)
        resample = memory.cache(afni.Resample)
        allineate = memory.cache(afni.Allineate)
        allineate2 = memory.cache(afni.Allineate)
        mask_tool = memory.cache(afni.MaskTool)
        catmatvec = memory.cache(afni.CatMatvec)
        qwarp = memory.cache(afni.Qwarp)
        qwarp2 = memory.cache(afni.Qwarp)  # workaround to initialize inputs
        nwarp_cat = memory.cache(afni.NwarpCat)
        warp_apply = memory.cache(afni.NwarpApply)
        nwarp_adjust = memory.cache(afni.NwarpAdjust)
        for step in [
                copy, unifize, compute_mask, calc, refit, tcat, tstat, undump,
                resample, allineate, allineate2, mask_tool, catmatvec, qwarp,
                nwarp_cat, warp_apply, nwarp_adjust
        ]:
            step.interface().set_default_terminal_output(terminal_output)
    else:
        copy = afni.Copy(terminal_output=terminal_output).run
        unifize = afni.Unifize(terminal_output=terminal_output).run
        clip_level = afni.ClipLevel().run  # XXX fix nipype bug with 'none'
        compute_mask = ComputeMask(terminal_output=terminal_output).run
        calc = afni.Calc(terminal_output=terminal_output).run
        center_mass = afni.CenterMass().run  # XXX fix nipype bug with 'none'
        refit = afni.Refit(terminal_output=terminal_output).run
        tcat = afni.TCat(terminal_output=terminal_output).run
        tstat = afni.TStat(terminal_output=terminal_output).run
        undump = afni.Undump(terminal_output=terminal_output).run
        resample = afni.Resample(terminal_output=terminal_output).run
        allineate = afni.Allineate(terminal_output=terminal_output).run
        allineate2 = afni.Allineate(terminal_output=terminal_output).run
        mask_tool = afni.MaskTool(terminal_output=terminal_output).run
        catmatvec = afni.CatMatvec(terminal_output=terminal_output).run
        qwarp = afni.Qwarp(terminal_output=terminal_output).run
        qwarp2 = afni.Qwarp(terminal_output=terminal_output).run
        nwarp_cat = afni.NwarpCat(terminal_output=terminal_output).run
        warp_apply = afni.NwarpApply(terminal_output=terminal_output).run
        nwarp_adjust = afni.NwarpAdjust(terminal_output=terminal_output).run

    current_dir = os.getcwd()
    os.chdir(write_dir)

    ###########################################################################
    # First copy anatomical files to make sure the originals are never changed
    # and they have different names across individuals. Then produce a video of
    # this raw data and a mean
    copied_anat_filenames = []
    for n, anat_file in enumerate(anat_filenames):
        suffixed_file = fname_presuffix(anat_file, suffix='_{}'.format(n))
        out_file = os.path.join(write_dir, os.path.basename(suffixed_file))
        out_copy = copy(in_file=anat_file,
                        out_file=out_file,
                        **verbosity_kwargs)
        copied_anat_filenames.append(out_copy.outputs.out_file)

    out_tcat = tcat(in_files=copied_anat_filenames,
                    out_file=os.path.join(write_dir, 'raw_heads.nii.gz'),
                    outputtype='NIFTI_GZ',
                    **verbosity_kwargs)
    out_tstat = tstat(in_file=out_tcat.outputs.out_file, outputtype='NIFTI_GZ')

    ###########################################################################
    # Bias correct and register using center of mass
    # -----------------------------
    # An initial coarse registration is done using brain centre of mass (CoM).
    #
    # First we loop through anatomical scans and correct intensities for bias.
    # This is done twice with parameters that can be set differently: once to
    # create an image for automatic brain mask generation, and a another time
    # for the image that will actually have its brain extracted by this mask and
    # also be passed on to the rest of the function. This separation is useful
    # because in some circumstances the ideal bias correction can create zones
    # of signal and noise that confuse the brain masker, so it is best if that
    # calculation is performed on a differently-corrected image. In a lot(most?)
    # cases, the same parameters can be used for both bias correctors (the
    # default) as though the correction was only ever done once.
    #
    # Second, image centers are redefined based on the CoM of brains extracted
    # by the brain masks. The images are then translated to force the new
    # centers to all be at the same position: the center of the image matrix.
    # This is a crude form of translation-only registration amongst images that
    # simultaneously shifts the position of all brains to being in the centre of
    # the image if this was not already the case (which it often is not in small
    # mammal head imaging where the brain is usually in the upper half).
    #
    # Note that the heads created at the end will be the start point for all
    # subsequent transformations (meaning any transformation generated from now
    # on will be a concatenation of itself and previous ones for direct
    # application to CoM-registered heads). This avoids the accumulation of
    # reslice error from one registration to the next. Ideally, the start point
    # should be the bias-corrected images prior to center correction (which
    # itself involes reslicing). However, I have not yet figured out the best
    # way to convert CoM change into an affine transform and then use it. The
    # conversion should be relatively easy, using nibabel to extract the two
    # affines then numpy to calculate the difference. Using it is not so simple.
    # Unlike 3dQwarp, 3dAllineate does not have a simple initialization flag.
    # Instead, it is necessary to use -parini to initialize any given affine
    # parameter individually. However, -parini can be overidden by other flags,
    # so careful checks need to be made to ensure that this will never happen
    # with the particular command or set of commands used here.

    # bias correction for images to be used for brain mask creation
    if brain_masking_unifize_kwargs is None:
        brain_masking_unifize_kwargs = {}
    brain_masking_unifize_kwargs.update(quietness_kwargs)
    brain_masking_in_files = []
    for n, anat_file in enumerate(copied_anat_filenames):
        out_unifize = unifize(in_file=anat_file,
                              out_file='%s_Unifized_for_brain_masking',
                              outputtype='NIFTI_GZ',
                              **brain_masking_unifize_kwargs)
        brain_masking_in_files.append(out_unifize.outputs.out_file)

    # brain mask creation
    brain_mask_files = []
    for n, brain_masking_in_file in enumerate(brain_masking_in_files):
        out_clip_level = clip_level(in_file=brain_masking_in_file)
        out_compute_mask = compute_mask(
            in_file=brain_masking_in_file,
            out_file=fname_presuffix(brain_masking_in_file, suffix='_mask'),
            volume_threshold=brain_volume,
            intensity_threshold=int(out_clip_level.outputs.clip_val),
            terminal_output=terminal_output)
        brain_mask_files.append(out_compute_mask.outputs.out_file)

    # bias correction for images to be both brain-extracted with the mask
    # generated above and then passed on to the rest of the function
    if unifize_kwargs is None:
        unifize_kwargs = {}

    unifize_kwargs.update(quietness_kwargs)
    unifized_files = []
    for n, anat_file in enumerate(copied_anat_filenames):
        out_unifize = unifize(in_file=anat_file,
                              out_file='%s_Unifized_for_brain_extraction',
                              outputtype='NIFTI_GZ',
                              **unifize_kwargs)
        unifized_files.append(out_unifize.outputs.out_file)

    # extrcat brains and set NIfTI image center (as defined in the header) to
    # the brain CoM
    brain_files = []
    for (brain_mask_file, unifized_file) in zip(brain_mask_files,
                                                unifized_files):
        out_calc_mask = calc(in_file_a=unifized_file,
                             in_file_b=brain_mask_file,
                             expr='a*b',
                             outputtype='NIFTI_GZ')
        out_center_mass = center_mass(in_file=out_calc_mask.outputs.out_file,
                                      cm_file=fname_presuffix(unifized_file,
                                                              suffix='_cm.txt',
                                                              use_ext=False),
                                      set_cm=(0, 0, 0))
        brain_files.append(out_center_mass.outputs.out_file)

    # apply center change to head files too
    head_files = []
    for unifized_file, brain_file in zip(unifized_files, brain_files):
        out_refit = refit(in_file=unifized_file, duporigin_file=brain_file)
        head_files.append(out_refit.outputs.out_file)

    # create an empty template with a center at the image matrix center
    out_undump = undump(in_file=out_tstat.outputs.out_file,
                        out_file=os.path.join(write_dir, 'undump.nii.gz'),
                        outputtype='NIFTI_GZ')
    out_refit = refit(in_file=out_undump.outputs.out_file,
                      xorigin='cen',
                      yorigin='cen',
                      zorigin='cen')

    # shift brains to place their new centers at the same central position.
    # make a quality check video and mean
    centered_brain_files = []
    for brain_file in brain_files:
        out_resample = resample(in_file=brain_file,
                                resample_mode='Cu',
                                master=out_refit.outputs.out_file,
                                outputtype='NIFTI_GZ')
        centered_brain_files.append(out_resample.outputs.out_file)
    out_tcat = tcat(in_files=centered_brain_files,
                    out_file=os.path.join(write_dir, 'centered_brains.nii.gz'),
                    **verbosity_kwargs)
    out_tstat_centered_brain = tstat(in_file=out_tcat.outputs.out_file,
                                     outputtype='NIFTI_GZ')

    # do the same for heads. is also a better quality check than the brain
    centered_head_files = []
    for head_file in head_files:
        out_resample = resample(in_file=head_file,
                                resample_mode='Cu',
                                master=out_refit.outputs.out_file,
                                outputtype='NIFTI_GZ')
        centered_head_files.append(out_resample.outputs.out_file)
    out_tcat = tcat(in_files=centered_head_files,
                    out_file=os.path.join(write_dir, 'centered_heads.nii.gz'),
                    **verbosity_kwargs)
    out_tstat_centered_brain = tstat(in_file=out_tcat.outputs.out_file,
                                     outputtype='NIFTI_GZ')

    ###########################################################################
    # At this point, we have achieved a translation-only registration of the
    # anatomical images to each other's brain's (as defined by the brain
    # masker) CoMs.
    ###########################################################################
    # Rigid-body registration (shift rotate in AFNI parlance)
    # -------------------------------------------------------
    # Now we move on to the rigid-body registration of CoM brains, and
    # application of this registration to CoM heads. This requires a target
    # template. Here we use the mean of all bias-corrected, brain-extracted,
    # mass-centered images. Other possibilities include an externally-sourced
    # image or, more biased, a nicely-aligned individual.
    #
    # In extreme cases where acquisitions were done at highly variable head
    # angles, it may be worth running this twice or even more (for which there
    # is no current functionality), but we have never found a case that extreme
    # so it is not implemented.

    # rigid-body registration
    shift_rotated_brain_files = []
    rigid_transform_files = []
    for centered_brain_file in centered_brain_files:
        suffixed_matrix = fname_presuffix(centered_brain_file,
                                          suffix='_shr.aff12.1D',
                                          use_ext=False)
        out_matrix = os.path.join(write_dir, os.path.basename(suffixed_matrix))
        out_allineate = allineate(
            in_file=centered_brain_file,
            reference=out_tstat_centered_brain.outputs.out_file,
            out_matrix=out_matrix,
            convergence=convergence,
            two_blur=blur_radius_coarse,
            warp_type='shift_rotate',
            out_file=fname_presuffix(centered_brain_file, suffix='_shr'),
            **verbosity_quietness_kwargs)
        rigid_transform_files.append(out_allineate.outputs.out_matrix)
        shift_rotated_brain_files.append(out_allineate.outputs.out_file)

    # application to the head images
    shift_rotated_head_files = []
    for centered_head_file, rigid_transform_file in zip(
            centered_head_files, rigid_transform_files):
        suffixed_file = fname_presuffix(centered_head_file, suffix='_shr')
        out_file = os.path.join(write_dir, os.path.basename(suffixed_file))
        out_allineate = allineate2(
            in_file=centered_head_file,
            master=out_tstat_centered_brain.outputs.out_file,
            in_matrix=rigid_transform_file,
            out_file=out_file,
            **verbosity_quietness_kwargs)
        shift_rotated_head_files.append(out_allineate.outputs.out_file)

    # quality check video and mean for head and brain
    out_tcat = tcat(in_files=shift_rotated_head_files,
                    out_file=os.path.join(
                        write_dir, 'rigid_body_registered_heads.nii.gz'),
                    **verbosity_kwargs)
    out_tstat_shr = tstat(in_file=out_tcat.outputs.out_file,
                          outputtype='NIFTI_GZ')
    out_tcat = tcat(in_files=shift_rotated_brain_files,
                    out_file=os.path.join(
                        write_dir, 'rigid_body_registered_brains.nii.gz'),
                    **verbosity_kwargs)
    out_tstat_shr = tstat(in_file=out_tcat.outputs.out_file,
                          outputtype='NIFTI_GZ')

    if registration_kind == 'rigid':
        os.chdir(current_dir)
        return Bunch(registered=shift_rotated_head_files,
                     transforms=rigid_transform_files)

    ###########################################################################
    # Affine transform
    # ----------------
    # Similar to the previous rigid-body registration but with the following
    # differences:
    # 1) The registration target is now the product of rigid-body rather than
    #    CoM registration.
    # 2) Rather than using the mean brain as a target, the mean head is used,
    #    weighted by a mask made by binarizing the brains and making a count
    #    mask out of them. This should mathematically be exactly the same thing
    #    and was done this way partially for fun, partially to make more use of
    #    the count mask, whose main purpose is to demonstrate variability in
    #    brain size and extraction quality.
    # 3) There is an extra step for concatenation of transform results.

    # make the count mask
    out_mask_tool = mask_tool(in_file=out_tcat.outputs.out_file,
                              count=True,
                              verbose=verbose,
                              outputtype='NIFTI_GZ')

    #affine transform
    affine_transform_files = []
    for shift_rotated_head_file, rigid_transform_file in zip(
            shift_rotated_head_files, rigid_transform_files):
        out_allineate = allineate(
            in_file=shift_rotated_head_file,
            reference=out_tstat_shr.outputs.out_file,
            out_matrix=fname_presuffix(shift_rotated_head_file,
                                       suffix='_affine.aff12.1D',
                                       use_ext=False),
            convergence=convergence,
            two_blur=blur_radius_coarse,
            one_pass=True,
            weight=out_mask_tool.outputs.out_file,
            out_file=fname_presuffix(shift_rotated_head_file,
                                     suffix='_affine'),
            **verbosity_quietness_kwargs)
        # matrix concatenation
        suffixed_matrix = fname_presuffix(shift_rotated_head_file,
                                          suffix='_affine_catenated.aff12.1D',
                                          use_ext=False)
        catmatvec_out_file = os.path.join(write_dir,
                                          os.path.basename(suffixed_matrix))
        out_catmatvec = catmatvec(in_file=[(rigid_transform_file, 'ONELINE'),
                                           (out_allineate.outputs.out_matrix,
                                            'ONELINE')],
                                  out_file=catmatvec_out_file)
        affine_transform_files.append(catmatvec_out_file)

    # application to brains
    allineated_brain_files = []
    for centered_brain_file, affine_transform_file in zip(
            centered_brain_files, affine_transform_files):
        out_allineate = allineate2(in_file=centered_brain_file,
                                   master=out_tstat_shr.outputs.out_file,
                                   in_matrix=affine_transform_file,
                                   out_file=fname_presuffix(
                                       centered_brain_file,
                                       suffix='_shr_affine_catenated'),
                                   **verbosity_quietness_kwargs)
        allineated_brain_files.append(out_allineate.outputs.out_file)

    # application to heads
    allineated_head_files = []
    for centered_head_file, affine_transform_file in zip(
            centered_head_files, affine_transform_files):
        suffixed_file = fname_presuffix(centered_head_file,
                                        suffix='_shr_affine_catenated')
        out_file = os.path.join(write_dir, os.path.basename(suffixed_file))
        out_allineate = allineate2(in_file=centered_head_file,
                                   master=out_tstat_shr.outputs.out_file,
                                   in_matrix=affine_transform_file,
                                   out_file=out_file,
                                   **verbosity_quietness_kwargs)
        allineated_head_files.append(out_allineate.outputs.out_file)

    #quality check videos and template for head and brain
    out_tcat_head = tcat(in_files=allineated_head_files,
                         out_file=os.path.join(
                             write_dir, 'affine_registered_heads.nii.gz'),
                         **verbosity_kwargs)
    out_tstat_allineated_head = tstat(in_file=out_tcat_head.outputs.out_file,
                                      outputtype='NIFTI_GZ')
    out_tcat_brain = tcat(in_files=allineated_brain_files,
                          out_file=os.path.join(
                              write_dir, 'affine_registered_brains.nii.gz'),
                          **verbosity_kwargs)
    out_tstat_allineated_brain = tstat(in_file=out_tcat_brain.outputs.out_file,
                                       outputtype='NIFTI_GZ')

    if registration_kind == 'affine':
        os.chdir(current_dir)
        return Bunch(registered=allineated_head_files,
                     transforms=affine_transform_files)

    ###########################################################################
    # Non-linear registration
    # -----------------------
    # A weight mask that extends beyond the brain, incorporating some
    # surrounding tissue, is needed to help better define the brain head
    # boundary.
    if nonlinear_weight_file is None:
        out_mask_tool = mask_tool(
            in_file=out_tcat.outputs.out_file,
            union=True,
            out_file=os.path.join(write_dir,
                                  'affine_registered_brains_unionmask.nii.gz'),
            outputtype='NIFTI_GZ',
            verbose=verbose)
        out_mask_tool = mask_tool(
            in_file=out_mask_tool.outputs.out_file,
            out_file=os.path.join(
                write_dir, 'affine_registered_brains_unionmask_dil4.nii.gz'),
            dilate_inputs='4',
            outputtype='NIFTI_GZ',
            verbose=verbose)
        nonlinear_weight_file = out_mask_tool.outputs.out_file

    ###########################################################################
    # Description to fill
    #
    #
    if nonlinear_levels is None:
        nonlinear_levels = [1, 2, 3]
    if nonlinear_minimal_patches is None:
        nonlinear_minimal_patches = []
    levels_minpatches = nonlinear_levels + nonlinear_minimal_patches

    for n_iter, level_or_minpatch in enumerate(levels_minpatches):

        if n_iter == 0:
            previous_warp_files = affine_transform_files
        warped_files = []
        warp_files = []

        for warp_file, centered_head_file in zip(previous_warp_files,
                                                 centered_head_files):

            out_file = fname_presuffix(centered_head_file,
                                       suffix='_warped{}'.format(n_iter))

            if n_iter == 0:
                out_nwarp_cat = nwarp_cat(
                    in_files=[('IDENT', centered_head_file), warp_file],
                    out_file=fname_presuffix(centered_head_file,
                                             suffix='_iniwarp'))
                out_qwarp = qwarp(
                    in_file=centered_head_file,
                    base_file=out_tstat_allineated_head.outputs.out_file,
                    noneg=True,
                    iwarp=True,
                    weight=nonlinear_weight_file,
                    iniwarp=[out_nwarp_cat.outputs.out_file],
                    inilev=0,
                    maxlev=level_or_minpatch,
                    out_file=out_file,
                    **verbosity_quietness_kwargs)

            elif n_iter < len(nonlinear_levels):
                out_qwarp = qwarp(in_file=centered_head_file,
                                  base_file=nwarp_adjusted_mean,
                                  noneg=True,
                                  iwarp=True,
                                  weight=nonlinear_weight_file,
                                  iniwarp=[warp_file],
                                  inilev=levels_minpatches[n_iter - 1] + 1,
                                  maxlev=level_or_minpatch,
                                  out_file=out_file,
                                  **verbosity_quietness_kwargs)

            else:
                out_qwarp = qwarp2(
                    in_file=centered_head_file,
                    base_file=nwarp_adjusted_mean,
                    noneg=True,
                    iwarp=True,
                    weight=nonlinear_weight_file,
                    iniwarp=[warp_file],
                    inilev=nonlinear_levels[-1] + 1,  # not ideal
                    minpatch=level_or_minpatch,
                    out_file=out_file)

            warped_files.append(out_qwarp.outputs.warped_source)
            warp_files.append(out_qwarp.outputs.source_warp)
            previous_warp_files = warp_files

        out_tcat = tcat(
            in_files=warped_files,
            out_file=os.path.join(
                write_dir,
                'warped_{0}iters_hetemplate_filenameads.nii.gz'.format(
                    n_iter)),
            **verbosity_kwargs)
        out_tstat_warp_head = tstat(in_file=out_tcat.outputs.out_file,
                                    outputtype='NIFTI_GZ')

        nwarp_adjusted_mean = 'warped_{0}_adjusted_mean.nii.gz'.format(n_iter)
        out_nwarp_adjust = nwarp_adjust(warps=warp_files,
                                        in_files=centered_head_files,
                                        out_file=nwarp_adjusted_mean)

    ###########################################################################
    # Register to template
    # --------------------
    # Apply non-linear registration results to uncorrected images
    warped_files = []
    for centered_head_file, warp_file in zip(centered_head_files, warp_files):
        suffixed_file = fname_presuffix(
            centered_head_file,
            suffix='affine_warp{}_catenated'.format(len(nonlinear_levels)))
        out_file = os.path.join(write_dir, os.path.basename(suffixed_file))
        out_warp_apply = warp_apply(
            in_file=centered_head_file,
            warp=warp_file,
            master=out_tstat_warp_head.outputs.out_file,
            out_file=out_file,
            **verbosity_quietness_kwargs)
        warped_files.append(out_warp_apply.outputs.out_file)

    os.chdir(current_dir)
    return Bunch(registered=warped_files, transforms=warp_files)