def to_mni_xfm(xfm): s = Stages() defs = xfm.newname_with_suffix("_defs", subdir="tmp") s.add(CmdStage(cmd=["transformix", "-def", "all", "-out", defs.dir, "-tp", xfm.path, "-xfm", os.path.join(defs.filename_wo_ext, defs.ext)], inputs=(xfm,), outputs=(defs,))) out_xfm = s.defer(itk.itk_convert_xfm(defs, out_ext=".mnc")) return Result(stages=s, output=out_xfm)
def convert(infile : ImgAtom, out_ext : str) -> Result[ImgAtom]: s = Stages() outfile = infile.newext(ext=out_ext) if infile.mask is not None: outfile.mask = s.defer(convert(infile.mask, out_ext=out_ext)) if infile.labels is not None: outfile.mask = s.defer(convert(infile.labels, out_ext=out_ext)) s.add(CmdStage(inputs=(infile,), outputs=(outfile,), cmd = ['c3d', infile.path, '-o', outfile.path])) return Result(stages=s, output=outfile)
def average_images( imgs: Sequence[ImgAtom], dimensions: int = 3, normalize: bool = False, output_dir: str = '.', name_wo_ext: str = "average", out_ext: Optional[str] = None, avg_file: Optional[ITKImgAtom] = None) -> Result[ITKImgAtom]: s = Stages() if len(imgs) == 0: raise ValueError( "`AverageImages` arg `imgs` is empty (can't average zero files)") ext = out_ext or imgs[0].ext # the output_dir basically gives us the equivalent of the pipeline_sub_dir for # regular input files to a pipeline, so use that here avg = avg_file or ImgAtom(name=os.path.join(output_dir, '%s.todo' % name_wo_ext), orig_name=None, pipeline_sub_dir=output_dir) avg.ext = ext # if all input files have masks associated with them, add the combined mask to # the average: # TODO what if avg_file has a mask ... should that be used instead? (then rename avg -> avg_file above) all_inputs_have_masks = all((img.mask for img in imgs)) if all_inputs_have_masks: combined_mask = (ImgAtom(name=os.path.join( avg_file.dir, '%s_mask.todo' % avg_file.filename_wo_ext), orig_name=None, pipeline_sub_dir=avg_file.pipeline_sub_dir) if avg_file is not None else ImgAtom( name=os.path.join(output_dir, '%s_mask.todo' % name_wo_ext), orig_name=None, pipeline_sub_dir=output_dir)) combined_mask.ext = ext s.defer( max(imgs=sorted({img_inst.mask for img_inst in imgs}), out_img=combined_mask)) avg.mask = combined_mask s.add( CmdStage( inputs=imgs, outputs=(avg, ), cmd=["AverageImages", str(dimensions), avg.path, "%d" % normalize] + [img.path for img in imgs])) return Result(stages=s, output=avg)
def convert(infile: ImgAtom, out_ext: str) -> Result[ImgAtom]: s = Stages() outfile = infile.newext(ext=out_ext) if infile.mask is not None: outfile.mask = s.defer(convert(infile.mask, out_ext=out_ext)) if infile.labels is not None: outfile.mask = s.defer(convert(infile.labels, out_ext=out_ext)) s.add( CmdStage(inputs=(infile, ), outputs=(outfile, ), cmd=['c3d', infile.path, '-o', outfile.path])) return Result(stages=s, output=outfile)
def dramms_warp_simple(img: NiiAtom, xfm: DrammsXfmAtom, like: Optional[NiiAtom] = None, extra_flags: Tuple[str] = (), use_nn_interpolation=None, invert: bool = False, new_name_wo_ext: str = None, subdir: str = None) -> Result[NiiAtom]: """ Resample an image, ignoring mask/labels ... new_name_wo_ext -- string indicating a user specified file name (without extension) """ s = Stages() if invert: inv_xfm = xfm.newname_with_suffix("_inverted") s.add( CmdStage(cmd=["dramms-defop", "-i", xfm.path, inv_xfm.path], inputs=(xfm, ), outputs=(inv_xfm, ))) xfm = inv_xfm if not subdir: subdir = 'resampled' if not new_name_wo_ext: # FIXME the path to `outf` is wrong. For instance, resampling a mask file ends up in the initial model # FIXME directory instead of in the resampled directory. # FIXME At the same time, `like.newname(...)` doesn't work either, for similar reasons. # FIXME Not clear if there's a general "automagic" fix for this. # FIXME Also, using the xfm's filename is wrong, since we might be resampling, e.g., a mask. # FIXME We should basically use the same naming scheme as is used to generate the xfm's name but # FIXME use the files for resampling, not registration outf = img.newname(name=xfm.filename_wo_ext + '-resampled', subdir=subdir) else: # we have the output filename without extension. This should replace the entire # current "base" of the filename. outf = img.newname(name=new_name_wo_ext, subdir=subdir) stage = CmdStage( inputs=(xfm, like, img), outputs=(outf, ), cmd=(['dramms-warp'] + (['-n'] if use_nn_interpolation else []) + (['-transform %s' % xfm.path]) #if xfm is not identity else []) + ['-like %s' % like.path, img.path, outf.path])) s.add(stage) return Result(stages=s, output=outf)
def average_transforms(xfms, avg_xfm): intermediate_xfm = avg_xfm.newname_with_suffix("_inter", subdir="tmp") s = Stages() s.add(CmdStage(cmd=["echo", ('(Transform "WeightedCombinationTransform")\n' '(SubTransforms %s)\n' '(NormalizeCombinationsWeights "true")\n') % ' '.join(sorted(xfm.path for xfm in xfms))], inputs=xfms, outputs=(intermediate_xfm,))) s.add(CmdStage(cmd=["transformix", "-def", "all", "-out", os.path.dirname(avg_xfm.path), "-tp", intermediate_xfm.path, "-xfm", avg_xfm.path], inputs=(intermediate_xfm,), outputs=(avg_xfm,)))
def to_mni_xfm(xfm): s = Stages() defs = xfm.newname_with_suffix("_defs", subdir="tmp") s.add( CmdStage(cmd=[ "transformix", "-def", "all", "-out", defs.dir, "-tp", xfm.path, "-xfm", os.path.join(defs.filename_wo_ext, defs.ext) ], inputs=(xfm, ), outputs=(defs, ))) out_xfm = s.defer(itk.itk_convert_xfm(defs, out_ext=".mnc")) return Result(stages=s, output=out_xfm)
def average_images(imgs : Sequence[ImgAtom], dimensions : int = 3, normalize : bool = False, output_dir : str = '.', name_wo_ext : str = "average", out_ext : Optional[str] = None, avg_file : Optional[ITKImgAtom] = None) -> Result[ITKImgAtom]: s = Stages() if len(imgs) == 0: raise ValueError("`AverageImages` arg `imgs` is empty (can't average zero files)") ext = out_ext or imgs[0].ext # the output_dir basically gives us the equivalent of the pipeline_sub_dir for # regular input files to a pipeline, so use that here avg = avg_file or ImgAtom(name=os.path.join(output_dir, '%s.todo' % name_wo_ext), orig_name=None, pipeline_sub_dir=output_dir) avg.ext = ext # if all input files have masks associated with them, add the combined mask to # the average: # TODO what if avg_file has a mask ... should that be used instead? (then rename avg -> avg_file above) all_inputs_have_masks = all((img.mask for img in imgs)) if all_inputs_have_masks: combined_mask = (ImgAtom(name=os.path.join(avg_file.dir, '%s_mask.todo' % avg_file.filename_wo_ext), orig_name=None, pipeline_sub_dir=avg_file.pipeline_sub_dir) if avg_file is not None else ImgAtom(name=os.path.join(output_dir, '%s_mask.todo' % name_wo_ext), orig_name=None, pipeline_sub_dir=output_dir)) combined_mask.ext = ext s.defer(max(imgs=sorted({img_inst.mask for img_inst in imgs}), out_img=combined_mask)) avg.mask = combined_mask s.add(CmdStage(inputs = imgs, outputs = (avg,), cmd = ["AverageImages", str(dimensions), avg.path, "%d" % normalize] + [img.path for img in imgs])) return Result(stages=s, output=avg)
def average_transforms(xfms, avg_xfm): intermediate_xfm = avg_xfm.newname_with_suffix("_inter", subdir="tmp") s = Stages() s.add( CmdStage(cmd=[ "echo", ('(Transform "WeightedCombinationTransform")\n' '(SubTransforms %s)\n' '(NormalizeCombinationsWeights "true")\n') % ' '.join(sorted(xfm.path for xfm in xfms)) ], inputs=xfms, outputs=(intermediate_xfm, ))) s.add( CmdStage(cmd=[ "transformix", "-def", "all", "-out", os.path.dirname(avg_xfm.path), "-tp", intermediate_xfm.path, "-xfm", avg_xfm.path ], inputs=(intermediate_xfm, ), outputs=(avg_xfm, )))
def shift_modify_header(img: MincAtom, shifted_img: MincAtom, newx: float, newy: float, newz: float): s = Stages() #Copy file to new location stage = CmdStage(inputs=(img,), outputs=(shifted_img,), memory=1, cmd=['cp', img.path, shifted_img.path]) print(stage.render()) s.add(stage) #Alter header of copied image to shift xspace_start = 'xspace:start='+newx yspace_start = 'yspace:start='+newy zspace_start = 'zspace:start='+newz stage = CmdStage(inputs=(shifted_img,), outputs=(shifted_img,), memory=1, cmd=['minc_modify_header','-dinsert',xspace_start, '-dinsert',yspace_start, '-dinsert',zspace_start, shifted_img.path]) print(stage.render()) s.add(stage) #Alter header of copied image with header modification append_history_string = ':history= >>> copy and shift: '+ shifted_img.path +' to '+ shifted_img.path stage = CmdStage(inputs=(shifted_img,), outputs=(shifted_img,), memory=1, cmd=['minc_modify_header','-sappend', append_history_string, shifted_img.path]) print(stage.render()) s.add(stage) return Result(stages=s, output=shifted_img)
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 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)))