Esempio n. 1
0
 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)
Esempio n. 2
0
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)
Esempio n. 3
0
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)
Esempio n. 4
0
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)
Esempio n. 5
0
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)
Esempio n. 6
0
 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,)))
Esempio n. 7
0
 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)
Esempio n. 8
0
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)
Esempio n. 9
0
 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)
Esempio n. 11
0
    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))
Esempio n. 12
0
  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)))
Esempio n. 14
0
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)))