Search code examples
pythonmatplotlibwxpythonmatplotlib-widget

Matplotlib RectangleSelector disappears when new image loaded in wxPython panel


I have an image in a wxPython panel that I want to edit by selecting with Matplotlib RectangleSelector. I have an Image_Viewer class that draws the image. I have an Editor class, where the RectangleSelector functionality resides. Editor inherits from Image_Viewer. Finally, a control panel holds the file selector button. The program starts with a default image loaded through the Image_Viewer draw function. I can use RectangleSelector fine. But when I load a new image, which also loads through the Image_Viewer draw function, all connection to the RectangleSelector disappears.

I've tried to pare down the code as much as I can, but the whole is part of a multi tabbed notebook, hence the presence of wx.lib.agw.aui and other possible idiosyncrasies.

import os
import cv2 as cv
import matplotlib.pyplot as plt
import numpy as np
import wx
import wx.lib.agw.aui as aui
from matplotlib.backends.backend_wxagg import FigureCanvasWxAgg as FigureCanvas
from matplotlib.figure import Figure
from matplotlib.widgets import RectangleSelector
from matplotlib.patches import Rectangle

# ------------------variables-------------------
# Base path
pth = os.path.dirname(os.path.abspath('__file__'))
# folder for gui graphics
gui_graphics = pth + '/gui_graphics'
# temp file for initial image handling
img_pth = os.path.join(pth, "tmp_img.png")
# Folders, subfolders for processed images
all_cats = pth + '/Cats'
new_cats = all_cats + '/new_cats'

min_img_wndw = 295      # min size of top panels
img_y, img_x = 64, 64   # raw cat size

class Image_Viewer(wx.Panel):
    def __init__(self, parent):
        super(Image_Viewer, self).__init__(parent)
        #load image
        im_pth = new_cats + '/av_cat'
        files = os.listdir(im_pth)
        im_pths = [os.path.join(im_pth, im_name) for im_name in files]
        try:
            self.img = max(im_pths, key=os.path.getctime)
            self.image = cv.imread(self.img)
        except:
            self.image = np.zeros((img_x, img_y), np.uint8)

        self.figure = Figure()
        self.draw(self.image)

    def draw(self, image):
        plt.close(self.figure)
        self.figure.clf()
        self.axes = self.figure.add_axes([0, 0, 1, 1])
        self.axes.axis('off')
        self.canvas = FigureCanvas(self, -1, self.figure)
        self.sizer = wx.BoxSizer(wx.VERTICAL)
        self.sizer.Add(self.canvas, 1, wx.EXPAND)
        self.SetSizer(self.sizer)
        self.axes.imshow(image, cmap='gray', vmin=0, vmax=255)
        self.canvas.draw()

class Editor(Image_Viewer):
    def __init__(self, parent):
        super(Editor, self).__init__(parent)

        self.RS = RectangleSelector(self.axes, self.on_click,
                                    drawtype='box', useblit=True,
                                    button=[1, 3],  # don't use middle button
                                    minspanx=5, minspany=5,
                                    spancoords='pixels',
                                    interactive=True)
        #self.canvas.draw()
        cid = self.canvas.mpl_connect('MouseEvent', self.on_click)
        print(cid)

    def on_click(self, eclick, erelease):
        print("You clicked")
        'eclick and erelease are the press and release events'
        x1, y1 = eclick.xdata, eclick.ydata
        x2, y2 = erelease.xdata, erelease.ydata
        w = int(x2-x1)
        h = int(y2 - y1)

class control_Panel(wx.Panel):
    def __init__(self, parent, image_panel):     #, image_panel, terminal_panel
        wx.Panel.__init__(self, parent=parent)
        #self.panel = wx.Panel(self, -1, size=(580, 180))
        self.screen = image_panel

        self.pth = os.path.dirname(os.path.abspath('__file__'))
        fileDlgBtn = wx.Button(self, label="Get Cats")
        fileDlgBtn.Bind(wx.EVT_BUTTON, self.OnOpen)

    def OnOpen(self, event):
        with wx.FileDialog(self, "Open image file", wildcard="PNG files (*.png)|*.png",
                           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()
            image = cv.imread(pathname)
            self.screen.draw(image)

class explorer_panel(wx.Panel):
    def __init__(self, parent):
        """Constructor"""
        wx.Panel.__init__(self, parent)
        bSplitter = wx.SplitterWindow(self)
        image_panel = Editor(bSplitter)
        panelThree = control_Panel(bSplitter, image_panel)  

        bSplitter.SplitHorizontally(image_panel, panelThree)
        bSplitter.SetSashGravity(0.5)

        bSplitter.SetMinimumPaneSize(min_img_wndw)

        sizer = wx.BoxSizer(wx.VERTICAL)
        sizer.Add(bSplitter, 1, wx.EXPAND)
        self.SetSizer(sizer)

class Main(wx.Frame):
    def __init__(self):
        wx.Frame.__init__(
            self, 
            parent = None, 
            title = "Borges Infinite Image", 
            size = (600,550)
            )
        self.SetIcon(wx.Icon(gui_graphics + '/Abe.png'))

        panel = wx.Panel(self)
        notebook = aui.AuiNotebook(panel)
        explorer = explorer_panel(notebook)
        notebook.AddPage(explorer, 'Explorer')
        sizer = wx.BoxSizer(wx.VERTICAL)
        sizer.Add(notebook, 1, wx.ALL|wx.EXPAND, 5)
        panel.SetSizer(sizer)

if __name__ == "__main__":
    app = wx.App()
    frame = Main()
    frame.Show()
    app.MainLoop()

Solution

  • I cannot test your code because I don't have wx installed atm, but I'm pretty sure your problem stems from you creating a new figure and new canvas everytime you load a new picture.

    I belive you should create one Figure and one set of axes, and then replace the content of this axes with the new picture instead

    e.g.

    class Image_Viewer(wx.Panel):
        def __init__(self, parent):
            (...)
    
            self.figure = Figure()
            self.axes = self.figure.add_axes([0, 0, 1, 1])
            self.axes.axis('off')
            self.canvas = FigureCanvas(self, -1, self.figure)
            self.sizer = wx.BoxSizer(wx.VERTICAL)
            self.sizer.Add(self.canvas, 1, wx.EXPAND)
            self.SetSizer(self.sizer)
            self.draw(self.image)
    
        def draw(self, image):
           self.axes.cla()
           self.axes.imshow(image, cmap='gray', vmin=0, vmax=255)
           self.canvas.draw()