def __init__(self,
                 frame,
                 realimage,
                 synthimage,
                 cellmap,
                 config,
                 distmap=None):
        self.frame = frame
        self.realimage = realimage
        self.old_synthimage = synthimage
        self.cellmap = cellmap
        self.old_simulation_config = frame.simulation_config
        self.new_simulation_config = frame.simulation_config.copy()
        self.config = config

        offset_mu = config["background_offset.mu"]
        offset_sigma = config["background_offset.sigma"]

        cell_brightness_mu = config["cell_brightness.mu"]
        cell_brightness_sigma = config["cell_brightness.sigma"]

        self.new_simulation_config["background.color"] += np.random.normal(
            offset_mu, offset_sigma)
        self.new_simulation_config["cell.color"] += np.random.normal(
            cell_brightness_mu, cell_brightness_sigma)
        self.new_synthimage, _ = optimization.generate_synthetic_image(
            frame.nodes, realimage.shape, self.new_simulation_config)
def save_output(imagefiles, realimages, lineage: LineageM, args, lineagefile):
    shape = realimages[0].shape
    for frame_index in range(len(lineage.frames)):
        realimage = realimages[frame_index]
        cellnodes = lineage.frames[frame_index].nodes
        synthimage = optimization.generate_synthetic_image(
            cellnodes, realimage.shape, grey_synthetic_image)
        cost = optimization.objective(realimage, synthimage)
        print('Final Cost:', cost)

        frame = np.empty((shape[0], shape[1], 3))
        frame[..., 0] = realimage
        frame[..., 1] = frame[..., 0]
        frame[..., 2] = frame[..., 0]

        if not args.output.is_dir():
            args.output.mkdir()

        for node in cellnodes:
            node.cell.drawoutline(frame, (1, 0, 0))
            properties = [imagefiles[frame_index].name, node.cell.name]
            properties.extend([
                str(node.cell.x),
                str(node.cell.y),
                str(node.cell.width),
                str(node.cell.length),
                str(node.cell.rotation)
            ])
            print(','.join(properties), file=lineagefile)

        frame = np.clip(frame, 0, 1)

        debugimage = Image.fromarray((255 * frame).astype(np.uint8))
        debugimage.save(args.output / imagefiles[frame_index].name)
def save_output(imagefiles, realimages, lineage: LineageM, args):
    shape = realimages[0].shape
    for frame_index in range(len(lineage.frames)):
        realimage = realimages[frame_index]
        cellnodes = lineage.frames[frame_index].nodes
        synthimage = optimization.generate_synthetic_image(
            cellnodes, realimage.shape, grey_synthetic_image)
        cost = optimization.objective(realimage, synthimage)
        print('Final Cost:', cost)

        frame = np.empty((shape[0], shape[1], 3))
        frame[..., 0] = realimage
        frame[..., 1] = frame[..., 0]
        frame[..., 2] = frame[..., 0]

        for node in cellnodes:
            node.cell.drawoutline(frame, (1, 0, 0))

        frame = np.clip(frame, 0, 1)

        debugimage = Image.fromarray((255 * frame).astype(np.uint8))
        debugimage.save(args.output / imagefiles[frame_index].name)
示例#4
0
    def __init__(self,
                 frame,
                 realimage,
                 synthimage,
                 cellmap,
                 config,
                 distmap=None):
        self.frame = frame
        self.realimage = realimage
        self.old_synthimage = synthimage
        self.cellmap = cellmap
        self.old_simulation_config = frame.simulation_config
        self.new_simulation_config = frame.simulation_config.copy()
        self.config = config

        offset_mu = config["perturbation"]["modification.background_offset.mu"]
        offset_sigma = config["perturbation"][
            "modification.background_offset.sigma"]
        self.new_simulation_config["background.color"] += random.gauss(
            mu=offset_mu, sigma=offset_sigma)
        self.new_synthimage, _ = optimization.generate_synthetic_image(
            frame.nodes, realimage.shape, self.new_simulation_config)
    def __init__(self,
                 frame,
                 realimage,
                 synthimage,
                 cellmap,
                 config,
                 distmap=None):
        self.frame = frame
        self.realimage = realimage
        self.old_synthimage = synthimage
        self.cellmap = cellmap
        self.old_simulation_config = frame.simulation_config
        self.new_simulation_config = frame.simulation_config.copy()
        self.config = config

        opacity_offset_mu = config["opacity_offset.mu"]
        opacity_offset_sigma = config["opacity_offset.sigma"]

        diffraction_strength_offset_mu = config[
            "diffraction_strength_offset.mu"]
        diffraction_strength_offset_sigma = config[
            "diffraction_strength_offset.sigma"]

        diffraction_sigma_offset_mu = config["diffraction_sigma_offset.mu"]
        diffraction_sigma_offset_sigma = config[
            "diffraction_sigma_offset.sigma"]

        self.new_simulation_config["cell.opacity"] += np.random.normal(
            opacity_offset_mu, opacity_offset_sigma)
        self.new_simulation_config[
            "light.diffraction.strength"] += np.random.normal(
                diffraction_strength_offset_mu,
                diffraction_strength_offset_sigma)
        self.new_simulation_config[
            "light.diffraction.sigma"] += np.random.normal(
                diffraction_sigma_offset_mu, diffraction_sigma_offset_sigma)
        self.new_synthimage, _ = optimization.generate_synthetic_image(
            frame.nodes, realimage.shape, self.new_simulation_config)
def optimize(imagefiles, lineageframes, lineagefile, args, config):
    # optimize normally and copy to my data structures
    lineage = build_initial_lineage(imagefiles, lineageframes, args, config)
    realimages = [
        optimization.load_image(imagefile) for imagefile in imagefiles
    ]
    shape = realimages[0].shape
    diffimages = []

    for frame_index, realimage in enumerate(realimages):
        synthimage = optimization.generate_synthetic_image(
            lineage.frames[frame_index].nodes, shape, grey_synthetic_image)
        diffimages.append(realimage - synthimage)

    # simulated annealing
    run_count = 2000 * lineage.total_cell_count
    temperature = args.temp
    end_temperature = args.endtemp
    alpha = (end_temperature / temperature)**(1 / run_count)

    bad_count = 0
    bad_accepted = 0

    for iteration in range(run_count):
        frame_index = lineage.choose_random_frame_index()
        frame = lineage.frames[frame_index]
        node = random.choice(frame.nodes)
        change = None

        if frame_index < len(lineage.frames) - 1 and random.random() < 1 / 3:
            change = Combination(node, config, diffimages[frame_index + 1],
                                 lineage.frames[frame_index + 1])
            if not change.is_valid:
                change = None

        if not change and frame_index < len(
                lineage.frames) - 1 and random.random() < 2 / 3:
            change = Split(node, config, diffimages[frame_index + 1],
                           lineage.frames[frame_index + 1])
            if not change.is_valid:
                change = None

        if not change:
            change = Perturbation(node, config, diffimages[frame_index])
            if not change.is_valid:
                continue

        # apply if acceptable
        costdiff = change.costdiff

        if costdiff <= 0:
            acceptance = 1.0
        else:
            bad_count += 1
            acceptance = np.exp(-costdiff / temperature)

        if acceptance > random.random():
            if acceptance < 1:
                bad_accepted += 1
            change.apply()

        if iteration % 1000 == 0 and bad_count > 0:
            print('pbad:', bad_accepted / bad_count)
            bad_count = bad_accepted = 0

        temperature *= alpha

    save_output(imagefiles, realimages, lineage, args)
def optimize(imagefiles, lineageframes, lineagefile, args, config):
    global grey_synthetic_image, useDistanceObjective
    grey_synthetic_image = args.graysynthetic
    useDistanceObjective = args.dist

    # optimize normally and copy to my data structures
    lineage = build_initial_lineage(imagefiles, lineageframes, args, config)
    realimages = [
        optimization.load_image(imagefile) for imagefile in imagefiles
    ]
    shape = realimages[0].shape
    diffimages = []
    distmaps = []
    if not useDistanceObjective:
        distmaps = [None] * len(realimages)

    for frame_index, realimage in enumerate(realimages):
        synthimage = optimization.generate_synthetic_image(
            lineage.frames[frame_index].nodes, shape, grey_synthetic_image)
        diffimages.append(realimage - synthimage)
        if useDistanceObjective:
            distmap = distance_transform_edt(realimage < .5)
            distmap /= config[
                f'{config["global.cellType"].lower()}.distanceCostDivisor'] * config[
                    'global.pixelsPerMicron']
            distmap += 1
            distmaps.append(distmap)

    # simulated annealing
    run_count = 500 * lineage.total_cell_count
    temperature = args.start_temp
    end_temperature = args.end_temp
    alpha = (end_temperature / temperature)**(1 / run_count)

    bad_count = 0
    bad_accepted = 0

    for iteration in range(run_count):
        frame_index = lineage.choose_random_frame_index()
        frame = lineage.frames[frame_index]
        node = random.choice(frame.nodes)
        change = None

        if frame_index < len(lineage.frames) - 1 and random.random() < 1 / 3:
            change = Combination(node, config, diffimages[frame_index + 1],
                                 lineage.frames[frame_index + 1],
                                 distmaps[frame_index + 1])
            if not change.is_valid:
                change = None

        if not change and frame_index < len(
                lineage.frames) - 1 and random.random() < 2 / 3:
            change = Split(node, config, diffimages[frame_index + 1],
                           lineage.frames[frame_index + 1],
                           distmaps[frame_index + 1])
            if not change.is_valid:
                change = None

        if not change:
            change = Perturbation(node, config, diffimages[frame_index],
                                  distmaps[frame_index])
            if not change.is_valid:
                continue

        # apply if acceptable
        costdiff = change.costdiff

        if costdiff <= 0:
            acceptance = 1.0
        else:
            bad_count += 1
            acceptance = np.exp(-costdiff / temperature)

        if acceptance > random.random():
            if acceptance < 1:
                bad_accepted += 1
            change.apply()

        temperature *= alpha

    save_output(imagefiles, realimages, lineage, args, lineagefile)
示例#8
0
def main(args):
    """Main function of cellanneal."""
    if (args.start_temp is not None or args.end_temp is not None) and args.auto_temp == 1:
        raise Exception("when auto_temp is set to 1(default value), starting temperature or ending temperature should not be set manually")

    # if not args.no_parallel:
    #     import dask
    #     from dask.distributed import Client, LocalCluster
    #     if not args.cluster:
    #         cluster = LocalCluster(
    #             n_workers=args.workers,local_dir="/tmp/CellUniverse/dask-worker-space"
    #         )
    #     else:
    #         cluster = args.cluster
    #
    #     client = Client(cluster)
    # else:
    client = None

    lineagefile = None
    start = time.time()

    try:
        config = load_config(args.config)
        
        simulation_config = config["simulation"]
        #Maybe better to store the image type in the config file in the first place, instead of using cmd?
        if args.graySynthetic == True:
            simulation_config["image.type"] = "graySynthetic"
        elif args.phaseContrast == True:
            simulation_config["image.type"] = "phaseContrastImage"
        elif args.binary == True:
            simulation_config["image.type"] = "binary"
        else:
            raise ValueError("Invalid Command: Synthetic image type must be specified")
        
        if not args.output.is_dir():
            args.output.mkdir()
        if not args.bestfit.is_dir():
            args.bestfit.mkdir()
        if args.residual and not args.residual.is_dir():
            args.residual.mkdir()
            
        seed = int(start * 1000) % (2**32)
        if args.seed != None:
            seed = args.seed
        np.random.seed(seed)
        print("Seed: {}".format(seed))
        
        
        celltype = config['global.cellType'].lower()

        # setup the colony from a file with the initial properties
        lineageframes = LineageFrames()
        colony = lineageframes.forward()
        imagefiles =  get_inputfiles(args)
        if args.lineage_file:
            load_colony(colony, args.lineage_file, config, initial_frame = imagefiles[0].name)
        else:
            load_colony(colony, args.initial, config)
        cost_diff = (-1, -1)

        # open the lineage file for writing
        lineagefile = open(args.output/'lineage.csv', 'w')
        header = ['file', 'name']
        if celltype == 'bacilli':
            header.extend(['x', 'y', 'width', 'length', 'rotation', "split_alpha", "opacity"])
        print(','.join(header), file=lineagefile)

        if args.debug:
            with open(args.debug/'debug.csv', 'w') as debugfile:
                print(','.join(['window_start', 'window_end', 'pbad_total', 'bad_count', 'temperature', 'total_cost_diff', 'current_iteration', 'total_iterations']), file=debugfile)


        if args.global_optimization:
            global useDistanceObjective
            
            useDistanceObjective = args.dist
            realimages = [optimization.load_image(imagefile) for imagefile in imagefiles]
            window = config["global_optimizer.window_size"]
            if args.lineage_file:
                lineage = global_optimization.build_initial_lineage(imagefiles, args.lineage_file, args.continue_from, config["simulation"])
            else:
                lineage = global_optimization.build_initial_lineage(imagefiles, args.initial, args.continue_from, config["simulation"])
            lineage = global_optimization.find_optimal_simulation_confs(imagefiles, lineage, realimages, args.continue_from)
            sim_start = args.continue_from - args.frame_first
            print(sim_start)
            shape = realimages[0].shape
            synthimages = []
            cellmaps = []
            distmaps = []
            iteration_per_cell = config["iteration_per_cell"]
            if not useDistanceObjective:
                distmaps = [None] * len(realimages)
            for window_start in range(1 - window, len(realimages)):
                 window_end = window_start + window
                 print(window_start, window_end)
                 if window_end <= len(realimages):
                        # get initial estimate
                    if window_start >= sim_start:
                        if window_end > 1:
                            lineage.copy_forward()
                    realimage = realimages[window_end - 1]
                    synthimage, cellmap = optimization.generate_synthetic_image(lineage.frames[window_end - 1].nodes, shape, lineage.frames[window_end - 1].simulation_config)
                    synthimages.append(synthimage)
                    cellmaps.append(cellmap)
                    if useDistanceObjective:
                        distmap = distance_transform_edt(realimage < .5)
                        distmap /= config[f'{config["global.cellType"].lower()}.distanceCostDivisor'] * config[
                            'global.pixelsPerMicron']
                        distmap += 1
                        distmaps.append(distmap)
                    if args.auto_temp == 1 and window_end == 1:
                        print("auto temperature schedule started")
                        args.start_temp, args.end_temp = \
                            global_optimization.auto_temp_schedule(imagefiles, lineage, realimages, synthimages, cellmaps, distmaps, 0, 1, lineagefile, args, config)
                        print("auto temperature schedule finished")
                        print("starting temperature is ", args.start_temp, "ending temperature is ", args.end_temp)
                    if args.auto_meth == "frame" and optimization.auto_temp_schedule_frame(window_end, 3):
                        print("auto temperature schedule restarted")
                        args.start_temp, args.end_temp = \
                            global_optimization.auto_temp_schedule(imagefiles, lineage, realimages, synthimages, cellmaps, distmaps, window_start, window_end, lineagefile, args, config)
                        print("auto temperature schedule finished")
                        print("starting temperature is ", args.start_temp, "ending temperature is ", args.end_temp)
                 if window_start >= sim_start:
                    if useDistanceObjective:
                        global_optimization.totalCostDiff = optimization.dist_objective(realimage, synthimage, distmap, cellmap, config["overlap.cost"])
                    else:
                        global_optimization.totalCostDiff = optimization.objective(realimage, synthimage, cellmap, config["overlap.cost"], config["cell.importance"])
                    global_optimization.optimize(imagefiles, lineage, realimages, synthimages, cellmaps, distmaps, window_start, window_end, lineagefile, args, config, iteration_per_cell)
                 if window_start >= 0:
                    global_optimization.save_lineage(imagefiles[window_start].name, lineage.frames[window_start].nodes, lineagefile)
                    global_optimization.save_output(imagefiles[window_start].name, synthimages[window_start], realimages[window_start], lineage.frames[window_start].nodes, args, config)
            return 0
        
        config["simulation"] = optimization.find_optimal_simulation_conf(config["simulation"], optimization.load_image(imagefiles[0]), list(colony))
        if args.auto_temp == 1:
            print("auto temperature schedule started")
            args.start_temp, args.end_temp = optimization.auto_temp_schedule(imagefiles[0], lineageframes.forward(), args, config)
            print("auto temperature schedule finished")
            print("starting temperature is ", args.start_temp, "ending temperature is ", args.end_temp)

        frame_num = 0
        prev_cell_num = len(colony)
        for imagefile in imagefiles: # Recomputing temperature when needed

            frame_num += 1

            if args.auto_meth == "frame":
                if optimization.auto_temp_schedule_frame(frame_num, 8):
                    print("auto temperature schedule started (recomputed)")
                    args.start_temp, args.end_temp = optimization.auto_temp_schedule(imagefile, colony, args, config)
                    print("auto temperature schedule finished")
                    print("starting temperature is ", args.start_temp, "ending temperature is ", args.end_temp)

            elif args.auto_meth == "factor":
                if optimization.auto_temp_schedule_factor(len(colony), prev_cell_num, 1.1):
                    print("auto temperature schedule started (recomputed)")
                    args.start_temp, args.end_temp = optimization.auto_temp_schedule(imagefile, colony, args, config)
                    print("auto temperature schedule finished")
                    print("starting temperature is ", args.start_temp, "ending temperature is ", args.end_temp)
                    prev_cell_num = len(colony)

            elif args.auto_meth == "const":
                if optimization.auto_temp_schedule_const(len(colony), prev_cell_num, 10):
                    print("auto temperature schedule started (recomputed)")
                    args.start_temp, args.end_temp = optimization.auto_temp_schedule(imagefile, colony, args, config)
                    print("auto temperature schedule finished")
                    print("starting temperature is ", args.start_temp, "ending temperature is ", args.end_temp)
                    prev_cell_num = len(colony)

            elif args.auto_meth == "cost":
                print(cost_diff, frame_num, optimization.auto_temp_shcedule_cost(cost_diff))
                if frame_num >= 2 and optimization.auto_temp_shcedule_cost(cost_diff):
                    print("auto temperature schedule started cost_diff (recomputed)")
                    args.start_temp, args.end_temp = optimization.auto_temp_schedule(imagefile, colony, args, config)
                    print("auto temperature schedule finished")
                    print("starting temperature is ", args.start_temp, "ending temperature is ", args.end_temp)

            colony = optimize(imagefile, lineageframes, args, config, client)

            cost_diff = optimization.update_cost_diff(colony, cost_diff)

            # flatten modifications and save cell properties

            colony.flatten()
            for cellnode in colony:
                properties = [imagefile.name, cellnode.cell.name]
                if celltype == 'bacilli':                                   
                    properties.extend([
                        str(cellnode.cell.x),
                        str(cellnode.cell.y),
                        str(cellnode.cell.width),
                        str(cellnode.cell.length),
                        str(cellnode.cell.rotation)])
                print(','.join(properties), file=lineagefile)

    except KeyboardInterrupt as error:
        raise error
    finally:
        if lineagefile:
            lineagefile.close()

    print(f'{time.time() - start} seconds')

    return 0
示例#9
0
def optimize(imagefiles,
             lineageframes,
             lineagefile,
             args,
             config,
             iteration_per_cell=6000,
             in_auto_temp_schedule=False,
             const_temp=None):
    global useDistanceObjective
    useDistanceObjective = args.dist

    if not args.output.is_dir():
        args.output.mkdir()
    if not args.bestfit.is_dir():
        args.bestfit.mkdir()
    if args.residual and not args.output.is_dir():
        args.residual.mkdir()

    lineage = build_initial_lineage(imagefiles, lineageframes, args, config)
    realimages = [
        optimization.load_image(imagefile) for imagefile in imagefiles
    ]
    shape = realimages[0].shape
    synthimages = []
    cellmaps = []
    distmaps = []
    pbad_total = 0
    window = config["global_optimizer.window_size"]
    perturbation_prob = config["prob.perturbation"]
    combine_prob = config["prob.combine"]
    split_prob = config["prob.split"]
    background_offset_prob = config["perturbation"]["prob.background_offset"]
    residual_vmin = config["residual.vmin"]
    residual_vmax = config["residual.vmax"]
    if args.residual:
        colormap = cm.ScalarMappable(norm=Normalize(vmin=residual_vmin,
                                                    vmax=residual_vmax),
                                     cmap="bwr")
    if not useDistanceObjective:
        distmaps = [None] * len(realimages)

    for window_start in range(1 - window, len(realimages)):
        window_end = window_start + window
        print(window_start, window_end)

        if window_end <= len(realimages):
            # get initial estimate
            if window_end > 1:
                lineage.copy_forward()

            # add next diffimage
            realimage = realimages[window_end - 1]
            synthimage, cellmap = optimization.generate_synthetic_image(
                lineage.frames[-1].nodes, shape,
                lineage.frames[-1].simulation_config)
            synthimages.append(synthimage)
            cellmaps.append(cellmap)
            if useDistanceObjective:
                distmap = distance_transform_edt(realimage < .5)
                distmap /= config[
                    f'{config["global.cellType"].lower()}.distanceCostDivisor'] * config[
                        'global.pixelsPerMicron']
                distmap += 1
                distmaps.append(distmap)

        # simulated annealing
        run_count = iteration_per_cell * lineage.count_cells_in(
            window_start, window_end) // window
        print(run_count)
        bad_count = 0
        for iteration in range(run_count):
            frame_index = lineage.choose_random_frame_index(
                window_start, window_end)
            if in_auto_temp_schedule:
                temperature = const_temp
            else:
                frame_start_temp = gerp(args.end_temp, args.start_temp,
                                        (frame_index - window_start + 1) /
                                        window)
                frame_end_temp = gerp(args.end_temp, args.start_temp,
                                      (frame_index - window_start) / window)
                temperature = gerp(frame_start_temp, frame_end_temp,
                                   iteration / (run_count - 1))
            frame = lineage.frames[frame_index]
            node = random.choice(frame.nodes)
            change_option = np.random.choice(
                ["split", "perturbation", "combine", "background_offset"],
                p=[
                    split_prob, perturbation_prob, combine_prob,
                    background_offset_prob
                ])
            change = None
            if change_option == "split" and random.random(
            ) < optimization.split_proba(node.cell.length) and frame_index > 0:
                change = Split(node.parent, config, realimages[frame_index],
                               synthimages[frame_index], cellmaps[frame_index],
                               lineage.frames[frame_index],
                               distmaps[frame_index])

            elif change_option == "perturbation":
                change = Perturbation(node, config, realimages[frame_index],
                                      synthimages[frame_index],
                                      cellmaps[frame_index],
                                      lineage.frames[frame_index],
                                      distmaps[frame_index])

            elif change_option == "combine" and frame_index > 0:
                change = Combination(node.parent, config,
                                     realimages[frame_index],
                                     synthimages[frame_index],
                                     cellmaps[frame_index],
                                     lineage.frames[frame_index],
                                     distmaps[frame_index])

            elif change_option == "background_offset" and frame_index > 0 and config[
                    "simulation"]["image.type"] == "graySynthetic":
                change = BackGround_luminosity_offset(
                    lineage.frames[frame_index], realimages[frame_index],
                    synthimages[frame_index], cellmaps[frame_index], config)

            if change and change.is_valid:
                # apply if acceptable
                costdiff = change.costdiff

                if costdiff <= 0:
                    acceptance = 1.0
                else:
                    bad_count += 1
                    acceptance = np.exp(-costdiff / temperature)
                    pbad_total += acceptance

                if acceptance > random.random():
                    change.apply()
        if in_auto_temp_schedule:
            print("pbad is ", pbad_total / bad_count)
            return pbad_total / bad_count

        #output module
        if window_start >= 0:
            bestfit_frame = Image.fromarray(
                np.uint8(255 * synthimages[window_start]), "L")
            bestfit_frame.save(args.bestfit / imagefiles[window_start].name)

            output_frame = np.empty((realimages[frame_index].shape[0],
                                     realimages[frame_index].shape[1], 3))
            output_frame[..., 0] = realimages[frame_index]
            output_frame[..., 1] = output_frame[..., 0]
            output_frame[..., 2] = output_frame[..., 0]
            for node in lineage.frames[frame_index].nodes:
                node.cell.drawoutline(output_frame, (1, 0, 0))
            output_frame = Image.fromarray(np.uint8(255 * output_frame))
            output_frame.save(args.output / imagefiles[window_start].name)

            if args.residual:
                residual_frame = Image.fromarray(
                    np.uint8(255 * colormap.to_rgba(
                        np.clip(
                            realimages[window_start] -
                            synthimages[window_start], residual_vmin,
                            residual_vmax))), "RGBA")
                residual_frame.save(args.residual /
                                    imagefiles[window_start].name)

    save_output(imagefiles, realimages, synthimages, cellmaps, lineage, args,
                lineagefile, config)
示例#10
0
def optimize(imagefiles, lineageframes, lineagefile, args, config, window=5):
    global useDistanceObjective
    useDistanceObjective = args.dist

    lineage = build_initial_lineage(imagefiles, lineageframes, args, config)
    realimages = [optimization.load_image(imagefile) for imagefile in imagefiles]
    shape = realimages[0].shape
    synthimages = []
    cellmaps = []
    distmaps = []
    if not useDistanceObjective:
        distmaps = [None] * len(realimages)

    for window_start in range(1 - window, len(realimages)):
        window_end = window_start + window
        print(window_start, window_end)

        if window_end <= len(realimages):
            # get initial estimate
            if window_end > 1:
                lineage.copy_forward()

            # add next diffimage
            realimage = realimages[window_end - 1]
            synthimage, cellmap = optimization.generate_synthetic_image(lineage.frames[-1].nodes, shape, config["simulation"])
            synthimages.append(synthimage)
            cellmaps.append(cellmap)
            if useDistanceObjective:
                distmap = distance_transform_edt(realimage < .5)
                distmap /= config[f'{config["global.cellType"].lower()}.distanceCostDivisor'] * config[
                    'global.pixelsPerMicron']
                distmap += 1
                distmaps.append(distmap)

        # simulated annealing
        run_count = 2000*lineage.count_cells_in(window_start, window_end)//window
        print(run_count)
        bad_count = 0
        bad_accepted = 0
        for iteration in range(run_count):
            frame_index = lineage.choose_random_frame_index(window_start, window_end)
            frame_start_temp = gerp(args.end_temp, args.start_temp, (frame_index - window_start + 1)/window)
            frame_end_temp = gerp(args.end_temp, args.start_temp, (frame_index - window_start)/window)
            temperature = gerp(frame_start_temp, frame_end_temp, iteration/(run_count - 1))

            frame = lineage.frames[frame_index]
            node = random.choice(frame.nodes)
            change = None

            if frame_index > 0 and random.random() < 1/3:
                change = Combination(node.parent, config, realimages[frame_index], synthimages[frame_index], cellmaps[frame_index], lineage.frames[frame_index], distmaps[frame_index])
                if not change.is_valid:
                    change = None

            if not change and frame_index > 0 and random.random() < 2/3:
                change = Split(node.parent, config, realimages[frame_index], synthimages[frame_index], cellmaps[frame_index], lineage.frames[frame_index], distmaps[frame_index])
                if not change.is_valid:
                    change = None

            if not change:
                change = Perturbation(node, config, realimages[frame_index], synthimages[frame_index], cellmaps[frame_index], distmaps[frame_index])
                if not change.is_valid:
                    continue

            # apply if acceptable
            costdiff = change.costdiff

            if costdiff <= 0:
                acceptance = 1.0
            else:
                bad_count += 1
                acceptance = np.exp(-costdiff / temperature)

            if acceptance > random.random():
                if acceptance < 1:
                    bad_accepted += 1
                change.apply()

    save_output(imagefiles, realimages, cellmaps, lineage, args, lineagefile, config['simulation'])