python - 将 pyqtgraph 多处理实现到 pyqt 小部件中

标签 python multithreading matplotlib multiprocessing pyqtgraph

我正在尝试在用 Python 设计的 GUI 上绘制图像。完整的程序将从相机收集图像数据,然后在 GUI 上显示图像。我探索过使用 matplotlib,但它对我的应用程序来说太慢了。我需要情节更新得相当快(最好尽可能快地从相机中获取,每半秒或秒一次)。这对 pyqt 来说是一个挑战,因为似乎即使在使用 QThread 时,绘图也只能在主线程上更新,这会导致我的程序停止并且用户无法访问 GUI。

我读到 pyqtgraph 应该是一个比 matplotlib 快得多的绘图库。于是尝试了一下,喜欢上了,但是在显示图片的时候好像和matplotlib一样的问题。它会停止整个 GUI。我做了一些研究并遇到了这个问题:Painting without paintEvent其中一个答案建议使用 QtProcess()。因此,我的问题是是否有可能(如果可以,您能否提供一些示例代码)将 QtProcess() 实现到 GUI 中,即如何在单独的进程中运行 GUI 上绘图? (此外,如果有一种方法可以使用 matplotlib 执行此操作,那将非常有帮助。)

这是我设计用来测试我的问题的简单示例。我从 pyqtgraph 的示例中获取了一个示例脚本,并将 pyqtgraph 绘图导入到 pyqt 小部件中。然后当按下按钮时,它显示的情节。如果您运行该脚本,您会注意到第一个绘图需要很长时间才能加载(6 或 7 秒)。再次按下按钮,它似乎加载得更快。我在 QThread() 中完成所有数据生成,但绘图似乎需要主线程才能工作,即使它是在 QThread 中完成的。除了使用 QtProcess() 来处理绘图之外,我想做与本例中完全相同的事情。

欢迎就绘图或可能的替代方案提出任何其他建议。谢谢

GUI 脚本:

# -*- coding: utf-8 -*-

# Form implementation generated from reading ui file 'GUI.ui'
#
# Created: Fri Jun 28 14:40:22 2013
#      by: PyQt4 UI code generator 4.9.5
#
# WARNING! All changes made in this file will be lost!

from PyQt4 import QtCore, QtGui

try:
    _fromUtf8 = QtCore.QString.fromUtf8
except AttributeError:
    _fromUtf8 = lambda s: s

class Ui_MainWindow(object):
    def setupUi(self, MainWindow):
        MainWindow.setObjectName(_fromUtf8("MainWindow"))
        MainWindow.resize(800, 534)
        self.centralwidget = QtGui.QWidget(MainWindow)
        self.centralwidget.setObjectName(_fromUtf8("centralwidget"))
        self.gridLayout_2 = QtGui.QGridLayout(self.centralwidget)
        self.gridLayout_2.setObjectName(_fromUtf8("gridLayout_2"))
        self.gridLayout = QtGui.QGridLayout()
        self.gridLayout.setObjectName(_fromUtf8("gridLayout"))
        self.scrollArea = QtGui.QScrollArea(self.centralwidget)
        self.scrollArea.setFrameShape(QtGui.QFrame.NoFrame)
        self.scrollArea.setFrameShadow(QtGui.QFrame.Plain)
        self.scrollArea.setWidgetResizable(True)
        self.scrollArea.setObjectName(_fromUtf8("scrollArea"))
        self.scrollAreaWidgetContents = QtGui.QWidget()
        self.scrollAreaWidgetContents.setGeometry(QtCore.QRect(0, 0, 780, 514))
        self.scrollAreaWidgetContents.setObjectName(_fromUtf8("scrollAreaWidgetContents"))
        self.widgetPlot = QtGui.QWidget(self.scrollAreaWidgetContents)
        self.widgetPlot.setGeometry(QtCore.QRect(20, 10, 741, 451))
        self.widgetPlot.setObjectName(_fromUtf8("widgetPlot"))
        self.pushButtonPlot = QtGui.QPushButton(self.scrollAreaWidgetContents)
        self.pushButtonPlot.setGeometry(QtCore.QRect(340, 480, 75, 23))
        self.pushButtonPlot.setObjectName(_fromUtf8("pushButtonPlot"))
        self.scrollArea.setWidget(self.scrollAreaWidgetContents)
        self.gridLayout.addWidget(self.scrollArea, 1, 0, 1, 1)
        self.gridLayout_2.addLayout(self.gridLayout, 0, 0, 1, 1)
        MainWindow.setCentralWidget(self.centralwidget)

        self.retranslateUi(MainWindow)
        QtCore.QMetaObject.connectSlotsByName(MainWindow)

    def retranslateUi(self, MainWindow):
        MainWindow.setWindowTitle(QtGui.QApplication.translate("MainWindow", "MainWindow", None, QtGui.QApplication.UnicodeUTF8))
        self.pushButtonPlot.setText(QtGui.QApplication.translate("MainWindow", "Plot", None, QtGui.QApplication.UnicodeUTF8))

包装器:

import numpy as np
import scipy

from pyqtgraph.Qt import QtCore, QtGui
import pyqtgraph as pg

import sys

from PyQt4.QtCore import * 
from PyQt4.QtGui import *

from GUI import Ui_MainWindow


class MyForm(QMainWindow):
    def __init__(self, parent=None):
        QWidget.__init__(self, parent)
        self.ui = Ui_MainWindow()
        self.ui.setupUi(self)

        self.imv = pg.ImageView()

        vbox = QVBoxLayout()
        vbox.addWidget(self.imv)
        self.ui.widgetPlot.setLayout(vbox)

        self.plot_thread = plotData()

        self.connect(self.ui.pushButtonPlot, SIGNAL("clicked()"), lambda : self.plot_thread.input(self.imv))

    def threadPlot(self):
        self.plot_thread.input(self.imv)


    def plot(self):
        ## Create random 3D data set with noisy signals
        self.img = scipy.ndimage.gaussian_filter(np.random.normal(size=(200, 200)), (5, 5)) * 20 + 100
        self.img = self.img[np.newaxis,:,:]
        decay = np.exp(-np.linspace(0,0.3,100))[:,np.newaxis,np.newaxis]
        data = np.random.normal(size=(100, 200, 200))
        data += self.img * decay
        data += 2

        ## Add time-varying signal
        sig = np.zeros(data.shape[0])
        sig[30:] += np.exp(-np.linspace(1,10, 70))
        sig[40:] += np.exp(-np.linspace(1,10, 60))
        sig[70:] += np.exp(-np.linspace(1,10, 30))

        sig = sig[:,np.newaxis,np.newaxis] * 3
        data[:,50:60,50:60] += sig

        self.imv.setImage(data, xvals=np.linspace(1., 3., data.shape[0]))


class plotData(QThread):
    def __init__(self,parent=None):
        QThread.__init__(self,parent)
        self.exiting = False
    def input(self, imv):
        self.imv = imv
        self.start()

    def collectImage(self):
        ## Create random 3D data set with noisy signals
        self.img = scipy.ndimage.gaussian_filter(np.random.normal(size=(200, 200)), (5, 5)) * 20 + 100
        self.img = self.img[np.newaxis,:,:]
        decay = np.exp(-np.linspace(0,0.3,100))[:,np.newaxis,np.newaxis]
        data = np.random.normal(size=(100, 200, 200))
        data += self.img * decay
        data += 2

        ## Add time-varying signal
        sig = np.zeros(data.shape[0])
        sig[30:] += np.exp(-np.linspace(1,10, 70))
        sig[40:] += np.exp(-np.linspace(1,10, 60))
        sig[70:] += np.exp(-np.linspace(1,10, 30))

        sig = sig[:,np.newaxis,np.newaxis] * 3
        data[:,50:60,50:60] += sig

        self.imv.setImage(data, xvals=np.linspace(1., 3., data.shape[0]))

    def run(self):

        self.collectImage()
        self.emit(SIGNAL("Done"))


app = QApplication(sys.argv)
myapp = MyForm()

myapp.show()
sys.exit(app.exec_())

我尝试使用 QtProcess()(不成功,我不知道如何将 QtProcess() 小部件导入 Qt GUI):

import numpy as np
import scipy

from pyqtgraph.Qt import QtCore, QtGui
import pyqtgraph as pg

import sys

from PyQt4.QtCore import * 
from PyQt4.QtGui import *

from GUI import Ui_MainWindow


class MyForm(QMainWindow):
    def __init__(self, parent=None):
        QWidget.__init__(self, parent)
        self.ui = Ui_MainWindow()
        self.ui.setupUi(self)

        self.proc = mp.QtProcess()

        self.remotepg = self.proc._import('pyqtgraph')
        self.win = self.remotepg.plot()

        self.imv = self.win.plot([1,4,2,3], [4,6,3,4], pen=None, symbol='o')

        vbox = QVBoxLayout()
        vbox.addWidget(self.imv)
        self.ui.widgetPlot.setLayout(vbox)

app = QApplication(sys.argv)
myapp = MyForm()

myapp.show()
sys.exit(app.exec_())

最佳答案

在后台工作时保持 GUI 响应的一般方法有以下三种:

  1. Timers -- 一切都在 GUI 线程中运行,您的工作是通过计时器完成的 在 GUI 更新之间调用一些函数。这是最简单和最常见的方法,并且是 使用 pyqtgraph 或 matplotlib 很可能就足够了(如果编写正确)。缺点 这种方法的一个缺点是,如果工作函数需要很长时间,GUI 将变得无响应。 不过,从相机中提取数据并显示它应该不会给您带来问题。
  2. Threads -- 如果你的工作函数变得太长,你可以移动一些工作 到一个单独的线程。重要的是,您不能从不同的线程更改 GUI。 做尽可能多的工作,然后将结果发送到 GUI 线程进行显示。
  3. Processes -- 这与使用线程的方法大致相同,但具有优势 您可能会获得更好的 CPU 利用率(阅读 python 中的全局解释器锁),以及 缺点是进程之间的通信比线程之间的通信更麻烦。 Pyqtgraph 具有内置的多处理功能,使这更容易 (参见 pyqtgraph/examples/RemoteSpeedTest.py)

这个话题在别处被广泛讨论,所以我就此打住。

考虑到这一点,您发布的代码存在一些问题:

  1. 您正在生成并显示每帧更新的 3D 数据集(100 帧视频)。 这就是它看起来更新如此缓慢的原因。参见 pyqtgraph/examples/ImageItem.py 和 VideoSpeedTest.py 用于正确完成视频的示例。
  2. 您正在从工作线程调用 setImage;这有时可能会起作用,但预计它会崩溃。 更好的方法是在 worker 中生成数据,然后将数据发送到主 GUI 要显示的线程。 Qt 文档深入讨论了线程。 (但正如我提到的 以前,完全避免线程会更好)

最后,要遵循一个重要规则:如果您遇到性能问题,分析您的代码。在知道问题的原因之前,您无法解决问题。

关于python - 将 pyqtgraph 多处理实现到 pyqt 小部件中,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/17424009/

相关文章:

Python:别名为python3

python - 导入声明全局变量的函数

python - 如何使用 matplotlib 绘制盒须图

python - "TypeError"在 Python 中使用 matplotlib 制作散点图

python - Matplotlib 标签重叠

java - 调用 java "from Python"时分配超过 4 GB 的堆空间时出现问题

python - 在线版scikit-learn的TfidfVectorizer

C++外部程序IO

c# - sqlite 在锁定或异步时的正确用法是什么

java - Pulsar 消息监听器的多线程