I'm trying to use blitting with pylab to animate my plots at a fast frame rate; the code below seems to work fine, but plots new data on top of the old rather than replotting, so that I end up with a figure filling up with lines rather than one animated line (in each subplot). Any suggestions to get a single animated line (in each subfigure) at as fast a frame rate as possible greatly appreciated.
import pylab
import time
import threading
import random
import math
time_series, cos_series, sin_series = [], [], []
MAX = 100
# This generates new data for the plot
def data_generator():
while True:
time.sleep(0.1)
ts = time.time()
time_series.append(ts)
cos_series.append(math.sin( ts ))
sin_series.append(math.cos( ts ))
if len(cos_series) > MAX:
cos_series.pop(0)
if len(sin_series) > MAX:
sin_series.pop(0)
if len(time_series) > MAX:
time_series.pop(0)
if __name__ == '__main__':
# Run the receiving function in a separate thread.
thread = threading.Thread(target=data_generator)
thread.daemon = True
thread.start()
fig = pylab.figure()
ax = fig.add_subplot(211)
bx = fig.add_subplot(212)
ax.grid(True)
bx.grid(True)
print(len(time_series),len(sin_series),len(cos_series))
fig.show()
fig.canvas.draw()
line1, = ax.plot(time_series, sin_series, '-.k', animated=True)
line2, = bx.plot(time_series, cos_series, 'r+-', animated=True)
ax.legend(['Sin(x)'])
bx.legend(['Cos(x)'])
ax.set_ylim([-1,1])
bx.set_ylim([-1,1])
background_a = [fig.canvas.copy_from_bbox(ax.bbox)]
background_b = [fig.canvas.copy_from_bbox(bx.bbox)]
timer = 0
t_start = time.time()
# Continuously update plot
while True:
time.sleep(0.1)
line1.set_data(time_series,sin_series)
ax.set_xlim([time_series[0],time_series[-1]])
line2.set_data(time_series,cos_series)
bx.set_xlim([time_series[0],time_series[-1]])
ax.draw_artist(line1)
bx.draw_artist(line2)
fig.canvas.restore_region(background_a)
fig.canvas.restore_region(background_b)
fig.canvas.blit(ax.bbox)
fig.canvas.blit(bx.bbox)
timer += 1
print('FPS = ',timer/(time.time() - t_start))
There are two problems with your code.
Firstly, when you do this:
background_a = [fig.canvas.copy_from_bbox(ax.bbox)]
background_b = [fig.canvas.copy_from_bbox(bx.bbox)]
you shouldn't put your buffer objects in a list - restore_region just takes the buffer objects directly, so you should just do this instead:
background_a = fig.canvas.copy_from_bbox(ax.bbox)
background_b = fig.canvas.copy_from_bbox(bx.bbox)
Secondly, in your rendering loop you need to restore the background before you draw any of your updated line artists on top, otherwise you'll always be drawing the background on top of your moving lines. Move those lines above your draw_artist calls, like this:
fig.canvas.restore_region(background_a)
fig.canvas.restore_region(background_b)
ax.draw_artist(line1)
bx.draw_artist(line2)
fig.canvas.blit(ax.bbox)
fig.canvas.blit(bx.bbox)
Now everything should work fine.
Update
If you want the x-axis to also be updated during the animation, things get a little bit more complicated. Firstly you'll need to set the x-axis to be animated for both sets of axes:
ax = fig.add_subplot(211)
bx = fig.add_subplot(212)
ax.xaxis.set_animated(True)
bx.xaxis.set_animated(True)
The axis bounding box (ax.bbox) doesn't contain the tick labels, so in order to get a large enough region to restore during the rendering loop you'll need to cache a larger region of the figure canvas, e.g. the whole figure bounding box:
figbackground = fig.canvas.copy_from_bbox(fig.bbox)
And to restore the background:
fig.canvas.restore_region(figbackground)
At each timepoint you need to force the x-axis to be re-drawn as well as the lines:
ax.draw_artist(line1)
bx.draw_artist(line2)
ax.xaxis.draw(fig.canvas.renderer)
bx.xaxis.draw(fig.canvas.renderer)
And finally, when you do the blitting you need to use the axes clipboxes, which contain the tick labels, rather than the bounding boxes, which do not:
fig.canvas.blit(ax.clipbox)
fig.canvas.blit(bx.clipbox)
With these changes the tick labels and the x-gridlines will get updated, but so far I haven't figured out how exactly to get the y-gridlines and the legend to be drawn correctly. Hopefully this gives you some idea of how to go about doing this.
Also tcaswell is right to suggest looking at the Animation class - for your case it might work out to be a lot simpler, although I think it's also good to have an understand of how blitting works under the hood.
Related
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.
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"
I am plotting data in a plot using wxPython where the data limits on the y- axis are changing with the data. I would like to change the axis dynamically without redrawing the whole canvas like canvas.draw() rather I'd like to use blitting for this as I do for the plot itself.
What I got to work is the changing y-axis, and I get the yticks animated with the plot, unfortunately the ylabels are gone and I cant find the solution. The reason is setting the get_yaxis().set_animated(True) setting for the axis.
I put together a little working example in the following.
What am I missing here?
import matplotlib
matplotlib.use('WXAgg')
import wx
import pylab as p
import numpy as npy
from time import sleep
ax = p.subplot(111)
canvas = ax.figure.canvas
x = npy.arange(0,2*npy.pi,0.01)
line, = p.plot(x, npy.sin(x), animated=True)
ax.get_yaxis().set_animated(True)
def update_line(*args):
if update_line.background is None:
update_line.background = canvas.copy_from_bbox(ax.bbox)
for i in range(20):
canvas.restore_region(update_line.background)
line.set_ydata((i/10.0)*npy.sin(x))
ax.set_ylim(-1*i/5.0-0.5,i/5.0+0.5)
ax.draw_artist(ax.get_yaxis())
ax.draw_artist(line)
canvas.blit(ax.bbox)
sleep(0.1)
print 'end'
update_line.cnt = 0
update_line.background = None
wx.EVT_IDLE(wx.GetApp(), update_line)
p.show()
Basically I am looking for something like get_ylabels().set_animated(True) but I cant find it.
It looks like the labels are drawn but the blit command doesn't copy them over to the canvas because the bounding box only includes the inner part of the axes.
For me changing update_line.background = canvas.copy_from_bbox(ax.bbox) to update_line.background = canvas.copy_from_bbox(ax.get_figure().bbox) and canvas.blit(ax.bbox) to canvas.blit(ax.clipbox) made it work.
I have a python script that reads in a data file and displays one figure with four plots using the matplotlib library. The data file is being updated every few seconds since it is an output file for a different piece of software that is running concurrently. I would like the four plots in my matplotlib figure to refresh themselves using the updated data file every 20 seconds. The way I've implemented this is as follows:
import pylab as pl
import time
pl.ion()
fig = pl.figure()
while True:
f = open('data.out', 'rb')
#code to parse data and plot four charts
ax = fig.add_subplot(2,2,1)
#...
ax = fig.add_subplot(2,2,4)
#...
pl.draw()
time.sleep(20)
This works, but I lose functionality of the zoom and pan buttons which normally work if pl.show() is called. This is not optimal. However, if pl.show() is substituted for pl.draw(), the script no longer updates the plots. Is there a way to dynamically update a plot without completely losing the zoom/pan functionality?
Your code is a little too vague to know what is going on.
I can offer this:
You should retain normal functionality if you create your subplots once, saving all the axes objects and then calling show().
Subsequent changes to those subplots could be done like this:
#inside while loop
for i in #subplotlist
ax[i].clear() #ax[i] is the axis object of the i'th subplot
ax[i].plot(#plotstuff)
ax[i].draw()
The toolbar for zooming and panning can be added by hand if you so desire.
As you are developping a sofware, I supposed you may have a multi-threaded approach.
So in this case using an infinite while loop is a bad idea, like you are holding up your main thread.
In addition when it comes to GUI it’s also a bad idea to interfere abruptly with GUI internal threads (wxPython for instance) and you should have an event driven design approach in order to not abruptly interrupt other threads (and that will cause the crash of your application).
The use of a timer will do the job.
A timer would do these actions in the following script :
1/ call a function to clear previous artist
2 / replot the data
3/ apply changes to canvas
4/ create another identical timer in the following design way : a timer who calls another identical timer after doing its job
Like I do not have access to your datas, I created a random data provider for the illustration.
The defined variable delay_repeat allows you to program in seconds the refresh.
import pylab as pl
import random
from threading import Timer
def dataprovider():
return [random.randint(0, 8) for i in range(8)]
def random_col():
return ['blue', 'red', 'green', 'orange'][random.randint(0,3)]
# .... #
fig = pl.figure()
axes = [fig.add_subplot(2,2,i) for i in range(1,5)]
paths = [ax.scatter(x=dataprovider(), y=dataprovider(), marker = '+', c=random_col()) for ax in axes]
# .... #
def clear_arts(paths, all_arts=-1):
if all_arts < 0:
all_arts = len(paths)
for path in paths[:all_arts]:
path.remove()
def refresh_arts(paths, delay_repeat):
# 1 - clear previous artists
clear_arts(paths,all_arts=-1)
# 2 - Get artists paths for cleaning
paths = [ax.scatter(x=dataprovider(), y=dataprovider(), marker = '+', c=random_col()) for ax in axes]
# 3 - Apply changes
fig.canvas.draw_idle()
# 4 - Create another timer
Timer(delay_repeat, refresh_arts, (paths, delay_repeat)).start()
# 4- Create a timer that will run function with arguments args and keyword arguments kwargs,
# after interval seconds have passed.
delay_repeat = 2
Timer(delay_repeat, refresh_arts, (paths, delay_repeat)).start()
# print("process continues here")
pl.show()
You can do it like this. It accept x,y as list and output a scatter plot plus a linear trend on the same plot.
from IPython.display import clear_output
from matplotlib import pyplot as plt
%matplotlib inline
def live_plot(x, y, figsize=(7,5), title=''):
clear_output(wait=True)
plt.figure(figsize=figsize)
plt.xlim(0, training_steps)
plt.ylim(0, 100)
x= [float(i) for i in x]
y= [float(i) for i in y]
if len(x) > 1:
plt.scatter(x,y, label='axis y', color='k')
m, b = np.polyfit(x, y, 1)
plt.plot(x, [x * m for x in x] + b)
plt.title(title)
plt.grid(True)
plt.xlabel('axis x')
plt.ylabel('axis y')
plt.show();
you just need to call live_plot(x, y) inside a loop. here's how it looks:
My problem is:
I have Matplotlib figure in PyGTK application, that is constatly updated each few seconds. I've added abbility to save figure to disk as PNG file. After calling figure.savefig(filename, other parameters) my figure in application stops being updated.
Figure initialization phase:
# setup matplotlib stuff on empty space in vbox4
figure = Figure()
canvas = FigureCanvasGTK(figure) # a gtk.DrawingArea
canvas.show()
self.win.get_widget('vbox4').pack_start(canvas, True, True) # this will be aded to last place
self.win.get_widget('vbox4').reorder_child(canvas, 1) #place plot to space where it should be
Figure is being updated this way (this called each few seconds in separate thread):
def _updateGraph(self, fig, x, x1, y):
#Various calculations done here
fig.clf()#repaint plot: delete current and formate a new one
axis = fig.add_subplot(111)
#axis.set_axis_off()
axis.grid(True)
#remove ticks and labels
axis.get_xaxis().set_ticks_position("none")
for i in range(len(axis.get_xticklabels())): axis.get_xticklabels()[i].set_visible(False)
axis.get_yaxis().set_ticks_position("none")
axis.plot(numpy.array(x),numpy.array(y)/(1.0**1), "k-" ,alpha=.2)
axis.set_title('myTitle')
fig.autofmt_xdate()
fig.canvas.draw()
everything works as expected. But after calling:
figure.savefig(fileName, bbox_inches='tight', pad_inches=0.05)
File have been saved, BUT my figure on screen stops being updated.
Any ideas how do I save figure to disk and still be able to update my fig on screen ?
Have you tried updating the line data instead of recreating the figure? This assumes the number of datapoints doesn't change each frame. It might help issue of things refusing to update, and at the least it will be faster.
def _updateGraph(self, fig, x, x1, y):
#Various calculations done here
ydata = numpy.array(y)/(1.0**1)
# retrieved the saved line object
line = getattr(fig, 'animated_line', None);
if line is None:
# no line object so create the subplot and axis and all
fig.clf()
axis = fig.add_subplot(111)
axis.grid(True)
#remove ticks and labels
axis.get_xaxis().set_ticks_position("none")
for i in range(len(axis.get_xticklabels())):
axis.get_xticklabels()[i].set_visible(False)
axis.get_yaxis().set_ticks_position("none")
xdata = numpy.array(x);
line = axis.plot(xdata, ydata, "k-" ,alpha=.2)
axis.set_title('myTitle')
fig.autofmt_xdate()
# save the line for later reuse
fig.animated_line = line
else:
line.set_ydata(ydata)
fig.canvas.draw()
I have found a work-a-round to this. As my figure refuses to be updated after calling figure.savefig() so i found a way how to work a round it. My figure is within HBox2 container (GUI is created with Glade 3.6.7) as first element
# some stuff going
figure.saveFig(fileName)
# WORK-A-ROUND: delete figure after calling savefig()
box = self.win.get_widget('hbox2')
box.remove(box.get_children()[0])
self._figPrepare()
def _figPrepare(self): #initialize graph
figure = Figure()
canvas = FigureCanvasGTK(figure) # a gtk.DrawingArea
canvas.show()
figure.clf()
gui.w().set("figure", figure)
self.win.get_widget('hbox2').pack_start(canvas, True, True) # this will be aded to last place
self.win.get_widget('hbox2').reorder_child(canvas, 0) #place plot to space where it should be
I know this is not best practice, and probably is slow, but it work OK for me. Hope someone else will find this useful
from http://matplotlib.org/examples/user_interfaces/embedding_in_gtk2.html
what seems to help is the "agg" not sure what that means but fixed this bug for me :)
from matplotlib.backends.backend_gtkagg import FigureCanvasGTKAgg as FigureCanvas