【问题标题】:PySide2 and Matplotlib: How to make MatPlotLib run in a separate Process? ..as it cannot run in a separate ThreadPySide2 和 Matplotlib:如何让 MatPlotLib 在单独的进程中运行? ..因为它不能在单独的线程中运行
【发布时间】:2019-09-24 08:10:53
【问题描述】:

我不是经验丰富的程序员,我正在尝试使用 Qt for python (PySide2) 在 python 中创建一种数据记录器程序来构建 GUI。我能够使用 Designer 创建一个 gui 并将其加载到 python 中。 gui 现在只是一个空白窗口。然后我创建了一个在显示图形的窗口中启动 MatplotLib 的函数,并使用 Qt 计时器更新主程序的每个循环中的数据。

一切正常,但 MatPlotLib 的重绘时间太慢了 gui 刷新。所以我尝试将 MatPlotLib 放在一个单独的线程中,经过大量试验后我明白它不能在单独的线程中运行.. 最后我决定尝试使用多处理。现在 MatPlotLib 在单独的进程中运行良好(我使用队列将数据发送到 MatPlotLib)并在进程完成后正确退出,但是当我关闭主窗口时,程序更新完全关闭,并且还键入 Ctrl+C 提示是被屏蔽了。

这是我的代码:

#!/usr/bin/env python3
import sys
from PySide2.QtUiTools import QUiLoader
from PySide2.QtWidgets import QApplication, QWidget
from PySide2.QtCore import QFile, QTimer

import matplotlib.pyplot as plt
from multiprocessing import Process, Queue, freeze_support
import random


class DSL(QWidget):
    def __init__(self):
        # LOAD HMI
        QWidget.__init__(self)
        designer_file = QFile('userInterface.ui')
        designer_file.open(QFile.ReadOnly)
        loader = QUiLoader()
        self.ui = loader.load(designer_file, self)
        designer_file.close()
        self.ui.show()

        # Data to be visualized
        self.data = []

    def mainLoop(self):
        self.data = []
        for i in range(10):
            self.data.append(random.randint(0, 10))

        # Send data to graph process
        queue.put(self.data)

        # LOOP repeater
        QTimer.singleShot(10, self.mainLoop)


def graphProcess(queue):
    for i in range(10):
        # Get data
        data = queue.get()

        # MatPlotLib
        plt.ion()
        plt.clf()
        plt.plot(data)
        plt.show()
        plt.pause(0.1)

    print('process end')


if __name__ == '__main__':
    # MatPlotLib Process
    queue = Queue()
    freeze_support()
    p = Process(target=graphProcess, args=(queue,))
    p.daemon = True
    p.start()

    # PySide2 Process
    app = QApplication(sys.argv)
    dsl = DSL()
    dsl.mainLoop()
    sys.exit(app.exec_())

【问题讨论】:

    标签: python matplotlib multiprocessing pyside2


    【解决方案1】:

    与其在辅助进程中使用 matplotlib 不如在 QWidget 中嵌入一个画布,使其可以在相同的 PySide2 进程中运行:

    #!/usr/bin/env python3
    import sys
    
    from PySide2.QtCore import QFile, QObject, Signal, Slot, QTimer
    from PySide2.QtWidgets import QApplication, QVBoxLayout, QWidget
    from PySide2.QtUiTools import QUiLoader
    
    import matplotlib.pyplot as plt
    from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
    from matplotlib.backends.backend_qt5agg import NavigationToolbar2QT as NavigationToolbar
    from matplotlib.figure import Figure
    
    import random
    
    
    class DSL(QObject):
        dataChanged = Signal(list)
    
        def __init__(self, parent=None):
            # LOAD HMI
            super().__init__(parent)
            designer_file = QFile("userInterface.ui")
            if designer_file.open(QFile.ReadOnly):
                loader = QUiLoader()
                self.ui = loader.load(designer_file)
                designer_file.close()
                self.ui.show()
            # Data to be visualized
            self.data = []
    
        def mainLoop(self):
            self.data = []
            for i in range(10):
                self.data.append(random.randint(0, 10))
            # Send data to graph
            self.dataChanged.emit(self.data)
            # LOOP repeater
            QTimer.singleShot(10, self.mainLoop)
    
    
    class MatplotlibWidget(QWidget):
        def __init__(self, parent=None):
            super().__init__(parent)
    
            fig = Figure(figsize=(7, 5), dpi=65, facecolor=(1, 1, 1), edgecolor=(0, 0, 0))
            self.canvas = FigureCanvas(fig)
            self.toolbar = NavigationToolbar(self.canvas, self)
            lay = QVBoxLayout(self)
            lay.addWidget(self.toolbar)
            lay.addWidget(self.canvas)
    
            self.ax = fig.add_subplot(111)
            self.line, *_ = self.ax.plot([])
    
        @Slot(list)
        def update_plot(self, data):
            self.line.set_data(range(len(data)), data)
    
            self.ax.set_xlim(0, len(data))
            self.ax.set_ylim(min(data), max(data))
            self.canvas.draw()
    
    
    if __name__ == "__main__":
    
        app = QApplication(sys.argv)
        dsl = DSL()
        dsl.mainLoop()
    
        matplotlib_widget = MatplotlibWidget()
        matplotlib_widget.show()
    
        dsl.dataChanged.connect(matplotlib_widget.update_plot)
        sys.exit(app.exec_())
    

    【讨论】:

    • 嗨 eyllanesc,非常感谢您的快速回答,这是我第一次使用 stackoverflow,我很高兴它的帮助。您编写的代码完美运行,这对我来说是向前迈出的一大步。我有一些问题想请教您的选择:
    • - 我的选择不适合你吗? (了解我的错误..) - 是否可以同时在图中绘制更多线(我需要两条) - 如果绘图窗口关闭,我使用按钮再次显示绘图窗口,使用 matplotlib_widget.show(),是吗对 ? - 为什么如果我关闭主窗口,grap 会继续绘制?
    • @Emilio 1) 我不会指出它是否正确,但正如您所见,还有另一个更简单的选择。 2)如果可以绘制任何图,在下面的链接中有一个示例:gist.github.com/eyllanesc/e5a2f0c86d80f4a2f241d6e3584032a9,3)是的,它是正确的,代码必须是:your_button.clicked.connect(matplotlib_widget.show) 4)尝试使用此功能的链接示例
    • /--------- 问题 ----------/ \n 不幸的是,更深入地测试你的代码,我在第一次实现时遇到了同样的问题创建绘图的代码只是在 DSL 类的一个函数中。如果你放更多、更多的点(例如 1000+),整个程序会变慢,包括主 GUI。这是因为正如您所说,两个窗口都在同一个进程中。这就是我试图避免的多处理。我在尝试多处理之前的代码是:
    • @Emilio 问题在于你的 for 循环耗时,检查链接的更新内容,你会发现它的作用相同,但效率更高。总之,分析瓶颈在哪里,尝试改进它,不要责怪代码的另一部分。
    猜你喜欢
    • 1970-01-01
    • 2023-03-27
    • 1970-01-01
    • 1970-01-01
    • 2013-09-26
    • 2019-08-12
    • 2015-05-20
    • 1970-01-01
    • 2012-05-20
    相关资源
    最近更新 更多