I want to create an animation in matplotlib using FuncAnimation. The animation contains various "stages" which I would like to separate (emphasize) by adding an extra delay to the interval between the two corresponding frames. Consider the following example code that draws five circles and the drawing of each two consecutive circles should be separated by 1 second:
import time
from matplotlib.animation import FuncAnimation
import matplotlib.pyplot as plt
import numpy as np
f, ax = plt.subplots()
ax.set_xlim([-5, 5])
ax.set_ylim([-5, 5])
radius = 1
dp = 2*np.pi / 50
circles = [[(radius, 0)]]
plots = ax.plot([radius], [0])
def update(frame):
global radius
if frame % 50 == 0:
radius += 1
circles.append([(radius, 0)])
plots.extend(ax.plot([radius], [0]))
# I want to add a delay here, i.e. before the drawing of a new circle starts.
# This works for `plt.show()` however it doesn't when saving the animation.
time.sleep(1)
angle = (frame % 50) * dp
circles[-1].append((radius * np.cos(angle), radius * np.sin(angle)))
plots[-1].set_data(*zip(*circles[-1]))
return plots[-1]
animation = FuncAnimation(f, update, frames=range(1, 251), interval=50, repeat=False)
### Uncomment one of the following options.
# animation.save('test.mp4', fps=20)
# with open('test.html', 'w') as fh:
# fh.write(animation.to_html5_video())
# plt.show()
This works when playing the animation via plt.show() however it doesn't work when saving as .mp4 or HTML5 video. This makes sense since, according to the documentation, the FPS determines the frame delay for mp4 video and the interval parameter is used for HTML5 video. Then frames are just played one after another (ignoring any compute time as well).
So how can I add a delay that will be retained upon saving the animation?
You should be able to use a generating function for your frames argument. For example:
from matplotlib.animation import FuncAnimation
import matplotlib.pyplot as plt
import numpy as np
INTERVAL = 50 # ms
HOLD_MS = 1000
HOLD_COUNT = HOLD_MS // INTERVAL
def frame_generator():
for frame in range(1, 251):
# Yield the frame first
yield frame
# If we should "sleep" here, yield None HOLD_COUNT times
if frame % 50 == 0:
for _ in range(HOLD_COUNT):
yield None
f, ax = plt.subplots()
ax.set_xlim([-5, 5])
ax.set_ylim([-5, 5])
radius = 1
dp = 2*np.pi / 50
circles = [[(radius, 0)]]
plots = ax.plot([radius], [0])
def update(frame):
global radius
if frame is None: return #--------------------------------- Added
if frame % 50 == 0:
radius += 1
circles.append([(radius, 0)])
plots.extend(ax.plot([radius], [0]))
#-------------------------------------------------------- sleep removed
angle = (frame % 50) * dp
circles[-1].append((radius * np.cos(angle), radius * np.sin(angle)))
plots[-1].set_data(*zip(*circles[-1]))
return plots[-1]
# Slightly changed
animation = FuncAnimation(f, update, frames=frame_generator(), interval=INTERVAL, repeat=False)
plt.show()
Should work.
print(list(frame_generator()))
May help clarify what's going on.
You may use the frame argument to steer your animation. Essentially a pause after frame n is the same as showing the frame number n repeatedly until the pause ends. E.g. if you run an animation at a rate of 1 frame per second, and want 3 seconds pause after the second frame, you can supply
0, 1, 1, 1, 1, 2, 3, ....
as frames, such that the frame with number 1 is shown four times.
Applying that concept can be done as follows in your code.
from matplotlib.animation import FuncAnimation
import matplotlib.pyplot as plt
import numpy as np
f, ax = plt.subplots()
ax.set_xlim([-5, 5])
ax.set_ylim([-5, 5])
radius = 0
bu = 50
dp = 2*np.pi / bu
circles = [[(radius, 0)]]
plots = ax.plot([radius], [0])
def update(frame):
global radius
if frame % bu == 0:
radius += 1
circles.append([(radius, 0)])
plots.extend(ax.plot([radius], [0]))
angle = (frame % bu) * dp
circles[-1].append((radius * np.cos(angle), radius * np.sin(angle)))
plots[-1].set_data(*zip(*circles[-1]))
return plots[-1]
interval = 50 # milliseconds
pause = int(1 * 1000 / interval)
cycles = 4
frames = []
for c in range(cycles):
frames.extend([np.arange(c*bu, (c+1)*bu), np.ones(pause)*((c+1)*bu)])
frames = np.concatenate(frames)
animation = FuncAnimation(f, update, frames=frames, interval=50, repeat=False)
### Uncomment one of the following options.
# animation.save('test.mp4', fps=20)
# with open('test.html', 'w') as fh:
# fh.write(animation.to_html5_video())
plt.show()
Related
Good evening,
I am new to Python. I am trying to process a signal saved in a npy file.
This file contains an electrical signal that I want to view as I do in the laboratory with the oscilloscope, so I want to generate an animation that shows me how the signal changes over time.
Here is my attempt:
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation
signal = np.load('signal.npy')
fig = plt.figure()
def animation(i):
plt.cla()
plt.plot(signal)
# what to do here?
anim = FuncAnimation(fig, animation, frames = len(signal), interval = 10)
plt.show()
I have no idea what to do in the animation function.
Thanks in advance and sorry for my english
Since I do not have access to your signal data, I generate mine in order to run the animation. Replace my random signal with yours.
A basic code to view you signal with respect to time could be this:
# import
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation
# signal generation
N = 10001
stop = 100
time = np.linspace(0, stop, N)
A = 1/4*np.cos(2*np.pi*(np.abs(time - stop/2)/stop)) + 1
f = np.concatenate((1*np.ones(int(N/4)), 2*np.ones(int(N/2) + 1), 1*np.ones(int(N/4))))
signal = A * np.sin(2*np.pi*f*time) + 0.05*np.random.randn(N)
# figure preparation
fig, ax = plt.subplots(1, 1, figsize = (8*0.9, 6*0.9))
displayed_period = int(2*f.min())
span = int(N/stop/f.min())
def animation(i):
# delete previous frame
ax.cla()
# plot and set axes limits
ax.plot(time[span*i: 1 + span*(i + displayed_period)],
signal[span*i: 1 + span*(i + displayed_period)])
ax.set_xlim([time[span*i], time[span*(i + displayed_period)]])
ax.set_ylim([1.1*signal.min(), 1.1*signal.max()])
# run animation
anim = FuncAnimation(fig, animation, frames = int(len(time)/span - 1), interval = 10)
plt.show()
which gives this animation:
Explanation
In my case, the signal is a sine wave which changes amplitude and frequency over time (plus some noise). I choose to see two complete oscillations of my signal per each frame, so I set
displayed_period = int(2*f.min())
to be sure to see at least the two complete oscillations. Then I have to define the amount of time passed through x axis between a frame and the following, so I set:
span = int(N/stop/f.min())
That being said, when you run the code, the animation function is called multiple times, in each time the i counter increases by 1. So you can use this counter to slice the time and the signal arrays: time[span*i: 1 + span*(i + displayed_period)].
In this way you plot a displayed_period number of complete oscillations and, for each frame, you scroll the x axis by span element.
You have to set displayed_period and span according to your signal properties in order to get a similar result.
If you want a little bit customization like an oscilloscope, check this code:
# import
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation
# signal generation
N = 10001
stop = 100
time = np.linspace(0, stop, N)
A = 1/4*np.cos(2*np.pi*(np.abs(time - stop/2)/stop)) + 1
f = np.concatenate((1*np.ones(int(N/4)), 2*np.ones(int(N/2) + 1), 1*np.ones(int(N/4))))
signal = A * np.sin(2*np.pi*f*time) + 0.05*np.random.randn(N)
# color definition
black = '#0F110D'
grey = '#3B3D3A'
yellow = '#FFFF21'
# figure preparation
fig, ax = plt.subplots(1, 1, figsize = (8*0.9, 6*0.9))
displayed_period = int(2*f.min())
span = int(N/stop/f.min())
def animation(i):
# delete previous frame
ax.cla()
# set background color and plot
ax.set_facecolor(black)
ax.plot(time[span*i: 1 + span*(i + displayed_period)],
signal[span*i: 1 + span*(i + displayed_period)],
color = yellow)
# plot axes lines
ax.hlines(y = 0,
xmin = 0,
xmax = stop,
lw = 2,
colors = grey)
ax.vlines(x = time[int(span*i + (1 + span*displayed_period)/2)],
ymin = 1.1*signal.min(),
ymax = 1.1*signal.max(),
lw = 2,
colors = grey)
# set grid, axes limits and ticks
ax.grid(which = 'major',
ls = '-',
lw = 0.5,
color = grey)
ax.set_xlim([time[span*i], time[span*(i + displayed_period)]])
ax.set_ylim([1.1*signal.min(), 1.1*signal.max()])
plt.tick_params(axis = 'both',
which = 'both',
bottom = False,
left = False,
labelbottom = False,
labelleft = False)
# run animation
anim = FuncAnimation(fig, animation, frames = int(len(time)/span - 1), interval = 10)
anim.save('oscilloscope.gif', writer = 'imagemagick')
plt.show()
I do not changed the functionalities, only the aspect of the animation:
In the matplotlib documentation you can see an example of a simulation of an oscilloscope here
I've been struggling for the last few nights how to turn my waves in the graph here into some sort of animation after each time step or after each x step. How can I modify and write the code so that my program animations each time step of the wave somehow. I'm very new to python and programming and never used the animation part of matplotlib before.
import numpy as np
import matplotlib
import matplotlib.pyplot as plt
import matplotlib.animation as animation
T = 20.0 # t
X = 10.0 # x
n = 300
m = 300
#positions of time and space, xp= x position accross grid, tp = t position accross grid
tp = T / (n - 1)
xp = X / (m - 1)
C = 0.5
U = np.zeros((n, m))
# Set the initial values for each x position
for i in range(0, n):
U[i, 0] = np.exp(- (i * xp - 2.5) ** 2)
for i in range(1, n): # across x
for j in range(1, m): # across t
U[i, j] = (xp * U[i, j - 1] + C * tp * U[i - 1, j]) / (xp + C * tp) # equation for new distribution value
fig = plt.figure(1)
#gives time position instead of time step
tn = np.zeros((m, 1))
for j in range(0, m):
tn[j] = j * tp
#gives x position instead of x step
xn = np.zeros((n, 1))
for j in range(0, n):
xn[j] = j * xp
for i in [0, 50, 100, 150, 200, 250, 299 ]: # selects which position of time
label = 't = ' + str(tn[i][0]) # lables legend
subfig = fig.add_subplot(1, 1, 1)
subfig.plot(xn, U[:, i], label=label)
subfig.legend()
subfig.grid(True)
print(tn)
# Save Image
plt.xlabel('x: position')
plt.ylabel('u: u(x, t)')
plt.title(r'$\frac{\partial u}{\partial t} + C \frac{\partial u}{\partial x} = 0$')
plt.savefig('transport-equation')`
plt is a tricky package to get used to. In general, graphics is not an easy thing to manage and plt tries to be as simple as possible, while also providing max flexibility. In general, when you use plt, there are a lot of global variables that are automatically generated, updated, cleaned, and handled for you. When you use things "plt.xlabel", you are really applying this to a particular axis in a particular figure, which are automatically determined for you. If you want more control in plt and/or you want to do something complicated like an animation, then you need to make your globals explicit.
#Create xn and U.
import matplotlib.pyplot as plt
figure = plt.figure() #This is the window that pops open.
axis = figure.add_subplot(1,1,1) #This is a graph/grid.
axis.grid(True) #Add a grid to the axis.
label = 't = ' + str(tn[i][0])
plots = axis.plot(xn,U[:,0],label=label) #These are the plots of data with X and Y.
An X and Y arrays can generate more than one plot at a time, hence, plots is a list with one item in it. To get a sense of how this works, you can literally manipulate the data in real time and watch it change in the plt window.
figure.show()
plots[0].set_ydata(U[:,10])
plots[0].set_ydata(U[:,-1])
# Close the window when done.
To make an animation we need to tell plt to apply an animation to a given figure. plt will then attempt to update the figure and everything that has been attached to it. If you already have the window open, the animation will still get applied and work, but you will also keep whatever was originally plotting in the figure (so we should close the window and re-code the animation). plt does NOT follow convention that executing one line at a time is the same as executing all the lines at once. plt behaves differently before and after a window is opened.
#Create xn and U.
import matplotlib.pyplot as plt
figure = plt.figure()
axis = figure.add_subplot(1,1,1)
axis.grid(True)
label = 't = ' + str(tn[i][0])
plots = axis.plot(xn,U[:,0],label=label)
def animate_function(frame):
frame %= 300 #frame is an integer that counts from 0.
plots[0].set_ydata(U[:,frame]) #Change which globals you want.
return plots #Return the changed items so plt knows.
#Tell plt to apply this animation function to your figure.
#Tell plt to wait approximately 10ms per frame.
#Tell plt to only update pixels that actually change (called blit).
#Save to a global variable so plt doesn't get upset at you.
ani = animation.FuncAnimation(figure,animate_function,interval=10,blit=True)
#Now open the window and show the figure.
figure.show()
I'm trying to animate multiple dots moving along the circumference of their own circle using matplotlib.
I've been able to animate a single dot moving along a circle, and here's the code to do that:
import numpy as np
import argparse
import matplotlib.pyplot as plt
import matplotlib.animation as animation
# To make the waving flag, we need N dots moving on a circle
# Each subsequent dot is going to be delayed by a slight time, and the last dot should be the same timing as the first dot
r = 3
def circle(phi, phi_off,offset_x, offset_y):
return np.array([r*np.cos(phi+phi_off), r*np.sin(phi+phi_off)]) + np.array([offset_x, offset_y])
plt.rcParams["figure.figsize"] = 8,6
# create a figure with an axes
fig, ax = plt.subplots()
# set the axes limits
ax.axis([-30,30,-30,30])
# set equal aspect such that the circle is not shown as ellipse
ax.set_aspect("equal")
# create a point in the axes
point, = ax.plot(0,1, marker="o")
def update(phi, phi_off, offset_x,offset_y):
# obtain point coordinates
x,y = circle(phi,phi_off, offset_x,offset_y)
# set point coordinates
point.set_data([x],[y])
return point,
ani = animation.FuncAnimation(fig,update,fargs=(0,8*i,0, ), interval = 2, frames=np.linspace(0,2*np.pi,360, endpoint=False))
It looks like this :
In order to have multiple dots, I tried to do ani.append in a loop, i.e. have it do something like this:
i=0
for i in range(3):
ani.append(animation.FuncAnimation(fig,update,fargs=(0,8*i,0, ), interval = 2, frames=np.linspace(0,2*np.pi,360, endpoint=False)))
Here's what it looks like:
Any ideas on how to have multiple dots each moving smoothly on their own circle?
You should only define one update function, which is updating all points:
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as animation
r = 3
def circle(phi, phi_off,offset_x, offset_y):
return np.array([r*np.cos(phi+phi_off), r*np.sin(phi+phi_off)]) + np.array([offset_x, offset_y])
plt.rcParams["figure.figsize"] = 8,6
fig, ax = plt.subplots()
ax.axis([-30,30,-30,30])
ax.set_aspect("equal")
# create initial conditions
phi_offs = [0, np.pi/2, np.pi]
offset_xs = [0, 0, 0]
offset_ys = [0, 0, 0]
# amount of points
N = len(phi_offs)
# create a point in the axes
points = []
for i in range(N):
x,y = circle(0, phi_offs[i], offset_xs[i], offset_ys[i])
points.append(ax.plot(x, y, marker="o")[0])
def update(phi, phi_off, offset_x,offset_y):
# set point coordinates
for i in range(N):
x, y = circle(phi,phi_off[i], offset_x[i], offset_y[i])
points[i].set_data([x],[y])
return points
ani = animation.FuncAnimation(fig,update,
fargs=(phi_offs, offset_xs, offset_ys),
interval = 2,
frames=np.linspace(0,2*np.pi,360, endpoint=False),
blit=True)
plt.show()
I also added the blit=True argument to make the animation smoother and faster (only the necessary artists will be updated) but be careful, you might have to omit this feature in more complex animations.
I'm trying to plot an animated sine signal as if it's passing through a figure window. So I expect that it takes 5 seconds for a 3-sec sine signal to pass through a 2-second window in my animation.
However, looking at the pop-up figure live, it takes twice as long by clock time on my machine using the following code, adapted from this excellent tutorial: https://jakevdp.github.io/blog/2012/08/18/matplotlib-animation-tutorial/ .
The recorded video seems OK at the specified frame rate though.
I can't seem to see where I did wrong in the logic. Is it just a performance issue? If so, how should I optimize the code with constraints, e.g., if possible, I hope I won't have to lower the sampling rate? The purpose of this is to prepare for drawing real-time data coming from the network.
import numpy as np
from matplotlib import pyplot as plt
from matplotlib import animation
import sys
# Signal comes in from right to left.
SR = 44100 # sampling rate in Hz
DurSec = 3 # Signal duration in seconds
Freq = 440 # frequency in Hz
Amp = 1.0 # amplitude
WindowDurSec = 2 # figure window size in seconds
FPS = 30 # frame per second
msPerSec = 1000
frameDurMs = int(np.floor(1 / FPS * msPerSec)) # animation frame duration
passThruSec = WindowDurSec + DurSec # Playback stops at DurSec, but graph moves on until leaving window.
passThruFrames = int(round(FPS * passThruSec)) # number of frames of the entire animation.
sampPerFrame = int(frameDurMs/1000*SR) # frame size in samples
sampPerWindow = int(SR*WindowDurSec) # window size in samples
x = np.linspace(0, DurSec, int(SR*DurSec))
y = Amp * np.sin(2 * np.pi * Freq * x) # effective signal
# Add head and tail = 2 * windowSize
pad = np.zeros((sampPerWindow,), dtype=float)
Y = np.concatenate((pad, y, pad))
# Set up the figure
fig = plt.figure()
ax = plt.axes(xlim=(0, WindowDurSec), ylim=(-2*Amp, 2*Amp))
line, = ax.plot([], [], lw=2)
# initialization function: plot the background of each frame
def init():
line.set_data([], [])
return line,
# animation function. This is called sequentially
def animate(i):
x = np.linspace(0, WindowDurSec, sampPerWindow)
start = i*sampPerFrame
stop = start + sampPerWindow
step = 1
y = Y[start : stop : step]
line.set_data(x, y)
return line,
# call the animator.
anim = animation.FuncAnimation(fig, animate, init_func=init,
frames=passThruFrames, interval=frameDurMs, blit=True, repeat=False)
anim.save('sinewave_animation.mp4', fps=FPS, extra_args=['-vcodec', 'libx264'])
plt.show()
I am currently having some trouble with my code which animates some time-series data, and I cannot quite figure it out. Basically I have 12 tags which I am animating through time. Each tag has a trajectory in time such that the movement path can be seen for each tag as it progresses (have a look at the image attached). Now I would like the animation to also include the lines between pairs of tags (i.e. pairs of points - for example, how to add an animation line between the yellow and green tags), but I am not entirely sure how to do this. This is code adapted from jakevdp.github.io.
Here is the code thus far.
"""
Full animation of a walking event (note: a lot of missing data)
"""
import numpy as np
import pandas as pd
import matplotlib
matplotlib.use('TkAgg') # Need to use in order to run on mac
from matplotlib import pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
from matplotlib.colors import cnames
from matplotlib import animation
#=============================================================================================
t_start = 1917 # start frame
t_end = 2130 # end frame
data = pd.read_csv('~/Smart-first_phase_NaN-zeros.csv') # only coordinate data
df = data.loc[t_start:t_end,'Shoulder_left_x':'Ankle_right_z']
# Find max and min values for animation ranges
df_minmax = pd.DataFrame(index=list('xyz'),columns=range(2))
for i in list('xyz'):
c_max = df.filter(regex='_{}'.format(i)).max().max()
c_min = df.filter(regex='_{}'.format(i)).min().min()
df_minmax.ix[i] = np.array([c_min,c_max])
df_minmax = 1.3*df_minmax # increase by 30% to make animation look better
df.columns = np.repeat(range(12),3) # store cols like this for simplicity
N_tag = df.shape[1]/3 # nr of tags used (all)
N_trajectories = N_tag
t = np.linspace(0,data.Time[t_end],df.shape[0]) # pseudo time-vector for first walking activity
x_t = np.zeros(shape=(N_tag,df.shape[0],3)) # empty animation array (3D)
for tag in range(12):
# store data in numpy 3D array: (tag,time-stamp,xyz-coordinates)
x_t[tag,:,:] = df[tag]
#===STICK-LINES========================================================================================
#xx = [x_t[1,:,0],x_t[2,:,0]]
#yy = [x_t[1,:,1],x_t[2,:,1]]
#zz = [x_t[1,:,2],x_t[2,:,2]]
#======================================================================================================
# Set up figure & 3D axis for animation
fig = plt.figure()
ax = fig.add_axes([0, 0, 1, 1], projection='3d')
ax.axis('on')
# choose a different color for each trajectory
colors = plt.cm.jet(np.linspace(0, 1, N_trajectories))
# set up trajectory lines
lines = sum([ax.plot([], [], [], '-', c=c) for c in colors], [])
# set up points
pts = sum([ax.plot([], [], [], 'o', c=c) for c in colors], [])
# set up lines which create the stick figures
stick_lines = sum([ax.plot([], [], [], '-', c=c) for c in colors], [])
# prepare the axes limits
ax.set_xlim(df_minmax.ix['x'].values)
ax.set_ylim(df_minmax.ix['z'].values) # note usage of z coordinate
ax.set_zlim(df_minmax.ix['y'].values) # note usage of y coordinate
# set point-of-view: specified by (altitude degrees, azimuth degrees)
ax.view_init(30, 0)
# initialization function: plot the background of each frame
def init():
for line, pt, stick_line in zip(lines, pts, stick_lines):
# trajectory lines
line.set_data([], [])
line.set_3d_properties([])
# points
pt.set_data([], [])
pt.set_3d_properties([])
# stick lines
stick_line.set_data([], [])
stick_line.set_3d_properties([])
return lines + pts + stick_lines
# animation function. This will be called sequentially with the frame number
def animate(i):
# we'll step two time-steps per frame. This leads to nice results.
i = (5 * i) % x_t.shape[1]
for line, pt, stick_line, xi in zip(lines, pts, stick_lines, x_t):
x, z, y = xi[:i].T # note ordering of points to line up with true exogenous registration (x,z,y)
# trajectory lines
line.set_data(x,y)
line.set_3d_properties(z)
# points
pt.set_data(x[-1:], y[-1:])
pt.set_3d_properties(z[-1:])
# stick lines
#stick_line.set_data(xx,zz)
#stick_line.set_3d_properties(yy)
ax.view_init(30, 0.3 * i)
fig.canvas.draw()
return lines + pts + stick_lines
# instantiate the animator.
anim = animation.FuncAnimation(fig, animate, init_func=init, frames=500, interval=30, blit=True)
# Save as mp4. This requires mplayer or ffmpeg to be installed
#anim.save('lorentz_attractor.mp4', fps=15, extra_args=['-vcodec', 'libx264'])
plt.show()
So, to conclude: I would like lines that moves with the point pairs (orange, yellow) and (yellow, green). If someone could show me how to do that I should be able to extrapolate the methods to the rest of the animation.
As ever, any help is much appreciated.
The original data can be found here, if anyone wants to replicate: https://www.dropbox.com/sh/80f8ue4ffa4067t/Pntl5-gUW4
EDIT: IMPLEMENTED SOLUTION
Here is the final result, using the proposed solution.
I modified your code to add stick lines, but to simplify the code, I removed the trace lines:
import numpy as np
import pandas as pd
import matplotlib
matplotlib.use('TkAgg') # Need to use in order to run on mac
from matplotlib import pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
from matplotlib.colors import cnames
from matplotlib import animation
#=============================================================================================
t_start = 1917 # start frame
t_end = 2130 # end frame
data = pd.read_csv('Smart-first_phase_NaN-zeros.csv') # only coordinate data
df = data.loc[t_start:t_end,'Shoulder_left_x':'Ankle_right_z']
# Find max and min values for animation ranges
df_minmax = pd.DataFrame(index=list('xyz'),columns=range(2))
for i in list('xyz'):
c_max = df.filter(regex='_{}'.format(i)).max().max()
c_min = df.filter(regex='_{}'.format(i)).min().min()
df_minmax.ix[i] = np.array([c_min,c_max])
df_minmax = 1.3*df_minmax # increase by 30% to make animation look better
df.columns = np.repeat(range(12),3) # store cols like this for simplicity
N_tag = df.shape[1]/3 # nr of tags used (all)
N_trajectories = N_tag
t = np.linspace(0,data.Time[t_end],df.shape[0]) # pseudo time-vector for first walking activity
x_t = np.zeros(shape=(N_tag,df.shape[0],3)) # empty animation array (3D)
for tag in range(12):
# store data in numpy 3D array: (tag,time-stamp,xyz-coordinates)
x_t[tag,:,:] = df[tag]
x_t = x_t[:, :, [0, 2, 1]]
# Set up figure & 3D axis for animation
fig = plt.figure()
ax = fig.add_axes([0, 0, 1, 1], projection='3d')
ax.axis('on')
# choose a different color for each trajectory
colors = plt.cm.jet(np.linspace(0, 1, N_trajectories))
# set up trajectory lines
lines = sum([ax.plot([], [], [], '-', c=c) for c in colors], [])
# set up points
pts = sum([ax.plot([], [], [], 'o', c=c) for c in colors], [])
# set up lines which create the stick figures
stick_defines = [
(0, 1),
(1, 2),
(3, 4),
(4, 5),
(6, 7),
(7, 8),
(9, 10),
(10, 11)
]
stick_lines = [ax.plot([], [], [], 'k-')[0] for _ in stick_defines]
# prepare the axes limits
ax.set_xlim(df_minmax.ix['x'].values)
ax.set_ylim(df_minmax.ix['z'].values) # note usage of z coordinate
ax.set_zlim(df_minmax.ix['y'].values) # note usage of y coordinate
# set point-of-view: specified by (altitude degrees, azimuth degrees)
ax.view_init(30, 0)
# initialization function: plot the background of each frame
def init():
for line, pt in zip(lines, pts):
# trajectory lines
line.set_data([], [])
line.set_3d_properties([])
# points
pt.set_data([], [])
pt.set_3d_properties([])
return lines + pts + stick_lines
# animation function. This will be called sequentially with the frame number
def animate(i):
# we'll step two time-steps per frame. This leads to nice results.
i = (5 * i) % x_t.shape[1]
for line, pt, xi in zip(lines, pts, x_t):
x, y, z = xi[:i].T # note ordering of points to line up with true exogenous registration (x,z,y)
pt.set_data(x[-1:], y[-1:])
pt.set_3d_properties(z[-1:])
for stick_line, (sp, ep) in zip(stick_lines, stick_defines):
stick_line._verts3d = x_t[[sp,ep], i, :].T.tolist()
ax.view_init(30, 0.3 * i)
fig.canvas.draw()
return lines + pts + stick_lines
# instantiate the animator.
anim = animation.FuncAnimation(fig, animate, init_func=init, frames=500, interval=30, blit=True)
plt.show()
Here is one frame of the animation: