Beispiel #1
0
def maget(imgs: List[MincAtom],
          options,
          prefix,
          output_dir,
          build_model_xfms=None):
    # FIXME prefix, output_dir aren't used !!

    s = Stages()

    maget_options = options.maget.maget

    resolution = options.registration.resolution  # TODO or get_resolution_from_file(...) -- only if file always exists!

    pipeline_sub_dir = os.path.join(
        options.application.output_directory,
        options.application.pipeline_name + "_atlases")

    atlases = get_atlases(maget_options, pipeline_sub_dir)

    lsq12_conf = get_linear_configuration_from_options(
        options.maget.lsq12,
        transform_type=LinearTransType.lsq12,
        file_resolution=resolution)

    nlin_component = get_nonlinear_component(options.maget.nlin.reg_method)

    # TODO should this be here or outside `maget` call?
    #imgs = [s.defer(nlin_component.ToMinc.from_mnc(img)) for img in imgs]

    #nlin_hierarchy = get_nonlinear_configuration_from_options(options.maget.nlin.nlin_protocol,
    #                                                          next(iter(options.maget.nlin.flags_.nlin_protocol)),
    #                                                          reg_method=options.maget.nlin.reg_method,
    #                                                          file_resolution=resolution)

    if maget_options.mask or maget_options.mask_only:

        # this used to return alignments but doesn't currently do so
        masked_img = s.defer(
            maget_mask(
                imgs=imgs,
                maget_options=options.maget,
                atlases=atlases,
                pipeline_sub_dir=pipeline_sub_dir +
                "_masking",  # FIXME repeats all alignments!!!
                resolution=resolution))

        # now propagate only the masked form of the images and atlases:
        imgs = masked_img
        #atlases = masked_atlases  # TODO is this needed?

    if maget_options.mask_only:
        # register each input to each atlas, creating a mask
        return Result(
            stages=s,
            output=masked_img)  # TODO rename `alignments` to `registrations`??
    else:
        if maget_options.mask:
            del masked_img
        # this `del` is just to verify that we don't accidentally use this later, since these potentially
        # coarser alignments shouldn't be re-used (but if the protocols for masking and alignment are the same,
        # hash-consing will take care of things), just the masked images they create; can be removed later
        # if a sensible use is found

        # images with labels from atlases
        # N.B.: Even though we've already registered each image to each initial atlas, this happens again here,
        #       but using `nlin_hierarchy` instead of `masking_nlin_hierarchy` as options.
        #       This is not 'work-efficient' in the sense that this computation happens twice (although
        #       hopefully at greater precision the second time!), but the idea is to run a coarse initial
        #       registration to get a mask and then do a better registration with that mask (though I'm not
        #       sure exactly when this is faster than doing a single registration).
        #       This _can_ allow the overall computation to finish more rapidly
        #       (depending on the relative speed of the two alignment methods/parameters,
        #       number of atlases and other templates used, number of cores available, etc.).
        atlas_labelled_imgs = (
            pd.DataFrame({
                'img':
                img,
                'label_file':
                s.defer(  # can't use `label` in a pd.DataFrame index!
                    mincresample_new(
                        img=atlas.labels,
                        xfm=s.defer(
                            lsq12_nlin(
                                source=img,
                                target=atlas,
                                nlin_module=nlin_component,
                                lsq12_conf=lsq12_conf,
                                nlin_options=options.maget.nlin.nlin_protocol,
                                resolution=resolution,
                                #nlin_conf=nlin_hierarchy,
                                resample_source=False)).xfm,
                        like=img,
                        invert=True,
                        interpolation=Interpolation.nearest_neighbour,
                        extra_flags=('-keep_real_range', '-labels')))
            } for img in imgs for atlas in atlases))

        if maget_options.pairwise:

            def choose_new_templates(ts, n):
                # currently silly, but we might implement a smarter method ...
                # FIXME what if there aren't enough other imgs around?!  This silently goes weird ...
                return pd.Series(
                    ts[:n + 1]
                )  # n+1 instead of n: choose one more since we won't use image as its own template ...

            # FIXME: the --max-templates flag is ambiguously named ... should be --max-new-templates
            # (and just use all atlases)
            # TODO we could have a separate templates_csv (or --template-files f [f ...]) but you can just
            # run a separate MAGeT pipeline and
            #if maget_options.templates_csv:
            #    templates = pd.read_csv(maget_options.templates_csv).template
            #else:
            templates = pd.DataFrame({
                'template':
                choose_new_templates(ts=imgs,
                                     n=maget_options.max_templates -
                                     len(atlases))
            })
            # note these images are the masked ones if masking was done ...

            # the templates together with their (multiple) labels from the atlases (this merge just acts as a filter)
            labelled_templates = pd.merge(left=atlas_labelled_imgs,
                                          right=templates,
                                          left_on="img",
                                          right_on="template").drop('img',
                                                                    axis=1)

            # images with new labels from the templates
            imgs_and_templates = pd.merge(  #left=atlas_labelled_imgs,
                left=pd.DataFrame({
                    "img": imgs
                }).assign(fake=1),
                right=labelled_templates.assign(fake=1),
                on='fake')
            #left_on='img', right_on='template')  # TODO do select here instead of below?

            #if build_model_xfms is not None:
            #    # use path instead of full mincatom as key in case we're reading these in from a CSV:
            #    xfm_dict = { x.source.path : x.xfm for x in build_model_xfms }

            template_labelled_imgs = (
                imgs_and_templates.rename(
                    columns={'label_file': 'template_label_file'})
                # don't register template to itself, since otherwise atlases would vote on that template twice
                .loc[lambda df: df.index.map(lambda ix: df.img[ix].path != df.
                                             template[ix].path)].
                assign(label_file=lambda df: df.apply(
                    axis=1,
                    func=lambda row: s.defer(
                        # TODO switch to uses of nlin_component.whatever(...) in several places below?
                        mincresample_new(
                            #nlin_component.Algorithms.resample(
                            img=row.template_label_file,
                            xfm=s.defer(
                                lsq12_nlin(
                                    source=row.img,
                                    target=row.template,
                                    lsq12_conf=lsq12_conf,
                                    resolution=resolution,
                                    nlin_module=nlin_component,
                                    nlin_options=options.maget.nlin.
                                    nlin_protocol,
                                    #nlin_conf=nlin_hierarchy,
                                    resample_source=False)).xfm
                            if build_model_xfms is None
                            # use transforms from model building if we have them:
                            else s.defer(
                                xfmconcat(
                                    #nlin_component.Algorithms.concat(
                                    [
                                        build_model_xfms[row.img.path],
                                        s.defer(
                                            xfminvert(
                                                #nlin_component.Algorithms.invert(
                                                build_model_xfms[row.template.
                                                                 path],
                                                subdir="tmp"))
                                    ])),
                            like=row.img,
                            invert=True,
                            #use_nn_interpolation=True
                            interpolation=Interpolation.nearest_neighbour,
                            extra_flags=('-keep_real_range', '-labels')))))
            ) if len(imgs) > 1 else pd.DataFrame({
                'img': [],
                'label_file': []
            })
            # ... as no distinct templates to align if only one image supplied (#320)

            imgs_with_all_labels = pd.concat([
                atlas_labelled_imgs[['img', 'label_file']],
                template_labelled_imgs[['img', 'label_file']]
            ],
                                             ignore_index=True)
        else:
            imgs_with_all_labels = atlas_labelled_imgs

        #imgs_with_all_labels = imgs_with_all_labels.applymap(
        #    lambda x: s.defer(nlin_component.ToMinc.to_mnc(x)))
        segmented_imgs = (imgs_with_all_labels.groupby('img').aggregate({
            'label_file':
            lambda resampled_label_files: list(resampled_label_files)
        }).rename(columns={
            'label_file': 'label_files'
        }).reset_index().assign(voted_labels=lambda df: df.apply(
            axis=1,
            func=lambda row: s.defer(
                voxel_vote(label_files=row.label_files,
                           output_dir=os.path.join(row.img.pipeline_sub_dir,
                                                   row.img.output_sub_dir),
                           name=row.img.filename_wo_ext + "_voted"))
        )).apply(axis=1,
                 func=lambda row: row.img._replace(labels=row.voted_labels)))

        return Result(stages=s, output=segmented_imgs)
Beispiel #2
0
def maget_mask(imgs : List[MincAtom], atlases, options):

    s = Stages()

    resample  = np.vectorize(mincresample_new, excluded={"extra_flags"})
    defer     = np.vectorize(s.defer)

    lsq12_conf = get_linear_configuration_from_options(options.maget.lsq12,
                                                       LinearTransType.lsq12,
                                                       options.registration.resolution)

    masking_nlin_hierarchy = get_nonlinear_configuration_from_options(options.maget.maget.masking_nlin_protocol,
                                                                      options.maget.maget.mask_method,
                                                                      options.registration.resolution)

    masking_alignments = pd.DataFrame({ 'img'   : img,
                                        'atlas' : atlas,
                                        'xfm'   : s.defer(lsq12_nlin(source=img, target=atlas,
                                                                     lsq12_conf=lsq12_conf,
                                                                     nlin_conf=masking_nlin_hierarchy,
                                                                     resample_source=False))}
                                      for img in imgs for atlas in atlases)
    # propagate a mask to each image using the above `alignments` as follows:
    # - for each image, voxel_vote on the masks propagated to that image to get a suitable mask
    # - run mincmath -clobber -mult <img> <voted_mask> to apply the mask to the files
    masked_img = (
        masking_alignments
        .assign(resampled_mask=lambda df: defer(resample(img=df.atlas.apply(lambda x: x.mask),
                                                         xfm=df.xfm.apply(lambda x: x.xfm),
                                                         like=df.img,
                                                         invert=True,
                                                         interpolation=Interpolation.nearest_neighbour,
                                                         postfix="-input-mask",
                                                         subdir="tmp",
                                                         # TODO annoying hack; fix mincresample(_mask) ...:
                                                         #new_name_wo_ext=df.apply(lambda row:
                                                         #    "%s_to_%s-input-mask" % (row.atlas.filename_wo_ext,
                                                         #                             row.img.filename_wo_ext),
                                                         #    axis=1),
                                                         extra_flags=("-keep_real_range",))))
        .groupby('img', sort=False, as_index=False)
        # sort=False: just for speed (might also need to implement more comparison methods on `MincAtom`s)
        .aggregate({'resampled_mask' : lambda masks: list(masks)})
        .rename(columns={"resampled_mask" : "resampled_masks"})
        .assign(voted_mask=lambda df: df.apply(axis=1,
                                               func=lambda row:
                                                 s.defer(voxel_vote(label_files=row.resampled_masks,
                                                                    name="%s_voted_mask" % row.img.filename_wo_ext,
                                                                    output_dir=os.path.join(row.img.output_sub_dir,
                                                                                            "tmp")))))
        .assign(masked_img=lambda df:
          df.apply(axis=1,
                 func=lambda row:
                   s.defer(mincmath(op="mult",
                                    # img must precede mask here
                                    # for output image range to be correct:
                                    vols=[row.img, row.voted_mask],
                                    new_name="%s_masked" % row.img.filename_wo_ext,
                                    subdir="resampled")))))  #['img']

    # resample the atlas images back to the input images:
    # (note: this doesn't modify `masking_alignments`, but only stages additional outputs)
    masking_alignments.assign(resampled_img=lambda df:
    defer(resample(img=df.atlas,
                   xfm=df.xfm.apply(lambda x: x.xfm),
                   subdir="tmp",
                   # TODO delete this stupid hack:
                   #new_name_wo_ext=df.apply(lambda row:
                   #  "%s_to_%s-resampled" % (row.atlas.filename_wo_ext,
                   #                          row.img.filename_wo_ext),
                   #                          axis=1),
                   like=df.img, invert=True)))

    # replace the table of alignments with a new one with masked images
    masking_alignments = (pd.merge(left=masking_alignments.assign(unmasked_img=lambda df: df.img),
                                   right=masked_img,
                                   on=["img"], how="right", sort=False)
                          .assign(img=lambda df: df.masked_img))

    return Result(stages=s, output=masking_alignments)
Beispiel #3
0
def maget(imgs : List[MincAtom], options, prefix, output_dir, build_model_xfms=None):
    # FIXME prefix, output_dir aren't used !!

    s = Stages()

    maget_options = options.maget.maget

    resolution = options.registration.resolution  # TODO or get_resolution_from_file(...) -- only if file always exists!

    pipeline_sub_dir = os.path.join(options.application.output_directory,
                                    options.application.pipeline_name + "_atlases")

    atlases = get_atlases(maget_options, pipeline_sub_dir)

    lsq12_conf = get_linear_configuration_from_options(options.maget.lsq12,
                                                       transform_type=LinearTransType.lsq12,
                                                       file_resolution=resolution)

    nlin_component = get_nonlinear_component(options.maget.nlin.reg_method)

    # TODO should this be here or outside `maget` call?
    #imgs = [s.defer(nlin_component.ToMinc.from_mnc(img)) for img in imgs]

    #nlin_hierarchy = get_nonlinear_configuration_from_options(options.maget.nlin.nlin_protocol,
    #                                                          next(iter(options.maget.nlin.flags_.nlin_protocol)),
    #                                                          reg_method=options.maget.nlin.reg_method,
    #                                                          file_resolution=resolution)

    if maget_options.mask or maget_options.mask_only:

        # this used to return alignments but doesn't currently do so
        masked_img = s.defer(maget_mask(imgs=imgs,
                                        maget_options=options.maget, atlases=atlases,
                                        pipeline_sub_dir=pipeline_sub_dir + "_masking", # FIXME repeats all alignments!!!
                                        resolution=resolution))

        # now propagate only the masked form of the images and atlases:
        imgs    = masked_img
        #atlases = masked_atlases  # TODO is this needed?

    if maget_options.mask_only:
        # register each input to each atlas, creating a mask
        return Result(stages=s, output=masked_img)   # TODO rename `alignments` to `registrations`??
    else:
        if maget_options.mask:
            del masked_img
        # this `del` is just to verify that we don't accidentally use this later, since these potentially
        # coarser alignments shouldn't be re-used (but if the protocols for masking and alignment are the same,
        # hash-consing will take care of things), just the masked images they create; can be removed later
        # if a sensible use is found

        # images with labels from atlases
        # N.B.: Even though we've already registered each image to each initial atlas, this happens again here,
        #       but using `nlin_hierarchy` instead of `masking_nlin_hierarchy` as options.
        #       This is not 'work-efficient' in the sense that this computation happens twice (although
        #       hopefully at greater precision the second time!), but the idea is to run a coarse initial
        #       registration to get a mask and then do a better registration with that mask (though I'm not
        #       sure exactly when this is faster than doing a single registration).
        #       This _can_ allow the overall computation to finish more rapidly
        #       (depending on the relative speed of the two alignment methods/parameters,
        #       number of atlases and other templates used, number of cores available, etc.).
        atlas_labelled_imgs = (
            pd.DataFrame({ 'img'        : img,
                           'label_file' : s.defer(  # can't use `label` in a pd.DataFrame index!
                              mincresample_new(img=atlas.labels,
                                               xfm=s.defer(lsq12_nlin(source=img,
                                                                      target=atlas,
                                                                      nlin_module=nlin_component,
                                                                      lsq12_conf=lsq12_conf,
                                                                      nlin_options=options.maget.nlin.nlin_protocol,
                                                                      resolution=resolution,
                                                                      #nlin_conf=nlin_hierarchy,
                                                                      resample_source=False)).xfm,
                                               like=img,
                                               invert=True,
                                               interpolation=Interpolation.nearest_neighbour,
                                               extra_flags=('-keep_real_range', '-labels')))}
                         for img in imgs for atlas in atlases)
        )

        if maget_options.pairwise:

            def choose_new_templates(ts, n):
                # currently silly, but we might implement a smarter method ...
                # FIXME what if there aren't enough other imgs around?!  This silently goes weird ...
                return pd.Series(ts[:n+1])  # n+1 instead of n: choose one more since we won't use image as its own template ...

            # FIXME: the --max-templates flag is ambiguously named ... should be --max-new-templates
            # (and just use all atlases)
            # TODO we could have a separate templates_csv (or --template-files f [f ...]) but you can just
            # run a separate MAGeT pipeline and
            #if maget_options.templates_csv:
            #    templates = pd.read_csv(maget_options.templates_csv).template
            #else:
            templates = pd.DataFrame({ 'template' : choose_new_templates(ts=imgs,
                                                                         n=maget_options.max_templates - len(atlases))})
            # note these images are the masked ones if masking was done ...

            # the templates together with their (multiple) labels from the atlases (this merge just acts as a filter)
            labelled_templates = pd.merge(left=atlas_labelled_imgs, right=templates,
                                          left_on="img", right_on="template").drop('img', axis=1)

            # images with new labels from the templates
            imgs_and_templates = pd.merge(#left=atlas_labelled_imgs,
                                          left=pd.DataFrame({ "img" : imgs }).assign(fake=1),
                                          right=labelled_templates.assign(fake=1),
                                          on='fake')
                                          #left_on='img', right_on='template')  # TODO do select here instead of below?

            #if build_model_xfms is not None:
            #    # use path instead of full mincatom as key in case we're reading these in from a CSV:
            #    xfm_dict = { x.source.path : x.xfm for x in build_model_xfms }

            template_labelled_imgs = (
                imgs_and_templates
                .rename(columns={ 'label_file' : 'template_label_file' })
                # don't register template to itself, since otherwise atlases would vote on that template twice
                .loc[lambda df: df.index.map(lambda ix: df.img[ix].path
                                               != df.template[ix].path)]
                .assign(label_file=lambda df: df.apply(axis=1, func=lambda row:
                          s.defer(
                            # TODO switch to uses of nlin_component.whatever(...) in several places below?
                            mincresample_new(
                            #nlin_component.Algorithms.resample(
                              img=row.template_label_file,
                              xfm=s.defer(
                                    lsq12_nlin(source=row.img,
                                               target=row.template,
                                               lsq12_conf=lsq12_conf,
                                               resolution=resolution,
                                               nlin_module=nlin_component,
                                               nlin_options=options.maget.nlin.nlin_protocol,
                                               #nlin_conf=nlin_hierarchy,
                                               resample_source=False)).xfm
                                  if build_model_xfms is None
                                  # use transforms from model building if we have them:
                                  else s.defer(
                                         xfmconcat(
                                         #nlin_component.Algorithms.concat(
                                          [build_model_xfms[row.img.path],
                                           s.defer(
                                             xfminvert(
                                             #nlin_component.Algorithms.invert(
                                               build_model_xfms[row.template.path],
                                               subdir="tmp"))])),
                              like=row.img,
                              invert=True,
                              #use_nn_interpolation=True
                              interpolation=Interpolation.nearest_neighbour,
                              extra_flags=('-keep_real_range', '-labels')
                            ))))
            ) if len(imgs) > 1 else pd.DataFrame({ 'img' : [], 'label_file' : [] })
              # ... as no distinct templates to align if only one image supplied (#320)

            imgs_with_all_labels = pd.concat([atlas_labelled_imgs[['img', 'label_file']],
                                              template_labelled_imgs[['img', 'label_file']]],
                                             ignore_index=True)
        else:
            imgs_with_all_labels = atlas_labelled_imgs


        #imgs_with_all_labels = imgs_with_all_labels.applymap(
        #    lambda x: s.defer(nlin_component.ToMinc.to_mnc(x)))
        segmented_imgs = (
                imgs_with_all_labels
                .groupby('img')
                .aggregate({'label_file' : lambda resampled_label_files: list(resampled_label_files)})
                .rename(columns={ 'label_file' : 'label_files' })
                .reset_index()
                .assign(voted_labels=lambda df: df.apply(axis=1, func=lambda row:
                          s.defer(voxel_vote(label_files=row.label_files,
                                             output_dir=os.path.join(row.img.pipeline_sub_dir, row.img.output_sub_dir),
                                             name=row.img.filename_wo_ext+"_voted"))))
                .apply(axis=1, func=lambda row: row.img._replace(labels=row.voted_labels))
        )

        return Result(stages=s, output=segmented_imgs)
Beispiel #4
0
def maget(imgs : List[MincAtom], options, prefix, output_dir):     # FIXME prefix, output_dir aren't used !!

    s = Stages()

    maget_options = options.maget.maget

    pipeline_sub_dir = os.path.join(options.application.output_directory,
                                    options.application.pipeline_name + "_atlases")

    if maget_options.atlas_lib is None:
        raise ValueError("Need some atlases ...")

    #atlas_dir = os.path.join(output_dir, "input_atlases") ???

    # TODO should alternately accept a CSV file ...
    atlas_library = read_atlas_dir(atlas_lib=maget_options.atlas_lib, pipeline_sub_dir=pipeline_sub_dir)

    if len(atlas_library) == 0:
        raise ValueError("No atlases found in specified directory '%s' ..." % options.maget.maget.atlas_lib)

    num_atlases_needed = min(maget_options.max_templates, len(atlas_library))
    # TODO arbitrary; could choose atlases better ...
    atlases = atlas_library[:num_atlases_needed]
    # TODO issue a warning if not all atlases used or if more atlases requested than available?
    # TODO also, doesn't slicing with a higher number (i.e., if max_templates > n) go to the end of the list anyway?

    lsq12_conf = get_linear_configuration_from_options(options.maget.lsq12,
                                                       LinearTransType.lsq12,
                                                       options.registration.resolution)

    masking_nlin_hierarchy = get_nonlinear_configuration_from_options(options.maget.maget.masking_nlin_protocol,
                                                                      options.maget.maget.mask_method,
                                                                      options.registration.resolution)

    nlin_hierarchy = get_nonlinear_configuration_from_options(options.maget.nlin.nlin_protocol,
                                                              options.maget.nlin.reg_method,
                                                              options.registration.resolution)

    resample  = np.vectorize(mincresample_new, excluded={"extra_flags"})
    defer     = np.vectorize(s.defer)

    # plan the basic registrations between all image-atlas pairs; store the result paths in a table
    masking_alignments = pd.DataFrame({ 'img'   : img,
                                        'atlas' : atlas,
                                        'xfm'   : s.defer(lsq12_nlin(source=img, target=atlas,
                                                                     lsq12_conf=lsq12_conf,
                                                                     nlin_conf=masking_nlin_hierarchy,
                                                                     resample_source=False))}
                                      for img in imgs for atlas in atlases)

    if maget_options.mask or maget_options.mask_only:

        masking_alignments = s.defer(maget_mask(imgs, atlases, options))

        masked_atlases = atlases.apply(lambda atlas:
                           s.defer(mincmath(op='mult', vols=[atlas, atlas.mask], subdir="resampled",
                                            new_name="%s_masked" % atlas.filename_wo_ext)))

        # now propagate only the masked form of the images and atlases:
        imgs    = masking_alignments.img
        atlases = masked_atlases  # TODO is this needed?

    if maget_options.mask_only:
        # register each input to each atlas, creating a mask
        return Result(stages=s, output=masking_alignments)   # TODO rename `alignments` to `registrations`??
    else:
        del masking_alignments
        # this `del` is just to verify that we don't accidentally use this later, since my intent is that these
        # coarser alignments shouldn't be re-used, just the masked images they create; can be removed later
        # if a sensible use is found

        if maget_options.pairwise:

            def choose_new_templates(ts, n):
                # currently silly, but we might implement a smarter method ...
                # FIXME what if there aren't enough other imgs around?!  This silently goes weird ...
                return ts[:n+1]  # n+1 instead of n: choose one more since we won't use image as its own template ...

            new_templates = choose_new_templates(ts=imgs, n=maget_options.max_templates)
            # note these images are the masked ones if masking was done ...

            # TODO write a function to do these alignments and the image->atlas one above
            # align the new templates chosen from the images to the initial atlases:
            new_template_to_atlas_alignments = (
                pd.DataFrame({ 'img'   : template,
                               'atlas' : atlas,
                               'xfm'   : s.defer(lsq12_nlin(source=template, target=atlas,
                                                            lsq12_conf=lsq12_conf,
                                                            nlin_conf=nlin_hierarchy,
                                                            resample_source=False))}
                             for template in new_templates for atlas in atlases))
                             # ... and these atlases are multiplied by their masks (but is this necessary?)

            # label the new templates from resampling the atlas labels onto them:
            # TODO now vote on the labels to be used for the new templates ...
            # TODO extract into procedure?
            new_templates_labelled = (
                new_template_to_atlas_alignments
                .assign(resampled_labels=lambda df: defer(
                                               resample(img=df.atlas.apply(lambda x: x.labels),
                                                                      xfm=df.xfm.apply(lambda x: x.xfm),
                                                                      interpolation=Interpolation.nearest_neighbour,
                                                                      extra_flags=("-keep_real_range",),
                                                                      like=df.img, invert=True)))
                .groupby('img', sort=False, as_index=False)
                .aggregate({'resampled_labels' : lambda labels: list(labels)})
                .assign(voted_labels=lambda df: df.apply(axis=1,
                                                         func=lambda row:
                                                           s.defer(voxel_vote(label_files=row.resampled_labels,
                                                                              name="%s_template_labels" %
                                                                                   row.img.filename_wo_ext,
                                                                              output_dir=os.path.join(
                                                                                  row.img.pipeline_sub_dir,
                                                                                  row.img.output_sub_dir,
                                                                                  "labels"))))))

            # TODO write a procedure for this assign-groupby-aggregate-rename...
            # FIXME should be in above algebraic manipulation but MincAtoms don't support flexible immutable updating
            for row in pd.merge(left=new_template_to_atlas_alignments, right=new_templates_labelled,
                                on=["img"], how="right", sort=False).itertuples():
                row.img.labels = s.defer(mincresample_new(img=row.voted_labels, xfm=row.xfm.xfm, like=row.img,
                                                          invert=True, interpolation=Interpolation.nearest_neighbour,
                                                          #postfix="-input-labels",
                                                          # this makes names really long ...:
                                                          # TODO this doesn't work for running MAGeT on the nlin avg:
                                                          #new_name_wo_ext="%s_on_%s" %
                                                          #                (row.voted_labels.filename_wo_ext,
                                                          #                 row.img.filename_wo_ext),
                                                          #postfix="_labels_via_%s" % row.xfm.xfm.filename_wo_ext,
                                                          new_name_wo_ext="%s_via_%s" % (row.voted_labels.filename_wo_ext,
                                                                                         row.xfm.xfm.filename_wo_ext),
                                                          extra_flags=("-keep_real_range",)))

            # now that the new templates have been labelled, combine with the atlases:
            # FIXME use the masked atlases created earlier ??
            all_templates = pd.concat([new_templates_labelled.img, atlases], ignore_index=True)

            # now take union of the resampled labels from the new templates with labels from the original atlases:
            #all_alignments = pd.concat([image_to_template_alignments,
            #                            alignments.rename(columns={ "atlas" : "template" })],
            #                           ignore_index=True, join="inner")

        else:
            all_templates = atlases

        # now register each input to each selected template
        # N.B.: Even though we've already registered each image to each initial atlas, this happens again here,
        #       but using `nlin_hierarchy` instead of `masking_nlin_hierarchy` as options.
        #       This is not 'work-efficient' in the sense that this computation happens twice (although
        #       hopefully at greater precision the second time!), but the idea is to run a coarse initial
        #       registration to get a mask and then do a better registration with that mask (though I'm not
        #       sure exactly when this is faster than doing a single registration).
        #       This _can_ allow the overall computation to finish more rapidly
        #       (depending on the relative speed of the two alignment methods/parameters,
        #       number of atlases and other templates used, number of cores available, etc.).
        image_to_template_alignments = (
            pd.DataFrame({ "img"      : img,
                           "template" : template_img,
                           "xfm"      : xfm }
                         for img in imgs      # TODO use the masked imgs here?
                         for template_img in
                             all_templates
                             # FIXME delete this one alignment
                             #labelled_templates[labelled_templates.img != img]
                             # since equality is equality of filepaths (a bit dangerous)
                             # TODO is there a more direct/faster way just to delete the template?
                         for xfm in [s.defer(lsq12_nlin(source=img, target=template_img,
                                                        lsq12_conf=lsq12_conf,
                                                        nlin_conf=nlin_hierarchy))]
                         )
        )

        # now do a voxel_vote on all resampled template labels, just as earlier with the masks
        voted = (image_to_template_alignments
                 .assign(resampled_labels=lambda df:
                                            defer(resample(img=df.template.apply(lambda x: x.labels),
                                                           # FIXME bug: at this point templates from template_alignments
                                                           # don't have associated labels (i.e., `None`s) -- fatal
                                                           xfm=df.xfm.apply(lambda x: x.xfm),
                                                           interpolation=Interpolation.nearest_neighbour,
                                                           extra_flags=("-keep_real_range",),
                                                           like=df.img, invert=True)))
                 .groupby('img', sort=False)
                 # TODO the pattern groupby-aggregate(lambda x: list(x))-reset_index-assign is basically a hack
                 # to do a groupby-assign with access to the group name;
                 # see http://stackoverflow.com/a/30224447/849272 for a better solution
                 # (note this pattern occurs several times in MAGeT and two-level code)
                 .aggregate({'resampled_labels' : lambda labels: list(labels)})
                 .reset_index()
                 .assign(voted_labels=lambda df: defer(np.vectorize(voxel_vote)(label_files=df.resampled_labels,
                                                                                output_dir=df.img.apply(
                                                                                    lambda x: os.path.join(
                                                                                        x.pipeline_sub_dir,
                                                                                        x.output_sub_dir))))))

        # TODO doing mincresample -invert separately for the img->atlas xfm for mask, labels is silly
        # (when Pydpiper's `mincresample` does both automatically)?

        # blargh, another destructive update ...
        for row in voted.itertuples():
            row.img.labels = row.voted_labels

        # returning voted_labels as a column is slightly redundant, but possibly useful ...
        return Result(stages=s, output=voted)  # voted.drop("voted_labels", axis=1))
Beispiel #5
0
def maget(imgs : List[MincAtom], options, prefix, output_dir):     # FIXME prefix, output_dir aren't used !!

    s = Stages()

    maget_options = options.maget.maget

    resolution = options.registration.resolution  # TODO or get_resolution_from_file(...) -- only if file always exists!

    pipeline_sub_dir = os.path.join(options.application.output_directory,
                                    options.application.pipeline_name + "_atlases")

    if maget_options.atlas_lib is None:
        raise ValueError("Need some atlases ...")

    # TODO should alternately accept a CSV file ...
    atlases = atlases_from_dir(atlas_lib=maget_options.atlas_lib,
                               max_templates=maget_options.max_templates,
                               pipeline_sub_dir=pipeline_sub_dir)

    lsq12_conf = get_linear_configuration_from_options(options.maget.lsq12,
                                                       transform_type=LinearTransType.lsq12,
                                                       file_resolution=resolution)

    nlin_hierarchy = get_nonlinear_configuration_from_options(options.maget.nlin.nlin_protocol,
                                                              reg_method=options.maget.nlin.reg_method,
                                                              file_resolution=resolution)

    if maget_options.mask or maget_options.mask_only:

        # this used to return alignments but doesn't currently do so
        masked_img = s.defer(maget_mask(imgs=imgs,
                                        maget_options=options.maget, atlases=atlases,
                                        pipeline_sub_dir=pipeline_sub_dir + "_masking", # FIXME repeats all alignments!!!
                                        resolution=resolution))

        # now propagate only the masked form of the images and atlases:
        imgs    = masked_img
        #atlases = masked_atlases  # TODO is this needed?

    if maget_options.mask_only:
        # register each input to each atlas, creating a mask
        return Result(stages=s, output=masked_img)   # TODO rename `alignments` to `registrations`??
    else:
        if maget_options.mask:
            del masked_img
        # this `del` is just to verify that we don't accidentally use this later, since these potentially
        # coarser alignments shouldn't be re-used (but if the protocols for masking and alignment are the same,
        # hash-consing will take care of things), just the masked images they create; can be removed later
        # if a sensible use is found

        # images with labels from atlases
        # N.B.: Even though we've already registered each image to each initial atlas, this happens again here,
        #       but using `nlin_hierarchy` instead of `masking_nlin_hierarchy` as options.
        #       This is not 'work-efficient' in the sense that this computation happens twice (although
        #       hopefully at greater precision the second time!), but the idea is to run a coarse initial
        #       registration to get a mask and then do a better registration with that mask (though I'm not
        #       sure exactly when this is faster than doing a single registration).
        #       This _can_ allow the overall computation to finish more rapidly
        #       (depending on the relative speed of the two alignment methods/parameters,
        #       number of atlases and other templates used, number of cores available, etc.).
        atlas_labelled_imgs = (
            pd.DataFrame({ 'img'        : img,
                           'label_file' : s.defer(  # can't use `label` in a pd.DataFrame index!
                              mincresample_new(img=atlas.labels,
                                               xfm=s.defer(lsq12_nlin(source=img,
                                                                      target=atlas,
                                                                      lsq12_conf=lsq12_conf,
                                                                      nlin_conf=nlin_hierarchy,
                                                                      resample_source=False)).xfm,
                                               like=img,
                                               invert=True,
                                               interpolation=Interpolation.nearest_neighbour,
                                               extra_flags=('-keep_real_range',)))}
                         for img in imgs for atlas in atlases)
        )

        if maget_options.pairwise:

            def choose_new_templates(ts, n):
                # currently silly, but we might implement a smarter method ...
                # FIXME what if there aren't enough other imgs around?!  This silently goes weird ...
                return pd.Series(ts[:n+1])  # n+1 instead of n: choose one more since we won't use image as its own template ...

            # FIXME: the --max-templates flag is ambiguously named ... should be --max-new-templates
            # (and just use all atlases)
            templates = pd.DataFrame({ 'template' : choose_new_templates(ts=imgs,
                                                                         n=maget_options.max_templates - len(atlases))})
            # note these images are the masked ones if masking was done ...

            # the templates together with their (multiple) labels from the atlases (this merge just acts as a filter)
            labelled_templates = pd.merge(left=atlas_labelled_imgs, right=templates,
                                          left_on="img", right_on="template").drop('img', axis=1)

            # images with new labels from the templates
            imgs_and_templates = pd.merge(#left=atlas_labelled_imgs,
                                          left=pd.DataFrame({ "img" : imgs }).assign(fake=1),
                                          right=labelled_templates.assign(fake=1),
                                          on='fake')
                                          #left_on='img', right_on='template')  # TODO do select here instead of below?

            template_labelled_imgs = (
                imgs_and_templates
                .rename(columns={ 'label_file' : 'template_label_file' })
                # don't register template to itself, since otherwise atlases would vote on that template twice
                .select(lambda ix: imgs_and_templates.img[ix].path
                                     != imgs_and_templates.template[ix].path)  # TODO hardcoded name
                .assign(label_file=lambda df: df.apply(axis=1, func=lambda row:
                           s.defer(mincresample_new(img=row.template_label_file,
                                                    xfm=s.defer(lsq12_nlin(source=row.img,
                                                                           target=row.template,
                                                                           lsq12_conf=lsq12_conf,
                                                                           nlin_conf=nlin_hierarchy,
                                                                           resample_source=False)).xfm,
                                                    like=row.img,
                                                    invert=True,
                                                    interpolation=Interpolation.nearest_neighbour,
                                                    extra_flags=('-keep_real_range',)))))
            )

            imgs_with_all_labels = pd.concat([atlas_labelled_imgs[['img', 'label_file']],
                                              template_labelled_imgs[['img', 'label_file']]],
                                             ignore_index=True)
        else:
            imgs_with_all_labels = atlas_labelled_imgs

        segmented_imgs = (
                imgs_with_all_labels
                .groupby('img')
                .aggregate({'label_file' : lambda resampled_label_files: list(resampled_label_files)})
                .rename(columns={ 'label_file' : 'label_files' })
                .reset_index()
                .assign(voted_labels=lambda df: df.apply(axis=1, func=lambda row:
                          s.defer(voxel_vote(label_files=row.label_files,
                                             output_dir=os.path.join(row.img.pipeline_sub_dir, row.img.output_sub_dir)))))
                .apply(axis=1, func=lambda row: row.img._replace(labels=row.voted_labels))
        )

        return Result(stages=s, output=segmented_imgs)