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

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.

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

Related

Updating pyqtgraph BarGraphItem inside a thread

i have some trouble to make a gui responsive while plotting live data. So that the GUI doesn't freeze, I try to thread all of my activities. I want to realize the following:
Recording data through a serial port
Calculations for the later plot in a thread
Plotting in a thread (currently via a QTimer, but when i drag the window, there is always a "small" freeze and the plot does not update on the drag)
1 and 2 is done, but now im not sure how to update my plot in a seperate thread.
My PlotWidget get initiated looks like this:
self.plottingDQ = [queue.deque(maxlen=100), queue.deque(maxlen=100), queue.deque(maxlen=100)]
self.graph = pg.PlotWidget()
self.barItem = pg.BarGraphItem(x0=self.plottingDQ[0], y0=self.plottingDQ[1], width=self.plottingDQ[2], height=1)
self.graph.addItem(self.barItem)
starting my threads is done via a button which is connected to this function. Writer-Thread is not relevant because it has no dependency on the plot. But the Calculator-Thread calculates the data for updating the plot
def startPlotting(self):
# not relevant for the example
self.csvData = queue.Queue()
self.csv = Writer(self.csvData)
self.csv.setDaemon(True)
self.csv.start()
self.calcData = queue.Queue()
self.calcPlot = Calculator(self.calcData, self.plottingDQ)
self.calcPlot.setDaemon(True)
self.calcPlot.start()
# Timer to update plot every x ms
self.timer = QTimer()
self.timer.timeout.connect(self.updatePlot)
self.timer.start(500)
Right now im updating my plot within the Qtimer every 500ms
def updatePlot(self):
print("update")
self.barItem.setOpts()
So every time i get some input from my serial port i just pass the data to my thread and call something like this:
def fromPort(self, data):
self.csvData.put(data)
self.calcData.put(data)
Inside my Calculator-Thread the data will be calculated and handed over to the plottingDQ which is connected to the BarGraphItem
class Calculator(threading.Thread):
def __init__(self, calcData, plottingDQ):
threading.Thread.__init__(self)
self.calcData = calcData
self.plottingDQ = plottingDQ
self.a = threading.Event()
self.a.set()
def run(self):
while self.a.isSet():
# Do some stuff here ...
# After the calculations, i write the data into the plottingDQ
self.plottingDQ[0].append(x)
self.plottingDQ[1].append(y)
self.plottingDQ[2].append(duration)
Is this the correct way to pass my calculated data from my Calcualtor-Thread into the deques which are used in the BarGraphItem? How can i update my BarGraphItem inside a thread?
The way you programmed this looks fine.
The root cause of the "stutter" seems to be an "update block" during the dragging process.
Try to enforce updates by adding pg.QtGui.QApplication.processEvents() to your updatePlot function, as described here:
def updatePlot(self):
print("update")
self.barItem.setOpts()
pg.QtGui.QApplication.processEvents()
It may be better to use Qthread to deal with this, having a worker thread doing the plot update inside the app loop.

Confusion with matplotlib timers

I tried to implement the workaround mentioned here to the problem of the figure not updating properly when a draw_event is triggered once a user zooms into a plot. This was meant to improve this answer. In the solution, a Timer is added to the Canvas of the Figure, which delays the draw function for a short while.
In the linked answer this is done for a single figure and a reference to the timer is stored as self.timer to the class that contains the update function. I would like to have an a bit more general behaviour and allow this for many Figure instances. Therefore I tried not to save the Timer reference (I don't need it anymore). But when I do so, the script crashes with a Segmentation Fault (no traceback). Here is my code:
from matplotlib import pyplot as plt
import numpy as np
##plt.switch_backend('TkAgg')
class DrawEventHandler:
def __init__(self):
x = np.linspace(0,1,10)
y = np.sin(x)
self.fig, self.ax = plt.subplots()
self.ax.plot(x,y)
self.ax.figure.canvas.mpl_connect('draw_event', self.update)
def update(self, event = None):
self._redraw_later_ok()
##self._redraw_later_not_ok(self.fig)
def _redraw_later_ok(self):
self.timer = self.fig.canvas.new_timer(interval=10)
self.timer.single_shot = True
self.timer.add_callback(lambda : self.fig.canvas.draw_idle())
self.timer.start()
def _redraw_later_not_ok(self, fig):
print('start')
timer = fig.canvas.new_timer(interval=10)
timer.single_shot = True
timer.add_callback(lambda : fig.canvas.draw_idle())
timer.start()
print('stop')
if __name__ == '__main__':
my_handler = DrawEventHandler()
plt.show()
The original solution is implemented as _redraw_later_ok(), which works fine for me. The problematic solution is called `_redraw_later_not_ok()', which produces this output:
start
stop
Segmentation fault: 11
I use a Mac with High Sierra, Python 3.6.4 or Python 2.7.14 and Matplotlib 2.1.1 with the MacOSX backend. When I switch to the TkAgg backend (which is terribly slow), the code works fine. Can anybody explain what is going on?
As usual with interactive features which use the event loop of a GUI you need to keep a reference to the object that handles the callback.
The problem with the approach of not storing the timer in this code
def _redraw_later_not_ok(self, fig):
print('start')
timer = fig.canvas.new_timer(interval=10)
timer.single_shot = True
timer.add_callback(lambda : fig.canvas.draw_idle())
timer.start()
print('stop')
is that timer gets garbage collected once the _redraw_later_not_ok function terminates (i.e. directly after stop is printed). At this point there would be a pointer to a place in memory, which may or may not store the callback (any more). An attempt by the python interpreter to call the callback would hence (most probably) fail.
The solution is indeed to always keep a reference to the object that steers the callback. This is the solution shown in _redraw_later_ok, where the timer is made a class attribute, self.timer. Such that it can later be used at the time the callback is called.
I do not understand in how far using this working approach would prevent the use of several figures, so it may make sense to create a MWE with two figures that shows the problem clearly.

PyQt5: QSlider valueChanged event with delay

I'm using QSlider in my GUI application in order to perform a heavy task after value changed in the QSlider. I'm doing that as follows.
self.slider.valueChanged.connect(self.value_changed) # Inside __init__() function
def value_changed(self): # Inside the class
# Do the heavy task
But I can't change the value of the slider smoothly because the heavy task is running every time I change the value.
What I need is to run the heavy task after the value changed but only if the value of the slider is not changing for a while.
I can't figure out how to do this in python. Any help..?
I am no expert, and I arrive long after the battle, but i needed a thing like that too, and i don't understand a thing about the timers. It worked fine for one slider, but i needed 3. So I came up with this solution : when the slider is pressed, I disconnect the valueChanged slot, and when the slider is released, I reconnect it and I throw a valueChanged signal, like this :
self.sldAZap.valueChanged.connect(self.sliderChanged)
self.sldAZap.sliderPressed.connect(self.sldDisconnect)
self.sldAZap.sliderReleased.connect(self.sldReconnect)
def sldDisconnect(self):
self.sender().valueChanged.disconnect()
def sldReconnect(self):
self.sender().valueChanged.connect(self.sliderChanged)
self.sender().valueChanged.emit(self.sender().value())
def sliderChanged(self):
print(self.sender().objectName() + " : " + str(self.sender().value())
With this solution, there is no delay between the moment the mouse is released and the execution of the code, and the code is executed just one time.
I hope I am clear enough and it may help someone.
You can use startTimer/killTimer to delay your task:
class Foo(QWidget):
def __init__(self):
super().__init__()
self.timer_id = -1
self.slider = QSlider(self)
self.slider.setMinimum(0)
self.slider.setMaximum(100)
self.slider.valueChanged.connect(self.value_changed)
def timerEvent(self, event):
self.killTimer(self.timer_id)
self.timer_id = -1
heavy_task()
def value_changed(self):
if self.timer_id != -1:
self.killTimer(self.timer_id)
self.timer_id = self.startTimer(3000)
so, as can you see we restart timer every time when
user something change, so if 3000 millseconds not expires
since last change heavy_task not run,
but any way it will be running in main thread, so for some
time interface freeze for user, so you should use
QThread in timerEvent to not have interface that not freeze during
heavy_task execution.

Plot freezing because of fast input stream to a GNU Radio block

I have implemented a sync block which plots inside its work function using the input_items values. Now the problem is that the plotting mechanism isn't fast enough for the input stream ( the value of input_items keeps on changing ).
I have tried to simplify the code as much as possible and added comments. Here it is:
....
import matplotlib
from matplotlib.backends.backend_wxagg import FigureCanvasWxAgg as FigCanvas
from matplotlib.backends.backend_wx import NavigationToolbar2Wx
from matplotlib.figure import Figure
temp = ''
class xyz(gr.sync_block):
def __init__(self,parent,title,order):
a = []
self.count = 1
gr.sync_block.__init__(self,
name="xyz",
in_sig=[numpy.float32,numpy.float32],
out_sig=None)
self.win = xyzPlot(parent) #I have a Top Block(wxFrame). I am just making it the parent of xyzPlot(wxPanel) here.
def work(self, input_items, output_items):
global temp
if (self.count == 1):
temp = input_items+list()
bool = not(np.allclose(temp,input_items))#bool will be true if the value of `input_items` changes.
.......
#the code that evaluates z1,z2 depending on the value of input_items
.......
if ( bool or self.count == 1 ):
#If bool is true or it is the first time then I am calling plot() which plots the graph.
self.win.plot(tf(self.z1,self.z3),None,True,True,True,True)
self.count = 0
temp = input_items+list()
return len(input_items[0])
class xyzPlot(wx.Panel):
def __init__(self, parent, dB=None, Hz=None, deg=None):
wx.Panel.__init__(self , parent , -1 ,size=(600,475))
self.fig = Figure()
self.axes = self.fig.add_subplot(111)
def plot(self, syslist, omega=None, dB=None, Hz=None, deg=None, Plot=True, *args , **kwargs):
self.axes.clear() #I clear the graph here so that new values can be plotted.
.....
self.axes.semilogx(omega,mag,*args,**kwargs)
self.canvas = FigCanvas(self, -1, self.fig)
self.canvas.draw()
As you can see I am working with wxPython but the panel freezes whenever I change the value of input_items too fast ( It works fine if I change it slowly ). Any recommendations? I am new to this.
To cite another answer I gave:
This will quickly also get a multithreading problem. To be clear: What
you're trying to do (call a plotting function from a block thread) is
problematic and usually won't work.
The problem is that you're working in a complex multithreading environment:
each GNU Radio block works in its own thread
The WX Gui main loop runs continously to update the screen.
What you're doing here is, from a GNU Radio block thread, change what is shown in the window. That is a bad thing, because it changes things that are in the context of the WX Gui thread. This can work, if these changes don't conflict, and if the WX Gui thread doesn't access this kind of data while you're changing it (at some point, it has to access it -- otherwise, noone will update your window).
This is a problem common to all kind of updated GUIs, not only to GNU Radio!
Whether or not that happens is a mere case of probability: With a slowly updated display, your probability of conflict is low, but when you update often, it approaches 1.
The existing visualizations are written in C++ and take very great care to do things the right way -- which is, letting your Gui toolkit (WX in your case, though I explicitely recommend, and have recommended, to move away from that) know that things need to be updated, and then offering WX a function to update the display in its own thread.

Memory leak when embedding and updating a matplotlib graph in a PyQt GUI

I am trying to embed a matplotlib graph that updates every second into a PyQt GUI main window.
In my program I call an update function every second using threading.Timer via the timer function shown below. I have a problem: my program grows bigger every second - at a rate of about 1k every 4 seconds. My initial thoughts are that the append function (that returns a new array in update_figure) does not delete the old array? Is it possible this is the cause of my problem?
def update_figure(self):
self.yAxis = np.append(self.yAxis, (getCO22()))
self.xAxis = np.append(self.xAxis, self.i)
# print(self.xAxis)
if len(self.yAxis) > 10:
self.yAxis = np.delete(self.yAxis, 0)
if len(self.xAxis) > 10:
self.xAxis = np.delete(self.xAxis, 0)
self.axes.plot(self.xAxis, self.yAxis, scaley=False)
self.axes.grid(True)
self.i = self.i + 1
self.draw()
This is my timer function - this is triggered by the click of a button in my PyQt GUI and then calls itself as you can see:
def timer(self):
getCH4()
getCO2()
getConnectedDevices()
self.dc.update_figure()
t = threading.Timer(1.0, self.timer)
t.start()
EDIT: I cant post my entire code because it requires a lot of .dll includes. So i'll try to explain what this program does.
In my GUI I want to show the my CO2 value over time. My get_co22 function just returns a float value and I'm 100% sure this works fine. With my timer, shown above, I want to keep append a value to a matplotlib graph - the Axes object is available to me as self.axes. I try to plot the last 10 values of the data.
EDIT 2: After some discussion in chat, I tried putting the call to update_figure() in a while loop and using just one thread to call it and was able to make this minimal example http://pastebin.com/RXya6Zah. This changed the structure of the code to call update_figure() to the following:
def task(self):
while True:
ui.dc.update_figure()
time.sleep(1.0)
def timer(self):
t = Timer(1.0, self.task())
t.start()
but now the program crashes after 5 iterations or so.
The problem is definitely not with how you are appending to your numpy array, or truncating it.
The problem here is with your threading model. Integrating calculation loops with a GUI control loop is difficult.
Fundamentally, you need your GUI threading to have control of when your update code is called (spawning a new thread to handle it if necessary) - so that
your code does not block the GUI updating,
the GUI updating does not block your code executing and
you don't spawn loads of threads holding multiple copies of objects (which might be where your memory leak comes from).
In this case, as your main window is controlled by PyQt4, you want to use a QTimer (see a simple example here)
So - alter your timer code to
def task(self):
getCH4()
getCO2()
getConnectedDevices()
self.dc.update_figure()
def timer(self):
self.t = QtCore.QTimer()
self.t.timeout.connect(self.task)
self.t.start(1000)
and this should work. Keeping the reference to the QTimer is essential - hence self.t = QtCore.QTimer() rather than t = QtCore.QTimer(), otherwise the QTimer object will be garbage collected.
Note:
This is a summary of a long thread in chat clarifying the issue and working through several possible solutions. In particular - the OP managed to mock up a simpler runnable example here: http://pastebin.com/RXya6Zah
and the fixed version of the full runnable example is here: http://pastebin.com/gv7Cmapr
The relevant code and explanation is above, but the links might help anyone who wants to replicate / solve the issue. Note that they require PyQt4 to be installed
if you are creating a new figure for every time this is quite common.
matplotlib do not free the figures you create, unless you ask it, somethink like:
pylab.close()
see How can I release memory after creating matplotlib figures

Categories