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)
def create_pipeline_graph(pipeline_name, graph_file, graph_kind='hierarchical'): """Creates pipeline graph for a given piepline. Parameters ---------- pipeline_name : one of {'anat_to_common_rigid', 'anat_to_common_affine', 'anat_to_common_nonlinear'} Pipeline name. graph_file : str. Path to save the graph image to. graph_kind : one of {'orig', 'hierarchical', 'flat', 'exec', 'colored'}, optional. The kind of the graph, passed to sammba.externals.nipype.pipeline.workflows.Workflow().write_graph """ pipeline_names = [ 'anats_to_common_rigid', 'anats_to_common_affine', 'anats_to_common_nonlinear' ] if pipeline_name not in pipeline_names: raise NotImplementedError( 'Pipeline name must be one of {0}, you entered {1}'.format( pipeline_names, pipeline_name)) graph_kinds = ['orig', 'hierarchical', 'flat', 'exec', 'colored'] if graph_kind not in graph_kinds: raise ValueError( 'Graph kind must be one of {0}, you entered {1}'.format( graph_kinds, graph_kind)) workflow = pe.Workflow(name=pipeline_name) ####################################################################### # Specify rigid body registration pipeline steps unifize = pe.Node(interface=afni.Unifize(), name='bias_correct') clip_level = pe.Node(interface=afni.ClipLevel(), name='compute_mask_threshold') compute_mask = pe.Node(interface=interfaces.MathMorphoMask(), name='compute_brain_mask') apply_mask = pe.Node(interface=afni.Calc(), name='apply_brain_mask') center_mass = pe.Node(interface=afni.CenterMass(), name='compute_and_set_cm_in_header') refit_copy = pe.Node(afni.Refit(), name='copy_cm_in_header') tcat1 = pe.Node(afni.TCat(), name='concatenate_across_individuals1') tstat1 = pe.Node(afni.TStat(), name='compute_average1') undump = pe.Node(afni.Undump(), name='create_empty_template') refit_set = pe.Node(afni.Refit(), name='set_cm_in_header') resample1 = pe.Node(afni.Resample(), name='resample1') resample2 = pe.Node(afni.Resample(), name='resample2') shift_rotate = pe.Node(afni.Allineate(), name='shift_rotate') apply_allineate1 = pe.Node(afni.Allineate(), name='apply_transform1') tcat2 = pe.Node(afni.TCat(), name='concatenate_across_individuals2') tstat2 = pe.Node(afni.TStat(), name='compute_average2') tcat3 = pe.Node(afni.TCat(), name='concatenate_across_individuals3') tstat3 = pe.Node(afni.TStat(), name='compute_average3') workflow.add_nodes([ unifize, clip_level, compute_mask, apply_mask, center_mass, refit_copy, tcat1, tstat1, undump, refit_set, resample1, resample2, shift_rotate, apply_allineate1, tcat2, tstat2, tcat3, tstat3 ]) ####################################################################### # and connections workflow.connect(unifize, 'out_file', clip_level, 'in_file') workflow.connect(clip_level, 'clip_val', compute_mask, 'intensity_threshold') workflow.connect(unifize, 'out_file', compute_mask, 'in_file') workflow.connect(compute_mask, 'out_file', apply_mask, 'in_file_a') workflow.connect(unifize, 'out_file', apply_mask, 'in_file_b') workflow.connect(apply_mask, 'out_file', center_mass, 'in_file') workflow.connect(unifize, 'out_file', refit_copy, 'in_file') workflow.connect(center_mass, 'out_file', refit_copy, 'duporigin_file') workflow.connect(center_mass, 'out_file', tcat1, 'in_files') workflow.connect(tcat1, 'out_file', tstat1, 'in_file') workflow.connect(tstat1, 'out_file', undump, 'in_file') workflow.connect(undump, 'out_file', refit_set, 'in_file') workflow.connect(refit_set, 'out_file', resample1, 'master') workflow.connect(refit_copy, 'out_file', resample1, 'in_file') workflow.connect(refit_set, 'out_file', resample2, 'master') workflow.connect(center_mass, 'out_file', resample2, 'in_file') workflow.connect(resample2, 'out_file', tcat2, 'in_files') workflow.connect(tcat2, 'out_file', tstat2, 'in_file') workflow.connect(tstat2, 'out_file', shift_rotate, 'reference') workflow.connect(resample2, 'out_file', shift_rotate, 'in_file') workflow.connect(tstat2, 'out_file', apply_allineate1, 'master') workflow.connect(resample1, 'out_file', apply_allineate1, 'in_file') workflow.connect(shift_rotate, 'out_matrix', apply_allineate1, 'in_matrix') workflow.connect(apply_allineate1, 'out_file', tcat3, 'in_files') workflow.connect(tcat3, 'out_file', tstat3, 'in_file') if pipeline_name in ['anats_to_common_affine', 'anat_to_common_nonlinear']: mask = pe.Node(afni.MaskTool(), name='generate_count_mask') allineate = pe.Node(afni.Allineate(), name='allineate') catmatvec = pe.Node(afni.CatMatvec(), name='concatenate_transforms') apply_allineate2 = pe.Node(afni.Allineate(), name='apply_transform2') tcat3 = pe.Node(afni.TCat(), name='concatenate_across_individuals4') tstat3 = pe.Node(afni.TStat(), name='compute_average4') workflow.add_nodes( [mask, allineate, catmatvec, apply_allineate2, tcat3, tstat3]) workflow.connect(tcat2, 'out_file', mask, 'in_file') workflow.connect(mask, 'out_file', allineate, 'weight') workflow.connect(apply_allineate1, 'out_file', allineate, 'in_file') workflow.connect(allineate, 'out_matrix', catmatvec, 'in_file') #XXX how can we enter multiple files ? workflow.connect(catmatvec, 'out_file', apply_allineate2, 'in_matrix') workflow.connect(resample1, 'out_file', apply_allineate2, 'in_file') workflow.connect(apply_allineate2, 'out_file', tcat3, 'in_files') workflow.connect(tcat3, 'out_file', tstat3, 'in_file') if pipeline_name == 'anats_to_common_nonlinear': pass graph_file_root, graph_file_ext = os.path.splitext(graph_file) if graph_file_ext: _ = workflow.write_graph(graph2use=graph_kind, format=graph_file_ext[1:], dotfilename=graph_file_root) else: _ = workflow.write_graph(graph2use=graph_kind, dotfilename=graph_file_root)
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)
def _realign(func_filename, write_dir, caching=False, terminal_output='allatonce', environ=None): if environ is None: environ = {'AFNI_DECONFLICT': 'OVERWRITE'} if caching: memory = Memory(write_dir) clip_level = memory.cache(afni.ClipLevel) threshold = memory.cache(fsl.Threshold) volreg = memory.cache(afni.Volreg) allineate = memory.cache(afni.Allineate) copy = memory.cache(afni.Copy) copy_geom = memory.cache(fsl.CopyGeom) tstat = memory.cache(afni.TStat) for step in [threshold, volreg, allineate, tstat, copy, copy_geom]: step.interface().set_default_terminal_output(terminal_output) else: clip_level = afni.ClipLevel().run threshold = fsl.Threshold(terminal_output=terminal_output).run volreg = afni.Volreg(terminal_output=terminal_output).run allineate = afni.Allineate(terminal_output=terminal_output).run copy = afni.Copy(terminal_output=terminal_output).run copy_geom = fsl.CopyGeom(terminal_output=terminal_output).run tstat = afni.TStat(terminal_output=terminal_output).run out_clip_level = clip_level(in_file=func_filename) out_threshold = threshold(in_file=func_filename, thresh=out_clip_level.outputs.clip_val, out_file=fname_presuffix(func_filename, suffix='_thresholded', newpath=write_dir)) thresholded_filename = out_threshold.outputs.out_file out_volreg = volreg( # XXX dfile not saved in_file=thresholded_filename, out_file=fname_presuffix(thresholded_filename, suffix='_volreg', newpath=write_dir), environ=environ, oned_file=fname_presuffix(thresholded_filename, suffix='_volreg.1Dfile.1D', use_ext=False, newpath=write_dir), oned_matrix_save=fname_presuffix(thresholded_filename, suffix='_volreg.aff12.1D', use_ext=False, newpath=write_dir)) # 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='_volreg', newpath=write_dir), 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 = copy(in_file=out_allineate.outputs.out_file, out_file=fname_presuffix(out_allineate.outputs.out_file, suffix='_oblique', newpath=write_dir), environ=environ) out_copy_geom = copy_geom(dest_file=out_copy.outputs.out_file, in_file=out_volreg.outputs.out_file) oblique_allineated_filename = out_copy_geom.outputs.out_file # Create a (hopefully) nice mean image for use in the registration out_tstat = tstat(in_file=oblique_allineated_filename, args='-mean', out_file=fname_presuffix(oblique_allineated_filename, suffix='_tstat', newpath=write_dir), environ=environ) # Remove intermediate outputs if not caching: for output_file in [ thresholded_filename, out_volreg.outputs.oned_matrix_save, out_volreg.outputs.out_file, out_volreg.outputs.md1d_file, out_allineate.outputs.out_file ]: os.remove(output_file) return (oblique_allineated_filename, out_tstat.outputs.out_file, out_volreg.outputs.oned_file)
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)
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)
def anats_to_template(anat_filenames, head_template_filename, write_dir, brain_volume, use_rats_tool=True, brain_template_filename=None, dilated_head_mask_filename=None, convergence=.005, maxlev=None, caching=False, verbose=1, unifize_kwargs=None, brain_masking_unifize_kwargs=None): """ Registers raw anatomical images to a given template. Parameters ---------- anat_filenames : list of str Paths to the anatomical images. head_template_filename : str Path to the head template. 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. use_rats_tool : bool, optional If True, brain mask is computed using RATS Mathematical Morphology. Otherwise, a histogram-based brain segmentation is used. brain_template_filename : str, optional Path to a brain template. Note that this must coincide with the brain from the given head template. If None, the brain is extracted from the template with RATS. dilated_head_mask_filename : str, optional Path to a dilated head mask. Note that this must be compliant with the the given head template. If None, the mask is set to the non-background voxels of the head template after one dilation. caching : bool, optional If True, caching is used for all the registration steps. convergence : float, optional Convergence limit, passed to sammba.externals.nipype.interfaces.afni.Allineate maxlev : int or None, optional If not None, maximal level for the nonlinear warping. Passed to sammba.externals.nipype.interfaces.afni.Qwarp. Lower implies faster but possibly lower precision. 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 extraction. 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. - `pre_transforms` : list of str. Paths to the affine transforms from the raw images to the images allineated to the template. - `transforms` : list of str. Paths to the transforms from the allineated images to the final registered images. 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/>`_ """ environ = {} if verbose: terminal_output = 'stream' quietness_kwargs = {} verbosity_quietness_kwargs = {'verb': verbose > 2} else: terminal_output = 'none' quietness_kwargs = {'quiet': True} verbosity_quietness_kwargs = {'quiet': True} 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 caching: memory = Memory(write_dir) clip_level = memory.cache(afni.ClipLevel) compute_mask = memory.cache(ComputeMask) calc = memory.cache(afni.Calc) mask_tool = memory.cache(afni.MaskTool) allineate = memory.cache(afni.Allineate) allineate2 = memory.cache(afni.Allineate) unifize = memory.cache(afni.Unifize) qwarp = memory.cache(afni.Qwarp) for step in [ compute_mask, allineate, allineate2, calc, mask_tool, unifize, qwarp ]: step.interface().set_default_terminal_output(terminal_output) else: unifize = afni.Unifize(terminal_output=terminal_output).run clip_level = afni.ClipLevel().run compute_mask = ComputeMask(terminal_output=terminal_output).run calc = afni.Calc(terminal_output=terminal_output).run mask_tool = afni.MaskTool(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 qwarp = afni.Qwarp(terminal_output=terminal_output).run environ['AFNI_DECONFLICT'] = 'OVERWRITE' current_dir = os.getcwd() os.chdir(write_dir) intermediate_files = [] if brain_template_filename is None: out_clip_level = clip_level(in_file=head_template_filename) out_rats = compute_mask(in_file=head_template_filename, volume_threshold=brain_volume, intensity_threshold=int( out_clip_level.outputs.clip_val)) brain_template_filename = out_rats.outputs.out_file if dilated_head_mask_filename is None: out_clip_level = clip_level(in_file=head_template_filename) out_calc_threshold = calc(in_file_a=head_template_filename, expr='ispositive(a-{0})*a'.format( out_clip_level.outputs.clip_val), outputtype='NIFTI_GZ') out_mask_tool = mask_tool(in_file=out_calc_threshold.outputs.out_file, dilate_inputs='3', outputtype='NIFTI_GZ', environ=environ, verbose=verbose) dilated_head_mask_filename = out_mask_tool.outputs.out_file intermediate_files.append(out_calc_threshold.outputs.out_file) if brain_masking_unifize_kwargs is None: brain_masking_unifize_kwargs = {} brain_masking_unifize_kwargs.update(quietness_kwargs) brain_extraction_in_files = [] for anat_filename in anat_filenames: out_unifize = unifize(in_file=anat_filename, outputtype='NIFTI_GZ', environ=environ, **brain_masking_unifize_kwargs) brain_extraction_in_files.append(out_unifize.outputs.out_file) brain_mask_files = [] for brain_extraction_in_file in brain_extraction_in_files: out_clip_level = clip_level(in_file=brain_extraction_in_file) out_rats = compute_mask(in_file=brain_extraction_in_file, volume_threshold=brain_volume, intensity_threshold=int( out_clip_level.outputs.clip_val)) brain_mask_files.append(out_rats.outputs.out_file) if unifize_kwargs is None: unifize_kwargs = {} unifize_kwargs.update(quietness_kwargs) unbiased_anat_filenames = [] for anat_filename in anat_filenames: out_unifize = unifize(in_file=anat_filename, environ=environ, urad=18.3, outputtype='NIFTI_GZ', **unifize_kwargs) unbiased_anat_filenames.append(out_unifize.outputs.out_file) affine_transforms = [] allineated_filenames = [] for (unbiased_anat_filename, brain_mask_file) in zip(unbiased_anat_filenames, brain_mask_files): out_calc_mask = calc(in_file_a=unbiased_anat_filename, in_file_b=brain_mask_file, expr='a*b', outputtype='NIFTI_GZ') masked_anat_filename = out_calc_mask.outputs.out_file # the actual T1anat to template registration using the brain extracted # image could do in one 3dQwarp step using allineate flags but will # separate as 3dAllineate performs well on brain image, and 3dQwarp # well on whole head affine_transform_filename = fname_presuffix(masked_anat_filename, suffix='_aff.aff12.1D', use_ext=False) out_allineate = allineate(in_file=masked_anat_filename, reference=head_template_filename, master=brain_template_filename, out_matrix=affine_transform_filename, two_blur=1, cost='nmi', convergence=convergence, two_pass=True, center_of_mass='', maxrot=90, out_file=fname_presuffix( masked_anat_filename, suffix='_aff'), environ=environ, **verbosity_quietness_kwargs) affine_transforms.append(affine_transform_filename) # Apply the registration to the whole head out_allineate2 = allineate2(in_file=unbiased_anat_filename, master=head_template_filename, in_matrix=affine_transform_filename, out_file=fname_presuffix( unbiased_anat_filename, suffix='_affine_general'), environ=environ, **verbosity_quietness_kwargs) allineated_filenames.append(out_allineate2.outputs.out_file) intermediate_files.extend([ unbiased_anat_filename, masked_anat_filename, out_allineate.outputs.out_file ]) intermediate_files.extend(allineated_filenames) warp_transforms = [] registered = [] for allineated_filename in allineated_filenames: # Non-linear registration of affine pre-registered whole head image # to template. Don't initiate straight from the original with an # iniwarp due to weird errors (like it creating an Allin it then can't # find) # XXX what is the need to the iwarp ? if maxlev is not None: out_qwarp = qwarp(in_file=allineated_filename, base_file=head_template_filename, weight=dilated_head_mask_filename, nmi=True, noneg=True, blur=[0], maxlev=maxlev, out_file=fname_presuffix(allineated_filename, suffix='_warped'), environ=environ, **verbosity_quietness_kwargs) else: out_qwarp = qwarp(in_file=allineated_filename, base_file=head_template_filename, weight=dilated_head_mask_filename, nmi=True, noneg=True, blur=[0], out_file=fname_presuffix(allineated_filename, suffix='_warped'), environ=environ, **verbosity_quietness_kwargs) registered.append(out_qwarp.outputs.warped_source) warp_transforms.append(out_qwarp.outputs.source_warp) os.chdir(current_dir) if not caching: for intermediate_file in intermediate_files: if os.path.isfile(intermediate_file): os.remove(intermediate_file) # XXX can't we just catenate the affine to the warp? return Bunch(registered=registered, transforms=warp_transforms, pre_transforms=affine_transforms)