Related
I want to create an animation that people can use to align their breathing with. I have made a class with PyQt5 that does exactly this, and has the breathing period as parameter. (See code below).
It works well, apart from the timing. When setting a specific delta_t and window size during the FuncAnimation I can get accurate timings. But when I change the window size, it either speeds up or slows down...
Im probably going to model this in another language, but I am still curious if I can get this right in Python. Can anyone here point me in the right direction?
import os
import time
import sys
import matplotlib.pyplot as plt
import numpy as np
import matplotlib.animation as animation
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.figure import Figure
import PyQt5.QtGui
import PyQt5.QtCore
import PyQt5.QtWidgets
class BreathingAnimation(PyQt5.QtWidgets.QMainWindow):
# Could inherit from GenericInterface
# Animate three ways:
# o Fixed sinusoid -> moving ball
# o Fixed ball (up/down) -> moving wave
# o Expanding ball
def __init__(self, period=1, delta_t=0.01, animation_type='ball'):
super().__init__()
self._main = PyQt5.QtWidgets.QWidget()
self.setCentralWidget(self._main)
"""
Plot variables
sin(omega * x)
period = 2 * np.pi * omega
omega = period / (2 * np.pi)
"""
self.delta_t = delta_t
self.frequentie = 1/period
self.max_plot_time = 5
self.max_periods = 20
self.t_range = np.arange(0, self.max_periods * period, delta_t)
self.animation_type = animation_type
self.cool_down = 10
self.prev_time = time.time()
self.prev_time_cor_time = time.time()
"""
Graphical definitions
"""
self.canvas, self.fig, self.axes = self.get_figure(self.max_plot_time)
self.axes.set_axis_off()
self.line_obj, self.scatter_obj = self.get_plot_objects(self.axes)
# Get the animation object
self.anim_obj = self.get_animation()
h_layout = PyQt5.QtWidgets.QHBoxLayout(self._main)
# Create buttons
# self.button_box_layout = self.get_button_box_layout()
# write_button = self.get_push_button(name='Write', shortcut='W', connect=self.write_animation)
# self.edit_button = self.get_line_edit(name=f'{period}', connect=self.update_period, max_length=4)
# self.button_box_layout.addWidget(self.edit_button)
# self.button_box_layout.addWidget(write_button)
# Add canvas to the figure
temp_canvas_layout = PyQt5.QtWidgets.QVBoxLayout()
temp_canvas_layout.addWidget(self.canvas)
h_layout.addLayout(temp_canvas_layout, stretch=1)
# h_layout.addLayout(self.button_box_layout, stretch=0.01)
#staticmethod
def get_figure(max_plot_time, debug=False):
if debug:
fig = plt.figure()
else:
fig = Figure(figsize=(5, 5), dpi=100)
canvas = FigureCanvas(fig)
axes = canvas.figure.subplots()
# self.axes.set_axis_off()
axes.set_ylim(-2, 2)
axes.set_xlim(0, max_plot_time)
return canvas, fig, axes
#staticmethod
def get_plot_objects(axes):
# Create a line object
line_obj = axes.plot([], [], zorder=1)[0]
# Create a scatter object
scatter_obj = axes.scatter([], [], s=40, marker='o', c='r')
return line_obj, scatter_obj
def get_y_value(self, i_t):
omega = 2 * np.pi * self.frequentie
y_value = np.sin(omega * i_t)
return y_value
def animate_moving_ball(self, i, line_obj=None, scatter_obj=None):
i = i % len(self.t_range)
if line_obj is None:
line_obj = self.line_obj
if scatter_obj is None:
scatter_obj = self.scatter_obj
line_obj.set_data(self.t_range, self.get_y_value(self.t_range))
sel_time = self.t_range[i]
scatter_obj.set_offsets(np.c_[sel_time, self.get_y_value(sel_time)])
return scatter_obj
def animate_moving_wave(self, i, line_obj=None, scatter_obj=None):
i = i % len(self.t_range)
if line_obj is None:
line_obj = self.line_obj
if scatter_obj is None:
scatter_obj = self.scatter_obj
line_obj.set_data(self.t_range, np.roll(self.get_y_value(self.t_range), -i))
sel_time = self.t_range[i] + self.max_plot_time/2.
# print(f'max plot time {i}', self.max_plot_time/2, self.get_y_value(sel_time))
scatter_obj.set_offsets(np.c_[self.max_plot_time/2., self.get_y_value(sel_time)])
# self.cool_down -= 1
# # print(self.cool_down)
if ((1 - self.get_y_value(sel_time)) < 0.0001):
time_difference = time.time() - self.prev_time
self.prev_time = time.time()
print('Time interval in seconds ', time_difference)
return scatter_obj
def update_period(self):
new_periode = float(self.edit_button.text())
self.frequentie = 1./new_periode
self.t_range = np.arange(0, self.max_periods * new_periode, self.delta_t)
def get_animation(self):
if self.animation_type == 'ball':
# Return a moving ball..
animation_fun = self.animate_moving_ball
elif self.animation_type == 'wave':
# Return a wave..
animation_fun = self.animate_moving_wave
else:
animation_fun = None
self.animation_obj = animation.FuncAnimation(self.canvas.figure, animation_fun,
blit=False, repeat=True,
interval=self.delta_t, # Delay in ms
frames=len(self.t_range))
self.animation_obj.new_frame_seq()
return self.animation_obj
def write_animation(self):
num_frames = len(self.t_range)
max_time = np.max(self.t_range) # in seconds?
print('frames ', num_frames / max_time)
ffmpeg_writer = animation.FFMpegWriter(fps=num_frames / max_time)
self.animation_obj.save(os.path.expanduser('~/breathing_animation.mp4'), writer=ffmpeg_writer)
print('Written')
if __name__ == "__main__":
qapp = PyQt5.QtWidgets.QApplication(sys.argv)
app = BreathingAnimation(period=3, animation_type='wave', delta_t=0.009)
app.show()
qapp.exec_()
I'm trying to continuously update matlibplots in tkinter GUI while being able to click on buttons to pause/continue/stop updating the plots. I've tried using threads, but they don't seem to be executing parallelly (e.g. data thread is being executed but the plots don't get updated + clicking on buttons is ignored). Why doesn't it work?
# Import Modules
import tkinter as tk
from threading import *
import matplotlib.pyplot as plt
from matplotlib.backends.backend_tkagg import (FigureCanvasTkAgg, NavigationToolbar2Tk)
from scipy.fft import fft
import numpy as np
import time
import random
# global variables
state = 1 # 0 starting state; 1 streaming; 2 pause; -1 end and save
x = [0]*12
y = [0]*12
# Thread buttons and plots separately
def threading():
state = 1
t_buttons = Thread(target = buttons)
t_plots = Thread(target = plots)
t_data = Thread(target = data)
t_buttons.start()
t_plots.start()
t_data.start()
def hex_to_dec(x, y):
for i in range(0, 12):
for j in range(0, len(y)):
x[i][j] = int(str(x[i][j]), 16)
y[i][j] = int(str(y[i][j]), 16)
def data():
fig1, axs1 = main_plot()
fig2, axs2 = FFT_plot()
# To be replaced with actual Arduino data
while(state!=-1):
for i in range(0, 12):
x[i] = [j for j in range(101)]
y[i] = [random.randint(0, 10) for j in range(-50, 51)]
for i in range(0, 12):
for j in range(0, len(y)):
x[i][j] = int(str(x[i][j]), 16)
y[i][j] = int(str(y[i][j]), 16)
# create buttons
def stream_clicked():
state = 1
print("clicked")
def pause_clicked():
state = 2
print("state")
def finish_clicked():
state = -1
def buttons():
continue_button = tk.Button(window, width = 30, text = "Stream data" ,
fg = "black", bg = '#98FB98', command = stream_clicked)
continue_button.place(x = window.winfo_screenwidth()*0.2, y = 0)
pause_button = tk.Button(window, width = 30, text = "Pause streaming data" ,
fg = "black", bg = '#FFA000', command = pause_clicked)
pause_button.place(x = window.winfo_screenwidth()*0.4, y = 0)
finish_button = tk.Button(window, width = 30, text = "End session and save",
fg = 'black', bg = '#FF4500', command = finish_clicked())
finish_button.place(x = window.winfo_screenwidth()*0.6, y = 0)
def plots():
fig1, axs1 = main_plot()
fig2, axs2 = FFT_plot()
if state==1:
print("update")
for i in range(0, 12):
axs1[i].plot(x[i], y[i], 'blue')
axs1[i].axes.get_yaxis().set_ticks([0], labels = ["channel " + str(i+1)])
axs1[i].grid(True)
axs1[i].margins(x = 0)
fig1.canvas.draw()
fig1.canvas.flush_events()
for i in range(0, 12):
axs1[i].clear()
for i in range(0, 12):
axs2.plot(x[i], fft(y[i]))
plt.title("FFT of all 12 channels", x = 0.5, y = 1)
fig2.canvas.draw()
fig2.canvas.flush_events()
axs2.clear()
def main_plot():
plt.ion()
fig1, axs1 = plt.subplots(12, figsize = (10, 9), sharex = True)
fig1.subplots_adjust(hspace = 0)
# Add fixed values for axis
canvas = FigureCanvasTkAgg(fig1, master = window)
canvas.draw()
canvas.get_tk_widget().pack()
canvas.get_tk_widget().place(x = 0, y = 35)
return fig1, axs1
def update_main_plot(fig1, axs1):
if state==1:
for i in range(0, 12):
axs1[i].plot(x[i], y[i], 'blue')
axs1[i].axes.get_yaxis().set_ticks([0], labels = ["channel " + str(i+1)])
axs1[i].grid(True)
axs1[i].margins(x = 0)
axs1[0].set_title("Plot recordings", x = 0.5, y = 1)
fig1.canvas.draw()
fig1.canvas.flush_events()
for i in range(0, 12):
axs1[i].clear()
def FFT_plot():
# Plot FFT figure
plt.ion()
fig2, axs2 = plt.subplots(1, figsize = (7, 9))
# Add fixed values for axis
canvas = FigureCanvasTkAgg(fig2, master = window)
canvas.draw()
canvas.get_tk_widget().pack()
canvas.get_tk_widget().place(x = window.winfo_screenwidth()*0.55, y = 35)
return fig2, axs2
def update_FFT_plot(fig2, axs2):
# Update FFT plot
for i in range(0, 12):
axs2.plot(x[i], fft(y[i]))
plt.title("FFT", x = 0.5, y = 1)
fig2.canvas.draw()
fig2.canvas.flush_events()
axs2.clear()
# create root window and set its properties
window = tk.Tk()
window.title("Data Displayer")
window.geometry("%dx%d" % (window.winfo_screenwidth(), window.winfo_screenheight()))
window.configure(background = 'white')
threading()
window.mainloop()
*** Sometimes it just doesn't work without any message and sometimes I also get "RuntimeError: main thread is not in main loop" ***
to be fair all functions in your code are very likely to cause a segmentation fault, and other functions that don't result in a segmentation fault simply don't work, it's hard to explain what's wrong.
define global variables as global if you are going to modify them
update GUI in your main thread by using the window.after method repeatedly.
only reading from your microcontroller should be done in separate thread.
creation of Tkinter objects should be done in the main thread, only updates are allowed in other threads, but it is not thread-safe, so while it may work it can lead to some weird behavior or errors sometimes.
calling matplotlib functions such as ion and flush_events causes errors because these are for matplotlib interactive canvas, not for tkinter canvas.
threading has a very tough learning curve so ask yourself "do i really need threads in here" and "is there any way to not use threads" before you attempt to use them as once you start using threads you are no longer using python's "safe code", despite all the efforts, threads are not safe to use for any task, it's up to you to make them safe, and to be honest threads are not needed here unless you are reading 1 GB/s from your microcontroller.
don't use numbers for states, it's not pythonic, and it confuses the readers, and it has no performance benefit over using Enums.
programs are built incrementally, not copy-paste from multiple working snippets, as it is harder to track where the error comes from when multiple parts of the code weren't verified to be working.
# Import Modules
import tkinter as tk
from threading import *
import matplotlib.pyplot as plt
from matplotlib.backends.backend_tkagg import (FigureCanvasTkAgg, NavigationToolbar2Tk)
from scipy.fft import fft
import numpy as np
import time
import random
from enum import Enum,auto
UPDATE_INTERVAL_MS = 300
class States(Enum):
STREAM = auto()
PAUSE = auto()
SAVE = auto()
START = auto()
# global variables
state = States.START # check States Enum
x = [[0]]*12
y = [[0]]*12
# Thread buttons and plots separately
def threading():
global state
global window
state = States.STREAM
buttons()
plots()
data()
t_grab_data = Thread(target=grab_data_loop,daemon=True)
t_grab_data.start()
# t_buttons = Thread(target=buttons)
# t_plots = Thread(target=plots)
# t_data = Thread(target=data)
#
# t_buttons.start()
# t_plots.start()
# t_data.start()
def hex_to_dec(x, y):
for i in range(0, 12):
for j in range(0, len(y)):
x[i][j] = int(str(x[i][j]), 16)
y[i][j] = int(str(y[i][j]), 16)
def data():
global fig1,axs1,fig2,axs2
fig1, axs1 = main_plot()
fig2, axs2 = FFT_plot()
# To be replaced with actual Arduino data
window.after(UPDATE_INTERVAL_MS,draw_data_loop)
def grab_data_loop():
while state != States.SAVE:
for i in range(0, 12):
x[i] = [j for j in range(101)]
y[i] = [random.randint(0, 10) for j in range(-50, 51)]
for i in range(0, 12):
for j in range(0, len(y)):
x[i][j] = int(str(x[i][j]), 16)
y[i][j] = int(str(y[i][j]), 16)
time.sleep(0.1) # because we are not reading from a microcontroller
def draw_data_loop():
if state == States.STREAM:
update_main_plot(fig1, axs1)
update_FFT_plot(fig2, axs2)
window.after(UPDATE_INTERVAL_MS,draw_data_loop)
# create buttons
def stream_clicked():
global state
state = States.STREAM
print("clicked")
def pause_clicked():
global state
state = States.PAUSE
print("state")
def finish_clicked():
global state
state = States.SAVE
window.destroy()
def buttons():
continue_button = tk.Button(window, width=30, text="Stream data",
fg="black", bg='#98FB98', command=stream_clicked)
continue_button.place(x=window.winfo_screenwidth() * 0.2, y=0)
pause_button = tk.Button(window, width=30, text="Pause streaming data",
fg="black", bg='#FFA000', command=pause_clicked)
pause_button.place(x=window.winfo_screenwidth() * 0.4, y=0)
finish_button = tk.Button(window, width=30, text="End session and save",
fg='black', bg='#FF4500', command=finish_clicked)
finish_button.place(x=window.winfo_screenwidth() * 0.6, y=0)
def plots():
global state
fig1, axs1 = main_plot()
fig2, axs2 = FFT_plot()
if state == States.STREAM:
print("update")
for i in range(0, 12):
axs1[i].plot(x[i], y[i], 'blue')
axs1[i].axes.get_yaxis().set_ticks([0], labels=["channel " + str(i + 1)])
axs1[i].grid(True)
axs1[i].margins(x=0)
# fig1.canvas.draw()
# fig1.canvas.flush_events()
# for i in range(0, 12):
# axs1[i].clear()
for i in range(0, 12):
axs2.plot(x[i], np.abs(fft(y[i])))
plt.title("FFT of all 12 channels", x=0.5, y=1)
# fig2.canvas.draw()
# fig2.canvas.flush_events()
# axs2.clear()
def main_plot():
# plt.ion()
global canvas1
fig1, axs1 = plt.subplots(12, figsize=(10, 9), sharex=True)
fig1.subplots_adjust(hspace=0)
# Add fixed values for axis
canvas1 = FigureCanvasTkAgg(fig1, master=window)
# canvas.draw()
canvas1.get_tk_widget().pack()
canvas1.get_tk_widget().place(x=0, y=35)
return fig1, axs1
def update_main_plot(fig1, axs1):
if state == States.STREAM:
for i in range(0, 12):
axs1[i].clear()
for i in range(0, 12):
axs1[i].plot(x[i], y[i], 'blue')
axs1[i].axes.get_yaxis().set_ticks([0], labels=["channel " + str(i + 1)])
axs1[i].grid(True)
axs1[i].margins(x=0)
axs1[0].set_title("Plot recordings", x=0.5, y=1)
canvas1.draw()
# fig1.canvas.draw()
# fig1.canvas.flush_events()
def FFT_plot():
# Plot FFT figure
# plt.ion()
global canvas2
fig2, axs2 = plt.subplots(1, figsize=(7, 9))
# Add fixed values for axis
canvas2 = FigureCanvasTkAgg(fig2, master=window)
# canvas.draw()
canvas2.get_tk_widget().pack()
canvas2.get_tk_widget().place(x=window.winfo_screenwidth() * 0.55, y=35)
return fig2, axs2
def update_FFT_plot(fig2, axs2):
# Update FFT plot
if state == States.STREAM:
axs2.clear()
for i in range(0, 12):
axs2.plot(x[i], np.abs(fft(y[i])))
plt.title("FFT", x=0.5, y=1)
canvas2.draw()
# fig2.canvas.draw()
# fig2.canvas.flush_events()
# axs2.clear()
# create root window and set its properties
window = tk.Tk()
window.title("Data Displayer")
window.geometry("%dx%d" % (window.winfo_screenwidth(), window.winfo_screenheight()))
window.configure(background='white')
threading()
window.mainloop()
# put saving logic here
from tkinter import *
from tkinter import ttk
def DOTEST_GUI():
GUI = Tk()
w = 1920
h = 1080
ws = GUI.winfo_screenwidth()
hs = GUI.winfo_screenheight()
x = (ws/2) - (w/2)
y = (hs/2) - (h/2)
GUI.geometry(f'{w}x{h}+{x:.0f}+{y:.0f}')
def start_p():
progress.start(5)
def stop_P():
progress.stop()
def print_cf(event = None):
import time
print('s')
start_p()
time.sleep(5)
stop_P()
B_TEST = ttk.Button(GUI, text = "test", width = 15, command = print_cf)
B_TEST.pack()
progress = ttk.Progressbar(GUI, orient = HORIZONTAL, length = 100, mode = 'indeterminate')
progress.pack(pady = 10)
GUI.bind("<Return>", print_cf)
GUI.focus()
GUI.mainloop()
DOTEST_GUI()
follow this code progress bar is not running properly.
I tried to remove stop_P(), it's work after 5 second of time.sleep(5).
I would like it to start running progress 5 second until the stop_P() code.
If you want to run a Progressbar for a set amount of time and then stop it, you could do something like this. Consolidate the print_cf function to start and stop the progress and set a range of 5, sleep for 1 second in between and then stop the Progressbar. Placing it on a thread would allow you to do something more time consuming than sleeping one second and printing something.
from tkinter import *
from tkinter import ttk
import time
import threading
def DOTEST_GUI():
GUI = Tk()
w = 1920
h = 1080
ws = GUI.winfo_screenwidth()
hs = GUI.winfo_screenheight()
x = (ws/2) - (w/2)
y = (hs/2) - (h/2)
GUI.geometry(f'{w}x{h}+{x:.0f}+{y:.0f}')
def run_thread():
execute_thread = threading.Thread(target=print_cf)
execute_thread.start()
def print_cf(event = None):
progress.start()
print('s')
for i in range(5):
print(i)
time.sleep(1)
if i ==4:
progress.stop()
B_TEST = ttk.Button(GUI, text = "test", width = 15, command = run_thread)
B_TEST.pack()
progress = ttk.Progressbar(GUI, orient = HORIZONTAL, length = 100, mode = 'indeterminate')
progress.pack(pady = 10)
GUI.bind("<Return>", print_cf)
GUI.focus()
GUI.mainloop()
DOTEST_GUI()
I am trying to create the DICOM viewer in python. I would like to use scroll mouse to skip images. It is working quite nice but the process is very slow, especially if I would like to do it in the full screen mode. I know that matplotlibt is not the faster library to do it. I know I could maybe use the fortbyte function from Pillow but I would like to use Navigation toolbar in the future. I was trying to get work the blit over matplotlibt, however I could not get it work properly. Does somebody has any idea to improve the speed of the image presented? Below I posted my code. Thanks in advance.
import tkinter
import pydicom as dicom
import numpy as np
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
from matplotlib.figure import Figure
from matplotlib import *
import time
pathDir = 'C:/DICOM/'
images = os.listdir(pathDir)
slices = [dicom.read_file(pathDir + s, force=True) for s in images]
rows = int(slices[0].Rows)
cols = int(slices[0].Columns)
RS = slices[0].RescaleSlope
RI = slices[0].RescaleIntercept
WC = slices[0].WindowCenter
WW = slices[0].WindowWidth
lv = (WC - WW) / 2
hv = (WC + WW) / 2
imageDim = np.zeros((rows, cols, len(slices)), 'int')
ii = int
for ii in range(len(slices)):
imageDim[:, :, ii] = slices[ii].pixel_array * RS + RI
root = tkinter.Tk()
# root.wm_title("Try MouseWheel")
# root.geometry("1920x1080")
root.resizable(width=True, height=True)
slide = 128
minInt = np.min(imageDim)
maxInt = np.max(imageDim)
# print(minInt, maxInt)
def main_loop():
fig = Figure()
ax = fig.subplots()
ax.imshow(imageDim[:, :, slide], cmap='gray', vmin=minInt, vmax=maxInt)
fig.canvas = FigureCanvasTkAgg(fig, master=root)
fig.canvas.draw()
fig.canvas.get_tk_widget().pack(side=tkinter.TOP, fill=tkinter.BOTH, expand='true')
fig.canvas.mpl_connect('scroll_event', mouse_wheel)
def mouse_wheel(event):
global slide
fig = event.canvas.figure
ax = fig.axes[0]
if event.step < 0:
slide += event.step
if event.step > 0:
slide += event.step
tstart = time.time()
ax.imshow(imageDim[:, :, int(slide)], cmap='gray', vmin=minInt, vmax=maxInt)
fig.canvas.draw()
print('FPS:', 1 / (time.time() - tstart))
main_loop()
root.mainloop()
It seems that adding ax.clear() help already much with the speed of the view. However, if somebody has better view to imrove this code I will be glad to hear about it.
def mouse_wheel(event):
global slide
fig = event.canvas.figure
ax = fig.axes[0]
if event.step < 0:
slide += event.step
if event.step > 0:
slide += event.step
tstart = time.time()
ax.clear()
ax.imshow(imageDim[:, :, int(slide)], cmap='gray', vmin=minInt, vmax=maxInt)
fig.canvas.draw()
print('FPS:', 1 / (time.time() - tstart))
I don't know if this is a bug with matplotlib/python but running the following from emacs removes my ability to kill the matplotlib process by clicking [x] on the figure window, it has no effect. Is there a command (I googled, no luck) for terminating a specific process that emacs has launched? I can kill the process by finding the buffer and doing C-x k but that is a bit of a hassle, any way to kill ALL running python processes?
#Simple circular box simulator, part of part_sim
#Restructure to import into gravity() or coloumb () or wind() or pressure()
#Or to use all forces: sim_full()
#Note: Implement crashing as backbone to all forces
import numpy as np
import matplotlib.pyplot as plt
from scipy.spatial.distance import pdist, squareform
N = 100 #Number of particles
R = 10000 #Box width
pR= 5 #Particle radius
r = np.random.randint(0, R, (N,2)) #Position vector
v = np.random.randint(-R/100,R/100,(N,2)) #velocity vector
a = np.array([0,-10]) #Forces
v_limit = R/2 #Speedlimit
plt.ion()
line, = plt.plot([],'o')
line2, = plt.plot([],'o') #Track a particle
plt.axis([0,R+pR,0,R+pR]
while True:
v=v+a #Advance
r=r+v
#Collision tests
r_hit_x0 = np.where(r[:,0]<0) #Hit floor?
r_hit_x1 = np.where(r[:,0]>R) #Hit roof?
r_hit_LR = np.where(r[:,1]<0) #Left wall?
r_hit_RR = np.where(r[:,1]>R) #Right wall?
#Stop at walls
r[r_hit_x0,0] = 0
r[r_hit_x1,0] = R
r[r_hit_LR,1] = 0
r[r_hit_RR,1] = R
#Reverse velocities
v[r_hit_x0,0] = -0.9*v[r_hit_x0,0]
v[r_hit_x1,0] = -v[r_hit_x1,0]
v[r_hit_LR,1] = -0.95*v[r_hit_LR,1]
v[r_hit_RR,1] = -0.99*v[r_hit_RR,1]
#Collisions
D = squareform(pdist(r))
ind1, ind2 = np.where(D < pR)
unique = (ind1 < ind2)
ind1 = ind1[unique]
ind2 = ind2[unique]
for i1, i2 in zip(ind1, ind2):
eps = np.random.rand()
vtot= v[i1,:]+v[i2,:]
v[i1,:] = -(1-eps)*vtot
v[i2,:] = -eps*vtot
line.set_ydata(r[:,1])
line.set_xdata(r[:,0])
line2.set_ydata(r[:N/5,1])
line2.set_xdata(r[:N/5,0])
plt.draw()
C-c C-\ will kill the program with a SIGQUIT, but that is not a graceful way
to end the program.
Alternatively, if you change the backend to TkAgg, C-c
C-c will also terminate the program (again ungracefully), but trying to close the window will still not work.
import numpy as np
import matplotlib as mpl
mpl.use('TkAgg') # do this before importing pyplot
import matplotlib.pyplot as plt
A full, robust solution requires removing plt.ion(), using a GUI framework like Tk, pygtk, wxpython or pyqt, and embedding the GUI window in a matplotlib FigureCanvas.
Here is an example using Tk:
"""
http://stackoverflow.com/q/13660042/190597
Simple circular box simulator, part of part_sim
Restructure to import into gravity() or coloumb () or wind() or pressure()
Or to use all forces: sim_full()
Note: Implement crashing as backbone to all forces
"""
import tkinter as tk
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.figure as mplfig
import scipy.spatial.distance as dist
import matplotlib.backends.backend_tkagg as tkagg
class App(object):
def __init__(self, master):
self.master = master
self.fig = mplfig.Figure(figsize = (5, 4), dpi = 100)
self.ax = self.fig.add_subplot(111)
self.canvas = canvas = tkagg.FigureCanvasTkAgg(self.fig, master)
canvas.get_tk_widget().pack(side = tk.TOP, fill = tk.BOTH, expand = 1)
self.toolbar = toolbar = tkagg.NavigationToolbar2TkAgg(canvas, master)
self.button = button = tk.Button(master, text = 'Quit', command = master.quit)
button.pack(side = tk.BOTTOM)
toolbar.update()
self.update = self.animate().__next__
master.after(10, self.update)
canvas.show()
def animate(self):
N = 100 #Number of particles
R = 10000 #Box width
pR= 5 #Particle radius
r = np.random.randint(0, R, (N,2)) #Position vector
v = np.random.randint(-R/100,R/100,(N,2)) #velocity vector
a = np.array([0,-10]) #Forces
v_limit = R/2 #Speedlimit
line, = self.ax.plot([],'o')
line2, = self.ax.plot([],'o') #Track a particle
self.ax.set_xlim(0, R+pR)
self.ax.set_ylim(0, R+pR)
while True:
v=v+a #Advance
r=r+v
#Collision tests
r_hit_x0 = np.where(r[:,0]<0) #Hit floor?
r_hit_x1 = np.where(r[:,0]>R) #Hit roof?
r_hit_LR = np.where(r[:,1]<0) #Left wall?
r_hit_RR = np.where(r[:,1]>R) #Right wall?
#Stop at walls
r[r_hit_x0,0] = 0
r[r_hit_x1,0] = R
r[r_hit_LR,1] = 0
r[r_hit_RR,1] = R
#Reverse velocities
v[r_hit_x0,0] = -0.9*v[r_hit_x0,0]
v[r_hit_x1,0] = -v[r_hit_x1,0]
v[r_hit_LR,1] = -0.95*v[r_hit_LR,1]
v[r_hit_RR,1] = -0.99*v[r_hit_RR,1]
#Collisions
D = dist.squareform(dist.pdist(r))
ind1, ind2 = np.where(D < pR)
unique = (ind1 < ind2)
ind1 = ind1[unique]
ind2 = ind2[unique]
for i1, i2 in zip(ind1, ind2):
eps = np.random.rand()
vtot= v[i1,:]+v[i2,:]
v[i1,:] = -(1-eps)*vtot
v[i2,:] = -eps*vtot
line.set_ydata(r[:,1])
line.set_xdata(r[:,0])
line2.set_ydata(r[:N//5,1])
line2.set_xdata(r[:N//5,0])
self.canvas.draw()
self.master.after(1, self.update)
yield
def main():
root = tk.Tk()
app = App(root)
tk.mainloop()
if __name__ == '__main__':
main()