Reputation: 33
I've been struggling with my Python application and I can't find any answers.
I'm having PyQT GUI application which uses Matplotlib widget. GUI starts a new thread which handles plotting to mpl widget. I'm afraid I run now to a race condition by accessing matplotlib drawing components from another thread which leads to crash.
This is basically, what my code looks like:
class Analyzer(QMainWindow, Ui_MainWindow):
def __init__(self, parent=None):
self.timer = QTimer()
super(Analyzer, self).__init__(parent)
self.setupUi(self)
self.background = self.mpl.canvas.copy_from_bbox(self.mpl.canvas.ax.bbox)
self.plotQueue = Queue.Queue()
self.plotterStarted = False
self.plotter = Plotter(self.mpl, self.plotQueue)
self.cam = Cam(self.plotQueue, self.textEdit)
...
class Ui_MainWindow(object):
def setupUi(self, MainWindow):
...
self.mpl = MplWidget(self.centralWidget)
...
class MplWidget(QtGui.QWidget):
"""Widget defined in Qt Designer"""
def __init__(self, parent = None):
QtGui.QWidget.__init__(self, parent)
self.canvas = MplCanvas()
...
class MplCanvas(FigureCanvas):
"""Class to represent the FigureCanvas widget"""
def __init__(self):
# setup Matplotlib Figure and Axis
self.fig = Figure()
self.ax = self.fig.add_subplot(111)
# initialization of the canvas
FigureCanvas.__init__(self, self.fig)
FigureCanvas.updateGeometry(self)
And plotter class:
class Plotter():
def __init__(self, mpl="", plotQueue=""):
self.mpl = mpl
self.background = self.mpl.canvas.copy_from_bbox(self.mpl.canvas.ax.bbox)
self.plotQueue = plotQueue
...
def start(self):
threading.Thread(target=self.run).start()
''' Real time plotting '''
def run(self):
while True:
try:
inputData = self.plotQueue.get(timeout=1)
# Go through samples
for samples in inputData:
self.line, = self.mpl.canvas.ax.plot(x, y, animated=True, label='Jee')
for sample in samples:
x.append(sample['tick'])
y.append(sample['linear'])
self.line.set_data(x,y)
self.mpl.canvas.ax.draw_artist(self.line)
self.mpl.canvas.blit(self.mpl.canvas.ax.bbox)
...
So I pass mpl and plotQueue to Plotter class object. PlotQueue is populated in Cam class which processes incoming data from external hw. Plotter reads the plotQueue, processes it and calls drawing for mpl.
But is this a thread safe method to access mpl? If not, how should I do it? Any tips on this are appreciated.
Edit 1.
I added QTimer in main thread to handle drawing, as suggested in comments. After a small tweaking, I got it working fairly well.
class Analyzer(...):
def __init__(self, parent=None):
QObject.connect(self.timer, SIGNAL("timeout()"), self.periodicCall)
def periodicCall(self):
self.plotter.draw()
def startButton(self):
self.timer.start(10)
Thanks a lot for useful comments.
Upvotes: 3
Views: 4792
Reputation: 11849
If matplotlib in your program is using the QT backend (which I assume it is since you are embedding it in a Qt application), then the drawing is going to be done in thread you call the matplotlib commands from. This is going to be a problem because Qt requires that all drawing is done from the main thread. So I'm fairly certain you can't fix it simply. (if you were using GTK you could use the gtk lock to prevent the main process from interacting with the GUI while you did GUI related things from your thread, but Qt got rid of their similar lock in v4 and above).
You have a few options:
Try and separate out the drawing parts of matplotlib (may not even be possible?) and have them run in the main thread by sending events with QApplication.postEvent()
Instead of using a thread, just use callbacks in the main thread (maybe called periodically using a QTimer or when the program is idle). This probbaly won't impact the performance of your application since the Python GIL prevents true multi-threading behaviour anyway.
Use a different plotting library. I had a look over PyQtGraph the other day, and it seems to be coming along nicely. From my brief glance I think it has the ability to handle all of this behind the scenes for you, using a RemoteGraphicsView
. This would launch a second process for doing the CPU intensive plotting stuff, which gets around the aforementioned Python GIL issue. Check out the examples they provide if you are interested
Upvotes: 1