Реализация многопроцессорной обработки pyqtgraph в виджет pyqt
Я пытаюсь нанести изображения на графический интерфейс, который я проектирую на Python. Полная программа будет собирать данные изображения с камеры и затем отображать изображения в графическом интерфейсе. Я исследовал использование matplotlib, но это было слишком медленно для моего приложения. Мне нужно, чтобы сюжет обновлялся довольно быстро (желательно настолько быстро, насколько я могу получить с камеры, раз в полсекунды или секунды). Это сложно с pyqt, потому что кажется, что даже при использовании QThread график может обновляться только в главном потоке, что приводит к остановке моей программы и недоступности пользователя для GUI.
Я читал, что pyqtgraph должен быть намного более быстрой библиотекой, чем matplotlib. Так что я попробовал и мне понравилось, но, похоже, та же проблема, что и у matplotlib при отображении изображения. Это останавливает весь графический интерфейс. Я провел небольшое исследование и наткнулся на этот вопрос: рисование без paintEvent, и в одном из ответов предлагалось использовать QtProcess(). Поэтому мой вопрос заключается в том, возможно ли (и если да, можете ли вы предоставить пример кода) реализовать QtProcess () в графическом интерфейсе, то есть, как я могу сделать график на графическом интерфейсе пользователя запущенным в отдельном процессе? (Также, если есть способ сделать это с помощью 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_())
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_())
1 ответ
Существует три основных подхода к поддержанию отзывчивости вашего GUI при работе в фоновом режиме:
- Таймеры - все работает в потоке GUI, и ваша работа выполняется с помощью вызова таймера некоторой функции между обновлениями GUI. Это самый простой и наиболее распространенный подход, и вполне вероятно, что он будет адекватным (если он написан правильно) с использованием pyqtgraph или matplotlib. Недостаток этого подхода заключается в том, что если рабочая функция занимает много времени, графический интерфейс пользователя перестает отвечать на запросы. Однако получение данных с камеры и их отображение не должно вызывать проблем.
- Потоки - В случае, если ваша рабочая функция становится слишком длинной, вы можете переместить часть работы в отдельный поток. Важно отметить, что вы НЕ можете вносить изменения в графический интерфейс из другого потока. Сделайте как можно больше работы, затем отправьте результаты в поток графического интерфейса для отображения.
- Процессы - это более или менее тот же подход, что и при использовании потоков, но имеет то преимущество, что вы можете получить лучшее использование процессора (читайте о глобальной блокировке интерпретатора в python), и недостатком является то, что связь между процессами может быть более громоздкой, чем между потоки. Pyqtgraph имеет встроенные функции многопроцессорной обработки, чтобы упростить эту задачу (см. Pyqtgraph/examples/RemoteSpeedTest.py).
Эта тема широко обсуждается в другом месте, поэтому я оставлю это на этом.
Имея это в виду, есть несколько проблем с кодом, который вы опубликовали:
- Вы генерируете и отображаете 3D- набор данных (100-кадровое видео) с каждым обновлением кадра. Это причина, по которой он, кажется, обновляется так медленно. Посмотрите pyqtgraph/examples/ImageItem.py и VideoSpeedTest.py для примеров видео, сделанного правильно.
- Вы вызываете setImage из рабочего потока; это может иногда работать, но ожидать, что это потерпит крах. Лучшим подходом является создание данных на рабочем месте, а затем отправка данных в основной поток графического интерфейса для отображения. Документация Qt подробно описывает потоки. (но, как я уже упоминал ранее, вообще избегать потоков было бы еще лучше)
Наконец, следует соблюдать одно важное правило: если у вас есть проблемы с производительностью, профилируйте свой код. Вы не можете решить проблему, пока не узнаете, что ее вызывает.