def _reset_affines(in_file, out_file, overwrite=False, axes_to_permute=None, axes_to_flip=None, xyzscale=None, center_mass=None, verbose=1): """Sets the qform equal to the sform in the header, with optionally rescaling, setting the image center to 0, permuting or/and flipping axes """ if not os.path.isfile(out_file) or overwrite: shutil.copy(in_file, out_file) else: return if verbose: terminal_output = 'stream' else: terminal_output = 'none' in_file = out_file if xyzscale is not None: refit = afni.Refit() refit.inputs.in_file = in_file refit.inputs.xyzscale = xyzscale refit.set_default_terminal_output(terminal_output) result = refit.run() in_file = result.outputs.out_file if center_mass is not None: set_center_mass = afni.CenterMass() set_center_mass.inputs.in_file = in_file set_center_mass.inputs.cm_file = fname_presuffix(out_file, suffix='.txt', use_ext=False) set_center_mass.inputs.set_cm = center_mass # set_center_mass.set_default_terminal_output(terminal_output) # XXX BUG result = set_center_mass.run() in_file = result.outputs.out_file img = nibabel.load(in_file) header = img.header.copy() sform, code = header.get_sform(coded=True) if axes_to_flip: for axis in axes_to_flip: sform[axis] *= -1 if axes_to_permute: for (axis1, axis2) in axes_to_permute: sform[[axis1, axis2]] = sform[[axis2, axis1]] header.set_sform(sform) header.set_qform(sform, int(code)) nibabel.Nifti1Image(img.get_data(), sform, header).to_filename(out_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 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 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)