Внедрение многопроцессорной обработки pyqtgraph в виджет pyqt

Я пытаюсь нанести изображения на графический интерфейс, который я разрабатываю на Python. Полная программа будет собирать данные изображения с камеры, а затем отображать изображения в графическом интерфейсе. Я пробовал использовать matplotlib, но это было слишком медленно для моего приложения. Мне нужно, чтобы график обновлялся довольно быстро (желательно так быстро, как я могу снимать с камеры, один раз за полсекунды или секунды). Это сложно с pyqt, потому что кажется, что даже при использовании QThread график может обновляться только в основном потоке, из-за чего моя программа останавливается, а графический интерфейс становится недоступным для пользователя.

Я читал, что pyqtgraph должен быть гораздо более быстрой библиотекой для построения графиков, чем matplotlib. Итак, я попробовал, и мне понравилось, но, похоже, у него та же проблема, что и у matplotlib при отображении изображения. Он останавливает весь графический интерфейс. Я провел небольшое исследование и наткнулся на этот вопрос: Рисование без paintEvent, и в одном из ответов предлагалось использовать QtProcess (). Поэтому у меня вопрос: возможно ли (и если да, можете ли вы предоставить пример кода) реализовать QtProcess () в графическом интерфейсе, то есть как я могу сделать график в графическом интерфейсе, запускаемый в отдельном процессе? (Кроме того, если есть способ сделать это с помощью matplotlib, это было бы чрезвычайно полезно.)

Вот простой пример, который я разработал, чтобы проверить свою проблему. Я взял пример сценария из примеров pyqtgraph и импортировал график pyqtgraph в виджет pyqt. Затем при нажатии кнопки отображается график. Если вы запустите сценарий, вы заметите, что загрузка первого графика займет много времени (6 или 7 секунд). Нажмите кнопку еще раз, и кажется, что он загружается намного быстрее. Я делаю всю генерацию данных в QThread (), но для построения графика, похоже, требуется, чтобы основной поток работал, даже если он выполняется в QThread. Я хочу сделать то же самое, что и в этом примере, за исключением использования QtProcess () для обработки построения.

Любые другие предложения по поводу построения графика или возможных альтернатив приветствуются. Спасибо

Сценарий графического интерфейса:

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

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

person Mink    schedule 02.07.2013    source источник


Ответы (1)


Есть три общих подхода к тому, чтобы ваш графический интерфейс оставался отзывчивым при работе в фоновом режиме:

  1. Таймеры - все работает в потоке графического интерфейса, и ваша работа выполняется с помощью таймера, вызывающего некоторую функцию между обновлениями графического интерфейса. Это самый простой и наиболее распространенный подход, который, скорее всего, будет адекватным (если он написан правильно) с использованием pyqtgraph или matplotlib. Недостатком этого подхода является то, что если рабочая функция занимает много времени, графический интерфейс перестанет отвечать. Однако получение данных с камеры и их отображение не должно вызывать у вас проблем.
  2. Темы. В случае, если ваша рабочая функция становится слишком длинной, вы можете переместить часть работы в отдельный поток. Важно отметить, что вы НЕ можете вносить изменения в графический интерфейс из другого потока. Сделайте как можно больше работы, а затем отправьте результаты в поток графического интерфейса для отображения.
  3. Процессы - это более или менее тот же подход, что и использование потоков, но имеет то преимущество, что вы можете лучше использовать ЦП (читайте о глобальной блокировке интерпретатора в python), а также недостаток, заключающийся в том, что связь между процессы могут быть более громоздкими, чем между потоками. Pyqtgraph имеет встроенные функции многопроцессорности, чтобы упростить это (см. Pyqtgraph / examples / RemoteSpeedTest.py)

Эта тема широко обсуждается в другом месте, поэтому я оставлю все как есть.

Имея это в виду, есть несколько проблем с опубликованным вами кодом:

  1. Вы создаете и отображаете набор данных 3D (100-кадровое видео) при каждом обновлении кадра. Это причина того, что он обновляется так медленно. См. Pyqtgraph / examples / ImageItem.py и VideoSpeedTest.py для примеров правильно выполненного видео.
  2. Вы вызываете setImage из рабочего потока; иногда это может сработать, но ожидайте сбоя. Лучший подход - сгенерировать данные в рабочем потоке, а затем отправить данные в основной поток графического интерфейса для отображения. В документации Qt подробно обсуждается многопоточность. (но, как я уже упоминал ранее, было бы даже лучше вообще избегать потоков)

Наконец, одно важное правило, которому нужно следовать: если у вас проблемы с производительностью, профилируйте свой код. Вы не можете решить проблему, пока не узнаете, что ее вызывает.

person Luke    schedule 02.07.2013