how can I keep the last frame when I use matplotlib animation function with blit = True - animation

I have to set blit = true, since the plotting is much faster. But after animation (repeat = false), if I use zoom in the figure, the figure will just disappear. I need to keep the last frame so that I can zoom in the last figure.
Thanks!

One work around is to initialize the animation using the last frame. The obvious down side is you have to precompute the last frame. Adapting this example would be
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as animation
fig, ax = plt.subplots()
x = np.arange(0, 2*np.pi, 0.01)
line, = ax.plot(x, np.sin(x))
cnt = 50 # Define so we know what the last frame will be.
def init():
# Note the function is the same as `animate` with `i` set to the last value
line.set_ydata(np.sin(x + cnt / 100))
return line,
def animate(i):
line.set_ydata(np.sin(x + i / 100)) # update the data.
return line,
ani = animation.FuncAnimation(
fig, animate, init_func=init, interval=2, blit=True, save_count=cnt)
ani.save("mwe.mov")
fig.savefig("mwe.png")

Related

What is the fastest way to draw circles in python and save as png?

Background
I'm optimizing a project.
Profiling the code I found that 50% of the time is spend on a function in which a set of circles (different radii, colors and locations) are drawn to a choosen sector of fixed size (white canvas) if their center corrdinates is within the sector bounds. Depending on the usage the function saves the figure as a png and returns the path or returns the image as an numpy array.
The build-in method matplotlib._png.write_png from savefig is the most expensive But there is also some overhead from creating the figures, etc.
Generally the code is used with multiprocessing / parallel programming.
Example output
Code
import matplotlib.pyplot as plt
import cv2
import os
def get_top_view(sector, circles, file_path, save_image_flag):
# get the sector bounds.
x_low, y_low, x_high, y_high = get_sector_bounds(sector)
# init figure
fig, ax = plt.subplots()
ax.set_xlim(y_low, y_high)
ax.set_ylim(x_low, x_high)
ax.set_yticklabels([])
ax.set_xticklabels([])
ax.set_yticks([])
ax.set_xticks([])
ax.set_aspect('equal')
ax.axis('off')
# c is a circle object with all relevant data (center coordinates,
# radius, RGB color tuple)
for c in circles:
if x_low <= c.x_coord <= x_high and y_low <= c.y_coord <= y_high:
shape = plt.Circle((c.x_coord, c.y_coord), c.radius, color=c.color)
shape_plot = ax.add_artist(shape)
shapes.append(shape_plot)
plt.gca().invert_yaxis()
if save_image_flag:
plt.savefig(file_path + '_cc.png', bbox_inches='tight', pad_inches=0.02)
plt.close()
return file_path
else:
ax.margins(0)
fig.tight_layout()
fig.canvas.draw()
image_from_plot = np.frombuffer(fig.canvas.tostring_rgb(), dtype=np.uint8)
image_from_plot = image_from_plot.reshape(
fig.canvas.get_width_height()[::-1] + (3,))
image_from_plot = image_from_plot[:, 13:-14]
resized = cv2.resize(image_from_plot, (499, 391))
cropped = resized[78:-78]
plt.close()
return cropped
Questions
There is the issue that the array version and the png image is slightly different. I think that relates to the DPI of the image. I want to fix that and I'm thinking about different options who to speedup this function.
Speedup the process and keeping matplotlib, similar to this example from Github.
Get rid of matplotlib and draw it with Pillow, e.g. some thing like:
from PIL import Image, ImageDraw
def get_top_view(sector, circles, file_path, save_image_flag):
# get the sector bounds.
x_low, y_low, x_high, y_high = get_sector_bounds(sector)
im = Image.new('RGB', (499, 235), (255, 255, 255))
draw = ImageDraw.Draw(im)
# there needs to be some rescaling that the corrdinates match which
# I don't account for at the moment.
for c in circles:
if x_low <= c.x_coord <= x_high and y_low <= c.y_coord <= y_high:
draw.ellipse((c.x_coord - c.radius, c.y_coord - c.radius,
c.x_coord + c.radius, c.y_coord + c.radius),
fill=c.color)
if save_image_flag:
im.save(file_path + '.png')
return file_path
else:
image_as_array = convert_to_array() # have to think about how I'll do that
return image_as_array
A different approach that is faster (and somehow convenient)...
I'd be glad for any feedback on the two issues.
I'll share my findings here. I'm calling the function 2200 times for each output type within the simulation framework. All computation is serial as the multiprocessing code is not part of the example.
There are 220 sectors with 200 circles randomly distributed amongst the sectors. The simulation runs for 10 steps, where the circles radii get updated and new figures are drawn. Hence, 2200 calls to the function.
To generate and save png images it took 293483ms previously.
To generate numpy arrays it took 92715ms previously.
Speedup with Matplotlib
Generating and saving images (save_image_flag = True) now takes 126485ms.
Generating numpy arrays now takes 57029ms.
import matplotlib.pyplot as plt
import os
def get_top_view(sector, circles, file_path, save_image_flag):
# get the sector bounds.
x_low, y_low, x_high, y_high = get_sector_bounds(sector)
# init figure
fig, ax = plt.subplots()
ax.set_xlim(y_low, y_high)
ax.set_ylim(x_low, x_high)
# These were unnecessary
# ax.set_yticklabels([])
# ax.set_xticklabels([])
# ax.set_yticks([])
# ax.set_xticks([])
ax.set_aspect('equal')
ax.axis('off')
# c is a circle object with all relevant data (center coordinates,
# radius, RGB color tuple)
for c in circles:
if x_low <= c.x_coord <= x_high and y_low <= c.y_coord <= y_high:
shape = plt.Circle((c.x_coord, c.y_coord), c.radius, color=c.color)
shape_plot = ax.add_artist(shape)
shapes.append(shape_plot)
plt.gca().invert_yaxis()
# I added this to get the discrepancies between the generated image and
# the numpy array, as bbox_inches='tight' only applies to the saved image.
bbox0 = fig.get_tightbbox(fig.canvas.get_renderer()).padded(0.02)
if save_image_flag:
plt.savefig(file_path + '_cc.png', bbox_inches=bbox0)
plt.close()
return file_path
else:
buf = io.BytesIO()
fig.savefig(buf, format="rgba", dpi=100, bbox_inches=bbox0)
buf.seek(0)
img = np.reshape(np.frombuffer(buf.getvalue(), dtype=np.uint8),
newshape=(235, 499, -1))
img = img[..., :3]
buf.close()
plt.close()
return image
Speedup without Matplotlib
Research is in progress...

Matplotlib: Animate function output in steps

I have a function that outputs return (x, y) and i would like to animate the x,y pair, from beginning to end. E.g. such that the line 'develops over time'.
This is what my output looks like:
x, y = stephan()
plt.plot(x,y)
And when I try to use a snippit of animation code:
fig = plt.figure()
ax = plt.axes(xlim=(0, 2), ylim=(-2, 2))
line, = ax.plot([], [])
def init():
line.set_data([], [])
return line,
def animate(i):
x, y = stephan()
line.set_data(x, y[i])
return line,
anim = animation.FuncAnimation(fig, animate, init_func=init,
frames=100, interval=20, blit=True)
I get this quite boring output:
It is plotting something, but certainly not the x,y output. I think that I might be using the animate or init function wrongly? And strangely enough, I haven't been able to find any code that does this quite simply.
If the call to stephan returns two floats, y[i] does not make any sense. Furthermore you would want to store the output in a list, to obtain a line and not a single dot per frame.
A working example could look like this
import matplotlib.pyplot as plt
import matplotlib.animation as animation
import numpy as np
class Stephan():
i=0
def __call__(self):
self.i += 0.02
return self.i, np.random.randn(1)
stephan = Stephan()
fig = plt.figure()
ax = plt.axes(xlim=(0, 2), ylim=(-2, 2))
line, = ax.plot([], [])
def init():
line.set_data([], [])
return line,
X = []
def animate(i):
x, y = stephan()
X.append((x,y))
line.set_data(zip(*X))
return line,
anim = animation.FuncAnimation(fig, animate, init_func=init,
frames=100, interval=20, blit=True)
plt.show()
Alternatively, if stephan returns two lists of values, you could directly set those lists as data to the line.
import matplotlib.pyplot as plt
import matplotlib.animation as animation
import numpy as np
class Stephan():
x=[0]
y=[1]
def __call__(self):
self.x.append(self.x[-1]+0.02)
self.y.append(np.random.randn(1))
return self.x, self.y
stephan = Stephan()
fig = plt.figure()
ax = plt.axes(xlim=(0, 2), ylim=(-2, 2))
line, = ax.plot([], [])
def init():
line.set_data([], [])
return line,
def animate(i):
x, y = stephan()
line.set_data(x,y)
return line,
anim = animation.FuncAnimation(fig, animate, init_func=init,
frames=100, interval=20, blit=True)
plt.show()

Dynamic plot in python

I'm trying to create a plot in Python where the data that is being plotted gets updated as my simulation progresses. In MATLAB, I could do this with the following code:
t = linspace(0, 1, 100);
figure
for i = 1:100
x = cos(2*pi*i*t);
plot(x)
drawnow
end
I'm trying to use matplotlib's FuncAnimation function in the animation module to do this inside a class. It calls a function plot_voltage which recalculates voltage after each timestep in my simulation. I have it set up as follows:
import matplotlib.pyplot as plt
import matplotlib.animation as animation
def __init__(self):
ani = animation.FuncAnimation(plt.figure(2), self.plot_voltage)
plt.draw()
def plot_voltage(self, *args):
voltages = np.zeros(100)
voltages[:] = np.nan
# some code to calculate voltage
ax1 = plt.figure(2).gca()
ax1.clear()
ax1.plot(np.arange(0, len(voltages), 1), voltages, 'ko-')`
When my simulation runs, the figures show up but just freeze. The code runs without error, however. Could someone please let me know what I am missing?
Here is a translation of the matlab code into matplotlib using FuncAnimation:
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as animation
t = np.linspace(0, 1, 100)
fig = plt.figure()
line, = plt.plot([],[])
def update(i):
x = np.cos(2*np.pi*i*t)
line.set_data(t,x)
ani = animation.FuncAnimation(fig, update,
frames=np.linspace(1,100,100), interval=100)
plt.xlim(0,1)
plt.ylim(-1,1)
plt.show()

How do I plot two animations in a single plot with matplotlib?

In the following code I have two separate animations and I have plotted them in a two separate subplots. I want both of them to run in a single plot instead of this. I have tried the approach explained below but it is giving me issues as explained below. Please help
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as animation
import time as t
x = np.linspace(0,5,100)
fig = plt.figure()
p1 = fig.add_subplot(2,1,1)
p2 = fig.add_subplot(2,1,2)
def gen1():
i = 0.5
while(True):
yield i
i += 0.1
def gen2():
j = 0
while(True):
yield j
j += 1
def run1(c):
p1.clear()
p1.set_xlim([0,15])
p1.set_ylim([0,100])
y = c*x
p1.plot(x,y,'b')
def run2(c):
p2.clear()
p2.set_xlim([0,15])
p2.set_ylim([0,100])
y = c*x
p2.plot(x,y,'r')
ani1 = animation.FuncAnimation(fig,run1,gen1,interval=1)
ani2 = animation.FuncAnimation(fig,run2,gen2,interval=1)
fig.show()
I tried creating a single subplot instead of p1 and p2 and have both the plots graphed in that single subplot. That is just plotting one graph and not both of them. As far as I can say it is because one of them is getting cleared right after it is plotted.
How do I get around this problem?
As you do not show the code that is actually producing the problem, it's hard to tell where the problem lies.
But to answer the question of how to animate two lines in the same axes (subplot), we can just get rid of the clear() command and update the lines, instead of producing a new plot for every frame (which is more efficient anyways).
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as animation
x = np.linspace(0,15,100)
fig = plt.figure()
p1 = fig.add_subplot(111)
p1.set_xlim([0,15])
p1.set_ylim([0,100])
# set up empty lines to be updates later on
l1, = p1.plot([],[],'b')
l2, = p1.plot([],[],'r')
def gen1():
i = 0.5
while(True):
yield i
i += 0.1
def gen2():
j = 0
while(True):
yield j
j += 1
def run1(c):
y = c*x
l1.set_data(x,y)
def run2(c):
y = c*x
l2.set_data(x,y)
ani1 = animation.FuncAnimation(fig,run1,gen1,interval=1)
ani2 = animation.FuncAnimation(fig,run2,gen2,interval=1)
plt.show()

Time lapse animation

I'm new to python, matplotlib, and animation.
I've not been able to find a clear, detailed description of
animation.FuncAnimation(, , , , , , ......), so I've been trying to modifiy examples I've found. What are all the allowed parameters for FuncAnimation in English?
I want to produce a graph of dots shown one at a time with a time about 1 second between appearances.
Here's my current code that just produces a continuous curve after a delay:
def init():
line1.set_data([],[],'og')
return line1,
def animate(x):
x = np.linspace(0, 650, num=20, endpoint = True) #start at 0, stop at 650, number of values
y1 = (v0_y/v0_x)*x - (g/2)*(x/v0_x)**2
line1.set_data(x, y1)
time.sleep(1)
return line1,
anim = animation.FuncAnimation(fig, animate, init_func=init, frames=100, interval=3000, blit=True)
All suggestions appreciated!
You can check the documentation of FuncAnimation here, and this is an example code that does what you want:
from matplotlib import pyplot as plt
from matplotlib import animation
import numpy as np
xs = np.linspace(0, 650, num=20, endpoint = True)
ys = np.random.rand(20)
fig = plt.figure()
line1, = plt.plot([],[],'og')
plt.gca().set_xlim(0,650)
def init():
return line1,
def animate(i):
line1.set_data(xs[:i], ys[:i])
return line1,
anim = animation.FuncAnimation(fig, animate, init_func=init, interval=1000, blit=True)
plt.show()
Output window:

Resources