1 Changelog

  • 20230328: Reimplemented steps as functions, connected them in one set of calls.
  • 20230317: Initial cellpose implementation.
  • 20230315: Separation of input image into timepoints.
  • 20230310: Used geopandas to trace cells over time by position.
  • 20230307: Setting up my environment to handle ipynb and python markdown.

2 Settings which may be necessary in emacs

I have been messing with my emacs setup to make this project easier.

(defvar elpy-rpc-virtualenv-path 'current)
(pyvenv-activate "/sw/local/fiji/202211")
(pyvenv-activate "venv")

3 Introduction

Jacques is seeking to segment and follow cells over time.

Steps performed so far:

  1. Python/venv installation of fiji
  2. Figured out some easy interactions between python and actual image data.
  3. Implemented a simple function to find minimum distances between putative cells using the extant methods.

4 Next steps

  1. Note that the czi images are immediately openable via fiji’s bioformat interface.
  2. Load dataset via fiji
  3. Invoke cellpose via the python interface (same as pyimagej)
  4. Save ROIs produced by cellpose, save them to zip output file
  1. The roi interface in fiji can address these
  1. Currently a macro is invoked which performs a set of measurements on every ROI and saves them to a csv. This creates the primary data structure used for all following processes.
  2. Need to create a datastructure which identifies each individual cell over time given these ROIs which x/y/time(frame) along with the measurements of interest (area/mean/stdev/intensities by color).
    1. Small postprocessing details: the intensity values produced must be normalized by cell area.

5 Demonstrate the first couple of steps using an actual dataset

Jacques sent me an image acquired from the microscope and I saved it as ‘test_data/raw.tif’, he also sent me a cellpose model which I saved to the ‘models/’ directory.

Given these as a starting point, let us try to open the image with a fiji instance and split it into a series of smaller files by timepoint.

5.1 Load necessary python modules

import os
import glob
import imagej
import pandas
from pandas import DataFrame
import numpy as np
import geopandas
import matplotlib.pyplot as plt
import seaborn
import scyjava
import multiprocessing as mp
from cellpose import models, io
from cellpose.io import *
from collections import defaultdict
from jpype import JArray, JInt

5.2 Set up some initial variables

base_dir = '/lab/scratch/atb/imaging/mtb_2023'
os.chdir(base_dir)
input_file = f"{base_dir}/test_data/raw.tif"
output_dir = f"{base_dir}/outputs"
pandas.set_option('display.max_columns', None)
verbose = True

5.3 Start imagej/fiji

Note that I am using fiji/imagej from within a virtual environment which I symbolically linked to the local directory ‘venv/’.

As a result, when I initialize fiji, I will call the base directory of the downloaded fiji tree within the virtual environment, which I somewhat erroneously put in bin/.

scyjava.config.add_option('-Xmx64g')
start_dir = os.getcwd()
ij = imagej.init('venv/bin/Fiji.app', mode = 'interactive')
## Something about this init() function changes the current working directory.
os.chdir(start_dir)
ij.getVersion()
## '2.9.0/1.53t'
PolygonRoi = scyjava.jimport('ij.gui.PolygonRoi')
Overlay = scyjava.jimport('ij.gui.Overlay')
Regions = scyjava.jimport('net.imglib2.roi.Regions')
LabelRegions = scyjava.jimport('net.imglib2.roi.labeling.LabelRegions')
ov = Overlay()

5.4 Open the input file and split it by time

Open the file, figure out its dimensions, and write portions of it to an output directory.

I wrote this thinking I could parallelize the output writing to 8 cpus. But I think I do not yet understand how python scopes variables in this context and so it did not quite work. I turned that off for the moment and ran it and it finished in about a minute.

The following function may require a test to see if the output directory already exists because scijava will freak out.

def separate_slices(input_file, output_base = 'outputs', wanted_x = True, wanted_y = True,
                    wanted_z = 1, wanted_channel = 2, cpus = 8, overwrite = False):
    pool = mp.Pool(cpus)
    input_base = os.path.basename(input_file)
    input_dir = os.path.dirname(input_file)
    input_name = os.path.splitext(input_base)[0]
    output_directory = f"{input_dir}/outputs/{input_name}_substack"
    os.makedirs(output_directory, exist_ok = True)
    if verbose:
        print("Starting to open the input file, this takes a moment.")
    dataset = ij.io().open(input_file)
    if verbose:
        print(f"Opened input file, writing images to {output_directory}")

    data_info = {}
    for element in range(len(dataset.dims)):
        name = dataset.dims[element]
        data_info[name] = dataset.shape[element]
    if verbose:
        print((f"This dataset has dimensions: X:{data_info['X']}",
              f"Y:{data_info['Y']} Z:{data_info['Z']} Time:{data_info['Time']}"))

    def save_slice(timepoint):
        wanted_slice = dataset[:, :, wanted_channel, wanted_z, timepoint]
        slice_image = ij.py.to_dataset(wanted_slice)
        output_filename = f"{output_directory}/{input_name}_{timepoint}.tif"
        if (os.path.exists(output_filename)):
            if overwrite:
                print(f"Rewriting {output_filename}")
                os.remove(output_filename)
                saved = ij.io().save(slice_image, output_filename)
            else:
                if verbose:
                    print(f"Skipping {output_filename}, it already exists.")
        else:
            saved = ij.io().save(slice_image, output_filename)
            if verbose:
                print(f"Saving image {input_name}_{timepoint}.")
        return wanted_slice

    slices = []
    for timepoint in range(data_info['Time']):
        saved = save_slice(timepoint)
        slices.append(saved)
        ## I want to see if I can split the saves across 8 cpus.
        ## pool.apply(save_slice, args = (timepoint, ))
    ## pool.close()
    return dataset, slices, output_directory

5.5 Cellpose

At this point we should have a directory containing files of individual timepoints. Jacques sent me an initial implementation of the usage of cellpose to call individual cells. Let us include that now. I think the previous function should probably also return the directory of the separated input files.

def invoke_cellpose(input_directory, model_file, channels = [[0, 0]], diameter = 160,
                    threshold = 0.4, do_3D = False, batch_size = 64):
    """ Invoke cellpose using the split files from the previous function.

    Jacques mentioned that this is getting slightly different results than the
    GUI-invoked version of this same function.  I figured that since I have a minute,
    I can poke at this and see if I can understand the requisites before he arrives.
    """

    ## Relevant options:
    ## model_type(cyto, nuclei, cyto2), net_avg(T/F if load built in networks and average them)
    model = models.CellposeModel(pretrained_model = model_file)
    files = get_image_files(input_directory, '_masks', look_one_level_down = False)
    imgs = [imread(f) for f in files]
    nimg = len(imgs)
    if verbose:
        print(f"Read {nimg} images, starting cellpose.")
    ## Relevant options:
    ## batch_size(increase for more parallelization), channels(two element list of two element
    ## channels to segment; the first is the segment, second is optional nucleus;
    ## internal elements are color channels to query, so [[0,0],[2,3]] means do main cells in
    ## grayscale and a second with cells in blue, nuclei in green.
    ## channel_axis, z_axis ? invert (T/F flip pixels from b/w I assume),
    ## normalize(T/F percentile normalize the data), diameter, do_3d,
    ## anisotropy (rescaling factor for 3d segmentation), net_avg (average models),
    ## augment ?, tile ?, resample, interp, flow_threshold, cellprob_threshold (interesting),
    ## min_size (turned off with -1), stitch_threshold ?, rescale ?.
    output_files = defaultdict(dict)
    existing_files = 0
    for filename in files:
        f_name = os.path.splitext(filename)[0]
        output_mask = f"{start_dir}/{f_name}_cp_masks.png"
        output_txt = f"{start_dir}/{f_name}_cp_outlines.txt"
        output_files[filename]['input_file'] = f"{start_dir}/{filename}"
        output_files[filename]['output_mask'] = output_mask
        output_files[filename]['output_txt'] = output_txt
        output_files[filename]['exists'] = False
        if (os.path.exists(output_files[filename]['output_txt'])):
            existing_files = existing_files + 1
            output_files[filename]['exists'] = True

    if existing_files > 0:
        if verbose:
            print(f"Out of {nimg} output files, {existing_files} already exist, not running cellpose.")
    else:
        masks, flows, styles = model.eval(
            imgs, diameter = diameter, channels = channels, flow_threshold = threshold,
            do_3D = do_3D, batch_size = batch_size)
        io.save_to_png(imgs, masks, flows, files)
    if verbose:
        print("Finished cellpose, returning output filenames.")
    return output_files

6 Create Regions of interest from cellpose outputs

In Jacques notebook, it looks like he only extracts ROIs from one of the cellpose slices. I am assuming the goal is to extend this across all images?

There is an important caveat that I missed: imagej comes with a python2-based scripting language from which it appears some of his code is coming. As a result I should look carefully before using it, and pay close attention to the examples provided here for the most appropriate ways of interacting with the ROI manager etc:

https://github.com/imagej/pyimagej/blob/main/doc/examples/blob_detection_interactive.py

## The following is from a mix of a couple of implementations I found:
## https://pyimagej.readthedocs.io/en/latest/Classic-Segmentation.html
## an alternative method may be taken from:
## https://pyimagej.readthedocs.io/en/latest/Classic-Segmentation.html#segmentation-workflow-with-imagej2
## My goal is to pass the ROI regions to this function and create a similar df.
def slices_to_roi_measurements(cellpose_result, saved_slices):
    output_dict = cellpose_result
    cellpose_slices = list(cellpose_result.keys())
    slice_number = 0
    for slice_name in cellpose_slices:
        output_dict[slice_name]['slice_number'] = slice_number
        slice_data = saved_slices[slice_number]
        input_tif = cellpose_result[slice_name]['input_file']
        input_txt = cellpose_result[slice_name]['output_txt']
        input_mask = cellpose_result[slice_name]['output_mask']
        if verbose:
            print(f"Processing cellpose outline: {input_txt}")
        # convert Dataset to ImagePlus
        imp = ij.py.to_imageplus(slice_data)
        rm = ij.RoiManager.getRoiManager()
        ## The logic for this was taken from:
        ## https://stackoverflow.com/questions/73849418/is-there-any-way-to-switch-imagej-macro-code-to-python3-code
        txt_fh = open(input_txt, 'r')
        set_string = f'Set Measurements...'
        measure_string = f'area mean min centroid median skewness kurtosis redirect=None decimal=3'
        ij.IJ.run(set_string, measure_string)
        roi_stats = defaultdict(list)
        for line in txt_fh:
            xy = line.rstrip().split(",")
            xy_coords = [int(element) for element in xy]
            x_coords = [int(element) for element in xy[::2]]
            y_coords = [int(element) for element in xy[1::2]]
            xcoords_jint = JArray(JInt)(x_coords)
            ycoords_jint = JArray(JInt)(y_coords)
            polygon_roi_instance = scyjava.jimport('ij.gui.PolygonRoi')
            roi_instance = scyjava.jimport('ij.gui.Roi')
            imported_polygon = polygon_roi_instance(xcoords_jint, ycoords_jint,
                                                    len(x_coords), int(roi_instance.POLYGON))
            imp.setRoi(imported_polygon)
            ij.IJ.run(imp, 'Measure', '')
        slice_result = ij.ResultsTable.getResultsTable()
        slice_table = ij.convert().convert(slice_result,
                                           scyjava.jimport('org.scijava.table.Table'))
        slice_measurements = ij.py.from_java(slice_table)
        output_dict[slice_name]['measurements'] = slice_measurements
        ij.IJ.run('Clear Results')
        txt_fh.close()
        imp.setOverlay(ov)
        imp.getProcessor().resetMinAndMax()
        slice_number = slice_number + 1
    return output_dict

7 Convert the slice measurements to pandas df

slices_to_roi_measurements() returns a dictionary with keys which are the filenames of each raw tif file. Each element of that dictionary is in turn a dictionary containing some information about the files along with a df of the measurements provided by imagej.

My little geopandas function assumes a single long df with some columns which tell it which timepoint. So lets make a quick function to give that here. OTOH it may be wiser/better to make some changes to slices_to_roi_measurements() so that it returns that format df; but since I am using this as a learning experience to get more comfortable with python data structures, I will not do it that way.

def convert_slices_to_pandas(slices):
    concatenated = pandas.DataFrame()
    slice_keys = list(slices.keys())
    slice_counter = 0
    for k in slice_keys:
        slice_counter = slice_counter + 1
        current_slice = slices[k]
        if verbose:
            print(f"The slice is {k}")
        slice_number = current_slice['slice_number']
        slice_data = current_slice['measurements']
        slice_data['Frame'] = slice_number
        if (slice_counter == 1):
            concatenated = slice_data
        else:
            concatenated = pandas.concat([concatenated, slice_data])
    ## This is a little silly, but I couldn't remember that the index attribute
    ## is the numeric rowname for a moment
    ## The reset_index() does what it says on the tine, and changes the 1:19, 1:20, etc
    ## of each individual time Frame to a single range of 1:2000
    concatenated.index = concatenated.reset_index().index
    return concatenated

8 Create cell groups

def nearest_cells_over_time(df, max_dist = 10.0, max_prop = 0.7,
                            x_column = 'X', y_column = 'Y', verbose = True):
    """Trace cells over time"""
    gdf = geopandas.GeoDataFrame(
        df,
        geometry = geopandas.points_from_xy(df[x_column], df[y_column]))

    final_time = gdf.Frame.max()
    pairwise_distances = []
    for start_time in range(1, final_time):
        i = start_time
        j = i + 1
        ti_idx = gdf.Frame == i
        tj_idx = gdf.Frame == j
        if verbose:
            print(f"Getting distances of dfs {i} and {j}.")
        ti = gdf[ti_idx]
        tj = gdf[tj_idx]
        ti_rows = ti.shape[0]
        tj_rows = tj.shape[0]
        titj = geopandas.sjoin_nearest(ti, tj, distance_col = "pairwise_dist")
        pairwise_distances.append(titj)

    id_counter = 0
    ## Cell IDs pointing to a list of cells
    traced = {}
    ## Endpoints pointing to the cell IDs
    ends = {}
    for i in range(0, final_time - 1):
        query = pairwise_distances[i]
        passed_idx = query.pairwise_dist <= max_dist
        failed_idx = query.pairwise_dist > max_dist
        if (failed_idx.sum() > 0):
            if verbose:
                print(f"Skipped {failed_idx.sum()} elements in segment {i}.")
        query = query[passed_idx]

        prop_change = query.Area_left / query.Area_right
        increased_idx = prop_change > 1.0
        prop_change[increased_idx] = 1.0 / prop_change[increased_idx]
        failed_idx = prop_change < max_prop
        passed_idx = prop_change >= max_prop
        if (failed_idx.sum() > 0):
            if verbose:
                skip_string = (f"Skipped {failed_idx.sum()} elements in segment {i} ",
                               f"because the size changed too much.")
                print(skip_string)
            query = query[passed_idx]

        for row in query.itertuples():
            start_cell = row.Index
            end_cell = row.index_right
            if start_cell in ends.keys():
                cell_id = ends[start_cell]
                current_value = traced[cell_id]
                current_value.append(end_cell)
                traced[cell_id] = current_value
                ends[end_cell] = cell_id
            else:
                id_counter = id_counter + 1
                traced[id_counter] = [start_cell, end_cell]
                ends[end_cell] = id_counter

    return traced

9 Run the functions and plot the results

raw_split, saved_slices, output_directory = separate_slices(input_file)
## Starting to open the input file, this takes a moment.
## Opened input file, writing images to /lab/scratch/atb/imaging/mtb_2023/test_data/outputs/raw_substack
## ('This dataset has dimensions: X:1024', 'Y:1024 Z:13 Time:87')
## Skipping /lab/scratch/atb/imaging/mtb_2023/test_data/outputs/raw_substack/raw_0.tif, it already exists.
## Skipping /lab/scratch/atb/imaging/mtb_2023/test_data/outputs/raw_substack/raw_1.tif, it already exists.
## Skipping /lab/scratch/atb/imaging/mtb_2023/test_data/outputs/raw_substack/raw_2.tif, it already exists.
## Skipping /lab/scratch/atb/imaging/mtb_2023/test_data/outputs/raw_substack/raw_3.tif, it already exists.
## Skipping /lab/scratch/atb/imaging/mtb_2023/test_data/outputs/raw_substack/raw_4.tif, it already exists.
## Skipping /lab/scratch/atb/imaging/mtb_2023/test_data/outputs/raw_substack/raw_5.tif, it already exists.
## Skipping /lab/scratch/atb/imaging/mtb_2023/test_data/outputs/raw_substack/raw_6.tif, it already exists.
## Skipping /lab/scratch/atb/imaging/mtb_2023/test_data/outputs/raw_substack/raw_7.tif, it already exists.
## Skipping /lab/scratch/atb/imaging/mtb_2023/test_data/outputs/raw_substack/raw_8.tif, it already exists.
## Skipping /lab/scratch/atb/imaging/mtb_2023/test_data/outputs/raw_substack/raw_9.tif, it already exists.
## Skipping /lab/scratch/atb/imaging/mtb_2023/test_data/outputs/raw_substack/raw_10.tif, it already exists.
## Skipping /lab/scratch/atb/imaging/mtb_2023/test_data/outputs/raw_substack/raw_11.tif, it already exists.
## Skipping /lab/scratch/atb/imaging/mtb_2023/test_data/outputs/raw_substack/raw_12.tif, it already exists.
## Skipping /lab/scratch/atb/imaging/mtb_2023/test_data/outputs/raw_substack/raw_13.tif, it already exists.
## Skipping /lab/scratch/atb/imaging/mtb_2023/test_data/outputs/raw_substack/raw_14.tif, it already exists.
## Skipping /lab/scratch/atb/imaging/mtb_2023/test_data/outputs/raw_substack/raw_15.tif, it already exists.
## Skipping /lab/scratch/atb/imaging/mtb_2023/test_data/outputs/raw_substack/raw_16.tif, it already exists.
## Skipping /lab/scratch/atb/imaging/mtb_2023/test_data/outputs/raw_substack/raw_17.tif, it already exists.
## Skipping /lab/scratch/atb/imaging/mtb_2023/test_data/outputs/raw_substack/raw_18.tif, it already exists.
## Skipping /lab/scratch/atb/imaging/mtb_2023/test_data/outputs/raw_substack/raw_19.tif, it already exists.
## Skipping /lab/scratch/atb/imaging/mtb_2023/test_data/outputs/raw_substack/raw_20.tif, it already exists.
## Skipping /lab/scratch/atb/imaging/mtb_2023/test_data/outputs/raw_substack/raw_21.tif, it already exists.
## Skipping /lab/scratch/atb/imaging/mtb_2023/test_data/outputs/raw_substack/raw_22.tif, it already exists.
## Skipping /lab/scratch/atb/imaging/mtb_2023/test_data/outputs/raw_substack/raw_23.tif, it already exists.
## Skipping /lab/scratch/atb/imaging/mtb_2023/test_data/outputs/raw_substack/raw_24.tif, it already exists.
## Skipping /lab/scratch/atb/imaging/mtb_2023/test_data/outputs/raw_substack/raw_25.tif, it already exists.
## Skipping /lab/scratch/atb/imaging/mtb_2023/test_data/outputs/raw_substack/raw_26.tif, it already exists.
## Skipping /lab/scratch/atb/imaging/mtb_2023/test_data/outputs/raw_substack/raw_27.tif, it already exists.
## Skipping /lab/scratch/atb/imaging/mtb_2023/test_data/outputs/raw_substack/raw_28.tif, it already exists.
## Skipping /lab/scratch/atb/imaging/mtb_2023/test_data/outputs/raw_substack/raw_29.tif, it already exists.
## Skipping /lab/scratch/atb/imaging/mtb_2023/test_data/outputs/raw_substack/raw_30.tif, it already exists.
## Skipping /lab/scratch/atb/imaging/mtb_2023/test_data/outputs/raw_substack/raw_31.tif, it already exists.
## Skipping /lab/scratch/atb/imaging/mtb_2023/test_data/outputs/raw_substack/raw_32.tif, it already exists.
## Skipping /lab/scratch/atb/imaging/mtb_2023/test_data/outputs/raw_substack/raw_33.tif, it already exists.
## Skipping /lab/scratch/atb/imaging/mtb_2023/test_data/outputs/raw_substack/raw_34.tif, it already exists.
## Skipping /lab/scratch/atb/imaging/mtb_2023/test_data/outputs/raw_substack/raw_35.tif, it already exists.
## Skipping /lab/scratch/atb/imaging/mtb_2023/test_data/outputs/raw_substack/raw_36.tif, it already exists.
## Skipping /lab/scratch/atb/imaging/mtb_2023/test_data/outputs/raw_substack/raw_37.tif, it already exists.
## Skipping /lab/scratch/atb/imaging/mtb_2023/test_data/outputs/raw_substack/raw_38.tif, it already exists.
## Skipping /lab/scratch/atb/imaging/mtb_2023/test_data/outputs/raw_substack/raw_39.tif, it already exists.
## Skipping /lab/scratch/atb/imaging/mtb_2023/test_data/outputs/raw_substack/raw_40.tif, it already exists.
## Skipping /lab/scratch/atb/imaging/mtb_2023/test_data/outputs/raw_substack/raw_41.tif, it already exists.
## Skipping /lab/scratch/atb/imaging/mtb_2023/test_data/outputs/raw_substack/raw_42.tif, it already exists.
## Skipping /lab/scratch/atb/imaging/mtb_2023/test_data/outputs/raw_substack/raw_43.tif, it already exists.
## Skipping /lab/scratch/atb/imaging/mtb_2023/test_data/outputs/raw_substack/raw_44.tif, it already exists.
## Skipping /lab/scratch/atb/imaging/mtb_2023/test_data/outputs/raw_substack/raw_45.tif, it already exists.
## Skipping /lab/scratch/atb/imaging/mtb_2023/test_data/outputs/raw_substack/raw_46.tif, it already exists.
## Skipping /lab/scratch/atb/imaging/mtb_2023/test_data/outputs/raw_substack/raw_47.tif, it already exists.
## Skipping /lab/scratch/atb/imaging/mtb_2023/test_data/outputs/raw_substack/raw_48.tif, it already exists.
## Skipping /lab/scratch/atb/imaging/mtb_2023/test_data/outputs/raw_substack/raw_49.tif, it already exists.
## Skipping /lab/scratch/atb/imaging/mtb_2023/test_data/outputs/raw_substack/raw_50.tif, it already exists.
## Skipping /lab/scratch/atb/imaging/mtb_2023/test_data/outputs/raw_substack/raw_51.tif, it already exists.
## Skipping /lab/scratch/atb/imaging/mtb_2023/test_data/outputs/raw_substack/raw_52.tif, it already exists.
## Skipping /lab/scratch/atb/imaging/mtb_2023/test_data/outputs/raw_substack/raw_53.tif, it already exists.
## Skipping /lab/scratch/atb/imaging/mtb_2023/test_data/outputs/raw_substack/raw_54.tif, it already exists.
## Skipping /lab/scratch/atb/imaging/mtb_2023/test_data/outputs/raw_substack/raw_55.tif, it already exists.
## Skipping /lab/scratch/atb/imaging/mtb_2023/test_data/outputs/raw_substack/raw_56.tif, it already exists.
## Skipping /lab/scratch/atb/imaging/mtb_2023/test_data/outputs/raw_substack/raw_57.tif, it already exists.
## Skipping /lab/scratch/atb/imaging/mtb_2023/test_data/outputs/raw_substack/raw_58.tif, it already exists.
## Skipping /lab/scratch/atb/imaging/mtb_2023/test_data/outputs/raw_substack/raw_59.tif, it already exists.
## Skipping /lab/scratch/atb/imaging/mtb_2023/test_data/outputs/raw_substack/raw_60.tif, it already exists.
## Skipping /lab/scratch/atb/imaging/mtb_2023/test_data/outputs/raw_substack/raw_61.tif, it already exists.
## Skipping /lab/scratch/atb/imaging/mtb_2023/test_data/outputs/raw_substack/raw_62.tif, it already exists.
## Skipping /lab/scratch/atb/imaging/mtb_2023/test_data/outputs/raw_substack/raw_63.tif, it already exists.
## Skipping /lab/scratch/atb/imaging/mtb_2023/test_data/outputs/raw_substack/raw_64.tif, it already exists.
## Skipping /lab/scratch/atb/imaging/mtb_2023/test_data/outputs/raw_substack/raw_65.tif, it already exists.
## Skipping /lab/scratch/atb/imaging/mtb_2023/test_data/outputs/raw_substack/raw_66.tif, it already exists.
## Skipping /lab/scratch/atb/imaging/mtb_2023/test_data/outputs/raw_substack/raw_67.tif, it already exists.
## Skipping /lab/scratch/atb/imaging/mtb_2023/test_data/outputs/raw_substack/raw_68.tif, it already exists.
## Skipping /lab/scratch/atb/imaging/mtb_2023/test_data/outputs/raw_substack/raw_69.tif, it already exists.
## Skipping /lab/scratch/atb/imaging/mtb_2023/test_data/outputs/raw_substack/raw_70.tif, it already exists.
## Skipping /lab/scratch/atb/imaging/mtb_2023/test_data/outputs/raw_substack/raw_71.tif, it already exists.
## Skipping /lab/scratch/atb/imaging/mtb_2023/test_data/outputs/raw_substack/raw_72.tif, it already exists.
## Skipping /lab/scratch/atb/imaging/mtb_2023/test_data/outputs/raw_substack/raw_73.tif, it already exists.
## Skipping /lab/scratch/atb/imaging/mtb_2023/test_data/outputs/raw_substack/raw_74.tif, it already exists.
## Skipping /lab/scratch/atb/imaging/mtb_2023/test_data/outputs/raw_substack/raw_75.tif, it already exists.
## Skipping /lab/scratch/atb/imaging/mtb_2023/test_data/outputs/raw_substack/raw_76.tif, it already exists.
## Skipping /lab/scratch/atb/imaging/mtb_2023/test_data/outputs/raw_substack/raw_77.tif, it already exists.
## Skipping /lab/scratch/atb/imaging/mtb_2023/test_data/outputs/raw_substack/raw_78.tif, it already exists.
## Skipping /lab/scratch/atb/imaging/mtb_2023/test_data/outputs/raw_substack/raw_79.tif, it already exists.
## Skipping /lab/scratch/atb/imaging/mtb_2023/test_data/outputs/raw_substack/raw_80.tif, it already exists.
## Skipping /lab/scratch/atb/imaging/mtb_2023/test_data/outputs/raw_substack/raw_81.tif, it already exists.
## Skipping /lab/scratch/atb/imaging/mtb_2023/test_data/outputs/raw_substack/raw_82.tif, it already exists.
## Skipping /lab/scratch/atb/imaging/mtb_2023/test_data/outputs/raw_substack/raw_83.tif, it already exists.
## Skipping /lab/scratch/atb/imaging/mtb_2023/test_data/outputs/raw_substack/raw_84.tif, it already exists.
## Skipping /lab/scratch/atb/imaging/mtb_2023/test_data/outputs/raw_substack/raw_85.tif, it already exists.
## Skipping /lab/scratch/atb/imaging/mtb_2023/test_data/outputs/raw_substack/raw_86.tif, it already exists.
output_directory = 'test_data/outputs/raw_substack'
cellpose_result = invoke_cellpose(output_directory, 'models/CP_20220523_104016')
## Read 87 images, starting cellpose.
## Out of 87 output files, 87 already exist, not running cellpose.
## Finished cellpose, returning output filenames.
slice_measurements = slices_to_roi_measurements(cellpose_result, saved_slices)
## Processing cellpose outline: /home/trey/sshfs/scratch/atb/imaging/mtb_2023/test_data/outputs/raw_substack/raw_0_cp_outlines.txt
## Processing cellpose outline: /home/trey/sshfs/scratch/atb/imaging/mtb_2023/test_data/outputs/raw_substack/raw_1_cp_outlines.txt
## Processing cellpose outline: /home/trey/sshfs/scratch/atb/imaging/mtb_2023/test_data/outputs/raw_substack/raw_2_cp_outlines.txt
## Processing cellpose outline: /home/trey/sshfs/scratch/atb/imaging/mtb_2023/test_data/outputs/raw_substack/raw_3_cp_outlines.txt
## Processing cellpose outline: /home/trey/sshfs/scratch/atb/imaging/mtb_2023/test_data/outputs/raw_substack/raw_4_cp_outlines.txt
## Processing cellpose outline: /home/trey/sshfs/scratch/atb/imaging/mtb_2023/test_data/outputs/raw_substack/raw_5_cp_outlines.txt
## Processing cellpose outline: /home/trey/sshfs/scratch/atb/imaging/mtb_2023/test_data/outputs/raw_substack/raw_6_cp_outlines.txt
## Processing cellpose outline: /home/trey/sshfs/scratch/atb/imaging/mtb_2023/test_data/outputs/raw_substack/raw_7_cp_outlines.txt
## Processing cellpose outline: /home/trey/sshfs/scratch/atb/imaging/mtb_2023/test_data/outputs/raw_substack/raw_8_cp_outlines.txt
## Processing cellpose outline: /home/trey/sshfs/scratch/atb/imaging/mtb_2023/test_data/outputs/raw_substack/raw_9_cp_outlines.txt
## Processing cellpose outline: /home/trey/sshfs/scratch/atb/imaging/mtb_2023/test_data/outputs/raw_substack/raw_10_cp_outlines.txt
## Processing cellpose outline: /home/trey/sshfs/scratch/atb/imaging/mtb_2023/test_data/outputs/raw_substack/raw_11_cp_outlines.txt
## Processing cellpose outline: /home/trey/sshfs/scratch/atb/imaging/mtb_2023/test_data/outputs/raw_substack/raw_12_cp_outlines.txt
## Processing cellpose outline: /home/trey/sshfs/scratch/atb/imaging/mtb_2023/test_data/outputs/raw_substack/raw_13_cp_outlines.txt
## Processing cellpose outline: /home/trey/sshfs/scratch/atb/imaging/mtb_2023/test_data/outputs/raw_substack/raw_14_cp_outlines.txt
## Processing cellpose outline: /home/trey/sshfs/scratch/atb/imaging/mtb_2023/test_data/outputs/raw_substack/raw_15_cp_outlines.txt
## Processing cellpose outline: /home/trey/sshfs/scratch/atb/imaging/mtb_2023/test_data/outputs/raw_substack/raw_16_cp_outlines.txt
## Processing cellpose outline: /home/trey/sshfs/scratch/atb/imaging/mtb_2023/test_data/outputs/raw_substack/raw_17_cp_outlines.txt
## Processing cellpose outline: /home/trey/sshfs/scratch/atb/imaging/mtb_2023/test_data/outputs/raw_substack/raw_18_cp_outlines.txt
## Processing cellpose outline: /home/trey/sshfs/scratch/atb/imaging/mtb_2023/test_data/outputs/raw_substack/raw_19_cp_outlines.txt
## Processing cellpose outline: /home/trey/sshfs/scratch/atb/imaging/mtb_2023/test_data/outputs/raw_substack/raw_20_cp_outlines.txt
## Processing cellpose outline: /home/trey/sshfs/scratch/atb/imaging/mtb_2023/test_data/outputs/raw_substack/raw_21_cp_outlines.txt
## Processing cellpose outline: /home/trey/sshfs/scratch/atb/imaging/mtb_2023/test_data/outputs/raw_substack/raw_22_cp_outlines.txt
## Processing cellpose outline: /home/trey/sshfs/scratch/atb/imaging/mtb_2023/test_data/outputs/raw_substack/raw_23_cp_outlines.txt
## Processing cellpose outline: /home/trey/sshfs/scratch/atb/imaging/mtb_2023/test_data/outputs/raw_substack/raw_24_cp_outlines.txt
## Processing cellpose outline: /home/trey/sshfs/scratch/atb/imaging/mtb_2023/test_data/outputs/raw_substack/raw_25_cp_outlines.txt
## Processing cellpose outline: /home/trey/sshfs/scratch/atb/imaging/mtb_2023/test_data/outputs/raw_substack/raw_26_cp_outlines.txt
## Processing cellpose outline: /home/trey/sshfs/scratch/atb/imaging/mtb_2023/test_data/outputs/raw_substack/raw_27_cp_outlines.txt
## Processing cellpose outline: /home/trey/sshfs/scratch/atb/imaging/mtb_2023/test_data/outputs/raw_substack/raw_28_cp_outlines.txt
## Processing cellpose outline: /home/trey/sshfs/scratch/atb/imaging/mtb_2023/test_data/outputs/raw_substack/raw_29_cp_outlines.txt
## Processing cellpose outline: /home/trey/sshfs/scratch/atb/imaging/mtb_2023/test_data/outputs/raw_substack/raw_30_cp_outlines.txt
## Processing cellpose outline: /home/trey/sshfs/scratch/atb/imaging/mtb_2023/test_data/outputs/raw_substack/raw_31_cp_outlines.txt
## Processing cellpose outline: /home/trey/sshfs/scratch/atb/imaging/mtb_2023/test_data/outputs/raw_substack/raw_32_cp_outlines.txt
## Processing cellpose outline: /home/trey/sshfs/scratch/atb/imaging/mtb_2023/test_data/outputs/raw_substack/raw_33_cp_outlines.txt
## Processing cellpose outline: /home/trey/sshfs/scratch/atb/imaging/mtb_2023/test_data/outputs/raw_substack/raw_34_cp_outlines.txt
## Processing cellpose outline: /home/trey/sshfs/scratch/atb/imaging/mtb_2023/test_data/outputs/raw_substack/raw_35_cp_outlines.txt
## Processing cellpose outline: /home/trey/sshfs/scratch/atb/imaging/mtb_2023/test_data/outputs/raw_substack/raw_36_cp_outlines.txt
## Processing cellpose outline: /home/trey/sshfs/scratch/atb/imaging/mtb_2023/test_data/outputs/raw_substack/raw_37_cp_outlines.txt
## Processing cellpose outline: /home/trey/sshfs/scratch/atb/imaging/mtb_2023/test_data/outputs/raw_substack/raw_38_cp_outlines.txt
## Processing cellpose outline: /home/trey/sshfs/scratch/atb/imaging/mtb_2023/test_data/outputs/raw_substack/raw_39_cp_outlines.txt
## Processing cellpose outline: /home/trey/sshfs/scratch/atb/imaging/mtb_2023/test_data/outputs/raw_substack/raw_40_cp_outlines.txt
## Processing cellpose outline: /home/trey/sshfs/scratch/atb/imaging/mtb_2023/test_data/outputs/raw_substack/raw_41_cp_outlines.txt
## Processing cellpose outline: /home/trey/sshfs/scratch/atb/imaging/mtb_2023/test_data/outputs/raw_substack/raw_42_cp_outlines.txt
## Processing cellpose outline: /home/trey/sshfs/scratch/atb/imaging/mtb_2023/test_data/outputs/raw_substack/raw_43_cp_outlines.txt
## Processing cellpose outline: /home/trey/sshfs/scratch/atb/imaging/mtb_2023/test_data/outputs/raw_substack/raw_44_cp_outlines.txt
## Processing cellpose outline: /home/trey/sshfs/scratch/atb/imaging/mtb_2023/test_data/outputs/raw_substack/raw_45_cp_outlines.txt
## Processing cellpose outline: /home/trey/sshfs/scratch/atb/imaging/mtb_2023/test_data/outputs/raw_substack/raw_46_cp_outlines.txt
## Processing cellpose outline: /home/trey/sshfs/scratch/atb/imaging/mtb_2023/test_data/outputs/raw_substack/raw_47_cp_outlines.txt
## Processing cellpose outline: /home/trey/sshfs/scratch/atb/imaging/mtb_2023/test_data/outputs/raw_substack/raw_48_cp_outlines.txt
## Processing cellpose outline: /home/trey/sshfs/scratch/atb/imaging/mtb_2023/test_data/outputs/raw_substack/raw_49_cp_outlines.txt
## Processing cellpose outline: /home/trey/sshfs/scratch/atb/imaging/mtb_2023/test_data/outputs/raw_substack/raw_50_cp_outlines.txt
## Processing cellpose outline: /home/trey/sshfs/scratch/atb/imaging/mtb_2023/test_data/outputs/raw_substack/raw_51_cp_outlines.txt
## Processing cellpose outline: /home/trey/sshfs/scratch/atb/imaging/mtb_2023/test_data/outputs/raw_substack/raw_52_cp_outlines.txt
## Processing cellpose outline: /home/trey/sshfs/scratch/atb/imaging/mtb_2023/test_data/outputs/raw_substack/raw_53_cp_outlines.txt
## Processing cellpose outline: /home/trey/sshfs/scratch/atb/imaging/mtb_2023/test_data/outputs/raw_substack/raw_54_cp_outlines.txt
## Processing cellpose outline: /home/trey/sshfs/scratch/atb/imaging/mtb_2023/test_data/outputs/raw_substack/raw_55_cp_outlines.txt
## Processing cellpose outline: /home/trey/sshfs/scratch/atb/imaging/mtb_2023/test_data/outputs/raw_substack/raw_56_cp_outlines.txt
## Processing cellpose outline: /home/trey/sshfs/scratch/atb/imaging/mtb_2023/test_data/outputs/raw_substack/raw_57_cp_outlines.txt
## Processing cellpose outline: /home/trey/sshfs/scratch/atb/imaging/mtb_2023/test_data/outputs/raw_substack/raw_58_cp_outlines.txt
## Processing cellpose outline: /home/trey/sshfs/scratch/atb/imaging/mtb_2023/test_data/outputs/raw_substack/raw_59_cp_outlines.txt
## Processing cellpose outline: /home/trey/sshfs/scratch/atb/imaging/mtb_2023/test_data/outputs/raw_substack/raw_60_cp_outlines.txt
## Processing cellpose outline: /home/trey/sshfs/scratch/atb/imaging/mtb_2023/test_data/outputs/raw_substack/raw_61_cp_outlines.txt
## Processing cellpose outline: /home/trey/sshfs/scratch/atb/imaging/mtb_2023/test_data/outputs/raw_substack/raw_62_cp_outlines.txt
## Processing cellpose outline: /home/trey/sshfs/scratch/atb/imaging/mtb_2023/test_data/outputs/raw_substack/raw_63_cp_outlines.txt
## Processing cellpose outline: /home/trey/sshfs/scratch/atb/imaging/mtb_2023/test_data/outputs/raw_substack/raw_64_cp_outlines.txt
## Processing cellpose outline: /home/trey/sshfs/scratch/atb/imaging/mtb_2023/test_data/outputs/raw_substack/raw_65_cp_outlines.txt
## Processing cellpose outline: /home/trey/sshfs/scratch/atb/imaging/mtb_2023/test_data/outputs/raw_substack/raw_66_cp_outlines.txt
## Processing cellpose outline: /home/trey/sshfs/scratch/atb/imaging/mtb_2023/test_data/outputs/raw_substack/raw_67_cp_outlines.txt
## Processing cellpose outline: /home/trey/sshfs/scratch/atb/imaging/mtb_2023/test_data/outputs/raw_substack/raw_68_cp_outlines.txt
## Processing cellpose outline: /home/trey/sshfs/scratch/atb/imaging/mtb_2023/test_data/outputs/raw_substack/raw_69_cp_outlines.txt
## Processing cellpose outline: /home/trey/sshfs/scratch/atb/imaging/mtb_2023/test_data/outputs/raw_substack/raw_70_cp_outlines.txt
## Processing cellpose outline: /home/trey/sshfs/scratch/atb/imaging/mtb_2023/test_data/outputs/raw_substack/raw_71_cp_outlines.txt
## Processing cellpose outline: /home/trey/sshfs/scratch/atb/imaging/mtb_2023/test_data/outputs/raw_substack/raw_72_cp_outlines.txt
## Processing cellpose outline: /home/trey/sshfs/scratch/atb/imaging/mtb_2023/test_data/outputs/raw_substack/raw_73_cp_outlines.txt
## Processing cellpose outline: /home/trey/sshfs/scratch/atb/imaging/mtb_2023/test_data/outputs/raw_substack/raw_74_cp_outlines.txt
## Processing cellpose outline: /home/trey/sshfs/scratch/atb/imaging/mtb_2023/test_data/outputs/raw_substack/raw_75_cp_outlines.txt
## Processing cellpose outline: /home/trey/sshfs/scratch/atb/imaging/mtb_2023/test_data/outputs/raw_substack/raw_76_cp_outlines.txt
## Processing cellpose outline: /home/trey/sshfs/scratch/atb/imaging/mtb_2023/test_data/outputs/raw_substack/raw_77_cp_outlines.txt
## Processing cellpose outline: /home/trey/sshfs/scratch/atb/imaging/mtb_2023/test_data/outputs/raw_substack/raw_78_cp_outlines.txt
## Processing cellpose outline: /home/trey/sshfs/scratch/atb/imaging/mtb_2023/test_data/outputs/raw_substack/raw_79_cp_outlines.txt
## Processing cellpose outline: /home/trey/sshfs/scratch/atb/imaging/mtb_2023/test_data/outputs/raw_substack/raw_80_cp_outlines.txt
## Processing cellpose outline: /home/trey/sshfs/scratch/atb/imaging/mtb_2023/test_data/outputs/raw_substack/raw_81_cp_outlines.txt
## Processing cellpose outline: /home/trey/sshfs/scratch/atb/imaging/mtb_2023/test_data/outputs/raw_substack/raw_82_cp_outlines.txt
## Processing cellpose outline: /home/trey/sshfs/scratch/atb/imaging/mtb_2023/test_data/outputs/raw_substack/raw_83_cp_outlines.txt
## Processing cellpose outline: /home/trey/sshfs/scratch/atb/imaging/mtb_2023/test_data/outputs/raw_substack/raw_84_cp_outlines.txt
## Processing cellpose outline: /home/trey/sshfs/scratch/atb/imaging/mtb_2023/test_data/outputs/raw_substack/raw_85_cp_outlines.txt
## Processing cellpose outline: /home/trey/sshfs/scratch/atb/imaging/mtb_2023/test_data/outputs/raw_substack/raw_86_cp_outlines.txt
concatenated = convert_slices_to_pandas(slice_measurements)
## The slice is test_data/outputs/raw_substack/raw_0.tif
## The slice is test_data/outputs/raw_substack/raw_1.tif
## The slice is test_data/outputs/raw_substack/raw_2.tif
## The slice is test_data/outputs/raw_substack/raw_3.tif
## The slice is test_data/outputs/raw_substack/raw_4.tif
## The slice is test_data/outputs/raw_substack/raw_5.tif
## The slice is test_data/outputs/raw_substack/raw_6.tif
## The slice is test_data/outputs/raw_substack/raw_7.tif
## The slice is test_data/outputs/raw_substack/raw_8.tif
## The slice is test_data/outputs/raw_substack/raw_9.tif
## The slice is test_data/outputs/raw_substack/raw_10.tif
## The slice is test_data/outputs/raw_substack/raw_11.tif
## The slice is test_data/outputs/raw_substack/raw_12.tif
## The slice is test_data/outputs/raw_substack/raw_13.tif
## The slice is test_data/outputs/raw_substack/raw_14.tif
## The slice is test_data/outputs/raw_substack/raw_15.tif
## The slice is test_data/outputs/raw_substack/raw_16.tif
## The slice is test_data/outputs/raw_substack/raw_17.tif
## The slice is test_data/outputs/raw_substack/raw_18.tif
## The slice is test_data/outputs/raw_substack/raw_19.tif
## The slice is test_data/outputs/raw_substack/raw_20.tif
## The slice is test_data/outputs/raw_substack/raw_21.tif
## The slice is test_data/outputs/raw_substack/raw_22.tif
## The slice is test_data/outputs/raw_substack/raw_23.tif
## The slice is test_data/outputs/raw_substack/raw_24.tif
## The slice is test_data/outputs/raw_substack/raw_25.tif
## The slice is test_data/outputs/raw_substack/raw_26.tif
## The slice is test_data/outputs/raw_substack/raw_27.tif
## The slice is test_data/outputs/raw_substack/raw_28.tif
## The slice is test_data/outputs/raw_substack/raw_29.tif
## The slice is test_data/outputs/raw_substack/raw_30.tif
## The slice is test_data/outputs/raw_substack/raw_31.tif
## The slice is test_data/outputs/raw_substack/raw_32.tif
## The slice is test_data/outputs/raw_substack/raw_33.tif
## The slice is test_data/outputs/raw_substack/raw_34.tif
## The slice is test_data/outputs/raw_substack/raw_35.tif
## The slice is test_data/outputs/raw_substack/raw_36.tif
## The slice is test_data/outputs/raw_substack/raw_37.tif
## The slice is test_data/outputs/raw_substack/raw_38.tif
## The slice is test_data/outputs/raw_substack/raw_39.tif
## The slice is test_data/outputs/raw_substack/raw_40.tif
## The slice is test_data/outputs/raw_substack/raw_41.tif
## The slice is test_data/outputs/raw_substack/raw_42.tif
## The slice is test_data/outputs/raw_substack/raw_43.tif
## The slice is test_data/outputs/raw_substack/raw_44.tif
## The slice is test_data/outputs/raw_substack/raw_45.tif
## The slice is test_data/outputs/raw_substack/raw_46.tif
## The slice is test_data/outputs/raw_substack/raw_47.tif
## The slice is test_data/outputs/raw_substack/raw_48.tif
## The slice is test_data/outputs/raw_substack/raw_49.tif
## The slice is test_data/outputs/raw_substack/raw_50.tif
## The slice is test_data/outputs/raw_substack/raw_51.tif
## The slice is test_data/outputs/raw_substack/raw_52.tif
## The slice is test_data/outputs/raw_substack/raw_53.tif
## The slice is test_data/outputs/raw_substack/raw_54.tif
## The slice is test_data/outputs/raw_substack/raw_55.tif
## The slice is test_data/outputs/raw_substack/raw_56.tif
## The slice is test_data/outputs/raw_substack/raw_57.tif
## The slice is test_data/outputs/raw_substack/raw_58.tif
## The slice is test_data/outputs/raw_substack/raw_59.tif
## The slice is test_data/outputs/raw_substack/raw_60.tif
## The slice is test_data/outputs/raw_substack/raw_61.tif
## The slice is test_data/outputs/raw_substack/raw_62.tif
## The slice is test_data/outputs/raw_substack/raw_63.tif
## The slice is test_data/outputs/raw_substack/raw_64.tif
## The slice is test_data/outputs/raw_substack/raw_65.tif
## The slice is test_data/outputs/raw_substack/raw_66.tif
## The slice is test_data/outputs/raw_substack/raw_67.tif
## The slice is test_data/outputs/raw_substack/raw_68.tif
## The slice is test_data/outputs/raw_substack/raw_69.tif
## The slice is test_data/outputs/raw_substack/raw_70.tif
## The slice is test_data/outputs/raw_substack/raw_71.tif
## The slice is test_data/outputs/raw_substack/raw_72.tif
## The slice is test_data/outputs/raw_substack/raw_73.tif
## The slice is test_data/outputs/raw_substack/raw_74.tif
## The slice is test_data/outputs/raw_substack/raw_75.tif
## The slice is test_data/outputs/raw_substack/raw_76.tif
## The slice is test_data/outputs/raw_substack/raw_77.tif
## The slice is test_data/outputs/raw_substack/raw_78.tif
## The slice is test_data/outputs/raw_substack/raw_79.tif
## The slice is test_data/outputs/raw_substack/raw_80.tif
## The slice is test_data/outputs/raw_substack/raw_81.tif
## The slice is test_data/outputs/raw_substack/raw_82.tif
## The slice is test_data/outputs/raw_substack/raw_83.tif
## The slice is test_data/outputs/raw_substack/raw_84.tif
## The slice is test_data/outputs/raw_substack/raw_85.tif
## The slice is test_data/outputs/raw_substack/raw_86.tif
nearest = nearest_cells_over_time(concatenated, max_dist = 10.0, x_column = 'X', y_column = 'Y')
## Getting distances of dfs 1 and 2.
## Getting distances of dfs 2 and 3.
## Getting distances of dfs 3 and 4.
## Getting distances of dfs 4 and 5.
## Getting distances of dfs 5 and 6.
## Getting distances of dfs 6 and 7.
## Getting distances of dfs 7 and 8.
## Getting distances of dfs 8 and 9.
## Getting distances of dfs 9 and 10.
## Getting distances of dfs 10 and 11.
## Getting distances of dfs 11 and 12.
## Getting distances of dfs 12 and 13.
## Getting distances of dfs 13 and 14.
## Getting distances of dfs 14 and 15.
## Getting distances of dfs 15 and 16.
## Getting distances of dfs 16 and 17.
## Getting distances of dfs 17 and 18.
## Getting distances of dfs 18 and 19.
## Getting distances of dfs 19 and 20.
## Getting distances of dfs 20 and 21.
## Getting distances of dfs 21 and 22.
## Getting distances of dfs 22 and 23.
## Getting distances of dfs 23 and 24.
## Getting distances of dfs 24 and 25.
## Getting distances of dfs 25 and 26.
## Getting distances of dfs 26 and 27.
## Getting distances of dfs 27 and 28.
## Getting distances of dfs 28 and 29.
## Getting distances of dfs 29 and 30.
## Getting distances of dfs 30 and 31.
## Getting distances of dfs 31 and 32.
## Getting distances of dfs 32 and 33.
## Getting distances of dfs 33 and 34.
## Getting distances of dfs 34 and 35.
## Getting distances of dfs 35 and 36.
## Getting distances of dfs 36 and 37.
## Getting distances of dfs 37 and 38.
## Getting distances of dfs 38 and 39.
## Getting distances of dfs 39 and 40.
## Getting distances of dfs 40 and 41.
## Getting distances of dfs 41 and 42.
## Getting distances of dfs 42 and 43.
## Getting distances of dfs 43 and 44.
## Getting distances of dfs 44 and 45.
## Getting distances of dfs 45 and 46.
## Getting distances of dfs 46 and 47.
## Getting distances of dfs 47 and 48.
## Getting distances of dfs 48 and 49.
## Getting distances of dfs 49 and 50.
## Getting distances of dfs 50 and 51.
## Getting distances of dfs 51 and 52.
## Getting distances of dfs 52 and 53.
## Getting distances of dfs 53 and 54.
## Getting distances of dfs 54 and 55.
## Getting distances of dfs 55 and 56.
## Getting distances of dfs 56 and 57.
## Getting distances of dfs 57 and 58.
## Getting distances of dfs 58 and 59.
## Getting distances of dfs 59 and 60.
## Getting distances of dfs 60 and 61.
## Getting distances of dfs 61 and 62.
## Getting distances of dfs 62 and 63.
## Getting distances of dfs 63 and 64.
## Getting distances of dfs 64 and 65.
## Getting distances of dfs 65 and 66.
## Getting distances of dfs 66 and 67.
## Getting distances of dfs 67 and 68.
## Getting distances of dfs 68 and 69.
## Getting distances of dfs 69 and 70.
## Getting distances of dfs 70 and 71.
## Getting distances of dfs 71 and 72.
## Getting distances of dfs 72 and 73.
## Getting distances of dfs 73 and 74.
## Getting distances of dfs 74 and 75.
## Getting distances of dfs 75 and 76.
## Getting distances of dfs 76 and 77.
## Getting distances of dfs 77 and 78.
## Getting distances of dfs 78 and 79.
## Getting distances of dfs 79 and 80.
## Getting distances of dfs 80 and 81.
## Getting distances of dfs 81 and 82.
## Getting distances of dfs 82 and 83.
## Getting distances of dfs 83 and 84.
## Getting distances of dfs 84 and 85.
## Getting distances of dfs 85 and 86.
## Skipped 10 elements in segment 0.
## Skipped 6 elements in segment 1.
## Skipped 9 elements in segment 2.
## Skipped 9 elements in segment 3.
## Skipped 12 elements in segment 4.
## Skipped 8 elements in segment 5.
## Skipped 9 elements in segment 6.
## ('Skipped 1 elements in segment 6 ', 'because the size changed too much.')
## Skipped 10 elements in segment 7.
## Skipped 10 elements in segment 8.
## Skipped 11 elements in segment 9.
## ('Skipped 1 elements in segment 9 ', 'because the size changed too much.')
## Skipped 12 elements in segment 10.
## Skipped 14 elements in segment 11.
## Skipped 8 elements in segment 12.
## ('Skipped 2 elements in segment 12 ', 'because the size changed too much.')
## Skipped 7 elements in segment 13.
## Skipped 13 elements in segment 14.
## Skipped 13 elements in segment 15.
## Skipped 14 elements in segment 16.
## Skipped 13 elements in segment 17.
## Skipped 13 elements in segment 18.
## Skipped 11 elements in segment 19.
## Skipped 14 elements in segment 20.
## Skipped 9 elements in segment 21.
## Skipped 13 elements in segment 22.
## Skipped 12 elements in segment 23.
## Skipped 14 elements in segment 24.
## Skipped 13 elements in segment 25.
## ('Skipped 1 elements in segment 25 ', 'because the size changed too much.')
## Skipped 11 elements in segment 26.
## Skipped 13 elements in segment 27.
## Skipped 16 elements in segment 28.
## Skipped 16 elements in segment 29.
## Skipped 11 elements in segment 30.
## Skipped 14 elements in segment 31.
## Skipped 13 elements in segment 32.
## ('Skipped 1 elements in segment 32 ', 'because the size changed too much.')
## Skipped 10 elements in segment 33.
## Skipped 12 elements in segment 34.
## Skipped 8 elements in segment 35.
## Skipped 13 elements in segment 36.
## Skipped 12 elements in segment 37.
## ('Skipped 1 elements in segment 37 ', 'because the size changed too much.')
## Skipped 10 elements in segment 38.
## ('Skipped 2 elements in segment 38 ', 'because the size changed too much.')
## Skipped 10 elements in segment 39.
## Skipped 11 elements in segment 40.
## Skipped 10 elements in segment 41.
## Skipped 12 elements in segment 42.
## Skipped 15 elements in segment 43.
## Skipped 11 elements in segment 44.
## Skipped 11 elements in segment 45.
## Skipped 12 elements in segment 46.
## Skipped 10 elements in segment 47.
## ('Skipped 1 elements in segment 47 ', 'because the size changed too much.')
## Skipped 10 elements in segment 48.
## Skipped 12 elements in segment 49.
## Skipped 7 elements in segment 50.
## Skipped 8 elements in segment 51.
## Skipped 10 elements in segment 52.
## Skipped 9 elements in segment 53.
## ('Skipped 1 elements in segment 53 ', 'because the size changed too much.')
## Skipped 10 elements in segment 54.
## Skipped 8 elements in segment 55.
## Skipped 7 elements in segment 56.
## Skipped 7 elements in segment 57.
## Skipped 10 elements in segment 58.
## Skipped 9 elements in segment 59.
## Skipped 10 elements in segment 60.
## Skipped 10 elements in segment 61.
## Skipped 13 elements in segment 62.
## Skipped 10 elements in segment 63.
## Skipped 12 elements in segment 64.
## Skipped 13 elements in segment 65.
## Skipped 12 elements in segment 66.
## Skipped 9 elements in segment 67.
## Skipped 9 elements in segment 68.
## Skipped 9 elements in segment 69.
## Skipped 11 elements in segment 70.
## Skipped 10 elements in segment 71.
## Skipped 12 elements in segment 72.
## Skipped 12 elements in segment 73.
## ('Skipped 1 elements in segment 73 ', 'because the size changed too much.')
## Skipped 13 elements in segment 74.
## ('Skipped 1 elements in segment 74 ', 'because the size changed too much.')
## Skipped 10 elements in segment 75.
## Skipped 12 elements in segment 76.
## Skipped 10 elements in segment 77.
## Skipped 10 elements in segment 78.
## Skipped 14 elements in segment 79.
## Skipped 10 elements in segment 80.
## Skipped 13 elements in segment 81.
## Skipped 16 elements in segment 82.
## Skipped 13 elements in segment 83.
## Skipped 13 elements in segment 84.

9.1 Get information from a group of cells

As a final step, we should be able to extract and play with the information from one or more groups of cells.

cell_id = 129
cell_idx = nearest[cell_id]
cell_data = concatenated.loc[cell_idx]
len(cell_data)
## 20
cell_data = cell_data.reset_index()

scatter = plt.scatter(cell_data['X'], cell_data['Y'])
final_row = cell_data.index.max()
for start_time in range(0, final_row - 1):
    ti_idx = cell_data.index == start_time
    tj_idx = cell_data.index == start_time + 1
    p1x = cell_data[ti_idx].X
    p2x = cell_data[tj_idx].X
    p1y = cell_data[ti_idx].Y
    p2y = cell_data[tj_idx].Y
    x_points = [p1x, p2x]
    y_points = [p1y, p2y]
    plt.plot(x_points, y_points)
finalm1_idx = cell_data.index == final_row - 1
final_idx = cell_data.index == final_row
finalm1_x = cell_data[finalm1_idx].X
final_x = cell_data[final_idx].X
finalm1_y = cell_data[finalm1_idx].Y
final_y = cell_data[final_idx].Y
x_points = [finalm1_x, final_x]
y_points = [finalm1_y, final_y]
plt.plot(x_points, y_points)
plt.show()

seaborn.violinplot(data = cell_data.Area)
plt.show()

pander::pander(sessionInfo())

R version 4.2.0 (2022-04-22)

Platform: x86_64-pc-linux-gnu (64-bit)

locale: LC_CTYPE=en_US.UTF-8, LC_NUMERIC=C, LC_TIME=en_US.UTF-8, LC_COLLATE=en_US.UTF-8, LC_MONETARY=en_US.UTF-8, LC_MESSAGES=en_US.UTF-8, LC_PAPER=en_US.UTF-8, LC_NAME=en_US.UTF-8, LC_ADDRESS=en_US.UTF-8, LC_TELEPHONE=en_US.UTF-8, LC_MEASUREMENT=en_US.UTF-8 and LC_IDENTIFICATION=en_US.UTF-8

attached base packages: stats4, stats, graphics, grDevices, utils, datasets, methods and base

other attached packages: spatstat.geom(v.3.0-6), spatstat.data(v.3.0-0), hpgltools(v.1.0), testthat(v.3.1.6), reticulate(v.1.28), SummarizedExperiment(v.1.28.0), GenomicRanges(v.1.50.2), GenomeInfoDb(v.1.34.9), IRanges(v.2.32.0), S4Vectors(v.0.36.1), MatrixGenerics(v.1.10.0), matrixStats(v.0.63.0), Biobase(v.2.58.0) and BiocGenerics(v.0.44.0)

loaded via a namespace (and not attached): rappdirs(v.0.3.3), rtracklayer(v.1.58.0), tidyr(v.1.3.0), ggplot2(v.3.4.1), clusterGeneration(v.1.3.7), bit64(v.4.0.5), knitr(v.1.42), DelayedArray(v.0.24.0), data.table(v.1.14.6), KEGGREST(v.1.38.0), RCurl(v.1.98-1.10), doParallel(v.1.0.17), generics(v.0.1.3), GenomicFeatures(v.1.50.4), callr(v.3.7.3), RhpcBLASctl(v.0.23-42), cowplot(v.1.1.1), usethis(v.2.1.6), RSQLite(v.2.2.20), shadowtext(v.0.1.2), bit(v.4.0.5), enrichplot(v.1.18.3), xml2(v.1.3.3), httpuv(v.1.6.8), assertthat(v.0.2.1), viridis(v.0.6.2), xfun(v.0.37), hms(v.1.1.2), jquerylib(v.0.1.4), evaluate(v.0.20), promises(v.1.2.0.1), fansi(v.1.0.4), restfulr(v.0.0.15), progress(v.1.2.2), caTools(v.1.18.2), dbplyr(v.2.3.0), igraph(v.1.4.0), DBI(v.1.1.3), htmlwidgets(v.1.6.1), purrr(v.1.0.1), ellipsis(v.0.3.2), dplyr(v.1.1.0), backports(v.1.4.1), annotate(v.1.76.0), aod(v.1.3.2), deldir(v.1.0-6), biomaRt(v.2.54.0), vctrs(v.0.5.2), here(v.1.0.1), remotes(v.2.4.2), cachem(v.1.0.6), withr(v.2.5.0), ggforce(v.0.4.1), HDO.db(v.0.99.1), GenomicAlignments(v.1.34.0), treeio(v.1.22.0), prettyunits(v.1.1.1), DOSE(v.3.24.2), ape(v.5.6-2), lazyeval(v.0.2.2), crayon(v.1.5.2), genefilter(v.1.80.3), edgeR(v.3.40.2), pkgconfig(v.2.0.3), tweenr(v.2.0.2), nlme(v.3.1-162), pkgload(v.1.3.2), devtools(v.2.4.5), rlang(v.1.0.6), lifecycle(v.1.0.3), miniUI(v.0.1.1.1), downloader(v.0.4), filelock(v.1.0.2), BiocFileCache(v.2.6.0), rprojroot(v.2.0.3), polyclip(v.1.10-4), graph(v.1.76.0), Matrix(v.1.5-3), aplot(v.0.1.9), boot(v.1.3-28.1), processx(v.3.8.0), png(v.0.1-8), viridisLite(v.0.4.1), rjson(v.0.2.21), bitops(v.1.0-7), gson(v.0.0.9), KernSmooth(v.2.23-20), pander(v.0.6.5), Biostrings(v.2.66.0), blob(v.1.2.3), stringr(v.1.5.0), qvalue(v.2.30.0), remaCor(v.0.0.11), gridGraphics(v.0.5-1), scales(v.1.2.1), memoise(v.2.0.1), GSEABase(v.1.60.0), magrittr(v.2.0.3), plyr(v.1.8.8), gplots(v.3.1.3), zlibbioc(v.1.44.0), compiler(v.4.2.0), scatterpie(v.0.1.8), BiocIO(v.1.8.0), RColorBrewer(v.1.1-3), lme4(v.1.1-31), Rsamtools(v.2.14.0), cli(v.3.6.0), XVector(v.0.38.0), urlchecker(v.1.0.1), patchwork(v.1.1.2), ps(v.1.7.2), MASS(v.7.3-58.2), mgcv(v.1.8-41), tidyselect(v.1.2.0), stringi(v.1.7.12), highr(v.0.10), yaml(v.2.3.7), GOSemSim(v.2.24.0), locfit(v.1.5-9.7), ggrepel(v.0.9.3), grid(v.4.2.0), sass(v.0.4.5), fastmatch(v.1.1-3), tools(v.4.2.0), parallel(v.4.2.0), rstudioapi(v.0.14), foreach(v.1.5.2), gridExtra(v.2.3), farver(v.2.1.1), ggraph(v.2.1.0), digest(v.0.6.31), shiny(v.1.7.4), Rcpp(v.1.0.10), broom(v.1.0.3), later(v.1.3.0), httr(v.1.4.4), AnnotationDbi(v.1.60.0), Rdpack(v.2.4), colorspace(v.2.1-0), brio(v.1.1.3), XML(v.3.99-0.13), fs(v.1.6.1), splines(v.4.2.0), yulab.utils(v.0.0.6), spatstat.utils(v.3.0-1), PROPER(v.1.30.0), tidytree(v.0.4.2), graphlayouts(v.0.8.4), ggplotify(v.0.1.0), plotly(v.4.10.1), sessioninfo(v.1.2.2), xtable(v.1.8-4), jsonlite(v.1.8.4), nloptr(v.2.0.3), ggtree(v.3.6.2), tidygraph(v.1.2.3), ggfun(v.0.0.9), R6(v.2.5.1), RUnit(v.0.4.32), profvis(v.0.3.7), pillar(v.1.8.1), htmltools(v.0.5.4), mime(v.0.12), glue(v.1.6.2), fastmap(v.1.1.0), minqa(v.1.2.5), clusterProfiler(v.4.6.0), BiocParallel(v.1.32.5), codetools(v.0.2-19), fgsea(v.1.24.0), pkgbuild(v.1.4.0), mvtnorm(v.1.1-3), utf8(v.1.2.3), lattice(v.0.20-45), bslib(v.0.4.2), tibble(v.3.1.8), sva(v.3.46.0), pbkrtest(v.0.5.2), curl(v.5.0.0), gtools(v.3.9.4), GO.db(v.3.16.0), survival(v.3.5-3), limma(v.3.54.1), rmarkdown(v.2.20), desc(v.1.4.2), munsell(v.0.5.0), GenomeInfoDbData(v.1.2.9), iterators(v.1.0.14), variancePartition(v.1.28.4), reshape2(v.1.4.4), gtable(v.0.3.1) and rbibutils(v.2.2.13)

message(paste0("This is hpgltools commit: ", get_git_commit()))
## If you wish to reproduce this exact build of hpgltools, invoke the following:
## > git clone http://github.com/abelew/hpgltools.git
## > git reset 465cd7740fb559303acb139de1d478adfc3f6612
## This is hpgltools commit: Fri Mar 24 14:37:21 2023 -0400: 465cd7740fb559303acb139de1d478adfc3f6612
this_save <- paste0(gsub(pattern="\\.Rmd", replace="", x=rmd_file), "-v", ver, ".rda.xz")
message(paste0("Saving to ", this_save))
## Saving to index-v20230403.rda.xz
tmp <- sm(saveme(filename=this_save))
LS0tCnRpdGxlOiAiTm90ZXMgZm9yIEphY3F1ZXMgaW1hZ2VzLiIKYXV0aG9yOiAiYXRiIGFiZWxld0BnbWFpbC5jb20iCmRhdGU6ICJgciBTeXMuRGF0ZSgpYCIKb3V0cHV0OgogIGh0bWxfZG9jdW1lbnQ6CiAgICBjb2RlX2Rvd25sb2FkOiB0cnVlCiAgICBjb2RlX2ZvbGRpbmc6IHNob3cKICAgIGZpZ19jYXB0aW9uOiB0cnVlCiAgICBmaWdfaGVpZ2h0OiA3CiAgICBmaWdfd2lkdGg6IDcKICAgIGhpZ2hsaWdodDogemVuYnVybgogICAga2VlcF9tZDogZmFsc2UKICAgIG1vZGU6IHNlbGZjb250YWluZWQKICAgIG51bWJlcl9zZWN0aW9uczogdHJ1ZQogICAgc2VsZl9jb250YWluZWQ6IHRydWUKICAgIHRoZW1lOiByZWFkYWJsZQogICAgdG9jOiB0cnVlCiAgICB0b2NfZmxvYXQ6CiAgICAgIGNvbGxhcHNlZDogZmFsc2UKICAgICAgc21vb3RoX3Njcm9sbDogZmFsc2UKLS0tCgo8c3R5bGUgdHlwZT0idGV4dC9jc3MiPgpib2R5LCB0ZCB7CiAgZm9udC1zaXplOiAxNnB4Owp9CmNvZGUucnsKICBmb250LXNpemU6IDE2cHg7Cn0KcHJlIHsKIGZvbnQtc2l6ZTogMTZweAp9Cjwvc3R5bGU+CgpgYGB7ciBvcHRpb25zLCBpbmNsdWRlPUZBTFNFfQpsaWJyYXJ5KGhwZ2x0b29scykKbGlicmFyeShyZXRpY3VsYXRlKQp0dCA8LSBkZXZ0b29sczo6bG9hZF9hbGwoIn4vaHBnbHRvb2xzIikKa25pdHI6Om9wdHNfa25pdCRzZXQoCiAgd2lkdGggPSAxMjAsIHByb2dyZXNzID0gVFJVRSwgdmVyYm9zZSA9IFRSVUUsIGVjaG8gPSBUUlVFKQprbml0cjo6b3B0c19jaHVuayRzZXQoZXJyb3IgPSBUUlVFLCBkcGkgPSA5NikKbHVhX2ZpbHRlcnMgPC0gcm1hcmtkb3duOjpwYW5kb2NfbHVhX2ZpbHRlcl9hcmdzKCJwYW5kb2Mtem90eHQubHVhIikKb2xkX29wdGlvbnMgPC0gb3B0aW9ucygKICBkaWdpdHMgPSA0LCBzdHJpbmdzQXNGYWN0b3JzID0gRkFMU0UsIGtuaXRyLmR1cGxpY2F0ZS5sYWJlbCA9ICJhbGxvdyIpCmdncGxvdDI6OnRoZW1lX3NldChnZ3Bsb3QyOjp0aGVtZV9idyhiYXNlX3NpemUgPSAxMCkpCnJ1bmRhdGUgPC0gZm9ybWF0KFN5cy5EYXRlKCksIGZvcm1hdCA9ICIlWSVtJWQiKQpwcmV2aW91c19maWxlIDwtICIiCnZlciA8LSBmb3JtYXQoU3lzLkRhdGUoKSwgIiVZJW0lZCIpCgojI3RtcCA8LSBzbShsb2FkbWUoZmlsZW5hbWU9cGFzdGUwKGdzdWIocGF0dGVybj0iXFwuUm1kIiwgcmVwbGFjZT0iIiwgeD1wcmV2aW91c19maWxlKSwgIi12IiwgdmVyLCAiLnJkYS54eiIpKSkKcm1kX2ZpbGUgPC0gImluZGV4LlJtZCIKbGlicmFyeShzcGF0c3RhdC5nZW9tKQpgYGAKCiMgQ2hhbmdlbG9nCgoqIDIwMjMwMzI4OiBSZWltcGxlbWVudGVkIHN0ZXBzIGFzIGZ1bmN0aW9ucywgY29ubmVjdGVkIHRoZW0gaW4gb25lIHNldCBvZiBjYWxscy4KKiAyMDIzMDMxNzogSW5pdGlhbCBjZWxscG9zZSBpbXBsZW1lbnRhdGlvbi4KKiAyMDIzMDMxNTogU2VwYXJhdGlvbiBvZiBpbnB1dCBpbWFnZSBpbnRvIHRpbWVwb2ludHMuCiogMjAyMzAzMTA6IFVzZWQgZ2VvcGFuZGFzIHRvIHRyYWNlIGNlbGxzIG92ZXIgdGltZSBieSBwb3NpdGlvbi4KKiAyMDIzMDMwNzogU2V0dGluZyB1cCBteSBlbnZpcm9ubWVudCB0byBoYW5kbGUgaXB5bmIgYW5kIHB5dGhvbiBtYXJrZG93bi4KCiMgU2V0dGluZ3Mgd2hpY2ggbWF5IGJlIG5lY2Vzc2FyeSBpbiBlbWFjcwoKSSBoYXZlIGJlZW4gbWVzc2luZyB3aXRoIG15IGVtYWNzIHNldHVwIHRvIG1ha2UgdGhpcyBwcm9qZWN0IGVhc2llci4KCmBgYHtlbGlzcCBzZXR1cF93b3Jrb24sIGV2YWw9RkFMU0V9CihkZWZ2YXIgZWxweS1ycGMtdmlydHVhbGVudi1wYXRoICdjdXJyZW50KQoocHl2ZW52LWFjdGl2YXRlICIvc3cvbG9jYWwvZmlqaS8yMDIyMTEiKQoocHl2ZW52LWFjdGl2YXRlICJ2ZW52IikKYGBgCgojIEludHJvZHVjdGlvbgoKSmFjcXVlcyBpcyBzZWVraW5nIHRvIHNlZ21lbnQgYW5kIGZvbGxvdyBjZWxscyBvdmVyIHRpbWUuCgpTdGVwcyBwZXJmb3JtZWQgc28gZmFyOgoKMS4gIFB5dGhvbi92ZW52IGluc3RhbGxhdGlvbiBvZiBmaWppCjIuICBGaWd1cmVkIG91dCBzb21lIGVhc3kgaW50ZXJhY3Rpb25zIGJldHdlZW4gcHl0aG9uIGFuZCBhY3R1YWwgaW1hZ2UgZGF0YS4KMy4gIEltcGxlbWVudGVkIGEgc2ltcGxlIGZ1bmN0aW9uIHRvIGZpbmQgbWluaW11bSBkaXN0YW5jZXMgYmV0d2VlbgogICAgcHV0YXRpdmUgY2VsbHMgdXNpbmcgdGhlIGV4dGFudCBtZXRob2RzLgoKIyBOZXh0IHN0ZXBzCgoxLiAgTm90ZSB0aGF0IHRoZSBjemkgaW1hZ2VzIGFyZSBpbW1lZGlhdGVseSBvcGVuYWJsZSB2aWEgZmlqaSdzIGJpb2Zvcm1hdCBpbnRlcmZhY2UuCjIuICBMb2FkIGRhdGFzZXQgdmlhIGZpamkKMy4gIEludm9rZSBjZWxscG9zZSB2aWEgdGhlIHB5dGhvbiBpbnRlcmZhY2UgKHNhbWUgYXMgcHlpbWFnZWopCjQuICBTYXZlIFJPSXMgcHJvZHVjZWQgYnkgY2VsbHBvc2UsIHNhdmUgdGhlbSB0byB6aXAgb3V0cHV0IGZpbGUKICBhLiAgVGhlIHJvaSBpbnRlcmZhY2UgaW4gZmlqaSBjYW4gYWRkcmVzcyB0aGVzZQo1LiAgQ3VycmVudGx5IGEgbWFjcm8gaXMgaW52b2tlZCB3aGljaCBwZXJmb3JtcyBhIHNldCBvZiBtZWFzdXJlbWVudHMKICAgIG9uIGV2ZXJ5IFJPSSBhbmQgc2F2ZXMgdGhlbSB0byBhIGNzdi4gIFRoaXMgY3JlYXRlcyB0aGUgcHJpbWFyeQogICAgZGF0YSBzdHJ1Y3R1cmUgdXNlZCBmb3IgYWxsIGZvbGxvd2luZyBwcm9jZXNzZXMuCjYuICBOZWVkIHRvIGNyZWF0ZSBhIGRhdGFzdHJ1Y3R1cmUgd2hpY2ggaWRlbnRpZmllcyBlYWNoIGluZGl2aWR1YWwKICAgIGNlbGwgb3ZlciB0aW1lIGdpdmVuIHRoZXNlIFJPSXMgd2hpY2ggeC95L3RpbWUoZnJhbWUpIGFsb25nIHdpdGgKICAgIHRoZSBtZWFzdXJlbWVudHMgb2YgaW50ZXJlc3QgKGFyZWEvbWVhbi9zdGRldi9pbnRlbnNpdGllcyBieQogICAgY29sb3IpLgogICAgYS4gIFNtYWxsIHBvc3Rwcm9jZXNzaW5nIGRldGFpbHM6IHRoZSBpbnRlbnNpdHkgdmFsdWVzIHByb2R1Y2VkCiAgICBtdXN0IGJlIG5vcm1hbGl6ZWQgYnkgY2VsbCBhcmVhLgoKIyBEZW1vbnN0cmF0ZSB0aGUgZmlyc3QgY291cGxlIG9mIHN0ZXBzIHVzaW5nIGFuIGFjdHVhbCBkYXRhc2V0CgpKYWNxdWVzIHNlbnQgbWUgYW4gaW1hZ2UgYWNxdWlyZWQgZnJvbSB0aGUgbWljcm9zY29wZSBhbmQgSSBzYXZlZCBpdAphcyAndGVzdF9kYXRhL3Jhdy50aWYnLCBoZSBhbHNvIHNlbnQgbWUgYSBjZWxscG9zZSBtb2RlbCB3aGljaCBJIHNhdmVkCnRvIHRoZSAnbW9kZWxzLycgZGlyZWN0b3J5LgoKR2l2ZW4gdGhlc2UgYXMgYSBzdGFydGluZyBwb2ludCwgbGV0IHVzIHRyeSB0byBvcGVuIHRoZSBpbWFnZSB3aXRoIGEKZmlqaSBpbnN0YW5jZSBhbmQgc3BsaXQgaXQgaW50byBhIHNlcmllcyBvZiBzbWFsbGVyIGZpbGVzIGJ5CnRpbWVwb2ludC4KCiMjIExvYWQgbmVjZXNzYXJ5IHB5dGhvbiBtb2R1bGVzCgpgYGB7cHl0aG9uIGxvYWR9CmltcG9ydCBvcwppbXBvcnQgZ2xvYgppbXBvcnQgaW1hZ2VqCmltcG9ydCBwYW5kYXMKZnJvbSBwYW5kYXMgaW1wb3J0IERhdGFGcmFtZQppbXBvcnQgbnVtcHkgYXMgbnAKaW1wb3J0IGdlb3BhbmRhcwppbXBvcnQgbWF0cGxvdGxpYi5weXBsb3QgYXMgcGx0CmltcG9ydCBzZWFib3JuCmltcG9ydCBzY3lqYXZhCmltcG9ydCBtdWx0aXByb2Nlc3NpbmcgYXMgbXAKZnJvbSBjZWxscG9zZSBpbXBvcnQgbW9kZWxzLCBpbwpmcm9tIGNlbGxwb3NlLmlvIGltcG9ydCAqCmZyb20gY29sbGVjdGlvbnMgaW1wb3J0IGRlZmF1bHRkaWN0CmZyb20ganB5cGUgaW1wb3J0IEpBcnJheSwgSkludApgYGAKCiMjIFNldCB1cCBzb21lIGluaXRpYWwgdmFyaWFibGVzCgpgYGB7cHl0aG9uIHBhcmFtZXRlcnN9CmJhc2VfZGlyID0gJy9sYWIvc2NyYXRjaC9hdGIvaW1hZ2luZy9tdGJfMjAyMycKb3MuY2hkaXIoYmFzZV9kaXIpCmlucHV0X2ZpbGUgPSBmIntiYXNlX2Rpcn0vdGVzdF9kYXRhL3Jhdy50aWYiCm91dHB1dF9kaXIgPSBmIntiYXNlX2Rpcn0vb3V0cHV0cyIKcGFuZGFzLnNldF9vcHRpb24oJ2Rpc3BsYXkubWF4X2NvbHVtbnMnLCBOb25lKQp2ZXJib3NlID0gVHJ1ZQpgYGAKCiMjIFN0YXJ0IGltYWdlai9maWppCgpOb3RlIHRoYXQgSSBhbSB1c2luZyBmaWppL2ltYWdlaiBmcm9tIHdpdGhpbiBhIHZpcnR1YWwgZW52aXJvbm1lbnQKd2hpY2ggSSBzeW1ib2xpY2FsbHkgbGlua2VkIHRvIHRoZSBsb2NhbCBkaXJlY3RvcnkgJ3ZlbnYvJy4KCkFzIGEgcmVzdWx0LCB3aGVuIEkgaW5pdGlhbGl6ZSBmaWppLCBJIHdpbGwgY2FsbCB0aGUgYmFzZSBkaXJlY3Rvcnkgb2YKdGhlIGRvd25sb2FkZWQgZmlqaSB0cmVlIHdpdGhpbiB0aGUgdmlydHVhbCBlbnZpcm9ubWVudCwgd2hpY2ggSQpzb21ld2hhdCBlcnJvbmVvdXNseSBwdXQgaW4gYmluLy4KCmBgYHtweXRob24gc3RhcnRfZmlqaX0Kc2N5amF2YS5jb25maWcuYWRkX29wdGlvbignLVhteDY0ZycpCnN0YXJ0X2RpciA9IG9zLmdldGN3ZCgpCmlqID0gaW1hZ2VqLmluaXQoJ3ZlbnYvYmluL0ZpamkuYXBwJywgbW9kZSA9ICdpbnRlcmFjdGl2ZScpCiMjIFNvbWV0aGluZyBhYm91dCB0aGlzIGluaXQoKSBmdW5jdGlvbiBjaGFuZ2VzIHRoZSBjdXJyZW50IHdvcmtpbmcgZGlyZWN0b3J5Lgpvcy5jaGRpcihzdGFydF9kaXIpCmlqLmdldFZlcnNpb24oKQpQb2x5Z29uUm9pID0gc2N5amF2YS5qaW1wb3J0KCdpai5ndWkuUG9seWdvblJvaScpCk92ZXJsYXkgPSBzY3lqYXZhLmppbXBvcnQoJ2lqLmd1aS5PdmVybGF5JykKUmVnaW9ucyA9IHNjeWphdmEuamltcG9ydCgnbmV0LmltZ2xpYjIucm9pLlJlZ2lvbnMnKQpMYWJlbFJlZ2lvbnMgPSBzY3lqYXZhLmppbXBvcnQoJ25ldC5pbWdsaWIyLnJvaS5sYWJlbGluZy5MYWJlbFJlZ2lvbnMnKQpvdiA9IE92ZXJsYXkoKQpgYGAKCiMjIE9wZW4gdGhlIGlucHV0IGZpbGUgYW5kIHNwbGl0IGl0IGJ5IHRpbWUKCk9wZW4gdGhlIGZpbGUsIGZpZ3VyZSBvdXQgaXRzIGRpbWVuc2lvbnMsIGFuZCB3cml0ZSBwb3J0aW9ucyBvZiBpdCB0bwphbiBvdXRwdXQgZGlyZWN0b3J5LgoKSSB3cm90ZSB0aGlzIHRoaW5raW5nIEkgY291bGQgcGFyYWxsZWxpemUgdGhlIG91dHB1dCB3cml0aW5nIHRvIDgKY3B1cy4gIEJ1dCBJIHRoaW5rIEkgZG8gbm90IHlldCB1bmRlcnN0YW5kIGhvdyBweXRob24gc2NvcGVzIHZhcmlhYmxlcwppbiB0aGlzIGNvbnRleHQgYW5kIHNvIGl0IGRpZCBub3QgcXVpdGUgd29yay4gIEkgdHVybmVkIHRoYXQgb2ZmIGZvcgp0aGUgbW9tZW50IGFuZCByYW4gaXQgYW5kIGl0IGZpbmlzaGVkIGluIGFib3V0IGEgbWludXRlLgoKVGhlIGZvbGxvd2luZyBmdW5jdGlvbiBtYXkgcmVxdWlyZSBhIHRlc3QgdG8gc2VlIGlmIHRoZSBvdXRwdXQKZGlyZWN0b3J5IGFscmVhZHkgZXhpc3RzIGJlY2F1c2Ugc2NpamF2YSB3aWxsIGZyZWFrIG91dC4KCmBgYHtweXRob24gc3BsaXRfaW1hZ2VfZnVuY3Rpb259CmRlZiBzZXBhcmF0ZV9zbGljZXMoaW5wdXRfZmlsZSwgb3V0cHV0X2Jhc2UgPSAnb3V0cHV0cycsIHdhbnRlZF94ID0gVHJ1ZSwgd2FudGVkX3kgPSBUcnVlLAogICAgICAgICAgICAgICAgICAgIHdhbnRlZF96ID0gMSwgd2FudGVkX2NoYW5uZWwgPSAyLCBjcHVzID0gOCwgb3ZlcndyaXRlID0gRmFsc2UpOgogICAgcG9vbCA9IG1wLlBvb2woY3B1cykKICAgIGlucHV0X2Jhc2UgPSBvcy5wYXRoLmJhc2VuYW1lKGlucHV0X2ZpbGUpCiAgICBpbnB1dF9kaXIgPSBvcy5wYXRoLmRpcm5hbWUoaW5wdXRfZmlsZSkKICAgIGlucHV0X25hbWUgPSBvcy5wYXRoLnNwbGl0ZXh0KGlucHV0X2Jhc2UpWzBdCiAgICBvdXRwdXRfZGlyZWN0b3J5ID0gZiJ7aW5wdXRfZGlyfS9vdXRwdXRzL3tpbnB1dF9uYW1lfV9zdWJzdGFjayIKICAgIG9zLm1ha2VkaXJzKG91dHB1dF9kaXJlY3RvcnksIGV4aXN0X29rID0gVHJ1ZSkKICAgIGlmIHZlcmJvc2U6CiAgICAgICAgcHJpbnQoIlN0YXJ0aW5nIHRvIG9wZW4gdGhlIGlucHV0IGZpbGUsIHRoaXMgdGFrZXMgYSBtb21lbnQuIikKICAgIGRhdGFzZXQgPSBpai5pbygpLm9wZW4oaW5wdXRfZmlsZSkKICAgIGlmIHZlcmJvc2U6CiAgICAgICAgcHJpbnQoZiJPcGVuZWQgaW5wdXQgZmlsZSwgd3JpdGluZyBpbWFnZXMgdG8ge291dHB1dF9kaXJlY3Rvcnl9IikKCiAgICBkYXRhX2luZm8gPSB7fQogICAgZm9yIGVsZW1lbnQgaW4gcmFuZ2UobGVuKGRhdGFzZXQuZGltcykpOgogICAgICAgIG5hbWUgPSBkYXRhc2V0LmRpbXNbZWxlbWVudF0KICAgICAgICBkYXRhX2luZm9bbmFtZV0gPSBkYXRhc2V0LnNoYXBlW2VsZW1lbnRdCiAgICBpZiB2ZXJib3NlOgogICAgICAgIHByaW50KChmIlRoaXMgZGF0YXNldCBoYXMgZGltZW5zaW9uczogWDp7ZGF0YV9pbmZvWydYJ119IiwKICAgICAgICAgICAgICBmIlk6e2RhdGFfaW5mb1snWSddfSBaOntkYXRhX2luZm9bJ1onXX0gVGltZTp7ZGF0YV9pbmZvWydUaW1lJ119IikpCgogICAgZGVmIHNhdmVfc2xpY2UodGltZXBvaW50KToKICAgICAgICB3YW50ZWRfc2xpY2UgPSBkYXRhc2V0WzosIDosIHdhbnRlZF9jaGFubmVsLCB3YW50ZWRfeiwgdGltZXBvaW50XQogICAgICAgIHNsaWNlX2ltYWdlID0gaWoucHkudG9fZGF0YXNldCh3YW50ZWRfc2xpY2UpCiAgICAgICAgb3V0cHV0X2ZpbGVuYW1lID0gZiJ7b3V0cHV0X2RpcmVjdG9yeX0ve2lucHV0X25hbWV9X3t0aW1lcG9pbnR9LnRpZiIKICAgICAgICBpZiAob3MucGF0aC5leGlzdHMob3V0cHV0X2ZpbGVuYW1lKSk6CiAgICAgICAgICAgIGlmIG92ZXJ3cml0ZToKICAgICAgICAgICAgICAgIHByaW50KGYiUmV3cml0aW5nIHtvdXRwdXRfZmlsZW5hbWV9IikKICAgICAgICAgICAgICAgIG9zLnJlbW92ZShvdXRwdXRfZmlsZW5hbWUpCiAgICAgICAgICAgICAgICBzYXZlZCA9IGlqLmlvKCkuc2F2ZShzbGljZV9pbWFnZSwgb3V0cHV0X2ZpbGVuYW1lKQogICAgICAgICAgICBlbHNlOgogICAgICAgICAgICAgICAgaWYgdmVyYm9zZToKICAgICAgICAgICAgICAgICAgICBwcmludChmIlNraXBwaW5nIHtvdXRwdXRfZmlsZW5hbWV9LCBpdCBhbHJlYWR5IGV4aXN0cy4iKQogICAgICAgIGVsc2U6CiAgICAgICAgICAgIHNhdmVkID0gaWouaW8oKS5zYXZlKHNsaWNlX2ltYWdlLCBvdXRwdXRfZmlsZW5hbWUpCiAgICAgICAgICAgIGlmIHZlcmJvc2U6CiAgICAgICAgICAgICAgICBwcmludChmIlNhdmluZyBpbWFnZSB7aW5wdXRfbmFtZX1fe3RpbWVwb2ludH0uIikKICAgICAgICByZXR1cm4gd2FudGVkX3NsaWNlCgogICAgc2xpY2VzID0gW10KICAgIGZvciB0aW1lcG9pbnQgaW4gcmFuZ2UoZGF0YV9pbmZvWydUaW1lJ10pOgogICAgICAgIHNhdmVkID0gc2F2ZV9zbGljZSh0aW1lcG9pbnQpCiAgICAgICAgc2xpY2VzLmFwcGVuZChzYXZlZCkKICAgICAgICAjIyBJIHdhbnQgdG8gc2VlIGlmIEkgY2FuIHNwbGl0IHRoZSBzYXZlcyBhY3Jvc3MgOCBjcHVzLgogICAgICAgICMjIHBvb2wuYXBwbHkoc2F2ZV9zbGljZSwgYXJncyA9ICh0aW1lcG9pbnQsICkpCiAgICAjIyBwb29sLmNsb3NlKCkKICAgIHJldHVybiBkYXRhc2V0LCBzbGljZXMsIG91dHB1dF9kaXJlY3RvcnkKYGBgCgojIyBDZWxscG9zZQoKQXQgdGhpcyBwb2ludCB3ZSBzaG91bGQgaGF2ZSBhIGRpcmVjdG9yeSBjb250YWluaW5nIGZpbGVzIG9mCmluZGl2aWR1YWwgdGltZXBvaW50cy4gIEphY3F1ZXMgc2VudCBtZSBhbiBpbml0aWFsIGltcGxlbWVudGF0aW9uIG9mCnRoZSB1c2FnZSBvZiBjZWxscG9zZSB0byBjYWxsIGluZGl2aWR1YWwgY2VsbHMuICBMZXQgdXMgaW5jbHVkZSB0aGF0Cm5vdy4gIEkgdGhpbmsgdGhlIHByZXZpb3VzIGZ1bmN0aW9uIHNob3VsZCBwcm9iYWJseSBhbHNvIHJldHVybiB0aGUKZGlyZWN0b3J5IG9mIHRoZSBzZXBhcmF0ZWQgaW5wdXQgZmlsZXMuCgpgYGB7cHl0aG9uIGNlbGxwb3NlfQpkZWYgaW52b2tlX2NlbGxwb3NlKGlucHV0X2RpcmVjdG9yeSwgbW9kZWxfZmlsZSwgY2hhbm5lbHMgPSBbWzAsIDBdXSwgZGlhbWV0ZXIgPSAxNjAsCiAgICAgICAgICAgICAgICAgICAgdGhyZXNob2xkID0gMC40LCBkb18zRCA9IEZhbHNlLCBiYXRjaF9zaXplID0gNjQpOgogICAgIiIiIEludm9rZSBjZWxscG9zZSB1c2luZyB0aGUgc3BsaXQgZmlsZXMgZnJvbSB0aGUgcHJldmlvdXMgZnVuY3Rpb24uCgogICAgSmFjcXVlcyBtZW50aW9uZWQgdGhhdCB0aGlzIGlzIGdldHRpbmcgc2xpZ2h0bHkgZGlmZmVyZW50IHJlc3VsdHMgdGhhbiB0aGUKICAgIEdVSS1pbnZva2VkIHZlcnNpb24gb2YgdGhpcyBzYW1lIGZ1bmN0aW9uLiAgSSBmaWd1cmVkIHRoYXQgc2luY2UgSSBoYXZlIGEgbWludXRlLAogICAgSSBjYW4gcG9rZSBhdCB0aGlzIGFuZCBzZWUgaWYgSSBjYW4gdW5kZXJzdGFuZCB0aGUgcmVxdWlzaXRlcyBiZWZvcmUgaGUgYXJyaXZlcy4KICAgICIiIgoKICAgICMjIFJlbGV2YW50IG9wdGlvbnM6CiAgICAjIyBtb2RlbF90eXBlKGN5dG8sIG51Y2xlaSwgY3l0bzIpLCBuZXRfYXZnKFQvRiBpZiBsb2FkIGJ1aWx0IGluIG5ldHdvcmtzIGFuZCBhdmVyYWdlIHRoZW0pCiAgICBtb2RlbCA9IG1vZGVscy5DZWxscG9zZU1vZGVsKHByZXRyYWluZWRfbW9kZWwgPSBtb2RlbF9maWxlKQogICAgZmlsZXMgPSBnZXRfaW1hZ2VfZmlsZXMoaW5wdXRfZGlyZWN0b3J5LCAnX21hc2tzJywgbG9va19vbmVfbGV2ZWxfZG93biA9IEZhbHNlKQogICAgaW1ncyA9IFtpbXJlYWQoZikgZm9yIGYgaW4gZmlsZXNdCiAgICBuaW1nID0gbGVuKGltZ3MpCiAgICBpZiB2ZXJib3NlOgogICAgICAgIHByaW50KGYiUmVhZCB7bmltZ30gaW1hZ2VzLCBzdGFydGluZyBjZWxscG9zZS4iKQogICAgIyMgUmVsZXZhbnQgb3B0aW9uczoKICAgICMjIGJhdGNoX3NpemUoaW5jcmVhc2UgZm9yIG1vcmUgcGFyYWxsZWxpemF0aW9uKSwgY2hhbm5lbHModHdvIGVsZW1lbnQgbGlzdCBvZiB0d28gZWxlbWVudAogICAgIyMgY2hhbm5lbHMgdG8gc2VnbWVudDsgdGhlIGZpcnN0IGlzIHRoZSBzZWdtZW50LCBzZWNvbmQgaXMgb3B0aW9uYWwgbnVjbGV1czsKICAgICMjIGludGVybmFsIGVsZW1lbnRzIGFyZSBjb2xvciBjaGFubmVscyB0byBxdWVyeSwgc28gW1swLDBdLFsyLDNdXSBtZWFucyBkbyBtYWluIGNlbGxzIGluCiAgICAjIyBncmF5c2NhbGUgYW5kIGEgc2Vjb25kIHdpdGggY2VsbHMgaW4gYmx1ZSwgbnVjbGVpIGluIGdyZWVuLgogICAgIyMgY2hhbm5lbF9heGlzLCB6X2F4aXMgPyBpbnZlcnQgKFQvRiBmbGlwIHBpeGVscyBmcm9tIGIvdyBJIGFzc3VtZSksCiAgICAjIyBub3JtYWxpemUoVC9GIHBlcmNlbnRpbGUgbm9ybWFsaXplIHRoZSBkYXRhKSwgZGlhbWV0ZXIsIGRvXzNkLAogICAgIyMgYW5pc290cm9weSAocmVzY2FsaW5nIGZhY3RvciBmb3IgM2Qgc2VnbWVudGF0aW9uKSwgbmV0X2F2ZyAoYXZlcmFnZSBtb2RlbHMpLAogICAgIyMgYXVnbWVudCA/LCB0aWxlID8sIHJlc2FtcGxlLCBpbnRlcnAsIGZsb3dfdGhyZXNob2xkLCBjZWxscHJvYl90aHJlc2hvbGQgKGludGVyZXN0aW5nKSwKICAgICMjIG1pbl9zaXplICh0dXJuZWQgb2ZmIHdpdGggLTEpLCBzdGl0Y2hfdGhyZXNob2xkID8sIHJlc2NhbGUgPy4KICAgIG91dHB1dF9maWxlcyA9IGRlZmF1bHRkaWN0KGRpY3QpCiAgICBleGlzdGluZ19maWxlcyA9IDAKICAgIGZvciBmaWxlbmFtZSBpbiBmaWxlczoKICAgICAgICBmX25hbWUgPSBvcy5wYXRoLnNwbGl0ZXh0KGZpbGVuYW1lKVswXQogICAgICAgIG91dHB1dF9tYXNrID0gZiJ7c3RhcnRfZGlyfS97Zl9uYW1lfV9jcF9tYXNrcy5wbmciCiAgICAgICAgb3V0cHV0X3R4dCA9IGYie3N0YXJ0X2Rpcn0ve2ZfbmFtZX1fY3Bfb3V0bGluZXMudHh0IgogICAgICAgIG91dHB1dF9maWxlc1tmaWxlbmFtZV1bJ2lucHV0X2ZpbGUnXSA9IGYie3N0YXJ0X2Rpcn0ve2ZpbGVuYW1lfSIKICAgICAgICBvdXRwdXRfZmlsZXNbZmlsZW5hbWVdWydvdXRwdXRfbWFzayddID0gb3V0cHV0X21hc2sKICAgICAgICBvdXRwdXRfZmlsZXNbZmlsZW5hbWVdWydvdXRwdXRfdHh0J10gPSBvdXRwdXRfdHh0CiAgICAgICAgb3V0cHV0X2ZpbGVzW2ZpbGVuYW1lXVsnZXhpc3RzJ10gPSBGYWxzZQogICAgICAgIGlmIChvcy5wYXRoLmV4aXN0cyhvdXRwdXRfZmlsZXNbZmlsZW5hbWVdWydvdXRwdXRfdHh0J10pKToKICAgICAgICAgICAgZXhpc3RpbmdfZmlsZXMgPSBleGlzdGluZ19maWxlcyArIDEKICAgICAgICAgICAgb3V0cHV0X2ZpbGVzW2ZpbGVuYW1lXVsnZXhpc3RzJ10gPSBUcnVlCgogICAgaWYgZXhpc3RpbmdfZmlsZXMgPiAwOgogICAgICAgIGlmIHZlcmJvc2U6CiAgICAgICAgICAgIHByaW50KGYiT3V0IG9mIHtuaW1nfSBvdXRwdXQgZmlsZXMsIHtleGlzdGluZ19maWxlc30gYWxyZWFkeSBleGlzdCwgbm90IHJ1bm5pbmcgY2VsbHBvc2UuIikKICAgIGVsc2U6CiAgICAgICAgbWFza3MsIGZsb3dzLCBzdHlsZXMgPSBtb2RlbC5ldmFsKAogICAgICAgICAgICBpbWdzLCBkaWFtZXRlciA9IGRpYW1ldGVyLCBjaGFubmVscyA9IGNoYW5uZWxzLCBmbG93X3RocmVzaG9sZCA9IHRocmVzaG9sZCwKICAgICAgICAgICAgZG9fM0QgPSBkb18zRCwgYmF0Y2hfc2l6ZSA9IGJhdGNoX3NpemUpCiAgICAgICAgaW8uc2F2ZV90b19wbmcoaW1ncywgbWFza3MsIGZsb3dzLCBmaWxlcykKICAgIGlmIHZlcmJvc2U6CiAgICAgICAgcHJpbnQoIkZpbmlzaGVkIGNlbGxwb3NlLCByZXR1cm5pbmcgb3V0cHV0IGZpbGVuYW1lcy4iKQogICAgcmV0dXJuIG91dHB1dF9maWxlcwpgYGAKCiMgQ3JlYXRlIFJlZ2lvbnMgb2YgaW50ZXJlc3QgZnJvbSBjZWxscG9zZSBvdXRwdXRzCgpJbiBKYWNxdWVzIG5vdGVib29rLCBpdCBsb29rcyBsaWtlIGhlIG9ubHkgZXh0cmFjdHMgUk9JcyBmcm9tIG9uZSBvZgp0aGUgY2VsbHBvc2Ugc2xpY2VzLiAgSSBhbSBhc3N1bWluZyB0aGUgZ29hbCBpcyB0byBleHRlbmQgdGhpcyBhY3Jvc3MKYWxsIGltYWdlcz8KClRoZXJlIGlzIGFuIGltcG9ydGFudCBjYXZlYXQgdGhhdCBJIG1pc3NlZDogaW1hZ2VqIGNvbWVzIHdpdGggYQpweXRob24yLWJhc2VkIHNjcmlwdGluZyBsYW5ndWFnZSBmcm9tIHdoaWNoIGl0IGFwcGVhcnMgc29tZSBvZiBoaXMKY29kZSBpcyBjb21pbmcuICBBcyBhIHJlc3VsdCBJIHNob3VsZCBsb29rIGNhcmVmdWxseSBiZWZvcmUgdXNpbmcgaXQsCmFuZCBwYXkgY2xvc2UgYXR0ZW50aW9uIHRvIHRoZSBleGFtcGxlcyBwcm92aWRlZCBoZXJlIGZvciB0aGUgbW9zdAphcHByb3ByaWF0ZSB3YXlzIG9mIGludGVyYWN0aW5nIHdpdGggdGhlIFJPSSBtYW5hZ2VyIGV0YzoKCmh0dHBzOi8vZ2l0aHViLmNvbS9pbWFnZWovcHlpbWFnZWovYmxvYi9tYWluL2RvYy9leGFtcGxlcy9ibG9iX2RldGVjdGlvbl9pbnRlcmFjdGl2ZS5weQoKYGBge3B5dGhvbiBjZWxscG9zZV90b19yb2l9CiMjIFRoZSBmb2xsb3dpbmcgaXMgZnJvbSBhIG1peCBvZiBhIGNvdXBsZSBvZiBpbXBsZW1lbnRhdGlvbnMgSSBmb3VuZDoKIyMgaHR0cHM6Ly9weWltYWdlai5yZWFkdGhlZG9jcy5pby9lbi9sYXRlc3QvQ2xhc3NpYy1TZWdtZW50YXRpb24uaHRtbAojIyBhbiBhbHRlcm5hdGl2ZSBtZXRob2QgbWF5IGJlIHRha2VuIGZyb206CiMjIGh0dHBzOi8vcHlpbWFnZWoucmVhZHRoZWRvY3MuaW8vZW4vbGF0ZXN0L0NsYXNzaWMtU2VnbWVudGF0aW9uLmh0bWwjc2VnbWVudGF0aW9uLXdvcmtmbG93LXdpdGgtaW1hZ2VqMgojIyBNeSBnb2FsIGlzIHRvIHBhc3MgdGhlIFJPSSByZWdpb25zIHRvIHRoaXMgZnVuY3Rpb24gYW5kIGNyZWF0ZSBhIHNpbWlsYXIgZGYuCmRlZiBzbGljZXNfdG9fcm9pX21lYXN1cmVtZW50cyhjZWxscG9zZV9yZXN1bHQsIHNhdmVkX3NsaWNlcyk6CiAgICBvdXRwdXRfZGljdCA9IGNlbGxwb3NlX3Jlc3VsdAogICAgY2VsbHBvc2Vfc2xpY2VzID0gbGlzdChjZWxscG9zZV9yZXN1bHQua2V5cygpKQogICAgc2xpY2VfbnVtYmVyID0gMAogICAgZm9yIHNsaWNlX25hbWUgaW4gY2VsbHBvc2Vfc2xpY2VzOgogICAgICAgIG91dHB1dF9kaWN0W3NsaWNlX25hbWVdWydzbGljZV9udW1iZXInXSA9IHNsaWNlX251bWJlcgogICAgICAgIHNsaWNlX2RhdGEgPSBzYXZlZF9zbGljZXNbc2xpY2VfbnVtYmVyXQogICAgICAgIGlucHV0X3RpZiA9IGNlbGxwb3NlX3Jlc3VsdFtzbGljZV9uYW1lXVsnaW5wdXRfZmlsZSddCiAgICAgICAgaW5wdXRfdHh0ID0gY2VsbHBvc2VfcmVzdWx0W3NsaWNlX25hbWVdWydvdXRwdXRfdHh0J10KICAgICAgICBpbnB1dF9tYXNrID0gY2VsbHBvc2VfcmVzdWx0W3NsaWNlX25hbWVdWydvdXRwdXRfbWFzayddCiAgICAgICAgaWYgdmVyYm9zZToKICAgICAgICAgICAgcHJpbnQoZiJQcm9jZXNzaW5nIGNlbGxwb3NlIG91dGxpbmU6IHtpbnB1dF90eHR9IikKICAgICAgICAjIGNvbnZlcnQgRGF0YXNldCB0byBJbWFnZVBsdXMKICAgICAgICBpbXAgPSBpai5weS50b19pbWFnZXBsdXMoc2xpY2VfZGF0YSkKICAgICAgICBybSA9IGlqLlJvaU1hbmFnZXIuZ2V0Um9pTWFuYWdlcigpCiAgICAgICAgIyMgVGhlIGxvZ2ljIGZvciB0aGlzIHdhcyB0YWtlbiBmcm9tOgogICAgICAgICMjIGh0dHBzOi8vc3RhY2tvdmVyZmxvdy5jb20vcXVlc3Rpb25zLzczODQ5NDE4L2lzLXRoZXJlLWFueS13YXktdG8tc3dpdGNoLWltYWdlai1tYWNyby1jb2RlLXRvLXB5dGhvbjMtY29kZQogICAgICAgIHR4dF9maCA9IG9wZW4oaW5wdXRfdHh0LCAncicpCiAgICAgICAgc2V0X3N0cmluZyA9IGYnU2V0IE1lYXN1cmVtZW50cy4uLicKICAgICAgICBtZWFzdXJlX3N0cmluZyA9IGYnYXJlYSBtZWFuIG1pbiBjZW50cm9pZCBtZWRpYW4gc2tld25lc3Mga3VydG9zaXMgcmVkaXJlY3Q9Tm9uZSBkZWNpbWFsPTMnCiAgICAgICAgaWouSUoucnVuKHNldF9zdHJpbmcsIG1lYXN1cmVfc3RyaW5nKQogICAgICAgIHJvaV9zdGF0cyA9IGRlZmF1bHRkaWN0KGxpc3QpCiAgICAgICAgZm9yIGxpbmUgaW4gdHh0X2ZoOgogICAgICAgICAgICB4eSA9IGxpbmUucnN0cmlwKCkuc3BsaXQoIiwiKQogICAgICAgICAgICB4eV9jb29yZHMgPSBbaW50KGVsZW1lbnQpIGZvciBlbGVtZW50IGluIHh5XQogICAgICAgICAgICB4X2Nvb3JkcyA9IFtpbnQoZWxlbWVudCkgZm9yIGVsZW1lbnQgaW4geHlbOjoyXV0KICAgICAgICAgICAgeV9jb29yZHMgPSBbaW50KGVsZW1lbnQpIGZvciBlbGVtZW50IGluIHh5WzE6OjJdXQogICAgICAgICAgICB4Y29vcmRzX2ppbnQgPSBKQXJyYXkoSkludCkoeF9jb29yZHMpCiAgICAgICAgICAgIHljb29yZHNfamludCA9IEpBcnJheShKSW50KSh5X2Nvb3JkcykKICAgICAgICAgICAgcG9seWdvbl9yb2lfaW5zdGFuY2UgPSBzY3lqYXZhLmppbXBvcnQoJ2lqLmd1aS5Qb2x5Z29uUm9pJykKICAgICAgICAgICAgcm9pX2luc3RhbmNlID0gc2N5amF2YS5qaW1wb3J0KCdpai5ndWkuUm9pJykKICAgICAgICAgICAgaW1wb3J0ZWRfcG9seWdvbiA9IHBvbHlnb25fcm9pX2luc3RhbmNlKHhjb29yZHNfamludCwgeWNvb3Jkc19qaW50LAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbGVuKHhfY29vcmRzKSwgaW50KHJvaV9pbnN0YW5jZS5QT0xZR09OKSkKICAgICAgICAgICAgaW1wLnNldFJvaShpbXBvcnRlZF9wb2x5Z29uKQogICAgICAgICAgICBpai5JSi5ydW4oaW1wLCAnTWVhc3VyZScsICcnKQogICAgICAgIHNsaWNlX3Jlc3VsdCA9IGlqLlJlc3VsdHNUYWJsZS5nZXRSZXN1bHRzVGFibGUoKQogICAgICAgIHNsaWNlX3RhYmxlID0gaWouY29udmVydCgpLmNvbnZlcnQoc2xpY2VfcmVzdWx0LAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc2N5amF2YS5qaW1wb3J0KCdvcmcuc2NpamF2YS50YWJsZS5UYWJsZScpKQogICAgICAgIHNsaWNlX21lYXN1cmVtZW50cyA9IGlqLnB5LmZyb21famF2YShzbGljZV90YWJsZSkKICAgICAgICBvdXRwdXRfZGljdFtzbGljZV9uYW1lXVsnbWVhc3VyZW1lbnRzJ10gPSBzbGljZV9tZWFzdXJlbWVudHMKICAgICAgICBpai5JSi5ydW4oJ0NsZWFyIFJlc3VsdHMnKQogICAgICAgIHR4dF9maC5jbG9zZSgpCiAgICAgICAgaW1wLnNldE92ZXJsYXkob3YpCiAgICAgICAgaW1wLmdldFByb2Nlc3NvcigpLnJlc2V0TWluQW5kTWF4KCkKICAgICAgICBzbGljZV9udW1iZXIgPSBzbGljZV9udW1iZXIgKyAxCiAgICByZXR1cm4gb3V0cHV0X2RpY3QKYGBgCgojIENvbnZlcnQgdGhlIHNsaWNlIG1lYXN1cmVtZW50cyB0byBwYW5kYXMgZGYKCnNsaWNlc190b19yb2lfbWVhc3VyZW1lbnRzKCkgcmV0dXJucyBhIGRpY3Rpb25hcnkgd2l0aCBrZXlzIHdoaWNoIGFyZQp0aGUgZmlsZW5hbWVzIG9mIGVhY2ggcmF3IHRpZiBmaWxlLiAgRWFjaCBlbGVtZW50IG9mIHRoYXQgZGljdGlvbmFyeQppcyBpbiB0dXJuIGEgZGljdGlvbmFyeSBjb250YWluaW5nIHNvbWUgaW5mb3JtYXRpb24gYWJvdXQgdGhlIGZpbGVzCmFsb25nIHdpdGggYSBkZiBvZiB0aGUgbWVhc3VyZW1lbnRzIHByb3ZpZGVkIGJ5IGltYWdlai4KCk15IGxpdHRsZSBnZW9wYW5kYXMgZnVuY3Rpb24gYXNzdW1lcyBhIHNpbmdsZSBsb25nIGRmIHdpdGggc29tZQpjb2x1bW5zIHdoaWNoIHRlbGwgaXQgd2hpY2ggdGltZXBvaW50LiAgU28gbGV0cyBtYWtlIGEgcXVpY2sgZnVuY3Rpb24KdG8gZ2l2ZSB0aGF0IGhlcmUuICBPVE9IIGl0IG1heSBiZSB3aXNlci9iZXR0ZXIgdG8gbWFrZSBzb21lIGNoYW5nZXMKdG8gc2xpY2VzX3RvX3JvaV9tZWFzdXJlbWVudHMoKSBzbyB0aGF0IGl0IHJldHVybnMgdGhhdCBmb3JtYXQgZGY7IGJ1dApzaW5jZSBJIGFtIHVzaW5nIHRoaXMgYXMgYSBsZWFybmluZyBleHBlcmllbmNlIHRvIGdldCBtb3JlIGNvbWZvcnRhYmxlCndpdGggcHl0aG9uIGRhdGEgc3RydWN0dXJlcywgSSB3aWxsIG5vdCBkbyBpdCB0aGF0IHdheS4KCmBgYHtweXRob24gY29udmVydF9zbGljZXNfdG9fcGFuZGFzfQpkZWYgY29udmVydF9zbGljZXNfdG9fcGFuZGFzKHNsaWNlcyk6CiAgICBjb25jYXRlbmF0ZWQgPSBwYW5kYXMuRGF0YUZyYW1lKCkKICAgIHNsaWNlX2tleXMgPSBsaXN0KHNsaWNlcy5rZXlzKCkpCiAgICBzbGljZV9jb3VudGVyID0gMAogICAgZm9yIGsgaW4gc2xpY2Vfa2V5czoKICAgICAgICBzbGljZV9jb3VudGVyID0gc2xpY2VfY291bnRlciArIDEKICAgICAgICBjdXJyZW50X3NsaWNlID0gc2xpY2VzW2tdCiAgICAgICAgaWYgdmVyYm9zZToKICAgICAgICAgICAgcHJpbnQoZiJUaGUgc2xpY2UgaXMge2t9IikKICAgICAgICBzbGljZV9udW1iZXIgPSBjdXJyZW50X3NsaWNlWydzbGljZV9udW1iZXInXQogICAgICAgIHNsaWNlX2RhdGEgPSBjdXJyZW50X3NsaWNlWydtZWFzdXJlbWVudHMnXQogICAgICAgIHNsaWNlX2RhdGFbJ0ZyYW1lJ10gPSBzbGljZV9udW1iZXIKICAgICAgICBpZiAoc2xpY2VfY291bnRlciA9PSAxKToKICAgICAgICAgICAgY29uY2F0ZW5hdGVkID0gc2xpY2VfZGF0YQogICAgICAgIGVsc2U6CiAgICAgICAgICAgIGNvbmNhdGVuYXRlZCA9IHBhbmRhcy5jb25jYXQoW2NvbmNhdGVuYXRlZCwgc2xpY2VfZGF0YV0pCiAgICAjIyBUaGlzIGlzIGEgbGl0dGxlIHNpbGx5LCBidXQgSSBjb3VsZG4ndCByZW1lbWJlciB0aGF0IHRoZSBpbmRleCBhdHRyaWJ1dGUKICAgICMjIGlzIHRoZSBudW1lcmljIHJvd25hbWUgZm9yIGEgbW9tZW50CiAgICAjIyBUaGUgcmVzZXRfaW5kZXgoKSBkb2VzIHdoYXQgaXQgc2F5cyBvbiB0aGUgdGluZSwgYW5kIGNoYW5nZXMgdGhlIDE6MTksIDE6MjAsIGV0YwogICAgIyMgb2YgZWFjaCBpbmRpdmlkdWFsIHRpbWUgRnJhbWUgdG8gYSBzaW5nbGUgcmFuZ2Ugb2YgMToyMDAwCiAgICBjb25jYXRlbmF0ZWQuaW5kZXggPSBjb25jYXRlbmF0ZWQucmVzZXRfaW5kZXgoKS5pbmRleAogICAgcmV0dXJuIGNvbmNhdGVuYXRlZApgYGAKCiMgQ3JlYXRlIGNlbGwgZ3JvdXBzCgpgYGB7cHl0aG9uIG5lYXJlc3RfbmVpZ2hib3JfZnVuY3Rpb259CmRlZiBuZWFyZXN0X2NlbGxzX292ZXJfdGltZShkZiwgbWF4X2Rpc3QgPSAxMC4wLCBtYXhfcHJvcCA9IDAuNywKICAgICAgICAgICAgICAgICAgICAgICAgICAgIHhfY29sdW1uID0gJ1gnLCB5X2NvbHVtbiA9ICdZJywgdmVyYm9zZSA9IFRydWUpOgogICAgIiIiVHJhY2UgY2VsbHMgb3ZlciB0aW1lIiIiCiAgICBnZGYgPSBnZW9wYW5kYXMuR2VvRGF0YUZyYW1lKAogICAgICAgIGRmLAogICAgICAgIGdlb21ldHJ5ID0gZ2VvcGFuZGFzLnBvaW50c19mcm9tX3h5KGRmW3hfY29sdW1uXSwgZGZbeV9jb2x1bW5dKSkKCiAgICBmaW5hbF90aW1lID0gZ2RmLkZyYW1lLm1heCgpCiAgICBwYWlyd2lzZV9kaXN0YW5jZXMgPSBbXQogICAgZm9yIHN0YXJ0X3RpbWUgaW4gcmFuZ2UoMSwgZmluYWxfdGltZSk6CiAgICAgICAgaSA9IHN0YXJ0X3RpbWUKICAgICAgICBqID0gaSArIDEKICAgICAgICB0aV9pZHggPSBnZGYuRnJhbWUgPT0gaQogICAgICAgIHRqX2lkeCA9IGdkZi5GcmFtZSA9PSBqCiAgICAgICAgaWYgdmVyYm9zZToKICAgICAgICAgICAgcHJpbnQoZiJHZXR0aW5nIGRpc3RhbmNlcyBvZiBkZnMge2l9IGFuZCB7an0uIikKICAgICAgICB0aSA9IGdkZlt0aV9pZHhdCiAgICAgICAgdGogPSBnZGZbdGpfaWR4XQogICAgICAgIHRpX3Jvd3MgPSB0aS5zaGFwZVswXQogICAgICAgIHRqX3Jvd3MgPSB0ai5zaGFwZVswXQogICAgICAgIHRpdGogPSBnZW9wYW5kYXMuc2pvaW5fbmVhcmVzdCh0aSwgdGosIGRpc3RhbmNlX2NvbCA9ICJwYWlyd2lzZV9kaXN0IikKICAgICAgICBwYWlyd2lzZV9kaXN0YW5jZXMuYXBwZW5kKHRpdGopCgogICAgaWRfY291bnRlciA9IDAKICAgICMjIENlbGwgSURzIHBvaW50aW5nIHRvIGEgbGlzdCBvZiBjZWxscwogICAgdHJhY2VkID0ge30KICAgICMjIEVuZHBvaW50cyBwb2ludGluZyB0byB0aGUgY2VsbCBJRHMKICAgIGVuZHMgPSB7fQogICAgZm9yIGkgaW4gcmFuZ2UoMCwgZmluYWxfdGltZSAtIDEpOgogICAgICAgIHF1ZXJ5ID0gcGFpcndpc2VfZGlzdGFuY2VzW2ldCiAgICAgICAgcGFzc2VkX2lkeCA9IHF1ZXJ5LnBhaXJ3aXNlX2Rpc3QgPD0gbWF4X2Rpc3QKICAgICAgICBmYWlsZWRfaWR4ID0gcXVlcnkucGFpcndpc2VfZGlzdCA+IG1heF9kaXN0CiAgICAgICAgaWYgKGZhaWxlZF9pZHguc3VtKCkgPiAwKToKICAgICAgICAgICAgaWYgdmVyYm9zZToKICAgICAgICAgICAgICAgIHByaW50KGYiU2tpcHBlZCB7ZmFpbGVkX2lkeC5zdW0oKX0gZWxlbWVudHMgaW4gc2VnbWVudCB7aX0uIikKICAgICAgICBxdWVyeSA9IHF1ZXJ5W3Bhc3NlZF9pZHhdCgogICAgICAgIHByb3BfY2hhbmdlID0gcXVlcnkuQXJlYV9sZWZ0IC8gcXVlcnkuQXJlYV9yaWdodAogICAgICAgIGluY3JlYXNlZF9pZHggPSBwcm9wX2NoYW5nZSA+IDEuMAogICAgICAgIHByb3BfY2hhbmdlW2luY3JlYXNlZF9pZHhdID0gMS4wIC8gcHJvcF9jaGFuZ2VbaW5jcmVhc2VkX2lkeF0KICAgICAgICBmYWlsZWRfaWR4ID0gcHJvcF9jaGFuZ2UgPCBtYXhfcHJvcAogICAgICAgIHBhc3NlZF9pZHggPSBwcm9wX2NoYW5nZSA+PSBtYXhfcHJvcAogICAgICAgIGlmIChmYWlsZWRfaWR4LnN1bSgpID4gMCk6CiAgICAgICAgICAgIGlmIHZlcmJvc2U6CiAgICAgICAgICAgICAgICBza2lwX3N0cmluZyA9IChmIlNraXBwZWQge2ZhaWxlZF9pZHguc3VtKCl9IGVsZW1lbnRzIGluIHNlZ21lbnQge2l9ICIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBmImJlY2F1c2UgdGhlIHNpemUgY2hhbmdlZCB0b28gbXVjaC4iKQogICAgICAgICAgICAgICAgcHJpbnQoc2tpcF9zdHJpbmcpCiAgICAgICAgICAgIHF1ZXJ5ID0gcXVlcnlbcGFzc2VkX2lkeF0KCiAgICAgICAgZm9yIHJvdyBpbiBxdWVyeS5pdGVydHVwbGVzKCk6CiAgICAgICAgICAgIHN0YXJ0X2NlbGwgPSByb3cuSW5kZXgKICAgICAgICAgICAgZW5kX2NlbGwgPSByb3cuaW5kZXhfcmlnaHQKICAgICAgICAgICAgaWYgc3RhcnRfY2VsbCBpbiBlbmRzLmtleXMoKToKICAgICAgICAgICAgICAgIGNlbGxfaWQgPSBlbmRzW3N0YXJ0X2NlbGxdCiAgICAgICAgICAgICAgICBjdXJyZW50X3ZhbHVlID0gdHJhY2VkW2NlbGxfaWRdCiAgICAgICAgICAgICAgICBjdXJyZW50X3ZhbHVlLmFwcGVuZChlbmRfY2VsbCkKICAgICAgICAgICAgICAgIHRyYWNlZFtjZWxsX2lkXSA9IGN1cnJlbnRfdmFsdWUKICAgICAgICAgICAgICAgIGVuZHNbZW5kX2NlbGxdID0gY2VsbF9pZAogICAgICAgICAgICBlbHNlOgogICAgICAgICAgICAgICAgaWRfY291bnRlciA9IGlkX2NvdW50ZXIgKyAxCiAgICAgICAgICAgICAgICB0cmFjZWRbaWRfY291bnRlcl0gPSBbc3RhcnRfY2VsbCwgZW5kX2NlbGxdCiAgICAgICAgICAgICAgICBlbmRzW2VuZF9jZWxsXSA9IGlkX2NvdW50ZXIKCiAgICByZXR1cm4gdHJhY2VkCmBgYAoKIyBSdW4gdGhlIGZ1bmN0aW9ucyBhbmQgcGxvdCB0aGUgcmVzdWx0cwoKYGBge3B5dGhvbiBydW5fZm5zfQpyYXdfc3BsaXQsIHNhdmVkX3NsaWNlcywgb3V0cHV0X2RpcmVjdG9yeSA9IHNlcGFyYXRlX3NsaWNlcyhpbnB1dF9maWxlKQoKb3V0cHV0X2RpcmVjdG9yeSA9ICd0ZXN0X2RhdGEvb3V0cHV0cy9yYXdfc3Vic3RhY2snCmNlbGxwb3NlX3Jlc3VsdCA9IGludm9rZV9jZWxscG9zZShvdXRwdXRfZGlyZWN0b3J5LCAnbW9kZWxzL0NQXzIwMjIwNTIzXzEwNDAxNicpCgpzbGljZV9tZWFzdXJlbWVudHMgPSBzbGljZXNfdG9fcm9pX21lYXN1cmVtZW50cyhjZWxscG9zZV9yZXN1bHQsIHNhdmVkX3NsaWNlcykKCmNvbmNhdGVuYXRlZCA9IGNvbnZlcnRfc2xpY2VzX3RvX3BhbmRhcyhzbGljZV9tZWFzdXJlbWVudHMpCgpuZWFyZXN0ID0gbmVhcmVzdF9jZWxsc19vdmVyX3RpbWUoY29uY2F0ZW5hdGVkLCBtYXhfZGlzdCA9IDEwLjAsIHhfY29sdW1uID0gJ1gnLCB5X2NvbHVtbiA9ICdZJykKYGBgCgojIyBHZXQgaW5mb3JtYXRpb24gZnJvbSBhIGdyb3VwIG9mIGNlbGxzCgpBcyBhIGZpbmFsIHN0ZXAsIHdlIHNob3VsZCBiZSBhYmxlIHRvIGV4dHJhY3QgYW5kIHBsYXkgd2l0aCB0aGUKaW5mb3JtYXRpb24gZnJvbSBvbmUgb3IgbW9yZSBncm91cHMgb2YgY2VsbHMuCgpgYGB7cHl0aG9uIGdldF9pbmZvfQpjZWxsX2lkID0gMTI5CmNlbGxfaWR4ID0gbmVhcmVzdFtjZWxsX2lkXQpjZWxsX2RhdGEgPSBjb25jYXRlbmF0ZWQubG9jW2NlbGxfaWR4XQpsZW4oY2VsbF9kYXRhKQpjZWxsX2RhdGEgPSBjZWxsX2RhdGEucmVzZXRfaW5kZXgoKQoKc2NhdHRlciA9IHBsdC5zY2F0dGVyKGNlbGxfZGF0YVsnWCddLCBjZWxsX2RhdGFbJ1knXSkKZmluYWxfcm93ID0gY2VsbF9kYXRhLmluZGV4Lm1heCgpCmZvciBzdGFydF90aW1lIGluIHJhbmdlKDAsIGZpbmFsX3JvdyAtIDEpOgogICAgdGlfaWR4ID0gY2VsbF9kYXRhLmluZGV4ID09IHN0YXJ0X3RpbWUKICAgIHRqX2lkeCA9IGNlbGxfZGF0YS5pbmRleCA9PSBzdGFydF90aW1lICsgMQogICAgcDF4ID0gY2VsbF9kYXRhW3RpX2lkeF0uWAogICAgcDJ4ID0gY2VsbF9kYXRhW3RqX2lkeF0uWAogICAgcDF5ID0gY2VsbF9kYXRhW3RpX2lkeF0uWQogICAgcDJ5ID0gY2VsbF9kYXRhW3RqX2lkeF0uWQogICAgeF9wb2ludHMgPSBbcDF4LCBwMnhdCiAgICB5X3BvaW50cyA9IFtwMXksIHAyeV0KICAgIHBsdC5wbG90KHhfcG9pbnRzLCB5X3BvaW50cykKZmluYWxtMV9pZHggPSBjZWxsX2RhdGEuaW5kZXggPT0gZmluYWxfcm93IC0gMQpmaW5hbF9pZHggPSBjZWxsX2RhdGEuaW5kZXggPT0gZmluYWxfcm93CmZpbmFsbTFfeCA9IGNlbGxfZGF0YVtmaW5hbG0xX2lkeF0uWApmaW5hbF94ID0gY2VsbF9kYXRhW2ZpbmFsX2lkeF0uWApmaW5hbG0xX3kgPSBjZWxsX2RhdGFbZmluYWxtMV9pZHhdLlkKZmluYWxfeSA9IGNlbGxfZGF0YVtmaW5hbF9pZHhdLlkKeF9wb2ludHMgPSBbZmluYWxtMV94LCBmaW5hbF94XQp5X3BvaW50cyA9IFtmaW5hbG0xX3ksIGZpbmFsX3ldCnBsdC5wbG90KHhfcG9pbnRzLCB5X3BvaW50cykKcGx0LnNob3coKQoKc2VhYm9ybi52aW9saW5wbG90KGRhdGEgPSBjZWxsX2RhdGEuQXJlYSkKcGx0LnNob3coKQpgYGAKCgoKYGBge3Igc2F2ZW1lfQpwYW5kZXI6OnBhbmRlcihzZXNzaW9uSW5mbygpKQptZXNzYWdlKHBhc3RlMCgiVGhpcyBpcyBocGdsdG9vbHMgY29tbWl0OiAiLCBnZXRfZ2l0X2NvbW1pdCgpKSkKdGhpc19zYXZlIDwtIHBhc3RlMChnc3ViKHBhdHRlcm49IlxcLlJtZCIsIHJlcGxhY2U9IiIsIHg9cm1kX2ZpbGUpLCAiLXYiLCB2ZXIsICIucmRhLnh6IikKbWVzc2FnZShwYXN0ZTAoIlNhdmluZyB0byAiLCB0aGlzX3NhdmUpKQp0bXAgPC0gc20oc2F2ZW1lKGZpbGVuYW1lPXRoaXNfc2F2ZSkpCmBgYAo=