time.sleep() требуется, чтобы QThread реагировал?
Во-первых, я новичок в Python. Я давний пользователь MatLab (инженер, а не специалист по компьютерам), и я начинаю процесс попытки использовать Python, NumPy, SciPy и т. Д. В моем рабочем процессе. Прошу прощения за очевидное незнание того, что такое замечательный язык программирования!
В качестве своей первой попытки я решил создать приложение для взаимодействия с разрабатываемым датчиком. Датчик имеет микросекундное разрешение (данные из 512 пикселей высокой энергии и 512 "пикселей" низкой энергии каждые 500 микросекунд), но ввод / вывод будет блокироваться. Поскольку я буду постоянно опрашивать устройство, я знаю, что многопоточность будет важна, чтобы поддерживать отзывчивость GUI (GUI, в конечном счете, также интегрирует последовательную связь с другим устройством и будет иметь подпрограмму обработки изображений, которая работает с данными датчика). Я создал многопоточный экземпляр MatPlotLib для отображения этих данных в режиме реального времени с датчика. Хотя я построил модуль, который взаимодействует с датчиком независимо и проверил, что я знаю, как это сделать в Python, я начинаю здесь просто с "симуляции" данных, генерируя 512 случайных чисел от 8 до 12 для низкой энергии "пикселей" и 512 случайных чисел от 90 до 110 для "энергичных" пикселей. Это то, что с резьбой. Работая со многими примерами здесь, я также научился использовать блиттинг, чтобы получить достаточно быстрое обновление экрана с MatPlotLib - НО, проблема в том, что если я не использую, переведите многопоточный процесс в спящий режим на 20 мс, используя time.sleep(0.02)
GUI не отвечает. Это можно проверить, потому что интерактивная обратная связь с точками данных X,Y от MatPlotLib не работает и кнопка "STOP" не может быть использована для прерывания процесса. Что-нибудь дольше чем time.sleep(0.02)
делает графический интерфейс еще более плавным, но за счет "скорости передачи данных". Что-нибудь медленнее, чем time.sleep(0.02)
делает графический интерфейс не отвечает. Я не уверен, что понимаю почему. Я собирался уйти и попытаться использовать GUIqwt вместо этого, но подумал, что я хотел бы спросить здесь, прежде чем отказаться от MatPlotLib, так как я не уверен, что это даже проблема. Я обеспокоен тем, что перевод потока в спящий режим на 20 мс будет означать, что я пропущу не менее 40 потенциальных строк данных из массива датчиков (40 строк * 500 мкс / строка = 20 мс).
Вот текущий код:
import time, random, sys
from PyQt4.QtCore import *
from PyQt4.QtGui import *
from matplotlib.figure import Figure
from matplotlib.backends.backend_qt4agg import NavigationToolbar2QTAgg as NavigationToolbar
from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg as FigureCanvas
class ApplicationWindow(QMainWindow):
def __init__(self, parent = None):
QMainWindow.__init__(self, parent)
self.thread = Worker()
self.create_main_frame()
self.create_status_bar()
self.connect(self.thread, SIGNAL("finished()"), self.update_UI)
self.connect(self.thread, SIGNAL("terminated()"), self.update_UI)
self.connect(self.startButton, SIGNAL("clicked()"), self.start_acquisition)
self.connect(self.stopButton, SIGNAL("clicked()"), self.stop_acquisition)
self.thread.pixel_list.connect(self.update_figure)
def create_main_frame(self):
self.main_frame = QWidget()
self.dpi = 100
self.width = 10
self.height = 8
self.fig = Figure(figsize=(self.width, self.height), dpi=self.dpi)
self.axes = self.fig.add_subplot(111)
self.axes.axis((0,512,0,120))
self.canvas = FigureCanvas(self.fig)
self.canvas.setParent(self.main_frame)
self.canvas.updateGeometry()
self.canvas.draw()
self.background = None
self.lE_line, = self.axes.plot(range(512), [0 for i in xrange(512)], animated=True)
self.hE_line, = self.axes.plot(range(512), [0 for i in xrange(512)], animated=True)
self.mpl_toolbar = NavigationToolbar(self.canvas, self.main_frame)
self.startButton = QPushButton(self.tr("&Start"))
self.stopButton = QPushButton(self.tr("&Stop"))
layout = QGridLayout()
layout.addWidget(self.canvas, 0, 0)
layout.addWidget(self.mpl_toolbar, 1, 0)
layout.addWidget(self.startButton, 2, 0)
layout.addWidget(self.stopButton, 2, 1)
self.main_frame.setLayout(layout)
self.setCentralWidget(self.main_frame)
self.setWindowTitle(self.tr("XRTdev Interface"))
def create_status_bar(self):
self.status_text = QLabel("I am a status bar. I need a status to show!")
self.statusBar().addWidget(self.status_text, 1)
def start_acquisition(self):
self.thread.exiting = False
self.startButton.setEnabled(False)
self.stopButton.setEnabled(True)
self.thread.render()
def stop_acquisition(self):
self.thread.exiting = True
self.startButton.setEnabled(True)
self.stopButton.setEnabled(False)
self.cleanup_UI()
def update_figure(self, lE, hE):
if self.background == None:
self.background = self.canvas.copy_from_bbox(self.axes.bbox)
self.canvas.restore_region(self.background)
self.lE_line.set_ydata(lE)
self.hE_line.set_ydata(hE)
self.axes.draw_artist(self.lE_line)
self.axes.draw_artist(self.hE_line)
self.canvas.blit(self.axes.bbox)
def update_UI(self):
self.startButton.setEnabled(True)
self.stopButton.setEnabled(False)
self.cleanup_UI()
def cleanup_UI(self):
self.background = None
self.axes.clear()
self.canvas.draw()
class Worker(QThread):
pixel_list = pyqtSignal(list, list)
def __init__(self, parent = None):
QThread.__init__(self, parent)
self.exiting = False
def __del__(self):
self.exiting = True
self.wait()
def render(self):
self.start()
def run(self):
# simulate I/O
n = random.randrange(100,200)
while not self.exiting and n > 0:
lE = [random.randrange(5,16) for i in xrange(512)]
hE = [random.randrange(80,121) for i in xrange(512)]
self.pixel_list.emit(lE, hE)
time.sleep(0.02)
n -= 1
def main():
app = QApplication(sys.argv)
form = ApplicationWindow()
form.show()
app.exec_()
if __name__ == "__main__":
main()
Возможно, моя проблема даже не в MatPlotLib или PyQT4, а в том, как я реализовал многопоточность. Как я уже отметил, я новичок в этом и учусь. И я даже не уверен, что GUIqwt решит любую из этих проблем - но я знаю, что видел здесь много рекомендаций, чтобы использовать что-то более быстрое, чем MatPlotLib для построения графиков в реальном времени в графическом интерфейсе. Спасибо за помощь в этом!
1 ответ
[отредактировано потому что QThread
сбивает с толку
Кажется, есть два способа его использования: либо подклассифицировать его (как говорится в вашем примере и документации), либо создать рабочий объект, а затем переместить его в поток (см. Этот пост в блоге). Тогда я запутываюсь, когда вы смешиваете сигнал / слоты. Как говорит Аварис, это изменение не может быть вашей проблемой.
Я переделал ваш Worker
класс как подкласс QObject
(потому что это стиль, который я понимаю).
Проблема в том, что если вы не поставите sleep
в вашей поддельной системе данных вы генерируете все обратные вызовы главного окна за < 1сек. Затем основной поток по существу блокируется, поскольку он очищает очередь сигналов. Если вы установите задержку на заданную вами задержку (0,0005 с), то она начнет генерировать данные намного быстрее, чем их можно отобразить, что говорит о том, что это может не подходить для вашей проблемы (будучи прагматичным, вы также можете не вижу, что быстро, и, кажется, все в порядке на 30 - 40 кадров в секунду).
import time, random, sys
#from PySide.QtCore import *
#from PySide.QtGui import *
from PyQt4 import QtCore
from PyQt4 import QtGui
from matplotlib.figure import Figure
from matplotlib.backends.backend_qt4agg import NavigationToolbar2QTAgg as NavigationToolbar
from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg as FigureCanvas
class ApplicationWindow(QtGui.QMainWindow):
get_data = QtCore.pyqtSignal()
def __init__(self, parent = None):
QtGui.QMainWindow.__init__(self, parent)
self.thread = QtCore.QThread(parent=self)
self.worker = Worker(parent=None)
self.worker.moveToThread(self.thread)
self.create_main_frame()
self.create_status_bar()
self.startButton.clicked.connect(self.start_acquisition)
self.stopButton.clicked.connect(self.stop_acquisition)
self.worker.pixel_list.connect(self.update_figure)
self.worker.done.connect(self.update_UI)
self.get_data.connect(self.worker.get_data)
self.thread.start()
def create_main_frame(self):
self.main_frame = QtGui.QWidget()
self.dpi = 100
self.width = 10
self.height = 8
self.fig = Figure(figsize=(self.width, self.height), dpi=self.dpi)
self.axes = self.fig.add_subplot(111)
self.axes.axis((0,512,0,120))
self.canvas = FigureCanvas(self.fig)
self.canvas.setParent(self.main_frame)
self.canvas.updateGeometry()
self.canvas.draw()
self.background = None
self.lE_line, = self.axes.plot(range(512), [0 for i in xrange(512)], animated=True)
self.hE_line, = self.axes.plot(range(512), [0 for i in xrange(512)], animated=True)
self.mpl_toolbar = NavigationToolbar(self.canvas, self.main_frame)
self.startButton = QtGui.QPushButton(self.tr("&Start"))
self.stopButton = QtGui.QPushButton(self.tr("&Stop"))
layout = QtGui.QGridLayout()
layout.addWidget(self.canvas, 0, 0)
layout.addWidget(self.mpl_toolbar, 1, 0)
layout.addWidget(self.startButton, 2, 0)
layout.addWidget(self.stopButton, 2, 1)
self.main_frame.setLayout(layout)
self.setCentralWidget(self.main_frame)
self.setWindowTitle(self.tr("XRTdev Interface"))
def create_status_bar(self):
self.status_text = QtGui.QLabel("I am a status bar. I need a status to show!")
self.statusBar().addWidget(self.status_text, 1)
def start_acquisition(self):
self.worker.exiting = False
self.startButton.setEnabled(False)
self.stopButton.setEnabled(True)
self.get_data.emit()
def stop_acquisition(self):
self.worker.exiting = True
self.startButton.setEnabled(True)
self.stopButton.setEnabled(False)
self.cleanup_UI()
def update_figure(self, lE, hE):
if self.background == None:
self.background = self.canvas.copy_from_bbox(self.axes.bbox)
self.canvas.restore_region(self.background)
self.lE_line.set_ydata(lE)
self.hE_line.set_ydata(hE)
self.axes.draw_artist(self.lE_line)
self.axes.draw_artist(self.hE_line)
self.canvas.blit(self.axes.bbox)
def update_UI(self):
self.startButton.setEnabled(True)
self.stopButton.setEnabled(False)
self.cleanup_UI()
def cleanup_UI(self):
self.background = None
self.axes.clear()
self.canvas.draw()
class Worker(QtCore.QObject):
pixel_list = QtCore.pyqtSignal(list, list)
done = QtCore.pyqtSignal()
def __init__(self, parent = None):
QtCore.QObject.__init__(self, parent)
self.exiting = True
@QtCore.pyqtSlot()
def get_data(self):
# simulate I/O
print 'data_start'
n = random.randrange(100,200)
while not self.exiting and n > 0:
lE = [random.randrange(5,16) for i in xrange(512)]
hE = [random.randrange(80,121) for i in xrange(512)]
self.pixel_list.emit(lE, hE)
time.sleep(0.05)
n -= 1
print 'n: ', n
self.done.emit()