def tamarack_pipeline(options):

    output_dir = options.application.output_directory
    pipeline_name = options.application.pipeline_name
    #processed_dir = os.path.join(output_dir, pipeline_name + "_processed")
    first_level_dir = os.path.join(output_dir, pipeline_name + "_first_level")

    s = Stages()

    with open(options.application.csv_file, 'r') as f:
        files_df = (pd.read_csv(
            filepath_or_buffer=f,
            usecols=['group', 'filename']).assign(file=lambda df: df.apply(
                axis="columns",
                func=lambda r: MincAtom(r.filename.strip(),
                                        pipeline_sub_dir=os.path.join(
                                            first_level_dir, "%s_processed" % r
                                            .group.strip())))))

    check_MINC_input_files(files_df.file.apply(lambda img: img.path))

    #grouped_files_df = pd.DataFrame({'file' : pd.concat([imgs])}).assign(group=lambda df: df.index)

    tamarack_result = s.defer(tamarack(files_df, options=options))

    tamarack_result.first_level_results.applymap(maybe_deref_path).to_csv(
        "first_level_results.csv", index=False)
    tamarack_result.resampled_determinants.applymap(maybe_deref_path).to_csv(
        "resampled_determinants.csv", index=False)
    tamarack_result.overall_determinants.applymap(maybe_deref_path).to_csv(
        "overall_determinants.csv", index=False)

    return Result(stages=s, output=tamarack_result)
Example #2
0
def asymmetry_pipeline(options):

    output_dir = options.application.output_directory
    pipeline_name = options.application.pipeline_name
    processed_dir = os.path.join(output_dir, pipeline_name + "_processed")

    s = Stages()

    #imgs_ = [MincAtom(f, pipeline_sub_dir=processed_dir) for f in options.application.files]

    imgs_ = get_imgs(options.application)

    check_MINC_input_files([img.path for img in imgs_])

    imgs = pd.Series(imgs_, index=[img.filename_wo_ext for img in imgs_])
    flipped_imgs = imgs.apply(lambda img: s.defer(volflip(img))
                              )  # TODO add flags to control flip axis ...

    # TODO ugly - MincAtom API should allow this somehow without mutation (also, how to pass into `volflip`, etc.?)
    for f_i in flipped_imgs:
        f_i.output_sub_dir += "_flipped"

    check_MINC_input_files(imgs.apply(lambda img: img.path))

    grouped_files_df = pd.DataFrame({
        'file': pd.concat([imgs, flipped_imgs])
    }).assign(group=lambda df: df.index)

    two_level_result = s.defer(two_level(grouped_files_df, options=options))

    return Result(stages=s, output=two_level_result)
def tamarack_pipeline(options):

    output_dir    = options.application.output_directory
    pipeline_name = options.application.pipeline_name
    #processed_dir = os.path.join(output_dir, pipeline_name + "_processed")
    first_level_dir = os.path.join(output_dir, pipeline_name + "_first_level")

    s = Stages()

    with open(options.application.csv_file, 'r') as f:
        files_df = (pd.read_csv(filepath_or_buffer=f,
                                usecols=['group', 'filename'])
                    .assign(file=lambda df:
                                   df.apply(axis="columns",
                                            func=lambda r:
                                                   MincAtom(r.filename.strip(),
                                                            pipeline_sub_dir=os.path.join(first_level_dir,
                                                                                          "%s_processed" % r.group.strip())))))

    check_MINC_input_files(files_df.file.apply(lambda img: img.path))

    #grouped_files_df = pd.DataFrame({'file' : pd.concat([imgs])}).assign(group=lambda df: df.index)

    tamarack_result = s.defer(tamarack(files_df, options=options))

    tamarack_result.first_level_results.applymap(maybe_deref_path).to_csv("first_level_results.csv", index=False)
    tamarack_result.resampled_determinants.applymap(maybe_deref_path).to_csv("resampled_determinants.csv", index=False)
    tamarack_result.overall_determinants.applymap(maybe_deref_path).to_csv("overall_determinants.csv", index=False)

    return Result(stages=s, output=tamarack_result)
Example #4
0
def maget_pipeline(options):

    imgs = get_imgs(options.application)
    check_MINC_input_files([img.path for img in imgs])
    # TODO fixup masking protocols ...

    if options.application.csv_file is not None:
        df = pd.read_csv(options.application.csv_file)
        # FIXME only works on MBM output, not, e.g., twolevel!
        if 'lsq12_nlin_xfm' in df.columns:
            csv_dir = os.path.dirname(options.application.csv_file)
            build_model_xfms = { row.file :
                                     XfmAtom(name=os.path.join(csv_dir, row.lsq12_nlin_xfm),
                                             pipeline_sub_dir=os.path.join(options.application.output_directory,
                                                                           options.application.pipeline_name
                                                                             + "_build_model_xfms"))
                                 for _, row in df.iterrows() }
        else:
            build_model_xfms = None
    else:
        build_model_xfms = None

    result = maget(imgs=imgs, options=options,
                   prefix=options.application.pipeline_name,
                   output_dir=options.application.output_directory,
                   build_model_xfms=build_model_xfms)

    # TODO this should also be created by MBM and other pipelines that run MAGeT
    (pd.DataFrame({ 'img_file'   : result.output.apply(lambda row: row.path),
                    'label_file' : result.output.apply(lambda row: row.labels.path),
                    #'mask_file'  : result.output.apply(lambda row: row.mask.path if row.mask else "NA")
                  })
        .to_csv(os.path.join(options.application.output_directory, "segmentations.csv"), index=False))

    return result
Example #5
0
def asymmetry_pipeline(options):

    output_dir    = options.application.output_directory
    pipeline_name = options.application.pipeline_name
    processed_dir = os.path.join(output_dir, pipeline_name + "_processed")

    s = Stages()

    #imgs_ = [MincAtom(f, pipeline_sub_dir=processed_dir) for f in options.application.files]

    imgs_ = get_imgs(options.application)

    check_MINC_input_files([img.path for img in imgs_])

    imgs  = pd.Series(imgs_, index=[img.filename_wo_ext for img in imgs_])
    flipped_imgs = imgs.apply(lambda img: s.defer(volflip(img)))  # TODO add flags to control flip axis ...

    # TODO ugly - MincAtom API should allow this somehow without mutation (also, how to pass into `volflip`, etc.?)
    for f_i in flipped_imgs:
        f_i.output_sub_dir += "_flipped"

    check_MINC_input_files(imgs.apply(lambda img: img.path))

    grouped_files_df = pd.DataFrame({'file' : pd.concat([imgs, flipped_imgs])}).assign(group=lambda df: df.index)

    two_level_result = s.defer(two_level(grouped_files_df, options=options))

    return Result(stages=s, output=two_level_result)
Example #6
0
def maget_pipeline(options):

    imgs = get_imgs(options.application)
    check_MINC_input_files([img.path for img in imgs])
    # TODO fixup masking protocols ...

    return maget(imgs=imgs, options=options,
                 prefix=options.application.pipeline_name,
                 output_dir=options.application.output_directory)
Example #7
0
def maget_pipeline(options):

    check_MINC_input_files(options.application.files)

    imgs = pd.Series({ name : MincAtom(name,
                                       pipeline_sub_dir=os.path.join(options.application.output_directory,
                                                                     options.application.pipeline_name + "_processed"))
                       for name in options.application.files })

    # TODO fixup masking protocols ...

    return maget(imgs=imgs, options=options,
                 prefix=options.application.pipeline_name,
                 output_dir=options.application.output_directory)
Example #8
0
def maget_pipeline(options):

    imgs = get_imgs(options.application)
    check_MINC_input_files([img.path for img in imgs])
    # TODO fixup masking protocols ...

    if options.application.csv_file is not None:
        df = pd.read_csv(options.application.csv_file)
        # FIXME only works on MBM output, not, e.g., twolevel!
        if 'lsq12_nlin_xfm' in df.columns:
            csv_dir = os.path.dirname(options.application.csv_file)
            build_model_xfms = {
                row.file: XfmAtom(name=os.path.join(csv_dir,
                                                    row.lsq12_nlin_xfm),
                                  pipeline_sub_dir=os.path.join(
                                      options.application.output_directory,
                                      options.application.pipeline_name +
                                      "_build_model_xfms"))
                for _, row in df.iterrows()
            }
        else:
            build_model_xfms = None
    else:
        build_model_xfms = None

    result = maget(imgs=imgs,
                   options=options,
                   prefix=options.application.pipeline_name,
                   output_dir=options.application.output_directory,
                   build_model_xfms=build_model_xfms)

    # TODO this should also be created by MBM and other pipelines that run MAGeT
    (pd.DataFrame({
        'img_file':
        result.output.apply(lambda row: row.path),
        'label_file':
        result.output.apply(lambda row: row.labels.path),
        #'mask_file'  : result.output.apply(lambda row: row.mask.path if row.mask else "NA")
    }).to_csv(os.path.join(options.application.output_directory,
                           "segmentations.csv"),
              index=False))

    return result
Example #9
0
def mbm_pipeline(options : MBMConf):
    s = Stages()
    imgs = [MincAtom(name, pipeline_sub_dir=os.path.join(options.application.output_directory,
                                                         options.application.pipeline_name + "_processed"))
            for name in options.application.files]

    check_MINC_input_files([img.path for img in imgs])

    prefix = options.application.pipeline_name

    mbm_result = s.defer(mbm(imgs=imgs, options=options,
                         prefix=prefix,
                         output_dir=options.application.output_directory))

    # create useful CSVs (note the files listed therein won't yet exist ...)
    for filename, dataframe in (("transforms.csv", mbm_result.xfms),
                                ("determinants.csv", mbm_result.determinants)):
        with open(filename, 'w') as f:
            f.write(dataframe.applymap(maybe_deref_path).to_csv(index=False))

    # TODO moved here from inside `mbm` for now ... does this make most sense?
    if options.mbm.segmentation.run_maget:
        import copy
        maget_options = copy.deepcopy(options)  #Namespace(maget=options)
        #maget_options
        #maget_options.maget = maget_options.mbm
        #maget_options.execution = options.execution
        #maget_options.application = options.application
        maget_options.application.output_directory = os.path.join(options.application.output_directory, "segmentation")
        maget_options.maget = options.mbm.maget

        fixup_maget_options(maget_options=maget_options.maget,
                            nlin_options=maget_options.mbm.nlin,
                            lsq12_options=maget_options.mbm.lsq12)
        del maget_options.mbm

        s.defer(maget([xfm.resampled for _ix, xfm in mbm_result.xfms.rigid_xfm.iteritems()],
                       options=maget_options,
                       prefix="%s_MAGeT" % prefix,
                       output_dir=os.path.join(options.application.output_directory, prefix + "_processed")))

    return Result(stages=s, output=mbm_result)
def stage_embryos_pipeline(options):
    s = Stages()

    imgs = get_imgs(options.application)
    rough_volume_imgs = get_volume_estimate(imgs)
    imgs_and_rough_volume = pd.DataFrame({"mincatom" : imgs,
                                          "rough_volume" : pd.Series(rough_volume_imgs, dtype=float)})

    check_MINC_input_files([img.path for img in imgs])

    output_directory = options.application.output_directory
    output_sub_dir = os.path.join(output_directory,
                                  options.application.pipeline_name + "_4D_atlas")

    time_points_in_4D_atlas = instances_in_4D_atlas_from_csv(options.staging.staging.csv_4D,
                                                             output_sub_dir)

    # we can use the resolution of one of the time points in the 4D atlas
    # for all the registrations that will be run.
    resolution = get_resolution_from_file(time_points_in_4D_atlas.loc[0]["mincatom"].orig_path)

    print(options.staging.lsq12)

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

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

    # match each of the embryos individually
    for i in range(imgs_and_rough_volume.shape[0]):
        s.defer(match_embryo_to_4D_atlas(imgs_and_rough_volume.loc[i],
                                         time_points_in_4D_atlas,
                                         lsq6_conf=options.staging.lsq6,
                                         lsq12_conf=lsq12_conf,
                                         nlin_module=nlin_component,
                                         resolution=resolution,
                                         nlin_options=options.staging.nlin))


    return Result(stages=s, output=None)
def two_level_pipeline(options : TwoLevelConf):

    first_level_dir = options.application.pipeline_name + "_first_level"

    if options.application.files:
        warnings.warn("Got extra arguments: '%s'" % options.application.files)
    with open(options.application.csv_file, 'r') as f:
        try:
            files_df = (pd.read_csv(
                          filepath_or_buffer=f,
                          usecols=['group', 'file'])
                        .assign(file=lambda df:
                                       df.apply(axis="columns",
                                                func=lambda r:
                                                  MincAtom(r.file,
                                                           pipeline_sub_dir=os.path.join(first_level_dir,
                                                                                         "%s_processed" % r.group,
                                                                                         )))))
        except AttributeError:
            warnings.warn("Something went wrong ... does your .csv file have `group` and `file` columns?")
            raise

    check_MINC_input_files(files_df.file.map(lambda x: x.path))

    pipeline = two_level(grouped_files_df=files_df, options=options)

    # TODO write these into the appropriate subdirectory ...
    with open("overall_determinants.csv", 'w') as overall_csv:
        overall_csv.write(pipeline.output.overall_determinants
                          .drop('inv_xfm', axis=1)
                          .applymap(maybe_deref_path)
                          .to_csv(index=False))
    with open("resampled_determinants.csv", 'w') as resampled_csv:
        resampled_csv.write(pipeline.output.resampled_determinants
                            .drop(['inv_xfm', 'full_det', 'nlin_det'], axis=1)
                            .applymap(maybe_deref_path)
                            .to_csv(index=False))

    return pipeline
def two_level_pipeline(options: TwoLevelConf):
    def relativize_path(fp):
        #this annoying function takes care of the csv_paths_relative_to_wd flag.
        return os.path.join(os.path.dirname(options.application.csv_file),fp) \
            if not options.application.csv_paths_relative_to_wd \
            else fp

    first_level_dir = options.application.pipeline_name + "_first_level"

    if options.application.files:
        warnings.warn("Got extra arguments: '%s'" % options.application.files)
    with open(options.application.csv_file, 'r') as f:
        try:
            files_df = (pd.read_csv(
                filepath_or_buffer=f,
                usecols=['group', 'file'],
                index_col=False).assign(file=lambda df: df.apply(
                    axis="columns",
                    func=lambda r: MincAtom(relativize_path(r.file).strip(),
                                            pipeline_sub_dir=os.path.join(
                                                first_level_dir,
                                                "%s_processed" % r.group,
                                            )))))
        except AttributeError:
            warnings.warn(
                "Something went wrong ... does your .csv file have `group` and `file` columns?"
            )
            raise

    # TODO is it actually sufficient that *within-study* filenames are distinct, as follows??
    for name, g in files_df.groupby("group"):  # TODO: collect the outputs
        check_MINC_input_files(g.file.map(lambda x: x.path))
    #check_MINC_input_files(files_df.file.map(lambda x: x.path))

    pipeline = two_level(grouped_files_df=files_df, options=options)

    # TODO write these into the appropriate subdirectory ...
    overall = (pipeline.output.overall_determinants.drop(
        'inv_xfm', axis=1).applymap(maybe_deref_path))
    overall.to_csv("overall_determinants.csv", index=False)
    resampled = (pipeline.output.resampled_determinants.drop(
        ['inv_xfm', 'full_det', 'nlin_det'],
        axis=1).applymap(maybe_deref_path))
    resampled.to_csv("resampled_determinants.csv", index=False)

    # rename/drop some columns, bind the dfs and write to "analysis.csv" as it should be.
    # deprecate the two csvs next release.
    analysis = pd.read_csv(options.application.csv_file).assign(
        native_file=lambda df: df.file.apply(relativize_path))

    overall = (overall.drop(["full_det", "nlin_det"],
                            axis=1).rename(columns={"overall_xfm": "xfm"}))
    resampled = resampled.rename(
        columns={
            "first_level_log_full_det": "log_full_det",
            "first_level_log_nlin_det": "log_nlin_det",
            "first_level_xfm": "xfm",
            "first_level_log_full_det_resampled": "resampled_log_full_det",
            "first_level_log_nlin_det_resampled": "resampled_log_nlin_det"
        })
    (analysis.merge(pd.concat([resampled, overall],
                              axis=1)).to_csv("analysis.csv", index=False))

    return pipeline
Example #13
0
def mbm_pipeline(options: MBMConf):
    s = Stages()

    imgs = get_imgs(options.application)

    check_MINC_input_files([img.path for img in imgs])

    output_dir = options.application.output_directory

    mbm_result = s.defer(
        mbm(imgs=imgs,
            options=options,
            prefix=options.application.pipeline_name,
            output_dir=output_dir))

    if options.mbm.common_space.do_common_space_registration:
        s.defer(common_space(mbm_result, options))

    # create useful CSVs (note the files listed therein won't yet exist ...):

    transforms = (mbm_result.xfms.assign(
        native_file=lambda df: df.rigid_xfm.apply(lambda x: x.source),
        lsq6_file=lambda df: df.lsq12_nlin_xfm.apply(lambda x: x.source),
        lsq6_mask_file=lambda df: df.lsq12_nlin_xfm.apply(
            lambda x: x.source.mask if x.source.mask else ""),
        nlin_file=lambda df: df.lsq12_nlin_xfm.apply(lambda x: x.resampled),
        nlin_mask_file=lambda df: df.lsq12_nlin_xfm.apply(
            lambda x: x.resampled.mask if x.resampled.mask else ""),
        common_space_file=lambda df: df.xfm_to_common.apply(lambda x: x.
                                                            resampled)
        if options.mbm.common_space.do_common_space_registration else None
    ).applymap(maybe_deref_path).drop(
        ["common_space_file"]
        if not options.mbm.common_space.do_common_space_registration else [],
        axis=1))
    transforms.to_csv("transforms.csv", index=False)

    determinants = (mbm_result.determinants.drop(
        ["full_det", "nlin_det"], axis=1).applymap(maybe_deref_path))
    determinants.to_csv("determinants.csv", index=False)

    analysis = (transforms.merge(determinants,
                                 left_on="lsq12_nlin_xfm",
                                 right_on="inv_xfm",
                                 how='inner').drop(["xfm", "inv_xfm"], axis=1))
    if options.mbm.segmentation.run_maget:
        maget_df = pd.DataFrame(
            data={
                'label_file':
                [result.labels.path for result in mbm_result.maget_result],
                'native_file':
                [result.orig_path for result in mbm_result.maget_result]
            })
        analysis = analysis.merge(maget_df, on="native_file")

    if options.application.files:
        analysis.to_csv("analysis.csv", index=False)
    elif options.application.csv_file:
        csv_file = (
            pd.read_csv(options.application.csv_file).assign(
                native_file=lambda df: df.file.apply(
                    # TODO this is duplicating the logic in get_imgs - fix
                    lambda fp: os.path.join(
                        os.path.dirname(options.application.csv_file), fp)
                    if not options.application.csv_paths_relative_to_wd else fp
                ).apply(os.path.normpath)))
        csv_file.merge(analysis, on="native_file").to_csv("analysis.csv",
                                                          index=False)

    # # TODO moved here from inside `mbm` for now ... does this make most sense?
    # if options.mbm.segmentation.run_maget:
    #     import copy
    #     maget_options = copy.deepcopy(options)  #Namespace(maget=options)
    #     #maget_options
    #     #maget_options.maget = maget_options.mbm
    #     #maget_options.execution = options.execution
    #     #maget_options.application = options.application
    #     maget_options.application.output_directory = os.path.join(options.application.output_directory, "segmentation")
    #     maget_options.maget = options.mbm.maget
    #
    #     fixup_maget_options(maget_options=maget_options.maget,
    #                         nlin_options=maget_options.mbm.nlin,
    #                         lsq12_options=maget_options.mbm.lsq12)
    #     del maget_options.mbm
    #
    #
    #     #def with_new_output_dir(img : MincAtom):
    #         #img = copy.copy(img)
    #         #img.pipeline_sub_dir = img.pipeline_sub_dir + img.output_dir
    #         #img.
    #         #return img.newname_with_suffix(suffix="", subdir="segmentation")
    #
    #     s.defer(maget([xfm.resampled for _ix, xfm in mbm_result.xfms.rigid_xfm.iteritems()],
    #                    options=maget_options,
    #                    prefix="%s_MAGeT" % prefix,
    #                    output_dir=os.path.join(options.application.output_directory, prefix + "_processed")))

    return Result(stages=s, output=mbm_result)
Example #14
0
def chain(options):
    """Create a registration chain pipeline from the given options."""

    # TODO:
    # one overall question for this entire piece of code is how
    # we are going to make sure that we can concatenate/add all
    # the transformations together. Many of the sub-registrations
    # that are performed (inter-subject registration, lsq6 using
    # multiple initial models) are applied to subsets of the entire 
    # data, making it harder to keep the mapping simple/straightforward


    chain_opts = options.chain  # type : ChainConf

    s = Stages()
    
    with open(options.chain.csv_file, 'r') as f:
        subject_info = parse_csv(rows=f, common_time_pt=options.chain.common_time_point)

    output_dir    = options.application.output_directory
    pipeline_name = options.application.pipeline_name

    pipeline_processed_dir = os.path.join(output_dir, pipeline_name + "_processed")
    pipeline_lsq12_common_dir = os.path.join(output_dir, pipeline_name + "_lsq12_" + options.chain.common_time_point_name)
    pipeline_nlin_common_dir = os.path.join(output_dir, pipeline_name + "_nlin_" + options.chain.common_time_point_name)
    pipeline_montage_dir = os.path.join(output_dir, pipeline_name + "_montage")
    
    
    pipeline_subject_info = map_over_time_pt_dict_in_Subject(
                                     lambda subj_str:  MincAtom(name=subj_str, pipeline_sub_dir=pipeline_processed_dir),
                                     subject_info)  # type: Dict[str, Subject[MincAtom]]
    
    # verify that in input files are proper MINC files, and that there 
    # are no duplicates in the filenames
    all_Minc_atoms = []  # type: List[MincAtom]
    for s_id, subj in pipeline_subject_info.items():
        for subj_time_pt, subj_filename in subj.time_pt_dict.items():
            all_Minc_atoms.append(subj_filename)
    # check_MINC_input_files takes strings, so pass along those instead of the actual MincAtoms
    check_MINC_input_files([minc_atom.path for minc_atom in all_Minc_atoms])

    if options.registration.input_space == InputSpace.lsq6 or \
        options.registration.input_space == InputSpace.lsq12:
        # the input files are not going through the lsq6 alignment. This is the place
        # where they will all be resampled using a single like file, and get the same
        # image dimensions/lengths/resolution. So in order for the subsequent stages to
        # finish (mincaverage stages for instance), all files need to have the same
        # image parameters:
        check_MINC_files_have_equal_dimensions_and_resolution([minc_atom.path for minc_atom in all_Minc_atoms],
                                                              additional_msg="Given that the input images are "
                                                                             "already in " + str(options.registration.input_space) +
                                                                             " space, all input files need to have "
                                                                             "the same dimensions/starts/step sizes.")

    if options.registration.input_space not in InputSpace.__members__.values():
        raise ValueError('unrecognized input space: %s; choices: %s' %
                         (options.registration.input_space, ','.join(InputSpace.__members__)))
    
    if options.registration.input_space == InputSpace.native:
        if options.lsq6.target_type == TargetType.bootstrap:
            raise ValueError("\nA bootstrap model is ill-defined for the registration chain. "
                             "(Which file is the 'first' input file?). Please use the --lsq6-target "
                             "flag to specify a target for the lsq6 stage, or use an initial model.")
        if options.lsq6.target_type == TargetType.pride_of_models:
            pride_of_models_dict = get_pride_of_models_mapping(pride_csv=options.lsq6.target_file,
                                                               output_dir=options.application.output_directory,
                                                               pipeline_name=options.application.pipeline_name)
            subj_id_to_subj_with_lsq6_xfm_dict = map_with_index_over_time_pt_dict_in_Subject(
                                    lambda subj_atom, time_point:
                                        s.defer(lsq6_nuc_inorm([subj_atom],
                                                               registration_targets=get_closest_model_from_pride_of_models(
                                                                                        pride_of_models_dict, time_point),
                                                               resolution=options.registration.resolution,
                                                               lsq6_options=options.lsq6,
                                                               lsq6_dir=None,  # never used since no average
                                                               # (could call this "average_dir" with None -> no avg ?)
                                                               subject_matter=options.registration.subject_matter,
                                                               create_qc_images=False,
                                                               create_average=False))[0],
                                        pipeline_subject_info)  # type: Dict[str, Subject[XfmHandler]]
        else:
            # if we are not dealing with a pride of models, we can retrieve a fixed
            # registration target for all input files:
            targets = registration_targets(lsq6_conf=options.lsq6,
                                           app_conf=options.application)
            
            # we want to store the xfm handlers in the same shape as pipeline_subject_info,
            # as such we will call lsq6_nuc_inorm for each file individually and simply extract
            # the first (and only) element from the resulting list via s.defer(...)[0].
            subj_id_to_subj_with_lsq6_xfm_dict = map_over_time_pt_dict_in_Subject(
                                         lambda subj_atom:
                                           s.defer(lsq6_nuc_inorm([subj_atom],
                                                                  registration_targets=targets,
                                                                  resolution=options.registration.resolution,
                                                                  lsq6_options=options.lsq6,
                                                                  lsq6_dir=None, # no average will be create, is just one file...
                                                                  create_qc_images=False,
                                                                  create_average=False,
                                                                  subject_matter=options.registration.subject_matter)
                                                   )[0],
                                         pipeline_subject_info)  # type: Dict[str, Subject[XfmHandler]]

        # create verification images to show the 6 parameter alignment
        montageLSQ6 = pipeline_montage_dir + "/quality_control_montage_lsq6.png"
        # TODO, base scaling factor on resolution of initial model or target
        filesToCreateImagesFrom = []
        for subj_id, subj in subj_id_to_subj_with_lsq6_xfm_dict.items():
            for time_pt, subj_time_pt_xfm in subj.time_pt_dict.items():
                filesToCreateImagesFrom.append(subj_time_pt_xfm.resampled)

        # TODO it's strange that create_quality_control_images gets the montage directory twice
        # TODO (in montages=output=montageLSQ6 and in montage_dir), suggesting a weirdness in create_q_c_images
        lsq6VerificationImages = s.defer(create_quality_control_images(filesToCreateImagesFrom,
                                                                       montage_output=montageLSQ6,
                                                                       montage_dir=pipeline_montage_dir,
                                                                       message=" the input images after the lsq6 alignment"))

    # NB currently LSQ6 expects an array of files, but we have a map.
    # possibilities:
    # - note that pairwise is enough (except for efficiency -- redundant blurring, etc.)
    #   and just use the map fn above with an LSQ6 fn taking only a single source
    # - rewrite LSQ6 to use such a (nested) map
    # - write conversion which creates a tagged array from the map, performs LSQ6,
    #   and converts back
    # - write 'over' which takes a registration, a data structure, and 'get/set' fns ...?
    

    # Intersubject registration: LSQ12/NLIN registration of common-timepoint images
    # The assumption here is that all these files are roughly aligned. Here is a toy
    # schematic of what happens. In this example, the common timepoint is set timepoint 2: 
    #
    #                            ------------
    # subject A    A_time_1   -> | A_time_2 | ->   A_time_3
    # subject B    B_time_1   -> | B_time_2 | ->   B_time_3
    # subject C    C_time_1   -> | C_time_2 | ->   C_time_3
    #                            ------------
    #                                 |
    #                            group_wise registration on time point 2
    #

    # dictionary that holds the transformations from the intersubject images
    # to the final common space average
    intersubj_img_to_xfm_to_common_avg_dict = {}  # type: Dict[MincAtom, XfmHandler]
    if options.registration.input_space in (InputSpace.lsq6, InputSpace.lsq12):
        # no registrations have been performed yet, so we can point to the input files
        s_id_to_intersubj_img_dict = { s_id : subj.intersubject_registration_image
                          for s_id, subj in pipeline_subject_info.items() }
    else:
        # lsq6 aligned images
        # When we ran the lsq6 alignment, we stored the XfmHandlers in the Subject dictionary. So when we call
        # xfmhandler.intersubject_registration_image, this returns an XfmHandler. From which
        # we want to extract the resampled file (in order to continue the registration with)
        s_id_to_intersubj_img_dict = { s_id : subj_with_xfmhandler.intersubject_registration_image.resampled
                          for s_id, subj_with_xfmhandler in subj_id_to_subj_with_lsq6_xfm_dict.items() }
    
    if options.application.verbose:
        print("\nImages that are used for the inter-subject registration:")
        print("ID\timage")
        for subject in s_id_to_intersubj_img_dict:
            print(subject + '\t' + s_id_to_intersubj_img_dict[subject].path)

    # determine what configuration to use for the non linear registration
    nonlinear_configuration = get_nonlinear_configuration_from_options(options.nlin.nlin_protocol,
                                                                       options.nlin.reg_method,
                                                                       options.registration.resolution)

    if options.registration.input_space in [InputSpace.lsq6, InputSpace.native]:
        intersubj_xfms = s.defer(lsq12_nlin_build_model(imgs=list(s_id_to_intersubj_img_dict.values()),
                                                lsq12_conf=options.lsq12,
                                                nlin_conf=nonlinear_configuration,
                                                resolution=options.registration.resolution,
                                                lsq12_dir=pipeline_lsq12_common_dir,
                                                nlin_dir=pipeline_nlin_common_dir,
                                                nlin_prefix="common"))
                                                #, like={atlas_from_init_model_at_this_tp}
    elif options.registration.input_space == InputSpace.lsq12:
        #TODO: write reader that creates a mincANTS configuration out of an input protocol
        # if we're starting with files that are already aligned with an affine transformation
        # (overall scaling is also dealt with), then the target for the non linear registration
        # should be the averge of the current input files.
        first_nlin_target = s.defer(mincaverage(imgs=list(s_id_to_intersubj_img_dict.values()),
                                                name_wo_ext="avg_of_input_files",
                                                output_dir=pipeline_nlin_common_dir))
        intersubj_xfms = s.defer(mincANTS_NLIN_build_model(imgs=list(s_id_to_intersubj_img_dict.values()),
                                                   initial_target=first_nlin_target,
                                                   nlin_dir=pipeline_nlin_common_dir,
                                                   conf=nonlinear_configuration))


    intersubj_img_to_xfm_to_common_avg_dict = { xfm.source : xfm for xfm in intersubj_xfms.output }

    # create one more convenience data structure: a mapping from subject_ID to the xfm_handler
    # that contains the transformation from the subject at the common time point to the
    # common time point average.
    subj_ID_to_xfm_handler_to_common_avg = {}
    for s_id, subj_at_common_tp in s_id_to_intersubj_img_dict.items():
        subj_ID_to_xfm_handler_to_common_avg[s_id] = intersubj_img_to_xfm_to_common_avg_dict[subj_at_common_tp]

    # create verification images to show the inter-subject  alignment
    montage_inter_subject = pipeline_montage_dir + "/quality_control_montage_inter_subject_registration.png"
    avg_and_inter_subject_images = []
    avg_and_inter_subject_images.append(intersubj_xfms.avg_img)
    for xfmh in intersubj_xfms.output:
        avg_and_inter_subject_images.append(xfmh.resampled)

    inter_subject_verification_images = s.defer(create_quality_control_images(
                                                  imgs=avg_and_inter_subject_images,
                                                  montage_output=montage_inter_subject,
                                                  montage_dir=pipeline_montage_dir,
                                                  message=" the result of the inter-subject alignment"))

    if options.application.verbose:
        print("\nTransformations for intersubject images to final nlin common space:")
        print("MincAtom\ttransformation")
        for subj_atom, xfm_handler in intersubj_img_to_xfm_to_common_avg_dict.items():
            print(subj_atom.path + '\t' + xfm_handler.xfm.path)


    ## within-subject registration
    # In the toy scenario below: 
    # subject A    A_time_1   ->   A_time_2   ->   A_time_3
    # subject B    B_time_1   ->   B_time_2   ->   B_time_3
    # subject C    C_time_1   ->   C_time_2   ->   C_time_3
    # 
    # The following registrations are run:
    # 1) A_time_1   ->   A_time_2
    # 2) A_time_2   ->   A_time_3
    #
    # 3) B_time_1   ->   B_time_2
    # 4) B_time_2   ->   B_time_3
    #
    # 5) C_time_1   ->   C_time_2
    # 6) C_time_2   ->   C_time_3    

    subj_id_to_Subjec_for_within_dict = pipeline_subject_info
    if options.registration.input_space == InputSpace.native:
        # we started with input images that were not aligned whatsoever
        # in this case we should use the images that were rigidly
        # aligned files to continue the within-subject registration with
        # # type: Dict[str, Subject[XfmHandler]]
        subj_id_to_Subjec_for_within_dict = map_over_time_pt_dict_in_Subject(lambda x: x.resampled,
                                                                             subj_id_to_subj_with_lsq6_xfm_dict)

    if options.application.verbose:
        print("\n\nWithin subject registrations:")
        for s_id, subj in subj_id_to_Subjec_for_within_dict.items():
            print("ID: ", s_id)
            for time_pt, subj_img in subj.time_pt_dict.items():
                print(time_pt, " ", subj_img.path)
            print("\n")

    # dictionary that maps subject IDs to a list containing:
    # ( [(time_pt_n, time_pt_n+1, XfmHandler_from_n_to_n+1), ..., (,,,)],
    #   index_of_common_time_pt)
    chain_xfms = { s_id : s.defer(intrasubject_registrations(
                                    subj=subj,
                                    linear_conf=default_lsq12_multilevel_minctracc,
                                    nlin_conf=mincANTS_default_conf.replace(
                                        file_resolution=options.registration.resolution,
                                        iterations="100x100x100x50")))
                   for s_id, subj in subj_id_to_Subjec_for_within_dict.items() }

    # create a montage image for each pair of time points
    for s_id, output_from_intra in chain_xfms.items():
        for time_pt_n, time_pt_n_plus_1, transform in output_from_intra[0]:
            montage_chain = pipeline_montage_dir + "/quality_control_chain_ID_" + s_id + \
                            "_timepoint_" + str(time_pt_n) + "_to_" + str(time_pt_n_plus_1) + ".png"
            chain_images = [transform.resampled, transform.target]
            chain_verification_images = s.defer(create_quality_control_images(chain_images,
                                                                              montage_output=montage_chain,
                                                                              montage_dir=pipeline_montage_dir,
                                                                              message="the alignment between ID " + s_id + " time point " +
                                                                                      str(time_pt_n) + " and " + str(time_pt_n_plus_1)))

    if options.application.verbose:
        print("\n\nTransformations gotten from the intrasubject registrations:")
        for s_id, output_from_intra in chain_xfms.items():
            print("ID: ", s_id)
            for time_pt_n, time_pt_n_plus_1, transform in output_from_intra[0]:
                print("Time point: ", time_pt_n, " to ", time_pt_n_plus_1, " trans: ", transform.xfm.path)
            print("\n")

    ## stats
    #
    # The statistic files we want to create are the following:
    # 1) subject <----- subject_common_time_point                              (resampled to common average)
    # 2) subject <----- subject_common_time_point <- common_time_point_average (incorporates inter subject differences)
    # 3) subject_time_point_n <----- subject_time_point_n+1                    (resampled to common average)

    # create transformation from each subject to the final common time point average,
    # and from each subject to the subject's common time point
    (non_rigid_xfms_to_common_avg, non_rigid_xfms_to_common_subj) = s.defer(get_chain_transforms_for_stats(subj_id_to_Subjec_for_within_dict,
                                                                            intersubj_img_to_xfm_to_common_avg_dict,
                                                                            chain_xfms))

    # Ad 1) provide transformations from the subject's common time point to each subject
    #       These are temporary, because they still need to be resampled into the
    #       average common time point space
    determinants_from_subject_common_to_subject = map_over_time_pt_dict_in_Subject(
        lambda xfm: s.defer(determinants_at_fwhms(xfms=[s.defer(invert_xfmhandler(xfm))],
                                                  inv_xfms=[xfm],  # determinants_at_fwhms now vectorized-unhelpful here
                                                  blur_fwhms=options.stats.stats_kernels)),
        non_rigid_xfms_to_common_subj)
    # the content of determinants_from_subject_common_to_subject is:
    #
    # {subject_ID : Subject(inter_subject_time_pt, time_pt_dict)
    #
    # where time_pt_dict contains:
    #
    # {time_point : Tuple(List[Tuple(float, Tuple(MincAtom, MincAtom))],
    #                     List[Tuple(float, Tuple(MincAtom, MincAtom))])
    #
    # And to be a bit more verbose:
    #
    # {time_point : Tuple(relative_stat_files,
    #                     absolute_stat_files)
    #
    # where either the relative_stat_files or the absolute_stat_files look like:
    #
    # [blur_kernel_1, (determinant_file_1, log_of_determinant_file_1),
    #  ...,
    #  blur_kernel_n, (determinant_file_n, log_of_determinant_file_n)]
    #
    # Now the only thing we really want to do, is to resample the actual log
    # determinants that were generated into the space of the common average.
    # To make that a little easier, I'll create a mapping that will contain:
    #
    # {subject_ID: Subject(intersubject_timepoint, {time_pt_1: [stat_file_1, ..., stat_file_n],
    #                                               ...,
    #                                               time_pt_n: [stat_file_1, ..., stat_file_n]}
    # }
    for s_id, subject_with_determinants in determinants_from_subject_common_to_subject.items():
        transform_from_common_subj_to_common_avg = subj_ID_to_xfm_handler_to_common_avg[s_id].xfm
        for time_pt, determinant_info in subject_with_determinants.time_pt_dict.items():
            # here, each determinant_info is a DataFrame where each row contains
            # 'abs_det', 'nlin_det', 'log_nlin_det', 'log_abs_det', 'fwhm' fields
            # of the log-determinants, blurred at various fwhms (corresponding to different rows)
            for _ix, row in determinant_info.iterrows():
                for log_det_file_to_resample in (row.log_full_det, row.log_nlin_det):
                    # TODO the MincAtoms corresponding to the resampled files are never returned
                    new_name_wo_ext = log_det_file_to_resample.filename_wo_ext + "_resampled_to_common"
                    s.defer(mincresample(img=log_det_file_to_resample,
                                         xfm=transform_from_common_subj_to_common_avg,
                                         like=log_det_file_to_resample,
                                         new_name_wo_ext=new_name_wo_ext,
                                         subdir="stats-volumes"))

    # Ad 2) provide transformations from the common avg to each subject. That's the
    #       inverse of what was provided by get_chain_transforms_for_stats()
    determinants_from_common_avg_to_subject = map_over_time_pt_dict_in_Subject(
        lambda xfm: s.defer(determinants_at_fwhms(xfms=[s.defer(invert_xfmhandler(xfm))],
                                                  inv_xfms=[xfm],  # determinants_at_fwhms now vectorized-unhelpful here
                                                  blur_fwhms=options.stats.stats_kernels)),
        non_rigid_xfms_to_common_avg)

    # TODO don't just return an (unnamed-)tuple here
    return Result(stages=s, output=Namespace(non_rigid_xfms_to_common=non_rigid_xfms_to_common_avg,
                                             determinants_from_common_avg_to_subject=determinants_from_common_avg_to_subject,
                                             determinants_from_subject_common_to_subject=determinants_from_subject_common_to_subject))