3D Animation in Python: Moving Point and Trajectory - python

I am switching from Matlab to Python, so I am a relative beginner. What I would like to get is a 3D animation of a point moving in space, say along a helix for simplicity, and a history of its trajectory.
Based on this example http://matplotlib.org/examples/animation/simple_3danim.html, I have come with the following code:
import numpy as np
import matplotlib.pyplot as plt
import mpl_toolkits.mplot3d.axes3d as p3
import matplotlib.animation as animation
###############################################################################
# Create helix:
def make_helix(n):
theta_max = 8 * np.pi
theta = np.linspace(0, theta_max, n)
x, y, z = theta, np.sin(theta), np.cos(theta)
helix = np.vstack((x, y, z))
return helix
# Update AUV position for plotting:
def update_auv(num, dataLines, lines) :
for line, data in zip(lines, dataLines) :
line.set_data(data[0:2, num-1:num])
line.set_3d_properties(data[2,num-1:num])
return lines
# Update trajectory for plotting:
def update_trj(num, dataLines, lines) :
for line, data in zip(lines, dataLines) :
line.set_data(data[0:2, :num])
line.set_3d_properties(data[2,:num])
return lines
###############################################################################
# Attach 3D axis to the figure
fig = plt.figure()
ax = p3.Axes3D(fig)
# Define no. data points and create helix:
n = 100
data = [make_helix(n)]
# Create line objects:
auv = [ax.plot(data[0][0,0:1], data[0][1,0:1], data[0][2,0:1], 'ro')[0]]
trj = [ax.plot(data[0][0,0:1], data[0][1,0:1], data[0][2,0:1])[0]]
# Setthe axes properties
ax.set_xlim3d([0.0, 8*np.pi])
ax.set_xlabel('X')
ax.set_ylim3d([-1.0, 1.0])
ax.set_ylabel('Y')
ax.set_zlim3d([-1.0, 1.0])
ax.set_zlabel('Z')
ax.set_title('3D Test')
# Creating the Animation object
ani_auv = animation.FuncAnimation(fig, update_auv, n, fargs=(data, auv),
interval=50, blit=False) #repeat=False,
ani_trj = animation.FuncAnimation(fig, update_trj, n, fargs=(data, trj),
interval=50, blit=False) #repeat=False,
plt.show()
Now, this code shows what I am trying to achieve (show the moving body as a point and the history of the trajectory at the same time), but it has two major problems:
The trajectory is recalculated at every time step, which is inefficient, but computing power should not be a problem;
The bigger problem is there is a misalignment between the point and trajectory. I think the reason for it is due to the lower computational time associated with the calculation of the position of the simple point.
An alternative could be what they do here: Animate a python pyplot by moving a point plotted via scatter, but to be honest, I would prefer to find a way to create an animation with time stamps. That way, I have to calculate the trajectory only once and can update the position of the point at every new time step.
Thank you for the help!

Related

Change point color in python plot animation

I have a list of points, lets say as (x,y) pairs. I am trying to animate a plot so that each frame of the animation, a new point show up on the plot in a different color. Specifically on the 0th frame, the 0th point appears, on the the 1st frame, the 1st point appears, and so on. I would also like to have these points appear in a new color, specifically like a linear progression through a color palette as the points progress, so that you can "follow" the points by their color. This is similar to, and how I got as far as I am now: How can i make points of a python plot appear over time?. The first animation in the link is spot on, except without the points changing colors.
I am using matplotlib, matplotlib.pyplot, and FuncAnimation from matplotlib.animation
What I have already:
def plot_points_over_time(list_of_points):
num_points = len(list_of_points)
fig = plt.figure()
x, y = zip(*list_of_points)
plt.xlim(min(x),max(x))
plt.ylim(min(y),max(y))
colors = [plt.cm.gist_rainbow(each) for each in np.linspace(0,1,num_points)]
graph, = plt.plot([],[],'o')
def animate(i):
graph.set_data(x[:i+1],y[:i+1])
return graph
ani = FuncAnimation(fig, animate, frames = num_points, repeat = False, interval = 60000/num_points)
plt.show()
I can change the color of all of the points together on each frame by including the line graph.set_color(colors[i]) in the animate function, but not each point individually.
Figured it out with some digging and trial and error:
def plot_points_over_time(list_of_points):
num_points = len(list_of_points)
fig = plt.figure()
x, y = zip(*list_of_points)
plt.xlim(min(x),max(x))
plt.ylim(min(y),max(y))
colors = [plt.cm.gist_rainbow(each) for each in np.linspace(0,1,num_points)]
scat, = plt.plot([],[])
def animate(i):
scat.set_offsets(np.c_[x[:i+1], y[:i+1]])
scat.set_color(colors[:i+1])
return scat,
ani = FuncAnimation(fig, animate, frames = num_points, repeat = False, interval = 60000/num_points)
plt.show()

Python animation strange behavior

I am doing an animation of a launched projectile and running into some odd behavior that I don't understand. I am plotting the animation of a point, representing the object, and also animating the path so that the trajectory shows up behind the object. However, when I do this the way I think I am supposed to do it, the point is shown one step ahead of the trajectory and the trajectory ends up one point shy of finishing. I can work around this by increasing the index number of the trajectory, but then it seems like the index should be out of bounds at the end. I am really confused and could use some help understanding what's going on.
I am working in a Jupyter notebook and have provided a minimally working example below. I am currently just using 10 points in my linspace command and have slowed down the animation a lot so you can see what's happening. If I use the command line1.set_data(x[0:frames+1], y[0:frames+1]) along with point1.set_data(x[frames], y[frames]) in the animate function, then everything looks fine. But that seems like it shouldn't work!
What am I missing?
%matplotlib notebook
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as animation
# Global constants
g = 9.8 # gravitational field strength in m/s^2
v0 = 40.0 # initial speed in m/s
theta = 30.0 # Launch angle in degrees
# determine trajectory
theta_rad = np.pi*theta/180
t = np.linspace(0, 2*v0*np.sin(theta_rad)/g, 10)
x = v0*np.cos(theta_rad)*t
y = v0*t*np.sin(theta_rad) - g*t**2/2
# Plot the results
fig1 = plt.figure(figsize=(6,4))
ax1 = fig1.add_subplot(111)
line1, = ax1.plot(x[0:1], y[0:1], 'b-', label='no drag')
point1, = ax1.plot(x[0], y[0], 'bo', ms=3)
ax1.set_xlim(0, 170)
ax1.set_ylim(0, 50)
plt.show()
# Animation update function
def animate(frames):
line1.set_data(x[0:frames], y[0:frames])
point1.set_data(x[frames], y[frames])
return
ani = animation.FuncAnimation(fig1, animate, frames=len(t), interval=1000, blit=True, repeat=False)
plt.show()
In python the syntax x[0:frames] defines a range from 0 inclusive up to frames exclusive.
So, consider the loop:
At iteration 0 you are updating the point to x[0], y[0] and the line plot is empty: x[0:0], y[0:0] are empty sets;
At iteration 1 you have updated the point to x[1], y[1] and the line only has one couple of coordinates: x[0:1], y[0:1], not enough to form a line;
At iteration 2 the point is at its third position x[2], y[2] but the line plot lags behind because x[0:2], y[0:2] contain enough coordinates for two points, namely the first and second point, enough to only form the first segment but not the second required to reach the point.

Matplotlib: animate plot_surface using ArtistAnimation [duplicate]

I am looking to create an animation in a surface plot. The animation has fixed x and y data (1 to 64 in each dimension), and reads through an np array for the z information. An outline of the code is like so:
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as animation
def update_plot(frame_number, zarray, plot):
#plot.set_3d_properties(zarray[:,:,frame_number])
ax.collections.clear()
plot = ax.plot_surface(x, y, zarray[:,:,frame_number], color='0.75')
fig = plt.figure()
ax = plt.add_subplot(111, projection='3d')
N = 64
x = np.arange(N+1)
y = np.arange(N+1)
x, y = np.meshgrid(x, y)
zarray = np.zeros((N+1, N+1, nmax+1))
for i in range(nmax):
#Generate the data in array z
#store data into zarray
#zarray[:,:,i] = np.copy(z)
plot = ax.plot_surface(x, y, zarray[:,:,0], color='0.75')
animate = animation.FuncAnimation(fig, update_plot, 25, fargs=(zarray, plot))
plt.show()
So the code generates the z data and updates the plot in FuncAnimation. This is very slow however, I suspect it is due to the plot being redrawn every loop.
I tried the function
ax.set_3d_properties(zarray[:,:,frame_number])
but it comes up with an error
AttributeError: 'Axes3DSubplot' object has no attribute 'set_3d_properties'
How can I update the data in only the z direction without redrawing the whole plot? (Or otherwise increase the framerate of the graphing procedure)
There is a lot going on under the surface when calling plot_surface. You would need to replicate all of it when trying to set new data to the Poly3DCollection.
This might actually be possible and there might also be a way to do that slightly more efficient than the matplotlib code does it. The idea would then be to calculate all the vertices from the gridpoints and directly supply them to Poly3DCollection._vec.
However, the speed of the animation is mainly determined by the time it takes to perform the 3D->2D projection and the time to draw the actual plot. Hence the above will not help much, when it comes to drawing speed.
At the end, you might simply stick to the current way of animating the surface, which is to remove the previous plot and plot a new one. Using less points on the surface will significantly increase speed though.
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
import matplotlib.animation as animation
def update_plot(frame_number, zarray, plot):
plot[0].remove()
plot[0] = ax.plot_surface(x, y, zarray[:,:,frame_number], cmap="magma")
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
N = 14
nmax=20
x = np.linspace(-4,4,N+1)
x, y = np.meshgrid(x, x)
zarray = np.zeros((N+1, N+1, nmax))
f = lambda x,y,sig : 1/np.sqrt(sig)*np.exp(-(x**2+y**2)/sig**2)
for i in range(nmax):
zarray[:,:,i] = f(x,y,1.5+np.sin(i*2*np.pi/nmax))
plot = [ax.plot_surface(x, y, zarray[:,:,0], color='0.75', rstride=1, cstride=1)]
ax.set_zlim(0,1.5)
animate = animation.FuncAnimation(fig, update_plot, nmax, fargs=(zarray, plot))
plt.show()
Note that the speed of the animation itself is determined by the interval argument to FuncAnimation. In the above it is not specified and hence the default of 200 milliseconds. Depending on the data, you can still decrease this value before running into issues of lagging frames, e.g. try 40 milliseconds and adapt it depending on your needs.
animate = animation.FuncAnimation(fig, update_plot, ..., interval=40, ...)
set_3d_properties() is a function of the Poly3DCollection class, not the Axes3DSubplot.
You should run
plot.set_3d_properties(zarray[:,:,frame_number])
as you have it commented in your update function BTW, instead of
ax.set_3d_properties(zarray[:,:,frame_number])
I don't know if that will solve your problem though, but I'm not sure since the function set_3d_properties has no documentation attached. I wonder if you'd be better off trying plot.set_verts() instead.

Matplotlib pyplot in real time

I have a while function that generates two lists of numbers and at the end I plot them using matplotlib.pyplot.
I'm doing
while True:
#....
plt.plot(list1)
plt.plot(list2)
plt.show()
But in order to see the progression I have to close the plot window.
Is there a way to refresh it with the new data every x seconds?
The most robust way to do what you want is to use matplotlib.animation. Here's an example of animating two lines, one representing sine and one representing cosine.
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as animation
fig, ax = plt.subplots()
sin_l, = ax.plot(np.sin(0))
cos_l, = ax.plot(np.cos(0))
ax.set_ylim(-1, 1)
ax.set_xlim(0, 5)
dx = 0.1
def update(i):
# i is a counter for each frame.
# We'll increment x by dx each frame.
x = np.arange(0, i) * dx
sin_l.set_data(x, np.sin(x))
cos_l.set_data(x, np.cos(x))
return sin_l, cos_l
ani = animation.FuncAnimation(fig, update, frames=51, interval=50)
plt.show()
For your particular example, you would get rid of the while True and put the logic inside that while loop in the update function. Then, you just have to make sure to do set_data instead of making a whole new plt.plot call.
More details can be found in this nice blog post, the animation API, or the animation examples.
I think what you're looking for is the "animation" feature.
Here is an example
This example is a second one.

How do I plot a spectrogram the same way that pylab's specgram() does?

In Pylab, the specgram() function creates a spectrogram for a given list of amplitudes and automatically creates a window for the spectrogram.
I would like to generate the spectrogram (instantaneous power is given by Pxx), modify it by running an edge detector on it, and then plot the result.
(Pxx, freqs, bins, im) = pylab.specgram( self.data, Fs=self.rate, ...... )
The problem is that whenever I try to plot the modified Pxx using imshow or even NonUniformImage, I run into the error message below.
/opt/local/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/matplotlib/image.py:336: UserWarning: Images are not supported on non-linear axes.
warnings.warn("Images are not supported on non-linear axes.")
For example, a part of the code I'm working on right is below.
# how many instantaneous spectra did we calculate
(numBins, numSpectra) = Pxx.shape
# how many seconds in entire audio recording
numSeconds = float(self.data.size) / self.rate
ax = fig.add_subplot(212)
im = NonUniformImage(ax, interpolation='bilinear')
x = np.arange(0, numSpectra)
y = np.arange(0, numBins)
z = Pxx
im.set_data(x, y, z)
ax.images.append(im)
ax.set_xlim(0, numSpectra)
ax.set_ylim(0, numBins)
ax.set_yscale('symlog') # see http://matplotlib.org/api/axes_api.html#matplotlib.axes.Axes.set_yscale
ax.set_title('Spectrogram 2')
Actual Question
How do you plot image-like data with a logarithmic y axis with matplotlib/pylab?
Use pcolor or pcolormesh. pcolormesh is much faster, but is limited to rectilinear grids, where as pcolor can handle arbitrary shaped cells. specgram uses pcolormesh, if I recall correctly. (It uses imshow.)
As a quick example:
import numpy as np
import matplotlib.pyplot as plt
z = np.random.random((11,11))
x, y = np.mgrid[:11, :11]
fig, ax = plt.subplots()
ax.set_yscale('symlog')
ax.pcolormesh(x, y, z)
plt.show()
The differences you're seeing are due to plotting the "raw" values that specgram returns. What specgram actually plots is a scaled version.
import matplotlib.pyplot as plt
import numpy as np
x = np.cumsum(np.random.random(1000) - 0.5)
fig, (ax1, ax2) = plt.subplots(nrows=2)
data, freqs, bins, im = ax1.specgram(x)
ax1.axis('tight')
# "specgram" actually plots 10 * log10(data)...
ax2.pcolormesh(bins, freqs, 10 * np.log10(data))
ax2.axis('tight')
plt.show()
Notice that when we plot things using pcolormesh, there's no interpolation. (That's part of the point of pcolormesh--it's just vector rectangles instead of an image.)
If you want things on a log scale, you can use pcolormesh with it:
import matplotlib.pyplot as plt
import numpy as np
x = np.cumsum(np.random.random(1000) - 0.5)
fig, (ax1, ax2) = plt.subplots(nrows=2)
data, freqs, bins, im = ax1.specgram(x)
ax1.axis('tight')
# We need to explictly set the linear threshold in this case...
# Ideally you should calculate this from your bin size...
ax2.set_yscale('symlog', linthreshy=0.01)
ax2.pcolormesh(bins, freqs, 10 * np.log10(data))
ax2.axis('tight')
plt.show()
Just to add to Joe's answer...
I was getting small differences between the visual output of specgram compared to pcolormesh (as noisygecko also was) that were bugging me.
Turns out that if you pass frequency and time bins returned from specgram to pcolormesh, it treats these values as values on which to centre the rectangles rather than edges of them.
A bit of fiddling gets them to allign better (though still not 100% perfect). The colours are identical now also.
x = np.cumsum(np.random.random(1024) - 0.2)
overlap_frac = 0
plt.subplot(3,1,1)
data, freqs, bins, im = pylab.specgram(x, NFFT=128, Fs=44100, noverlap = 128*overlap_frac, cmap='plasma')
plt.title("specgram plot")
plt.subplot(3,1,2)
plt.pcolormesh(bins, freqs, 20 * np.log10(data), cmap='plasma')
plt.title("pcolormesh no adj.")
# bins actually returns middle value of each chunk
# so need to add an extra element at zero, and then add first to all
bins = bins+(bins[0]*(1-overlap_frac))
bins = np.concatenate((np.zeros(1),bins))
max_freq = freqs.max()
diff = (max_freq/freqs.shape[0]) - (max_freq/(freqs.shape[0]-1))
temp_vec = np.arange(freqs.shape[0])
freqs = freqs+(temp_vec*diff)
freqs = np.concatenate((freqs,np.ones(1)*max_freq))
plt.subplot(3,1,3)
plt.pcolormesh(bins, freqs, 20 * np.log10(data), cmap='plasma')
plt.title("pcolormesh post adj.")

Categories