Python program becoming unstable after multiple simulations - python

I apologize if this has an easy answer, but I have been searching everywhere and trying everything I could think of to solve this problem to no avail.
Basically I have a differential equation solver that simulates heat flow in a plate, and every time I click a Tkinter button, I would like to run a fresh simulation/animation. The problem is that the program tends to become unstable after multiple calls for new simulations.
I knew something was awry when I allowed the user to specify whether an animation would loop continuously. If an animation was allowed to loop, subsequent simulations (looping or not) would slow down and the CPU would remain loaded as if the animation was still looping somewhere in the background after it should have been replaced with the new one.
Here are the relevant portions of my code, please let me know if I'm doing something dumb.
class GUI(object):
def __init__(self,root):
self.print_button = tk.Button(self.root, text='Run Simulation',
command=self.run).grid()
def run(self):
simulation = HeatSim(self)
simulation.compute(self)
simulation.animate(self)
class HeatSim(GUI):
def __init__(self,gui):
# Initialize variables here
def compute(self,gui):
# Compute values of data matrix
def animate(self,gui):
frames = 100
fig = plt.figure()
ax = fig.add_subplot(111)
# Canvas used to display the animation embedded within a tkinter window
canvas = FigureCanvasTkAgg(fig, master=gui.root)
canvas.show()
canvas.get_tk_widget().grid()
images = []
for i in range(frames):
im = ax.imshow(data[:,:,i],cmap='hot',animated=True)
images.append([im])
anim = animation.ArtistAnimation(fig,images,interval=0,blit=True,
repeat=0)
canvas.show()
# Main program loop
root = tk.Tk()
gui = GUI(root)
root.mainloop()

Related

Tkinter GUI start button registering input but not restarting program

Brief explanation of my program (or what it's meant to do):
I have created a simulation program that models amoeba populations in Pygame. The program uses two classes - Main and Amoeba. The Main class runs the simulation and displays the results on a Pygame window and a Matplotlib plot. The Amoeba class models the properties and behavior of each amoeba in the population, including its maturing speed, age, speed, and movement direction. The simulation runs in a loop until the "q" key is pressed or the simulation is stopped. The GUI is created using the Tkinter library, which allows the user to interact with the simulation by starting and stopping it. The simulation updates the amoeba population and displays their movements on the Pygame window and updates the Matplotlib plot every 100 steps. The plot displays the average maturing speed and the reproduction rate of the amoeba population.
My issue is that whilst the stop button in the GUI works fine, the start button does not. It registers being pressed and actually outputs the variable it is meant to change to the terminal (the running variable which you can see more of in the code). So the issue is not in the button itself, but rather the way in which the program is restarted. I have tried to do this via if statements and run flags but it has failed. There are no error messages, the program just remains paused.
Here is the code to run the simulation from my Main.py file (other initialisation code before this):
def run_simulation():
global step_counter
global num_collisions
global run_flag
while run_flag:
if globalvars.running:
#main code here
else:
run_flag = False
gc.root = tk.Tk()
app = gc.GUI(gc.root)
app.root.after(100, run_simulation)
gc.root.mainloop()
This is the code from my GUI class:
import tkinter as tk
import globalvars
class GUI:
def __init__(self,root):
self.root = root
self.root.title("Graphical User Interface")
self.root.geometry("200x200")
self.startbutton = tk.Button(root, bg="green", text="Start", command=self.start)
self.startbutton.pack()
self.stopbutton = tk.Button(root, bg="red", text="Stop", command=self.stop)
self.stopbutton.pack()
def start(self):
globalvars.running = True
print(globalvars.running)
def stop(self):
globalvars.running = False
print(globalvars.running)
Also in a globalvars.py file I store global variables which includes the running var.
Would you mind explaining the issue please?
There's a logic error in the application: when stop() is called it sets globalvars.running = False. This means, in run_simulation() the else branch is executed which turns run_flag = False.
This variable is never reset to True!
So the while loop is left and never entered again and #main code here not executed.
In addition to setting run_flag = True, function run_simulation() needs to be called from start().
Turned my earlier comment into an answer so it can be accepted and the question resolved.

Python Tkinter OOP Layout Configuration

I am trying to build an application with tkinter.
The layout works without OO principles, but I am struggling to understand how I should move it to OO.
The layout is as shown in the pic below. (1280x720px)
I have the following:
banner on top with username/welcome message, and logo rh corner, columnspan=8
menu bar with buttons on the left, split into 2 rows (row1: rowspan 6, row2: rowspan=4)
working area (white block) that has a Frame, which I'll add a notebook to, each menu button opening a different notebook page.
What is the best way to make this OO? (I am still learning, so very new to OO)
There is no straight translation possible, since everything depends on your needs.
If you create a simple programm you can just create the class and create every Button,Label,Frame... in the constructor. When created you have to choose one of the layout managers grid,pack or place. After that you create your functions and you are done. If you deal with bigger projects and have a big amount of Labels, Buttons etc.. you maybe want to create containers for each.
In your case you wont need a lot of functions and buttons, so you should maybe go with the basic approach:
from tkinter import *
class name_gui:
def __init__(self, top):
#specify main window
self.top = top
self.title = top.title("name_gui")
self.minsize = top.geometry("1280x720")
self.resizable = top.resizable(height=False,width=False)
#create buttons,Labels,Frames..
self.Button1 = Button(top,text="Button1",command=self.exa_fun)
self.Button2 = Button(top,text="Button2",command=self.exa_fun2)
#place them, choose grid/place/pack
self.Button1.place(relx=0.5,rely=0.5)
self.Button2.place(relx=0.5,rely=0.2)
#create your functions
def exa_fun(self):
pass
def exa_fun2(self):
pass
top = Tk()
exa_gui = name_gui(top)
top.mainloop()

Slow FuncAnimation Script

I use python 3.6 (the newest one) with the spider interpreter on a windows 10 PC with more-than-average hardware and wrote a script that enables me to continuously measure and plot the frequencies of two channels on an Agilent frequency counter and saves the data to a txt file. I furthermore have to convert the script to a .exe file (using pyinstaller) in order to distribute it to several measuring PCs.
Everything works fine, even from the .exe file, until the measuring time reaches about 2000 seconds. The software then starts to become very slow until it even show the "window not answering" thing that windows does while plotting.
I tried to activate the bliting-function of FuncAnimate, but when I do so, it only shows a white window.
Therefore I am now looking for options to enhance the speed of my software, especially at high data amounts, without cutting of the lots and lots of data (they are needed to be seen from t=0 to t=whatever).
Why does the blit=True kill my animation? Or is there a better way to plot these amounts of data fast and over and over again?
My code:
#Importing all required additional Python packages and sub-packages/functions
import visa, time, tkinter,sys
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg as FigureCanvas
from tkinter import messagebox, filedialog
from matplotlib.figure import Figure
import matplotlib.animation as animation
#Initialize the tkinter main window (graphical user interface window)
root=tkinter.Tk()
root.geometry('1200x900')
#Initialize the VISA resource manager and define a list object
rm=visa.ResourceManager('C:\\Windows\\System32\\visa32.dll')
liste=[]
#lists all available VISA resources and adds them to the list object
#one entry per VISA instrument
string=str(rm.list_resources())[1:-1]
liste.append(string.split('\'')[1:-1])
#opens a message box for each object in the list (each instrument)
#if user chosses "yes" for a VISA resource, the software tries to access the device
#if the device is present, the for-loop is left, otherwise the whole software quits
for i in liste[0]:
box=messagebox.askyesno('VISA Resources','Is this the correct VISA-Resource?'+'\n'+str(i))
if box==True:
try: inst=rm.open_resource(i)
except:
messagebox.showerror('Wrong Resource','The VISA resource was wrong!')
root.destroy()
sys.exit()
break
elif box==False: continue
#checks if the VISA resource is actually existent and present
try: inst
except:
messagebox.showerror('No Resource found','No VISA Resource was chosen.')
root.destroy()
sys.exit()
#opens a file dialog window and safes the chosen location as "filename"
#furthermore checks if the user specified a valid path or quited by using "cancel"
#if the user clicked "cancel", the software quits
filename=filedialog.asksaveasfilename()
if len(filename)==0:
messagebox.showerror('No File','No file location was specified.')
root.destroy()
sys.exit()
#definition of variables as well as the update function
x_data,y_data1, y_data2, y_data3=[],[],[],[]
def update(frame):
#create X-data, seconds since .clock was first called
x_data.append(time.clock())
#read out current freq on channel1 and float it (originaly returned as string)
value1=float(inst.query('MEAS:FREQ? 10 MHz, 0.1 Hz, (#1)'))
#add data to list
y_data1.append(value1)
#define and automatically adjust subplot limits
subplot1.set_ylim(min(y_data1)-1,max(y_data1)+1)
subplot1.set_xlim(min(x_data)-1,max(x_data)+1)
#define subplot title and labels
subplot1.set_title('Channel 1')
subplot1.set_xlabel('Time')
subplot1.set_ylabel('Frequency')
#same as above for second channel
value2=float(inst.query('MEAS:FREQ? 10 MHz, 0.1 Hz, (#2)'))
y_data2.append(value2)
subplot2.set_ylim(min(y_data2)-1,max(y_data2)+1)
subplot2.set_xlim(min(x_data)-1,max(x_data)+1)
subplot2.set_title('Channel 2')
subplot2.set_xlabel('Time')
subplot2.set_ylabel('Frequency')
#calculates and plots the difference of the upper two channels
y_data3.append(value1-value2)
subplot3.set_ylim(min(y_data3)-1,max(y_data3)+1)
subplot3.set_xlim(min(x_data)-1,max(x_data)+1)
subplot3.set_title('Difference')
subplot3.set_xlabel('Time')
subplot3.set_ylabel('Frequency')
#plots the subplots in the main plot frame
subplot1.plot(x_data,y_data1,'b')
subplot2.plot(x_data, y_data2,'r')
subplot3.plot(x_data, y_data3,'g')
#writes all data do a new file defined before
newfile.write(str(time.clock())+', '+str(value1)+', ' +str(value2)+'\n')
#enables the code to make use of the defined variables/data
return x_data, y_data1, y_data2, y_data3
#create a global boolean variable and set it to "True"
global boolean
boolean=True
#define a Pause function using the global boolean variable
def Pause():
global boolean
#if the boolean is True, the animation stops and the variable is set to False
if boolean==True:
anim.event_source.stop()
boolean=False
#if the boolean is False, the animation continues and the variable is set to True
elif boolean==False:
anim.event_source.start()
boolean=True
#define a Quit function that quits the application and closes the created file
def Quit():
newfile.close()
root.destroy()
#define a function that applies the user input data aquisition time to the animation
def SpeedApply():
anim.event_source.interval=int(float(Interv.get())*1000)
#create and place different buttons that call the defined functions upon buttonclick
QuitBut=tkinter.Button(text='Quit', command=Quit)
QuitBut.place(x=15,y=15)
StartBut=tkinter.Button(text='Pause/Resume',command=Pause)
StartBut.place(x=55, y=15)
Interv=tkinter.Spinbox(root,values=(0.1,0.2,0.5,1,1.5,2), width=8)
Interv.place(x=160, y=17)
Interv.delete(0,'end')
Interv.insert(0,1)
Speedbut=tkinter.Button(text='Apply Speed', command=SpeedApply)
Speedbut.place(x=250, y=15)
#create the figure needed to plot the animated data
figure=Figure(figsize=(8,8), dpi=100)
subplot1=figure.add_subplot(311)
subplot2=figure.add_subplot(312)
subplot3=figure.add_subplot(313)
figure.subplots_adjust(hspace=0.6)
#create a tkinter canvas, needed to embedd the figure into a tkinter root window
canvas=FigureCanvas(figure,root)
canvas.draw()
#canvas.start_event_loop(0.001)
canvas.get_tk_widget().place(x=25,y=50, height=850, width=1150)
#create the newfile where the data will be stored lateron
newfile=open(filename+time.strftime('%d')+time.strftime('%m')+time.strftime('%y')+'.txt','w')
newfile.write('Time, Channel 1, Channel 2\n')
#animation calling the update function upon the figure in the canvas with an interval of 1 second
anim=animation.FuncAnimation(figure,update, blit=False, interval=1000)
#tkinter mainloop, needed to react to user input in tkinter GUI
root.mainloop()
Okay, as requested, a simpler script showing my problem:
import time, tkinter
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg as FigureCanvas
from matplotlib.figure import Figure
import matplotlib.animation as animation
#Initialize the tkinter main window (graphical user interface window)
root=tkinter.Tk()
root.geometry('1200x900')
value1=1
value2=2
#definition of variables as well as the update function
x_data,y_data1, y_data2, y_data3=[],[],[],[]
def update(frame):
#create X-data, seconds since .clock was first called
x_data.append(time.clock())
global value1, value2, value3
value1=value1+2
#add data to list
y_data1.append(value1)
#define and automatically adjust subplot limits
subplot1.set_ylim(min(y_data1)-1,max(y_data1)+1)
subplot1.set_xlim(min(x_data)-1,max(x_data)+1)
#define subplot title and labels
subplot1.set_title('Channel 1')
subplot1.set_xlabel('Time')
subplot1.set_ylabel('Frequency')
#same as above for second channel
value2=value2+1
y_data2.append(value2)
subplot2.set_ylim(min(y_data2)-1,max(y_data2)+1)
subplot2.set_xlim(min(x_data)-1,max(x_data)+1)
subplot2.set_title('Channel 2')
subplot2.set_xlabel('Time')
subplot2.set_ylabel('Frequency')
#calculates and plots the difference of the upper two channels
y_data3.append(value1-value2)
subplot3.set_ylim(min(y_data3)-1,max(y_data3)+1)
subplot3.set_xlim(min(x_data)-1,max(x_data)+1)
subplot3.set_title('Difference')
subplot3.set_xlabel('Time')
subplot3.set_ylabel('Frequency')
#plots the subplots in the main plot frame
subplot1.plot(x_data,y_data1,'b')
subplot2.plot(x_data, y_data2,'r')
subplot3.plot(x_data, y_data3,'g')
#enables the code to make use of the defined variables/data
return x_data, y_data1, y_data2, y_data3
#create a global boolean variable and set it to "True"
global boolean
boolean=True
#define a Quit function that quits the application and closes the created file
def Quit():
root.destroy()
#create and place different buttons that call the defined functions upon buttonclick
QuitBut=tkinter.Button(text='Quit', command=Quit)
QuitBut.place(x=15,y=15)
#create the figure needed to plot the animated data
figure=Figure(figsize=(8,8), dpi=100)
subplot1=figure.add_subplot(311)
subplot2=figure.add_subplot(312)
subplot3=figure.add_subplot(313)
figure.subplots_adjust(hspace=0.6)
#create a tkinter canvas, needed to embedd the figure into a tkinter root window
canvas=FigureCanvas(figure,root)
canvas.draw()
#canvas.start_event_loop(0.001)
canvas.get_tk_widget().place(x=25,y=50, height=850, width=1150)
#animation calling the update function upon the figure in the canvas with an interval of 1 second
anim=animation.FuncAnimation(figure,update, blit=False, interval=100)
#tkinter mainloop, needed to react to user input in tkinter GUI
root.mainloop()
I left the "beautyful-makings" like titles in the code.
To actually see the problem I am having you would have to run that script for at least 600 seconds, the problem becomes "stronger" after 2000 seconds.
It seems that a large part of your issues is having to recompute the entire lists min and max each time,
e.g. your subplot x and y min/max values, rather than processing your entire appended list, you should use a variable to hold the lowest and highest values so far compare the current piece of input data to them, and if it meets condition update them,
subplot1.set_ylim(min(y_data1)-1,max(y_data1)+1)
subplot1.set_xlim(min(x_data)-1,max(x_data)+1)
You also appear to be resetting up the subplot labels every single update, now I am not as familiar with GUI's in TKinter, but i feel this could be covered in an initilisation definition,
subplot1.set_title('Channel 1')
subplot1.set_xlabel('Time')
subplot1.set_ylabel('Frequency')
The first suggestion should keep your program running in a more consistent time, rather than incrementally slowing down, the other should be able to reduce how much your doing per update.

Python 3.x Proper way to make gameloop in tkinter? [duplicate]

I have a simple code to visualise some data using tkinter. A button click is bound to the function that redraws the next "frame" of data. However, I'd like to have the option to redraw automatically with a certain frequency. I'm very green when it comes to GUI programming (I don't have to do a lot for this code), so most of my tkinter knowledge comes from following and modifying examples. I guess I can use root.after to achieve this, but I'm not quite sure I understand how from other codes. The basic structure of my program is as follows:
# class for simulation data
# --------------------------------
def Visualisation:
def __init__(self, args):
# sets up the object
def update_canvas(self, Event):
# draws the next frame
canvas.delete(ALL)
# draw some stuff
canvas.create_........
# gui section
# ---------------------------------------
# initialise the visualisation object
vis = Visualisation(s, canvasWidth, canvasHeight)
# Tkinter initialisation
root = Tk()
canvas = Canvas(root, width = canvasWidth, height = canvasHeight)
# set mouse click to advance the simulation
canvas.grid(column=0, row=0, sticky=(N, W, E, S))
canvas.bind('<Button-1>', vis.update_canvas)
# run the main loop
root.mainloop()
Apologies for asking a question which I'm sure has an obvious and simple answer. Many thanks.
The basic pattern for doing animation or periodic tasks with Tkinter is to write a function that draws a single frame or performs a single task. Then, use something like this to call it at regular intervals:
def animate(self):
self.draw_one_frame()
self.after(100, self.animate)
Once you call this function once, it will continue to draw frames at a rate of ten per second -- once every 100 milliseconds. You can modify the code to check for a flag if you want to be able to stop the animation once it has started. For example:
def animate(self):
if not self.should_stop:
self.draw_one_frame()
self.after(100, self.animate)
You would then have a button that, when clicked, sets self.should_stop to False
I just wanted to add Bryan's answer. I don't have enough rep to comment.
Another idea would be to use self.after_cancel() to stop the animation.
So...
def animate(self):
self.draw_one_frame()
self.stop_id = self.after(100, self.animate)
def cancel(self):
self.after_cancel(self.stop_id)

simple animation using tkinter

I have a simple code to visualise some data using tkinter. A button click is bound to the function that redraws the next "frame" of data. However, I'd like to have the option to redraw automatically with a certain frequency. I'm very green when it comes to GUI programming (I don't have to do a lot for this code), so most of my tkinter knowledge comes from following and modifying examples. I guess I can use root.after to achieve this, but I'm not quite sure I understand how from other codes. The basic structure of my program is as follows:
# class for simulation data
# --------------------------------
def Visualisation:
def __init__(self, args):
# sets up the object
def update_canvas(self, Event):
# draws the next frame
canvas.delete(ALL)
# draw some stuff
canvas.create_........
# gui section
# ---------------------------------------
# initialise the visualisation object
vis = Visualisation(s, canvasWidth, canvasHeight)
# Tkinter initialisation
root = Tk()
canvas = Canvas(root, width = canvasWidth, height = canvasHeight)
# set mouse click to advance the simulation
canvas.grid(column=0, row=0, sticky=(N, W, E, S))
canvas.bind('<Button-1>', vis.update_canvas)
# run the main loop
root.mainloop()
Apologies for asking a question which I'm sure has an obvious and simple answer. Many thanks.
The basic pattern for doing animation or periodic tasks with Tkinter is to write a function that draws a single frame or performs a single task. Then, use something like this to call it at regular intervals:
def animate(self):
self.draw_one_frame()
self.after(100, self.animate)
Once you call this function once, it will continue to draw frames at a rate of ten per second -- once every 100 milliseconds. You can modify the code to check for a flag if you want to be able to stop the animation once it has started. For example:
def animate(self):
if not self.should_stop:
self.draw_one_frame()
self.after(100, self.animate)
You would then have a button that, when clicked, sets self.should_stop to False
I just wanted to add Bryan's answer. I don't have enough rep to comment.
Another idea would be to use self.after_cancel() to stop the animation.
So...
def animate(self):
self.draw_one_frame()
self.stop_id = self.after(100, self.animate)
def cancel(self):
self.after_cancel(self.stop_id)

Categories