I am trying to animate some fractals in matplotlib using FuncAnimation.
When I have the blit set to False I get no errors: the code runs fine and generates a nice animation for me. However, when I set the blit to True, it gives me TypeError: 'Line2D' object is not iterable. Does anyone know why this happens and how I can fix it?
I would like to take advantage of blitting as I am planning to animate a large family of fractals and just taking a small slice of them (64 different fractals) already takes noticeable computation time. I have a fast way to generate a matrix with columns containing the different fractals so I know computation time is spent trying to animate a bunch of plots without blitting.
In my example I am just animating the iteration of generating a fractal. This is a short and fast way of illustrating the error I am getting, not what I am actually trying to animate because otherwise I wouldn't care about the blitting.
Here is a minimal example that should run in a jupyter notebook if you have ffmpeg installed:
import numpy as np
import scipy as sp
import scipy.linalg as la
from matplotlib import pyplot as plt
from matplotlib.animation import FuncAnimation
from matplotlib import animation, rc
from IPython.display import HTML
%matplotlib inline
plt.rcParams['figure.figsize'] = [8,8]
plt.rcParams['animation.ffmpeg_path'] = "C:\\Program Files\\ffmpeg\\bin\\ffmpeg.exe" #<- CHANGE TO YOUR PATH TO FFMPEG or delete this line if the notebook knows where to find ffmpeg.
class IFS:
"""Represent an iterated function system used to generate a fractal."""
def __init__(self,start,f1,f2,reversef2=False):
self.points = start
self.f1 = f1
self.f2 = f2
self.reversef2 = reversef2
def iterate(self,iterations=1):
"""Perform iterations using the functions"""
for i in range(0,iterations):
if self.reversef2: #This is needed for the dragon curve
self.points = np.append(self.f1(self.points),self.f2(self.points)[::-1])
else: #However, you do not want to append in reverse when constructing the levyC curve
self.points = np.append(self.f1(self.points),self.f2(self.points))
def get_points(self):
return self.points
def dragon_ifs():
"""Return a dragon fractal generating IFS"""
def f1(z):
return (0.5+0.5j)*z
def f2(z):
return 1 - (0.5-0.5j)*z
return IFS(np.array([0,1]),f1,f2,reversef2=True)
#Animation
class UpdateFractal:
"""A class for animating an IFS by slowly iterating it"""
def __init__(self,ax,ifs):
self.ifs = ifs
self.line, = ax.plot([], [], 'k-',lw=2)
self.ax = ax
#set parameters
self.ax.axis([-1,2,-1,2])
def get_plot_points(self):
"""Get plottable X and Y values from the IFS points"""
points = self.ifs.get_points()
X = points.real
Y = points.imag
return X,Y
def init(self):
X,Y = self.get_plot_points()
self.line.set_data(X,Y)
return self.line
def __call__(self,i):
self.ifs.iterate()
X,Y = self.get_plot_points()
self.line.set_data(X,Y)
return self.line
fig, ax = plt.subplots()
dragon = dragon_ifs()
uf = UpdateFractal(ax,dragon)
anim = FuncAnimation(fig, uf, frames=np.arange(15), init_func=uf.init,
interval=200, blit=True)
#Show animation
HTML(anim.to_html5_video())
Side Note: I saw this also unanswered question: FuncAnimation not iterable and I think they may be facing the a similar problem. I noticed they have blit set to True and if I had enough reputation I would comment and ask them if setting blit to False "fixes" their problem.
As the error suggests, the return of the animating function needs to be an iterable.
Replace the line return self.line by
return self.line,
(note the comma)
Related
I am trying to create an animation containing a fixed sphere and a trajectory on the surface of the sphere, with a trail of the trajectory containing the last "windowSize" points in the trajectory.
Now, for the purposes of the code I will show here, I won't have an actual such trajectory, but rather just some random points changing each frame.
I am using matplotlib.animation.FuncAnimation. When I use the option blit=False, the animation works as expected. However, I would like to use blit=True to optimize performance.
When I do that, though, what happens is that nothing seems to happen in the animation, except that when I rotate the figure, then it shows an updated version of the figure (some number of frames ahead) and then freezes again.
The code below is based on this similar question.
Let me show the code I am using
import numpy as np
from matplotlib import pyplot as plt
import matplotlib.animation
import pandas as pd
Np = 5000
windowSize = 1000
m = np.random.rand(Np, 3)
df = pd.DataFrame({ "x" : m[0:Np,0], "y" : m[0:Np,1], "z" : m[0:Np,2]})
def init_graph():
u, v = np.mgrid[0:2*np.pi:50j, 0:np.pi:50j]
x = np.cos(u)*np.sin(v)
y = np.sin(u)*np.sin(v)
z = np.cos(v)
ax.plot_surface(x, y, z, color="bisque", alpha=0.3)
return graph,
def update_graph(num):
if (num<windowSize):
graph._offsets3d = (df.x[0:num], df.y[0:num], df.z[0:num])
else:
graph._offsets3d = (df.x[(num-windowSize):num], df.y[(num-windowSize):num], df.z[(num-windowSize):num])
title.set_text('3D Test, time={}'.format(num))
return graph,
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
ax.set_box_aspect((1,1,1))
title = ax.set_title('3D Test')
graph = ax.scatter(0, 0, 0)
ani = matplotlib.animation.FuncAnimation(fig, update_graph, frames=Np, init_func=init_graph, interval=200, blit=True, repeat=False)
plt.show()
m is an Np by 3 matrix, and each row represents a 3d point (in my real use case, each row is a point in a trajectory on the sphere surface, but for this demo I created m as random numbers).
I create a variable graph that contains a scatter plot, which I believe is an Artist. This is what I return from both the init_func and the updating func which are passed to FuncAnimation (as per the docs).
From what I read, you return an iterable of the Artists which will be updated in the animation. Thus I return a tuple of one element, graph,.
Now, in update_graph, the updating function for the animation, I am updating the scatter plot using graph._offsets3d, which I read in another question here on StackOverflow. I am not totally sure if this is the way to do it and I didn't find much information in the docs about whether to use this or one of the setting methods on the scatter plot.
Why doesn't blitting work with scatter plots?
I am currently doing an experiment with perlin noise, but generating this noise uses a series of plotted markers. Like, a bunch of them, and i need to render a lot of graphs, rendering just 20 takes around 15 minutes. Is there any way to speed up my code?
import matplotlib.pyplot as plt
import math, random, noise, time
import numpy as np
def GeneratePlot(x:int,y:int) -> list:
pnoiseValues = []
fig, ax = plt.subplots()
for X in range(x**2):
plt.gcf().canvas.flush_events()
marker = str(random.choice(("o", "s")))
YPOS = y+noise.pnoise2(X*x/x**2/50, y/x**2/50)
ax.scatter(X, YPOS, marker=marker)
pnoiseValues.append(YPOS)
plt.show()
return np.array(pnoiseValues)
def GetResults(amount:int, useDelay=True) -> list:
results = []
for i in range(amount):
print(f"Generating Images & Arrays.. (This may take a while depending on the # of points)")
time.sleep(.100 if useDelay else 0)
results.append(GeneratePlot(i**2,i//2**2))
print(results)
return results;
GetResults(16)
So I haven’t tried anything yet, since i am new to matplotlib
Use blit which could speed up the plot largely. In your example you need re-plot figure every time when you get some data, however blit just plot some part of artist in the graph and use the fig from last time plot as background. My suggestion is to ceate a new class which could receive the AXES class for constructor and append new data everytime and replot. Here is a example from my real-time recording application. This is a very simple demo, and did not update the axis. if you want to update the axis at some time, you can do ax.draw_artist(axis). In my i5 8250 cpu, the speed could have ~100 fps. some official document https://matplotlib.org/stable/tutorials/advanced/blitting.html
class DisplayRealTime():
def __init__(self, ax:Axes):
self.ax = ax
self.ax.plot(0, 0, animated = True, linewidth = 1)
self.line = ax.lines
self.fig = ax.figure
self.fig.canvas.draw()
self.fig.canvas.flush_events()
self.bg = ax.figure.canvas.copy_from_bbox(ax.figure.bbox)
def append(self, data:np.ndarray):
self.fig.canvas.restore_region(self.bg)
self.line[0].set_data(data)
for i in self.line:
self.ax.draw_artist(i)
self.fig.canvas.blit(self.fig.bbox)
self.fig.canvas.flush_events()\nself.bg = self.ax.figure.canvas.copy_from_bbox(self.ax.figure.bbox)
I am trying to use matplotlib's FuncAnimation to make an animated video. Each frame is just a boolean n x n array visualised as white/black squares. I can do this successfully by defining all the arrays in advance and then going through them one by one. This uses code similar to matplotlib's example.
My items are rather large and I want to run the simulation for a long time. I thus don't want to create the entire list of arrays then go through them one by one. Instead, I want to define the animate function to do each step. Let me explain with a minimal non-working example. My actual example includes far larger arrays!
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation
def create_video(n):
global X
X = np.random.binomial(1, 0.3, size = (n,n))
fig = plt.figure()
im = plt.imshow(X, cmap = plt.cm.gray)
def animate(t):
global X
X = np.roll(X, +1, axis = 0)
im.set_array(X)
anim = FuncAnimation(
fig,
animate,
frames = 100,
interval = 1000 / 30,
blit = True
)
return anim
anim = create_video(10)
This initialises some random 10 x 10 set of 0/1s then just 'rolls' it at each step. I get an error.
RuntimeError: The animation function must return a sequence of Artist objects.
If I remove the return anim, replacing it with pass, and replacing anim = create_video(10) with create_video(10), then I get a warning.
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.
Clearly, I don't understand well enough FuncAnimation. What I want to happen is for the function animate to update the array X, by 'rolling' it one step, as well as doing im.set_array(X).
As explained in this answer:
As the error suggests, and as can be seen e.g. in the
simple_animation example, but also from the FuncAnimation
documentation, the init_func as well as the updating func are
supposed to return an iterable of artists to animate.
The documentation does not say that this is actually only needed when
using blit=True, but since you are using blitting here, it is
definitely needed.
So you have two ways:
add
return im,
to animate function
set blit = False in FuncAnimation
Complete Code
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation
def create_video(n):
global X
X = np.random.binomial(1, 0.3, size = (n, n))
fig = plt.figure()
im = plt.imshow(X, cmap = plt.cm.gray)
def animate(t):
global X
X = np.roll(X, +1, axis = 0)
im.set_array(X)
return im,
anim = FuncAnimation(
fig,
animate,
frames = 100,
interval = 1000/30,
blit = True
)
plt.show()
return anim
anim = create_video(10)
UPDATE: It is an issue getting the animation to compile and run within Jupyter itself. As an easy workaround, I am just going to compile animations in IDLE, save them as a gif, then import them into my notebook via markdown.
I am trying to animate different pendulum systems within a Jupyter notebook. I am solving the differential equations for the systems, then creating a list of angle values to plot versus time (that part is easy ;). I have created a dummy list of theta values to simplify things for this post. The first goal is to get a line with a fixed point to sway back and forth (like a pendulum), eventually adding a mass on the end of it for the aesthetics. Here is my code:
%matplotlib inline
import matplotlib.animation as animation
import matplotlib.pyplot as plt
import numpy as np
l = 0.25 # length of pendulum
# create a list of angles, convert to radians
theta_points_approx = [5.0,4.8,4.2,3.2,2.1,1.9,0.2,-1.2,-2.2,-3.1,-3.9,-4.6,-5.0]
theta_points_approx_rad = []
for ang in theta_points_approx:
theta_points_approx_rad.append(ang*np.pi/180)
# create blank window for animation
fig = plt.figure()
axis = plt.axes()
pend_approx, = axis.plot([], [], lw=2)
#create a function to draw the background of our animations
def init():
pend_approx.set_data([], [])
return(pend_approx,)
# initialize empty sets for x and y coordinates
x_points_to_plot = []
y_points_to_plot = []
# animation function
def animate(theta):
x = [0,l*np.sin(theta)]
y = [0,-l*np.cos(theta)]
pend_approx.set_data(x,y)
return(pend_approx,)
# compile animation
anim = animation.FuncAnimation(fig, animate, init_func=init, frames=theta_points_approx_rad, interval=40, blit=True)
plt.show()
This just outputs an empty plot. I have spent the evening watching YT videos and reading through animation examples trying to get this solved, but to no avail. I am not sure what to even try from here. Any advice would be greatly appreciated.
Edit: It was not because I didn't have the show() function at the end. Even with show I still get an empty plot
I'm trying to animate a 3D array in python using the first dimension as time.
I'm not sure where I am going wrong, as I recieve no error with this code. But my animation is stationary, stuck on the first page of the array.
import numpy as np
from matplotlib import pyplot as plt
from matplotlib.animation import FuncAnimation
array = np.random.random(size=(10, 20, 30))
empty = np.zeros(array[0].shape)
fig = plt.figure()
mat = plt.imshow(empty)
def func(i):
mat.set_data(array[i])
return mat
frames = len(array)
FuncAnimation(fig, func, frames)
plt.show()
I'd like the use the below code but I haven't seen an annonymous function used anywhere with FuncAnimation. It produces the same result except mat is not created setting the initial axes.
fig = plt.figure()
func = lambda i: plt.imshow(array[i])
frames = len(array)
FuncAnimation(fig, func, frames)
plt.show()
The main difference between your code and any example you find on matplotlib animations is that you do not actually store the FuncAnimation. Depending on how you run things it would then directly be garbage-collected.
ani = FuncAnimation(...)