Python realtime plotting - python

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"

Related

How can I generate matplotlib graphs faster

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)

Automatically update a graph based on parameter changes

I want a graph to update when parameter values are changed or periodically. I have the following code:
a=0`
b=50
c=100
def sine(x,y,l):
A=numpy.zeros(l)
for i in range(l):
A[i]=numpy.sin(2*math.pi*(i+x)/y)
return A
def plot(l):
matplotlib.pyplot.clf()
matplotlib.pyplot.plot(l)
plot(sine(a,b,c))`
`
How do I get the plot function to re-run every time a/b/c is updated or periodically?
So a few things here, you should learn about the proper use of numpy ufuncs which operate on ndarrays without having to loop over them:
https://docs.scipy.org/doc/numpy-1.12.0/reference/ufuncs.html
Secondly, you have to have a place where an update event is triggered, for example:
http://openbookproject.net/thinkcs/python/english3e/events.html
Since there are no such examples in this code, I will just assume you know where that is going to happen. Wherever that happens, then you need a handle of the line in order to update the data.
https://matplotlib.org/api/lines_api.html#matplotlib.lines.Line2D.set_xdata
https://matplotlib.org/api/lines_api.html#matplotlib.lines.Line2D.set_ydata
Here is some example code highlighting what I believe you are trying to do:
import numpy as np
import matplotlib.pyplot as plt
l = 100
x = np.linspace(0, 2 * np.pi, l)
y = np.sin(x)
fig, ax = plt.subplots(1, 1) # generate figure and axes objects so that we have handles for them
curve = ax.plot(x, y)[0] # capture the handle for the lines object so we can update its data later
# now some kind of event happens where the data is changed, and we update the plot
y = np.cos(x) # the data is changed!
curve.set_ydata(y)
# necessary if you are just executing this as a script
# this example is a little more clear if executed stepwise in ipython
plt.show(block=True)

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 animated plot includes previous data

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.

How can I dynamically update my matplotlib figure as the data file changes?

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:

Categories

Resources