matplotlib 和 pyqt4:在一个线程中更新绘图,在另一个线程中绘制

发布于 2024-12-02 22:56:40 字数 1073 浏览 1 评论 0原文

我正在使用 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 动画风格绘图。但我还是很好奇我有多幸运。

感谢您的任何帮助。

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

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.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

窗影残 2024-12-09 22:56:40

我偶然发现了类似的问题。我有很多图要绘制(~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()

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()
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文