Note: only the last code chunk brings an error. The earlier chunks are to give context to the animation that I want. This is all in Jupyter for Windows.
I have a matplotlib pyplot with two line segments, one is "wrong" and the other is "right." I want to animate the graph to start with both lines where the blue "wrong" line is,and have the red one pivot and move to be in the right place. The "wrong" line goes from (x,y) = (-1.25,9.1) to (0.75,8.0). The "right" line goes from (-1.25,9.7) to (0.75,7.5)
Here is the code for the static comparison:
import matplotlib.pyplot as plt
import numpy as np
from matplotlib.animation import FuncAnimation
boring_fig = plt.figure()
blue = plt.plot([-1.25,.75], [9.1,8], color = 'b', label = 'wrong')
red = plt.plot([-1.25,.75], [9.7,7.5], color = 'r', label = 'right')
plt.show()
Now I want to start them both where the blue line is, and then have the red line incrementally move to the correct position. I made these two arrays for y coordinates to incrementally change between lines. Then I graph everything but the red line, in hopes of adding it as an animation after this.
y_left = np.array([9.1, 9.16, 9.22, 9.28, 9.34, 9.4, 9.46, 9.52, 9.58, 9.64, 9.7])
y_right = np.array([8.0, 7.95, 7.9, 7.85, 7.8, 7.75, 7.7, 7.65, 7.6, 7.55, 7.5])
fig = plt.figure()
blue = plt.plot([-1.25,.75], [9.1,8], color = 'b', label = 'wrong')
plt.show()
And then I try to animate the red line segment shifting along those incremented y values. I get the error somewhere in here:
def animate_1(i):
return plt.plot([-1.25,.75], [y_left[i],y_right[i]], color = 'r'),
anim = FuncAnimation(fig = fig, func = animate_1, interval = 100, frames = 10)
plt.show(anim)
And I get this message: "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."
I spent hours trying to figure this out, but I am too much of a noob. Please help.
It turns out I had to install ffmpeg for windows:
https://www.wikihow.com/Install-FFmpeg-on-Windows
Now the graph shows up but I can't see it animating. Oh well. That is an entirely different question.
here is the end graph I made (with a couple of extra details)
Add this line to the beginning of your code in order to see the animation work:
%matplotlib notebook
In Jupyter Notebook (at least on Windows) specifying the GUI toolkit will allow you to see the animation updating, not just a static graph. You can read more about toolkits and user interfaces here.
If there is a message that says:
Warning: Cannot change to a different GUI toolkit: ...
then restart the Jupyter kernel and run the cell again to update the GUI. For troubleshooting the GUI, see this answer.
In regards to the original question, if you set blit = False in FuncAnimation then the animation should display. Otherwise, you can add a return blue, statement to the animate function. See my code below and feel free to ask questions!
Complete Code
%matplotlib notebook
import matplotlib.pyplot as plt
import numpy as np
from matplotlib.animation import FuncAnimation
def move_line(num_frames):
x_vals = [-1.25,.75]
y_left_array = np.linspace(9.1, 9.7, num=num_frames, endpoint=True)
y_right_array = np.linspace(8.0, 7.5, num=num_frames, endpoint=True)
fig = plt.figure()
red = plt.plot(x_vals, [9.7,7.5], color = 'r', label = 'right')
blue, = plt.plot([], [], color = 'b', label = 'wrong')
def animate(I):
y_vals = [y_left_array[i],y_right_array[I]]
blue.set_data(x_vals,y_vals)
return blue,
anim = FuncAnimation(
fig,
animate,
frames = num_frames,
interval = 1000/30,
repeat = False,
blit = True
)
plt.show()
return anim
animate_lines = move_line(100)
Related
Please consider this code. I use it for generating two figures.
The first figure is generated with the code exactly as it follows, while the second includes the two commented lines for changing the colour of xtick and ytick.
If I save the two figures, I get what I expect. However, the "previews" in the Jupyter notebook are different. See below.
from cycler import cycler
import matplotlib as mpl
from matplotlib import rc
import matplotlib.pyplot as plt
import numpy as np
%matplotlib inline
# rc("backend", "pdf")
rc("font", **{"family": "sans-serif", "sans-serif":["Helvetica"], "size":8})
## for Palatino and other serif fonts use:
rc("font", **{"family":"serif", "serif":["Palatino"], "size":8})
rc("text", usetex=True)
rc("figure", **{"dpi": 300})
rc("lines", **{"linewidth": 0.5, "markersize": 2})
rc("axes", **{"edgecolor": "gray", "facecolor":"white",
"linewidth": 0.3, "labelsize": 8, "labelcolor": "gray",
"grid": True})
rc("grid", **{"color": "gray", "linestyle": ":", "linewidth": 0.1})
rc("legend", **{"markerscale": 0.7, "fontsize": 6, "framealpha": 0.9, "frameon":True, "edgecolor": "lightgray"})
# rc("xtick", **{"color": "gray"})
# rc("ytick", **{"color": "gray"})
color_c = cycler("color", ["k"])
style_c = cycler("linestyle", ["-", "--", ":", "-."])
marker_c = cycler("marker", ["", ".", "o"])
cycler_cms = color_c * marker_c * style_c
image_width = 2.5
image_height = image_width / 1.618
# test style_cms
plt.rc("axes", prop_cycle=cycler_cms)
fig, ax = plt.subplots(figsize=(image_width, image_height))
n_lines = 4 # len(cycler_cms)
x = np.linspace(0, 8, 101)
y = np.cos(np.arange(n_lines)+x[:,None])
ax.plot(x, y)
ax.legend([f"$line_{{{i}}}$" for i in range(n_lines)])
The following is obtained with the two lines commented out.
The following is obtained setting to "gray" the colour property of xtick and ytick
For comparison, this is the saved file corresponding to the second figure (with the dark background):
The Jupyter notebook is run inside of the latest version of VSC, with all the pluings updated.
Is there any way to have the "preview" equal to the what I save? May be a specific backend, I don't know... I tried a few...
PS.
I know I can solve this specific issue by setting figure.facecolor to white, but my question concerns how to get the exact previews of what I will save for any rc parameters.
I'm trying to monitor real-time data with matplotlib.
I found that I can update plot dynamically with interactive mode in Pyplot.
And it worked well, but one problem is 'I cannot manipulate the figure window at all'. For example, move or re-size the figure window.
Here is my code.
This is cons of interactive mode? or I'm using it incorrectly?
import matplotlib.pyplot as plt
import time
import math
# generate data
x = [0.1*_a for _a in range(1000)]
y = map(lambda x : math.sin(x), x)
# interactive mode
plt.ion() # identical plt.interactive(True)
fig, ax = plt.subplots()
# ax = plt.gca()
lines, = ax.plot([], [])
# ax.set_ylim(-1, 1)
ax.grid()
MAX_N_DATA = 100
x_data = []
y_data = []
for i in range(len(x)):
# New data received
x_data.append(x[i])
y_data.append(y[i])
# limit data length
if x_data.__len__() > MAX_N_DATA:
x_data.pop(0)
y_data.pop(0)
# Set Data
lines.set_xdata(x_data)
lines.set_ydata(y_data)
# The data limits are not updated automatically.
ax.relim()
# with tight True, graph flows smoothly.
ax.autoscale_view(tight=True, scalex=True, scaley=True)
# draw
plt.draw()
time.sleep(0.01)
Thank you.
As shown in this answer to another question, replace plt.draw() with plt.pause(0.05). This solved the problem for me.
Although I still think you should use bokeh, I'll tell you how to do it with matplotlib.
The problem why it won't work ist that matplotlib's event loop is not active and therefore it cannot digest window events (like close or resize). Unfortunately it is not possible to trigger this digestion from the outside. What you have to do is to use matplotlib's animation system.
Your code is actually quite well prepared for it so you can use FuncAnimation.
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation
import math
# generate data
x = [0.1*_a for _a in range(1000)]
y = map(lambda x : math.sin(x), x)
# don't need ion, we're using block=True (see end of code)
fig, ax = plt.subplots()
fig.show()
# ax = plt.gca()
lines, = ax.plot([], [])
# ax.set_ylim(-1, 1)
ax.grid()
MAX_N_DATA = 100
x_data = []
y_data = []
def showdata(i):
# New data received
x_data.append(x[i])
y_data.append(y[i])
# limit data length
if x_data.__len__() > MAX_N_DATA:
x_data.pop(0)
y_data.pop(0)
# Set Data
lines.set_xdata(x_data)
lines.set_ydata(y_data)
# The data limits are not updated automatically.
ax.relim()
# with tight True, graph flows smoothly.
ax.autoscale_view(tight=True, scalex=True, scaley=True)
# draw will be called by the animation system
# instead of time.sleep(0.01) we use an update interval of 10ms
# which has the same effect
anim = FuncAnimation(fig, showdata, range(len(x)), interval=10, repeat=False)
# start eventloop
plt.show(block=True)
I am writing a program for 2D FDTD light propagation, in this code, when I run the program with ax.imshow() command in the animate function, the program works fine whereas when I use the im.set_data() command, it gives me a blank image. Can somebody please tell me what am I doing wrong? Also, can somebody tell me how to set the colormap at the beginning so that I dont have to update it during the animation loop. The point is I don't want the imshow() command to draw everything everytime the loop is run.
Thanks for all the help. I am learning programming please suggest me what to do.
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as animation
xdim = 100
ydim = 100
epsilon = np.ones([xdim,ydim])*8.854187817*10**(-12)
mu = np.ones([xdim,ydim])*4*np.pi*10**(-7)
c = 299792458
delta = 10**-6
deltat = delta/(c*(2**0.5))
Hz = np.zeros([xdim,ydim])
Ey = np.zeros([xdim,ydim])
Ex = np.zeros([xdim,ydim])
fig = plt.figure()
ax = plt.axes()
im = ax.imshow(Hz)
Hz[xdim/2,ydim/2]=1
def init():
im.set_data(np.zeros(Hz.shape))
return
def animate(n, *args, **kwargs):
Ex[0:xdim-1,0:ydim-1]=Ex[0:xdim-1,0:ydim-1]+(deltat/(delta*mu[0:xdim-1,0:ydim-1]))*(Hz[1:xdim,0:ydim-1]-Hz[0:xdim-1,0:ydim-1])
Ey[0:xdim-1,0:ydim-1]=Ey[0:xdim-1,0:ydim-1]-(deltat/(delta*mu[0:xdim-1,0:ydim-1]))*(Hz[0:xdim-1,1:ydim]-Hz[0:xdim-1,0:ydim-1])
Hz[1:xdim,1:ydim]=Hz[1:xdim,1:ydim]+(deltat/(delta*epsilon[1:xdim,1:ydim]))*(Ex[1:xdim,1:ydim]-Ex[0:xdim-1,1:ydim]-Ey[1:xdim,1:ydim]+Ey[1:xdim,0:ydim-1])
if(n==0):Hz[xdim/2,ydim/2]=0
#im.set_data(Hz)
ax.imshow(Hz) # Delete this command and try running the program with the above command.
return
ani = animation.FuncAnimation(fig, animate, init_func=init, frames = 200, interval = 10, blit = False, repeat = False)
fig.show()
Actually, your first version was working just fine also. The problem was that because im is initialized with an array of zeros, the vmin and vmax for the colorscale were both zero. Updates to im after that using set_data did not update vmin and vmax, whereas ax.imshow automatically rescales the color ranges. If you set the color ranges at the beginning to something reasonable, it works fine:
ax.imshow(Hz, vmin=-0.2, vmax=0.2)
That's the only thing you need to change from the code in the question to make it work (with im.set_data in the animation function).
I got the program to work by making a few changes, though i cannot understand why it is not working the way i wrote it in the question. Here is what i changed:
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as animation
xdim = 100
ydim = 100
epsilon = np.ones([xdim,ydim])*8.854187817*10**(-12)
mu = np.ones([xdim,ydim])*4*np.pi*10**(-7)
c = 299792458
delta = 10**-6
deltat = delta/(c*(2**0.5))
Hz = np.zeros([xdim,ydim])
Ey = np.zeros([xdim,ydim])
Ex = np.zeros([xdim,ydim])
Hz[xdim/2,ydim/2]=1
def init():
global fig, ax, im
fig = plt.figure()
ax = plt.axes()
im = ax.imshow(Hz, cmap="jet")
im.set_data(np.zeros(Hz.shape))
return
def animate(n):
Ex[0:xdim-1,0:ydim-1]=Ex[0:xdim-1,0:ydim-1]+(deltat/(delta*mu[0:xdim-1,0:ydim-1]))*(Hz[1:xdim,0:ydim-1]-Hz[0:xdim-1,0:ydim-1])
Ey[0:xdim-1,0:ydim-1]=Ey[0:xdim-1,0:ydim-1]-(deltat/(delta*mu[0:xdim-1,0:ydim-1]))*(Hz[0:xdim-1,1:ydim]-Hz[0:xdim-1,0:ydim-1])
Hz[1:xdim,1:ydim]=Hz[1:xdim,1:ydim]+(deltat/(delta*epsilon[1:xdim,1:ydim]))*(Ex[1:xdim,1:ydim]-Ex[0:xdim-1,1:ydim]-Ey[1:xdim,1:ydim]+Ey[1:xdim,0:ydim-1])
if(n==0):Hz[xdim/2,ydim/2]=0
im.set_data(Hz)
return
init()
ani = animation.FuncAnimation(fig, animate, frames = 500, interval = 10, blit = False, repeat = False)
fig.show()
I try to generate a movie using the matplotlib movie writer. If I do that, I always get a white margin around the video. Has anyone an idea how to remove that margin?
Adjusted example from http://matplotlib.org/examples/animation/moviewriter.html
# This example uses a MovieWriter directly to grab individual frames and
# write them to a file. This avoids any event loop integration, but has
# the advantage of working with even the Agg backend. This is not recommended
# for use in an interactive setting.
# -*- noplot -*-
import numpy as np
import matplotlib
matplotlib.use("Agg")
import matplotlib.pyplot as plt
import matplotlib.animation as manimation
FFMpegWriter = manimation.writers['ffmpeg']
metadata = dict(title='Movie Test', artist='Matplotlib',
comment='Movie support!')
writer = FFMpegWriter(fps=15, metadata=metadata, extra_args=['-vcodec', 'libx264'])
fig = plt.figure()
ax = plt.subplot(111)
plt.axis('off')
fig.subplots_adjust(left=None, bottom=None, right=None, wspace=None, hspace=None)
ax.set_frame_on(False)
ax.set_xticks([])
ax.set_yticks([])
plt.axis('off')
with writer.saving(fig, "writer_test.mp4", 100):
for i in range(100):
mat = np.random.random((100,100))
ax.imshow(mat,interpolation='nearest')
writer.grab_frame()
Passing None as an arguement to subplots_adjust does not do what you think it does (doc). It means 'use the deault value'. To do what you want use the following instead:
fig.subplots_adjust(left=0, bottom=0, right=1, top=1, wspace=None, hspace=None)
You can also make your code much more efficent if you re-use your ImageAxes object
mat = np.random.random((100,100))
im = ax.imshow(mat,interpolation='nearest')
with writer.saving(fig, "writer_test.mp4", 100):
for i in range(100):
mat = np.random.random((100,100))
im.set_data(mat)
writer.grab_frame()
By default imshow fixes the aspect ratio to be equal, that is so your pixels are square. You either need to re-size your figure to be the same aspect ratio as your images:
fig.set_size_inches(w, h, forward=True)
or tell imshow to use an arbitrary aspect ratio
im = ax.imshow(..., aspect='auto')
I searched all day for this and ended up using this solution from #matehat when creating each image.
import matplotlib.pyplot as plt
import matplotlib.animation as animation
To make a figure without the frame :
fig = plt.figure(frameon=False)
fig.set_size_inches(w,h)
To make the content fill the whole figure
ax = plt.Axes(fig, [0., 0., 1., 1.])
ax.set_axis_off()
fig.add_axes(ax)
Draw the first frame, assuming your movie is stored in 'imageStack':
movieImage = ax.imshow(imageStack[0], aspect='auto')
I then wrote an animation function:
def animate(i):
movieImage.set_array(imageStack[i])
return movieImage
anim = animation.FuncAnimation(fig,animate,frames=len(imageStack),interval=100)
anim.save('myMovie.mp4',fps=20,extra_args=['-vcodec','libx264']
It worked beautifully!
Here is the link to the whitespace removal solution:
1: remove whitespace from image
In a recent build of matplotlib, it looks like you can pass arguments to the writer:
def grab_frame(self, **savefig_kwargs):
'''
Grab the image information from the figure and save as a movie frame.
All keyword arguments in savefig_kwargs are passed on to the 'savefig'
command that saves the figure.
'''
verbose.report('MovieWriter.grab_frame: Grabbing frame.',
level='debug')
try:
# Tell the figure to save its data to the sink, using the
# frame format and dpi.
self.fig.savefig(self._frame_sink(), format=self.frame_format,
dpi=self.dpi, **savefig_kwargs)
except RuntimeError:
out, err = self._proc.communicate()
verbose.report('MovieWriter -- Error running proc:\n%s\n%s' % (out,
err), level='helpful')
raise
If this was the case, you could pass bbox_inches="tight" and pad_inches=0 to grab_frame -> savefig and this should remove most of the border. The most up to date version on Ubuntu however, still has this code:
def grab_frame(self):
'''
Grab the image information from the figure and save as a movie frame.
'''
verbose.report('MovieWriter.grab_frame: Grabbing frame.',
level='debug')
try:
# Tell the figure to save its data to the sink, using the
# frame format and dpi.
self.fig.savefig(self._frame_sink(), format=self.frame_format,
dpi=self.dpi)
except RuntimeError:
out, err = self._proc.communicate()
verbose.report('MovieWriter -- Error running proc:\n%s\n%s' % (out,
err), level='helpful')
raise
So it looks like the functionality is being put in. Grab this version and give it a shot!
If you "just" want to save a matshow/imshow rendering of a matrix without axis annotation then newest developer version of scikit-video (skvideo) may also be relevant, - if you have avconv installed. An example in the distribution shows a dynamic image constructed from numpy function: https://github.com/aizvorski/scikit-video/blob/master/skvideo/examples/test_writer.py
Here is my modification of the example:
# Based on https://github.com/aizvorski/scikit-video/blob/master/skvideo/examples/test_writer.py
from __future__ import print_function
from skvideo.io import VideoWriter
import numpy as np
w, h = 640, 480
checkerboard = np.tile(np.kron(np.array([[0, 1], [1, 0]]), np.ones((30, 30))), (30, 30))
checkerboard = checkerboard[:h, :w]
filename = 'checkerboard.mp4'
wr = VideoWriter(filename, frameSize=(w, h), fps=8)
wr.open()
for frame_num in range(300):
checkerboard = 1 - checkerboard
image = np.tile(checkerboard[:, :, np.newaxis] * 255, (1, 1, 3))
wr.write(image)
print("frame %d" % (frame_num))
wr.release()
print("done")
I'm trying to animate two subplots, each with multiple lines. I am using Matplotlib, and I am using the FuncAnimation, which is used by many of the animation examples.
Using animation:
If I try to animate it, I only get the result of the first frame:
Without using animation:
If I manually call my update_lines function, it works fine.
Code:
Below is the full code (uncommenting the 3 indicated lines in main() works, but I would like to see it update in real-time, hence trying to use the animation).
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation
def make_subplots():
def setup_axes(axes):
for ax in axes:
ax.set_xbound(0, 100) # bound will change as needed.
ax.set_ylim(0, 1) # limit won't change automatically.
def make_lines(axes):
labels = ('a', 'b', 'c')
lines = []
for ax in axes:
ax_lines = []
for label in labels:
x, y = [0], [0]
line, = ax.plot(x, y, label=label) # comma for unpacking.
ax_lines.append((line, x, y))
lines.append(ax_lines)
return lines
fig, axes = plt.subplots(2, 1, sharex=True, sharey=True)
lines = make_lines(axes)
setup_axes(axes)
return fig, axes, lines
def make_data():
for i in xrange(100):
print 'make_data():', i
data = dict()
for label in ('a', 'b', 'c'):
from random import random
data[label] = random()
yield (i + 1, data)
def update_lines(data, lines):
print 'update_lines():', data, lines
updated_lines = []
for ax_lines in lines:
for line, x, y in ax_lines:
label = line.get_label()
x.append(data[0])
y.append(data[1][label])
line.set_data(x, y)
updated_lines.append(line)
def main():
fig, axes, lines = make_subplots()
# Uncomment these 3 lines, and it works!
# new_data = make_data()
# for data in new_data:
# update_lines(data, lines)
FuncAnimation(fig=fig,
func=update_lines,
frames=make_data,
fargs=(lines,),
interval=10,
blit=False)
plt.show()
if __name__ == '__main__':
main()
(Undocumented?) Hooks
So, I was digging around the source-code of matplotlib.animation.Animation, and I noticed these lines in the __init__() function:
# Clear the initial frame
self._init_draw()
# Instead of starting the event source now, we connect to the figure's
# draw_event, so that we only start once the figure has been drawn.
self._first_draw_id = fig.canvas.mpl_connect('draw_event', self._start)
Sounds familiar...
This looks right so far. The self._init_draw() call draws my first frame immediately. Then the animation-object hooks into the figure-object and waits for the figure to be shown before attempting to draw any more frames for the animation.
Eureka!
The keyword is: animation-object. Since I wasn't planning on using the animation instance later (for example, to draw a movie), I didn't assign it to a variable. In fact, I was being yelled at by pyflakes because Local variable '...' is assigned to but never used.
But because all of the functionality relies on the hook, when the canvas is finally shown I presume Python's garbage collection has removed the Animation instance---since it was never assigned to a variable---and therefore the animation can never be started.
The fix
Simply assign the instance FuncAnimation instance to a variable, and everything works as expected!
anim = FuncAnimation(fig=fig,
func=update_lines,
frames=make_data,
fargs=(lines,),
interval=10,
blit=False)