Create single animation from programme - python

This programme fills a figure with square patches. The y axis limit is set so that it will be seen that there is only one patch in one position. It plots this filling process. I want to record the filling as an animation and am trying to do so with 'matplotlib.animation'. I turn the plotting part of the programme into a function (def filler(b):) so that I can pass this function to the animation lines at the bottom. When I run the programme I get an error right at the end of the plotting saying Python has stopped working. Please could somebody explain why. Thanks.
Note that I don't know what the b in the function argument is meant to represent. I include it because without it the programme doesn't run, asking for a positional argument.
import matplotlib.pyplot as plt
import matplotlib.patches as patches
import matplotlib.animation as animation
import numpy as np
startx = 0
endx = 10
blocks = 100
points = np.random.randint(startx,endx,size=blocks)
y = [-1]*int(endx-startx)
fig = plt.figure(figsize=(5,6))
ax = fig.add_subplot(111,aspect='equal')
ax.set_xlim(startx,endx)
ax.set_ylim(0,5)
def filler(b):
for i in range(blocks):
z = 5
a = patches.Rectangle((points[i],z),1,1,ec='k',fc=(1-i/blocks,i/(2*blocks),i/blocks))
ax.add_patch(a)
while z>y[int(points[i])]+1:
z=z-1
plt.pause(0.001)
a.set_y(z)
y[int(points[i])]=z
filler_ani = animation.FuncAnimation(fig, filler,interval=50, repeat=False, blit=True)
filler_ani.save('filler.mp4')

The code in the question mixes two different types of animations. Using a loop and plt.draw(), and a FuncAnimation. This will lead to chaos, as essentially the complete animation on screen is done during the first frame of the FuncAnimation, at the end of that first frame the animation fails.
So, one has to decide. Since it seems you want to do a FuncAnimation here, in order to be able to save it, one needs to get rid of the plt.draw. Then the problem is that there is a for loop and a while loop. This makes it hard to use a framenumber based animation.
Instead one may use a generator based animation.
import matplotlib.pyplot as plt
import matplotlib.patches as patches
import matplotlib.animation as animation
import numpy as np
startx = 0
endx = 10
blocks = 101
points = np.random.randint(startx,endx,size=blocks)
y = [-1]*int(endx-startx)
fig = plt.figure(figsize=(5,6))
ax = fig.add_subplot(111,aspect='equal')
ax.set_xlim(startx,endx)
ax.set_ylim(0,5)
def filler():
yield None
for i in range(blocks):
z = 5
a = patches.Rectangle((points[i],z),1,1,ec='k',fc="r")
ax.add_patch(a)
while z>y[int(points[i])]+1:
z=z-1
a.set_y(z)
yield a
y[int(points[i])]=z
filler_ani = animation.FuncAnimation(fig, lambda x: None, frames=filler,
interval=50, blit=False, repeat=False)
plt.show()
This is kind of hacky, but stays most closely to your initial code.

Related

Animating with matplotlib without animation function

Is there a way to animate a graph in matplotlib without resorting to the built in animation functions? I find them extremely awkward to use and feel it would be much simpler to just plot a point, wipe the graph, then plot the next point.
I envision something such as:
def f():
# do stuff here
return x, y, t
where each t would be a different frame.
I mean, I've tried stuff like using plt.clf(), plt.close() etc. but nothing seems to work.
It is sure possible to animate without FuncAnimation. The purpose of "the enivisioned function", however, is not really clear. In an animation, the time is the independent variable, i.e. for each time step you produce some new data to plot or similar. Therefore the function would take t as an input and give some data back.
import matplotlib.pyplot as plt
import numpy as np
def f(t):
x=np.random.rand(1)
y=np.random.rand(1)
return x,y
fig, ax = plt.subplots()
ax.set_xlim(0,1)
ax.set_ylim(0,1)
for t in range(100):
x,y = f(t)
# optionally clear axes and reset limits
#plt.gca().cla()
#ax.set_xlim(0,1)
#ax.set_ylim(0,1)
ax.plot(x, y, marker="s")
ax.set_title(str(t))
fig.canvas.draw()
plt.pause(0.1)
plt.show()
Also, it is not clear why you would want to avoid FuncAnimation. The same animation as above can be produced with FuncAnimation as follows:
import matplotlib.pyplot as plt
import matplotlib.animation
import numpy as np
def f(t):
x=np.random.rand(1)
y=np.random.rand(1)
return x,y
fig, ax = plt.subplots()
ax.set_xlim(0,1)
ax.set_ylim(0,1)
def update(t):
x,y = f(t)
# optionally clear axes and reset limits
#plt.gca().cla()
#ax.set_xlim(0,1)
#ax.set_ylim(0,1)
ax.plot(x, y, marker="s")
ax.set_title(str(t))
ani = matplotlib.animation.FuncAnimation(fig, update, frames=100)
plt.show()
There is not much changed, you have the same number of lines, nothing really awkward to see here.
Plus you have all the benefits from FuncAnimation when the animation gets more complex, when you want to repeat the animation, when you want to use blitting, or when you want to export it to a file.
it is not clear why you would want to avoid FuncAnimation.
For very simple tests, where you want to check a situation deep inside a loop, it is not easy to set up an animation function.
For instance, I wanted to visualize what happens with this strange sort algorithm: https://arxiv.org/pdf/2110.01111.pdf. To my opinion, the simplest way to do it is:
import numpy as np
import matplotlib.pyplot as plt
def sort(table):
n = len(table)
for i in range (n):
for j in range (n):
if table[i] < table[j]:
tmp = table[i]
table[i] = table[j]
table[j] = tmp
plt.plot(table, 'ro')
plt.title(f"i {i} j {j}")
plt.pause(0.001)
plt.clf() # clear figure
return table
n = 50
table = np.random.randint(1,101,n)
sort(table)
```python
I agree that FuncAnimation is awkward to use (not pythonic at all). Actually I believe this function doesn't make too much sense. What is the advantage to have it?
Yes, it introduces an implicit loop that you do not have to write yourself. But the reader cannot fully control this loop and -unless he knows the syntax of the function in advance- he cannot even understand it. Personally I avoid FuncAnimation for reasons of clarity and versatility. Here's a minimal pseudocode example to do that:
fig=plt.figure("animation")
M=zeros((sizeX,sizeY)) # initialize the data (your image)
im=plt.imshow(M) # make an initial plot
########### RUN THE "ANIMATION" ###########################
while {some condition}:
M=yourfunction() # updates your image
im.set_array(M) # prepare the new image
fig.canvas.draw() # draw the image
plt.pause(0.1) # slow down the "animation"
Very simple and you can see what is happening in your code.

Matplotlib multiple lines on graph

I've been having an issue with saving matplotlib graphs as images.
The images are saving differently from what shows up when I call the .show() method on the graph.
An example is here:
http://s1.postimg.org/lbyei5cfz/blue5.png
I'm not sure what else to do. I've spent the past hours trying to figure out what's causing it, but I can't figure it out.
Here is my code in it's entirety.
import matplotlib.pyplot as plt
import random
turn = 1 #for the x values
class Graph():
def __init__(self, name, color):
self.currentValue = 5 #for the y values
self.x = [turn]
self.y = [self.currentValue]
self.name = name
self.color = color
def update(self):
if random.randint(0,1): #just to show if the graph's value goes up or down
self.currentValue += random.randint(0,10)
self.y.append(self.currentValue)
else:
self.currentValue -= random.randint(0,10)
self.y.append(self.currentValue)
self.x.append(turn)
def plot(self):
lines = plt.plot(self.x,self.y)
plt.setp(lines, 'color',self.color)
plt.savefig(self.name + str(turn))
#plt.show() will have a different result from plt.savefig(args)
graphs = [Graph("red",'r'),Graph("blue",'b'),Graph("green",'g')]
for i in range(5):
for i in graphs:
i.update() #changes the x and y value
i.plot() #saves the picture of the graph
turn += 1
Sorry if this is a stupid mistake I'm making, I just find it peculiar how plt.show() and plt.savefig are different.
Thanks for the help.
As stated correctly by David, plt.show() resets current figure. plt.savefig(), however, does not, so you need to reset it explicitly. plt.clf() or plt.figure() are two functions that can do it dor you. Just insert the call right after plt.savefig:
plt.savefig(self.name + str(turn))
plt.clf()
If you want to save the figure after displaying it, you'll need to hold on to the figure instance. The reason that plt.savefig doesn't work after calling show is that the current figure has been reset.
pyplot keeps track of which figures, axes, etc are "current" (i.e. have not yet been displayed with show) behind-the-scenes. gcf and gca get the current figure and current axes instances, respectively. plt.savefig (and essentially any other pyplot method) just does plt.gcf().savefig(...). In other words, get the current figure instance and call its savefig method. Similarly plt.plot basically does plt.gca().plot(...).
After show is called, the list of "current" figures and axes is empty.
In general, you're better off directly using the figure and axes instances to plot/save/show/etc, rather than using plt.plot, etc, to implicitly get the current figure/axes and plot on it. There's nothing wrong with using pyplot for everything (especially interactively), but it makes it easier to shoot yourself in the foot.
Use pyplot for plt.show() and to generate a figure and an axes object(s), but then use the figure or axes methods directly. (e.g. ax.plot(x, y) instead of plt.plot(x, y), etc) The main advantage of this is that it's explicit. You know what objects you're plotting on, and don't have to reason about what the pyplot state-machine does (though it's not that hard to understand the state-machine interface, either).
As an example of the "recommended" way of doing things, do something like:
import numpy as np
import matplotlib.pyplot as plt
x = np.linspace(-1, 1, 100)
y = x**2
fig, ax = plt.subplots()
ax.plot(x, y)
fig.savefig('fig1.pdf')
plt.show()
fig.savefig('fig2.pdf')
If you'd rather use the pyplot interface for everything, then just grab the figure instance before you call show. For example:
import numpy as np
import matplotlib.pyplot as plt
x = np.linspace(-1, 1, 100)
y = x**2
plt.plot(x, y)
fig = plt.gcf()
fig.savefig('fig1.pdf')
plt.show()
fig.savefig('fig2.pdf')
source

Make a matplotlib animation, by clf()'ing each frame

i am trying to make the simplest matplotlib animation, using animation.FuncAnimation. I dont care about efficiency. i do not want to keep track of the plotted lines and update their data (in my desired application this would be annoying), i simply want to erase the plot before animating every frame. i thought somthing like this should work, but its not..
import matplotlib.animation as animation
fig = Figure()
def updatefig(i):
clf()
p = plot(rand(100))
draw()
anim = animation.FuncAnimation(fig, updatefig, range(10))
At least this seems to work:
import matplotlib.animation as animation
import matplotlib.pyplot as plt
import numpy as np
fig = plt.figure()
def updatefig(i):
fig.clear()
p = plt.plot(np.random.random(100))
plt.draw()
anim = animation.FuncAnimation(fig, updatefig, 10)
anim.save("/tmp/test.mp4", fps=1)
The issue with the original code is Figure written with a capital F (should be figure).
Otherwise, I would suggest not to use the pylab style "everything in the same namespace" approach with matplotlib. Also, using the object-oriented interface instead of plt.draw, plt.plot, etc. will save a lot of trouble later on.

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.

Updating the x-axis values using matplotlib animation

I am trying to use matplotlib.ArtistAnimation to animate two subplots. I want the x-axis to increase in value as the animation progresses, such that the total length of the animation is 100 but at any time the subplot is only presenting me with the time values from 0-24 and then iterates up to 100.
A great example is given here. The link uses FuncAnimation and updates the x-axis labels in a rolling fashion using plot().axes.set_xlim() and incrementing the x-values. The code is available via the link below the YouTube video in the link provided.
I have appended code below that shows my attempts to replicate these results but the x-limits seem to take on their final values instead of incrementing with time. I have also tried incrementing the solution (as opposed to the axis) by only plotting the values in the window that will be seen in the subplot, but that does not increment the x-axis values. I also tried to implement autoscaling but the x-axis still does not update.
I also found this question which is virtually the same problem, but the question was never answered.
Here is my code:
import matplotlib.pylab as plt
import matplotlib.animation as anim
import numpy as np
#create image with format (time,x,y)
image = np.random.rand(100,10,10)
#setup figure
fig = plt.figure()
ax1=fig.add_subplot(1,2,1)
ax2=fig.add_subplot(1,2,2)
#set up viewing window (in this case the 25 most recent values)
repeat_length = (np.shape(image)[0]+1)/4
ax2.set_xlim([0,repeat_length])
#ax2.autoscale_view()
ax2.set_ylim([np.amin(image[:,5,5]),np.amax(image[:,5,5])])
#set up list of images for animation
ims=[]
for time in xrange(np.shape(image)[0]):
im = ax1.imshow(image[time,:,:])
im2, = ax2.plot(image[0:time,5,5],color=(0,0,1))
if time>repeat_length:
lim = ax2.set_xlim(time-repeat_length,time)
ims.append([im, im2])
#run animation
ani = anim.ArtistAnimation(fig,ims, interval=50,blit=False)
plt.show()
I only want the second subplot (ax2) to update the x-axis values.
Any help would be much appreciated.
If you don't need blitting
import matplotlib.pylab as plt
import matplotlib.animation as animation
import numpy as np
#create image with format (time,x,y)
image = np.random.rand(100,10,10)
#setup figure
fig = plt.figure()
ax1 = fig.add_subplot(1,2,1)
ax2 = fig.add_subplot(1,2,2)
#set up viewing window (in this case the 25 most recent values)
repeat_length = (np.shape(image)[0]+1)/4
ax2.set_xlim([0,repeat_length])
#ax2.autoscale_view()
ax2.set_ylim([np.amin(image[:,5,5]),np.amax(image[:,5,5])])
#set up list of images for animation
im = ax1.imshow(image[0,:,:])
im2, = ax2.plot([], [], color=(0,0,1))
def func(n):
im.set_data(image[n,:,:])
im2.set_xdata(np.arange(n))
im2.set_ydata(image[0:n, 5, 5])
if n>repeat_length:
lim = ax2.set_xlim(n-repeat_length, n)
else:
# makes it look ok when the animation loops
lim = ax2.set_xlim(0, repeat_length)
return im, im2
ani = animation.FuncAnimation(fig, func, frames=image.shape[0], interval=30, blit=False)
plt.show()
will work.
If you need to run faster, you will need to play games with the bounding box used for blitting so that the axes labels are updated.
If you are using blitting, you can call pyplot.draw() to redraw the entire figure, each time you change y/x axis.
This updates whole figure, so is relatively slow, but it's acceptable if you don't call it many items.
This moves your axis, but is very slow.
import matplotlib.pylab as plt
import matplotlib.animation as anim
import numpy as np
image = np.random.rand(100,10,10)
repeat_length = (np.shape(image)[0]+1)/4
fig = plt.figure()
ax1 = ax1=fig.add_subplot(1,2,1)
im = ax1.imshow(image[0,:,:])
ax2 = plt.subplot(122)
ax2.set_xlim([0,repeat_length])
ax2.set_ylim([np.amin(image[:,5,5]),np.amax(image[:,5,5])])
im2, = ax2.plot(image[0:0,5,5],color=(0,0,1))
canvas = ax2.figure.canvas
def init():
im = ax1.imshow(image[0,:,:])
im2.set_data([], [])
return im,im2,
def animate(time):
time = time%len(image)
im = ax1.imshow(image[time,:,:])
im2, = ax2.plot(image[0:time,5,5],color=(0,0,1))
if time>repeat_length:
print time
im2.axes.set_xlim(time-repeat_length,time)
plt.draw()
return im,im2,
ax2.get_yaxis().set_animated(True)
# call the animator. blit=True means only re-draw the parts that have changed.
animate = anim.FuncAnimation(fig, animate, init_func=init,
interval=0, blit=True, repeat=True)
plt.show()

Categories