Search code examples
pythonmultithreadingmatplotlibwxpythonpython-multithreading

wxpython interface becomes unresponsive when running live matplotlib draw() function


So I am using wxpython to make a GUI for a program. I have also embedded matplotlib graphs in this program.

My problem is when I try to use draw() to update the plot continuously, my program becomes unresponsible, however the matplotlib graph is still updating.

Here is part of my code, and how I execute all this,

def update(self, e):
    if self.liveMode:
        self.tune.plot();  # new plot
    self.canvas.draw();
    self.startLive(e);

def startLive(self, e):
    self.liveMode = False;
    refFreq = 500e6 / 80;
    qx = self.pv1.get();
    qy = self.pv2.get();
    self.tune.newWorkingpoint(refFreq/qx, refFreq/qy);
    self.tune.liveUpdate();
    time.sleep(0.3);
    self.update(e);

When the 'live mode' button is pressed in my program the 'update' method gets called. I plot all the background lines, and then repeatedly get new point values from elsewhere and plot this as a circle on the graph and update with the draw() method. Of course since I am essentially in a continuous loop, nothing else will work until this process is finished. My goal was to just put a 'stop' button to stop this live plotting loop, but since everything becomes unresponsive when matplotlib is drawing, no buttons are clickable, and I have to essentially stop compiling to stop the program.

Is there anyway around this? I have looked into threading, but I get the same problems with this.

EDIT: ----------------------------------------------------------------------------------------------------------------------------I have got it working using this page as a guide. However, after a few seconds I get this fatal error and my program crashes. 'python2.7: Fatal IO error 11 (Resource temporarily unavailable) on X server :0.0.'

Here is the method I implemented. When a start button is pressed, 'startlive' starts, and when a stop button is pressed, 'stoplive' starts. Everything works fine, and my matplotlib graphs are updated properly without my wxpython GUI becoming unresponsive.

The problem is after a few seconds I get the fatal error and my program crashes. I believe this is most likely tied to the canvas.draw() method updating matplotlib inside a thread. Any ideas on how to get around this problem?

def startLive(self, e):
    self.livemode.change(True);
    self.worker = CountingThread(self.panel,self.tune,self.canvas,self.livemode, self.pv1, self.pv2);
    self.worker.start();

def stopLive(self, evt):
    self.livemode.change(False);

class CountingThread(threading.Thread):
    def __init__(self, parent, tune, canvas, livemode, pv1, pv2):
        threading.Thread.__init__(self)
        self._parent = parent;
        self._tune = tune;
        self._canvas = canvas;
        self._livemode = livemode;
        self._pv1 = pv1;
        self._pv2 = pv2;

    def run(self):
        refFreq = 500e6 / 80;
        i=0;
        while self._livemode.get():
            self._tune.newWorkingpoint(refFreq / self._pv1.get(), refFreq / self._pv2.get());
            self._tune.liveUpdate();
            self._canvas.draw();
            time.sleep(0.2);
            i+=1;
            print(i)
class livemode:
    livemode=False;

    def __init__(self):
        self.livemode = False;

    def change(self, livemode):
        self.livemode = livemode;

    def get(self):
        return self.livemode;

--------------------------------EDIT--------------------------------- I solved the problem if anyone is interested,

In the threading run method, I removed the loop and simply had one instance of 'updating'. After the update, wx.PostEvent is called which calls a method back in the main class containing the wx.App which updates the plot by calling draw(). After this, the threading is restarted again and the same process repeats. My GUI continues to work and the plotting speed is still really fast. The important methods are below,

Threading.start() is called and the below exectures

    def run(self):
        refFreq = 500e6 / 80;
        self._tune.newWorkingpoint(refFreq / self._pv1.get(), refFreq /self._pv2.get());
        self._tune.liveUpdate();
        evt = CountEvent(myEVT_PLOT, -1);
        wx.PostEvent(self._parent, evt);

wx.PostEvent calls the following new method in main wx.App class,

    def continueLive(self, evt):
        if self.livemode.get():
            self.canvas.draw();
            self.startLive(evt)

The plot is updated and the whole process starts over.

This methods keeps the GUI responsive and the plotting speed does not slow down.


Solution

  • I think it might be a stack issue. Your update loop is recursive and the stack has a limited size (every function call gets pushed to the stack and removed once the function returns).

    You should change it to a iterative version.

    def update(self, e):
        if self.liveMode:
            self.tune.plot();  # new plot
        self.canvas.draw();
        #self.startLive(e); # causes endless recursion
    
    def startLive(self, e):
        while (self.isInLiveMode): # some flag here that gets set to False once the mode is deactivated
            self.liveMode = False;
            refFreq = 500e6 / 80;
            qx = self.pv1.get();
            qy = self.pv2.get();
            self.tune.newWorkingpoint(refFreq/qx, refFreq/qy);
            self.tune.liveUpdate();
            time.sleep(0.3);
            self.update(e);
    

    Well i assume self.liveMode should be that flag.

    Hope this helps, LG Daniel