I'm using pyplot 2.0 with the FuncAnimation for streaming serial data to a figure where it is continously updated.
For the FuncAnimation to work you have a Figure, Func (function to call at each frame), and Frames (Source of data to pass func and each frame of the animation) fairly simple I thought and it does animate the way I would expect.
However the figure continues to chew up memory until it crashes the Raspberry Pi even though I have the blit flag set to optimize the drawing and only render the new data points.
Here is the code:
import matplotlib.pyplot as plt
import matplotlib.animation as animation
# Setup plotting, one figure and two subplots
fig, (ax1, ax2) = plt.subplots(2,1)#,sharex=True)
g_line = ax1.plot([],[],'g')
r_line = ax1.plot([],[],'r')
lines = []
# Add each line to o ur tuples of lines to be plotted
lines.extend(g_line)
lines.extend(r_line)
# Data plotting function, this is call from the animation
# Each data element is in the data list being returned below
def plot_data(data):
x1 = data[0]; y1 = data[1];
x2 = data[2]; y2 = data[3];
lines[0].set_data(x1,y1) # Audio - green
lines[1].set_data(x2,y2) # Audio thresh - red
return tuple(lines)
def data_gen():
my_list = []
my_list2 = []
# Main infinite loop for processing of serial data
while True:
# Grab input RX serial data
(my_list, my_list2) = readUART(serReader)
yield my_list, my_list2
#end while
ani = animation.FuncAnimation(fig, plot_data, data_gen, interval=1, blit=True)
plt.show()
I have tried to clear the figure after each iteration but that severly slows down the rendering.
Is there some memory i'm not aware of that is holding onto it each time it plots even when the plotting fills the figure completely and moves off the screen?
Note - I ran a memory profiler on the entire program minus the plotting and just did it on the while TRUE loop and the memory didn't move past 55mb the entire time I let it run.
Related
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(...)
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 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: