1

Goal:

I want to embed a Matplotlib plot in a pyQt4 GUI window. The plot has to update in time.

Problem:

The window freezes until the plotting has finished. I want the plot to be updated in real time.

Context:

We have numerical algorithm that is working on some data and I want the plot to show how the dataset is being affected by algorithm. The algorithm finishes an iteration ever ~0.5 seconds - the plot must be updated every iteration.

Test Code:

The algorithm is replaced by test(), which plots a random point 100 times. The code below illustrates the problems:

import sys
from PlotGUI import *
import threading
from random import randint
import time

class GUIForm(QtGui.QDialog):

    def __init__(self, parent=None):
        QtGui.QWidget.__init__(self,parent)
        self.ui = Ui_Dialog()
        self.ui.setupUi(self)
        QtCore.QObject.connect(self.ui.pushButton, QtCore.SIGNAL('clicked()'), self.startSim)
        self.cPlot = None # custom plotter
        self.instantiatePlot()        

    def instantiatePlot(self):
        self.cPlot = CustomPlotter(self.ui.widget.canvas) 
        self.cPlot.prepareFigure()

    def startSim(self):
        self.cPlot.clear();        
        draw_thread = threading.Thread(target=self.cPlot.test())
        draw_thread.start()

class CustomPlotter():
    def __init__(self, canvas):
        print 'constructor'
        self.canvas = canvas        

    def prepareFigure(self):
        ax = self.canvas.ax

        ax.set_ylim([-1,101])
        #ax.set_xlim([dt[0],dt[1]])
        ax.set_ylim([-1, 10])
        self.canvas.draw()

    def clear(self):
        self.canvas.ax.clear()

    def test(self):
        canvas = self.canvas 
        ax = canvas.ax
        for x in range(0,100):
            y = randint(0,9)
            ax.plot(x, y, 'ro')
            print x
            canvas.draw()
            time.sleep(1)
            #canvas.show()
            #canvas.update()

if __name__ == "__main__":
    app = QtGui.QApplication(sys.argv)
    myapp = GUIForm()
    myapp.show()
    sys.exit(app.exec_())

Thanks in advance. This is for some prototyping, so I'd be open to all options / alternatives that offer a quick'ish solution.

3
  • Which library has PlotGUI module? Commented Nov 19, 2015 at 14:37
  • QtGui, QtCore, Ui_Dialog() depend on the PlotGUI module Commented Nov 19, 2015 at 14:43
  • 1
    The answers here are a bit disjointed, but basically you are not creating the thread properly, hence why it blocks. However, creating it properly will likely result in crashes because of Qt calls in the secondary thread (which is not allowed). Furthermore, the GUI will always block when the plot is actually being redrawn, no way around that, but you can offload the preparation/fetching of the plot data into another thread. You just need to send this data to the main thread to update the GUI (probably using a QThread and signal emission - there are plenty of questions on SO about that) Commented Nov 20, 2015 at 10:40

3 Answers 3

1

You have an error when you try to instantiate a new thread:

draw_thread = threading.Thread(target=self.cPlot.test())

This will immediately execute the test method in the current thread and then pass the result (None) as target. What you probably wanted to do is:

draw_thread = threading.Thread(target=self.cPlot.test)

Thread(target=None) simply creates a thread that does nothing, just immediately exits, and is therefore valid and doesn't produce any exception that would indicate this problem.

Because the test() method is started within the GUI thread, the GUI is blocked until the method returns.

Sign up to request clarification or add additional context in comments.

Comments

1

PySide/Qt Cannot update widgets (QPixmap/display items) from another thread. You cannot directly call plot for a GUI item in another thread.

See matplotlib.animation.FuncAnimation. http://matplotlib.org/api/animation_api.html

import numpy as np

# ========== Matplotlib [PySide] ==========
import matplotlib
matplotlib.use("Qt4Agg")
matplotlib.rcParams["backend.qt4"] = "PySide"

import matplotlib.animation as mplanimation
from matplotlib.figure import Figure
from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg as FigureCanvas

class InteractivePlotWidget(FigureCanvas):

    def __init__(self):
        super().__init__(Figure(tight_layout=True))
        self.axes = self.figure.add_subplot(111)

        self.setSizePolicy(QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Expanding)

        self.name = ""
        self.data = np.zeros(shape=(100, 2))
    # end __init__

    def plot_data(self, interval=0):
        data = np.array([(i, np.sin(i)) for i in range(interval)])
        try:
            self.axes.lines[0].set_data(data[:,0], data[:,1])
        except IndexError:
            self.axes.plot(data, label=self.name) # Lots of overhead. Do once.

        self.axes.relim()
        self.axes.autoscale_view(True, True, True)
        return self.axes.lines  # animation handles draw
        # manually trigger draw
        # self.draw_idle()
        # self.flush_events()
    # end plot_data
# end class InteractivePlotWidget

if __name__ == "__main__":
    QtGui.QApplication([])

    w = InteractivePlotWidget()
    w.show()

    # Create and start the animation (timer)
    anim = mplanimation.FuncAnimation(w.figure, w.plot_data, interval=0)
    anim._start()

    sys.exit(QtGui.qApp.exec_())

Comments

0

The embedding_in_qt4.py example from the matplotlib gallery should be enough, right?

# ...
class MyMplCanvas(FigureCanvas):
    """Ultimately, this is a QWidget (as well as a FigureCanvasAgg, etc.)."""

    def __init__(self, parent=None, width=5, height=4, dpi=100):
        fig = Figure(figsize=(width, height), dpi=dpi)
        self.axes = fig.add_subplot(111)
        # We want the axes cleared every time plot() is called
        self.axes.hold(False)

        self.compute_initial_figure()

        #
        FigureCanvas.__init__(self, fig)
        self.setParent(parent)

        FigureCanvas.setSizePolicy(self,
                                   QtGui.QSizePolicy.Expanding,
                                   QtGui.QSizePolicy.Expanding)
        FigureCanvas.updateGeometry(self)

    def compute_initial_figure(self):
        pass
# ...
class MyDynamicMplCanvas(MyMplCanvas):
    """A canvas that updates itself every second with a new plot."""

    def __init__(self, *args, **kwargs):
        MyMplCanvas.__init__(self, *args, **kwargs)
        timer = QtCore.QTimer(self)
        timer.timeout.connect(self.update_figure)
        timer.start(1000)

    def compute_initial_figure(self):
        self.axes.plot([0, 1, 2, 3], [1, 2, 0, 4], 'r')

    def update_figure(self):
        # Build a list of 4 random integers between 0 and 10 (both inclusive)
        l = [random.randint(0, 10) for i in range(4)]

        self.axes.plot([0, 1, 2, 3], l, 'r')
        self.draw()
# ...

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.