Embedding matplotlibAnimation

2019-07-29 09:46发布

I am designing an app that must plot a serial from a sensor through Arduino and Python. I am using matplotlib to animate my graph, and it works fine with the code that can be seen in a question I posted yesterday: Arduino Live Serial Plotting with a MatplotlibAnimation gets slow. Now, since I want to make a nice looking GUI, I want to embed my animation in PyQt5. For that I've taken as a reference this link https://pythonspot.com/en/pyqt5-matplotlib/ together with that one Getting blitting to work in funcAnimation embedded in PyQT4 GUI. My resulting code looks as follows:

import sys
from PyQt5.QtWidgets import QApplication, QMainWindow, QMenu, QVBoxLayout, QSizePolicy, QMessageBox, QWidget, \
    QPushButton
from PyQt5.QtGui import QIcon
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.figure import Figure
import matplotlib.pyplot as plt
import matplotlib.animation as animation
import serial
import time



class App(QMainWindow):
    def __init__(self):
        super().__init__()
        self.left = 10
        self.top = 10
        self.title = 'PyQt5 matplotlib example - pythonspot.com'
        self.width = 640
        self.height = 400

        self.initUI()

    def initUI(self):
        self.setWindowTitle(self.title)
        self.setGeometry(self.left, self.top, self.width, self.height)

        m = PlotCanvas(self, width=5, height=4)
        m.move(0, 0)

        button = QPushButton('PyQt5 button', self)
        button.setToolTip('This is an example button')
        button.move(500, 0)
        button.resize(140, 100)

        self.show()


class PlotCanvas(FigureCanvas):

    def __init__(self, parent=None, width=5, height=4, dpi=100):
        global fig

        fig = Figure(figsize=(width, height), dpi=dpi)
        FigureCanvas.__init__(self, fig)
        self.setParent(parent)
        #self.axes = fig.add_subplot(111)#, IYV: can be removed
        FigureCanvas.setSizePolicy(self,
                                   QSizePolicy.Expanding,
                                   QSizePolicy.Expanding)
        FigureCanvas.updateGeometry(self)
        self.plot()
        self.animate()


    def plot(self):
        global xar, yar, optimal_frequency, ser, ax1
        ser = serial.Serial("com3", 2400)
        ser.readline()
        optimal_frequency = 100
        ax1 = self.figure.add_subplot(111)
        xar = []
        yar = []
        print(time.ctime())


    def  animate(self):
        self.anim = animation.FuncAnimation(fig, self.animate_loop(), interval=optimal_frequency)
        self.draw()

    def animate_loop(self):
        global xar, yar
        ser.readline()
        for i in range(optimal_frequency):
            a = str(ser.readline(), 'utf-8')
            try:
                b = float(a)
            except ValueError:
                ser.readline()
            xar.append(str(time.time()))
            print(time.ctime())
            yar.append(int(b))
        ax1.clear()
        ax1.plot(xar, yar)

if __name__ == '__main__':
    app = QApplication(sys.argv)
    ex = App()
    sys.exit(app.exec_())

But I get the error:

Traceback (most recent call last):
  File "C:/Users/iyv/Documents/Udvikling/20161205_Serial_Plotter/Embedding_PyQt5/20161220_Embedding_Serial.py", line 113, in <module>
    ex = App()
  File "C:/Users/iyv/Documents/Udvikling/20161205_Serial_Plotter/Embedding_PyQt5/20161220_Embedding_Serial.py", line 35, in __init__
    self.initUI()
  File "C:/Users/iyv/Documents/Udvikling/20161205_Serial_Plotter/Embedding_PyQt5/20161220_Embedding_Serial.py", line 41, in initUI
    m = PlotCanvas(self, width=5, height=4)
  File "C:/Users/iyv/Documents/Udvikling/20161205_Serial_Plotter/Embedding_PyQt5/20161220_Embedding_Serial.py", line 71, in __init__
    self.animate()
  File "C:/Users/iyv/Documents/Udvikling/20161205_Serial_Plotter/Embedding_PyQt5/20161220_Embedding_Serial.py", line 87, in animate
    self.draw()
  File "C:\Users\iyv\AppData\Local\Programs\Python\Python35\lib\site-packages\matplotlib\backends\backend_qt5agg.py", line 159, in draw
    FigureCanvasAgg.draw(self)
  File "C:\Users\iyv\AppData\Local\Programs\Python\Python35\lib\site-packages\matplotlib\backends\backend_agg.py", line 474, in draw
    self.figure.draw(self.renderer)
  File "C:\Users\iyv\AppData\Local\Programs\Python\Python35\lib\site-packages\matplotlib\artist.py", line 62, in draw_wrapper
    draw(artist, renderer, *args, **kwargs)
  File "C:\Users\iyv\AppData\Local\Programs\Python\Python35\lib\site-packages\matplotlib\figure.py", line 1165, in draw
    self.canvas.draw_event(renderer)
  File "C:\Users\iyv\AppData\Local\Programs\Python\Python35\lib\site-packages\matplotlib\backend_bases.py", line 1809, in draw_event
    self.callbacks.process(s, event)
  File "C:\Users\iyv\AppData\Local\Programs\Python\Python35\lib\site-packages\matplotlib\cbook.py", line 563, in process
    proxy(*args, **kwargs)
  File "C:\Users\iyv\AppData\Local\Programs\Python\Python35\lib\site-packages\matplotlib\cbook.py", line 430, in __call__
    return mtd(*args, **kwargs)
  File "C:\Users\iyv\AppData\Local\Programs\Python\Python35\lib\site-packages\matplotlib\animation.py", line 661, in _start
    self._init_draw()
  File "C:\Users\iyv\AppData\Local\Programs\Python\Python35\lib\site-packages\matplotlib\animation.py", line 1221, in _init_draw
    self._draw_frame(next(self.new_frame_seq()))
  File "C:\Users\iyv\AppData\Local\Programs\Python\Python35\lib\site-packages\matplotlib\animation.py", line 1243, in _draw_frame
    self._drawn_artists = self._func(framedata, *self._args)
TypeError: 'NoneType' object is not callable
Exception ignored in: <bound method TimerQT.__del__ of <matplotlib.backends.backend_qt5.TimerQT object at 0x0000026C3260DD30>>
Traceback (most recent call last):
  File "C:\Users\iyv\AppData\Local\Programs\Python\Python35\lib\site-packages\matplotlib\backends\backend_qt5.py", line 201, in __del__
TypeError: 'method' object is not connected

Any help on how can I get this running? Cheers

2条回答
forever°为你锁心
2楼-- · 2019-07-29 09:59

As you can also see in the question you link to, FuncAnimation requires a method as its second argument. However in your call you provide None instead (since self.animate_loop() evaluates to None). Change this to

self.anim = animation.FuncAnimation(fig, self.animate_loop, interval=optimal_frequency)

Second, as can also be seen from the linked question, self.animate_loop needs to take an argument, so probably you would need to change this to

def animate_loop(self,i):

Apart from that there are some minor problems in your code, e.g. if b = float(a) fails, b is undefined and yar.append(int(b)) will raise an error. Also using global inside classes seems very strange; it's not a problem, but makes the code hard to read. Better use class variables.

查看更多
祖国的老花朵
3楼-- · 2019-07-29 10:19

Thanks a lot @ImportanceOfBeingErnest, that solved the problem. I also take your critic into consideration; I really need to gain a better insight into object-oriented programming.

The GUI though is slow-resposive: the window cannot easily be moved around or resized, and the button is hard to press. For this reason, I recommend discarding matplotlib for serial plotting, and use PyQtGraoh instead. The following code does the same, just with PyQtGraph/PyQt4 instead of Matplotlib/PyQt5. The code looks as:

import numpy as np

from pyqtgraph.Qt import QtGui, QtCore

import pyqtgraph as pg

import serial

import random

import time

app = QtGui.QApplication([])

p = pg.plot(title='random numbers generator')

curve = p.plot()

data = [0]

port = "com3"

baudrate = 600

ser = serial.Serial(port, baudrate)
ser.readline()
print(time.ctime())

def update():
    global curve, data
    a = str(ser.readline(), 'utf-8')
    try:
        data.append(float(a))
    except ValueError:
        ser.readline()

    curve.setData(data) #xdata is not necessary
    app.processEvents()


timer = QtCore.QTimer()
timer.timeout.connect(update)
timer.start(0)

if __name__ == '__main__':
    import sys
    if (sys.flags.interactive != 1) or not hasattr(QtCore, 'PYQT_VERSION'):
        QtGui.QApplication.instance().exec_()

I really wanted to use PyQt5 in order to being able to deply with PyQtDeploy, but I'll have to go all the way with PyInstaller, since this way of including PyQtGraph in PyQt5 seems pretty complicated for me : https://github.com/pyqtgraph/pyqtgraph/issues/33

查看更多
登录 后发表回答