2015-11-19 282 views
0

目標:動態繪圖與Matplotlib和PyQt的 - 凍結窗口

我想嵌入在PyQt4的GUI窗口Matplotlib情節。情節必須及時更新。

問題:

窗口凍結,直到繪圖完成。我希望情節能夠實時更新。

語境:

我們有數值算法正在研究一些數據,我想的情節,以顯示數據集是如何被影響的算法。算法完成一次迭代~0.5秒 - 繪圖必須在每次迭代中更新。

測試代碼:

算法由test()取代,其繪出的隨機點的100倍。下面的代碼說明了問題:

import sys 
from PlotGUI import * 
import threading 
from random import randint 
import time 

class GUIForm(QtGui.QDialog): 

    def __init__(self, parent=None): 
     QtGui.QWidget.__init__(self,parent) 
     self.ui = Ui_Dialog() 
     self.ui.setupUi(self) 
     QtCore.QObject.connect(self.ui.pushButton, QtCore.SIGNAL('clicked()'), self.startSim) 
     self.cPlot = None # custom plotter 
     self.instantiatePlot()   

    def instantiatePlot(self): 
     self.cPlot = CustomPlotter(self.ui.widget.canvas) 
     self.cPlot.prepareFigure() 

    def startSim(self): 
     self.cPlot.clear();   
     draw_thread = threading.Thread(target=self.cPlot.test()) 
     draw_thread.start() 

class CustomPlotter(): 
    def __init__(self, canvas): 
     print 'constructor' 
     self.canvas = canvas   

    def prepareFigure(self): 
     ax = self.canvas.ax 

     ax.set_ylim([-1,101]) 
     #ax.set_xlim([dt[0],dt[1]]) 
     ax.set_ylim([-1, 10]) 
     self.canvas.draw() 

    def clear(self): 
     self.canvas.ax.clear() 

    def test(self): 
     canvas = self.canvas 
     ax = canvas.ax 
     for x in range(0,100): 
      y = randint(0,9) 
      ax.plot(x, y, 'ro') 
      print x 
      canvas.draw() 
      time.sleep(1) 
      #canvas.show() 
      #canvas.update() 

if __name__ == "__main__": 
    app = QtGui.QApplication(sys.argv) 
    myapp = GUIForm() 
    myapp.show() 
    sys.exit(app.exec_()) 

在此先感謝。這是爲了一些原型,所以我會接受所有提供快速解決方案的選項/替代方案。

+0

哪個庫有PlotGUI模塊? – dopstar

+0

QtGui,QtCore,Ui_Dialog()取決於PlotGUI模塊 – AdrianFox

+1

這裏的答案有些脫節,但基本上你沒有正確地創建線程,因此它阻止了它。但是,正確創建它可能會導致由於輔助線程中的Qt調用而導致崩潰(這是不允許的)。此外,當繪圖實際上正在重繪時,GUI總是會阻塞,但是您可以將繪圖數據的準備/獲取卸載到另一個線程中。你只需要將這些數據發送到主線程來更新GUI(可能使用'QThread'和信號發射 - 關於這個問題有很多問題) –

回答

0

embedding_in_qt4.py來自matplotlib畫廊的例子應該夠了吧?

draw_thread = threading.Thread(target=self.cPlot.test()) 

在當前線程這將立即執行測試方法,然後傳遞結果(None)爲target

# ... 
class MyMplCanvas(FigureCanvas): 
    """Ultimately, this is a QWidget (as well as a FigureCanvasAgg, etc.).""" 

    def __init__(self, parent=None, width=5, height=4, dpi=100): 
     fig = Figure(figsize=(width, height), dpi=dpi) 
     self.axes = fig.add_subplot(111) 
     # We want the axes cleared every time plot() is called 
     self.axes.hold(False) 

     self.compute_initial_figure() 

     # 
     FigureCanvas.__init__(self, fig) 
     self.setParent(parent) 

     FigureCanvas.setSizePolicy(self, 
            QtGui.QSizePolicy.Expanding, 
            QtGui.QSizePolicy.Expanding) 
     FigureCanvas.updateGeometry(self) 

    def compute_initial_figure(self): 
     pass 
# ... 
class MyDynamicMplCanvas(MyMplCanvas): 
    """A canvas that updates itself every second with a new plot.""" 

    def __init__(self, *args, **kwargs): 
     MyMplCanvas.__init__(self, *args, **kwargs) 
     timer = QtCore.QTimer(self) 
     timer.timeout.connect(self.update_figure) 
     timer.start(1000) 

    def compute_initial_figure(self): 
     self.axes.plot([0, 1, 2, 3], [1, 2, 0, 4], 'r') 

    def update_figure(self): 
     # Build a list of 4 random integers between 0 and 10 (both inclusive) 
     l = [random.randint(0, 10) for i in range(4)] 

     self.axes.plot([0, 1, 2, 3], l, 'r') 
     self.draw() 
# ... 
1

當您嘗試實例化一個新的線程,你有一個錯誤。什麼你可能想要做的是:

draw_thread = threading.Thread(target=self.cPlot.test) 

Thread(target=None)只需創建一個線程,什麼也不做,就立即退出,因此是有效的,且不會產生任何異常,它表明這個問題。

因爲test()方法在GUI線程內啓動,所以GUI被阻塞,直到方法返回。

1

PySide/Qt無法從另一個線程更新小部件(QPixmap/display items)。您不能直接在另一個線程中調用GUI項目的繪圖。

請參閱matplotlib.animation.FuncAnimation。 http://matplotlib.org/api/animation_api.html

import numpy as np 

# ========== Matplotlib [PySide] ========== 
import matplotlib 
matplotlib.use("Qt4Agg") 
matplotlib.rcParams["backend.qt4"] = "PySide" 

import matplotlib.animation as mplanimation 
from matplotlib.figure import Figure 
from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg as FigureCanvas 

class InteractivePlotWidget(FigureCanvas): 

    def __init__(self): 
     super().__init__(Figure(tight_layout=True)) 
     self.axes = self.figure.add_subplot(111) 

     self.setSizePolicy(QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Expanding) 

     self.name = "" 
     self.data = np.zeros(shape=(100, 2)) 
    # end __init__ 

    def plot_data(self, interval=0): 
     data = np.array([(i, np.sin(i)) for i in range(interval)]) 
     try: 
      self.axes.lines[0].set_data(data[:,0], data[:,1]) 
     except IndexError: 
      self.axes.plot(data, label=self.name) # Lots of overhead. Do once. 

     self.axes.relim() 
     self.axes.autoscale_view(True, True, True) 
     return self.axes.lines # animation handles draw 
     # manually trigger draw 
     # self.draw_idle() 
     # self.flush_events() 
    # end plot_data 
# end class InteractivePlotWidget 

if __name__ == "__main__": 
    QtGui.QApplication([]) 

    w = InteractivePlotWidget() 
    w.show() 

    # Create and start the animation (timer) 
    anim = mplanimation.FuncAnimation(w.figure, w.plot_data, interval=0) 
    anim._start() 

    sys.exit(QtGui.qApp.exec_())