Matplotlib animation update suptitle - python

I would like to show in an animated plot bunches of images (which I assign to specific subplots). To this reason I wrote the following (I am using a jupyter notebook):
import matplotlib.pyplot as plt
from matplotlib import animation, rc
img1 = np.random.rand(1, 4)
img2 = np.random.rand(2, 4)
img3 = np.random.rand(3, 4)
img4 = np.random.rand(4, 4)
images = [[img1, img2], [img3, img4]]
%matplotlib auto #The only way I found so that this does not show an extra chart
def gen_anim():
fig, ax = plt.subplots(1, 2)
ims = []
for i in range(2):
im_1 = ax.ravel()[0].imshow(images[i][0], animated = True)
ax.ravel()[0].axis('off')
im_2 = ax.ravel()[1].imshow(images[i][1], animated = True)
ax.ravel()[1].axis('off')
ims.append([im_1, im_2])
plt.suptitle(t = 'Image set' + str(i), fontsize = 20)
return fig, ims
fig, ims = gen_anim()
rc('animation', html='html5')
anim = animation.ArtistAnimation(fig, ims, interval=1000, blit = True, repeat_delay = 1000)
anim
This works great except for the fact that I cannot update the suptitle of each set of images. I have seen examples where the title of each of the charts gets updated (here) but (based on my limited understanding of matplotlib) the suptitle is not the same.
So my question would be: Is it possible to update the suptitle in the current setting or should I try my luck with FuncAnimation?
(My efforts with FuncAnimation so far have been disappointing)
def animate(i):
to_plot_imgs = images[i]
fig, ax = plt.subplots(1, 2)
for j in range(2):
ax.ravel()[j].imshow(to_plot_imgs[j], interpolation='bilinear', animated = True)
ax.ravel()[j].axis('off')
plt.suptitle(t = 'Image set' + str(i), fontsize = 20)
anim = animation.FuncAnimation(plt.gcf(), animate, frames=len(images), interval=500)
anim

The title can also be changed dynamically by using the Funcanimation function. Initially, we set no title and font size, and update it with set_text during the animation.
import matplotlib.pyplot as plt
from matplotlib import animation, rc
from IPython.display import HTML
import numpy as np
img1 = np.random.rand(1, 4)
img2 = np.random.rand(2, 4)
img3 = np.random.rand(3, 4)
img4 = np.random.rand(4, 4)
images = [[img1, img2], [img3, img4]]
# %matplotlib auto #The only way I found so that this does not show an extra chart
fig, ax = plt.subplots(1, 2)
title = plt.suptitle(t='', fontsize = 20)
def animate(i):
to_plot_imgs = images[i]
# fig, ax = plt.subplots(1, 2)
for j in range(2):
ax.ravel()[j].imshow(to_plot_imgs[j], interpolation='bilinear', animated = True)
ax.ravel()[j].axis('off')
title.set_text('Image set{}'.format(i))
anim = animation.FuncAnimation(fig, animate, frames=len(images), interval=1500, repeat=False)
plt.close()
HTML(anim.to_html5_video())

Related

MatPlotLib Animation taking hours to save?

I finally managed to make my animations work. The only problem comes when I save them with ffmpeg writer. A ~250 frame gif takes literally a few hours to save. It took me 3 hours to save an 11 second video.
What is making it take so long??
import pandas as pd
import geopandas as gpd
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation
import contextily as cx
from matplotlib.offsetbox import AnchoredText
#Writer information, path, and where to save
plt.rcParams['animation.ffmpeg_path'] =r"the path I saved ffmpeg"
#writer = animation.writers['ffmpeg']
f = r"location I will save the .mp4"
# Reads the Excel sheet specified from the doc. IT ONLY OPENS .XLSM
df = pd.read_excel(r'the excel file', sheet_name='the sheet name')
# Creates a list of important datasets
df['Points'] = list(zip(df.Latitude,df.Longitude))
Longs = list(df.Longitude)
Lats = list(df.Latitude)
Time = list(df.Last_Record)
Speed = list(df.Speed)
#This is the list of Coordinates
Coords = df['Points']
#print(Coords)
#how many frames to save for the animation
savecount = len(Longs)
print("Frames: ",savecount)
#turns the dataframe into a geodataframe
gdf = gpd.GeoDataFrame(df, geometry=gpd.points_from_xy(df.Longitude, df.Latitude),crs='EPSG:4326')
#Geodataframe boundaries
minx, miny, maxx, maxy = gdf.geometry.total_bounds
print("Boundaries: ",minx, miny, maxx, maxy)
#plt background
ax = gdf.plot(figsize=(6,6), alpha =0.5, facecolor="None")
plt.subplots_adjust(top = 1, bottom = 0, right = 1, left = 0,
hspace = 0, wspace = 0)
ax.margins(0,0)
ax.get_xaxis().set_visible(False)
ax.get_yaxis().set_visible(False)
plt.axis('off')
#North arrow
x, y, arrow_length = 0.85, 0.10, 0.07
ax.annotate('N', xy=(x, y), xytext=(x, y-arrow_length),
arrowprops=dict(facecolor='black', width=5, headwidth=15),
ha='center', va='center', fontsize=20,
xycoords=ax.transAxes)
#Use contextily to create the basemap
cx.add_basemap(ax, crs=gdf.crs.to_string())
#Saves map
plt.savefig("image name.png", dpi=300, bbox_inches='tight', format="png", transparent=False,pad_inches = 0)
plt.close()
#Read the map
plotmap = r"image name above^^"
truthplot = plt.imread(plotmap)
#Create subplot over the map
fig, ax = plt.subplots(figsize = (6,6),linewidth = 0.1, frameon=False)
plottitle = "plot title"
ax.set_title(plottitle)
ax.set_xlabel("Longitude")
ax.set_ylabel("Latitude")
fig.tight_layout()
def animate(i):
Time.remove(Time[0])
Speed.remove(Speed[0])
scat = ax.scatter(Longs[i], Lats[i], zorder=1, alpha= 0.5, c='r', s=7)
annotation = AnchoredText(s=("Time: " + str(Time[0]) + "\n" + "Speed: " + str(Speed[0])),
prop=dict(size=8), frameon=True, loc='upper left')
annotation.patch.set_boxstyle("round,pad=0.,rounding_size=0.2")
ax.add_artist(annotation)
ax.imshow(truthplot, extent=(minx, maxx, miny, maxy), aspect='auto')
return [annotation],[scat],[Longs],[Lats]
#make the animation
ani = FuncAnimation(fig, animate,frames = savecount, interval=20, repeat = False)
ani.save(f, fps=15,writer='ffmpeg'
)
Everything else works except saving it takes WAY longer than it should, I think.
Thank you for any help!

Matplotlib save animaiton as video but get empty content

I am trying to make an animation with continue rotating an image, but the output video file has empty content(Only axis left), how to fix it?
import math
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation
import scipy.misc
from scipy import ndimage
my_image="img.png"
out_file="myvideo.mp4"
class UpdateDist:
def __init__(self, ax):
self.ax = ax
self.img = mpimg.imread(my_image)
self.ax.imshow(self.img)
self.degree = 1
def __call__(self, i):
rotated_img = ndimage.rotate(img, self.degree*10)
self.ax.imshow(rotated_img)
self.degree += 1
return self.ax,
plt.axis(False)
plt.grid(False)
fig, ax = plt.subplots()
ud = UpdateDist(ax)
anim = FuncAnimation(fig, ud, frames=100, interval=10, blit=True)
plt.show()
ani.save(out_file, fps=30, extra_args=['-vcodec', 'libx264'])
I applied some edits to your code:
replaced self.degree with i: i increases by 1 in each iteration, no need for another counter
moved ax.grid(False) and ax.axis(False) (and added ax.clear()) within __call__ method, in order to use them in each frame
removed blit parameter from FuncAnimation
replaced .mp4 output file format with .gif
used imagemagik as writer
Let me know if this code achieves your goal or if you need any further modifications.
Complete Code
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation
from scipy import ndimage
import numpy as np
my_image='img.png'
out_file='myvideo.gif'
class UpdateDist:
def __init__(self, ax, rotational_speed):
self.ax = ax
self.img = plt.imread(my_image)
self.rotational_speed = rotational_speed
def __call__(self, i):
rotated_img = ndimage.rotate(self.img, self.rotational_speed*i, reshape=False)
self.ax.clear()
self.ax.grid(False)
self.ax.axis(False)
self.ax.imshow((rotated_img*255).astype(np.uint8))
return self.ax,
fig, ax = plt.subplots()
ud = UpdateDist(ax = ax, rotational_speed = 1)
anim = FuncAnimation(fig, ud, frames = 91, interval = 1)
anim.save(filename = out_file, writer = 'pillow', fps = 30)
Animation

Can't save Matplotlib figure correctly when using a function

This is an example of my code to plot and save a figure:
I'm using Python 3.7.4 and matplotlib==3.0.3.
import matplotlib.pyplot as plt
import pandas as pd
from yahoo_fin import stock_info
import statsmodels.api as sm
brk_data = stock_info.get_data("BRK-A")
with plt.style.context('dark_background'):
fig, ax = plt.subplots(figsize=(16, 9))
sm.qqplot(brk_data['adjclose'].pct_change(1).fillna(0), fit=True, line='45', ax=ax)
plt.title('QQ Plot', fontsize = 16)
ax.axvline(0, c = 'w', linestyle = "--", alpha = 0.5)
ax.grid(True,linewidth=0.30)
ax.set_xlim(4,-4)
ax.set_ylim(5,-5)
plt.savefig('qqplot.png', bbox_inches = 'tight', pad_inches = 0.4, dpi = 300, edgecolor = 'k')
plt.show()
plt.close()
This code saves and displays the plot figure correctly, as follows:
But when the plot is built inside a function, the saved picture background will stay white, making the white ticks and labels from the 'dark-background' style invisible, e.g.:
for
def qqplot2(pct, save = False):
with plt.style.context('dark_background'):
fig, ax = plt.subplots(figsize=(16, 9))
sm.qqplot(pct, fit=True, line='45', ax=ax)
plt.title('QQ Plot', fontsize = 16)
ax.axvline(0, c = 'w', linestyle = "--", alpha = 0.5)
ax.grid(True,linewidth=0.30)
ax.set_xlim(4,-4)
ax.set_ylim(5,-5)
if save == True:
plt.savefig('qqplot2.png', bbox_inches = 'tight', pad_inches = 0.4, dpi = 300, edgecolor = 'k')
plt.show()
plt.close()
else:
plt.show()
calling the function with qqplot2(brk_data['adjclose'].pct_change(1).fillna(0), save = True) will display the correct plot:
but will save the figure incorrectly:
You just need to indent your if clause in the function like this:
def qqplot2(pct, save = False):
with plt.style.context('dark_background'):
fig, ax = plt.subplots(figsize=(16, 9))
sm.qqplot(pct, fit=True, line='45', ax=ax)
plt.title('QQ Plot', fontsize = 16)
ax.axvline(0, c = 'w', linestyle = "--", alpha = 0.5)
ax.grid(True,linewidth=0.30)
ax.set_xlim(4,-4)
ax.set_ylim(5,-5)
if save == True:
plt.savefig('qqplot2.png', bbox_inches = 'tight', pad_inches = 0.4, dpi = 300, edgecolor = 'k')
plt.show()
plt.close()
else:
plt.show()

FuncAnimation update function not called

I'm not quite getting how to create a class for animating data. Here is the gist:
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as animation
x = np.arange(100).reshape((100, 1))
y = np.random.randn(100, 1)
xy = np.hstack((x, y))
class PlotData:
def __init__(self):
fig, ax = plt.subplots()
fig.set_size_inches((11, 9))
self.fig = fig
self.ax = ax
self.ln0, = ax.plot([], [])
def init(self):
self.ln0.set_data([], [])
return(self.ln0, )
def update(self, frame_no):
data = xy[0:frame_no + 1]
self.ln0.set_data(data[:, 0], data[:, 1])
return(self.ln0, )
if __name__ == '__main__':
my_plot = PlotData()
anim = animation.FuncAnimation(my_plot.fig, my_plot.update,
init_func=my_plot.init, blit=True,
frames=99, interval=50)
plt.show()
This only produces the init method output but not the update, so ends up a blank plot with no animation. What is going on?
For me your code works perfectly fine. The only problem is that most of the data are outside of the plotting limits. If you adjust your plot limits like this:
class PlotData:
def __init__(self):
fig, ax = plt.subplots(figsize = (11,9))
self.fig = fig
self.ax = ax
self.ax.set_xlim([0,100])
self.ax.set_ylim([-3,3])
self.ln0, = ax.plot([], [])
The line is animated just fine. If you want that the x- and y-limits are adjusted automatically, see this question on how to do it. However, if I recall correctly, this will only work properly with blit=False.

Pyplot zorder lost with animation

I am trying to animate different objects in the same graph using pyplot's funcanimation.
It works almost as I expect it to, except for the order in which the different elements are displayed in. So the plot curve, text and legend are shown behind the image where they are barely seen.
Here is my (not so) minimal working example:
#! /usr/bin/python
import numpy as np
from matplotlib import pyplot as plt
from matplotlib import animation
import random
def init():
line_simu.set_data([], [])
time_text.set_text('')
imobj.set_data(np.zeros((100, 100)))
imobj.set_zorder(0)
time_text.set_zorder(10)
return line_simu, time_text, imobj
def animate(i):
imobj.set_zorder(0)
time_text.set_zorder(10)
y_simu = np.linspace(0,100, 100)
x_simu = np.linspace(-10, 10, 100)
line_simu.set_data(x_simu, y_simu)
time_text.set_text('time = %.1f' % i )
global data
imobj.set_data( data + np.random.random((100,1)) * 0.5 )
return line_simu, time_text, imobj
def forceAspect(ax,aspect=1):
im = ax.get_images()
extent = im[0].get_extent()
ax.set_aspect(abs((extent[1]-extent[0])/(extent[3]-extent[2]))/aspect)
fig = plt.figure()
ax = plt.axes(xlim=(-15,15), ylim=(-110, 0) , aspect=1)
data = np.random.random((100,100)) - .5
imobj = ax.imshow( data , extent=[-15,15, -110, 0.0], origin='lower', cmap=plt.cm.gray, vmin=-2, vmax=2, alpha=.7, zorder=0, aspect=1)
line_simu, = ax.plot([], [],"r--", lw=2, markersize=4 , label = "Some curve" , zorder= 2 )
time_text = ax.text(-14.9, -108, '', zorder=10)
l = plt.legend(loc='lower right', prop={'size':8} )
l.set_zorder(200)
forceAspect(ax,aspect=1)
anim = animation.FuncAnimation(fig, animate, init_func=init, frames=range( 50), interval=3000, blit=True)
plt.show()
Without animation, I can easily control the order of the different elements with set_zorder, but when the animation updates the image, this order is lost. I tried to set the zorder in the init function and again in the animate function, without success.
I am very thankful for any help on that matter.
To answer my own question: It seems that the order in witch the init() and animate() functions return objects controls the order in which they are displayed. Additionally those functions should return the legend object in order to include it in the animation.
Here is my corrected code:
#! /usr/bin/python
import numpy as np
from matplotlib import pyplot as plt
from matplotlib import animation
import random
def init():
imobj.set_data(np.zeros((100, 100)))
line_simu.set_data([], [])
time_text.set_text('time = 0.0')
return imobj , line_simu, time_text, l
def animate(i):
global data
imobj.set_data( data + np.random.random((100,1)) * 0.5 )
imobj.set_zorder(0)
y_simu = np.linspace(-100,-10, 100)
x_simu = np.linspace(-10, 10, 100)
line_simu.set_data(x_simu, y_simu)
time_text.set_text('time = %.1f' % i )
return imobj , line_simu, time_text, l
def forceAspect(ax,aspect=1):
im = ax.get_images()
extent = im[0].get_extent()
ax.set_aspect(abs((extent[1]-extent[0])/(extent[3]-extent[2]))/aspect)
fig = plt.figure()
ax = plt.axes(xlim=(-15,15), ylim=(-110, 0) , aspect=1)
data = np.random.random((100,100)) - .5
imobj = ax.imshow( data , extent=[-15,15, -110, 0.0], origin='lower', cmap=plt.cm.gray, vmin=-2, vmax=2, alpha=1.0, zorder=1, aspect=1)
line_simu, = ax.plot([], [],"r--", lw=2, markersize=4 , label = "Some curve" , zorder= 1 )
time_text = ax.text(-14.0, -108, '', zorder=10)
forceAspect(ax,aspect=1)
l = plt.legend(loc='lower right', prop={'size':8} )
anim = animation.FuncAnimation(fig, animate, init_func=init, frames=range( 50), interval=500, blit=True)
plt.show()

Categories

Resources