Time.sleep() требуется, чтобы QThread реагировал?

Во-первых, я новичок в Python. Я давний пользователь MatLab (инженер, а не компьютерный ученый), и я начинаю процесс работы с Python, NumPy, SciPy и т.д. В моем рабочем процессе. Поэтому, пожалуйста, извините мое явное незнание того, что является прекрасным языком программирования!

В качестве моей первой попытки я решил создать приложение для взаимодействия с датчиком, который я разрабатываю. Датчик имеет разрешение в микросекундах (данные 512 и 512 пикселей с низким энергопотреблением каждые 500 микросекунд), но ввод-вывод будет блокироваться. Поскольку я буду постоянно опробовать устройство, я знаю, что потоковая передача будет важна, чтобы поддерживать графический интерфейс (графический интерфейс в конечном итоге также будет интегрировать последовательную связь с другим устройством и иметь подпрограмму обработки изображений, которая работает с данными датчика). Я создал потоковый экземпляр MatPlotLib для построения данных "реального времени" с датчика. Хотя я построил модуль, который обменивается данными с датчиком самостоятельно и проверен, я знаю, как это сделать на Python, я начинаю здесь просто с "моделирования" данных, генерируя 512 случайных чисел от 8 до 12 для низкой энергии "пиксели" и 512 случайных чисел от 90 до 110 для "пикселей" с высокой энергией. Это то, что нарезается резьбой. Работая со многими примерами здесь, я также научился использовать blitting, чтобы получить достаточно быстрое обновление экрана с помощью MatPlotLib - НО, проблема в том, что если я не использую, чтобы потоковый процесс спал в течение 20 мс с помощью time.sleep(0.02), GUI не отвечает. Это можно проверить, потому что интерактивная обратная связь данных X, Y от MatPlotLib не работает, и кнопка "STOP" не может использоваться для прерывания процесса. Все, что превышает time.sleep(0.02), делает графический интерфейс более плавным, но за счет "скорости передачи данных". Все, что медленнее, чем time.sleep(0.02), делает GUI невосприимчивым. Я не уверен, что понимаю, почему. Я собирался уйти и попытаться использовать GUIqwt вместо этого, но думал, что попрошу здесь, прежде чем отказаться от MatPlotLib, так как я не уверен, что это даже проблема. Я обеспокоен тем, что перенос нити на 20 мс будет означать, что я пропускаю по меньшей мере 40 потенциальных данных из массива датчиков (40 строк * 500us/line = 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 в свою поддельную систему данных, вы генерируете все обратные вызовы в главное окно в < 1s. Основной поток затем по существу блокируется, поскольку он очищает очередь сигналов. Если вы установите задержку на указанную задержку (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()