Completely update matplotlib figure (not appending data) - python

Edit: I got it to work by omitting matplotlib animation completely and adding plt.ion() before initializing the figures.
I have a measurement device that sends data to my PC that I can read with Python. Every second it sends 100 data points. Every time these 100 points are received, they can be obtained by calling a function. I want to update the figure, starting from X=0 again. The data contains X and Y data.
In other SO threads I found that matplotlib.animation.FuncAnimation can update the graph efficiently, but I have only found examples where data is appended to the graph.
At the moment, my graph is redrawing, but it just draws new points without clearing the previous graph and it is really slow (I can't drag the plot window while it's drawing). The clear function just leaves me with a grey window.
Does anyone know a fast enough workaround for this problem? Thanks a lot!
My current implementation:
def plot():
freq = np.linspace(f_start, f_stop, points, endpoint=True)
def update(frame):
#for i in range (iterations - 1):
print(frame)
complex_data = vna.data(0)
plotFigure(figInitProperties, freq, Rect2Pol(complex_data))
time.sleep(2) # Wait for all points to be scanned
ani = anim.FuncAnimation(figInitProperties[2], func=update, frames=(10), repeat=False)
plt.show()
plot()
figInitProperties returns the fig (size, labels, axis, ticks etc.) of the figure.

You can use ax.clear() or plt.cla() at the beginning of the update function.

Related

Plotting real-time gaze data in python

I am writing a program which reads in data from a pupil tracker. The system uses msgpack to encode data into dictionaries. I subscribe to the gaze topic to get the gaze data and from there can call specific values from the dictionary. In this case, I am plotting the x gaze coordinate over time. I can read out the values in real time but have been struggling to get the data to plot in real-time. I am trying to model something similar to this example.
I have been using the following code (does not include the section calling in the data through the network API):
# create the figure and axes objects
fig = plt.figure()
ax = fig.add_subplot(1,1,1)
# create empty lists for the t and x data
t=[]
xs=[]
# function that draws each frame of the animation
def animate(i, t, xs):
while True:
topic, payload = subscriber.recv_multipart()
message = msgpack.loads(payload)
gaze_norm = message[b"gaze_point_3d"]
x_norm = gaze_norm[0]
time = message[b"timestamp"]
xs.append(x_norm)
t.append(time)
print(f"{x_norm},{time}")
ax.clear()
ax.plot(t, xs)
plt.title('Gaze Position over Time')
plt.ylabel('x-Position (m)')
plt.xlabel('time')
# run the animation
ani = animation.FuncAnimation(fig, animate, fargs=(t,xs), interval=1000)
plt.show()
Right now, this code prints the x-coordinate and the timestamp from the pupil-tracker so that I can make sure it is acutally reading data. The data comes in very quickly (100 data points per second). So I am not sure if it is a speed issue causing the problem. When I try to plot the data, I do not get an error, but the plotting window opens and then gives a message after a few seconds that it is not responding.
These are the kinds of values I am working with (x-position, timestamp).
494.5785722912332,254099.545318
488.4470958790914,254099.553249
504.43555359647513,254099.56164399997
501.52079783729266,254099.569292
489.80047236719173,254099.57736599998
500.31885068266115,254099.58562
496.32645013939197,254099.59423199997
503.4202519233139,254099.60130299997
490.2611210943025,254099.609245
106.32671092267813,254099.616085
470.1624883444511,254099.62522299998
496.958907552685,254099.632712
493.8912298397422,254099.64100099998
106.32671092267813,254099.648195
495.17531431095733,254099.657384
470.4794143807467,254099.665594
496.577815047261,254099.67225899998
106.32671092267813,254099.681369

Matplotlib: Animate multiple scatter plots

I'm trying to create an animation which shows multiple particles moving around.
If I have one particle with one array giving the positions of that particle in each step of the animation, I get it to work (mostly thanks to extensive help from other answers I found here on stackoverflow).
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as animation
positions = np.array([[2,2],[3,3],[4,4]])
def init():
scatterplot.set_offsets([[], []])
return [scatterplot]
def update(i, scatterplot, positions):
scatterplot.set_offsets(positions[i])
return [scatterplot]
fig = plt.figure()
scatterplot = plt.scatter([], [], s=100)
plt.xlim(0,5)
plt.ylim(0,5)
anim = animation.FuncAnimation(
fig, update, init_func=init, fargs=(scatterplot, positions), interval=1000, frames=3,
blit=True, repeat=True)
plt.show()
But I cannot figure out how to add more particles to the same animation.
Let's say I want to add a second particle with positions
positions2 = np.array([[2,1][3,2][4,3]])
and have it move around in the same scatter plot, how do I manage that?
I'm a matplotlib newbie, and have been googling furiously to no avail, will be very grateful for any help :)
EDIT:
I figured it out eventually, just a matter of formatting the data correctly.
positions = np.array([[[2,2],[2,1]],[[3,3],[3,2]],[[4,4],[4,3]]])
Where the array contains all the positions in step one, then all the positions in step two etc. works.
I'd prefer to get one color pr moving point, to keep track of them, but at least it works now.
I figured it out eventually, just a matter of formatting the data correctly.
positions = np.array([[[2,2],[2,1]],[[3,3],[3,2]],[[4,4],[4,3]]])
Where the array contains all the positions in step one, then all the positions in step two etc. works.
I'd prefer to get one color pr moving point, to keep track of them, but at least it works now.

matplotlib animated line plot stays empty

I am trying to follow the basic animation tutorial located here and adapting it to display an already computed dataset instead of evaluating a function every frame, but am getting stuck. My dataset involves XY coordinates over time, contained in the lists satxpos and satypos I am trying to create an animation such that it traces a line starting at the beginning of the dataset through the end, displaying say 1 new point every 0.1 seconds. Any help with where I'm going wrong?
from matplotlib import pyplot as plt
from matplotlib import animation
import numpy as np
Code here creates satxpos and satypos as lists
fig = plt.figure()
ax = plt.axes(xlim=(-1e7,1e7), ylim = (-1e7,1e7))
line, = ax.plot([], [], lw=2)
def init():
line.set_data([], [])
return line,
def animate(i):
line.set_data(satxpos[i], satypos[i])
return line,
anim = animation.FuncAnimation(fig, animate, init_func=init,
frames = len(satxpos), interval = 1, blit=True)
Edit: The code runs without errors, but generates a blank plot window with no points/lines displayed and nothing animates. The dataset is generated correctly and views fine in a static plot.
In order to "trace a line starting at the beginning of the dataset through the end" you would index your arrays to contain one more element per timestep:
line.set_data(satxpos[:i], satypos[:i])
(Note the :!)
Everything else in the code looks fine, such that with the above manipulation you should get and extending line plot. You might then want to set interval to something greater than 1, as that would mean 1 millesecond timesteps (which might be a bit too fast). I guess using interval = 40 might be a good start.
Your code looks correct! So long as satxpos and satypos are both configured and initialized properly, I believe everything else is valid!
One part of the code you do not show in your question is the invocation of the anim.save() and plt.show() functions, which are both necessary for your code to work (as per the tutorial you shared!)
You would therefore need to add something like:
anim.save('basic_animation.mp4', fps=30, extra_args=['-vcodec', 'libx264'])
plt.show()
to the end of your code to create the animation (and show it, I presume)!
Hope it helps!
Source - Matplotlib Animation Tutorial
I saw you mentioned "the parts that generate satxpos and satypos do create valid datasets. I can view those as a static plot just fine". But my guess is still the problem originated from your satxpos and satypos.
One way you can trouble shoot is to replace your two functions and animation code with line.set_data(satxpos[i], satypos[i]). Set i as 0, 1, ... and see if you can see the plot. If not, your satxpos and satypos are not as valid as you claimed.
As an example, a valid satxpos and satypos can be like this:
x = np.array([np.linspace(-1e7, 1e7, 1000)])
i = 200
satxpos = x.repeat(i, axis=0)
satypos = np.sin(2 * np.pi * (satxpos - 0.01 * np.arange(i).reshape(-1, 1).repeat(satxpos.shape[1], axis=1)))
satypos *= 1e7 / 2
This works with the code you provided thus indicating the code you've shown us is fine.
Edit in response to comments:
If your satxpos and satypos are just np.linespace, the animation loop will get just one point with (satxpos[i], satypos[i]) and you won't see the point on the plot without a setting like marker='o'. Therefore, you see nothing in your animation.

Efficiently Animating pcolormesh

Im trying to save a GIF with the evolucion of some waves in 2d using pcolormesh (using surface or wireframe would also be ok).
This has been my aproach so far:
set the quadmesh to plot in polar coordinates:
from matplotlib import pyplot
from matplotlib.animation import FuncAnimation as FuncAnimation
pi=np.pi
rmax=6.
r=2*np.linspace(0,np.sqrt(rmax*.5),100)**2
phi=np.linspace(0,2*pi,80)
R, P = np.meshgrid(r, phi)
X, Y = R*np.cos(P), R*np.sin(P)
set the figure and functions for the animation:
count is the amount of frames i have.
Z is a count*2D-array with the values i want to plot.
(it has the sum of some fourier like series)
fig, ax = pyplot.subplots()
def anim_I(count,r,phi):
anim=np.zeros((count,len(phi), len(r)))
for i in range(count):
anim[i,:,:]=coef_transf(final_coefs[i,:,:,:,0],r,phi)**2
return anim
Z=anim_I(count,r,phi)
def animate(i):
pyplot.title('Time: %s'%time[i])
#This is where new data is inserted into the plot.
plot=ax.pcolormesh(X, Y,Z[i,:,:],cmap=pyplot.get_cmap('viridis'),vmin=0., vmax=15.)
return plot,
ax.pcolormesh(X, Y,Z[0,:,:],cmap=pyplot.get_cmap('viridis'),vmin=0., vmax=15.)
pyplot.colorbar()
anim = FuncAnimation(fig, animate, frames = range(0,count,7), blit = False)
i don't really need to see it live, so i just save a gif.
anim.save('%d_%d_%d-%d.%d.%d-2dgif.gif' %(localtime()[0:6]), writer='imagemagick')
pyplot.close()
While this works, it can take to an hour to make the gif of a even a hundred frames.
I wan't to know what would be the correct way to do this so it could be usable.
I have seen the other post in this regard, but i couldn't get the code working, or it would be just as inneficient.
You could try to write
def animate(i):
pyplot.title('Time: %s'%time[i])
#This is where new data is inserted into the plot.
plot=plot.set_array(Z[i,:,:].ravel())
return plot,
instead of
def animate(i):
pyplot.title('Time: %s'%time[i])
#This is where new data is inserted into the plot.
plot=ax.pcolormesh(X, Y,Z[i,:,:],cmap=pyplot.get_cmap('viridis'),vmin=0., vmax=15.)
return plot,
This does not create a new object every time you call the animate funtion. Instead it changes the image of object that was already created.
However, the set_array method seems to need a flattened array, hence the .ravel().
This only produces the right image if you set the shading option of the pcolormap function to shading='gouraud'.
I don't know why, unfortunatelly, it seems to have to do with the sorting of the array.
I hoped, that helped a little.
I suggest inserting a
pyplot.clf()
at the beginning of your animate(i) function. This will start each frame with a blank figure. Otherwise, I suspect the plot will not be cleared, and the long time is due to actually plotting all previous frame below the new one.

Dynamically updating plot in matplotlib

I am making an application in Python which collects data from a serial port and plots a graph of the collected data against arrival time. The time of arrival for the data is uncertain. I want the plot to be updated when data is received. I searched on how to do this and found two methods:
Clear the plot and re-draw the plot with all the points again.
Animate the plot by changing it after a particular interval.
I do not prefer the first one as the program runs and collects data for a long time (a day for example), and redrawing the plot will be pretty slow.
The second one is also not preferable as time of arrival of data is uncertain and I want the plot to update only when the data is received.
Is there a way in which I can update the plot just by adding more points to it only when the data is received?
Is there a way in which I can update the plot just by adding more point[s] to it...
There are a number of ways of animating data in matplotlib, depending on the version you have. Have you seen the matplotlib cookbook examples? Also, check out the more modern animation examples in the matplotlib documentation. Finally, the animation API defines a function FuncAnimation which animates a function in time. This function could just be the function you use to acquire your data.
Each method basically sets the data property of the object being drawn, so doesn't require clearing the screen or figure. The data property can simply be extended, so you can keep the previous points and just keep adding to your line (or image or whatever you are drawing).
Given that you say that your data arrival time is uncertain your best bet is probably just to do something like:
import matplotlib.pyplot as plt
import numpy
hl, = plt.plot([], [])
def update_line(hl, new_data):
hl.set_xdata(numpy.append(hl.get_xdata(), new_data))
hl.set_ydata(numpy.append(hl.get_ydata(), new_data))
plt.draw()
Then when you receive data from the serial port just call update_line.
In order to do this without FuncAnimation (eg you want to execute other parts of the code while the plot is being produced or you want to be updating several plots at the same time), calling draw alone does not produce the plot (at least with the qt backend).
The following works for me:
import matplotlib.pyplot as plt
plt.ion()
class DynamicUpdate():
#Suppose we know the x range
min_x = 0
max_x = 10
def on_launch(self):
#Set up plot
self.figure, self.ax = plt.subplots()
self.lines, = self.ax.plot([],[], 'o')
#Autoscale on unknown axis and known lims on the other
self.ax.set_autoscaley_on(True)
self.ax.set_xlim(self.min_x, self.max_x)
#Other stuff
self.ax.grid()
...
def on_running(self, xdata, ydata):
#Update data (with the new _and_ the old points)
self.lines.set_xdata(xdata)
self.lines.set_ydata(ydata)
#Need both of these in order to rescale
self.ax.relim()
self.ax.autoscale_view()
#We need to draw *and* flush
self.figure.canvas.draw()
self.figure.canvas.flush_events()
#Example
def __call__(self):
import numpy as np
import time
self.on_launch()
xdata = []
ydata = []
for x in np.arange(0,10,0.5):
xdata.append(x)
ydata.append(np.exp(-x**2)+10*np.exp(-(x-7)**2))
self.on_running(xdata, ydata)
time.sleep(1)
return xdata, ydata
d = DynamicUpdate()
d()
Here is a way which allows to remove points after a certain number of points plotted:
import matplotlib.pyplot as plt
# generate axes object
ax = plt.axes()
# set limits
plt.xlim(0,10)
plt.ylim(0,10)
for i in range(10):
# add something to axes
ax.scatter([i], [i])
ax.plot([i], [i+1], 'rx')
# draw the plot
plt.draw()
plt.pause(0.01) #is necessary for the plot to update for some reason
# start removing points if you don't want all shown
if i>2:
ax.lines[0].remove()
ax.collections[0].remove()
I know I'm late to answer this question, but for your issue you could look into the "joystick" package. I designed it for plotting a stream of data from the serial port, but it works for any stream. 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 terminal remains available for monitoring commands while plotting.
See http://www.github.com/ceyzeriat/joystick/ or https://pypi.python.org/pypi/joystick (use pip install joystick to install)
Just replace np.random.random() by your real data point read from the serial port in the code below:
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()

Categories