在 pyqt 小部件中实现 pyqtgraph 多处理

时间:2023-03-12
本文介绍了在 pyqt 小部件中实现 pyqtgraph 多处理的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着跟版网的小编来一起学习吧!

问题描述

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

I am trying to plot images on a GUI that I am designing in Python. The full program will be collecting image data from a camera and then displaying the images on the GUI. I have explored using matplotlib, but it was too slow for my application. I need the plot to update rather quickly (preferably as fast as I can acquisition from the camera, once ever half second or second). This is challenging with pyqt because it seems that even when using a QThread, the plot can only update on the main thread, which causes my program to stop and the GUI to be inaccessible to the user.

我读到 pyqtgraph 被认为是一个比 matplotlib 快得多的绘图库.所以我试了一下,喜欢它,但是在显示图像的时候,它似乎和matplotlib有同样的问题.它停止整个 GUI.我做了一些研究并遇到了这个问题:Painting without paintEvent,其中一个答案建议使用Qt进程().因此,我的问题是是否有可能(如果可以,您能否提供一些示例代码)将 QtProcess() 实现到 GUI 中,即如何在一个单独的进程中运行 GUI 上的绘图?(另外,如果有办法用 matplotlib 做到这一点,那将非常有帮助.)

I read that pyqtgraph is suppose to be a much faster plotting library than matplotlib. So I gave it a try and like it, but it seems to have the same problem as matplotlib when displaying an image. It stops the entire GUI. I did some research and came across this question: Painting without paintEvent and one of the answers proposed the use of QtProcess(). Therefore, my question is whether it is possible (and if so, can you provide some example code) to implement QtProcess() into a GUI, i.e. how can I make a plot on a GUI run in a separate process? (Also, if there is a way to do this with matplotlib, that would be extremely helpful.)

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

Here is the simple example I designed to test out my problem. I took an example script from pyqtgraph's examples and imported a pyqtgraph plot into a pyqt widget. Then when the pushButton is pressed, the plot it displayed. If you run the script, you will notice that the first plot will take a long time to load (6 or 7 seconds). Press the button again and it seems to load much faster. I am doing all of the data generation in a QThread(), but the plotting seems to require the main thread to work even though it is done in the QThread. I want to do exactly the same thing as in this example, except use QtProcess() to handle the plotting.

欢迎任何有关绘图或可能替代方案的其他建议.谢谢

Any other suggestions about plotting or possible alternatives are welcome. Thanks

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

My attempt at trying to use QtProcess() (unsuccessful, I do not know how to import a QtProcess() widget into a 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 响应的一般方法有以下三种:

There are three general approaches to keeping your GUI responsive while doing work in the background:

  1. 计时器 -- 一切都在 GUI 线程中运行,您的工作通过计时器完成在 GUI 更新之间调用一些函数.这是最简单和最常见的方法,并且是使用 pyqtgraph 或 matplotlib 很可能就足够了(如果编写正确的话).缺点这种方法的一个原因是 如果 工作功能需要很长时间,GUI 将变得无响应.不过,从相机中提取数据并显示它不会给您带来问题.
  2. 线程 -- 如果你的工作函数变得太长,你可以移动一些工作到一个单独的线程.重要的是,您不能从不同的线程对 GUI 进行更改.做尽可能多的工作,然后将结果发送到要显示的 GUI 线程.
  3. Processes -- 这与使用线程的方法大致相同,但具有优势您可能会获得更好的 CPU 利用率(阅读有关 python 中的全局解释器锁的信息),并且缺点是进程之间的通信可能比线程之间的通信更麻烦.Pyqtgraph 具有内置的多处理功能,使这更容易(参见 pyqtgraph/examples/RemoteSpeedTest.py)
  1. Timers -- Everything operates within the GUI thread and your work is done by having a timer call some function in between GUI updates. This is the simplest and most common approach and is very likely to be adequate (if written correctly) using either pyqtgraph or matplotlib. The drawback of this approach is that if the work function takes a long time, the GUI will become unresponsive. Pulling data from a camera and displaying it should not cause you problems, though.
  2. Threads -- In the case that your work function becomes too long, you can move some of the work out to a separate thread. Importantly, you can NOT make changes to the GUI from a different thread. Do as much work as possible, then send the results to the GUI thread to be displayed.
  3. Processes -- This is more or less the same approach as using threads, but has the advantage that you may get better CPU utilization (read about the global interpreter lock in python), and the disadvantage that communication between processes can be more cumbersome than between threads. Pyqtgraph has built-in multiprocessing features to make this easier (see pyqtgraph/examples/RemoteSpeedTest.py)

这个话题已经在别处广泛讨论过,所以我就不说了.

This topic is discussed extensively elsewhere, so I'll leave it at that.

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

With that in mind, there are a few problems with the code you have posted:

  1. 您正在生成并显示一个 3D 数据集(100 帧视频),每帧更新一次.这就是它似乎更新如此缓慢的原因.请参阅 pyqtgraph/examples/ImageItem.py 和VideoSpeedTest.py 以获取正确完成的视频示例.
  2. 您正在从工作线程调用 setImage;这有时可能会起作用,但预计它会崩溃.更好的方法是在worker中生成数据,然后将数据发送到主GUI要显示的线程.Qt 文档深入讨论了线程.(但正如我提到的之前,完全避免线程会更好)
  1. You are generating and displaying a 3D data set (100-frame video) with every frame update. This is the reason it appears to update so slowly. See pyqtgraph/examples/ImageItem.py and VideoSpeedTest.py for examples of video done correctly.
  2. You are calling setImage from the worker thread; this may work sometimes, but expect it to crash. A better approach is to generate the data in the worker, then send the data to the main GUI thread to be displayed. The Qt documentation discusses threading in depth. (but as I mentioned before, avoiding threads altogether would be even better)

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

Finally, one important rule to follow: If you are having performance issues, profile your code. You cannot fix the problem until you know what is causing it.

这篇关于在 pyqt 小部件中实现 pyqtgraph 多处理的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持跟版网!

上一篇:与 multiprocessing.Pool 共享一个计数器 下一篇:python3.x 多处理循环没有“if __name__ == '__main__':"

相关文章