Plotting real-time gaze data in python - 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

Related

Matplotlib function animation not animating for pre-calculated data

I am working on some animated scatter plots in python with matplotlib. I currently have this code:
def calulateStep():
# Math stuff ....
# Changes values in in 'circpos' Nx2 array
fig, ax = plt.subplots(figsize=(5, 5))
ax.set(xlim=(-WELLRADIUS,WELLRADIUS), ylim=(-WELLRADIUS,WELLRADIUS))
[x,y] = np.hsplit(circpos,2)
scat = ax.scatter(x.flatten(),y.flatten())
def animate(i):
calculateStep()
scat.set_offsets(circpos)
return scat,
anim = FuncAnimation(fig, animate, frames=60)
anim.save('test2.gif',writer='imagemagick')
plt.draw()
plt.show()
The function calculateStep calculates new x,y values for the scatter. circpos contains the data array at each step. this works well and produces an animated gif ofthe scatter plot as expected. However the function is a rather slow numerical calculation and many many steps are required to produce stable output, so I would rather calculate all before and then animate only select frames. So I tried this.
results = [circpos]
for h in range(61):
calculateStep()
results.append(circpos)
fig, ax = plt.subplots(figsize=(5, 5))
ax.set(xlim=(-WELLRADIUS,WELLRADIUS), ylim=(-WELLRADIUS,WELLRADIUS))
[x,y] = np.hsplit(results[0],2)
scat = ax.scatter(x.flatten(),y.flatten())
def animate(i):
scat.set_offsets(results.pop(0))
return scat,
anim = FuncAnimation(fig, animate, frames=60)
anim.save('test2.gif',writer='imagemagick')
plt.draw()
plt.show()
However with this method the generated gif contains only the final frame of the animation. If I print the data from within the animate function I find that the correct numerical values are being popped from the results list but for some reason only the final value is there in the gif. I have also tried using results[i] rather than results.pop(0) I am at a loss to understand this behavior.
Well it seems I solved my own problem. When I add each iteration of the global array circpos to the results list, it is of course a shallow copy. Meaning it's just a reference to the original circpos array. So I end up with a list full of references to the same object. The print out was just me misinterpreting what I was looking at.
Instead I now add circpos.copy() to my list to get new copies of the array at each step.
This has tripped me up in Python before I realize. Still learning!

Completely update matplotlib figure (not appending data)

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.

How to update Matplotlib graph when new data arrives?

I'm building a bot which fetches data from the internet every 30 seconds.
I want to plot this data using matplotlib and be able to update the graphs when I fetch some new one.
At the beginning of my script I initialise my plots.
Then I run a function to update these plots every 30 seconds, but my plot window freezes at this moment.
I've done some research but can't seem to find any working solution:
plt.show(block=False)
plt.pause(0.001)
What am I doing wrong ?
General structure of the code:
import matplotlib.pyplot as plt
import time
def init_plots():
global fig
global close_line
plt.ion()
fig = plt.figure()
main_fig = fig.add_subplot(111)
x = [datetime.fromtimestamp(x) for x in time_series]
close_line, = main_fig.plot(x, close_history, 'k-')
plt.draw()
def update_all_plots():
global close_line
x = [datetime.fromtimestamp(x) for x in time_series]
close_line.set_xdata(time_series)
close_line.set_ydata(close_history)
plt.draw()
# SCRIPT :
init_plots()
while(True):
# Fetch new data...
# Update time series...
# Update close_history...
update_plots()
time.sleep(30)
There is a module in matplotlib for specifically for plots that change over time: https://matplotlib.org/api/animation_api.html
Basically you define an update function, that updates the data for your line objects. That update function can then be used to create a FuncAnimation object which automatically calls the update function every x milliseconds:
ani = FuncAnimation(figure, update_function, repeat=True, interval=x)
There is a simple way to do it, given a panda dataframe . You would usually do something like this to draw(df is dataframe) :
ax = df.plot.line()
Using
df.plot.line(reuse_plot=True,ax=ax)
one can reuse the same figure to redraw it elegantly and probably fast enough.
Possibly duplicate of Matplotlib updating live plot

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