<jupyter_start><jupyter_text>Coordinate Calculator and Label Compiler Inputs:- location in pixels of beginning and end of axes- range of axes- units of axis range- user's desired spacing for x coordinates- NO legend input! how to specify different colors/markers that separates the curves (for later stages)    - could eventually have mask look for the legend (distinctive enough) to learn what types of distinguishing features exist between the lines on the graph    - I will ignore this part for now- detectron2 output:    - list of dicts, one dict for each image    - each dict has an object 'instances' with the following fields.        - pred_masks: a tensor of shape (N,H,W) for N instances and an image of height H and width W.        - access it using: ```outputs['instances'].pred_masks```        - True False for each pixel    Outputs:- dictionary with the following fields:    - coordinates: a dictionary, with fields equal to the curve labels (one for each label), and entries that are a list of tuples, each with (x, and y coordinates in same units as input image)    - units: (a tuple of x and y units)    - beginning and end of curve    - NO labels (for later stages): a list of labels for each curve, taken from legend if possible, or automatic (data1, data2, etc)Options for coordinates:1. based on the actual axis, choose a set of x coordinates for which we want to know the y coordinates (for each curve)2. based on pixels, grab points that occur every "few" pixels along the x axis, then convert to coordinates- could do the first option by finding out how many pixels are between each of the given x points, and using that number of pixels paired with the second option.- do we want user to input the spacing between points, or should we use a default value that seems reasonable based on the range of the x axis?- should the different curves have uniform x axis values (so y values are directly comparable), or should we have the sets of data points for each curve start at the 'beginning' of the curve and end at the 'end'Outlier cases:- nested plots- weird legend formatting uniform x axis but also save the very first and last endpoints of the curve  maybe incorporate later:convert ID to the label from the legend inputdef get_label_for_id():    return label  Notes, TODO- adjust the x and y pixel locations to lie on the same horizontal and vertical lines- put the coordinate and pixel min max values in a dictionary or something more compact- use os to create a directory for each project. currently puts excel files in the current folder- IMPORTANT: how to do axis_info_dict for multiple images.- use OS to create a new folder each time or ensure that the filename is not taken already<jupyter_code>import pandas as pd
import numpy as np<jupyter_output><empty_output><jupyter_text>This is the first function called in my code. The arguments are all of the user input that I need<jupyter_code>def get_axis_info(xcoordinatemin, xcoordinatemax, xpixelmax, ycoordinatemin, ycoordinatemax, ypixelmax, origin, max_points, units):
    """
    Collects information provided by the user into a convenient dictionary form to be used in the csv conversion process.
    :param xcoordinatemin: array with length 1, smallest value along the x axis in input image (location of origin)
    :param xcoordinatemax: array with length 1, largest value along the x axis in input image
    :param xpixelmax: array with length 1, pixel location along x axis (integer distance from left side of image) of the xcoordinatemax
    :param ycoordinatemin: array with length 1, smallest value along the y axis in input image (location of origin)
    :param ycoordinatemax: array with length 1, largest value along the y axis in input image
    :param ypixelmax: array with length 1, pixel location along y axis (integer distance from top edge of image) of the ycoordinatemax
    :param origin: array of length 2 with the pixel location of the origin: 
                   origin[0] is the pixel location along x axis (integer distance from left side of image) 
                   of the xcoordinatemin.
                   origin[1] is the pixel location along y axis (integer distance from top edge of image)
                   of the ycoordinatemin
    :param max_points: int, maximum number of xy points desired in output for each curve
    :param units: array with length 2, with units of each axis (x first) of the input image
    :return axis_info_dict: a dictionary with the following fields: pixel_origin, x_scale, y_scale, step,
                            units, y_pixel_range, and x_pixel_range.
    """
    # this code assumes that ypixelmin < ypixelmax, which is not the case for the input, so a small correction is made
    ypixelmin_true = ypixelmax[0]
    ypixelmax_true = origin[1]
    xpixelmin = origin[0]
    x_scale = get_x_scale(xcoordinatemin[0], xcoordinatemax[0], xpixelmin, xpixelmax[0])
    y_scale = get_x_scale(ycoordinatemin[0], ycoordinatemax[0], ypixelmin_true, ypixelmax_true)
    pixel_origin = (xpixelmin, ypixelmax_true) # assumes that y pixel max is the smaller y value
    axis_info_dict = {'pixel_origin': pixel_origin,
                      'x_scale': x_scale,
                      'y_scale': y_scale,
                      'step': get_step(max_points, xpixelmin, xpixelmax[0]),
                      'units': (units[0], units[1]),
                      'y_pixel_range': (ypixelmin_true, ypixelmax_true),
                      'x_pixel_range': (xpixelmin, xpixelmax[0])}
    return axis_info_dict
def test_get_axis_info():
    """
    Tests the get_axis_info function
    """
    # the output is a dictionary with the fields: pixel_origin, x_scale, y_scale, step, and units
    axis_info_dict = get_axis_info([1], [5], [20], [250], [10], [25], [30, 280], 30, ['volts', 'amps'])
    assert isinstance(axis_info_dict, dict), 'axis_info_dict is not a dictionary'
    for field in ['step', 'pixel_origin', 'x_scale', 'y_scale', 'units', 'y_pixel_range', 'x_pixel_range']:
        assert field in axis_info_dict.keys(), 'axis_info_dict is missing fields'
    return
test_get_axis_info()
def get_step(max_points, xpixelmin, xpixelmax):
    """
    Converts the maximum desired points along the x axis into a step size in pixels
    :param max_points: int, user defined maximum desired points along the x axis
    :param xpixelmin: int, minimum pixel location along the x axis
    :param xpixelmax: int, maximum pixel location along the x axis
    :return step: int, distance between x values in the output
    """
    #step = length of x axis in pixels / max_points
    step = int(np.ceil((xpixelmax - xpixelmin) / max_points))
    return step
def test_get_step():
    """
    Tests the get_step function
    """
    step1 = get_step(19, 10, 200)
    step2 = get_step(18, 10, 200)
    step3 = get_step(16, 10, 200)
    # the step size * the number of points should be close to the length of the axis
    # step size is an integer
    for step in [step1, step2, step3]:
        assert isinstance(step, int), 'the step size is not an integer'
    # the length of the axis/ step size should be close to but less than the max points
    assert np.isclose(190 / step1, 19), 'length of axis/step size not ~< max points'
    assert ((190 / step2) < 18) and ((190 / step2) > 17), 'length of axis/step size not ~< max points'
    assert ((190 / step3) < 16) and ((190 / step3) > 15), 'length of axis/step size not ~< max points'
    return
test_get_step()
def get_x_scale(xcoordinatemin, xcoordinatemax, xpixelmin, xpixelmax):
    """
    Establishes the scaling from pixels to x axis units. Can also be used for the y scaling, with all 'x'
    in input variables replaced with their 'y' counterparts
    :param xcoordinatemin: number, minimum x (or y) value (from input image)
    :param xcoordinatemax: number, maximum x ( or y) value (from input image) 
    :param xpixelmin: int, minimum pixel location along the x (or y) axis
    :param xpixelmax: int, maximum pixel location along the x ( or y) axis
    :return x_scale: a value with units (pixels/coordinate unit) used to scale distances along the x (or y) axis
    """
    # the x pixel and x coordinate count up in the same direction
    pixel_range = xpixelmax - xpixelmin
    coordinate_range = xcoordinatemax - xcoordinatemin
    x_scale = pixel_range / coordinate_range
    return x_scale<jupyter_output><empty_output><jupyter_text>if the ypixelmin is the smaller of the ypixel values, then this is the same as the x scaling. just using get_scale for nowdef get_y_scale(ycoordinatemin, ycoordinatemax, ypixelmin, ypixelmax):     y pixel count down and y coordinate count up from origin    pixel_range = ypixelmin - ypixelmax    coordinate_range = ycoordinatemax - ycoordinatemin    y_scale = pixel_range / coordinate_range    return y_scale  pixels per coordinate<jupyter_code>def test_get_x_scale():
    """
    Tests the get_x_scale function
    """
    x_scale = get_x_scale(1, 5, 20, 250)
    # x_scale * coordinate range should equal pixel range
    assert np.isclose(x_scale * (5-1), (250-20)), 'the x scaling is incorrect'
    assert np.isclose(x_scale, 57.5), 'the x scaling is incorrect'
    
    x_scale = get_x_scale(-1, -5, 20, 250)
    assert np.isclose(x_scale * (-5+1), (250-20)), 'the x scaling is incorrect'
    assert np.isclose(x_scale, -57.5), 'the x scaling is incorrect'
    return
test_get_x_scale()<jupyter_output><empty_output><jupyter_text>def test_get_y_scale():    y_scale = get_y_scale(10, 25, 30, 280)     y_scale should be greater than zero    assert y_scale > 0, 'y scale should be greater than zero'     y_scale * coordinate range should equal pixel range    assert np.isclose(y_scale * (25-10), (280-30)), 'the y scaling is incorrect'    assert np.isclose(y_scale, 16.666666666), 'example y scale is incorrect'    returntest_get_y_scale()<jupyter_code>def pixel_to_coords(pixel_loc, axis_info_dict):
    """
    Converts a pixel location to coordinates on the xy axis
    :param pixel_loc: tuple, (x,y) location of a single pixel starting from the top left
    :param axis_info_dict: dict, result of the get_axis_info function
    :return (coord_x, coord_y): tuple with the x and y coordinates of the pixel in same units as input image
    """
    # pixel_loc is a tuple (x,y) of pixel location starting from top left
    x_pixel_loc = pixel_loc[0]
    coord_x = x_pixel_to_coords(x_pixel_loc, axis_info_dict)
    
    # get signed distance from pixel to origin in x and y(pixel units):
    pixel_distance_y = axis_info_dict['pixel_origin'][1] - pixel_loc[1]
    
    # pixels / (pixel/coord) = coord
    coord_y = pixel_distance_y / axis_info_dict['y_scale']
    return (coord_x, coord_y)
def x_pixel_to_coords(x_pixel_loc, axis_info_dict):
    """
    Converts the pixel location on the x axis to coordinates
    :param x_pixel_loc: int, distance in pixels of a single pixel from the left side of the image
    :param axis_info_dict: dict, result of the get_axis_info function
    :return coord_x: tuple with the x and y coordinates of the pixel in same units as input image
    """
    pixel_distance_x = x_pixel_loc - axis_info_dict['pixel_origin'][0]
    coord_x = pixel_distance_x / axis_info_dict['x_scale']
    return coord_x
def test_pixel_to_coords():
    """
    Tests the pixel_to_coords function (and by extension the x_pixel_to_coords function)
    """
    axis_info_dict1 = {'pixel_origin': (20, 100), 'y_scale': 5.3, 'x_scale': 20.5}
    axis_info_dict2 = {'pixel_origin': (20, 100), 'y_scale': -0.2, 'x_scale': 0.005}
    # the output coordinates should be within the coordinate ranges for each axis
    # given a scale and a location, test a few cases (+-0)
    coords1 = pixel_to_coords((20, 100), axis_info_dict1) # (0,0)
    coords2 = pixel_to_coords((20, 100), axis_info_dict2) # (0,0)
    coords3 = pixel_to_coords((55, 33), axis_info_dict1) # (1.707317, 12.641509)
    coords4 = pixel_to_coords((55, 33), axis_info_dict2) # (7000, -335)
    coords5 = pixel_to_coords((55, 105), axis_info_dict2) # (1.707317, 25)
    
    assert np.isclose(coords1[0], 0), 'pixel to coordinate conversion is incorrect'
    assert np.isclose(coords1[1], 0), 'pixel to coordinate conversion is incorrect'
    assert np.isclose(coords2[0], 0), 'pixel to coordinate conversion is incorrect'
    assert np.isclose(coords2[1], 0), 'pixel to coordinate conversion is incorrect'
    assert np.isclose(coords3[0], 1.707317), 'pixel to coordinate conversion is incorrect'
    assert np.isclose(coords3[1], 12.64150943), 'pixel to coordinate conversion is incorrect'
    assert np.isclose(coords4[0], 7000), 'pixel to coordinate conversion is incorrect'
    assert np.isclose(coords4[1], -335), 'pixel to coordinate conversion is incorrect'
    assert np.isclose(coords5[1], 25), 'pixel to coordinate conversion is incorrect'
    return
test_pixel_to_coords()<jupyter_output><empty_output><jupyter_text>dont really need this. accomplished in the unify x functionpretty much get the avg y pixel function for each unique x pixel valuedef clean_pixel_lst(pixel_lst):    return cleaned_pixel_lst<jupyter_code>def closest(lst, val):
    """
    Finds the closest number in a list to a value. If there is a tie, the first value in the list is selected.
    :param lst: list, a list of numbers
    :param val: any number
    :return: the value in list 'lst' that is closest to val
    """
    lst = np.asarray(lst)
    idx = (np.abs(lst - val)).argmin()
    return lst[idx]
def test_closest():
    """
    Tests the closest function
    """
    lst = [0, 2, 1, 3, 4, 5, 6]
    # val is equidistant to two values in list, first one in list is chosen
    assert closest(lst, 1.5) == 2, 'closest value is incorrect'
    assert closest(lst, 3.5) == 3, 'closest value is incorrect'
    # val is equal to one value in list
    assert closest(lst, 2) == 2, 'closest value is incorrect'
    # val is closer to one in particular
    assert closest(lst, 1.8) == 2, 'closest value is incorrect'
    return
test_closest()
# return x and y values of the unified list
def unify_x(pixel_lst, axis_info_dict):
    """
    Fits the y values in the input list to an evenly spaced list of x axis values
    :param pixel_lst: list, a list of tuples (x, y), each with the xy location of a pixel starting
                            from the top left of the image
    :param axis_info_dict: dict, result of the get_axis_info function
    :return unified_pixel_lst: a list of (x, y) pixels, similar to input list
    """
    # get the number of pixels between each desired coordinate pt based on scale
    # create bins of pixels and avg the values between them
    pixel_lst.sort()
    
    # step is a global variable based on user input
    step = axis_info_dict['step']
    x_pixels = [i[0] for i in pixel_lst]
    x_vals = list(range(min(x_pixels), max(x_pixels), step))

    
    # create dictionary of point and closest standard x val
    closest_dict = {}
    for point in pixel_lst:
        key = closest(x_vals, point[0])
        if key in closest_dict.keys():
            closest_dict[key].append(point)
        else:
            closest_dict[key] = [point]
    
    # iterate through keys to average all y values in each set
    for key in closest_dict:
        y_vals = [i[1] for i in closest_dict[key]]
        y_val = sum(y_vals) / len(y_vals)
        closest_dict[key] = y_val
    
    # for all the missing dict keys, make a line between nearest values and fill it in
    existing_keys = list(closest_dict.keys())
    existing_keys.sort()
    for x in x_vals:
        if x not in existing_keys:
            # find the index of first existing x greater than x
            i = 0
            while existing_keys[i] < x and i < (len(x_vals) + 1):
                i += 1
            
            x2 = existing_keys[i] # existing x just above missing x
            y2 = closest_dict[x2]
            x1 = existing_keys[i-1] # existing x just below missing x
            y1 = closest_dict[x1]
            
            # find line between bounds
            m = (y1 - y2) / (x1 - x2)
            b = (x1 * y2 - x2 * y1) / (x1 - x2)
            
            # solve for y of x
            y = m * x + b
            closest_dict[x] = y

        else:
            continue
    
    # turn dictionary into a list of tuples
    unified_pixel_lst = list(closest_dict.items())
    unified_pixel_lst.sort()
    
    
    return unified_pixel_lst
def test_unify_x():
    """
    Tests the unify_x function
    """
    axis_info_dict = {'step': 3}
    pixel_lst = [(20, 100), (20, 90), (21, 91), (22, 85), (22, 83), (23, 80), (24, 81), (24, 83), (25, 80), (29, 50), (29, 45), (30, 30), (30, 10)]
    pixels_y = [i[1] for i in pixel_lst]
    pixels_x = [i[0] for i in pixel_lst]
    unified_pixel_lst = unify_x(pixel_lst, axis_info_dict)
    unified_x = [i[0] for i in unified_pixel_lst]
    unified_y = [i[1] for i in unified_pixel_lst]
    x_spaces = np.diff(unified_x)
    # the x values in the list of tuples are all stepsize apart
    assert np.allclose(x_spaces, 3), 'the spacing between x values is incorrect'
    # the x values are unique
    assert len(set(unified_x)) == len(unified_x), 'the x values are not unique'
    # y values are all between the min and max pixel
    for y in unified_y:
        assert y <= max(pixels_y) and y >= min(pixels_y), 'unified y value is outside of expected bounds'
    # same for x values
    for x in unified_x:
        assert x <= max(pixels_x) and x >= min(pixels_x), 'unified x value is outside of expected bounds'
    return
test_unify_x()
def get_pixels_2d(pixel_array_2d):
    """
    Identifies pixels in a 2d slice of the pred_masks array belonging to a single instance
    :param pixel_array_2d: a 2D numpy array of shape (H,W) with only boolean values, where
                           H and W are the height and width of the input image in pixels
    :return pixel_lst: a list of (x, y) pixels belonging to the same instance
    """
    result = np.where(pixel_array_2d)
    pixel_lst = list(zip(result[1], result[0])) # gives the x then the y in the tuple
    return pixel_lst
def test_get_pixels_2d():
    """
    Tests the get_pixels_2d function
    """
    # create an array with a few trues and many falses, check positions in output match
    test_array = np.array([[ False,  True, False,  False, False, False, False],
       [ False, False,  False,  True, False, False, False],
       [ False,  False, False, False, False,  True, False],
       [True, False, False,  False, False, False, True],
       [False, False,  False, False, False, False, False]])
    pixel_lst = get_pixels_2d(test_array)
    expected_pixel_lst = [(1, 0), (3, 1), (5, 2), (0, 3), (6, 3)]
    assert set(expected_pixel_lst) == set(pixel_lst), 'unexpected values in 2d pixel list'
    return
test_get_pixels_2d()
# testing
zeros = np.random.rand(2,3,4)
np.random.choice([True, False], (2, 5, 7), p=[0.2, 0.8]);
def create_pixel_dict(pred_masks):
    """
    Creates a dictionary of pixels belonging to each instance
    :param pred_masks: a 3D tensor of shape (N, H,W) with only boolean values, where
                           H and W are the height and width of the input image in pixels
                           and N is the number of unique instances
    :return pixel_dict: a dict with keys of the form 'curve_N', where N starts at 1,
                        and values that are each lists of (x, y) pixels belonging to the same instance
    """
    # initialize dict
    pixel_dict = {}
    
    pixel_array_3d = np.array(pred_masks)
    for N in range(len(pixel_array_3d)):
        pixel_array_2d = pixel_array_3d[N]
        pixel_lst = get_pixels_2d(pixel_array_2d)
        
        # add the list of pixels for this N to the pixel dict
        ID = 'curve_' + str(N+1)
        pixel_dict[ID] = pixel_lst
    return pixel_dict
def test_create_pixel_dict():
    """
    Tests the create_pixel_dict function
    """
    pred_masks = np.array([[[False,  True, False,  True, False, False, False],
        [False, False, False, False, False,  True,  True],
        [False,  True, False, False, False, False, False],
        [False,  True,  True, False, False, False, False],
        [False, False,  True, False, False, False, False]],

       [[ True, False, False, False, False,  True,  True],
        [ True, False, False, False,  True, False, False],
        [False, False, False, False, False,  True, False],
        [False, False,  True, False,  True, False, False],
        [False, False, False, False,  True, False, False]]])
    pixel_dict = create_pixel_dict(pred_masks)
    # check that the keys are 'curve_' and then a unique number that matches the shape of pred_masks
    assert list(pixel_dict.keys()) == ['curve_1', 'curve_2'], 'incorrect keys in pixel_dict'
    # the values are not the same for each one (not repeated)
    assert set(pixel_dict['curve_1']) != set(pixel_dict['curve_2']), 'curves in pixel_dict are not unique'
    return
test_create_pixel_dict()
# create coordinate dictionary to add to the output dict
def create_coordinate_dict(pixel_dict, axis_info_dict):
    """
    Creates a dictionary of coordinate locations of pixels belonging to each instance
    :param pixel_dict: a dict with keys of the form 'curve_N', where N starts at 1,
                        and values that are each lists of (x, y) pixels belonging to the same instance
    :param axis_info_dict: dict, result of the get_axis_info function
    :return coordinate_dict: a dict with keys of the form 'curve_N', where N starts at 1,
                        and values that are each lists of (x, y) positions belonging to the same instance
    """
    # initialize dict
    coordinate_dict = {}
    
    for ID in pixel_dict.keys():
        pixel_lst = pixel_dict[ID]
        
        # get unified x axis:
        # add an if statement to handle user specifiying either step size (in coordinates) or number of points
        unified_pixel_lst = unify_x(pixel_lst, axis_info_dict)
        
        # convert pixels to coordinates
        coordinate_lst = []
        for pixel_loc in unified_pixel_lst:
            coordinate_lst.append(pixel_to_coords(pixel_loc, axis_info_dict))
        
        # add the list of coordinates for this ID to the coordinate dict
        coordinate_dict[str(ID)] = coordinate_lst
    return coordinate_dict
def test_create_coordinate_dict():
    """
    Tests the create_coordinate_dict function
    """
    pred_masks = np.array([[[False,  True, False,  True, False, False, False],
        [False, False, False, False, False,  True,  True],
        [False,  True, False, False, False, False, False],
        [False,  True,  True, False, False, False, False],
        [False, False,  True, False, False, False, False]],

       [[ True, False, False, False, False,  True,  True],
        [ True, False, False, False,  True, False, False],
        [False, False, False, False, False,  True, False],
        [False, False,  True, False,  True, False, False],
        [False, False, False, False,  True, False, False]]])
    pixel_dict = create_pixel_dict(pred_masks)
    axis_info_dict = {'pixel_origin': (0, 4), 'y_scale': 5.3, 'x_scale': 20.5, 'step': 1}
    coordinate_dict = create_coordinate_dict(pixel_dict, axis_info_dict)
    # the keys of the coordinate dict are the same as that of the pixel dict
    assert set(coordinate_dict.keys()) == set(pixel_dict.keys()), 'keys of coordinate and pixel dicts dont match'
    return
test_create_coordinate_dict()
def get_start_end(pixel_dict, axis_info_dict):
    """
    Creates a dictionary of coordinate locations of first and last points for each instance
    :param pixel_dict: a dict with keys of the form 'curve_N', where N starts at 1,
                        and values that are each lists of (x, y) pixels belonging to the same instance
    :param axis_info_dict: dict, result of the get_axis_info function
    :return pixel_dict: a dict with keys of the form 'curve_N', where N starts at 1,
                        and values that are tupeles: (x_start, x_end) with first and last x values
                        of points for each instance
    """
    # initialize dict
    start_end_dict = {}
    
    for ID in pixel_dict.keys():
        pixel_lst = pixel_dict[ID]
        
        # get start and end, assumes x is in the first position
        start = x_pixel_to_coords(min(pixel_lst)[0], axis_info_dict)
        end = x_pixel_to_coords(max(pixel_lst)[0], axis_info_dict)
        # add the start and end tuple for this ID to the pixel dict
        start_end_dict[str(ID)] = (start, end)
    return start_end_dict
def test_get_start_end():
    """
    Tests the get_start_end function
    """
    pred_masks = np.array([[[False,  True, False,  True, False, False, False],
        [False, False, False, False, False,  True,  True],
        [False,  True, False, False, False, False, False],
        [False,  True,  True, False, False, False, False],
        [False, False,  True, False, False, False, False]],

       [[ True, False, False, False, False,  True,  True],
        [ True, False, False, False,  True, False, False],
        [False, False, False, False, False,  True, False],
        [False, False,  True, False,  True, False, False],
        [False, False, False, False,  True, False, False]]])
    pixel_dict = create_pixel_dict(pred_masks)
    axis_info_dict = {'pixel_origin': (0, 4), 'y_scale': 5.3, 'x_scale': 20.5, 'step': 1}
    start_end_dict = get_start_end(pixel_dict, axis_info_dict)
    # the keys of the output dict are the same as the input
    assert set(start_end_dict.keys()) == set(pixel_dict.keys()), 'keys of start_end and pixel dicts dont match'
    for key in start_end_dict.keys():
        # the end is larger than (or equal to) the start
        assert start_end_dict[key][1] >= start_end_dict[key][0], 'starting x value greater than end value'
        # the start and end are between the coordinate ranges (hard to test without using all other functions)
    return
test_get_start_end()
def create_output_dict(pred_masks, axis_info_dict):
    """
    Creates a dictionary of the output info for each instance (coordinates, start/end values, and units)
    :param pred_masks: a 3D tensor of shape (N, H,W) with only boolean values, where
                           H and W are the height and width of the input image in pixels
                           and N is the number of unique instances
    :param axis_info_dict: dict, result of the get_axis_info function
    :return output_dict: a dictionary with keys 'coordinates' and 'start_end', which each refer to the dictionaries
                        created by create_coordinate_dict and get_start_end, and a key 'units' with value of type
                        tuple containing the x and y coordinate units
    """
    pixel_dict = create_pixel_dict(pred_masks)
    output_dict = {}
    output_dict['coordinates'] = create_coordinate_dict(pixel_dict, axis_info_dict)
    output_dict['start_end'] = get_start_end(pixel_dict, axis_info_dict)
    output_dict['units'] = axis_info_dict['units']
    return output_dict
def write_results_to_excel(output_dict, filename):
    """
    Saves the results stored in the output dictionary in an excel file with name 'filename', and one sheet per instance
    and one sheet with start and end locations on the x axis for each instance
    :param output_dict: a dictionary with keys 'coordinates' and 'start_end', which each refer to the dictionaries
                        created by create_coordinate_dict and get_start_end, and a key 'units' with value of type
                        tuple containing the x and y coordinate units
    :param filename: a string with the desired name of the output excel file
    """
    excel_filename = str(filename) + '.xlsx'
    writer = pd.ExcelWriter(excel_filename, engine='xlsxwriter')
    x_units = output_dict['units'][0]
    y_units = output_dict['units'][1]
    
    # a summary of the start and end of the x axis for each ID
    starts = []
    ends = []
    ids = []
    for ID in output_dict['start_end'].keys():
        ids.append(ID)
        start = output_dict['start_end'][ID][0]
        end = output_dict['start_end'][ID][1]
        starts.append(start)
        ends.append(end)
    df = pd.DataFrame([ids, starts, ends],  columns=['ID', 'x start '+str(x_units), 'x end '+str(x_units)])
    df.to_excel(writer, sheet_name='starts_ends')
    
    # the actual data in xy form, one ID per sheet
    for ID in output_dict['coordinates'].keys():
        x = output_dict['coordinates'][ID][0]
        y = output_dict['coordinates'][ID][1]
        column_titles = ['x, ' + str(x_units), 'y, ' + str(y_units)]
        df = pd.DataFrame([x, y], columns=column_titles)
        df.to_excel(writer, sheet_name=str(ID))
    writer.save()
    return
def datayoink_to_csv(detect_output, axis_info_dict, filename='image'):
    """
    Converts the detectron2 prediction output to a list of coordinate values and saves them in
    an excel file (.xlsx) with a name based on filename and one sheet per instance and one sheet
    with start and end locations on the x axis for each instance.
    :param detect_output: the output of detectron2 with an 'instances' field containing information about
                          predicted masks
    :param axis_info_dict: dict, result of the get_axis_info function
    :param filename: the base filename for the output excel file
    """
    n_images = len(detect_output)
    if n_images > 1:
        for image in range(n_images):
            pred_masks = detect_output[image]['instances'].pred_masks
            output_dict = create_output_dict(pred_masks, axis_info_dict)
            filename_N = filename + '_' + str(image)
            write_results_to_excel(output_dict, filename_N)
    elif n_images == 1:
        pred_masks = detect_output['instances'].pred_masks
        output_dict = create_output_dict(pred_masks, axis_info_dict)
        filename_N = filename + '_' + str(1)
        write_results_to_excel(output_dict, filename_N)
    return<jupyter_output><empty_output><jupyter_text>In order to use these functions, code looks like this:<jupyter_code># detect_output is the output of detectron2 - list of instances objects
axis_info_dict = get_axis_info(xcoordinatemin, xcoordinatemax, xpixelmin, xpixelmax, ycoordinatemin, ycoordinatemax, ypixelmin, ypixelmax, max_points, units)
datayoink_to_csv(detect_output, axis_info_dict)<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.08662579100954129, 'fraction_numerical': 0.016164464408669137, 'mean_word_length': 3.1901360076933645, 'pattern_counts': {'":': 0, '<': 35, '<?xml version=': 0, '>': 32, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 1, 'word_list_counts': {'cursed_substrings.json': 7, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '6', 'dir': 'jupyter-structured-clean-dedup', 'id': '623731', 'license': 'MIT', 'n_tokens_mistral': 9492, 'n_tokens_neox': 9155, 'n_words': 3602, 'path': 'docs/dev/ChristinaNotebook.ipynb', 'repo_name': 'DataYoink/DataYoink', 'size': '30499'}