Exemplo n.º 1
0
class ImageLabeler(wx.App):
    '''
    The Main Application Class
    '''
    def __init__(self, starting_image=None, image_dir=None, conf_dir=None):

        wx.App.__init__(self)

        self.labeler_dir = labeler.__path__[0]
        self.starting_image = starting_image
        self.image_dir = image_dir

        # Mode that the labeler should use: single or batch
        self.labeler_mode = "single"

        # Frame that will contain image and grid
        self.frame = wx.Frame(None, title='Image Display')

        #What is the display of the monitor
        self.monitor_size = wx.GetDisplaySize()

        # Where does our code live
        self.bin_dir = os.path.dirname(os.path.abspath(__file__))
        self.CanvasPanel = wx.Panel(self.frame,
                                    style=wx.BORDER_SUNKEN | wx.CLOSE_BOX
                                    | wx.SYSTEM_MENU | wx.CAPTION)
        self.CanvasPanel.SetBackgroundColour("dark gray")

        self.frame.Bind(wx.EVT_CLOSE, self.OnFileExit)

        # Intitialise the matplotlib figure
        self.figure = Figure()

        # Create an axes, turn off the labels and add them to the figure
        self.axes = plt.Axes(self.figure, [0, 0, 1, 1])
        self.axes.set_axis_off()
        self.figure.add_axes(self.axes)

        # Add the figure to the wxFigureCanvas
        self.canvas = FigureCanvas(self.CanvasPanel, -1, self.figure)
        self.toolbar = NavigationToolbar(self.canvas)
        self.toolbar.Realize()
        self.toolbar.Hide()

        # What mode is the cursor in: bb,toolbar
        self.cursor_mode = "nobb"

        # Connect the mouse events to their relevant callbacks
        self.canvas.mpl_connect('button_press_event', self.OnLeftDown)
        self.canvas.mpl_connect('button_release_event', self.OnLeftUp)
        self.canvas.mpl_connect('motion_notify_event', self.OnMotion)
        self.canvas.mpl_connect('key_press_event', self.OnKeyDown)

        # Lock to stop the motion event from behaving badly when the mouse isn't pressed
        self.frame.pressed = False

        # Setting up the file menu
        filemenu = wx.Menu()
        menuAbout = filemenu.Append(wx.ID_ABOUT, "&About",
                                    "Information About This Program")
        menuOpenGrid = filemenu.Append(wx.ID_FILE, "&Open Grid",
                                       "Open File Containing Bounding Boxes")
        menuOpenImage = filemenu.Append(wx.ID_OPEN, "&Open Image",
                                        "Open Image File")
        menuSaveGrid = filemenu.Append(wx.ID_SAVE, "&Save Grid",
                                       "Save Bounding Boxes to CSV File")
        menuSaveImage = filemenu.Append(wx.ID_SAVEAS, "&Save Image",
                                        "Save Image")
        menuExit = filemenu.Append(wx.ID_EXIT, "&Exit", "Exit Image Labeler")

        # Setting up the models menu
        configmenu = wx.Menu()
        menuConfigModel = configmenu.Append(wx.ID_ABOUT, "&Models",
                                            "Configure Custom Models")

        # Creating the menubar.
        menuBar = wx.MenuBar()
        menuBar.Append(filemenu,
                       "&File")  # Adding the "filemenu" to the MenuBar
        menuBar.Append(configmenu, "&Config")
        self.frame.SetMenuBar(
            menuBar)  # Adding the MenuBar to the Frame content.

        # Set events for menuBar things
        self.frame.Bind(wx.EVT_MENU, self.OnFileAbout, menuAbout)
        self.frame.Bind(wx.EVT_MENU, self.OnFileOpen, menuOpenImage)
        self.frame.Bind(wx.EVT_MENU, self.OnImportGrid, menuOpenGrid)
        self.frame.Bind(wx.EVT_MENU, self.OnFileExit, menuExit)
        self.frame.Bind(wx.EVT_MENU, self.OnSaveGrid, menuSaveGrid)
        self.frame.Bind(wx.EVT_MENU, self.OnSaveImage, menuSaveImage)
        self.frame.Bind(wx.EVT_MENU, self.OnConfigModel, menuConfigModel)

        #Keep track of how many images you have displayed
        self.imagecounter = 0

        #Define where this program should find images
        if self.image_dir == None:
            self.image_dir = os.getcwd()

        # Get list of image files in the image_dir
        self.images_obj = get_list_files(self.image_dir)

        if self.images_obj != None:
            self.labeler_mode = "batch"
        else:
            print(
                "Warning: no images found in image path or current directory.")

        #What image will we be starting on
        if self.starting_image != None:  #use the one specified on the command line.
            self.imagepath = self.starting_image
        elif self.labeler_mode == "single":  #Just use the default application image
            print("Info: Using application default image:",
                  self.labeler_dir + "/image.jpg")
            self.imagepath = self.labeler_dir + "/image.jpg"

        elif self.labeler_mode == "batch":
            print(
                "Info: Starting labeler in batch mode, multiple images detected."
            )
            #in batch mode start with the first image
            self.imagepath = self.images_obj[0]['path']

        # Create Panel to display Bounding Box Coordinates
        self.BBPanel = wx.Panel(self.frame,
                                style=wx.BORDER_SUNKEN | wx.CLOSE_BOX
                                | wx.SYSTEM_MENU | wx.CAPTION)
        self.BBPanel.SetBackgroundColour("dark gray")

        # Create the Grid to Hold the Coordinates
        self.BBGrid = gridlib.Grid(self.BBPanel)
        self.BBGrid.CreateGrid(100, 5)
        self.BBGrid.SetColLabelValue(0, "X1")
        self.BBGrid.SetColLabelValue(1, "Y1")
        self.BBGrid.SetColLabelValue(2, "X2")
        self.BBGrid.SetColLabelValue(3, "Y2")
        self.BBGrid.SetColLabelValue(4, "Label")

        BBsizer = wx.BoxSizer(wx.VERTICAL)
        BBsizer.Add(self.BBGrid, 1, wx.EXPAND | wx.ALL)

        # Do Some things when the mouse clicks inside of the Grid
        self.BBGrid.Bind(wx.grid.EVT_GRID_SELECT_CELL, self.OnGridLeft)
        # Do some things when delete key is pressed inside of the grid
        self.BBGrid.Bind(wx.EVT_KEY_DOWN, self.OnGridDelete)
        # Get rid of row labels
        self.BBGrid.SetRowLabelSize(0)
        # Set all columns to read only, except the Label column
        set_grid_edit(self)
        #self.BBGrid.EnableEditing(False)

        self.BBPanel.SetSizer(BBsizer)

        # Create Panel for Image Controls
        self.ControlPanel = wx.Panel(self.frame,
                                     style=wx.BORDER_SUNKEN | wx.CLOSE_BOX
                                     | wx.SYSTEM_MENU | wx.CAPTION)
        self.ControlBox = wx.BoxSizer(wx.VERTICAL)
        self.ControlBox.Add(self.ControlPanel)
        self.ControlPanel.SetBackgroundColour("dark gray")

        # Create Buttons to help label image
        self.button_list = []

        self.selected_button = "HOME"

        self.sibut = wx.Button(self.ControlPanel,
                               -1,
                               size=(50, 50),
                               pos=(5, 5),
                               name="zoom")
        zoom_img = wx.Image(self.labeler_dir + '/icons/zoom.png',
                            wx.BITMAP_TYPE_ANY)
        zoom_img = zoom_img.Scale(20, 20)
        self.sibut.SetBitmap(wx.Bitmap(zoom_img))
        self.sibut.Bind(wx.EVT_BUTTON, self.zoom)
        self.button_list.append(self.sibut)

        self.hmbut = wx.Button(self.ControlPanel,
                               -1,
                               size=(50, 50),
                               pos=(60, 5),
                               name="home")
        home_img = wx.Image(self.labeler_dir + '/icons/home.png',
                            wx.BITMAP_TYPE_ANY)
        home_img = home_img.Scale(20, 20)
        self.hmbut.SetBitmap(wx.Bitmap(home_img))
        self.hmbut.Bind(wx.EVT_BUTTON, self.home)
        self.button_list.append(self.hmbut)

        self.hibut = wx.Button(self.ControlPanel,
                               -1,
                               size=(50, 50),
                               pos=(115, 5),
                               name="pan")
        pan_img = wx.Image(self.labeler_dir + '/icons/pan.png',
                           wx.BITMAP_TYPE_ANY)
        pan_img = pan_img.Scale(20, 20)
        self.hibut.SetBitmap(wx.Bitmap(pan_img))
        self.hibut.Bind(wx.EVT_BUTTON, self.pan)
        self.button_list.append(self.hibut)

        self.plotbut = wx.Button(self.ControlPanel,
                                 -1,
                                 size=(50, 50),
                                 pos=(170, 5),
                                 name="bbox")
        box_img = wx.Image(self.labeler_dir + '/icons/bbox.png',
                           wx.BITMAP_TYPE_ANY)
        box_img = box_img.Scale(20, 20)
        self.plotbut.SetBitmap(wx.Bitmap(box_img))
        self.plotbut.Bind(wx.EVT_BUTTON, self.plot)
        self.button_list.append(self.plotbut)

        # Create Panel Controls for Dataset movement
        self.BatchPanel = wx.Panel(self.frame,
                                   style=wx.BORDER_SUNKEN | wx.CLOSE_BOX
                                   | wx.SYSTEM_MENU | wx.CAPTION)
        self.BatchBox = wx.BoxSizer(wx.VERTICAL)
        self.BatchBox.Add(self.BatchPanel)
        self.BatchPanel.SetBackgroundColour("dark gray")

        self.prevbut = wx.Button(self.BatchPanel,
                                 -1,
                                 size=(50, 50),
                                 pos=(7, 5))
        box_img = wx.Image(self.labeler_dir + '/icons/left_arrow.png',
                           wx.BITMAP_TYPE_ANY)
        box_img = box_img.Scale(20, 20)
        self.prevbut.SetBitmap(wx.Bitmap(box_img))
        self.prevbut.Bind(wx.EVT_BUTTON, self.prev)

        self.nextbut = wx.Button(self.BatchPanel,
                                 -1,
                                 size=(50, 50),
                                 pos=(60, 5))
        box_img = wx.Image(self.labeler_dir + '/icons/right_arrow.png',
                           wx.BITMAP_TYPE_ANY)
        box_img = box_img.Scale(20, 20)
        self.nextbut.SetBitmap(wx.Bitmap(box_img))
        self.nextbut.Bind(wx.EVT_BUTTON, self.next)

        if self.labeler_mode == "single":  #Disable button in single mode
            self.nextbut.Disable()
            self.prevbut.Disable()

        # Create Panel for Grid controls
        self.GridControlPanel = wx.Panel(self.frame,
                                         style=wx.BORDER_SUNKEN | wx.CLOSE_BOX
                                         | wx.SYSTEM_MENU | wx.CAPTION)
        self.GridControlBox = wx.BoxSizer(wx.VERTICAL)
        self.GridControlBox.Add(self.GridControlPanel)
        self.GridControlPanel.SetBackgroundColour("dark gray")

        # Button to import csv file with bounding boxes
        self.imbut = wx.Button(self.GridControlPanel,
                               -1,
                               size=(50, 50),
                               pos=(5, 5))
        imp_img = wx.Image(self.labeler_dir + '/icons/import.png',
                           wx.BITMAP_TYPE_ANY)
        imp_img = imp_img.Scale(20, 20)
        self.imbut.SetBitmap(wx.Bitmap(imp_img))
        self.imbut.Bind(wx.EVT_BUTTON, self.OnImportGrid)

        # Button to save grid to csv file
        self.grsavebut = wx.Button(self.GridControlPanel,
                                   -1,
                                   size=(50, 50),
                                   pos=(60, 5))
        save_img = wx.Image(self.labeler_dir + '/icons/filesave.png',
                            wx.BITMAP_TYPE_ANY)
        save_img = save_img.Scale(20, 20)
        self.grsavebut.SetBitmap(wx.Bitmap(save_img))
        self.grsavebut.Bind(wx.EVT_BUTTON, self.save_grid)

        # Button to delete grid and bounding boxes
        self.grdelbut = wx.Button(self.GridControlPanel,
                                  -1,
                                  size=(50, 50),
                                  pos=(115, 5))
        del_img = wx.Image(self.labeler_dir + '/icons/delete_all.png',
                           wx.BITMAP_TYPE_ANY)
        del_img = del_img.Scale(20, 20)
        self.grdelbut.SetBitmap(wx.Bitmap(del_img))
        self.grdelbut.Bind(wx.EVT_BUTTON, self.clear_bb)

        # Are we moving the rectangle or creating a new one
        self.is_moving = False

        # Hold list of rectangle objects
        self.rect_obj_list = []
        self.rect_labels = []

        # A Statusbar i the bottom of the window
        self.frame.CreateStatusBar()
        self.frame.Show(True)

        self.FirstImage()

        # Frame for image transformations
        self.TransFrame = TransFrame(None, self)

        # Frame for image segmentation
        self.SegFrame = SegmentFrame(None, self)

        # Frame for configuring models
        self.ModelFrame = None

    def OnGridLeft(self, event):
        '''
            Action taken when left click happens on grid
        '''
        row = event.GetRow()
        self.selected_rect = row
        self.change_rect_color()
        highlight_row(self, row)

    def OnGridDelete(self, event):
        '''
            Delete row in grid
        '''
        row = self.BBGrid.GetGridCursorRow()
        col = self.BBGrid.GetGridCursorCol()

        if event.GetKeyCode() == wx.WXK_DELETE:
            self.OnDelete()

    def toggle_cursor_mode(self, button, name):
        '''  
            Change cursor_mode between bb and rest toolbar 
            Hides active button
        '''

        for butt in self.button_list:
            if button == butt:
                if name == "home":  # This one doesn't need to be hidden
                    next
                else:
                    butt.Hide()
            else:
                butt.Show()

    def zoom(self, event):
        '''
            Use Matplotlibs zoom tool
        '''
        self.cursor_mode = "nobb"

        self.toggle_cursor_mode(self.sibut, "zoom")
        self.toolbar.zoom()
        # Toggle off other buttons
        if self.selected_button == 'HOME':
            self.toolbar.home()
        elif self.selected_button == 'PAN':
            self.toolbar.pan()
        elif self.selected_button == 'PLOT`':
            self.toolbar.plot()

        self.selected_button = "ZOOM"

    def toggle_off_mode(self):
        ''' 
           Turn off any button that is selected
        '''

        # Toggle off other buttons
        if self.selected_button == 'ZOOM':
            self.toolbar.zoom()
        elif self.selected_button == 'PAN':
            self.toolbar.pan()
        elif self.selected_button == 'PLOT`':
            self.toolbar.plot()
        elif self.selected_button == 'HOME':
            self.toolbar.home()

    def home(self, event):
        '''
            Return view back to original position
        '''
        self.cursor_mode = "nobb"
        self.toggle_cursor_mode(self.hmbut, "home")

        self.toolbar.home()

        # Reset axes so they don't get messed up when zooming
        self.axes.set_xbound(0, self.image_shape[1])
        self.axes.set_ybound(0, self.image_shape[0])

        self.toggle_off_mode()
        self.selected_button = "HOME"

    def pan(self, event):
        '''
            Uses Matplotlibs pan tool
        '''
        self.cursor_mode = "nobb"
        self.toggle_cursor_mode(self.hibut, "pan")
        self.toolbar.pan()

        self.toggle_off_mode()
        self.selected_button = "PAN"

    def plot(self, event):
        '''
            Draw a rectangle on the canvas
        '''
        self.cursor_mode = "bb"
        self.toggle_cursor_mode(self.plotbut, "plot")
        # Set Crosshair as mouse cursor.

        self.toggle_off_mode()
        self.selected_button = "PLOT"

    def next(self, event):
        '''
            Move to next image
        '''
        self.cursor_mode = "nobb"
        self.toggle_cursor_mode(self.nextbut, "next")

        # Find Out which image you are currently on in self.images_obj
        i = 0
        for obj in self.images_obj:
            if obj['path'] == self.imagepath:
                self.cur_obj_num = i
                break
            i += 1

        if self.cur_obj_num + 1 == len(self.images_obj):
            self.user_error("There are no more images left to work on.")
            self.cur_obj_num = 0
            return 0

        self.cur_obj_num += 1

        # Now display the new image
        self.imagepath = self.images_obj[self.cur_obj_num]['path']
        self.NewImage()

    def prev(self, event):
        '''
            Move to previous image
        '''
        self.cursor_mode = "nobb"
        self.toggle_cursor_mode(self.prevbut, "prev")
        # Find Out which image you are currently on in self.images_obj
        i = 0
        for obj in self.images_obj:
            if obj['path'] == self.imagepath:
                self.cur_obj_num = i
                break
            i += 1

        if self.cur_obj_num == 0:
            self.user_error("You are on the first image.")
            return 0

        self.cur_obj_num -= 1

        # Now display the new image
        self.imagepath = self.images_obj[self.cur_obj_num]['path']
        self.NewImage()

    def OnFileExit(self, event):
        '''
            Close every frame in the app.
        '''
        self.frame.Destroy()
        self.TransFrame.Close()
        self.SegFrame.Close()
        if self.ModelFrame != None:
            self.ModelFrame.Close()

    def OnImportGrid(self, event):
        '''
            Choose CSV file with coordinates to import.
        '''
        with wx.FileDialog(self.frame,
                           "Import CSV File",
                           wildcard="*.csv",
                           style=wx.FD_OPEN) as fileDialog:
            if fileDialog.ShowModal() == wx.ID_CANCEL:
                return  # the user changed their mind

            # Get Pathname
            pathname = fileDialog.GetPath()

            # Read file into list
            new_coords = import_grid_csv(self, pathname)

            # Loop through coordinates and draw rectangle
            for coord in new_coords:
                self.draw_rect(coord)

            self.canvas.draw()

    def OnConfigModel(self, event):
        self.ModelFrame = ModelFrame(None, self)

    def draw_rect(self, rect):
        if len(rect) > 4:
            x0, y0, x1, y1, label = rect
            self.rect_labels.append(label)
        else:
            x0, y0, x1, y1 = rect
            self.rect_labels.append("")

        x0 = int(x0)
        y0 = int(y0)
        x1 = int(x1)
        y1 = int(y1)

        if x0 < 0:
            x0 = 0
        if x1 < 0:
            x1 = 0
        if y0 < 0:
            y0 = 0
        if y1 < 0:
            y1 = 0

        max_height = self.image_shape[0]
        max_width = self.image_shape[1]

        if x0 > max_width:
            x0 = max_width
        if x1 > max_width:
            x1 = max_width
        if y0 > max_height:
            y0 = max_height
        if y1 > max_height:
            y1 = max_height

        width = int(x1) - int(x0)
        height = int(y1) - int(y0)

        self.rect = Rectangle((int(x0), int(y0)),
                              width,
                              height,
                              facecolor='None',
                              edgecolor='green',
                              linewidth='2')
        self.axes.add_patch(self.rect)
        self.rect_obj_list.append(self.rect)

    def OnSaveGrid(self, event):
        '''
            Choose filename to save a CSV with grid with the coordinates.
        '''
        with wx.FileDialog(self.frame,
                           "Save CSV file",
                           wildcard="*.csv",
                           style=wx.FD_SAVE
                           | wx.FD_OVERWRITE_PROMPT) as fileDialog:
            if fileDialog.ShowModal() == wx.ID_CANCEL:
                return  # the user changed their mind

            # Get Pathname
            pathname = fileDialog.GetPath()

            # Write to that file
            write_grid_csv(self, pathname)

    def OnSaveImage(self, event):
        '''
            Choose filename to save the image.
        '''
        with wx.FileDialog(self.frame,
                           "Save an image file",
                           style=wx.FD_SAVE
                           | wx.FD_OVERWRITE_PROMPT) as fileDialog:
            if fileDialog.ShowModal() == wx.ID_CANCEL:
                return  # the user changed their mind

            # Get Pathname
            imagepathname = fileDialog.GetPath()

            # Write to that file
            write_image(self, imagepathname)

    def OnFileOpen(self, event):
        ''' 
            Open Dialog so user can select a new image. 
        '''
        # Get the file path you wan to open.
        with wx.FileDialog(self.frame,
                           "Open Image File",
                           wildcard="Image Files *.png|*.jpg",
                           style=wx.FD_OPEN
                           | wx.FD_FILE_MUST_EXIST) as fileDialog:

            if fileDialog.ShowModal() == wx.ID_CANCEL:
                return  # the user changed their mind

            # Proceed loading the file chosen by the user
            pathname = fileDialog.GetPath()
            self.imagepath = pathname

            self.NewImage()

    def OnFileAbout(self, event):
        '''
            Display information about the application
        '''
        # A message dialog box with an OK button. wx.OK is a standard ID in wxWidgets.
        dlg = wx.MessageDialog(self.frame, "A GUI for labeling images",
                               "About Image Labeler", wx.OK)
        dlg.ShowModal()
        dlg.Destroy()

    def OnKeyDown(self, event):
        '''
            Actions to be taken when a key is pressed
        '''
        if event.key == 'delete':
            self.OnDelete()

    def OnLeftDown(self, event):
        '''
            Actions taken when mouse button is pressed
        '''
        # Is the click inside a rectangle?
        found = 0
        current_x = event.xdata
        current_y = event.ydata

        for i in range(len(self.rect_obj_list)):
            rect = self.rect_obj_list[i]
            result = check_inside_rect((current_x, current_y), rect)
            if result == 1:
                self.selected_rect = i
                found += 1

        # We want to select this rectangle, and move it
        if found > 0:
            self.is_moving = True
            self.change_rect_color()
            self.selected_rect_obj = self.rect_obj_list[self.selected_rect]
            self.x0, self.y0 = self.selected_rect_obj.xy
            self.x1 = None
            self.y1 = None
            self.press = self.x0, self.y0, event.xdata, event.ydata
            return 0

        if self.cursor_mode == "nobb":
            return 0

        # If the above is not satisified, it is time to draw a new rectangle
        # Initialise the rectangle
        self.rect = Rectangle((0, 0),
                              1,
                              1,
                              facecolor='None',
                              edgecolor='green',
                              linewidth='2')

        self.x0 = None
        self.y0 = None
        self.x1 = None
        self.y1 = None
        self.axes.add_patch(self.rect)

        # Check the mouse press was actually on the canvas
        if event.xdata is not None and event.ydata is not None:
            # Upon initial press of the mouse record the origin and record the mouse as pressed
            self.frame.pressed = True
            self.rect.set_linestyle('dashed')
            self.x0 = event.xdata
            self.y0 = event.ydata

    def OnMotion(self, event):
        '''
            Action taken when mouse movement happens over the canvas
        '''

        # If a rectangle is selected and needs to be moved
        if self.is_moving == True:
            if event.xdata is not None and event.ydata is not None:
                if self.press is None: return
                self.x0, self.y0, xpress, ypress = self.press
                dx = event.xdata - xpress
                dy = event.ydata - ypress
                self.x1 = self.x0 + dx
                self.y1 = self.y0 + dy
                self.selected_rect_obj.set_x(self.x1)
                self.selected_rect_obj.set_y(self.y1)
                self.selected_rect_obj.figure.canvas.draw()

            return 0
        # If the mouse has been pressed draw an updated rectangle when the mouse is
        # moved so the user can see what the current selection is
        elif self.frame.pressed:

            # Check the mouse was released on the canvas,
            # and if it wasn't then just leave the width and
            # height as the last values set by the motion event
            if event.xdata is not None and event.ydata is not None:
                self.x1 = event.xdata
                self.y1 = event.ydata

            # Set the width and height and draw the rectangle
            self.rect.set_width(self.x1 - self.x0)
            self.rect.set_height(self.y1 - self.y0)
            self.rect.set_xy((self.x0, self.y0))
            self.canvas.draw()

    def OnLeftUp(self, event):
        '''
            Actions taken with mouse button is released
        '''

        # Rectangle has finished moving and objects need to be updated.
        if self.is_moving == True:

            x0 = self.selected_rect_obj.get_bbox().x0
            y0 = self.selected_rect_obj.get_bbox().y0
            x1 = self.selected_rect_obj.get_bbox().x1
            y1 = self.selected_rect_obj.get_bbox().y1

            self.selected_rect_obj.figure.canvas.draw()
            self.is_moving = False

            self.press = None

            # Update Grid with new coordinates
            fill_grid(self)
            return 0
        # A new rectangle is finished being drawn
        elif self.frame.pressed:

            # Upon release draw the rectangle as a solid rectangle
            self.frame.pressed = False
            self.rect.set_linestyle('solid')

            # Check the mouse was released on the canvas, and if it wasn't then
            # just leave the width and height as the last values set by the motion event
            if event.xdata is not None and event.ydata is not None:
                self.x1 = event.xdata
                self.y1 = event.ydata

            # Set the width and height and origin of the bounding rectangle
            self.boundingRectWidth = self.x1 - self.x0
            self.boundingRectHeight = self.y1 - self.y0
            self.bouningRectOrigin = (self.x0, self.y0)

            # Draw the bounding rectangle
            self.rect.set_width(self.boundingRectWidth)
            self.rect.set_height(self.boundingRectHeight)
            self.rect.set_xy((self.x0, self.y0))
            self.canvas.draw()

            # Keep list of rect objects
            self.rect_obj_list.append(self.rect)
            self.rect_labels.append("")

            # Fill the grid with the bounding boxes
            fill_grid(self)

    def NewImage(self):
        '''
            A new image needs to be read in, and various objects need to be cleaned up.
        '''

        # Delete all rectangles from the canvas
        self.clear_bb()
        # Uncheck all transformation boxes
        self.TransFrame.reset_boxes()

        # Clear Rectangle List
        self.rect_obj_list = []
        self.rect_labels = []

        # Read image into original_image and current_image
        self.ReadImage()

        # Refresh the canvas
        self.RefreshImage()

    def ReadImage(self):
        ''' 
            Read image off disk
        '''
        self.original_image = cv2.imread(self.imagepath)

        #Matplotlib is RGB, opencv is BGR
        self.original_image = cv2.cvtColor(self.original_image,
                                           cv2.COLOR_BGR2RGB)
        self.current_image = self.original_image.copy()
        self.image_shape = self.current_image.shape

    def FirstImage(self):
        '''
            The very first image is handled differently
        '''
        # Read image into the original_image and current_image
        self.ReadImage()

        # Set Frame to size of image, plust a little extra
        self.frame.SetSize(
            (self.image_shape[1] + 550, self.image_shape[0] + 200))

        self.set_panels()

        # Display the image on the canvas
        self.img_obj = self.axes.imshow(self.current_image, cmap='gray')
        self.canvas.draw()

    def RefreshImage(self):
        '''
            Display new image to Matplotlib canvas and tiddy up
        '''

        # Set Frame to size of image, plust a little extra
        self.frame.SetSize(
            (self.image_shape[1] + 550, self.image_shape[0] + 200))

        self.set_panels()

        # Display the image on the canvas
        self.img_obj.set_extent(
            (0.0, self.image_shape[1], self.image_shape[0], 0.0))

        self.BasicRefresh()

    def BasicRefresh(self):
        self.img_obj.set_data(self.current_image)
        self.canvas.draw()

    def OnDelete(self):
        ''' 
            Delete the selected rectangle
        '''

        # Don't try to delete if empty
        if len(self.rect_obj_list) < 1:
            self.user_error("There is nothing to delete.")
            return 1

        try:
            self.selected_rect
        except:
            self.user_error("You haven't selected a rectangle yet.")
            return 1

        rectangle = self.rect_obj_list[self.selected_rect]
        # Remove object from list
        self.rect_obj_list.remove(rectangle)
        del self.rect_labels[self.selected_rect]
        # Remove object from canvas
        rectangle.remove()
        # Remove coordinates from grid
        self.BBGrid.DeleteRows(self.selected_rect)
        # redraw the canvas
        self.canvas.draw()

        # clear
        del self.selected_rect

    def change_rect_color(self):
        ''' 
            change the line color of currently selected rectangle
        '''
        # Set selected rectangle line color black
        if len(self.rect_obj_list) < 1:
            return 0

        rect = self.rect_obj_list[self.selected_rect]

        #set everything back to green
        for i in range(len(self.rect_obj_list)):
            rect = self.rect_obj_list[i]
            # Set currently selected rect line as black
            if i == self.selected_rect:
                rect.set_edgecolor('red')
            else:  # Set everything else as green
                rect.set_edgecolor('green')

        # Also highlight row in grid
        highlight_row(self, self.selected_rect)

        self.canvas.draw()

    def clear_bb(self, event=None):
        '''
            Remove all rectangles and empty grid
        '''

        for rectangle in self.rect_obj_list:
            # Remove object from canvas
            rectangle.remove()

        empty_grid(self)

        # Set the list back to empty
        self.rect_obj_list = []
        self.rect_labels = []

        # redraw the canvas
        self.canvas.draw()

    def set_panels(self):
        '''
            Set the size and position of the pannels based on the images size.
        '''

        grid_width = 425
        control_height = 70
        img_pane_max_width = self.monitor_size[0] - grid_width - 10
        img_pane_max_height = self.monitor_size[1] - control_height - 120

        #Set some common sense things in relation to image widths
        if self.image_shape[1] < 525:
            img_pane_width = 525
        elif self.image_shape[1] > img_pane_max_width:
            img_pane_width = img_pane_max_width
        else:
            img_pane_width = self.image_shape[1]

        #Set some common sense things in relation to image height
        if self.image_shape[0] < 525:
            img_pane_height = 525
        elif self.image_shape[0] > img_pane_max_height:
            img_pane_height = img_pane_max_height
        else:
            img_pane_height = self.image_shape[0]

        #Come up with what the frame size should be
        frame_width = img_pane_width + grid_width + 10
        frame_height = img_pane_height + control_height + 90

        self.CanvasPanel.SetPosition((0, 0))
        self.CanvasPanel.SetSize((img_pane_width, img_pane_height))

        self.ControlPanel.SetPosition((0, img_pane_height + 5))
        self.ControlPanel.SetSize((235, control_height))

        self.BatchPanel.SetPosition((240, img_pane_height + 5))
        self.BatchPanel.SetSize((125, control_height))

        self.BBPanel.SetPosition((img_pane_width + 5, 0))
        self.BBPanel.SetSize((grid_width, img_pane_height))

        self.GridControlPanel.SetPosition(
            (img_pane_width + 5, img_pane_height + 5))
        self.GridControlPanel.SetSize((grid_width, control_height))

        #Set Overall frame size
        self.frame.SetSize((frame_width, frame_height))

        self.canvas.SetSize((self.image_shape[1], self.image_shape[0]))

        # Reset axes so they don't get messed up when zooming
        self.axes.set_ybound(0, self.image_shape[0])
        self.axes.set_xbound(0, self.image_shape[1])

    def save_grid(self, event):
        '''
            Save the selected bounding boxes to some file, database,etc 
        '''
        if len(self.rect_obj_list) < 1:
            self.user_error("You haven't selected a single bounding box yet.")
            return 1
        else:
            write_grid_csv(self)
            return 0

    def user_info(self, message):
        wx.MessageBox(message, 'Info', wx.OK)

    def user_error(self, message):
        wx.MessageBox(message, 'Error', wx.ICON_ERROR | wx.OK)