开发者

matplotlib and pyqt4: update plot in one thread, draw in another

开发者 https://www.devze.com 2023-04-02 09:49 出处:网络
I\'m creating a live data monitor GUI with PyQt4 and matplotlib to create the plots.The GUI displays multiple plots at a time (around 6 or 7).To give the GUI thread more time and slightly better respo

I'm creating a live data monitor GUI with PyQt4 and matplotlib to create the plots. The GUI displays multiple plots at a time (around 6 or 7). To give the GUI thread more time and slightly better response time I only do the drawing canvas.draw() and all other plotting commands I do in the thread that updates the plot data. So in the non-GUI thread I do commands like line.set_ydata, ax.set_ylim, and other things that might need to be updated.

The two threads have access to the figure and canvas objects through a dictionary that is passed to the two threads at initialization. When the non-GUI thread gets data and updates the plot, it then signals the GUI thread to redraw the canvas using Qts signals (auto-connection). My threading experience tells me that I should use a lock or make sure the non-GUI thread is blocked on the redraw in some way, but in my coding rush I never put it in and forgot about it until now. The key point to this situation is that I want to see every update of the plot, not redraw in the middle开发者_如何学运维 of the other thread updating or even miss an update (if that makes sense). Currently, I think I'm just getting lucky with timing and things seem to be working ok.

Another thing that might be helpful to know is that I'm creating threads by moving a QObject to a QThread by using moveToThread.

My questions are:

  • Am I just getting lucky or is Qt just doing something magical?
  • What is the best way to accomplish the blocking on the matplotlib canvas/figure?

I should probably note that this was my first attempt at making the GUI more responsive (moving matplotlib commands into the data thread) and I may be moving to a blit animation style drawing of only updating the parts of the plot that change. But I'm still curious as to how lucky I am.

Thanks for any help.

Update/Clarification/Continuation from comments: I wanted the entire monitor system to be easily changed/updated by scientists who may only be familiar with matlab and maybe matplotlib. I'm not completely against changing to pyqwt for plotting for speed. And in terms of frames per second, I don't really need a lot at all since the data that is being plotted only comes in every 0.5 seconds nominal (0.2 seconds at the fastest). The GUI responsiveness just seems to "eat **" because there are so many plots. I've done a proof of concept hacking of my code with matplotlib blitting and it seems to help a ton, pyqwt will happen if needed. My previous questions still stand.


I stumbled upon similar problem. I had a lot of plots to draw (~250) so my GUI window would eventually show up as hanging in Windows. I modified figure class to perform plotting as a separate thread. The result - my GUI does not hang anymore, plot window shows up once the plotting is done. To use it, you create PlotDialog instance and call draw_plots method with plot_data argument. plot_data is a list of dictionaries, each dictionary represents subplot. Each subplot has following keys (and corresponding data): title, xlabel, ylabel and data. Hope that makes sense.

Here is my code:

import math

from PyQt4 import QtCore, QtGui

from matplotlib.figure import Figure
from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.backends.backend_qt4agg import NavigationToolbar2QT as NavigationToolbar


class MyFigure(Figure, QtCore.QThread):
    def __init__(self, parent, *args, **kwargs):
        QtCore.QThread.__init__(self, parent)
        Figure.__init__(self, *args, **kwargs)

        self.plot_data = list()

    def start_plotting_thread(self, plot_data, on_finish=None):
        """ Start plotting """
        self.plot_data = plot_data

        if on_finish is not None:
            self.finished.connect(on_finish)

        self.start()

    def run(self):
        """ Run as a thread """
        # Figure out rows and columns
        total_plots = len(self.plot_data)

        columns = int(math.sqrt(total_plots))
        if columns < 1:
            columns = 1

        rows = int(total_plots / columns)
        if (total_plots % columns) > 0:
            rows += 1
        if rows < 1:
            rows = 1

        # Plot Data
        for plot_index, _plot_data in enumerate(self.plot_data):
            plot_number = plot_index + 1
            args = (rows, columns, plot_number)
            kwargs = {
                'title': _plot_data['title'],
                'xlabel': _plot_data['xlabel'],
                'ylabel': _plot_data['ylabel']
            }

            figure = self.add_subplot(*args, **kwargs)

            figure.plot(_plot_data['data'])


class PlotDialog(QtGui.QDialog):
    def __init__(self, parent):
        super(PlotDialog, self).__init__(parent, QtCore.Qt.WindowMinMaxButtonsHint | QtCore.Qt.WindowCloseButtonHint)

        self.figure = MyFigure(self)
        self.canvas = FigureCanvas(self.figure)
        self.toolbar = NavigationToolbar(self.canvas, self)

        self.layout = QtGui.QGridLayout()
        self.setLayout(self.layout)

        layout = [
            [self.canvas],
            [self.toolbar],
        ]

        for row_index, columns in enumerate(layout):
            if type(columns) is list:
                for column_index, widget in enumerate(columns):
                    if widget is not None:
                        self.layout.addWidget(widget, row_index, column_index)

    def draw_plots(self, plot_data):
        """ Plot Plots """
        self.figure.start_plotting_thread(plot_data, on_finish=self.finish_drawing_plots)

    def finish_drawing_plots(self):
        """ Finish drawing plots """
        self.canvas.draw()
        self.show()
0

精彩评论

暂无评论...
验证码 换一张
取 消

关注公众号