update the plot in animation for tkinter in python - python-3.x

I have a complicated program and have problem to update the curve in animation, so I simplified the program and posted a similar post before previous post, but that example is simplified too much and could not represent the actual problem. So that example got fixed but the actual program still does not work. Here I made some changes and post the code again.
The program will try to update a straight line twice at time = 3 seconds and 5 seconds , the problem is
if I do not define the xlim and ylim, then if the update is out of range, we could not see it; if I define the xlim and ylim, then the animation will plot two different curves every time I use canvas.draw(). I also tried subplot1.clear() and replot, but same thing. when I set blit=False, everything is ok, but I have to use blit=true. It looks animation keeps two sets of data in the memory, any way to delete the old data?
from multiprocessing import Process
import matplotlib
matplotlib.use("TkAgg")
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
##, NavigationToolbar2TkAgg
from matplotlib.figure import Figure
import matplotlib.animation as animation
from matplotlib import style
import tkinter as tk
from tkinter import ttk
from tkinter import *
import matplotlib.pyplot as plt
import numpy as np
import time
f = Figure(figsize=(6,6), dpi=100)
subplot_1 = f.add_subplot(211)
subplot_2 = f.add_subplot(212)
a_count = 0
b_count = 0
LARGE_FONT= ("Verdana", 12)
style.use("ggplot")
count_plot = 0
first_replot,second_replot = 0,0
start_time = 0
draw_replot = 0
a_t = [1,2,3,4,5]
a_T = np.ones(5)
canvas_draw_timer = 0
def replot(update_flag):
global a_t, count_plot,theory_line,a_T
if update_flag == 1:
a_t = [3,4,5,6,7]
a_T = np.ones(5)*2
theory_line[0].set_data(a_t,a_T)
elif update_flag == 2:
a_t = [5,6,7,8,9]
a_T = np.ones(5)*1.5
theory_line[0].set_data(a_t,a_T)
else:
theory_line = subplot_1.plot(a_t,a_T,'c-')
canvas.draw()
def animate(i):
global a_count,b_count,first_replot,second_replot,canvas_draw_timer
time1 = np.random.rand(2, 25)
data = np.random.rand(2, 25)*2
#first time update the canvas
if (time.time() - start_time > 3) and (first_replot == 0):
replot(1)
first_replot = 1
#second time update the canvas
if (time.time() - start_time > 5) and (second_replot == 0):
replot(2)
second_replot = 1
#refresh the canvas per second, just example, many other places need to use canvas.draw() later
if time.time() - canvas_draw_timer > 1:
canvas.draw()
canvas_draw_timer = time.time()
a = subplot_1.scatter(time1,data,c='blue',s=2)
return a,
class home(tk.Tk):
def __init__(self, *args, **kwargs):
tk.Tk.__init__(self, *args, **kwargs)
tk.Tk.wm_title(self, "Graph ")
self.geometry("1000x1000")
container = tk.Frame(self)
container.pack(side="top", fill="both", expand = True)
container.grid_rowconfigure(0, weight=1)
container.grid_columnconfigure(0, weight=1)
self.frames = {}
F=Graph
frame=Graph(container, self)
self.frames[F] = frame
frame.grid(row=0, column=0, sticky="nsew")
self.show_frame(Graph)
def show_frame(self, cont):
frame = self.frames[cont]
frame.tkraise()
def get_frame(self, frame_class):
return self.frames[frame_class]
##
class Graph(tk.Frame):
def __init__(self, parent, controller):
global canvas
tk.Frame.__init__(self, parent)
label = tk.Label(self, text=" ", font=LARGE_FONT)
label.pack(pady=120,padx=10)
collectbtn=Button(self,text='collect',command=self.clickstart)
collectbtn.place(x=200,y=100)
canvas = FigureCanvasTkAgg(f, self)
canvas.get_tk_widget().pack(side=tk.BOTTOM, fill=tk.BOTH, expand=True)
canvas._tkcanvas.pack(side=tk.TOP, fill=tk.BOTH, expand=True)
def clickstart(self):
global theory_line,start_time
theory_line = subplot_1.plot(a_t,a_T,'c-')
#set the large x and y range first, otherwise blit = true could not plot out of range plots
f.axes[0].set_xlim([0,10])
f.axes[0].set_ylim([0,5])
start_time = time.time()
aniplot_photon = animation.FuncAnimation(f, animate, blit=True,interval=100)
canvas.draw()
app = home()
app.mainloop()

Related

how to use blit=true for animation subplot in python tkinter

The following code is a simplified version of my project, and it could run and plot with animation, but I have to add blit=true in the animation to speed up (my original UI is much more complicated and slow). I know the animation function must return a sequence of Artist objects. here what is the artist object in my subplot_2? I tried subplot_2,a,f, none of them work. Many thanks
from multiprocessing import Process
import matplotlib
matplotlib.use("TkAgg")
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
##, NavigationToolbar2TkAgg
from matplotlib.figure import Figure
import matplotlib.animation as animation
from matplotlib import style
import tkinter as tk
from tkinter import ttk
from tkinter import *
import matplotlib.pyplot as plt
import numpy as np
f = Figure(figsize=(6,6), dpi=100)
subplot_1 = f.add_subplot(211)
subplot_2 = f.add_subplot(212)
LARGE_FONT= ("Verdana", 12)
style.use("ggplot")
def animate(i):
time = np.random.rand(2, 25)
data = np.random.rand(2, 25)
a = subplot_2.scatter(time,data,c='blue',s=2)
#return a
class home(tk.Tk):
def __init__(self, *args, **kwargs):
tk.Tk.__init__(self, *args, **kwargs)
tk.Tk.wm_title(self, "Graph ")
self.geometry("1000x1000")
container = tk.Frame(self)
container.pack(side="top", fill="both", expand = True)
container.grid_rowconfigure(0, weight=1)
container.grid_columnconfigure(0, weight=1)
self.frames = {}
F=Graph
frame=Graph(container, self)
self.frames[F] = frame
frame.grid(row=0, column=0, sticky="nsew")
self.show_frame(Graph)
def show_frame(self, cont):
frame = self.frames[cont]
frame.tkraise()
def get_frame(self, frame_class):
return self.frames[frame_class]
##
class Graph(tk.Frame):
def __init__(self, parent, controller):
global canvas
tk.Frame.__init__(self, parent)
label = tk.Label(self, text=" ", font=LARGE_FONT)
label.pack(pady=120,padx=10)
collectbtn=Button(self,text='collect',command=self.clickstart)
collectbtn.place(x=200,y=100)
canvas = FigureCanvasTkAgg(f, self)
## canvas.draw()
canvas.get_tk_widget().pack(side=tk.BOTTOM, fill=tk.BOTH, expand=True)
canvas._tkcanvas.pack(side=tk.TOP, fill=tk.BOTH, expand=True)
def clickstart(self):
animate(0)
#aniplot_photon = animation.FuncAnimation(f, animate, blit=True,interval=100)
aniplot_photon = animation.FuncAnimation(f, animate, interval=100)
canvas.draw()
app = home()
app.mainloop()
Updates:
Thanks to the William's answer, it works. However, I have further questions, if this subplot include many curves "subplot_2.scatter(x1..),subplot_2.scatter(x2..),subplot_2.scatter(x3..),subplot_2.scatter(x1..)...", or some subplots are updated from different threads (make objects and global them?), it will be hard to put all curves into return list. However, I found a simple solution for the above listed problems, I could just use return [subplot_2] and it will update all plots inside subplot_2, but the coordinates(x ticks) are lost (run and you will see). is there a simple way I could keep the coordinates when I use return [suplot_2]?
def animate(i):
time = np.random.rand(2, 25)
data = np.random.rand(2, 25)
a = subplot_2.scatter(time,data,c='blue',s=2)
return [subplot_2]
matplotlib.pyplot.scatter returns an artist (a PathCollection to be exact), so the artist you need to return from animate is stored in a. However, FuncAnimation requires a sequence of artists, so
return a
is insufficient (as this is a single artist, not a sequence). You can use one of
return [a]
or
return a,
to satisfy the "sequence of artists" requirement.

A Single Tkinter Window Which Moves Through Frames, Bringing Each Frame to the Foreground. One Frame needs to be split into two Sub-frames

The need for two different geometric plotters arises because i want to display a sophisticated GUI with text, entry fields, pictures, buttons, alongside an animated matplotlib graph on a FigureCanvasTkAgg with a NavigationToolbar2Tk. The NavigationToolbar2Tk fails to work when any geometric plotter other than pack() is used. I have tried over the course of the week several different methods of putting the NavigationToolbar2Tk into its own frame, but in the examples, only a single frame exists. I came to the conclusion that ideally, splitting my frame ~ PageOne ~ into two subframes or instantiating two frames which make up page one.
I have several frames as shown in my code below and my knowledge of python is rudimentary so i don't have the imagination or know how to circumvent this problem. I have gutted my code to show the problem concisely and Included my Imports.
import matplotlib
matplotlib.use("TkAgg")
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg, NavigationToolbar2Tk
from matplotlib.figure import Figure
from matplotlib.pylab import *
from matplotlib import style
import matplotlib.animation as animation
import matplotlib.pyplot as plt
import matplotlib.dates as dates
import matplotlib.ticker as mticker
import tkinter as tk
from tkinter import ttk
from tkinter import *
from PIL import Image, ImageTk
from mpl_toolkits.axes_grid1 import host_subplot
from gpiozero import CPUTemperature
import time
import datetime as dt
import pandas as pd
import numpy as np
#************************************************************************#
# Format Graph_1 [ax1] onto fig1 at subplot [ row = 1, col = 1, index = 1 ]
# Set Figure Text
font = {'size' : 9}
matplotlib.rc('font', **font)
# Setup Figure
fig1 = Figure()
# Define Axis 1 for Vin and Vout
ax1 = fig1.add_subplot(1, 1, 1)
#subplots_adjust(left=0.05, bottom=0.10, right=0.55, top=0.8, wspace=0.2)
ax1.minorticks_on()
ax1.grid(b=True, which='major', color='k', linestyle='-')
ax1.grid(b=True, which='minor', color='k', linestyle=':')
ax1.set_title("PI3740 Paramaters", fontsize = 12)
ax1.set_xlabel("Relative Time (s)", fontsize = 10)
ax1.set_ylabel("Voltage (V)", fontsize =10)
# Define Axis 2 for Iout Which is tied to Axis 1's X-Axis
ax2 = ax1.twinx()
ax2.set_ylabel("Output Current (A)")
# Parameters
x_len = 500 # Resolution [Number of Points in Window]
x_max = 2 # X-Axis Range [ (Step)ms Samp^-1 * (x_len)Samp = X_Range]
y_range = [0, 50] # Range of possible Y values to display
# Create figure for plotting
steps = (x_max/x_len)
stepms = steps * 1000
xs = np.arange(0, x_max, steps) # xs is a list from 0 to 10 in steps of 0.01 [A list refers to a 1D Array]
ys1 = [0] * x_len # ys is a list indexed from ys[0] to ys[999] all containing 0 # Vin
ys2 = [0] * x_len # ys is a list indexed from ys[0] to ys[999] all containing 0 # Vout
ys3 = [0] * x_len # ys is a list indexed from ys[0] to ys[999] all containing 0 # Iout
ax1.set_ylim(y_range) # Y-Axis Voltage Range Set
ax2.set_ylim(0, 10) # Y-Axis Current Range Set
ax1.set_xlim(0, x_max) # X-Axis Shared Relative Time Range Set
# Create a blank line. We will update the line in animate
line1, = ax1.plot(xs, ys1, 'b-', label = "Vin")
line2, = ax1.plot(xs, ys2, 'g-', label = "Vout")
line3, = ax2.plot(xs, ys3, 'r-', label = "Iout")
# Create a Legend
ax1.legend([line1, line2],[line1.get_label(), line2.get_label()])
ax1.legend(bbox_to_anchor = (0.,0.99,1.,.102), loc = 3, ncol = 2, borderaxespad = 0., frameon = False)
ax2.legend([line3],[line3.get_label()])
ax2.legend(bbox_to_anchor = (1.00,0.99), loc = 'lower right', borderaxespad = 0., frameon = False)
#************************************************************************#
#**********************Animation Function********************************#
# This function is called periodically from FuncAnimation
def updateData(self):
# Drop down menu event flags
global ChartLoad
# Graph variables
global xs
global ys1
global ys2
global ys3
if ChartLoad == True:
# Read temperature (Celsius) from TMP102
temp_c = cpu.temperature
temp_c1 = temp_c + 5.0
temp_c2 = temp_c - 35.0
# Add y to list
ys1.append(temp_c)
ys2.append(temp_c1)
ys3.append(temp_c2)
# Limit y list to set number of items
ys1 = ys1[-x_len:]
ys2 = ys2[-x_len:]
ys3 = ys3[-x_len:]
# Update line with new Y values
line1.set_ydata(ys1)
line2.set_ydata(ys2)
line3.set_ydata(ys3)
return line1, line2, line3,
#************************************************************************#
#*******************Tkinter Window Initalization*************************#
class MyApp(tk.Tk):
def __init__(self, *args, **kwargs):
tk.Tk.__init__(self, *args, **kwargs)
tk.Tk.wm_title(self,"EMEA Vicor Charging Application ")
img=tk.PhotoImage(file='/home/pi/Pictures/Vicor_Icon1.png')
self.tk.call('wm','iconphoto',self._w,img)
container = tk.Frame(self)
container.pack(side="top", fill="both", expand = True)
container.grid_rowconfigure(0, weight=1)
container.grid_columnconfigure(0, weight=1)
#*********************************************************
#******Function Required to Display Seperate Pages********
self.frames = {}
for F in (StartPage, HomePage, PageOne):
frame = F(container, self)
self.frames[F] = frame
frame.grid(row=0, column=0, sticky="nsew")
self.show_frame(StartPage)
def show_frame(self, cont):
frame = self.frames[cont]
frame.tkraise()
#*********************************************************
#*********************************************************
#Start Page - Agreement
class StartPage(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
#Page One - Primary Terminal
class PageOne(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
tk.Frame.__init__(self, parent, bg = "white")
# Two Frames Need to be Established on Page One (Which is a “Higher Order Frame”)
my_window = Tk()
frame_name = Frame(my_window)
frame_addr = Frame(my_window)
label_f = Label(frame_name, text = "Check")
label_f.grid(row = 0, column = 0)
label_g = Label(frame_addr, text = "Correct")
label_g.grid(row = 0, column = 0)
frame_name.grid(row = 0, column = 0)
frame_addr.grid(row = 0, column = 1)
#my_window.mainloop()
app = MyApp()
app.geometry("1010x700")
ani = animation.FuncAnimation(fig1, updateData, blit = True, interval = stepms)
app.mainloop()
Page One will contain my Canvas for the Graph and Tool Bar. however to simplify the problem into its fundamental, getting two frames to make up "Higher Order" frame PageOne, with each subframe containing a label. When I run the code, another window opens with the two labels displayed. This is more progress than from other solutions that i can't implement/don't understand, producing error messages i can't respond to. I am self taught at python, and have been following tutorials from the community. I just need some help implementing the solution. If I've gutted my code to much i can provide a more comprehensive code snippet. But the essence of the problem is, while displaying one of several frames on a Tkinter Window which move to the foreground when requested, how do you split one of these "High Order" frames into two frames as to allow two different geometric plotters to be used to structure each one.
Resources Used So Far:
https://www.youtube.com/watch?v=Mxk4cMBaH3g&list=PL6lxxT7IdTxGoHfouzEK-dFcwr_QClME_&index=37&t=0s
[Most Recent Attempt - Trying to Simplify the problem]
how to make two split up screen ( canvas) inside the python tkinter window
http://www.openbookproject.net/courses/python4fun/tkphone1.html
matplotlib Navigation Bar error 'FigureCanvasTkAgg' object has no attribute 'manager' in tkinter
Displaying Matplotlib Navigation Toolbar in Tkinter via grid
[Hit the Same Problem - One Master Frame Split Into Two Sub Frames]
[This Link is probably has the answer in it and if that is the case, apologies for repeating a question already asked on stack overflow)
EDIT. To Expand Further for Clarity take the code below:
import tkinter as tk # python 3
from tkinter import font as tkfont # python 3
#import Tkinter as tk # python 2
#import tkFont as tkfont # python 2
class SampleApp(tk.Tk):
def __init__(self, *args, **kwargs):
tk.Tk.__init__(self, *args, **kwargs)
self.title_font = tkfont.Font(family='Helvetica', size=18, weight="bold", slant="italic")
# the container is where we'll stack a bunch of frames
# on top of each other, then the one we want visible
# will be raised above the others
container = tk.Frame(self)
container.pack(side="top", fill="both", expand=True)
container.grid_rowconfigure(0, weight=1)
container.grid_columnconfigure(0, weight=1)
self.frames = {}
for F in (StartPage, PageOne, PageTwo):
page_name = F.__name__
frame = F(parent=container, controller=self)
self.frames[page_name] = frame
# put all of the pages in the same location;
# the one on the top of the stacking order
# will be the one that is visible.
frame.grid(row=0, column=0, sticky="nsew")
self.show_frame("StartPage")
def show_frame(self, page_name):
'''Show a frame for the given page name'''
frame = self.frames[page_name]
frame.tkraise()
class StartPage(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
self.controller = controller
label = tk.Label(self, text="This is the start page", font=controller.title_font)
label.pack(side="top", fill="x", pady=10)
button1 = tk.Button(self, text="Go to Page One",
command=lambda: controller.show_frame("PageOne"))
button2 = tk.Button(self, text="Go to Page Two",
command=lambda: controller.show_frame("PageTwo"))
button1.pack()
button2.pack()
class PageOne(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
self.controller = controller
label = tk.Label(self, text="This is page 1", font=controller.title_font)
label.pack(side="top", fill="x", pady=10)
button = tk.Button(self, text="Go to the start page",
command=lambda: controller.show_frame("StartPage"))
button.pack()
class PageTwo(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
self.controller = controller
label = tk.Label(self, text="This is page 2", font=controller.title_font)
label.pack(side="top", fill="x", pady=10)
button = tk.Button(self, text="Go to the start page",
command=lambda: controller.show_frame("StartPage"))
button.pack()
if __name__ == "__main__":
app = SampleApp()
app.mainloop()
how would i get PageTwo to compose of two different frames so i can use two different geometric plotters (pack() and grid()).

Can someone please tell me what is wrong with this code?

I'm working on Idle and when I ask to check the file, it says no problems. When I run it though, it crashes the app. I'm also working on a MAC, I don't know if that will influence anything. Can anyone please tell me what's wrong with this code? I'm still fairly new, so any help is appreciated.
Also what I am trying to do is mostly imbed a matplotlib animation into a GUI when the GUI has the entries for the animation data, and a button to call the animation is on the GUI as well. Ideally the button would be clicked and the Animation would show up above the two entries. I have been working on this program for a while, but imbedding the animation/ getting the button to callback the graph has been the most troublesome part.
import tkinter as tk
from tkinter import *
import time
import pyaudio
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
from matplotlib.figure import Figure
from matplotlib import pyplot as plt
import matplotlib.animation as animation
import numpy as np
import random
fig = plt.figure()
ax1 = fig.add_subplot(1,1,1)
def get_data():
while True:
return x, mylist
class App(tk.Frame):
def __init__(self, root):
self.root = root
self.root.title("Testing")
self.setupTk()
self.quit_button = Button(master=self.root, text='Quit', command=self._quit)
self.quit_button.pack(side='right')
def _quit(self):
self.root.destroy()
fig = plt.Figure()
x = np.arange(0, 2*np.pi, 0.01) # x-array
def animate(i):
line.set_ydata(np.sin((x+i/10.0))) # update the data
return line,
root = tk.Tk()
label = tk.Label(root,text="SHM Simulation").grid(column=0, row=0)
canvas = FigureCanvasTkAgg(fig, master=root)
canvas.get_tk_widget().grid(column=0,row=1)
entry1= Entry(root, text='Frequency')
canvas.get_tk_widget().pack(side=TOP, fill=BOTH, expand=1)
entry2= Entry(root, text='Duration')
canvas.get_tk.widget().pack(side=TOP, fill=BOTH, expand=1)
entry2.pack()
f=int(entry1.get())
t=float(entry2.get())
def callback():
p=pyaudio.Pyaudio
fs=44100
volume=.7
samples=(np.sin(f*2*np.pi*np.arange(fs*t)/(fs).astype(np/float32)))
stream=p.open(format=pyaudio.paFloat32,
channels=1,
rate=fs,
output=True)
stream.write(volume*samples)
b=Button(root, text='Sound', command=callback)
ax = fig.add_subplot(111)
line, = ax.plot(x, np.sin(x))
ani = animation.FuncAnimation(fig, animate, np.arange(1, 200), interval=25, blit=False)
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.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()
print('started animation')
def update_graph(self, i):
self.line.set_data(*get_data()) # update graph
if i >= self.points - 1:
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
As I was interested in the pyaudio I have tried to make your code runnable with the functionality that I think you had intended. You can build from here to add a random signal to the sine wave or use different signals all together. Main changes is that all functions are now in the class App. Also to have the audio sound and plot to run at the same time I start the audio in a different thread.
Hope this is useful for you.
import threading
import random
import numpy as np
import tkinter as tk
from tkinter import Label, Button, Entry, TOP, BOTH
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
from matplotlib.figure import Figure
from matplotlib import pyplot as plt
from matplotlib.animation import FuncAnimation
import pyaudio
DEFAULT_FREQUENCY = 220
DEFAULT_DURATION = 5
VOLUME = 0.3
INTERVAL = 100
class App(tk.Frame):
def __init__(self, root):
self.root = root
self.root.title("SHM Simulation - Testing")
label = tk.Label(self.root, text="SHM Simulation")
label.pack()
self.fig, self.ax = plt.subplots(figsize=(5, 5))
self.canvas = FigureCanvasTkAgg(self.fig, master=root)
self.canvas.get_tk_widget().pack(side=TOP, fill=BOTH, expand=1)
self.ax.set_ylim(-1.1, 1.1)
self.ax.set_xlim(-0.1, 2 * np.pi + 0.1)
self.quit_button = Button(master=self.root, text='Quit', command=self.quit)
self.quit_button.pack(side='right')
self.sound_button = Button(self.root, text='Sound off', command=self.toggle_sound)
self.sound_button.pack(side='right')
self.plot_button = Button(self.root, text='Start')
self.plot_button.pack(side='right')
self.plot_button.bind('<Button-1>', self.on_click)
tk.Label(self.root, text='Frequency').pack(side='left')
self.frequency_entry = Entry(self.root)
self.frequency_entry.insert(0, DEFAULT_FREQUENCY)
self.frequency_entry.pack(side='left')
tk.Label(self.root, text='Duration').pack(side='left')
self.duration_entry = Entry(self.root)
self.duration_entry.insert(0, DEFAULT_DURATION)
self.duration_entry.pack(side='left')
self.ani = None
self.running = False
self.audio = pyaudio.PyAudio()
self.sound = False
self.samples = np.array([])
def quit(self):
self.audio.terminate()
self.root.quit()
def get_data(self, frame):
self.ydata = np.sin(self.frequency * self.xdata + frame)
def update_frame(self, frame):
self.get_data(frame)
self.line.set_data(self.xdata, self.ydata)
if frame > self.duration - 1.1 * INTERVAL / 1000 :
self.ani = None
self.running = False
self.plot_button.config(text='Start')
return self.line,
def start_animation(self):
self.xdata = []
self.ydata = []
self.line, = self.ax.plot(self.xdata, self.ydata, lw=3)
self.xdata = np.arange(0, 2 * np.pi, 0.01)
duration_range = np.arange(0, self.duration, INTERVAL / 1000)
self.ani = FuncAnimation(self.fig,
self.update_frame,
frames=duration_range,
interval=INTERVAL,
repeat=False)
if self.sound:
''' start audio in a seperate thread as otherwise audio and
plot will not be at the same time
'''
x = threading.Thread(target=self.play_sound)
x.start()
def callback(self, in_data, frame_count, time_info, status):
out = self.samples[:frame_count]
self.samples = self.samples[frame_count:]
return (out*VOLUME, pyaudio.paContinue)
def play_sound(self):
fs = 44100
self.samples = (np.sin(2 * np.pi * np.arange(fs * self.duration) *
self.frequency / fs)).astype(np.float32)
stream = self.audio.open(format=pyaudio.paFloat32,
channels=1,
rate=fs,
output=True,
stream_callback=self.callback)
stream.start_stream()
# pause audio when self.running is False
while stream.is_active():
while not self.running:
if stream.is_active:
stream.stop_stream()
else:
pass
if self.running and not stream.is_active():
stream.start_stream()
else:
pass
stream.stop_stream()
stream.close()
def toggle_sound(self):
if self.sound:
self.sound_button.configure(text='Sound Off')
else:
self.sound_button.configure(text='Sound On ')
self.sound = not self.sound
def on_click(self, event):
if self.ani is None:
try:
self.ax.lines.pop(0)
except IndexError:
pass
try:
self.frequency = int(self.frequency_entry.get())
self.duration = int(self.duration_entry.get())
except ValueError:
self.frequency = DEFAULT_FREQUENCY
self.frequency_entry.insert(0, DEFAULT_FREQUENCY)
self.duration = DEFAULT_DURATION
self.duration_entry.insert(0, DEFAULT_DURATION)
self.start_animation()
self.running = True
self.plot_button.config(text='Pause')
print('animation started ...')
return
if self.running:
self.ani.event_source.stop()
self.plot_button.config(text='Run ')
else:
self.ani.event_source.start()
self.plot_button.config(text='Pause')
self.running = not self.running
def main():
root = tk.Tk()
app = App(root)
root.mainloop()
if __name__ == '__main__':
main()

Memory used increases with tkinter notebook and matplotlib

I've got a small program that creates a GUI using tkinter. It contains a button that loads a .csv file, creates a notebook with as many tabs as columns in the csv file. Then, on every active tab (at least this is my intention) I have a plot created from a Figure.
The program works as expected , the only problem is that when switching Tabs, the memory used increases with each Tab click.
Memory usage was monitored using the Windows Task Manager.
After loading a csv file, I didn't see the used memory dropping when I chose not loading a new file.
If I don't call the plotting function, when creating only the Tabs, there is no memory issue.
I already tried to manually invoke the garbage collector with gc.collect(), but that didn't help. This is the code I have:
import matplotlib
matplotlib.use('TkAgg')
import pandas as pd
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg, NavigationToolbar2Tk
from matplotlib.figure import Figure
import matplotlib.pyplot as plt
import os
import sys
import tkinter as tk
from tkinter import messagebox as msg
from tkinter import ttk, filedialog
##import gc
class Graphs(tk.Tk):
def __init__(self, *args, **kwargs):
tk.Tk.__init__(self, *args, **kwargs)
container = tk.Frame(self)
container.pack(side='top', fill='both', expand=1)
container.grid_rowconfigure(0, weight=1)
container.grid_columnconfigure(0, weight=1)
self.protocol('WM_DELETE_WINDOW', self._destroyWindow)
self.frames = {}
frame = StartPage(parent=container, controller=self)
self.frames[StartPage] = frame
frame.grid(row=0, column=0, sticky='nsew')
self.show_frame(StartPage)
def show_frame(self, cont):
frame = self.frames[cont]
frame.tkraise()
def _destroyWindow(self):
self.quit() # stops mainloop
self.destroy()
class StartPage(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
# initialize lists
self.tabs_list = []
self.hdrs = []
self.figs_list = []
self.ax_list = []
self.canvas_list = []
self.toolbars_list = []
# initialize Data Frame
self.df = pd.DataFrame()
self.nb = None
self.canvas = None
self.toolbar = None
# create LOAD button
self.btn = tk.Button(self, text = 'Load file', command=self.load_csv)
self.btn.pack()
def load_csv(self):
'''
Reset Data Frame;
Destroy notebook if exists;
Load CSV file.
'''
# reset Data Frame
self.df = pd.DataFrame()
# destroy notebook if exists
if self.nb:
self.nb.pack_forget()
self.nb.destroy()
self.nb = None
## gc.collect()
# Select CSV file
self.file_path = filedialog.askopenfilename()
if not self.file_path:
msg.showinfo('Select CSV file', "No file chosen.")
return
try:
# read csv file (exemple.csv)
self.df = pd.read_csv(self.file_path, header=0)
except:
msg.showinfo('Select CSV file', 'Not a csv file / corrupt file.')
return
print(self.df.head())
print(self.df.shape)
# get dimensions
self.m, self.n = self.df.shape
# build the abscissa x from first column
self.x = self.df.iloc[:,0]
# create the notebook
self.nb = ttk.Notebook(self)
# allow Tab navigation
self.nb.enable_traversal()
# add Tabs
for k in range(1, self.n):
hdr = self.df.columns[k]
self.hdrs.append(hdr)
tab = tk.Frame(self.nb, name=hdr.lower())
self.nb.add(tab, text=hdr)
self.tabs_list.append(tab)
self.nb.pack(fill='both', expand=1)
# virtual event after a new tab is selected
self.nb.bind("<<NotebookTabChanged>>", self.plotTH)
def plotTH(self, event):
'''
Plot each Column from Data Frame on its own Tab/Figure
'''
# get path of the selected Tab
tab_path = event.widget.nametowidget(event.widget.select())
# add selected Tab to the list of Tabs
self.tabs_list.append(tab_path)
# get the Tab index;
# When there are no tabs, .select() returns an empty string,
# but .index('current') throws an exception;
# nb.select() returns the Tab NAME (string) of the current selection
if self.nb.select():
i = self.nb.index('current')
# get the Tab text
tab_text = self.nb.tab(i)['text']
else:
return
# remove previous figures ... not sure...
# the used memory as seen in Task Manager still increases
if self.canvas_list:
for cnv in self.canvas_list:
cnv.figure.get_axes().clear()
cnv.get_tk_widget().pack_forget()
cnv.get_tk_widget().destroy()
cnv._tkcanvas.pack_forget()
cnv._tkcanvas.destroy()
cnv = None
if self.figs_list:
for fig in self.figs_list:
fig.delaxes(fig.gca())
plt.cla()
fig.clf()
fig.clear()
plt.close(fig)
self.figs_list = []
# remove toolbar
for widget in tab_path.winfo_children():
widget.pack_forget()
widget.destroy()
self.nb.update() #!!!!!!!!!!!!
######## gc.collect()
# prepare plotting
fig = Figure(figsize=(7, 5), dpi=100)
ax = fig.add_subplot(111)
ax.plot(self.x, self.df.iloc[:,i+1], 'b-', linewidth=1, label=tab_text)
ax.set_xlabel('index')
ax.set_title(self.hdrs[i], fontsize = 8)
ax.legend(loc='best')
ax.grid()
# add to list of figures
self.figs_list.append(fig)
# add to list of axes
self.ax_list.append(ax)
canvas = FigureCanvasTkAgg(fig, master=tab_path)
# add to list of canvases
self.canvas_list.append(canvas)
## self.canvas.draw()
canvas.draw_idle()
canvas.get_tk_widget().pack(side=tk.TOP, fill=tk.BOTH, expand=1)
toolbar = NavigationToolbar2Tk(canvas, tab_path)
# add to list of toolbars
self.toolbars_list.append(toolbar)
toolbar.update()
canvas._tkcanvas.pack(side=tk.TOP, fill=tk.BOTH, expand=1)
def clearPlot(self):
""" not used"""
pass
app = Graphs()
app.title('CSV Plots')
app.geometry('800x600+400+150')
app.resizable(True, True)
app.mainloop();
I created a working csv file using this code:
import numpy as np
import pandas as pd
np.random.seed(0)
df = pd.DataFrame(np.random.randn(50,4), columns=['ALPHA', 'BETA', 'GAMMA', 'DELTA'])
df.index.names = ['Rec']
df.index = df.index + 1
df.to_csv('example.csv', index=True)
print(df)
I'm sorry for the long post. I really don't know were to go from here, so any help would be greatly appreciated.
None of the following lists are ever purged; they hold references to large objects that cannot be garbage collected.
self.tabs_list = []
self.hdrs = []
self.figs_list = []
self.ax_list = []
self.canvas_list = []
self.toolbars_list = []
You probably should create an object that holds the data for each tab, and destroy/purge/reassign to None that when changing tabs.
I changed the code; I realized I was creating a new figure (with all the Artists) and a new canvas (FigureCanvasTkAgg) with every TabChanged event. Instead, I create a figure and its canvas at the moment of Tab addition to the notebook; I do the plot only when the TabChanged occurs and I take care to close all the plots and to destroy the previous notebook when performing a new CSV file load. Destroying the notebook removes the canvases too. Here is the code:
import matplotlib
import matplotlib.style as mplstyle
matplotlib.use('TkAgg')
import pandas as pd
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg, NavigationToolbar2Tk
from matplotlib.figure import Figure
import matplotlib.pyplot as plt
import os, sys, time
import tkinter as tk
from tkinter import messagebox as msg
from tkinter import ttk, filedialog
mplstyle.use('fast')
class Graphs(tk.Tk):
def __init__(self, *args, **kwargs):
tk.Tk.__init__(self, *args, **kwargs)
container = tk.Frame(self)
container.pack(side='top', fill='both', expand=1)
container.grid_rowconfigure(0, weight=1)
container.grid_columnconfigure(0, weight=1)
self.protocol('WM_DELETE_WINDOW', self._destroyWindow)
self.frames = {}
frame = StartPage(parent=container, controller=self)
self.frames[StartPage] = frame
frame.grid(row=0, column=0, sticky='nsew')
self.show_frame(StartPage)
def show_frame(self, cont):
frame = self.frames[cont]
frame.tkraise()
def _destroyWindow(self):
self.quit() # stops mainloop
self.destroy()
class StartPage(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
# initialize Data Frame
self.df = pd.DataFrame()
self.nb = None
self.fig = None
self.canvas = None
self.toolbar = None
# create LOAD button
self.btn = tk.Button(self, text = 'Load file', command=self.load_csv)
self.btn.pack()
def load_csv(self):
''' Close the plots;
Reset Data Frame;
Destroy notebook if exists;
Load CSV file.
'''
# Setting interactive mode off
if plt.isinteractive():
plt.ioff()
plt.close("all")
# reset Data Frame
self.df = pd.DataFrame()
# initialize list
self.hdrs = []
try:
# destroy notebook if exists
self.nb.pack_forget()
self.nb.destroy()
except:
pass
self.nb = None
# Select CSV file
self.file_path = filedialog.askopenfilename()
if not self.file_path:
msg.showinfo('Select CSV file', "No file chosen.")
return
try:
# read csv file (exemple.csv)
self.df = pd.read_csv(self.file_path, header=0)
except:
msg.showinfo('Select CSV file', 'Not a csv file / corrupt file.')
return
# get dimensions
self.m, self.n = self.df.shape
# build the abscissa x from first column
self.x = self.df.iloc[:,0]
# create the notebook
self.nb = ttk.Notebook(self)
# allow Tab navigation
self.nb.enable_traversal()
# add Tabs
for k in range(1, self.n):
hdr = self.df.columns[k]
self.hdrs.append(hdr)
tab = tk.Frame(self.nb, name=hdr.lower())
self.nb.add(tab, text=hdr)
self.fig = plt.figure(num=hdr.lower(), clear=True, figsize=(7, 5), dpi=100)
# self.ax = self.fig.add_subplot()
self.canvas = FigureCanvasTkAgg(self.fig, master=tab)
self.canvas.get_tk_widget().pack(side=tk.TOP, fill=tk.BOTH, expand=1)
toolbar = NavigationToolbar2Tk(self.canvas, tab)
toolbar.update()
self.canvas._tkcanvas.pack(side=tk.TOP, fill=tk.BOTH, expand=1)
self.nb.pack(fill='both', expand=1)
# virtual event after a new tab is selected
self.nb.bind("<<NotebookTabChanged>>", self.plotTH)
def plotTH(self, event):
'''
Plot each Column from Data Frame on its own Tab/Figure
'''
# Setting interactive mode on is essential: plt.ion()
if not plt.isinteractive():
plt.ion()
# tab index
i = self.nb.index('current')
# tab text
tab_text = self.nb.select().split('.')[-1]
# set current figure
cf = plt.figure(tab_text)
plt.clf()
# plotting
ax = plt.subplot(111)
ax.plot(self.x, self.df.iloc[:,i+1], 'b-', linewidth=1, label=tab_text)
ax.set_xlabel('index')
ax.set_title(self.hdrs[i], fontsize = 8)
ax.legend(loc='best')
ax.grid()
cf.canvas.draw()
app = Graphs()
app.title('CSV Plots')
app.geometry('800x600+400+150')
app.resizable(True, True)
app.mainloop()

Display an array as an image Tkinter

I'm attempting to display an image from a compressed DICOM data array using tkinter. I compressed the image to an 8-bit numpy array. I know that the array can be visualized, as I have visualized it using both cv2 and matplotlib. Below is how I created the data:
import cv2
import numpy as np
import pydicom #https://pydicom.github.io/pydicom/stable/api_ref.html
import os
import glob
from PIL import Image, ImageTk
import PIL
import tkinter as tk
from pathlib import Path
path = Path("C:/Users/H61972/Desktop/1-1058/DICOM")
os.chdir( path )
os.getcwd()
print(__doc__)
#Get dicom files sorted by filename
def get_dicom():
return glob.glob("**/IM*",recursive=True)#slices
#return one file to be read at a time
def load_image(dicom):
ds = pydicom.read_file(dicom)
#print(ds.SliceLocation)
data = np.array(ds.pixel_array)
#data = data - np.min(data)
x = np.max(data)/255
data = data/x
data = np.clip(data, 0, 255)
return data
dicom = get_dicom()
def process_frame():
global data
frame = load_image(dicom[10])
frame = cv2.equalizeHist(frame)
frame = cv2.blur(frame,(5,5))
return frame
And below is the Tkinter gui I am building:
class mainWindow():
def __init__(self):
self.root = tk.Tk()
self.frame = tk.Frame(self.root, width=500, height=400)
self.frame.pack()
self.canvas = tk.Canvas(self.frame, width=500,height=400)
self.canvas.place(x=-2,y=-2)
data= process_frame()
self.im=Image.frombytes('L', (data.shape[1],data.shape[0]), data.astype('b').tostring())
self.photo = ImageTk.PhotoImage(image=self.im)
self.canvas.create_image(0,0,image=self.photo,anchor=tk.NW)
self.root.update()
self.root.mainloop()
mainWindow()
Any advice would be much appreciated!
I found a solution to most of my problem, however I have created a new problem in doing so. Now when I run the program I create an extra window. Here are my changes:
class DICOM(tk.Tk):
def __init__(self, *args, **kwargs):
tk.Tk.__init__(self, *args, **kwargs)#initialized tkinter
container = tk.Frame(self)#define our tkinter container
container.pack(side ="top", fill="both", expand=True)
container.grid_rowconfigure(0,weight=1)
container.grid_columnconfigure(0,weight=1)
self.frames = { }
#for F in (StartPage, Viewer):
frame = mainWindow(container, self)
self.frames[mainWindow] = frame
frame.grid(row =0, column =0, sticky ="nsew")
self.show_frame(mainWindow)
def show_frame(self, cont):
frame = self.frames[cont]
frame.tkraise()
class mainWindow(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
self.controller = controller
self.root = tk.Toplevel(class_=self.controller)
# self.frame = tk.Frame(self.root)
# self.frame.pack()
self.canvas = tk.Canvas(self.root)
self.canvas.place(x=0,y=0)
data= process_frame()
self.im=Image.frombytes('L', (data.shape[1],data.shape[0]), data.astype('b').tostring())
self.photo = ImageTk.PhotoImage(image=self.im)
self.canvas.create_image(0,0,image=self.photo,anchor=tk.NW)
self.root.update()
#self.root.mainloop()
app = DICOM()
app.geometry("800x800")
app.title("Head CT Volumetric Analysis")
app.config(bg="gray")
app.mainloop()
How can I prevent this extra window from appearing?

Resources