2016-09-19 4 views
0

Vor langer Zeit entwarf ich ein kleines PyQt Gui, das ein Live-Diagramm plottet. Ein Sensorsignal tritt in den Computer ein und dieses Signal wird von meinem Gui in Echtzeit geplottet.Live-Grafik in Matplotlib verhindert Python zum Herunterfahren

enter image description here

Damals arbeitete ich mit PyQt4 und matplotlib 1.5. Hier ist der Code, der solch einen Live-Graph erzeugt (das Sensorsignal wird emuliert). Kopieren Sie diesen Code einfach in eine Python-Datei. Führen Sie es, und Sie werden schöne Tanzgraphen sehen:

################################################################### 
#                 # 
#      PLOTTING A LIVE GRAPH      # 
#     ----------------------------     # 
#   EMBED A MATPLOTLIB ANIMATION INSIDE YOUR    # 
#   OWN GUI!            # 
#   -> Python 3.5.x          # 
#   -> matplotlib: 1.5         # 
#   -> PyQt: 4           # 
#                 # 
################################################################### 


import sys 
import os 
from PyQt4 import QtGui 
from PyQt4 import QtCore 
import functools 
import numpy as np 
import random as rd 
import matplotlib 
matplotlib.use("Qt4Agg") 
from matplotlib.figure import Figure 
from matplotlib.animation import TimedAnimation 
from matplotlib.lines import Line2D 
from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg as FigureCanvas 
import time 
import threading 



def setCustomSize(x, width, height): 
    sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Fixed, QtGui.QSizePolicy.Fixed) 
    sizePolicy.setHorizontalStretch(0) 
    sizePolicy.setVerticalStretch(0) 
    sizePolicy.setHeightForWidth(x.sizePolicy().hasHeightForWidth()) 
    x.setSizePolicy(sizePolicy) 
    x.setMinimumSize(QtCore.QSize(width, height)) 
    x.setMaximumSize(QtCore.QSize(width, height)) 

'''''' 

class CustomMainWindow(QtGui.QMainWindow): 

    def __init__(self): 

     super(CustomMainWindow, self).__init__() 

     # Define the geometry of the main window 
     self.setGeometry(300, 300, 800, 400) 
     self.setWindowTitle("my first window") 

     # Create FRAME_A 
     self.FRAME_A = QtGui.QFrame(self) 
     self.FRAME_A.setStyleSheet("QWidget { background-color: %s }" % QtGui.QColor(210,210,235,255).name()) 
     self.LAYOUT_A = QtGui.QGridLayout() 
     self.FRAME_A.setLayout(self.LAYOUT_A) 
     self.setCentralWidget(self.FRAME_A) 

     # Place the zoom button 
     self.zoomBtn = QtGui.QPushButton(text = 'zoom') 
     setCustomSize(self.zoomBtn, 100, 50) 
     self.zoomBtn.clicked.connect(self.zoomBtnAction) 
     self.LAYOUT_A.addWidget(self.zoomBtn, *(0,0)) 

     # Place the matplotlib figure 
     self.myFig = CustomFigCanvas() 
     self.LAYOUT_A.addWidget(self.myFig, *(0,1)) 

     # Add the callbackfunc to .. 
     myDataLoop = threading.Thread(name = 'myDataLoop', target = dataSendLoop, args = (self.addData_callbackFunc,)) 
     myDataLoop.start() 

     self.show() 

    '''''' 


    def zoomBtnAction(self): 
     print("zoom in") 
     self.myFig.zoomIn(5) 

    '''''' 

    def addData_callbackFunc(self, value): 
     # print("Add data: " + str(value)) 
     self.myFig.addData(value) 



''' End Class ''' 


class CustomFigCanvas(FigureCanvas, TimedAnimation): 

    def __init__(self): 

     self.addedData = [] 
     print(matplotlib.__version__) 

     # The data 
     self.xlim = 200 
     self.n = np.linspace(0, self.xlim - 1, self.xlim) 
     a = [] 
     b = [] 
     a.append(2.0) 
     a.append(4.0) 
     a.append(2.0) 
     b.append(4.0) 
     b.append(3.0) 
     b.append(4.0) 
     self.y = (self.n * 0.0) + 50 

     # The window 
     self.fig = Figure(figsize=(5,5), dpi=100) 
     self.ax1 = self.fig.add_subplot(111) 


     # self.ax1 settings 
     self.ax1.set_xlabel('time') 
     self.ax1.set_ylabel('raw data') 
     self.line1 = Line2D([], [], color='blue') 
     self.line1_tail = Line2D([], [], color='red', linewidth=2) 
     self.line1_head = Line2D([], [], color='red', marker='o', markeredgecolor='r') 
     self.ax1.add_line(self.line1) 
     self.ax1.add_line(self.line1_tail) 
     self.ax1.add_line(self.line1_head) 
     self.ax1.set_xlim(0, self.xlim - 1) 
     self.ax1.set_ylim(0, 100) 


     FigureCanvas.__init__(self, self.fig) 
     TimedAnimation.__init__(self, self.fig, interval = 50, blit = True) 

    def new_frame_seq(self): 
     return iter(range(self.n.size)) 

    def _init_draw(self): 
     lines = [self.line1, self.line1_tail, self.line1_head] 
     for l in lines: 
      l.set_data([], []) 

    def addData(self, value): 
     self.addedData.append(value) 

    def zoomIn(self, value): 
     bottom = self.ax1.get_ylim()[0] 
     top = self.ax1.get_ylim()[1] 
     bottom += value 
     top -= value 
     self.ax1.set_ylim(bottom,top) 
     self.draw() 


    def _step(self, *args): 
     # Extends the _step() method for the TimedAnimation class. 
     try: 
      TimedAnimation._step(self, *args) 
     except Exception as e: 
      self.abc += 1 
      print(str(self.abc)) 
      TimedAnimation._stop(self) 
      pass 

    def _draw_frame(self, framedata): 
     margin = 2 
     while(len(self.addedData) > 0): 
      self.y = np.roll(self.y, -1) 
      self.y[-1] = self.addedData[0] 
      del(self.addedData[0]) 


     self.line1.set_data(self.n[ 0 : self.n.size - margin ], self.y[ 0 : self.n.size - margin ]) 
     self.line1_tail.set_data(np.append(self.n[-10:-1 - margin], self.n[-1 - margin]), np.append(self.y[-10:-1 - margin], self.y[-1 - margin])) 
     self.line1_head.set_data(self.n[-1 - margin], self.y[-1 - margin]) 
     self._drawn_artists = [self.line1, self.line1_tail, self.line1_head] 



''' End Class ''' 


# You need to setup a signal slot mechanism, to 
# send data to your GUI in a thread-safe way. 
# Believe me, if you don't do this right, things 
# go very very wrong.. 
class Communicate(QtCore.QObject): 
    data_signal = QtCore.pyqtSignal(float) 

''' End Class ''' 



def dataSendLoop(addData_callbackFunc): 
    # Setup the signal-slot mechanism. 
    mySrc = Communicate() 
    mySrc.data_signal.connect(addData_callbackFunc) 

    # Simulate some data 
    n = np.linspace(0, 499, 500) 
    y = 50 + 25*(np.sin(n/8.3)) + 10*(np.sin(n/7.5)) - 5*(np.sin(n/1.5)) 
    i = 0 

    while(True): 
     if(i > 499): 
      i = 0 
     time.sleep(0.1) 
     mySrc.data_signal.emit(y[i]) # <- Here you emit a signal! 
     i += 1 
    ### 
### 




if __name__== '__main__': 
    app = QtGui.QApplication(sys.argv) 
    QtGui.QApplication.setStyle(QtGui.QStyleFactory.create('Plastique')) 
    myGUI = CustomMainWindow() 


    sys.exit(app.exec_()) 

'''''' 

Vor kurzem hatte ich zu matplotlib 2.0.0b4 wechseln. Frühere Versionen von Matplotlib sind nicht mit PyQt5 kompatibel. Ich möchte ein Live-Diagramm in eine bestehende PyQt5-Anwendung einbinden. Also musste ich auf matplotlib 2.0.0b4 wechseln. Hier

ist der angepasste Code:

################################################################### 
#                 # 
#      PLOTTING A LIVE GRAPH      # 
#     ----------------------------     # 
#   EMBED A MATPLOTLIB ANIMATION INSIDE YOUR    # 
#   OWN GUI!            # 
#   -> Python 3.5.2          # 
#   -> matplotlib: 2.0.0b4        # 
#   -> PyQt: 5           # 
#                 # 
################################################################### 


import sys 
import os 
from PyQt5.QtWidgets import * 
from PyQt5.QtGui import * 
from PyQt5.QtCore import * 

import functools 
import numpy as np 
import random as rd 
import matplotlib 
matplotlib.use("Qt5Agg") 
from matplotlib.figure import Figure 
from matplotlib.animation import TimedAnimation 
from matplotlib.lines import Line2D 
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas 
import time 
import threading 



def setCustomSize(x, width, height): 
    sizePolicy = QSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) 
    sizePolicy.setHorizontalStretch(0) 
    sizePolicy.setVerticalStretch(0) 
    sizePolicy.setHeightForWidth(x.sizePolicy().hasHeightForWidth()) 
    x.setSizePolicy(sizePolicy) 
    x.setMinimumSize(QSize(width, height)) 
    x.setMaximumSize(QSize(width, height)) 

'''''' 

class CustomMainWindow(QMainWindow): 

    def __init__(self): 

     super(CustomMainWindow, self).__init__() 


     # Define the geometry of the main window 
     self.setGeometry(300, 300, 800, 400) 
     self.setWindowTitle("my first window") 

     # Create FRAME_A 
     self.FRAME_A = QFrame(self) 
     self.FRAME_A.setStyleSheet("QWidget { background-color: %s }" % QColor(210,210,235,255).name()) 
     self.LAYOUT_A = QGridLayout() 
     self.FRAME_A.setLayout(self.LAYOUT_A) 
     self.setCentralWidget(self.FRAME_A) 

     # Place the zoom button 
     self.zoomBtn = QPushButton(text = 'zoom') 
     setCustomSize(self.zoomBtn, 100, 50) 
     self.zoomBtn.clicked.connect(self.zoomBtnAction) 
     self.LAYOUT_A.addWidget(self.zoomBtn, *(0,0)) 

     # Place the matplotlib figure 
     self.myFig = CustomFigCanvas() 
     self.LAYOUT_A.addWidget(self.myFig, *(0,1)) 

     # Add the callbackfunc to .. 
     myDataLoop = threading.Thread(name = 'myDataLoop', target = dataSendLoop, args = (self.addData_callbackFunc,)) 
     myDataLoop.start() 

     self.show() 

    '''''' 


    def zoomBtnAction(self): 
     print("zoom in") 
     self.myFig.zoomIn(5) 

    '''''' 

    def addData_callbackFunc(self, value): 
     # print("Add data: " + str(value)) 
     self.myFig.addData(value) 



''' End Class ''' 


class CustomFigCanvas(FigureCanvas, TimedAnimation): 

    def __init__(self): 
     self.addedData = [] 
     print(matplotlib.__version__) 

     # The data 
     self.xlim = 200 
     self.n = np.linspace(0, self.xlim - 1, self.xlim) 
     a = [] 
     b = [] 
     a.append(2.0) 
     a.append(4.0) 
     a.append(2.0) 
     b.append(4.0) 
     b.append(3.0) 
     b.append(4.0) 
     self.y = (self.n * 0.0) + 50 

     # The window 
     self.fig = Figure(figsize=(5,5), dpi=100) 
     self.ax1 = self.fig.add_subplot(111) 


     # self.ax1 settings 
     self.ax1.set_xlabel('time') 
     self.ax1.set_ylabel('raw data') 
     self.line1 = Line2D([], [], color='blue') 
     self.line1_tail = Line2D([], [], color='red', linewidth=2) 
     self.line1_head = Line2D([], [], color='red', marker='o', markeredgecolor='r') 
     self.ax1.add_line(self.line1) 
     self.ax1.add_line(self.line1_tail) 
     self.ax1.add_line(self.line1_head) 
     self.ax1.set_xlim(0, self.xlim - 1) 
     self.ax1.set_ylim(0, 100) 


     FigureCanvas.__init__(self, self.fig) 
     TimedAnimation.__init__(self, self.fig, interval = 50, blit = True) 

    def new_frame_seq(self): 
     return iter(range(self.n.size)) 

    def _init_draw(self): 
     lines = [self.line1, self.line1_tail, self.line1_head] 
     for l in lines: 
      l.set_data([], []) 

    def addData(self, value): 
     self.addedData.append(value) 

    def zoomIn(self, value): 
     bottom = self.ax1.get_ylim()[0] 
     top = self.ax1.get_ylim()[1] 
     bottom += value 
     top -= value 
     self.ax1.set_ylim(bottom,top) 
     self.draw() 


    def _step(self, *args): 
     # Extends the _step() method for the TimedAnimation class. 
     try: 
      TimedAnimation._step(self, *args) 
     except Exception as e: 
      self.abc += 1 
      print(str(self.abc)) 
      TimedAnimation._stop(self) 
      pass 

    def _draw_frame(self, framedata): 
     margin = 2 
     while(len(self.addedData) > 0): 
      self.y = np.roll(self.y, -1) 
      self.y[-1] = self.addedData[0] 
      del(self.addedData[0]) 


     self.line1.set_data(self.n[ 0 : self.n.size - margin ], self.y[ 0 : self.n.size - margin ]) 
     self.line1_tail.set_data(np.append(self.n[-10:-1 - margin], self.n[-1 - margin]), np.append(self.y[-10:-1 - margin], self.y[-1 - margin])) 
     self.line1_head.set_data(self.n[-1 - margin], self.y[-1 - margin]) 
     self._drawn_artists = [self.line1, self.line1_tail, self.line1_head] 



''' End Class ''' 


# You need to setup a signal slot mechanism, to 
# send data to your GUI in a thread-safe way. 
# Believe me, if you don't do this right, things 
# go very very wrong.. 
class Communicate(QObject): 
    data_signal = pyqtSignal(float) 

''' End Class ''' 



def dataSendLoop(addData_callbackFunc): 
    # Setup the signal-slot mechanism. 
    mySrc = Communicate() 
    mySrc.data_signal.connect(addData_callbackFunc) 

    # Simulate some data 
    n = np.linspace(0, 499, 500) 
    y = 50 + 25*(np.sin(n/8.3)) + 10*(np.sin(n/7.5)) - 5*(np.sin(n/1.5)) 
    i = 0 

    while(True): 
     if(i > 499): 
      i = 0 
     time.sleep(0.1) 
     mySrc.data_signal.emit(y[i]) # <- Here you emit a signal! 
     i += 1 
    ### 
### 




if __name__== '__main__': 

    app = QApplication(sys.argv) 
    QApplication.setStyle(QStyleFactory.create('Plastique')) 
    myGUI = CustomMainWindow() 
    sys.exit(app.exec_()) 



'''''' 

Der Code läuft gut. Das animierte Diagramm wird auf meinem Bildschirm angezeigt und es läuft reibungslos. Aber wenn ich die GUI schließe, wird Python nicht beendet. Es hängt einfach. Da ich meine Python-Programme immer von einer Windows-Shell aus starte, klicke ich auf die Schaltflächen Ctrl-C, um den Prozess zu beenden. Aber das hilft auch nicht. Ich muss die Cmd-Shell vollständig schließen, um den Python-Prozess zu beenden.

EDIT:

Offenbar matplotlib 1.5 mit PyQt5 kompatibel ist (danke Herr Tacaswell darauf hinweisen). Mein Hauptgrund, bei Matplotlib v2 zu bleiben, war der Einsatz von PyQt5. Dieses Argument gilt nicht mehr, also habe ich beschlossen, Matplotlib auf 1.5 herunterzustufen. Ich habe eine saubere Neuinstallation von Anaconda vorgenommen, um zu Matplotlib 1.5.3 zurückzukehren und alle Spuren von Matplotlib 2.0.0b zu löschen. Mein System ist nun wie folgt:

  • OS: Windows 10 64-Bit-
  • Python: 3.5.2 | Anaconda custom (64-bit) |
  • PyQt4:
    • Qt-Version: 4.8.7
    • SIP-Version: 4.18.1
    • PyQt Version: 4.11.4
  • pyqt5:
    • Qt-Version: 5.7.0
    • SIP-Version: 4.18.1
    • PyQt Version: 5,7
  • matplotlib: 1.5.3

TEST 1: Live-Grafik mit matplotlib 1.5.3 und PyQt4

ich den Code nur ausführen, die ich gegeben habe oben - basierend auf PyQt4. Die Live-Grafik wird problemlos angezeigt. Aber das Schließen der GUI ist nicht genug, um den Python-Prozess vollständig zu stoppen. Im Hintergrund läuft immer noch ein Zombie-Prozess. Ich muss in den Windows Task-Manager eintauchen, um es zu töten.Erst danach fordert die cmd-Shell erneut zur Eingabe auf. Also, das Problem ist immer noch das gleiche. Ich bin ein wenig verwirrt, weil ich mich erinnere, dass dieser Code auf Matplotlib 1.5 und PyQt4 gut funktioniert hat.

TEST 2: Live-Graph mit matplotlib 1.5.3 und PyQt5

ich genau das gleiche Problem.

+1

Es ist wirklich nicht Beta-Funktionalität weise. Wir sind nur vorsichtig beim Ändern der Standardstile. Außerdem sollte mpl 1.5 qt5 ... – tacaswell

+0

unterstützen. Können Sie das mit einem kürzeren Beispiel reproduzieren? – tacaswell

+0

Hallo @tacaswell. Ich möchte diese Gelegenheit nutzen, um ein großes Dankeschön für Ihre freiwillige Arbeit an Matplotlib zu sagen. Deine Arbeit macht einen großen Unterschied für Menschen wie mich, die sich Matlab nicht leisten können. Ich schätze deine harte Arbeit wirklich. Ich bin auch dankbar für Ihre Bemühungen hier auf StackOverflow Menschen zu helfen. Ich habe einen Abschnitt ** EDIT ** zu meiner Frage mit weiteren Details hinzugefügt. Ich hoffe, dass wir herausfinden können, wie man die Matplotlib-Prozesse ordnungsgemäß herunterfahren lässt. Nochmals vielen Dank :-) –

Antwort

0

Offenbar ist das Problem in der Schaffung des Hintergrund-Thread:

myDataLoop = threading.Thread(name = ..., target = ..., args = ...) 

sicher, dass eine solche Hintergrundthread wird beendet machen, wenn die Mainthread endet, haben Sie es als daemon zu definieren:

myDataLoop = threading.Thread(name = ..., daemon = True, target = ..., args = ...) 

Jetzt schließt es richtig :-)

Verwandte Themen