How can I generate matplotlib graphs faster - python

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)

Related

Why doesn't blitting work with animation of a scatter plot in matplotlib?

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?

'Line2D' object is not iterable error when blit=true matplotlib FuncAnimation

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)

Increase the speed of redrawing contour plot in matplotlib

I have a python program that plots the data from a file as a contour plot for each line in that text file. Currently, I have 3 separate contour plots in my interface. It does not matter if I read the data from a file or I load it to the memory before executing the script I can only get ~6fps from the contour plots.
I also tried using just one contour and the rest normal plots but the speed only increased to 7fps. I don't believe that it is so computationally taxing to draw few lines. Is there a way to make it substantially faster? Ideally, it would be nice to get at least 30fps.
The way I draw the contour is that for each line of my data I remove the previous one:
for coll in my_contour[0].collections:
coll.remove()
and add a new one
my_contour[0] = ax[0].contour(x, y, my_func, [0])
At the beginning of the code, I have plt.ion() to update the plots as I add them.
Any help would be appreciated.
Thanks
Here is an example on how to use a contour plot in an animation. It uses matplotlib.animation.FuncAnimation which makes it easy to turn blitting on and off.
With blit=True it runs at ~64 fps on my machine, without blitting ~55 fps. Note that the interval must of course allow for the fast animation; setting it to interval=10 (milliseconds) would allow for up to 100 fps, but the drawing time limits it to something slower than that.
import matplotlib.pyplot as plt
import matplotlib.animation
import numpy as np
import time
x= np.linspace(0,3*np.pi)
X,Y = np.meshgrid(x,x)
f = lambda x,y, alpha, beta :(np.sin(X+alpha)+np.sin(Y*(1+np.sin(beta)*.4)+alpha))**2
alpha=np.linspace(0, 2*np.pi, num=34)
levels= 10
cmap=plt.cm.magma
fig, ax=plt.subplots()
props = dict(boxstyle='round', facecolor='wheat')
timelabel = ax.text(0.9,0.9, "", transform=ax.transAxes, ha="right", bbox=props)
t = np.ones(10)*time.time()
p = [ax.contour(X,Y,f(X,Y,0,0), levels, cmap=cmap ) ]
def update(i):
for tp in p[0].collections:
tp.remove()
p[0] = ax.contour(X,Y,f(X,Y,alpha[i],alpha[i]), levels, cmap= cmap)
t[1:] = t[0:-1]
t[0] = time.time()
timelabel.set_text("{:.3f} fps".format(-1./np.diff(t).mean()))
return p[0].collections+[timelabel]
ani = matplotlib.animation.FuncAnimation(fig, update, frames=len(alpha),
interval=10, blit=True, repeat=True)
plt.show()
Note that in the animated gif above a slower frame rate is shown, since the process of saving the images takes a little longer.

Matplotlib Scatterplot Animation with Time Stamp

Hello and thanks in advance for any tips and advice. I'm trying to create an animated scatterplot that changes dot color based on model results that I'm reading into python. The model results would be much easier to interpret if I could display an updating time stamp along with the animation.
I tried to incorporate the answer for to this post:
Matplotlib animating multiple lines and text, but I think the fact that I'm using a scatter dataset instead of lines complicates the way the data needs to be returned, and I'm not sure how to correct the problem.
This code alternately flashes the time step and the scatter animation, which makes for a distracting and useless visual.
# load result
testModel.addResult(testDye,type='dye')
xs = testModel.grid['x']
ys = testModel.grid['y']
zs = testModel.grid['z']
# graphing
fig = plt.figure()
ax = fig.add_subplot(111)
gcf = plt.gcf()
scat = ax.scatter(xs,ys)
timetext = gcf.text(0.2, 0.2, '', size = 14)
def animate(i):
print i
actDye = testModel.getResult(type='dye',layer=1,tIndex=i)
scat.set_array((actDye/1000)*100) #update colors of points
timetext.set_text(i)
return scat,
def init():
actDye = testModel.getResult(type='dye',layer=1,tIndex=0)
scat.set_array((actDye/1000)*100) #update colors of points
return scat,
ani = animation.FuncAnimation(fig,animate,np.arange(0,200),init_func=init, interval=500, blit=True)
plt.show()
I think that returning timetext along with scat, would fix the problem (like it did for the other poster), but I can't get the syntax right. Switching to this block of code gives me the error that the 'PathCollection' object is not iterable.
def animate(i):
print i
actDye = testModel.getResult(type='dye',layer=1,tIndex=i)
scat.set_array((actDye/1000)*100) #update colors of points
timetext.set_text(i)
return tuple(scat,) + (timetext,)
What should I be doing differently? Thanks!

Python realtime plotting

I acquire some data in two arrays: one for the time, and one for the value. When I reach 1000 points, I trigger a signal and plot these points (x=time, y=value).
I need to keep on the same figure the previous plots, but only a reasonable number to avoid slowing down the process. For example, I would like to keep 10,000 points on my graph.
The matplotlib interactive plot works fine, but I don't know how to erase the first points and it slows my computer very quickly.
I looked into matplotlib.animation, but it only seems to repeat the same plot, and not really actualise it.
I'm really looking for a light solution, to avoid any slowing.
As I acquire for a very large amount of time, I erase the input data on every loop (the 1001st point is stored in the 1st row and so on).
Here is what I have for now, but it keeps all the points on the graph:
import matplotlib.pyplot as plt
def init_plot():
plt.ion()
plt.figure()
plt.title("Test d\'acqusition", fontsize=20)
plt.xlabel("Temps(s)", fontsize=20)
plt.ylabel("Tension (V)", fontsize=20)
plt.grid(True)
def continuous_plot(x, fx, x2, fx2):
plt.plot(x, fx, 'bo', markersize=1)
plt.plot(x2, fx2, 'ro', markersize=1)
plt.draw()
I call the init function once, and the continous_plot is in a process, called every time I have 1000 points in my array.
The lightest solution you may have is to replace the X and Y values of an existing plot. (Or the Y value only, if your X data does not change. A simple example:
import matplotlib.pyplot as plt
import numpy as np
import time
fig = plt.figure()
ax = fig.add_subplot(111)
# some X and Y data
x = np.arange(10000)
y = np.random.randn(10000)
li, = ax.plot(x, y)
# draw and show it
ax.relim()
ax.autoscale_view(True,True,True)
fig.canvas.draw()
plt.show(block=False)
# loop to update the data
while True:
try:
y[:-10] = y[10:]
y[-10:] = np.random.randn(10)
# set the new data
li.set_ydata(y)
fig.canvas.draw()
time.sleep(0.01)
except KeyboardInterrupt:
break
This solution is quite fast, as well. The maximum speed of the above code is 100 redraws per second (limited by the time.sleep), I get around 70-80, which means that one redraw takes around 4 ms. But YMMV depending on the backend, etc.
Use a fixed size array and plot that using matplot.
import collections
array = collections.deque([None] * 1000, maxlen=1000)
Whenver you append to the array it will remove the first element.
I know I'm late to answer this question, bt for your issue you could look into the "joystick" package. It is based on the line.set_data() and canvas.draw() methods, with optional axes re-scaling. It also allows for interactive text logging or image plotting (in addition to graph plotting).
No need to do your own loops in a separate thread, the package takes care of it, just give the update frequency you wish. Plus the console remains available for additional monitoring commands.
See http://www.github.com/ceyzeriat/joystick/ or https://pypi.python.org/pypi/joystick (use pip install joystick to install)
try:
import joystick as jk
import numpy as np
import time
class test(jk.Joystick):
# initialize the infinite loop decorator
_infinite_loop = jk.deco_infinite_loop()
def _init(self, *args, **kwargs):
"""
Function called at initialization, see the doc
"""
self._t0 = time.time() # initialize time
self.xdata = np.array([self._t0]) # time x-axis
self.ydata = np.array([0.0]) # fake data y-axis
# create a graph frame
self.mygraph = self.add_frame(jk.Graph(name="test", size=(500, 500), pos=(50, 50), fmt="go-", xnpts=10000, xnptsmax=10000, xylim=(None, None, 0, 1)))
#_infinite_loop(wait_time=0.2)
def _generate_data(self): # function looped every 0.2 second to read or produce data
"""
Loop starting with the simulation start, getting data and
pushing it to the graph every 0.2 seconds
"""
# concatenate data on the time x-axis
self.xdata = jk.core.add_datapoint(self.xdata, time.time(), xnptsmax=self.mygraph.xnptsmax)
# concatenate data on the fake data y-axis
self.ydata = jk.core.add_datapoint(self.ydata, np.random.random(), xnptsmax=self.mygraph.xnptsmax)
self.mygraph.set_xydata(t, self.ydata)
t = test()
t.start()
t.stop()
To be totally interactive, you could use Bokeh for this. Concretely you could use an update function that is called every X ms and stream the new data.
Here there is a fragment I use:
def update():
candle_data.stream(new_data, 300)
plot = figure(x_axis_type='datetime',x_range=(start_day, final_day), width=1500, height=900, title='Live Chart', sizing_mode='scale_both')
plot.segment(x0='time', y0='highest', x1='time', y1='lowest', color='black', source=candle_data)
plot.vbar(x='time', width = 0.5*60*60*50 ,bottom='open', top='close',fill_color='color', line_color='black', source = candle_data)
doc.add_root(column([plot]))
doc.add_periodic_callback(update, 20000)
doc.title = "Candle Data Live Rates"

Categories

Resources