I'm working on a script that animates the process of drawing a shape and saves the animation to a GIF. However, when I call plt.show() after saving the animation, the popup window only displays the last frame of the animation. Here's the current code (hex_draw is just the module providing the drawing functions I use):
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation,PillowWriter
import hex_draw as hdraw
import json
def animate(frame,x_vals,y_vals,scale):
plt.plot(x_vals[frame],y_vals[frame],'go',ms=1.5*scale)
def main():
ax = plt.figure(figsize=(4,4)).add_axes([0,0,1,1])
plt.gca().axis("off")
ax.set_aspect("equal")
plot_data = hdraw.convert_to_points("qaq","east",settings)
hdraw.plot_monochrome(plot_data,settings)
ani = FuncAnimation(plt.gcf(),
func=animate,
fargs=[*plot_data[:3]],
frames=len(plot_data[0]),
interval=800,
repeat=False)
ani.save("test_anim.gif",dpi=100,writer=PillowWriter(fps=1))
plt.show()
with open("settings.json",mode="r") as file:
settings = json.load(file)
main()
Removing the line that saves the animation to a GIF causes the popup to animate properly, so that's definitely the cause of the problem. I've tried setting repeat to True in the FuncAnimation call, and that doesn't fix anything.
Related
I was trying the first simple animation from this page. I am clearly a beginner at making animations. I paste the code below.
from matplotlib import pyplot as plt
from celluloid import Camera
fig = plt.figure()
camera = Camera(fig)
for i in range(10):
plt.plot([i] * 10)
camera.snap()
animation = camera.animate()
It gives me the following error.
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.
As far as I can see, animate() has already been assigned a name. Could anyone resolve this issue for me?
To avoid this error (in fact is just a UserWarning), you have to display or save the animation. At the bottom of your code:
from matplotlib import pyplot as plt
from celluloid import Camera
fig = plt.figure()
camera = Camera(fig)
for i in range(10):
plt.plot([i] * 10)
camera.snap()
animation = camera.animate()
plt.show()
# OR
animation.save('test.mp4')
Edit: My question is not in regards to an "animation" per se. My question here, is simply about how to continuously show, a new inline image, in a for loop, within an Ipython notebook.
In essence, I would like to show an updated image, at the same location, inline, and have it update within the loop to show. So my code currently looks something like this:
import numpy as np
import matplotlib
import matplotlib.pyplot as plt
from IPython import display
%matplotlib inline
fig, ax = plt.subplots(nrows = 1, ncols = 1, figsize=(10, 10))
for ii in xrange(10):
im = np.random.randn(100,100)
ax.cla()
ax.imshow(im, interpolation='None')
ax.set_title(ii)
plt.show()
The problem is that this currently just..., well, shows the first image, and then it never changes.
Instead, I would like it to simply show the updated image at each iteration, inline, at the same place. How do I do that? Thanks.
I am not sure that you can do this without animation. Notebooks capture the output of matplotlib to include in the cell once the plotting is over. The animation framework is rather generic and covers anything that is not a static image. matplotlib.animation.FuncAnimation would probably do what you want.
I adapted your code as follows:
%matplotlib notebook
import matplotlib.pyplot as plt
import numpy as np
import matplotlib.animation
f = plt.figure()
ax = f.gca()
im = np.random.randn(100,100)
image = plt.imshow(im, interpolation='None', animated=True)
def function_for_animation(frame_index):
im = np.random.randn(100,100)
image.set_data(im)
ax.set_title(str(frame_index))
return image,
ani = matplotlib.animation.FuncAnimation(f, function_for_animation, interval=200, frames=10, blit=True)
Note: You must restart the notebook for the %matplotlib notebook to take effect and use a backend that supports animation.
EDIT: There is normally a way that is closer to your original question but it errors on my computer. In the example animation_demo there is a plain "for loop" with a plt.pause(0.5) statement that should also work.
You can call figure.canvas.draw() each time you append something new to the figure. This will refresh the plot (from here). Try:
import numpy as np
import matplotlib
matplotlib.use('TkAgg')
import matplotlib.pyplot as plt
from IPython import display
from time import sleep
fig = plt.figure()
ax = fig.gca()
fig.show()
for ii in range(10):
im = np.random.randn(100, 100)
plt.imshow(im, interpolation='None')
ax.set_title(ii)
fig.canvas.draw()
sleep(0.1)
I could not test this in an IPython Notebook, however.
I have a bunch of PNGs that I want to turn into an animation. I use matplotlib for this. I can display the resulting animation on the screen and everything looks fine. But the saved MP4 is just a blank. I mean I can play it, but it just shows a white, featureless window. Any ideas what I'm doing wrong? Here's the code:
import matplotlib
matplotlib.use('TKAgg')
import pylab as pyl
import matplotlib.pyplot as pplt
import matplotlib.image as mplimg
import matplotlib.animation as mplanim
myimages = []
for k in range(1,100):
fname = "data{0:03d}.png".format(k)
img = mplimg.imread(fname)
imgplot = pplt.imshow(img)
myimages.append([imgplot])
fig = pyl.figure()
myanim = mplanim.ArtistAnimation(fig, myimages, interval=20,
blit=True, repeat_delay=1000)
myanim.save("anim.mp4", fps=10)
pyl.show()
UPDATE: Moving fig = pyl.figure() to the top of the code, right after the imports solves the problem. If anyone knows why, feel free to tell! Thanks.
Correct this line:
myimages.append([imgplot])
into:
myimages.append(imgplot)
and see if it works.
If I create an Axes object in matplotlib and mutate it (i.e. by plotting some data) and then I call a function without passing my Axes object to that function then that function can still mutate my Axes. For example:
import matplotlib.pyplot as plt
import numpy as np
def innocent_looking_function():
#let's draw a red line on some unsuspecting Axes!
plt.plot(100*np.random.rand(20), color='r')
fig, ax = plt.subplots()
ax.plot(100*np.random.rand(20), color='b') #draw blue line on ax
#ax now has a blue line, as expected
innocent_looking_function()
#ax now unexpectedly has a blue line and a red line!
My question is: can I prevent this global-variable behaviour in general? I know I can call plt.close() before calling any innocent_looking_function() but is there some way to make this the default?
Sure! What you need to do is bypass the pyplot state machine entirely when you make your figure.
It's more verbose, as you can't just call fig = plt.figure().
First off, let me explain how plt.gca() or plt.gcf() works. When using the pyplot interface, matplotlib stores all created-but-not-displayed figure managers. Figure managers are basically the gui wrapper for a figure.
plt._pylab_helpers.Gcf is the singleton object that stores the figure managers and keeps track of which one is currently active. plt.gcf() returns the active figure from _pylab_helpers.Gcf. Each Figure object keeps track of it's own axes, so plt.gca() is just plt.gcf().gca().
Normally, when you call plt.figure(), it:
Creates the figure object that's returned
Creates a FigureManager for that figure using the appropriate backend
The figure manager creates a FigureCanvas, gui window (as needed), and NavigationToolbar2 (zoom buttons, etc)
The figure manager instance is then added to _pylab_helpers.Gcf's list of figures.
It's this last step that we want to bypass.
Here's a quick example using a non-interactive backend. Note that because we're not worried about interacting with the plot, we can skip the entire figure manager and just create a Figure and FigureCanvas instance. (Technically we could skip the FigureCanvas, but it will be needed as soon as we want to save the plot to an image, etc.)
import matplotlib.backends.backend_agg as backend
from matplotlib.figure import Figure
# The pylab figure manager will be bypassed in this instance. `plt.gca()`
# can't access the axes created here.
fig = Figure()
canvas = backend.FigureCanvas(fig)
ax = fig.add_subplot(111)
Just to prove that gca can't see this axes:
import matplotlib.pyplot as plt
import matplotlib.backends.backend_agg as backend
from matplotlib.figure import Figure
# Independent figure/axes
fig = Figure()
canvas = backend.FigureCanvas(fig)
ax = fig.add_subplot(111)
ax.plot(range(10))
# gca() is completely unaware of this axes and will create a new one instead:
ax2 = plt.gca()
print 'Same axes?:', id(ax) == id(ax2)
# And `plt.show()` would show the blank axes of `ax2`
With an interactive backed, it's a touch more complicated. You can't call plt.show(), so you need to start the gui's mainloop yourself. You can do it all "from scratch" (see any of the "embedding matplotlib" examples), but the FigureManager abstracts the backed-specific parts away:
As an example using the TkAgg backend:
import matplotlib.backends.backend_tkagg as backend
from matplotlib.figure import Figure
fig = Figure()
ax = fig.add_subplot(111)
manager = backend.new_figure_manager_given_figure(1, fig)
manager.show()
backend.show.mainloop()
To use one of the other backends, just change the backend import. For example, for Qt4:
import matplotlib.backends.backend_qt4agg as backend
from matplotlib.figure import Figure
fig = Figure()
ax = fig.add_subplot(111)
manager = backend.new_figure_manager_given_figure(1, fig)
manager.show()
backend.show.mainloop()
This actually even works with the nbagg backend used in IPython notebooks. Just change the backend import to import matplotlib.backends.backend_nbagg as backend
New to matplotlib and trying to explore existing data by iterating through a DataFrame via animation, but it seems very slow, can anyone see what I'm doing wrong or suggest improvements, have tried playing with frame speed but has little effect so I think its my code, would like to view this 2000 row object in 15 sec give or take. box is 8gb linex so should be fine, using ipython pop out figure to plot.
from pandas import *
import numpy as np
import matplotlib.pyplot as plt
from matplotlib import animation
coef_mean = DataFrame(np.random.rand(2000,50))
def animate(f_frame):
plt.cla()
plt.plot(coef_mean.columns.values, coef_mean.ix[f_frame])
plt.ylim(f_coef_min,f_coef_max)
fig = plt.figure(figsize=(9,5))
f_coef_min, f_coef_max = coef_mean.min().min()-.02, coef_mean.max().max()+.02
anim = animation.FuncAnimation(fig, animate, frames=150)
plt.show()
any ideas out there what I have done wrong ? many thanks, LW
also to get the popout figure try using
%matplotlib qt
You don't need to replot inside the animation function. Instead, you should just update the data of the plot. In your case something like this should work:
fig, ax = plt.subplots()
custom_plot, = ax.plot(coef_mean.columns.values, coef_mean.ix[0])
ax.set_ylim(f_coef_min,f_coef_max)
def animate(f_frame):
custom_plot.set_ydata(coef_mean.ix[f_frame])
return custom_plot,
Look at some animation examples for more information. E.g:
http://matplotlib.org/examples/animation/simple_anim.html