[PyQt] Multithreading and plotting

David Hoese dhoese at gmail.com
Fri Dec 21 17:19:31 GMT 2012


Hey Fabien,

See comments below and the bottom for my final changes. Please CC me in 
replies.

On 12/21/12 5:06 AM, lafont.fabien at gmail.com wrote:
> Hello everyone,
>
> I'm trying to plot live datas using matplotlib and PyQt. I need a
> multithreaded program beacause I use time.sleep and it freeze completely
> the app during that moment. I've tried that but it crash immediatly:
>
Do you get a seg. fault or some other error? I'm pretty sure I've helped 
you with this type of Qt application before, but I'll see what I can 
clear up.
> from PyQt4 import QtCore, QtGui
>
> import time
>
> import sys
>
> from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg as
> FigureCanvas
>
> from matplotlib.backends.backend_qt4agg import NavigationToolbar2QTAgg as
> NavigationToolbar
>
> from matplotlib.figure import Figure
>
> # Subclassing QThread
>
> # http://doc.qt.nokia.com/latest/qthread.html
>
> class Graph(FigureCanvas):
>
> def __init__(self,parent):
>
> self.fig = Figure()
>
> self.ax = self.fig.add_subplot(111)
>
> FigureCanvas.__init__(self, self.fig)
>
> self.R1 = []
>
> self.l_R1, = self.ax.plot([], self.R1,"-o")
>
> self.fig.canvas.draw()
>
> FigureCanvas.updateGeometry(self)
>
> class ApplicationWindow(QtGui.QMainWindow):
>
> """Example main window"""
>
> def __init__(self):
>
> QtGui.QMainWindow.__init__(self)
>
> self.main_widget = QtGui.QWidget(self)
>
> vbl = QtGui.QGridLayout(self.main_widget)
>
> qmc = Graph(self.main_widget)
>
> vbl.addWidget(qmc,0,0,1,11)
>
> self.setCentralWidget(self.main_widget)
First, a small thing, I'm not sure if this matters but I've always 
created layouts by doing:

    vbl = QtGui.QGridLayout()
    # addWidget
    self.main_widget.setLayout(vbl)

I got this pattern from the documentation:
http://www.riverbankcomputing.co.uk/static/Docs/PyQt4/html/qwidget.html#setLayout 

>
> class AThread(QtCore.QThread):
>
> def run(self):
>
> count = 0
>
> while count < 5:
>
> time.sleep(1)
>
> print "Increasing"
>
> count += 1
>
> aw.l_R1.set_data(count, count)
I don't approve of subclassing the "run" method of QThread, but what you 
have should work a little bit for now.  I really don't approve of using 
a global in another thread affinity and I highly doubt this will work 
correctly.  You should be using Qt signals to communicate between 
threads, but using signals you may have to get a little more complex 
with how you are using QThreads to free up the event loops.

You are also calling "aw.l_R1" which does not exist.  You need to have a 
way to access the graph.  As a quick hack I just set the graph to 
"self.qmc" and then in the run method I did "aw.qmc.l_R1.set_data(count, 
count)".  You will also want to update the limits to show the new data 
as it comes in or set it to a certain range from the start. You will 
need to redraw the canvas every time you update it.  You will likely run 
into problems in the future if you draw in a different thread from the 
GUI thread, but for some reason it works in your example.

I'm not sure if this was intended, but since you are not saving the 
previous "count" you are only plotting points not a line. One way to do 
this is if you want to keep the last N records (lets say N=200) then you 
can plot originally "self.ax.plot(numpy.arange(200), numpy.zeros(200))" 
and then update the y-data only like this "aw.qmc.set_ydata(<length 200 
array of previous counts>)".
>
> def usingQThread():
>
> app = QtCore.QCoreApplication([])
>
> thread = AThread()
>
> thread.finished.connect(app.exit)
>
> thread.start()
>
> sys.exit(app.exec_())
I'm really confused at this point.  How have you been calling this 
module? As "python test.py"? You are creating 2 QApplications, which 
isn't the easiest to follow.  You should create 1 application, then make 
the window, then make the threads.

Another big thing is that when you run 
"thread.finished.connect(app.exit)" you are connecting the end of the 
thread to the app closing.  Do you want the Application to completely 
close when the thread finishes (as you have it which I've never done so 
not sure if this will cause problems in more complex applications) or do 
you want the thread to stop, leaving the window open? The application 
would then close when the last window is closed.
>
> if __name__ == "__main__":
>
> #
>
>   qApp = QtGui.QApplication(sys.argv)
>
> aw = ApplicationWindow()
>
> aw.showMaximized()
>
> usingQThread()
>
> sys.exit(qApp.exec_())
>
I removed "usingQThread" method and moved that logic to the if statement 
so it now looks like this:

    if __name__ == "__main__":
         qApp = QtGui.QApplication([" "])
         aw = ApplicationWindow()
         aw.showMaximized()
         thread = AThread()
         thread.finished.connect(qApp.exit)
         thread.start()
         sys.exit(qApp.exec_())


I also added a "self.qmc = qmc" in the ApplicationWindow and forced the 
y-limits in the Graph.__init__ method using the following:

    self.ax.set_xlim(-1, 6)
    self.ax.set_ylim(-1, 6)

Then in the run method the last 2 lines in the while loop now say:

      aw.qmc.l_R1.set_data(count, count)
      aw.qmc.draw()


This got it running for me, although it had the plotting points problem 
I mentioned earlier.  Hopefully this will get you somewhere. But I can 
not stress enough that you need to research QThreads and how they should 
be used correctly and how threads work in general. As I noted above you 
should not be drawing in non-GUI threads and you should not be magically 
using a global variable that lives in another thread (use signals and 
slots).  This is bad practice and will likely lead to obscure errors and 
a lot of headache when you're short on time (assuming this is for a work 
project like mine was).

Good luck.

-Dave

-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://www.riverbankcomputing.com/pipermail/pyqt/attachments/20121221/c17d5568/attachment.html>


More information about the PyQt mailing list