How to speed up tkinter embedded matplot lib and python

≯℡__Kan透↙ 提交于 2020-01-17 06:53:23

问题


I have a GUI that I am using to send a query data from an arduino. I then went to live graph this data without slow down. I currently am getting slow down with the more numbers that I am plotting and i had a similar problem when I first wrote this GUI in matlab. I wanted to know if i could make this faster as I eventually want to be able to graph to multiple windows at once. I am aware of the animation library from a previous suggestion but was unsuccessful with getting it to work insife of my tkinter object from what I understand that would be the ideal way to speed up the graphing besides for drawing straight to the canvas. I only need to plot about 200 points of data at a time. Here is the current GUI code:

import Tkinter
import numpy as np
import serial
import time
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
from matplotlib.figure import Figure
from matplotlib import pyplot as plt
import matplotlib.animation as animation
from math import cos, sin
from collections import deque

class App:
    def __init__(self, master):

        frame = Tkinter.Frame(master)

        self.Max_press = Tkinter.StringVar()
        self.Max_press.set("10")
        self.Min_press = Tkinter.StringVar()
        self.Min_press.set("0")
        self.Cycle_per_minute = Tkinter.StringVar()
        self.Cycle_per_minute.set("12")
        self.Duration_cycle = Tkinter.StringVar()
        self.Duration_cycle.set("1")

        self.respiration = Tkinter.LabelFrame(frame, text="Respiration 
        Testing", borderwidth=10, relief=Tkinter.GROOVE, padx=10, pady=10)
        self.respiration.grid(row=0, column=0, padx=20, pady=20)

        self.max_pressure = Tkinter.Label(self.respiration, text="Maximum 
        Pressure (mmHg)")
        self.max_pressure.grid(row=0, column=0, padx=5, pady=5)

        self.Max_pressure = 
        Tkinter.Entry(self.respiration,textvariable=self.Max_press)
        self.Max_pressure.grid(row=1, column=0, padx=5, pady=5)

        self.min_pressure = Tkinter.Label(self.respiration, text="Minimum 
        Pressure (mmHg)")
        self.min_pressure.grid(row=2, column=0, padx=5, pady=5)

        self.Min_pressure = Tkinter.Entry(self.respiration, 
        textvariable=self.Min_press)
        self.Min_pressure.grid(row=3, column=0, padx=5, pady=5)

        self.cycles_per_minute = Tkinter.Label(self.respiration, 
        text="Cycles Per Minute")
        self.cycles_per_minute.grid(row=4, column=0, padx=5, pady=5)

        self.Cycles_per_minute = 
        Tkinter.Entry(self.respiration,textvariable=self.Cycle_per_minute)
        self.Cycles_per_minute.grid(row=5, column=0, padx=5, pady=5)

        self.duration_of_test = Tkinter.Label(self.respiration, 
        text="Duration (minutes)")
        self.duration_of_test.grid(row=6, column=0, padx=5, pady=5)

        self.Duration_of_test = Tkinter.Entry(self.respiration, 
        textvariable=self.Duration_cycle)
        self.Duration_of_test.grid(row=7, column=0, padx=5, pady=5)

        self.run_respiration = Tkinter.Button(self.respiration, text="RUN 
        RESPIRATION", command=self.getData)
        self.run_respiration.grid(row=8, column=0, padx=5, pady=5)

        self.burst = Tkinter.LabelFrame(frame, text="Burst Test", 
        borderwidth=10, relief=Tkinter.GROOVE, padx=10, pady=10 )
        self.burst.grid(row=0, column=1, padx=20, pady=20)

        self.burst_pressure = Tkinter.Button(self.burst, text="RUN BURST 
        TEST")
        self.burst_pressure.grid(row=0, column=0, padx=5, pady=5)

        self.test_options = Tkinter.LabelFrame(frame, text="Test Options", 
        borderwidth=10, relief=Tkinter.GROOVE, padx=10, pady=10 )
        self.test_options.grid(row=0, column=2, padx=20, pady=35)

        self.stop = Tkinter.Button(self.test_options, text="STOP", bd=10, 
        height=5, width=10)
        self.stop.grid(row=0, column=0, padx=10, pady=25)

        self.pause = Tkinter.Button(self.test_options, text="PAUSE", bd=10, 
        height=5, width=10)
        self.pause.grid(row=1, column=0, padx=10, pady=25)

        self.reset = Tkinter.Button(self.test_options, text="RESET", bd=10, 
        height=5, width=10)
        self.reset.grid(row=2, column=0, padx=10, pady=25)

        self.save = Tkinter.Button(self.test_options, text="SAVE", bd=10, 
        height=5, width=10)
        self.save.grid(row=3, column=0, padx=10, pady=25)

        self.xdata = deque([0]*200,maxlen=200)
        self.ydata = deque([0]*200,maxlen=200)

        self.fig = plt.Figure()
        self.ax1 = self.fig.add_subplot(111)
        self.line, = self.ax1.plot(self.xdata, self.ydata, lw=2)
        self.canvas = FigureCanvasTkAgg(self.fig,master=master)
        self.canvas.show()
        self.canvas.get_tk_widget().grid(row=0, column=3, padx=20, pady=20)
        frame.grid(row=0, column=0, padx=20, pady=20)

    def getData(self):
        press_max = float(self.Max_press.get())
        press_min = float(self.Min_press.get())
        duration = float(self.Duration_cycle.get())*60*20
        cycle_time = float(self.Cycles_per_minute.get())
        i = 0
        x = []
        y = []
        amp = (press_max - press_min)/2
        offset = amp + press_min
        spb = 60/cycle_time
        while (i < duration):
            x.append(i)
            sine = amp*np.sin((x[i]*(np.pi*4))/(2*spb)) + offset + 1
            y.append(sine)
            i = i + 1
        arduinoData = serial.Serial('com5', 115200)
        arduinoData.flushInput()
        press = []
        t = []
        i = 0
        start = time.time()
        while (i < duration + 1):
            while (arduinoData.inWaiting()==0):
                pass
            arduinoString = arduinoData.readline()
            dataArray = int(arduinoString)
            i = i + 1
            print (dataArray)
            self.plotData(dataArray,i)
    end = time.time()
    print (end - start)

    def plotData(self, y, x ):
        self.xdata.append(x)
        self.ydata.append(y)
        self.ax1.plot(self.xdata, self.ydata)
        self.canvas.show()



root = Tkinter.Tk()
app = App(root)
root.mainloop()

But the most important part is here:

        self.fig = plt.Figure()
        self.ax1 = self.fig.add_subplot(111)
        self.line, = self.ax1.plot(self.xdata, self.ydata, lw=2)
        self.canvas = FigureCanvasTkAgg(self.fig,master=master)
        self.canvas.show()
        self.canvas.get_tk_widget().grid(row=0, column=3, padx=20, pady=20)
        frame.grid(row=0, column=0, padx=20, pady=20)

    def getData(self):
        press_max = float(self.Max_press.get())
        press_min = float(self.Min_press.get())
        duration = float(self.Duration_cycle.get())*60*20
        cycle_time = float(self.Cycles_per_minute.get())
        i = 0
        x = []
        y = []
        amp = (press_max - press_min)/2
        offset = amp + press_min
        spb = 60/cycle_time
        while (i < duration):
            x.append(i)
            sine = amp*np.sin((x[i]*(np.pi*4))/(2*spb)) + offset + 1
            y.append(sine)
            i = i + 1
        arduinoData = serial.Serial('com5', 115200)
        arduinoData.flushInput()
        press = []
        t = []
        i = 0
        start = time.time()
        while (i < duration + 1):
            while (arduinoData.inWaiting()==0):
                pass
            arduinoString = arduinoData.readline()
            dataArray = int(arduinoString)
            i = i + 1
            print (dataArray)
            self.plotData(dataArray,i)
        end = time.time()
        print (end - start)

    def plotData(self, y, x ):
        self.xdata.append(x)
        self.ydata.append(y)
        self.ax1.plot(self.xdata, self.ydata)
        self.canvas.show()

root = Tkinter.Tk()
app = App(root)
root.mainloop()

回答1:


You need to use the matplotlib animation capabilities for the fastest response. Here's an example inside a tkinter window. I obviously can't test it since I don't have your arduino, so I commented out the arduino parts and added some random parts.

import Tkinter as tk
import serial
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
from matplotlib.figure import Figure
from matplotlib import pyplot as plt
import matplotlib.animation as animation
from collections import deque
import random

HISTORY_LEN = 200

class App(tk.Frame):
    def __init__(self, master=None, **kwargs):
        tk.Frame.__init__(self, master, **kwargs)

        self.running = False
        self.ani = None

        btns = tk.Frame(self)
        btns.pack()

        lbl = tk.Label(btns, text="Number of points")
        lbl.pack(side=tk.LEFT)

        self.points_ent = tk.Entry(btns, width=5)
        self.points_ent.insert(0, '500')
        self.points_ent.pack(side=tk.LEFT)

        lbl = tk.Label(btns, text="update interval (ms)")
        lbl.pack(side=tk.LEFT)

        self.interval = tk.Entry(btns, width=5)
        self.interval.insert(0, '30')
        self.interval.pack(side=tk.LEFT)

        self.btn = tk.Button(btns, text='Start', command=self.on_click)
        self.btn.pack(side=tk.LEFT)

        self.fig = plt.Figure()
        self.ax1 = self.fig.add_subplot(111)
        self.line, = self.ax1.plot([], [], lw=2)
        self.canvas = FigureCanvasTkAgg(self.fig,master=master)
        self.canvas.show()
        self.canvas.get_tk_widget().pack()

    def on_click(self):
        if self.ani is None:
            return self.start()
        if self.running:
            self.ani.event_source.stop()
            self.btn.config(text='Un-Pause')
        else:
            self.ani.event_source.start()
            self.btn.config(text='Pause')
        self.running = not self.running

    def start(self):
        self.xdata = deque([], maxlen=HISTORY_LEN)
        self.ydata = deque([], maxlen=HISTORY_LEN)
        #~ self.arduinoData = serial.Serial('com5', 115200)
        #~ self.arduinoData.flushInput()
        self.points = int(self.points_ent.get()) + 1
        self.ani = animation.FuncAnimation(
            self.fig,
            self.update_graph,
            frames=self.points,
            interval=int(self.interval.get()),
            repeat=False)
        self.running = True
        self.btn.config(text='Pause')
        self.ani._start()

    def update_graph(self, i):
        self.xdata.append(i)
        #~ self.ydata.append(int(self.arduinoData.readline()))
        self.ydata.append(random.randrange(100)) # DEBUG
        self.line.set_data(self.xdata, self.ydata)
        self.ax1.set_ylim(min(self.ydata), max(self.ydata))
        self.ax1.set_xlim(min(self.xdata), max(self.xdata))
        if i >= self.points - 1:
            #~ self.arduinoData.close()
            self.btn.config(text='Start')
            self.running = False
            self.ani = None
        return self.line,

def main():
    root = tk.Tk()
    app = App(root)
    app.pack()
    root.mainloop()

if __name__ == '__main__':
    main()

For more speed, cut out the print functions. Printing to the terminal is very slow. Also, you may want to move the serial initialization to someplace where it's only called once.

For my computer, the fastest I could go was 25 ms / frame.



来源:https://stackoverflow.com/questions/43477681/how-to-speed-up-tkinter-embedded-matplot-lib-and-python

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!