在QThread(pyQt4)中加速imshow()(Matplotlib)和fig.canvas.restore_region()

时间:2016-01-21 21:42:59

标签: python matplotlib qt4

跟进this Question以及tcaswell提供的解决方案我尝试采用imshow()的代码来生成一个非冻结窗口,其中包含用于图像处理的滑块,例如高斯模糊过滤器。 (我在彼此的顶部绘制了两个图像,因为我想在稍后阶段显示部分透明的蒙版。)

我希望你们中的一些人可能觉得这很有用,虽然我仍然可以使用一些帮助。

编辑:您可以在下面的第三节中找到当前状态。我正在为希望深入了解详细信息的其他用户保留旧版本。

Appearance

我派生了两个不同的工作代码,每个代码都有一些(次要)问题,我真的很感激一些建议。

第一个代码:

  • 只要拖动QSlider线程正在运行。但是,您不能简单地单击滑块。有什么建议吗?
  • 图像轴未正确绘制,即它们再次消失。为什么?
  • 情节更新不是我所说的快速,尽管它比每次调用imshow()更快。我怎样才能加快速度呢?
  • 在更新绘图的极短时间内,窗口仍然处于冻结状态。 (循环运行时窗口拖动是口吃。)可以改进吗?
  • 为了不进入QThread: Destroyed while thread is still running我在closeEvent()中放了一个time.sleep(1)。我知道这真的很糟糕,但如果没有新的旗帜我怎么能避免呢?

    import time, sys
    from PyQt4 import QtCore
    from PyQt4 import QtGui
    from scipy import misc
    from scipy import ndimage
    from matplotlib.figure import Figure
    import numpy as np
    from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg as FigureCanvas
    
    class ApplicationWindow(QtGui.QMainWindow):
        get_data = QtCore.pyqtSignal()
        close_request = 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.close_request.connect(self.thread.quit)
            self.startButton.clicked.connect(self.start_calculation) 
            self.stopButton.clicked.connect(self.stop_calculation)
            self.worker.started.connect(self.thread.start)
            self.worker.new_pixel_array.connect(self.update_figure)
            self.slider.sliderPressed.connect(self.start_calculation)
            self.slider.valueChanged.connect(self.slider_value_changed)
            self.slider.sliderReleased.connect(self.stop_calculation)
            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 = 5
            self.height = 5
            self.fig = Figure(figsize=(self.width, self.height), dpi=self.dpi)
            self.axes = self.fig.add_subplot(111)               
            self.axes.axis((0,512,0,512))
    
            self.canvas = FigureCanvas(self.fig)
            self.canvas.setParent(self.main_frame)
            self.canvas.updateGeometry()    
            self.canvas.draw()
            self.background = None
            self.background = self.canvas.copy_from_bbox(self.axes.bbox)
            self.im1 = self.axes.imshow(misc.ascent(), cmap='bone', interpolation='lanczos', extent=[0,512,0,512], aspect=(1), animated=True)        
            self.im2 = self.axes.imshow(misc.lena(), cmap='afmhot', interpolation='lanczos', extent=[0,265,0,256], aspect=(1), animated=True)  
    
            self.startButton = QtGui.QPushButton(self.tr("Keep Calculating"))
            self.stopButton = QtGui.QPushButton(self.tr("Stop Calculation"))
            self.slider = QtGui.QSlider(QtCore.Qt.Horizontal)
            self.slider.setRange(0, 100)
            self.slider.setValue(50)
            self.slider.setTracking(True)
            self.slider.setTickPosition(QtGui.QSlider.TicksBothSides)
            layout = QtGui.QGridLayout()
            layout.addWidget(self.canvas, 0, 0)
            layout.addWidget(self.slider, 1, 0) 
            layout.addWidget(self.startButton, 2, 0)       
            layout.addWidget(self.stopButton, 3, 0)
            self.main_frame.setLayout(layout)
            self.setCentralWidget(self.main_frame)
            self.setWindowTitle(self.tr("Gaussian Filter - Slider not clickable"))
    
        def slider_value_changed(self):
            #self.worker.blockSignals(False)
            self.worker.slider = self.slider.value()    
    
        def start_calculation(self):
            self.worker.exiting = False
            self.worker.slider = self.slider.value()
            self.startButton.setEnabled(False)
            self.stopButton.setEnabled(True)
            self.get_data.emit()
    
        def stop_calculation(self):
            self.worker.exiting = True
            self.startButton.setEnabled(True)
            self.stopButton.setEnabled(False)
            self.cleanup_UI()  
    
        def update_figure(self, im1_data,im2_data):
            self.canvas.restore_region(self.background)
            self.im1.set_array(im1_data)
            self.im2.set_array(im2_data)
            self.axes.draw_artist(self.im1) 
            self.axes.draw_artist(self.im2) 
            self.canvas.blit(self.axes.bbox)  
    
        def cleanup_UI(self):
            self.background = None
            self.canvas.draw()
    
        def closeEvent(self, event):
            self.stop_calculation()
            self.close_request.emit()
            time.sleep(1)  
            ## ugly workaround to prevent window from closing before thread is closed. (calculation takes time) How can this be avoided without additional flag?
            event.accept()        
    
    class Worker(QtCore.QObject):
    
        new_pixel_array = QtCore.pyqtSignal(np.ndarray,np.ndarray)
        started = QtCore.pyqtSignal()
    
        def __init__(self, parent = None):
            QtCore.QObject.__init__(self, parent)
            self.exiting = True
            self.slider = 0
    
        @QtCore.pyqtSlot()
        def get_data(self):
            while self.exiting == False:
                self.started.emit()
                im1_data = self.gauss(misc.ascent(),self.slider)
                im2_data = self.gauss(misc.lena(),self.slider)
                self.new_pixel_array.emit(im1_data, im2_data)
                print 'Slider Value: ', self.slider  
    
        def gauss(self,im,radius):
            gaussed = ndimage.gaussian_filter(im, radius)
            return gaussed
    
    def main():
        app = QtGui.QApplication(sys.argv)
        form = ApplicationWindow()
        form.show()
        app.exec_()
    
    if __name__ == "__main__":
        main()        
    

第二个代码:

  • 您现在也可以点击滑块。
  • 背景(轴)重建仍然无效。当然,在self.canvas.draw()中调用cleanup_UI()会以某种方式修复此问题。
  • 单击滑块时,计算执行一次,但如果拖动并释放滑块,则计算将以相同的值执行两次。为什么?我试图用blockSignals捕捉到这个但有时(当滑块被快速拖动并释放时)图中的第二个图像没有正确更新。你通过两种不同的模糊来识别它。

    import sys
    from PyQt4 import QtCore
    from PyQt4 import QtGui
    from scipy import misc
    from scipy import ndimage
    from matplotlib.figure import Figure
    import numpy as np
    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.startButton.clicked.connect(self.start_calculation) 
            self.worker.new_pixel_array.connect(self.update_figure)
            self.worker.done.connect(self.stop_calculation)
            self.slider.sliderPressed.connect(self.start_calculation)
            self.slider.valueChanged.connect(self.slider_value_changed) 
            self.slider.actionTriggered.connect(self.start_calculation)
            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 = 5
            self.height = 5
            self.fig = Figure(figsize=(self.width, self.height), dpi=self.dpi)
            self.axes = self.fig.add_subplot(111)               
            self.axes.axis((0,512,0,512))
    
            self.canvas = FigureCanvas(self.fig)
            self.canvas.setParent(self.main_frame)
            self.canvas.updateGeometry()    
            self.canvas.draw()
            self.background = None
            self.background = self.canvas.copy_from_bbox(self.axes.bbox)
            self.im1 = self.axes.imshow(misc.ascent(), cmap='bone', interpolation='lanczos', extent=[0,512,0,512], aspect=(1), animated=True)        
            self.im2 = self.axes.imshow(misc.lena(), cmap='afmhot', interpolation='lanczos', extent=[0,265,0,256], aspect=(1), animated=True)  
    
            self.startButton = QtGui.QPushButton(self.tr("Do a Calculation"))
            self.slider = QtGui.QSlider(QtCore.Qt.Horizontal)
            self.slider.setRange(0, 100)
            self.slider.setValue(50)
            self.slider.setTracking(True)
            self.slider.setTickPosition(QtGui.QSlider.TicksBothSides)
            layout = QtGui.QGridLayout()
            layout.addWidget(self.canvas, 0, 0)
            layout.addWidget(self.slider, 1, 0) 
            layout.addWidget(self.startButton, 2, 0)       
    
            self.main_frame.setLayout(layout)
            self.setCentralWidget(self.main_frame)
            self.setWindowTitle(self.tr("Gaussian Filter"))
    
        def slider_value_changed(self):
            #self.worker.blockSignals(False)
            self.worker.slider = self.slider.value()
    
        def start_calculation(self):
            self.slider_value_changed()
            self.worker.exiting = False
            self.startButton.setEnabled(False)
            self.get_data.emit()
    
        def stop_calculation(self):
            self.worker.exiting = True
            self.startButton.setEnabled(True)
            self.cleanup_UI()  
    
        def update_figure(self, im1_data,im2_data):  
            self.im1.set_array(im1_data)
            self.im2.set_array(im2_data)
            self.axes.draw_artist(self.im1) 
            self.axes.draw_artist(self.im2) 
            self.canvas.blit(self.axes.bbox)       
    
        def cleanup_UI(self):
            self.canvas.restore_region(self.background)
            #self.canvas.draw() 
            #self.worker.blockSignals(True)
    
    class Worker(QtCore.QObject):
    
        new_pixel_array = QtCore.pyqtSignal(np.ndarray,np.ndarray)
        done = QtCore.pyqtSignal()
    
        def __init__(self, parent = None):
            QtCore.QObject.__init__(self, parent)
            self.exiting = True
            self.slider = 0
    
        @QtCore.pyqtSlot()
        def get_data(self):
            if self.exiting == False:
                im1_data = self.gauss(misc.ascent(),self.slider)            
                im2_data = self.gauss(misc.lena(),self.slider)
                self.new_pixel_array.emit(im1_data,im2_data)
                print 'Calculation performed, Slider Value: ', self.slider  
                self.done.emit()
            else: None
    
        def gauss(self,im,radius):
            gaussed = ndimage.gaussian_filter(im, radius)
            return gaussed
    
    def main():
        app = QtGui.QApplication(sys.argv)
        form = ApplicationWindow()
        form.show()
        app.exec_()
    
    if __name__ == "__main__":
        main()             
    

编辑:第三段代码(已解决的主要问题和更新率有限)

  • 滑块现在只在前一个线程的计算完成时才启动新线程。这是由disconnect实现的。
  • 绘图仍然很慢,(模糊功能也是如此)。
  • restore_region似乎仍然没有效果。
  • 我现在已将两个图像的计算放入线程中,并通过Queue()返回结果。如果你看到一些改进的可能性,请告诉我。
  • 我曾尝试切换到multiprocessing模块并将计算放在Pool()内,但它会引发Can't pickle...错误。由于我对多处理完全陌生,我非常希望了解更多信息。

    import sys
    from PyQt4 import QtCore
    from PyQt4 import QtGui
    from scipy import misc
    from scipy import ndimage
    from matplotlib.figure import Figure
    import numpy as np
    from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg as FigureCanvas
    from threading import Thread
    from Queue import Queue
    
    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.startButton.clicked.connect(self.start_calculation) 
            self.stopButton.clicked.connect(self.stop_calculation)
            self.worker.started.connect(self.thread.start)
            self.worker.new_pixel_array.connect(self.update_figure)
            self.slider.actionTriggered.connect(self.start_calculation)
            self.slider.valueChanged.connect(self.slider_value_changed)
            self.worker.done.connect(self.stop_calculation)
            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 = 5
            self.height = 5
            self.fig = Figure(figsize=(self.width, self.height), dpi=self.dpi)
            self.axes = self.fig.add_subplot(111)               
            self.axes.axis((0,512,0,512))
    
            self.canvas = FigureCanvas(self.fig)
            self.canvas.setParent(self.main_frame)
            self.canvas.updateGeometry()    
            self.background = None
            self.canvas.draw()
            self.background = self.canvas.copy_from_bbox(self.axes.bbox)
            self.im1 = self.axes.imshow(misc.ascent(), cmap='bone', interpolation='lanczos', extent=[0,512,0,512], aspect=(1), animated=True)        
            self.im2 = self.axes.imshow(misc.lena(), cmap='afmhot', interpolation='lanczos', extent=[0,265,0,256], aspect=(1), animated=True)  
    
            self.startButton = QtGui.QPushButton(self.tr("Start Calculation"))
            self.stopButton = QtGui.QPushButton(self.tr("Stop Calculation"))
            self.slider = QtGui.QSlider(QtCore.Qt.Horizontal)
            self.slider.setRange(0, 100)
            self.slider.setValue(50)
            self.slider.setTracking(True)
            self.slider.setTickPosition(QtGui.QSlider.TicksBothSides)
            layout = QtGui.QGridLayout()
            layout.addWidget(self.canvas, 0, 0)
            layout.addWidget(self.slider, 1, 0) 
            layout.addWidget(self.startButton, 2, 0)       
            layout.addWidget(self.stopButton, 3, 0)
            self.main_frame.setLayout(layout)
            self.setCentralWidget(self.main_frame)
            self.setWindowTitle(self.tr("Gaussian Filter"))
    
        def slider_value_changed(self):
            self.worker.slider = self.slider.value()    
    
        def start_calculation(self):
            if self.worker.exiting:
                self.slider.actionTriggered.disconnect(self.start_calculation)
                self.worker.slider = self.slider.value()
                self.startButton.setEnabled(False)
                self.stopButton.setEnabled(True)
                self.get_data.emit()
                self.worker.exiting = False
    
    
        def stop_calculation(self):
            if not self.worker.exiting:
                self.slider.actionTriggered.connect(self.start_calculation)
                self.worker.exiting = True
                self.startButton.setEnabled(True)
                self.stopButton.setEnabled(False)  
            self.cleanup_UI()  
    
        def update_figure(self, im1_data,im2_data):
            #self.canvas.restore_region(self.background)
            self.im1.set_array(im1_data)
            self.im2.set_array(im2_data)
            self.axes.draw_artist(self.im1) 
            self.axes.draw_artist(self.im2) 
            self.canvas.blit(self.axes.bbox)  
    
        def cleanup_UI(self):
            self.background = None
            self.canvas.draw()    
    
    class Worker(QtCore.QObject):
    
        new_pixel_array = QtCore.pyqtSignal(np.ndarray,np.ndarray)
        started = QtCore.pyqtSignal()
        done = QtCore.pyqtSignal()
    
        def __init__(self, parent = None):
            QtCore.QObject.__init__(self, parent)
            self.exiting = True
            self.slider = 0
    
        @QtCore.pyqtSlot()
        def get_data(self):
            while self.exiting == False:
                self.started.emit()
                queue1  = Queue()
                queue2  = Queue()
                im1T = Thread(target=self.gauss, args=(misc.ascent(),queue1))
                im2T = Thread(target=self.gauss, args=(misc.lena(),queue2))
                slider_val = self.slider
                im1T.start()
                im2T.start()
                im1T.join()            
                im2T.join()            
                im1_data = queue1.get() 
                im2_data = queue2.get() 
                self.new_pixel_array.emit(im1_data, im2_data)
                if slider_val == self.slider:
                    self.done.emit()
                    print 'Slider Value: ', self.slider  
                    break
    
        def gauss(self,im,output_queue):
            gaussed = ndimage.gaussian_filter(im,self.slider)
            output_queue.put(gaussed)   
    
    def main():
        app = QtGui.QApplication(sys.argv)
        form = ApplicationWindow()
        form.show()
        app.exec_()
    
    if __name__ == "__main__":
        main()                       
    

0 个答案:

没有答案