by Idalia Machuca and Nancy Soontiens
In order to save these movies, we will need a library for writing videas. I've been using ffmpeg which can be downloaded here: http://www.ffmpeg.org/download.html. Download the package approriate for your system (windows, Mac, etc).
You will have to add the ffmpeg executable to your path.
export PATH = 'path to where I extracted the FFMPEG download':$PATH
Actually, Windows users can also the above line in their Git-Bash window too.
You can test if you've done this correctly by typing ffmpeg in your terminal. If you get an error like "ffmpeg executable not found" then you haven't set things up correctly. Feel free to ask for help.
For those of you working on "Waterhole" machines, ffmpeg has already been installed.
There are many examples of using Python to generate movies available on the internet with a quick Google search. We have found these pages helpful:
Like all of our notebooks, we will need a set of tools to help us generate awesome movies! Like usual, load in matplotlib.pyplot, numpy, but also matlplotlib.animation.
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as animation
Notice that we didn't use the %matlplotlib inline feature. This means our movies will pop up in a new window with the plt.show() command. We have done this because plots don't animate inline.
We will need some data in order to produce an animation! Later, we will talk about loading data from a file but for now we will generate a sine curve as a simple example.
fig = plt.figure()
x = np.arange(0, 2*np.pi, 0.01)
plt.plot(x, np.sin(x),'b')
plt.axis([0,2*np.pi,-1,1])
plt.show()
To animate the sine curve, we will need to create a function that will help us loop through frames. It will take one simple argument that we can think of as time.
def animate_sine(t):
plt.plot(x,np.sin(x+t/10.0),'b')
plt.axis([0,2*np.pi,-1,1])
Now, we can use methods from the animation package to generate the movie.
Notes: we are passing the figure handle, our animate function and the number of frames we would like to be animated. The frame number is the argument that is passed to the animate_sine function.
fig=plt.figure()
ani = animation.FuncAnimation(fig,animate_sine, frames=200)
plt.show()
But this isn't exactly what we were expecting! Each frame contains the plot from the previous frame. We can get around this by clearing the axis at the start of our animiate_sine function.
def animate_sine(t):
plt.cla()
plt.plot(x,np.sin(x+t/10.0),'b')
plt.axis([0,2*np.pi,-1,1])
fig, ax = plt.subplots()
ani = animation.FuncAnimation(fig,animate_sine, frames=200)
plt.show()
We can also overcome the duplicate plots using the "blit" attribute of the FuncAnimation function. This isn't as intuitive as clearing the frame, but is perhaps more "Pythonic" and takes advantage of the way the animation function was written. An example is provided below.
fig,ax = plt.subplots()
#An empty plot
pl,=ax.plot([],[])
ax.set_xlim([0,2*np.pi])
ax.set_ylim([-1,1])
#animate funcion
def animate_sine(t):
pl.set_data(x,np.sin(x+t/10.0))
return pl,
#set a clear initial frame
def init():
pl.set_data([],[])
return pl,
#blit=True to remove instances from previous frames
ani = animation.FuncAnimation(fig,animate_sine, frames=200, init_func=init, blit=True)
plt.show()
Now we would like to save the movie. This is where we need to use ffmepg. We will define a movie writer that uses the ffmpeg libraries to save movies.
The fps argument specifies the number of frames per second.
fig, ax = plt.subplots()
ani = animation.FuncAnimation(fig,animate_sine, frames=200)
mywriter = animation.FFMpegWriter(fps=50)
ani.save('sine_wave.mp4', writer=mywriter)
There are many, many options to play experiment with. This is just a simple introduction. For example, additional arguments can be provided to the animate_sine() function during the call to FuncAnimation using the fargs argument.
See the documentation for FuncAnimation http://matplotlib.org/api/animation_api.html
Next, we will load data from a file and animate it. Like last time, we will use the pandas package to read a text file. The text file we are using can be found at www.eos.ubc.ca/~nsoontie/traj.txt . Place this file in your current working directory.
We will also switch to plotting inline.
import pandas as pd
%matplotlib inline
Next, we will take a closer look at the traj.txt. Remember that we can execuate shell commands in the notebook using the ! .
! cat traj.txt
1 -123.25558 48.61595 -86.96747 .00000 1 -123.23922 48.59781 -28.42194 .10000 1 -123.18216 48.36028 -11.73121 .20000 1 -123.39746 48.30912 -7.30910 .30000 1 -123.69411 48.27558 -15.48464 .40000 1 -123.96874 48.35421 -23.59864 .50000 1 -124.17054 48.41030 -16.37882 .60000 1 -124.33354 48.47167 -9.71394 .70000 1 -124.39400 48.50298 -7.74570 .80000 1 -124.46454 48.51483 -21.74961 .90000
This file contains a particle trajectory from an ocean model output.
Each column lists the particle number, longitude, latitude, depth, and age. Use pandas to read this in. Since this file doesn't have a header, we can name the columns in the pandas read_csv command using the names argument.
data=pd.read_csv('traj.txt',delimiter=' ',names=['num','lon','lat','depth','age'])
Look at the data now.
print data
num lon lat depth age 0 1 -123.25558 48.61595 -86.96747 0.0 1 1 -123.23922 48.59781 -28.42194 0.1 2 1 -123.18216 48.36028 -11.73121 0.2 3 1 -123.39746 48.30912 -7.30910 0.3 4 1 -123.69411 48.27558 -15.48464 0.4 5 1 -123.96874 48.35421 -23.59864 0.5 6 1 -124.17054 48.41030 -16.37882 0.6 7 1 -124.33354 48.47167 -9.71394 0.7 8 1 -124.39400 48.50298 -7.74570 0.8 9 1 -124.46454 48.51483 -21.74961 0.9 [10 rows x 5 columns]
This data set contains the latitude,longitude and depth of a particle trajectory. There are 10 outputs for one single particle.
Let's store the lat,lon and depth in variables so we can plot them.
lon = data['lon']
lat=data['lat']
dep = data['depth']
We can plot the horizontal positions of this particle. We will colour the initial position red so we have a sense of direction.
fig =plt.figure()
plt.scatter(lon,lat,color='blue')
plt.scatter(lon[0],lat[0],color='red')
plt.title('2D')
plt.xlabel('Longitude')
plt.ylabel('Latitude')
plt.axis([-124.6,-123.0,48.25,48.65])
[-124.6, -123.0, 48.25, 48.65]
Using the same strategy as earlier, we will plot this trajectory in an animation. This time our animate() function takes an argument "p", which correspsonds to each trajectory point. So, "p" will be passed as an index to the lon and lat variables. There are 10 outputs, so our animation will have 10 frames.
#Empty map
fig= plt.figure()
plt.title('2D')
plt.xlabel('Longitude')
plt.ylabel('Latitude')
plt.axis([-124.6,-123.0,48.25,48.65])
#Animated points
def animate(p):
plt.scatter(lon[p],lat[p],color='blue')
#The animation function
anim = animation.FuncAnimation(fig, animate, frames=10)
#A line that makes it all work
mywriter = animation.FFMpegWriter()
#Save in current folder
anim.save('Software-Carpentry2D.mp4',writer=mywriter)
Notice that we didn't clear the axis between each frame because it was useful to see where the particle came from.
We aren't using all of the information by just plotting in 2D. So, let's try a 3D plot so that we can see how the particle changes with depth. We need to import some more tools.
from mpl_toolkits.mplot3d import Axes3D
fig = plt.figure()
ax=plt.gca(projection='3d')
ax.scatter(lon,lat,dep,color='blue')
ax.set_title('3D')
ax.set_xlabel('Longitude')
ax.set_ylabel('Latitude')
ax.set_zlabel('Depth')
ax.set_xlim([-124.6,-123.0])
ax.set_ylim([48.25,48.65])
ax.set_zlim([-100,0])
(-100, 0)
Adjust the camera position to get a different perspective using ax.view_init().
fig = plt.figure()
ax=plt.gca(projection='3d')
ax.scatter(lon,lat,dep,color='blue')
ax.set_title('3D')
ax.set_xlabel('Longitude')
ax.set_ylabel('Latitude')
ax.set_zlabel('Depth')
ax.set_xlim([-124.6,-123.0])
ax.set_ylim([48.25,48.65])
ax.set_zlim([-100,0])
ax.view_init(elev=0, azim=-90)
Now, put it all together in an animation. This is very similat to the code for the 2D animation.
#Empty map
fig = plt.figure(figsize=(20,10))
ax = fig.gca(projection='3d')
ax.set_title('3D')
ax.set_xlabel('Longitude')
ax.set_ylabel('Latitude')
ax.set_zlabel('Depth')
ax.set_xlim([-124.6,-123.0])
ax.set_ylim([48.25,48.65])
ax.set_zlim([-100,0])
ax.view_init(elev=0, azim=-90)
#Animated points
def animate(p):
ax.scatter(lon[p],lat[p],dep[p],color='blue')
#The animation function
anim = animation.FuncAnimation(fig, animate, frames=10)
#A line that makes it all work
mywriter = animation.FFMpegWriter()
#Save in current folder
anim.save('Software-Carpentry3D.mp4',writer=mywriter)