3

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

Я читал, что pyqtgraph предположительно является гораздо более быстрой графической библиотекой, чем matplotlib. Поэтому я дал ему попробовать и понравилось, но у него, похоже, такая же проблема, как и matplotlib при отображении изображения. Он останавливает весь графический интерфейс. Я провел некоторое исследование и наткнулся на этот вопрос: Painting without 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_()) 

Моя попытка пытается использовать 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_()) 

ответ

9

Есть три общие подход к поддержанию вашего GUI реагировать при выполнении работы в фоновом режиме:

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

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

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

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

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

Смежные вопросы