Generating figures in a loop and creating animation - python

I'm doing a complicated calculation inside a while loop in Python, and plotting the results for each iteration in a figure. I'm having problems saving each figure and making a gif with these images. I tried the suggestions in this post, but I don't know what to put in the imshow field:
import matplotlib.pyplot as plt
import matplotlib.animation as animation
import numpy as np
thist = 4 # total time
tstep = 1 # time step
# ims is a list of lists, each row is a list of artists to draw in the
# current frame; here we are just animating one artist, the image, in
# each frame
ims = []
while thist>=tstep:
x = np.linspace(0,2,100)
y = np.sin(x*(thist-tstep)) # complicated calculation
fig = plt.figure(figsize=(10,6))
ax = plt.subplot(111)
plt.plot(x,y)
#plt.show()
im = plt.imshow(fig)
ims.append([im])
tstep += 1
ani = animation.ArtistAnimation(fig,ims,interval=100,blit=True,repeat_delay=1000)
ani.save('mytest.gif', writer='imagemagick')

Related

Matplotlib FuncAnimation Step-by-Step Animation Function

I am trying to use matplotlib's FuncAnimation to make an animated video. Each frame is just a boolean n x n array visualised as white/black squares. I can do this successfully by defining all the arrays in advance and then going through them one by one. This uses code similar to matplotlib's example.
My items are rather large and I want to run the simulation for a long time. I thus don't want to create the entire list of arrays then go through them one by one. Instead, I want to define the animate function to do each step. Let me explain with a minimal non-working example. My actual example includes far larger arrays!
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation
def create_video(n):
global X
X = np.random.binomial(1, 0.3, size = (n,n))
fig = plt.figure()
im = plt.imshow(X, cmap = plt.cm.gray)
def animate(t):
global X
X = np.roll(X, +1, axis = 0)
im.set_array(X)
anim = FuncAnimation(
fig,
animate,
frames = 100,
interval = 1000 / 30,
blit = True
)
return anim
anim = create_video(10)
This initialises some random 10 x 10 set of 0/1s then just 'rolls' it at each step. I get an error.
RuntimeError: The animation function must return a sequence of Artist objects.
If I remove the return anim, replacing it with pass, and replacing anim = create_video(10) with create_video(10), then I get a warning.
UserWarning: Animation was deleted without rendering anything. This is most likely unintended. To prevent deletion, assign the Animation to a variable that exists for as long as you need the Animation.
Clearly, I don't understand well enough FuncAnimation. What I want to happen is for the function animate to update the array X, by 'rolling' it one step, as well as doing im.set_array(X).
As explained in this answer:
As the error suggests, and as can be seen e.g. in the
simple_animation example, but also from the FuncAnimation
documentation, the init_func as well as the updating func are
supposed to return an iterable of artists to animate.
The documentation does not say that this is actually only needed when
using blit=True, but since you are using blitting here, it is
definitely needed.
So you have two ways:
add
return im,
to animate function
set blit = False in FuncAnimation
Complete Code
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation
def create_video(n):
global X
X = np.random.binomial(1, 0.3, size = (n, n))
fig = plt.figure()
im = plt.imshow(X, cmap = plt.cm.gray)
def animate(t):
global X
X = np.roll(X, +1, axis = 0)
im.set_array(X)
return im,
anim = FuncAnimation(
fig,
animate,
frames = 100,
interval = 1000/30,
blit = True
)
plt.show()
return anim
anim = create_video(10)

Matplotlib: Plot matrix in a loop while updating colorbar value range

I want to plot a 3D tensor plane by plane using matplotlib in a loop.
However, in this example, matplotlib keeps on adding colorbars to the figure:
data = np.random.rand(100,100,10)
for i in range(10):
plt.imshow(np.squeeze(data[:, :, i]))
plt.colorbar()
plt.pause(2)
print(i)
Caveat: I've seen some complicated answers to this simple question, which didn't work. The problem may sound simple, but I'm thinking there might be an easy (short) solution.
The easy solution
Clear the figure in each loop run.
import numpy as np
import matplotlib.pyplot as plt
data = np.random.rand(100,100,10) * np.linspace(1,7,10)
fig = plt.figure()
for i in range(10):
plt.clf()
plt.imshow(np.squeeze(data[:, :, i]))
plt.colorbar()
plt.pause(2)
plt.show()
The efficient solution
Use the same image and just update the data. Also use a FuncAnimation instead of a loop to run everything within the GUI event loop.
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation
data = np.random.rand(100,100,10) * np.linspace(1,7,10)
fig, ax = plt.subplots()
im = ax.imshow(np.squeeze(data[:, :, 0]))
cbar = fig.colorbar(im, ax=ax)
def update(i):
im.set_data(data[:, :, i])
im.autoscale()
ani = FuncAnimation(fig, update, frames=data.shape[2], interval=2000)
plt.show()
So here is a solution. Unfortunately it is not short at all. If someone knows how to make this less complicated, feel free to post another answer.
This is slightly modified version of this answer
import matplotlib.pyplot as plt
import numpy as np
def visualize_tensor(data, delay=0.5):
""" data must be 3 dimensional array and
have format:
[height x width x channels]"""
assert(np.ndim(data) == 3)
# Get number of channels from last dimension
num_channels = np.shape(data)[-1]
# Plot data of first channel
fig = plt.figure()
ax = fig.add_subplot(111)
data_first_channel = data[:, :, 0]
plot = ax.imshow(data_first_channel)
# Create colorbar
cbar = plt.colorbar(plot)
plt.show(block=False)
# Iterate over all channels
for i in range(num_channels):
print(f"channel = {i}")
data_nth_channel = np.squeeze(data[:, :, i])
plot.set_data(data_nth_channel)
plot.autoscale()
vmin = np.min(data_nth_channel.view()) # get minimum of nth channel
vmax = np.max(data_nth_channel.view()) # get maximum of nth channel
cbar.set_clim(vmin=vmin, vmax=vmax)
cbar_ticks = np.linspace(vmin, vmax, num=11, endpoint=True)
cbar.set_ticks(cbar_ticks)
cbar.draw_all()
plt.draw()
plt.pause(delay)
Example execution:
data = np.random.rand(20,20,10)
visualize_tensor(data)
Update:
Using plot.autoscale() forces the colorbar to adapt dynamically, see this answer
This question intrigued me as hacking at matplotlib is somewhat my hobby. Next to the solution posed by #mcExchange one could use this
from matplotlib.pyplot import subplots
import numpy as np
%matplotlib notebook
d = np.random.rand(10, 10)
fig, ax = subplots(figsize = (2,2))
# create mappable
h = ax.imshow(d)
# create colorbar
cb = fig.colorbar(h)
# show non-blocking
fig.show(0)
for i in range(100):
# generate new data
h.set_data(np.random.randn(*d.shape) + 1)
h.autoscale()
# flush events update time
ax.set_title(f't = {i}')
fig.canvas.draw(); fig.canvas.flush_events();
How did I get this solution?
The docs state that colorbar.update_normal only updates if the norm on the mappable is different than before. Setting the data doesn't change this. As such manually function have to be called to register this update.
Behind the scene the following happens:
# rescale data for cb trigger
h.norm.autoscale(h._A) #h._A is the representation of the data
# update mappable
h.colorbar.update_normal(h.colorbar.mappable)

General way to animate any artist in matplotlib?

I have tried to animate two different artists plt.quiver() and plt.hist() in matplotlib recently and both times I ran into the same problem. Apparently those classes (I hope my OOP literacy is holding up) both don't have a set_data like method. Well, technically plt.quiver() does have set_UVC, but that doesn't work with Line3D instances, only with Line2D. Also, there is an example for animating a histogram, but it seemed like some serious jerry-rigging to me. I tried to simply define my artist with new values in the update() function and then just return the new artist instead of defining the artist outside the update() and then updating the data of the artist using a set_data() method. But this only results in an animation in which all frames are kept in the plot and overlap. Below are the animations for both the Histogram and the Quiver plot.
Histogram:
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation
"""
evolution of mean values produced by 1000 dice rolls with
more and more dices, which lead to a narrowing variance
with a growing number of dices.
"""
fig, ax = plt.subplots()
def update(i):
k = [np.mean(np.random.randint(0,7,i)) for j in range(1000)]
lol = ax.hist(k,bins=20)
return lol
ani = FuncAnimation(fig, update, frames=(1,2,10,100,1000))
plt.show()
Quiver:
from mpl_toolkits.mplot3d import axes3d
import matplotlib.pyplot as plt
import numpy as np
from matplotlib.animation import FuncAnimation
def rot_z(angle):
o = 2*np.pi*(angle/360)
mat = np.array(((np.cos(o),-np.sin(o),0),
(np.sin(o), np.cos(o),0),
( 0 , 0 ,0)))
return mat
fig = plt.figure()
ax = fig.gca(projection='3d')
ax.set_xlim(-1.5,1.5)
ax.set_ylim(-1.5,1.5)
ax.set_zlim(-1.5,1.5)
def update(frame):
x,y,z = rot_z(frame).dot(np.array((1,1,1)))
quiv = ax.quiver(0,
0,
0,
x,
y,
z,
length=1)
return quiv
ani = FuncAnimation(fig, update, frames=np.linspace(0,360,100))
plt.show()
If you run them, you can see the issue. So I wanted to know: Isn't there an easier, abstractable way of animating artists, or am I at the mercy of potentially non-existent setters? I have checked both dir(plt.quiver), dir(plt.hist) to see if I was simply overlooking those methods in the docs, but the example of the animated histogram seemed to confirm my fears.
You could try to clear the image at every update with ax.clear(). Maybe the histogram animation would be more smooth if you would extend an array of throws instead of restarting from scratch at each frame?
Edit: the code below includes a test to reuse the same samples
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation
fig, ax = plt.subplots()
randnums = [np.random.randint(0,7,1000) for j in range(1000)]
def update(i):
k = [np.mean(randnums[j][:i]) for j in range(1000)]
ax.clear()
lol = ax.hist(k,bins=20)
return lol
ani = FuncAnimation(fig, update, frames=[2**t for t in range(11)])
plt.show()

FuncAnimation printing first image only

I'm trying to animate a 3D array in python using the first dimension as time.
I'm not sure where I am going wrong, as I recieve no error with this code. But my animation is stationary, stuck on the first page of the array.
import numpy as np
from matplotlib import pyplot as plt
from matplotlib.animation import FuncAnimation
array = np.random.random(size=(10, 20, 30))
empty = np.zeros(array[0].shape)
fig = plt.figure()
mat = plt.imshow(empty)
def func(i):
mat.set_data(array[i])
return mat
frames = len(array)
FuncAnimation(fig, func, frames)
plt.show()
I'd like the use the below code but I haven't seen an annonymous function used anywhere with FuncAnimation. It produces the same result except mat is not created setting the initial axes.
fig = plt.figure()
func = lambda i: plt.imshow(array[i])
frames = len(array)
FuncAnimation(fig, func, frames)
plt.show()
The main difference between your code and any example you find on matplotlib animations is that you do not actually store the FuncAnimation. Depending on how you run things it would then directly be garbage-collected.
ani = FuncAnimation(...)

animate with variable time

I have trajectory data where each vehicle has its own time to start. Each vehicle is a point in the animation. So, in the dataset, for each row there is coordinate point (x,y) along with a timestamp. So, fixed time interval would not work for me. I tried with loop and sleep but it not showing the animation but only the first result. But if debug line by line, it seems okay(updating with new points after each iteration). Here is my code (this is to test: loop, sleep and animation):
#sample data
x=[20,23,25,27,29,31]
y=[10,12,14,16,17,19]
t=[2,5,1,4,3,1,]
#code
fig, ax = plt.subplots()
ax.set(xlim=(10, 90), ylim=(0, 60))
for i in range(1,6):
ax.scatter(x[:i+1], y[:i+1])
plt.show()
time.sleep(t[i])
How can get the animation effect?
The already mentioned FuncAnimation has a parameter frame that the animation function can use an index:
import matplotlib.pyplot as plt
import matplotlib.animation as anim
fig = plt.figure()
x=[20,23,25,27,29,31]
y=[10,12,14,16,17,19]
t=[2,9,1,4,3,9]
#create index list for frames, i.e. how many cycles each frame will be displayed
frame_t = []
for i, item in enumerate(t):
frame_t.extend([i] * item)
def init():
fig.clear()
#animation function
def animate(i):
#prevent autoscaling of figure
plt.xlim(15, 35)
plt.ylim( 5, 25)
#set new point
plt.scatter(x[i], y[i], c = "b")
#animate scatter plot
ani = anim.FuncAnimation(fig, animate, init_func = init,
frames = frame_t, interval = 100, repeat = True)
plt.show()
Equivalently, you could store the same frame several time in the ArtistAnimation list. Basically the flipbook approach.
Sample output:

Categories

Resources