2013-10-15 80 views
2

我一直在努力與我的Python應用程序,我找不到任何答案。實時繪製Matplotlib,PyQt和線程結束python崩潰

我有使用Matplotlib小部件的PyQT GUI應用程序。 GUI啓動一個處理繪圖到mpl小部件的新線程。我害怕現在通過從另一個線程訪問matplotlib繪圖組件來導致崩潰,現在運行到競賽狀態。

這基本上是,我的代碼看起來像:

class Analyzer(QMainWindow, Ui_MainWindow): 
    def __init__(self, parent=None): 
    self.timer = QTimer() 
    super(Analyzer, self).__init__(parent) 
    self.setupUi(self) 

    self.background = self.mpl.canvas.copy_from_bbox(self.mpl.canvas.ax.bbox) 

    self.plotQueue = Queue.Queue() 
    self.plotterStarted = False 

    self.plotter = Plotter(self.mpl, self.plotQueue) 
    self.cam = Cam(self.plotQueue, self.textEdit) 
    ... 

class Ui_MainWindow(object): 
    def setupUi(self, MainWindow): 
    ... 
    self.mpl = MplWidget(self.centralWidget) 
    ... 

class MplWidget(QtGui.QWidget): 
"""Widget defined in Qt Designer""" 
    def __init__(self, parent = None): 
    QtGui.QWidget.__init__(self, parent) 
    self.canvas = MplCanvas() 
    ... 

class MplCanvas(FigureCanvas): 
"""Class to represent the FigureCanvas widget""" 
    def __init__(self):   
    # setup Matplotlib Figure and Axis 
    self.fig = Figure() 
    self.ax = self.fig.add_subplot(111) 

    # initialization of the canvas 
    FigureCanvas.__init__(self, self.fig) 

    FigureCanvas.updateGeometry(self) 

和繪圖類:

class Plotter(): 
    def __init__(self, mpl="", plotQueue=""): 
    self.mpl = mpl 
    self.background = self.mpl.canvas.copy_from_bbox(self.mpl.canvas.ax.bbox) 
    self.plotQueue = plotQueue 
    ... 
    def start(self): 
    threading.Thread(target=self.run).start() 

    ''' Real time plotting ''' 
    def run(self): 
    while True: 
     try: 
     inputData = self.plotQueue.get(timeout=1) 

     # Go through samples 
     for samples in inputData: 
      self.line, = self.mpl.canvas.ax.plot(x, y, animated=True, label='Jee') 

      for sample in samples: 
       x.append(sample['tick']) 
       y.append(sample['linear']) 

      self.line.set_data(x,y) 
      self.mpl.canvas.ax.draw_artist(self.line) 
      self.mpl.canvas.blit(self.mpl.canvas.ax.bbox) 
     ... 

所以我通過MPL和plotQueue繪圖儀類對象。 PlotQueue填充在處理來自外部hw的傳入數據的Cam類中。繪圖儀讀取plotQueue,處理它並調用mpl的繪圖。

但是,這是一個線程安全的方法來訪問mpl?如果不是,我該怎麼做?任何提示在此讚賞。


編輯1.

我加QTimer在主線程來處理繪製,如在意見提出。經過小小的調整後,我的工作得很好。

class Analyzer(...): 
    def __init__(self, parent=None): 
    QObject.connect(self.timer, SIGNAL("timeout()"), self.periodicCall) 

    def periodicCall(self): 
    self.plotter.draw() 

    def startButton(self): 
    self.timer.start(10) 

非常感謝有用的意見。

+0

我記得讀一些關於matplotlib有線程問題。_most_繪圖命令應該非常快(並且可以使用blitting來解決大部分緩慢的情況),請在主線程中執行它們。 – tacaswell

+0

謝謝,這正是我一直在想的,但在這種情況下,我的主線程是在實現GUI的Analyzer類中。分析器類有按鈕等,所以如果我有一個循環讀取隊列和調用繪圖,它會阻止整個應用程序。 我怎麼能夠在主線程中做循環? 我相信它必須是另一個線程做繪圖,但如何做到線程安全?我可以使用同步,但除了我的plot方法之外,我應該在哪裏放置另一個同步調用,它應該位於matplotlib庫中的某個位置。 – PyTechnic

回答

1

如果程序中的matplotlib正在使用QT後端(我認爲這是因爲您將它嵌入到Qt應用程序中),那麼圖形將在您稱之爲matplotlib命令的線程中完成。這將是一個問題,因爲Qt要求所有繪圖都是從主線程完成的。 所以我相當肯定你不能簡單地修復它。(如果您使用的是GTK,則可以使用gtk鎖來防止主進程與GUI進行交互,而從線程執行GUI相關的事情,但Qt在v4及更高版本中刪除了類似的鎖)。

您有幾種選擇:

  1. 嘗試並分離出matplotlib的繪圖部分(甚至可能不是可能嗎?),並讓他們在主線程通過發送事件與QApplication.postEvent()

  2. 運行
  3. 而不是使用線程,只需在主線程中使用回調函數(可能會定期使用QTimer或在程序空閒時調用)。這個probbaly不會影響你的應用程序的性能,因爲Python GIL無論如何都會阻止真正的多線程行爲。

  4. 使用不同的繪圖庫。那天我看了一下PyQtGraph,看起來好像很好。從我簡短的一瞥中,我認爲它有能力使用RemoteGraphicsView來處理幕後的所有這些事情。這將啓動第二個用於執行CPU密集型繪圖的過程,該過程可以解決上述Python GIL問題。看看他們提供的例子,如果你有興趣

+0

非常感謝。 選項1.是我在之前的文章評論中試圖找出的東西,但我相信這不是要走的路。 選項2可以爲我工作。我也會檢查選項3.如果圖書館提供給我我需要的東西。 – PyTechnic

+0

或者如果我在繪製主線程中有一個回調方法。我從線程傳遞它所需的參數來調用這個方法。所以基本上,我只是有線程而不是定時器調用方法? – PyTechnic

+0

這將不起作用,因爲您調用的方法將在線程中執行,而不是在主線程中執行。您需要通過使用QApplication.postEvent() 使主函數在主線程中運行。這實際上相當複雜,我會嘗試在接下來的24小時內爲您提供一些代碼(只需與人員覈對誰寫的,他很樂意釋放它) –