Matplotlib animation saves only one part of the video with ffmpeg - python

I have a weird problem with Python Matplotlib animation. I want to produce a video which includes another small video in the corner: the bigger plot should animate some data, and the small inserted plot should indicate with a moving dashed line the time position on the global data plot. It all works fine when I use only plot.show(), just the way I want it. But when I comment plot.show() and try to save it with ani.save, all it saves is a big plot, and the inserted one just disappears!
If I try to uncomment both ani.save and plot.show() at the same time, I get an error message "AttributeError: draw_artist can only be used after an initial draw which caches the render", and the animation screen just stays blank.
Could you please help me how to solve it? I need a saved file with the full movie.
Here is the code:
import sys
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as animation
Q_data = np.arange(100.).reshape((10,10))
age = np.arange(10.)
Q_total = np.arange(10.)
Q = Q_data[1:,:]
r_ax = np.arange(10.)
fig, ax = plt.subplots()
line, = ax.semilogy(r_ax,Q[0,:])
time_template = 'time = %.1f'
time_text = ax.text(0.58, 0.9, '', size=20, transform=ax.transAxes)
plt.title('Plot 1')
ax.set_xlabel('Axis Title')
ax.set_ylabel('Axis Title')
# this is another inset axes over the main axes
a = plt.axes([0.2, 0.5, .3, .3])
line2, = plt.semilogx(age,Q_total,color='r',linewidth=1)
plt.title('Plot 2', y=1.11)
a.set_xlabel('Small axis title')
#fig.canvas.draw()
def animate(i):
line.set_ydata(Q[i,:]) # update the data
time_text.set_text(time_template % (age[i]))
a.lines.pop()
a.semilogx(age,Q_total,color='r',linewidth=1)
a.axvline(age[i], color='k', linestyle='--', linewidth=1)
return line, a, time_text
# Init only required for blitting to give a clean slate.
def init():
line.set_ydata(np.ma.array(r_ax, mask=True))
time_text.set_text('')
return line, time_text
ani = animation.FuncAnimation(fig, animate, np.arange(1, Q.shape[0], 1), init_func=init, interval=25, blit=True)
ani.save('testanimation.avi', writer="ffmpeg", fps=15)
plt.show()

I made you a small example with big axis and small axis inside it.
import numpy as np
import matplotlib.pyplot as plt
plt.rcParams['animation.ffmpeg_path'] = r'D:\ffmpeg-20161029-1e660fe-win64-static\bin\ffmpeg.exe'
import matplotlib.animation as animation
r_ax = np.arange(1, 10., 0.1)
fig, ax = plt.subplots()
line, = ax.plot(r_ax, np.log(r_ax))
a = plt.axes([0.2, 0.5, .3, .3])
line2, = a.plot([], [],color='r',linewidth=1)
a.set_xlim([1, 10])
a.set_ylim([0, 3])
def animate(i):
line.set_data(r_ax[:i], np.log(r_ax[:i])) # update the data
line2.set_data(r_ax[:i], 3-np.log(r_ax[:i]))
return line, line2
ani = animation.FuncAnimation(fig, animate, frames=100, interval=35, blit=True)
ani.save('testanimation.avi', writer="ffmpeg", fps=15)
# plt.show()

Related

Animate labels using FuncAnimation in Matplotlib

I am not able to make (animated) labels using FuncAnimation from matplotlib. Please find below a minimal code that I made. ax.annotate has no effect at all - the animation itself works though. What can I change to get animated labels/titles, which are different for each frame?
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as animation
fig, ax = plt.subplots()
fig.clear()
steps = 10
data = np.random.rand(20,20,10)
imagelist = [data[:,:,i] for i in range(steps) ]
im = plt.imshow(imagelist[0], cmap='Greys', origin='lower', animated=True)
plt.colorbar(shrink=1, aspect=30, label='Counts')
# does not work
ax.annotate("Frame: %d " % steps,(0.09,0.92),xycoords ='figure fraction')
def updatefig(j):
im.set_array(imagelist[j])
return [im]
ani = animation.FuncAnimation(fig, updatefig, frames=range(steps), interval=200, blit=True)
plt.show()
Two problems overall:
The annotation text never gets updated in updatefig()
The canvas gets cleared+blitted, which wipes out annotations
Five steps to resolve:
Remove fig.clear() to preserve annotations
Save the initial annotation's handle
Update the annotation's text in updatefig()
Include the annotation in the return of updatefig()
Set blit=False to preserve annotations
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as animation
fig, ax = plt.subplots()
#1 do NOT call fig.clear()
steps = 10
data = np.random.rand(20, 20, steps)
im = plt.imshow(data[:, :, 0], cmap='Greys', origin='lower', animated=True)
plt.colorbar(shrink=1, aspect=30, label='Counts')
#2 annotate frame 0 and save handle
annot = ax.annotate('Frame: 0', (0.09, 0.92), xycoords='figure fraction')
def updatefig(j):
im.set_array(data[:, :, j])
#3 update annotation text
annot.set_text(f'Frame: {j}')
#4 include annotation when returning
return im, annot
#5 set blit=False
anim = animation.FuncAnimation(fig, updatefig, frames=steps, blit=False)

Using Python's Matplotlib how can I align annotation updates for specific points to my data set as it is graphed using FuncAnimation?

I have the following code that functions perfectly with the exception that the timing of placing my point annotations and updating the legend does not align to when the point appears on the graph. How do I get them to align?
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation
from matplotlib import style
import matplotlib.patches as mpatches
style.use('ggplot')
x_data = [1,5,7,9,11,12,14,15,27,29,37,39,45,47,52,53,57,58,61,62,66,80,82,83,84,85,90,91,93,96,98,105,109,110,111,113,114,116,117,120,122,123,127,134,136,138,140,141,144,160,161,162,165,174,176,179,183,184,185,186,190,191,192,193,194,195,199,200,204]
y_data = [50,55,40,30,31,20,21,18,25,21,15,18,20,24,27,30,32,35,37,38,30,11,13,10,10,14,16,18,19,17,14,9,9,4,5,5,6,5,7,3,6,8,10,13,15,12,10,13,8,4,3,5,4,5,7,6,4,8,10,12,10,12,12,12,12,15,17,18,18]
annotations = ['','','Pasted Value','Cut Value','','Pasted Cut Value','','Abnormal Pause','','Abnormal Pause','Out of Order Field Interaction','','','','','','','','','','Window Exit','Window Entry','','Pasted Value','Out of Order Field Interaction','','','Irregular Typing Cadence','','Irregular Typing Cadence','Abnormal Pause','Irregular Typing Cadence','Out of Order Field Interaction','Value Manipulation','','Out of Order Field Interaction','','Value Manipulation','','Value Manipulation','','','','','','','','','Window Exit','Window Entry','Pasted Value','','Value Manipulation','','','Value Manipulation','Value Manipulation','','','','Copied Value','','Frustration - Repeat Paste','Frustration - Repeat Paste','Frustration - Repeat Paste','','','','']
print(len(x_data))
print(len(y_data))
print(len(annotations))
fig, ax = plt.subplots()
ax.set_xlim(0,205)
ax.set_ylim(0,100)
line, = ax.plot(0,50)
def init():
line.set_data([], [])
return line,
def animate(n):
line, = plt.plot(x_data[:n], y_data[:n], color='b', marker='o')
ax.annotate(annotations[n],(x_data[n],y_data[n]))
updated_score = mpatches.Patch(color='b', label=y_data[n])
plt.legend(handles=[updated_score])
return line,
animation = FuncAnimation(fig, animate, frames=len(x_data), interval=500)
plt.title('A Cool Title')
plt.xlabel('Time in Seconds')
plt.ylabel('Value')
plt.show()
Here is a working replit
I think the mix of ax and plot is the issue, I rewrote it in object style and fixed the code.
fig, ax = plt.subplots()
ax.set_xlim(0,205)
ax.set_ylim(0,100)
line, = ax.plot(0,50)
ax.set_title('A Cool Title')
ax.set_xlabel('Time in Seconds')
ax.set_ylabel('Value')
def init():
line.set_data([], [])
return line,
def animate(n):
line, = ax.plot(x_data[:n], y_data[:n], color='b', marker='o')
ax.annotate(annotations[n],(x_data[n],y_data[n]))
updated_score = mpatches.Patch(color='b', label=y_data[n])
ax.legend(handles=[updated_score])
return line,
animation = FuncAnimation(fig, animate, frames=len(x_data), interval=500, repeat=False)
# animation.save('line_legend_sync_ani.gif', writer='pillow')
plt.show()
Remember that when slicing Pandas doesn't include the last value, therefore in this line plt.plot(x_data[:n+1], y_data[:n+1]) you should add +1 to include the n-th value. I think that solve the lack of timming.
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation
from matplotlib import style
import matplotlib.patches as mpatches
style.use('ggplot')
x_data = [1,5,7,9,11,12,14,15,27,29,37,39,45,47,52,53,57,58,61,62,66,80,82,83,84,85,90,91,93,96,98,105,109,110,111,113,114,116,117,120,122,123,127,134,136,138,140,141,144,160,161,162,165,174,176,179,183,184,185,186,190,191,192,193,194,195,199,200,204]
y_data = [50,55,40,30,31,20,21,18,25,21,15,18,20,24,27,30,32,35,37,38,30,11,13,10,10,14,16,18,19,17,14,9,9,4,5,5,6,5,7,3,6,8,10,13,15,12,10,13,8,4,3,5,4,5,7,6,4,8,10,12,10,12,12,12,12,15,17,18,18]
annotations = ['','','Pasted Value','Cut Value','','Pasted Cut Value','','Abnormal Pause','','Abnormal Pause','Out of Order Field Interaction','','','','','','','','','','Window Exit','Window Entry','','Pasted Value','Out of Order Field Interaction','','','Irregular Typing Cadence','','Irregular Typing Cadence','Abnormal Pause','Irregular Typing Cadence','Out of Order Field Interaction','Value Manipulation','','Out of Order Field Interaction','','Value Manipulation','','Value Manipulation','','','','','','','','','Window Exit','Window Entry','Pasted Value','','Value Manipulation','','','Value Manipulation','Value Manipulation','','','','Copied Value','','Frustration - Repeat Paste','Frustration - Repeat Paste','Frustration - Repeat Paste','','','','']
print(len(x_data))
print(len(y_data))
print(len(annotations))
fig, ax = plt.subplots()
ax.set_xlim(0,205)
ax.set_ylim(0,100)
line, = ax.plot(0,50)
def init():
line.set_data([], [])
return line,
def animate(n):
line, = plt.plot(x_data[:n+1], y_data[:n+1], color='b', marker='o')
ax.annotate(annotations[n],(x_data[n],y_data[n]))
updated_score = mpatches.Patch(color='b', label=y_data[n])
ax.legend(handles=[updated_score])
return line,
animation = FuncAnimation(fig, animate, frames=len(x_data), interval=500)
plt.title('A Cool Title')
plt.xlabel('Time in Seconds')
plt.ylabel('Value')
plt.show()
Check here the result: https://repl.it/#JuanJavier1/A-Cool-Title

How to show only 'x' amount of values on a graph in python

I am new to python and am carrying out some little projects along side watching tutorials to enable me to learn.
I have recently been working with some APIs to collect data - I save this data in a CSV file and then open the CSV file to show the data as a graph.
I want the graph to show the data LIVE, but in doing so I only want 10 values on the screen at once, so when the 11th value is plotted, the 1st is no longer visible unless the scrolling function is used to look back at it..
I have managed to pull together the code that plots the live data from the CSV file, as well as some code that creates the graph in the desired format - but as I am quite new to python I am unsure of how I'd make them work together.. Any advice would be greatly appreciated.
Below is the code that I have created to read and plot from a CSV file:
import random
from itertools import count
import pandas as pd
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as animation
index = count()
def animate(i):
data = pd.read_csv('x.csv')
x = data['Time']
y = data['R1Temp']
y1 = data['R2Temp']
y2 = data['R3Temp']
plt.cla()
plt.plot(x, y, marker = 'o', label='Room 1 Temp')
plt.plot(x, y1, marker = 'o', label='Room 2 Temp')
plt.plot(x, y2, marker = 'o', label='Room 3 Temp')
plt.xlabel("Time")
plt.ylabel("Temperature °C")
plt.title("Live temperature of Rooms")
plt.legend(loc='upper left')
plt.tight_layout()
ani = FuncAnimation(plt.gcf(), animate, interval=1000)
plt.tight_layout()
plt.show()
Below is the code that shows the way in which I'd like the graph to format the data plots:
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as animation
def update(frame):
global x, y
start = x[max(frame-PAN//2, 0)]
start = x[max(frame-PAN+1, 0)]
end = start + PAN
ax.set_xlim(start, end)
start, end = ax.get_xlim()
ax.xaxis.set_ticks(np.arange(start, end, TICK))
ax.figure.canvas.draw()
line1.set_data(x[0:frame+1], y[0:frame+1])
return (line1,)
# main
NUM = 100
TICK = 1
PAN = 10
x = np.arange(start=1, stop=NUM + 1, step=1)
for i in range(NUM):
y = np.random.rand(NUM) * 100
fig, ax = plt.subplots()
ax.set_xlim(0, PAN)
start, end = ax.get_xlim()
ax.xaxis.set_ticks(np.arange(start, end, TICK))
ax.set_ylim(0, 100)
line1, = ax.plot([], [], color="r")
ani = animation.FuncAnimation(fig, update, frames=len(x), interval=1000, repeat=False)
plt.show()
I have tried many ways to merge them together, but I just cant seem to find the correct way to go about it.
Thanks in advance!!
Showing the last N time points is quite easy. Just use DataFrame.tail() to get the last N rows of your dataframe.
Note that when doing an animation, the recommended way is to create your axes and artists outside the animation code, and only update your artists' data inside the animate code.
import pandas as pd
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation
fig, ax = plt.subplots()
l1, = ax.plot([], [], marker='o', label='Room 1 Temp')
l2, = ax.plot([], [], marker='o', label='Room 2 Temp')
l3, = ax.plot([], [], marker='o', label='Room 3 Temp')
plt.xlabel("Time")
plt.ylabel("Temperature °C")
plt.title("Live temperature of Rooms")
plt.legend(loc='upper left')
plt.tight_layout()
def animate(i, N):
data = pd.read_csv('x.csv').tail(N)
l1.set_data(data['Time'], data['R1Temp'])
l2.set_data(data['Time'], data['R2Temp'])
l3.set_data(data['Time'], data['R3Temp'])
ax.relim()
ax.autoscale_view()
return l1, l2, l3
ani = FuncAnimation(fig, animate, interval=1000, frames=None, fargs=(10,))
plt.show()

Time dependent plot matplotlib

I would like to plot point-by-point a sine wave in Python via Matplotlib, for which, each point, is added every x milliseconds, in order to obtain a smooth animation of the drawing.
This is my attempt:
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as animation
from math import sin
fig, ax = plt.subplots()
x = [0]
line, = ax.plot(x, np.asarray(0))
def animate(i):
x.append(x[-1]+0.04)
line.set_xdata(np.asarray(x)*2*np.pi/5)
line.set_ydata(np.sin(np.asarray(x)*2*np.pi/5))
plt.draw()
def init():
line.set_ydata(np.ma.array(x, mask=True))
return line,
ani = animation.FuncAnimation(fig, animate, 10, init_func=init, interval=40, blit=True)
plt.show()
Which raises:
RuntimeError: The animation function must return a sequence of Artist objects.
What did I mistaken? What is, in your opinion, the most efficient way to obtain this effect?
PS The time axis should stay fixed and not move, so it should be wider than the plot
Firstly you are getting the error because your animate(i) is not returning anything. You need to return line,. Secondly you are not using the iin animate(i) aswell.
Here is a simple sine curve animation from https://jakevdp.github.io/blog/2012/08/18/matplotlib-animation-tutorial/:
import numpy as np
from matplotlib import pyplot as plt
from matplotlib import animation
# First set up the figure, the axis, and the plot element we want to
animate
fig = plt.figure()
ax = plt.axes(xlim=(0, 2), ylim=(-2, 2))
line, = ax.plot([], [], lw=2)
# initialization function: plot the background of each frame
def init():
line.set_data([], [])
return line,
# animation function. This is called sequentially
def animate(i):
x = np.linspace(0, 2, 1000)
y = np.sin(2 * np.pi * (x - 0.01 * i))
line.set_data(x, y)
return line,
# call the animator. blit=True means only re-draw the parts that have
changed.
anim = animation.FuncAnimation(fig, animate, init_func=init,
frames=200, interval=20, blit=True)
plt.show()
There is additional inspiration in the link which might help you further.
Animate should return a sequence of artist objects:
You should add:
return line, to the end of the animate function
def animate(i):
x.append(x[-1]+0.04)
line.set_xdata(np.asarray(x)*2*np.pi/5)
line.set_ydata(np.sin(np.asarray(x)*2*np.pi/5))
return line,
Source:
Another answer
Simple Example

Matplotlib animation update title using ArtistAnimation

I am trying to use the ArtistAnimation to create an animation. And everything is working, except set_title isn't working. I don't understand why blit=False doesn't work.
Do I need to go to a FuncAnimation?
for time in np.arange(-0.5,2,0.01):
writer.UpdatePipeline(time=time)
df=pd.read_csv(outputPath + '0.csv', sep=',')
df['x'] = 1E6*df['x']
df['Efield'] = 1E-6*df['Efield']
line3, = ax3.plot(df['x'], df['Efield'])
line1A, = ax1.semilogy(df['x'], df['em_lin'])
line1B, = ax1.semilogy(df['x'], df['Arp_lin'])
line2A, = ax2.plot(df['x'], df['Current_em'])
line2B, = ax2.plot(df['x'], df['Current_Arp'])
ax1.set_title('$t = ' + str(round(time, n)))
ims.append([line1A, line1B, line2A, line2B, line3])
im_ani = animation.ArtistAnimation(fig, ims, interval=50, blit=False)
im_ani.save(outputPath + 'lines.gif', writer='imagemagick', fps=10, dpi=100)
plt.show()
Two problems. The immeadiate is that the title is not part of the list of artists to update, hence the animation cannot know that you want to update it.
The more profound problem is that there is only a single title per axes. Hence even if you include the title in the list of artists, it will always show the text that it has last been set to.
The solution would be not to use the axes' title to update, but other text elements, one per frame.
import matplotlib.pyplot as plt
from matplotlib import animation
import numpy as np
a = np.random.rand(10,10)
fig, ax=plt.subplots()
container = []
for i in range(a.shape[1]):
line, = ax.plot(a[:,i])
title = ax.text(0.5,1.05,"Title {}".format(i),
size=plt.rcParams["axes.titlesize"],
ha="center", transform=ax.transAxes, )
container.append([line, title])
ani = animation.ArtistAnimation(fig, container, interval=200, blit=False)
plt.show()
For reference the same as FuncAnimation would look as follows, where the title can be set directly as usual.
import matplotlib.pyplot as plt
from matplotlib import animation
import numpy as np
a = np.random.rand(10,10)
fig, ax=plt.subplots()
ax.axis([-0.5,9.5,0,1])
line, = ax.plot([],[])
def animate(i):
line.set_data(np.arange(len(a[:,i])),a[:,i])
ax.set_title("Title {}".format(i))
ani = animation.FuncAnimation(fig,animate, frames=a.shape[1], interval=200, blit=False)
plt.show()

Categories