def register( source: MincAtom, target: MincAtom, conf: ANTSConf, initial_source_transform: Optional[XfmAtom] = None, transform_name_wo_ext: str = None, generation: int = None, resample_source: bool = False, #resample_name_wo_ext: Optional[str] = None, resample_subdir: str = "resampled" ) -> Result[XfmHandler]: """ ... transform_name_wo_ext -- to use for the output transformation (without the extension) generation -- if provided, the transformation name will be: source.filename_wo_ext + "_ANTS_nlin-" + generation resample_source -- whether or not to resample the source file Construct a single call to ANTS. Also does blurring according to the specified options since the cost function might use these. """ s = Stages() if initial_source_transform is not None: raise ValueError("ANTs doesn't accept an initial transform") # if we resample the source, and place it in the "tmp" directory, we should do # the same with the transformation that is created: trans_output_dir = "transforms" if resample_source and resample_subdir == "tmp": trans_output_dir = "tmp" if transform_name_wo_ext: name = os.path.join(source.pipeline_sub_dir, source.output_sub_dir, trans_output_dir, "%s.xfm" % (transform_name_wo_ext)) elif generation is not None: name = os.path.join( source.pipeline_sub_dir, source.output_sub_dir, trans_output_dir, "%s_ANTS_nlin-%s.xfm" % (source.filename_wo_ext, generation)) else: name = os.path.join( source.pipeline_sub_dir, source.output_sub_dir, trans_output_dir, "%s_ANTS_to_%s.xfm" % (source.filename_wo_ext, target.filename_wo_ext)) out_xfm = XfmAtom(name=name, pipeline_sub_dir=source.pipeline_sub_dir, output_sub_dir=source.output_sub_dir) similarity_cmds = [] # type: List[str] similarity_inputs = set() # type: Set[MincAtom] # TODO: similarity_inputs should be a set, but `MincAtom`s aren't hashable for sim_metric_conf in conf.sim_metric_confs: if sim_metric_conf.use_gradient_image: if sim_metric_conf.blur is not None: gradient_blur_resolution = sim_metric_conf.blur elif conf.file_resolution is not None: gradient_blur_resolution = conf.file_resolution else: gradient_blur_resolution = None raise ValueError( "A similarity metric in the ANTS configuration " "wants to use the gradients, but I know neither the file resolution nor " "an intended nonnegative blur fwhm.") if gradient_blur_resolution <= 0: warnings.warn( "Not blurring the gradients as this was explicitly disabled" ) src = s.defer(mincblur(source, fwhm=gradient_blur_resolution)).gradient dest = s.defer(mincblur( target, fwhm=gradient_blur_resolution)).gradient else: # these are not gradient image terms; only blur if explicitly specified: if sim_metric_conf.blur is not None and sim_metric_conf.blur > 0: src = s.defer(mincblur(source, fwhm=sim_metric_conf.blur)).img dest = s.defer(mincblur(source, fwhm=sim_metric_conf.blur)).img else: src = source dest = target similarity_inputs.add(src) similarity_inputs.add(dest) inner = ','.join([ src.path, dest.path, str(sim_metric_conf.weight), str(sim_metric_conf.radius_or_bins) ]) subcmd = "'" + "".join([sim_metric_conf.metric, '[', inner, ']' ]) + "'" similarity_cmds.extend(["-m", subcmd]) stage = CmdStage( inputs=(source, target) + tuple(similarity_inputs) + cast(tuple, ((source.mask, ) if source.mask else ())), # need to cast to tuple due to mypy bug; see mypy/issues/622 outputs=(out_xfm, ), cmd=['ANTS', '3', '--number-of-affine-iterations', '0'] + similarity_cmds + [ '-t', conf.transformation_model, '-r', conf.regularization, '-i', conf.iterations, '-o', out_xfm.path ] + (['-x', source.mask.path] if conf.use_mask and source.mask else [])) # see comments re: mincblur memory configuration stage.when_runnable_hooks.append(lambda st: set_memory( st, source=source, conf=conf, mem_cfg=default_ANTS_mem_cfg)) s.add(stage) resampled = ( s.defer( mincresample( img=source, xfm=out_xfm, like=target, interpolation=Interpolation.sinc, #new_name_wo_ext=resample_name_wo_ext, subdir=resample_subdir)) if resample_source else None ) # type: Optional[MincAtom] return Result(stages=s, output=XfmHandler(source=source, target=target, xfm=out_xfm, resampled=resampled))
def register(source: MincAtom, target: MincAtom, conf: ANTSConf, initial_source_transform: Optional[XfmAtom] = None, transform_name_wo_ext: str = None, generation: int = None, resample_source: bool = False, #resample_name_wo_ext: Optional[str] = None, resample_subdir: str = "resampled") -> Result[XfmHandler]: """ ... transform_name_wo_ext -- to use for the output transformation (without the extension) generation -- if provided, the transformation name will be: source.filename_wo_ext + "_ANTS_nlin-" + generation resample_source -- whether or not to resample the source file Construct a single call to ANTS. Also does blurring according to the specified options since the cost function might use these. """ s = Stages() if initial_source_transform is not None: raise ValueError("ANTs doesn't accept an initial transform") # if we resample the source, and place it in the "tmp" directory, we should do # the same with the transformation that is created: trans_output_dir = "transforms" if resample_source and resample_subdir == "tmp": trans_output_dir = "tmp" if transform_name_wo_ext: name = os.path.join(source.pipeline_sub_dir, source.output_sub_dir, trans_output_dir, "%s.xfm" % (transform_name_wo_ext)) elif generation is not None: name = os.path.join(source.pipeline_sub_dir, source.output_sub_dir, trans_output_dir, "%s_ANTS_nlin-%s.xfm" % (source.filename_wo_ext, generation)) else: name = os.path.join(source.pipeline_sub_dir, source.output_sub_dir, trans_output_dir, "%s_ANTS_to_%s.xfm" % (source.filename_wo_ext, target.filename_wo_ext)) out_xfm = XfmAtom(name=name, pipeline_sub_dir=source.pipeline_sub_dir, output_sub_dir=source.output_sub_dir) similarity_cmds = [] # type: List[str] similarity_inputs = set() # type: Set[MincAtom] # TODO: similarity_inputs should be a set, but `MincAtom`s aren't hashable for sim_metric_conf in conf.sim_metric_confs: if sim_metric_conf.use_gradient_image: if sim_metric_conf.blur is not None: gradient_blur_resolution = sim_metric_conf.blur elif conf.file_resolution is not None: gradient_blur_resolution = conf.file_resolution else: gradient_blur_resolution = None raise ValueError("A similarity metric in the ANTS configuration " "wants to use the gradients, but I know neither the file resolution nor " "an intended nonnegative blur fwhm.") if gradient_blur_resolution <= 0: warnings.warn("Not blurring the gradients as this was explicitly disabled") src = s.defer(mincblur(source, fwhm=gradient_blur_resolution)).gradient dest = s.defer(mincblur(target, fwhm=gradient_blur_resolution)).gradient else: # these are not gradient image terms; only blur if explicitly specified: if sim_metric_conf.blur is not None and sim_metric_conf.blur > 0: src = s.defer(mincblur(source, fwhm=sim_metric_conf.blur)).img dest = s.defer(mincblur(source, fwhm=sim_metric_conf.blur)).img else: src = source dest = target similarity_inputs.add(src) similarity_inputs.add(dest) inner = ','.join([src.path, dest.path, str(sim_metric_conf.weight), str(sim_metric_conf.radius_or_bins)]) subcmd = "'" + "".join([sim_metric_conf.metric, '[', inner, ']']) + "'" similarity_cmds.extend(["-m", subcmd]) stage = CmdStage( inputs=(source, target) + tuple(similarity_inputs) + cast(tuple, ((source.mask,) if source.mask else ())), # need to cast to tuple due to mypy bug; see mypy/issues/622 outputs=(out_xfm,), cmd=['ANTS', '3', '--number-of-affine-iterations', '0'] + similarity_cmds + ['-t', conf.transformation_model, '-r', conf.regularization, '-i', conf.iterations, '-o', out_xfm.path] + (['-x', source.mask.path] if conf.use_mask and source.mask else [])) # see comments re: mincblur memory configuration stage.when_runnable_hooks.append(lambda st: set_memory(st, source=source, conf=conf, mem_cfg=default_ANTS_mem_cfg)) s.add(stage) resampled = (s.defer(mincresample(img=source, xfm=out_xfm, like=target, interpolation=Interpolation.sinc, #new_name_wo_ext=resample_name_wo_ext, subdir=resample_subdir)) if resample_source else None) # type: Optional[MincAtom] return Result(stages=s, output=XfmHandler(source=source, target=target, xfm=out_xfm, resampled=resampled))
def img_blur_56um_result(img): return mincblur(img=img, fwhm=0.056)
def cortical_thickness(xfms : pd.Series, # nlin avg -> subject XfmHandler (iirc)... atlas : MincAtom, # nlin avg label_mapping : FileAtom, atlas_fwhm : float, thickness_fwhm : float): try: import vtk except: warnings.warn("couldn't `import vtk`, without which `decimate.py` is unable to run ...") raise s = Stages() # generate thickness maps for the average: left_grid, right_grid = [s.defer(make_laplace_grid(input_labels=atlas.labels, label_mapping=label_mapping, binary_closing=True, side=side)) for side in (Side.left, Side.right)] atlas_left_thickness, atlas_right_thickness = ( [s.defer(decimate( s.defer(minclaplace(input_grid=grid, extra_args=["--create-surface-range", "0", "10"])).surface, # enclose entire cortex reduction=0.8, # FIXME: magic number ... implement a way to specify number rather than fraction instead? smoothing_method=Smoothing.laplace)) for grid in (left_grid, right_grid)]) # as per comment in MICe_thickness, blur atlas instead of transformed object files ... ? # (maybe this workaround is now obsolete) blurred_atlas = s.defer(mincblur(img=atlas, fwhm=atlas_fwhm)).img # TODO rename this dataframe resampled = (pd.DataFrame( { 'xfm' : xfms, # resample the atlas files to each subject: 'blurred_atlas_grid_resampled' : xfms.apply(lambda xfm: s.defer(mincresample_new(img=blurred_atlas, xfm=xfm.xfm, like=xfm.target))), 'atlas_left_resampled' : xfms.apply(lambda xfm: s.defer(transform_objects(input_obj=atlas_left_thickness, xfm=xfm.xfm))), 'atlas_right_resampled' : xfms.apply(lambda xfm: s.defer(transform_objects(input_obj=atlas_right_thickness, xfm=xfm.xfm))), }) .assign(left_grid=lambda df: df.xfm.map(lambda xfm: s.defer( make_laplace_grid(input_labels=xfm.target, label_mapping=label_mapping, binary_closing=True, side=Side.left))), right_grid=lambda df: df.xfm.map(lambda xfm: s.defer( make_laplace_grid(input_labels=xfm.target, label_mapping=label_mapping, binary_closing=True, side=Side.right)))) .assign(left_thickness=lambda df: df.apply(axis=1, func=lambda row: s.defer(minclaplace(input_grid=row.left_grid, solution_vertices=row.atlas_left_resampled))), right_thickness=lambda df: df.apply(axis=1, func=lambda row: s.defer(minclaplace(input_grid=row.right_grid, solution_vertices=row.atlas_right_resampled)))) .assign(smooth_left_fwhm=lambda df: df.apply(axis=1, func=lambda row: s.defer(diffuse(obj_file=row.atlas_left_resampled, input_signal=row.left_thickness.solved, kernel=thickness_fwhm, iterations=1000))), smooth_right_fwhm=lambda df: df.apply(axis=1, func=lambda row: s.defer(diffuse(obj_file=row.atlas_right_resampled, input_signal=row.right_thickness.solved, kernel=thickness_fwhm, iterations=1000))))) return Result(stages=s, output=resampled)
def cortical_thickness( xfms: pd.Series, # nlin avg -> subject XfmHandler (iirc)... atlas: MincAtom, # nlin avg label_mapping: FileAtom, atlas_fwhm: float, thickness_fwhm: float): try: import vtk except: warnings.warn( "couldn't `import vtk`, without which `decimate.py` is unable to run ..." ) raise s = Stages() # generate thickness maps for the average: left_grid, right_grid = [ s.defer( make_laplace_grid(input_labels=atlas.labels, label_mapping=label_mapping, binary_closing=True, side=side)) for side in (Side.left, Side.right) ] atlas_left_thickness, atlas_right_thickness = ([ s.defer( decimate( s.defer( minclaplace( input_grid=grid, extra_args=["--create-surface-range", "0", "10"])).surface, # enclose entire cortex reduction= 0.8, # FIXME: magic number ... implement a way to specify number rather than fraction instead? smoothing_method=Smoothing.laplace)) for grid in (left_grid, right_grid) ]) # as per comment in MICe_thickness, blur atlas instead of transformed object files ... ? # (maybe this workaround is now obsolete) blurred_atlas = s.defer(mincblur(img=atlas, fwhm=atlas_fwhm)).img # TODO rename this dataframe resampled = ( pd.DataFrame({ 'xfm': xfms, # resample the atlas files to each subject: 'blurred_atlas_grid_resampled': xfms.apply(lambda xfm: s.defer( mincresample_new( img=blurred_atlas, xfm=xfm.xfm, like=xfm.target))), 'atlas_left_resampled': xfms.apply(lambda xfm: s.defer( transform_objects(input_obj=atlas_left_thickness, xfm=xfm.xfm)) ), 'atlas_right_resampled': xfms.apply(lambda xfm: s.defer( transform_objects(input_obj=atlas_right_thickness, xfm=xfm.xfm) )), }).assign(left_grid=lambda df: df.xfm.map(lambda xfm: s.defer( make_laplace_grid(input_labels=xfm.target, label_mapping=label_mapping, binary_closing=True, side=Side.left))), right_grid=lambda df: df.xfm.map(lambda xfm: s.defer( make_laplace_grid(input_labels=xfm.target, label_mapping=label_mapping, binary_closing=True, side=Side.right)))). assign(left_thickness=lambda df: df.apply( axis=1, func=lambda row: s.defer( minclaplace(input_grid=row.left_grid, solution_vertices=row.atlas_left_resampled))), right_thickness=lambda df: df.apply( axis=1, func=lambda row: s.defer( minclaplace(input_grid=row.right_grid, solution_vertices=row.atlas_right_resampled) ))).assign( smooth_left_fwhm=lambda df: df.apply( axis=1, func=lambda row: s.defer( diffuse(obj_file=row.atlas_left_resampled, input_signal=row.left_thickness.solved, kernel=thickness_fwhm, iterations=1000))), smooth_right_fwhm=lambda df: df.apply( axis=1, func=lambda row: s.defer( diffuse(obj_file=row.atlas_right_resampled, input_signal=row.right_thickness.solved, kernel=thickness_fwhm, iterations=1000))))) return Result(stages=s, output=resampled)
def antsRegistration(source: MincAtom, target: MincAtom, conf: ANTSRegistrationConf, initial_source_transform: Optional[XfmAtom] = None, initial_target_transform: Optional[XfmAtom] = None, # TODO create source_to_target_transform_name_wo_ext, target_to_source_... transform_name_wo_ext: Optional[str] = None, generation: Optional[int] = None, subdir: Optional[str] = None, resample_source: bool = False, resample_target: bool = False, resample_subdir: str = 'tmp'): """ :param source: fixedImage :param target: movingImage :param conf: :param initial_source_transform: for --initial-fixed-transform :param initial_target_transform: for --initial-moving-transform :param transform_name_wo_ext: name of the target_to_source transform will be based on this :param generation: :param subdir: :param resample_source: :return: """ s = Stages() source_subdir = subdir if subdir is not None else 'transforms' # Deal with the transformations first. This function will return two XfmHandlers. One # from source to target, and one in the other direction. if transform_name_wo_ext: xfm_source_to_target = XfmAtom(name=os.path.join(source.pipeline_sub_dir, source.output_sub_dir, source_subdir, "%s.xfm" % transform_name_wo_ext), pipeline_sub_dir=source.pipeline_sub_dir, output_sub_dir=source.output_sub_dir) elif generation is not None: xfm_source_to_target = XfmAtom(name=os.path.join(source.pipeline_sub_dir, source.output_sub_dir, source_subdir, "%s_antsR_to_%s_nlin_%s.xfm" % (source.filename_wo_ext, target.filename_wo_ext, generation)), pipeline_sub_dir=source.pipeline_sub_dir, output_sub_dir=source.output_sub_dir) else: xfm_source_to_target = XfmAtom(name=os.path.join(source.pipeline_sub_dir, source.output_sub_dir, source_subdir, "%s_antsR_to_%s.xfm" % (source.filename_wo_ext, target.filename_wo_ext)), pipeline_sub_dir=source.pipeline_sub_dir, output_sub_dir=source.output_sub_dir) # model the target_to_source in a similar manner. Given that # antsRegistration will be passed the "output prefix" for the transform, # being the whole filename with .xfm, this transform will live in a # directory belonging to the source image. # TODO: is this what we want? perhaps we actually want to move this transformation # over to a subdirectory of the target file... # N.B.: the name of the following file is forced by antsRegistration; we can't specify a subdir or anything... # DON'T CHANGE THIS! xfm_target_to_source = xfm_source_to_target.newname_with_suffix("_inverse", subdir=source_subdir) # outputs from antRegistration are: # {output_prefix}_grid_0.mnc # {output_prefix}.xfm # {output_prefix}_inverse_grid_0.mnc # {output_prefix}_inverse.xfm # Outputs: # 1) transform from source_to_target # 2) transform from target_to_source --> need to create an XfmHandler for this one def optional(x, f, default=[]): return f(x) if x is not None else [] # TODO: use a proper configuration to set the parameters # TODO: add a second metric for the gradients (and get gradient files) if any(m.use_gradient_image for m in conf.metrics): if conf.file_resolution is None: raise ValueError("A similarity metric in the ANTS configuration " "wants to use the gradients, but the file resolution for the " "configuration has not been set.") blurred_source, blurred_target = [s.defer(mincblur(img, fwhm=conf.file_resolution)).gradient for img in (source, target)] else: blurred_source = blurred_target = None def render_metric(m : Metric): if m.use_gradient_image: if conf.file_resolution is None: raise ValueError("A similarity metric in the ANTS configuration " "wants to use the gradients, but the file resolution for the " "configuration has not been set.") fixed = blurred_source moving = blurred_target else: fixed = source moving = target return "'%s[%s,%s,%s,%s]'" % (m.metric, fixed.path, moving.path, m.weight, m.radius_or_bins) if conf.use_masks: if source.mask is not None and target.mask is not None: mask_arr = ['--masks', '[%s,%s]' % (source.mask.path, target.mask.path)] elif source.mask is not None: mask_arr = ['--masks', '[%s]' % source.mask.path] elif target.mask is not None: warnings.warn("only target mask is specified; antsRegistration needs at least a source mask") mask_arr = [] else: warnings.warn("no masks supplied") mask_arr = [] else: mask_arr = [] cmd = CmdStage( inputs=tuple(img for img in (source, target, source.mask, target.mask, blurred_source, blurred_target, initial_source_transform, initial_target_transform) if img is not None), outputs=(xfm_source_to_target, xfm_target_to_source), cmd=['antsRegistration'] + optional(conf.dimensionality, lambda d: ['--dimensionality', "%d" % d]) + ['--convergence', render_convergence_conf(conf.convergence)] + ['--verbose'] + ['--minc'] + ['--collapse-output-transforms', '1'] + ['--write-composite-transform'] + ['--winsorize-image-intensities', '[0.01,0.99]'] + optional(conf.use_histogram_matching, lambda _: ['--use-histogram-matching', '1']) + ['--float', '0'] + ['--output', '[' + os.path.join(xfm_source_to_target.dir, xfm_source_to_target.filename_wo_ext) + ']'] + ['--transform', conf.transformation_model] + optional(initial_source_transform, lambda xfm: ['--initial-fixed-transform', xfm.path]) + optional(initial_target_transform, lambda xfm: ['--initial-moving-transform', xfm.path]) + flatten(*[['--metric', render_metric(m)] for m in conf.metrics]) + mask_arr + ['--shrink-factors', 'x'.join(str(s) for s in conf.shrink_factors)] + ['--smoothing-sigmas', 'x'.join(str(s) for s in conf.smoothing_sigmas)] ) # shamelessly stolen from ANTS, probably inaccurate # see comments re: mincblur memory configuration def set_memory(st, mem_cfg): # see comments re: mincblur memory configuration voxels = reduce(mul, volumeFromFile(source.path).getSizes()) mem_per_voxel = (mem_cfg.mem_per_voxel_coarse if 0 in conf.convergence.iterations[-1:] #-2? # yikes ... this parsing should be done earlier else mem_cfg.mem_per_voxel_fine) st.setMem(mem_cfg.base_mem + voxels * mem_per_voxel) cmd.when_runnable_hooks.append(lambda st: set_memory(st, mem_cfg=default_ANTSRegistration_mem_cfg)) s.add(cmd) # create file names for the two output files. It's better to use our standard # mincresample command for this, because that also deals with any associated # masks, whereas antsRegistration would only resample the input files. resampled_source = (s.defer(mincresample(img=source, xfm=xfm_source_to_target, like=target, interpolation=Interpolation.sinc, subdir=resample_subdir)) if resample_source else None) resampled_target = (s.defer(mincresample(img=target, xfm=xfm_target_to_source, like=source, interpolation=Interpolation.sinc, subdir=resample_subdir)) if resample_target else None) # return an XfmHandler for both the forward and the inverse transformations return Result(stages=s, output=XfmHandler(source=source, target=target, xfm=xfm_source_to_target, resampled=resampled_source, inverse=XfmHandler(source=target, target=source, xfm=xfm_target_to_source, resampled=resampled_target)))
def antsRegistration( source: MincAtom, target: MincAtom, conf: ANTSRegistrationConf, initial_source_transform: Optional[XfmAtom] = None, initial_target_transform: Optional[XfmAtom] = None, # TODO create source_to_target_transform_name_wo_ext, target_to_source_... transform_name_wo_ext: Optional[str] = None, generation: Optional[int] = None, subdir: Optional[str] = None, resample_source: bool = False, resample_target: bool = False, resample_subdir: str = 'tmp'): """ :param source: fixedImage :param target: movingImage :param conf: :param initial_source_transform: for --initial-fixed-transform :param initial_target_transform: for --initial-moving-transform :param transform_name_wo_ext: name of the target_to_source transform will be based on this :param generation: :param subdir: :param resample_source: :return: """ s = Stages() source_subdir = subdir if subdir is not None else 'transforms' # Deal with the transformations first. This function will return two XfmHandlers. One # from source to target, and one in the other direction. if transform_name_wo_ext: xfm_source_to_target = XfmAtom( name=os.path.join(source.pipeline_sub_dir, source.output_sub_dir, source_subdir, "%s.xfm" % transform_name_wo_ext), pipeline_sub_dir=source.pipeline_sub_dir, output_sub_dir=source.output_sub_dir) elif generation is not None: xfm_source_to_target = XfmAtom( name=os.path.join( source.pipeline_sub_dir, source.output_sub_dir, source_subdir, "%s_antsR_to_%s_nlin_%s.xfm" % (source.filename_wo_ext, target.filename_wo_ext, generation)), pipeline_sub_dir=source.pipeline_sub_dir, output_sub_dir=source.output_sub_dir) else: xfm_source_to_target = XfmAtom( name=os.path.join( source.pipeline_sub_dir, source.output_sub_dir, source_subdir, "%s_antsR_to_%s.xfm" % (source.filename_wo_ext, target.filename_wo_ext)), pipeline_sub_dir=source.pipeline_sub_dir, output_sub_dir=source.output_sub_dir) # model the target_to_source in a similar manner. Given that # antsRegistration will be passed the "output prefix" for the transform, # being the whole filename with .xfm, this transform will live in a # directory belonging to the source image. # TODO: is this what we want? perhaps we actually want to move this transformation # over to a subdirectory of the target file... # N.B.: the name of the following file is forced by antsRegistration; we can't specify a subdir or anything... # DON'T CHANGE THIS! xfm_target_to_source = xfm_source_to_target.newname_with_suffix( "_inverse", subdir=source_subdir) # outputs from antRegistration are: # {output_prefix}_grid_0.mnc # {output_prefix}.xfm # {output_prefix}_inverse_grid_0.mnc # {output_prefix}_inverse.xfm # Outputs: # 1) transform from source_to_target # 2) transform from target_to_source --> need to create an XfmHandler for this one def optional(x, f, default=[]): return f(x) if x is not None else [] # TODO: use a proper configuration to set the parameters # TODO: add a second metric for the gradients (and get gradient files) if any(m.use_gradient_image for m in conf.metrics): if conf.file_resolution is None: raise ValueError( "A similarity metric in the ANTS configuration " "wants to use the gradients, but the file resolution for the " "configuration has not been set.") blurred_source, blurred_target = [ s.defer(mincblur(img, fwhm=conf.file_resolution)).gradient for img in (source, target) ] else: blurred_source = blurred_target = None def render_metric(m: Metric): if m.use_gradient_image: if conf.file_resolution is None: raise ValueError( "A similarity metric in the ANTS configuration " "wants to use the gradients, but the file resolution for the " "configuration has not been set.") fixed = blurred_source moving = blurred_target else: fixed = source moving = target return "'%s[%s,%s,%s,%s]'" % (m.metric, fixed.path, moving.path, m.weight, m.radius_or_bins) if conf.use_masks: if source.mask is not None and target.mask is not None: mask_arr = [ '--masks', '[%s,%s]' % (source.mask.path, target.mask.path) ] elif source.mask is not None: mask_arr = ['--masks', '[%s]' % source.mask.path] elif target.mask is not None: warnings.warn( "only target mask is specified; antsRegistration needs at least a source mask" ) mask_arr = [] else: warnings.warn("no masks supplied") mask_arr = [] else: mask_arr = [] cmd = CmdStage( inputs=tuple(img for img in (source, target, source.mask, target.mask, blurred_source, blurred_target, initial_source_transform, initial_target_transform) if img is not None), outputs=(xfm_source_to_target, xfm_target_to_source), cmd=['antsRegistration'] + optional(conf.dimensionality, lambda d: ['--dimensionality', "%d" % d]) + ['--convergence', render_convergence_conf(conf.convergence)] + ['--verbose'] + ['--minc'] + ['--collapse-output-transforms', '1'] + ['--write-composite-transform'] + ['--winsorize-image-intensities', '[0.01,0.99]'] + optional(conf.use_histogram_matching, lambda _: ['--use-histogram-matching', '1']) + ['--float', '0'] + [ '--output', '[' + os.path.join(xfm_source_to_target.dir, xfm_source_to_target.filename_wo_ext) + ']' ] + ['--transform', conf.transformation_model] + optional(initial_source_transform, lambda xfm: ['--initial-fixed-transform', xfm.path]) + optional(initial_target_transform, lambda xfm: ['--initial-moving-transform', xfm.path]) + flatten(*[['--metric', render_metric(m)] for m in conf.metrics]) + mask_arr + ['--shrink-factors', 'x'.join(str(s) for s in conf.shrink_factors)] + [ '--smoothing-sigmas', 'x'.join( str(s) for s in conf.smoothing_sigmas) ]) # shamelessly stolen from ANTS, probably inaccurate # see comments re: mincblur memory configuration def set_memory(st, mem_cfg): # see comments re: mincblur memory configuration voxels = reduce(mul, volumeFromFile(source.path).getSizes()) mem_per_voxel = ( mem_cfg.mem_per_voxel_coarse if 0 in conf.convergence.iterations[-1:] #-2? # yikes ... this parsing should be done earlier else mem_cfg.mem_per_voxel_fine) st.setMem(mem_cfg.base_mem + voxels * mem_per_voxel) cmd.when_runnable_hooks.append( lambda st: set_memory(st, mem_cfg=default_ANTSRegistration_mem_cfg)) s.add(cmd) # create file names for the two output files. It's better to use our standard # mincresample command for this, because that also deals with any associated # masks, whereas antsRegistration would only resample the input files. resampled_source = (s.defer( mincresample(img=source, xfm=xfm_source_to_target, like=target, interpolation=Interpolation.sinc, subdir=resample_subdir)) if resample_source else None) resampled_target = (s.defer( mincresample(img=target, xfm=xfm_target_to_source, like=source, interpolation=Interpolation.sinc, subdir=resample_subdir)) if resample_target else None) # return an XfmHandler for both the forward and the inverse transformations return Result(stages=s, output=XfmHandler(source=source, target=target, xfm=xfm_source_to_target, resampled=resampled_source, inverse=XfmHandler( source=target, target=source, xfm=xfm_target_to_source, resampled=resampled_target)))