def _do(self, action, data, clear_future=True):
        self.history.append(dict(action=action, data=deepcopy(data)))
        if clear_future:
            del self.future[:]

        if action == self.ACTION_LOAD_ANNOTATION:
            filename = data['filename']
            assert 'ori_filename' in data
            self.facade = fsi.Annotation(filename, root=self.root)
        elif action == self.ACTION_SET_OBJECT:
            index = data['index']
            obj = data['value']
            assert 'ori_value' in data
            self.facade.annotation.object[index] = deepcopy(obj)
            self.active_index = index
            self._update_polygon(index)
        elif action == self.ACTION_INSERT_OBJECT:
            index = data['index']
            obj = data['value']
            self.facade.annotation.object.insert(index, obj)
            self.poly_selector.insert_polygon(index, self._make_polygon(obj))
            self.active_index = index
            self._update_polygon(index)
def run():
    from argparse import ArgumentParser

    # Parse arguments -- read the help below or run with '-h' for help.
    p = ArgumentParser(description="Interactive tool to visualize, and edit, annotations")
    p.add_argument('--input', '-i', type=str, nargs='*',
                   help="The files to edit. If none are specified, we "
                        "move through them all from oldest to newest")
    p.add_argument('--root', type=str, default=C.DATA_ROOT,
                   help="The root of the dataset.")
    p.add_argument('--folder', type=str, default='merged',
                   help="The folder (group of annotations) to work with")
    p.add_argument('--username', '-u', type=str,
                   help='name of the person editing annotations')
    p.add_argument('--auto-advance', type=bool,
                   help="Automatically advance to the next file when no more selectable items found")
    args = p.parse_args()

    # This code is designed to work inside a Jupyter notebook
    # so we use a matplotlib Figure as our GUI
    fig = plt.figure(figsize=(9, 6), frameon=False)

    # Prevent the toolbar from handling some key press events (e.g. s)
    fig.canvas.mpl_disconnect(fig.canvas.manager.key_press_handler_id)

    # Create the label selection widget in a plot at the bottom
    labels_selector_ax = plt.axes([0., 0., 1., 0.09], xticks=[], yticks=[])
    ax = plt.axes([0., 0.09, 1., 0.91], xticks=[], yticks=[])
    ax.axis('equal')

    # Choose the list of annotation XMLs to display
    if args.input:
        annotations = args.input
    else:
        annotations = glob(os.path.join(args.root, 'Annotations', args.folder, '*.xml'))
        annotations = sorted(annotations, key=lambda x: os.stat(x).st_mtime)

    facade = fsi.Annotation(annotation=annotations[0], root=args.root)
    ae = AnnotationEditor(ax,
                          facade=facade,
                          root=args.root,
                          username=args.username,
                          folder=args.folder,
                          annotations=annotations
                          )
    ae.set_label_box(ae.create_label_box(labels_selector_ax))


    # TODO: Can this be made part of AnnotationEditor?
    def on_resize(event: ResizeEvent):
        h = ae.get_label_box().get_preferred_height()
        box = ae.ax.get_position()
        new_label_box = Bbox.from_bounds(box.xmin, 0, box.width, h)
        ae.get_label_box().ax.set_position(new_label_box)

        # Resize the annotation editor itself
        box = ae.ax.get_position()
        new_editor_box = Bbox.from_bounds(box.xmin,
                                          h,
                                          box.width,
                                          1-h)
        ae.ax.set_position(new_editor_box)

    resize_cid = fig.canvas.mpl_connect('resize_event', on_resize)
    plt.show()
    return ae
    def __init__(self, ax, facade=None,
                 root=C.DATA_ROOT,
                 on_select=None,
                 on_hover=None,
                 fill_alpha=0.4,
                 expand_by=2,
                 username='******',
                 annotations=None,
                 folder='merged'):
        """
        Parameters:
        - *ax*: The axis to render onto, 
        - *facade*: A facade object to edit. 
        - *labels_selector*: An (optional) LabelSelector widget to 
          allow the labels of annotations to be changed. 
        """
        super().__init__()

        if folder is None and facade is not None:
            folder = facade.annotation.folder

        if annotations is None:
            annotations = glob(os.path.join(root, 'Annotations', folder, '*.xml'))

        if facade is None:
            facade = fsi.Annotation(annotation=annotations[0])

        self.ax = ax
        self.canvas = ax.figure.canvas
        self._facade = None
        self.labels_selector = None
        self.cids = []
        self.fill_alpha = fill_alpha
        self.username = username

        # Keep a list of all of the annotated images so we can load the 'next' file 
        # automatically
        self._annotation_list = copy(annotations)
        self._annotation_index = -1
        if facade.annotation_path in annotations:
            self._annotation_index = annotations.index(facade.annotation_path)

        # The parameters used to filter objects
        self.last_filter = None

        self._label_box = None

        self.axis_image = None

        # Callbacks
        self.on_hover = on_hover
        self.on_select = on_select

        ##
        # Not sure that ths widget should be aware of the root -- should probably be passed
        # the names and colors as parameters....
        self.root = root
        self.names = np.loadtxt(f"{self.root}/names.txt", dtype='str')
        self.colors = np.loadtxt(f"{self.root}/colors.txt", delimiter=',', dtype=np.uint8) / 255.
        self.z_orders = {i: name for (name, i) in enumerate(self.names)}
        ##

        # I have a (hopefully) generic polygon selector that allows me to interact with the
        # polygons
        self.poly_selector = PolygonSelector(self.ax,
                                             onactivate=self._on_poly_activate,
                                             onhover=self._on_poly_hover,
                                             expandby=expand_by)

        # Create a text object to render information about the active object
        # To make the text visible over any background, give it a shadow
        self.label = self.ax.text(0, 0, 'unknown', visible=False,
                                  horizontalalignment='center',
                                  verticalalignment='center')
        self.label.set_path_effects([patheffects.Normal(),
                                     patheffects.SimplePatchShadow(offset=(1, -1), shadow_rgbFace=(1, 1, 0))])

        # Create polygon editor when the user pressed a certain key (e.g. enter)
        self.poly_editor = None
        self._poly_editor_index = -1  # Save the index of the object we last started editing

        # Set our facade using the property setter to trigger updates
        self.facade = facade

        # Keep track of changes made with this tool
        self.history = []
        self.future = []

        # Initialize properties to pass stupid code inspection
        if False and "This is dumb":
            self.active_index = self.get_active_index()
            self.active_object = self.get_active_object()
            self.hover_index = self.get_hover_index()
            self.hover_object = self.get_hover_object()

        self.connect_events()