【问题标题】:matplotlib and pyqt4: update plot in one thread, draw in anothermatplotlib 和 pyqt4:在一个线程中更新绘图,在另一个线程中绘制
【发布时间】:2011-11-09 18:49:07
【问题描述】:

我正在使用 PyQt4 和 matplotlib 创建一个实时数据监视器 GUI 来创建绘图。 GUI 一次显示多个图(大约 6 或 7 个)。为了给 GUI 线程更多的时间和稍微更好的响应时间,我只执行绘图 canvas.draw() 以及我在更新绘图数据的线程中执行的所有其他绘图命令。因此,在非 GUI 线程中,我执行 line.set_ydataax.set_ylim 之类的命令以及其他可能需要更新的内容。

两个线程可以通过在初始化时传递给两个线程的字典来访问图形和画布对象。当非 GUI 线程获取数据并更新绘图时,它会向 GUI 线程发出信号以使用 Qts 信号(自动连接)重新绘制画布。我的线程经验告诉我,我应该使用锁或确保以某种方式在重绘时阻塞非 GUI 线程,但在我的编码匆忙中,我从来没有把它放进去,直到现在才忘记它。这种情况的关键是我希望看到情节的每次更新,而不是在其他线程更新的中间重绘,甚至错过更新(如果有意义的话)。目前,我想我只是在时机上很幸运,而且一切似乎都很好。

另一件可能有助于了解的事情是,我通过使用 moveToThread 将 QObject 移动到 QThread 来创建线程。

我的问题是:

  • 是我运气好还是 Qt 只是做了一些神奇的事情?
  • 在 matplotlib 画布/图形上完成阻塞的最佳方法是什么?

我可能应该注意到,这是我第一次尝试使 GUI 更具响应性(将 matplotlib 命令移动到数据线程中),并且我可能正在转向只更新绘图中发生变化的部分的 blit 动画样式绘图。但我仍然很好奇我有多幸运。

感谢您的帮助。

来自 cmets 的更新/澄清/继续:我希望可能只熟悉 matlab 和可能 matplotlib 的科学家能够轻松更改/更新整个监​​控系统。我并不完全反对更改为 pyqwt 以提高速度。就每秒帧数而言,我真的不需要太多,因为正在绘制的数据仅在标称每 0.5 秒出现一次(最快为 0.2 秒)。 GUI 响应似乎只是“吃掉了 **”,因为有太多的情节。我已经用 matplotlib blitting 对我的代码进行了概念验证,它似乎有很大帮助,如果需要,pyqwt 会发生。我之前的问题仍然存在。

【问题讨论】:

  • matplotlib 在不使用 blitting 的情况下非常慢。即使有了它,对于数据监控,我建议使用其他东西。看看packages.python.org/guiqwt/index.html
  • 我确实看过 qwt,因为很多人说它比 matplotlib 快得多。我不想马上转向它的原因是为了让熟悉 matplotlib(实际上是 matlab)的人(科学家)更容易将绘图添加到整体数据监视器包中。而且我没有过多研究它,但如果它不太难,也许我会制作一个类似于使用 matplotlib 的界面。
  • 我非常喜欢 matplotlib,我将它用于我所有的出版物图形。但是对于交互式使用,您必须使用 blitting 来获得合理的速度,这很麻烦,否则速度对于我的需求来说太差了(多个绘图大约 20 fps)。
  • 现在实现类似的东西。戴夫,你认为你可以分享一些代码吗?这是我的想法。您必须使用多个类,一个基于 PyQt 的 QDialog 或类似的 GUI 输出。另一个是基于 QThread 的工人阶级。一旦 QThread 完成绘图,它将使用 PyQt 信号引擎将图形和画布实例传递给 GUI 对象。我尝试将 QDialog 和 QThread 组合成一个类,但显然你做不到。 (我相信你可以做到,但使用 2 个单独的类更容易)
  • @Barmaley 这个问题已经有将近 5 年的历史了。我不确定我的代码是否适用于较新版本的 mpl。我建议您对应该如何使用 Qt 线程(由 QThread“拥有”的 QObject)进行更多研究。您可能不想通过信号传递图形和画布。它们是大型复杂对象,画布实际上是一个 GUI 元素,因此它保留在 GUI 线程中。另外,为什么是 QDialog?只需在其中制作一个带有 mpl 画布的普通 QWidget。经过一些研究,如果您仍然需要指导,您应该提出自己的 stackoverflow 问题或询问 mpl 邮件列表。

标签: python qt4 matplotlib pyqt4


【解决方案1】:

我偶然发现了类似的问题。我有很多要绘制的图(~250),所以我的 GUI 窗口最终会在 Windows 中显示为挂起。 我修改了图形类以将绘图作为单独的线程执行。 结果 - 我的 GUI 不再挂起,绘图完成后会显示绘图窗口。要使用它,您需要创建 PlotDialog 实例并使用 plot_data 参数调用 draw_plots 方法。 plot_data 是一个字典列表,每个字典代表子图。每个子图都有以下键(和相应的数据):titlexlabelylabeldata。希望这是有道理的。

这是我的代码:

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()

【讨论】:

  • 你可能也想看看 vispy,因为它应该能够处理大量这样的情节。
  • 我会看看的。我之前没有在 python 中做过很多绘图,所以 matplotlib 似乎是一个不错的选择。
猜你喜欢
  • 2017-10-08
  • 2016-07-10
  • 2013-04-23
  • 1970-01-01
  • 1970-01-01
  • 2016-09-26
  • 2014-06-28
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多