I am currently want to visualize 3D-rawdata out of my Walabot device and display it in a 3D animation created with matplotlib FuncAnimation. I already searched for answers, but I could not find anything helpful.
In my case I already have a 3 dimensional array, where each index has a specific value, which changes over the time. I already could figure out how to display it in a 3D chart with different colors and sizes but now I want to make update itself. I have found some example code which gave me a good start, but my chart does not update on its own. I have to close the window and then the window pops up again with different values from the 3D array. Do you guys know how to solve this problem?
Here is my code so far:
def update(plot, signal, figure):
plot.clear()
scatterplot = plot.scatter(x, y, z, zdir='z', s=signal[0], c=signal[0])
figure.show()
return figure
def calc_RasterImage(signal):
# 3D index is represnted is the following schema {i,j,k}
# sizeX - signal[1] represents the i dimension length
# sizeY - signal[2] represents the j dimension length
# sizeZ - signal[3] represents the k dimension length
# signal[0][i][j][k] - represents the walabot 3D scanned image (internal data)
#Initialize 3Dplot with matplotlib
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
ax.set_xlim([xMin-1,xMax-1])
ax.set_ylim([yMin-1,yMax-1])
ax.set_zlim([zMin-1,zMax-1])
ax.set_xlabel('X AXIS')
ax.set_ylabel('Y AXIS')
ax.set_zlabel('Z AXIS')
scatterplot = ax.scatter(x, y, z, zdir='z', s=signal[0], c= signal[0])
cbar = plt.colorbar(scatterplot)
cbar.set_label('Density')
#def update(signal):
# ax.clear()
# scatterplot = ax.scatter(x, y, z, zdir='z', s=signal[0], c=signal[0])
ani = anim.FuncAnimation(fig, update(ax, signal, plt), frames=10 , blit=True, repeat = True)
def main():
wlbt = Walabot()
wlbt.connect()
if not wlbt.isConnected:
print("Not Connected")
else:
print("Connected")
wlbt.start()
calc_index(wlbt.get_RawImage_values())
while True:
#print_RawImage_values(wlbt.get_RawImage_values())
calc_RasterImage(wlbt.get_RawImage_values())
wlbt.stop()
if __name__ == '__main__':
main()
As you can see the row with
ani = anim.FuncAnimation(fig, update(ax, signal, plt), frames=10 , blit=True, repeat = True)
needs the update function from the top. This function clears my plot and recreates a new plot with different values. But I always need to close the plot window first, which I would like to avoid.
This is how the plot looks like:
3D array plot with matplotlib scatter
Do you guys have an idea how to solve this problem?
cheers
Your code isn't really a minimal working example, and you shouldn't be lazy and actually read the docs for FuncAnimation before coming to SO. That being said, something like this should work:
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
Display walabot output.
"""
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation
def display(walabot_instance):
# set x, y, z
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
path_collection = ax.scatter(x, y, z, zdir='z')
# do your labelling, layout etc
def update(ignored, walabot_instance):
signal = walabot_instance.get_RawImage_values()
path_collection.set_sizes(signal[0])
path_collection.set_color(signal[1])
return path_collection,
return FuncAnimation(fig, update, fargs=[walabot_instance])
def main():
wlbt = Walabot()
wlbt.connect()
if not wlbt.isConnected:
print("Not Connected")
else:
print("Connected")
wlbt.start()
plt.ion()
animation = display(wlbt)
raw_input("Press any key when done watching Walabot...")
if __name__ == "__main__":
main()
If you have any questions (after having read the docs!), drop a comment.
Related
I would like to animate a 3D scatter plot where each data point has a text label that moves along with it.
Right now the text labels do follow the data points as I want, but they persist through each frame; the text does not disappear from the last animation update. See image below. The data points themselves are moving just fine.
2 Questions whose answers might help me >
Is there some way to clear the text without clearing the data points?
My implementation seems a bit clunky. Is there a hidden function similar to _offset3d that works for text objects.
Here's the graphing function:
def graph(data, update_cnt):
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
dots = ax.scatter(data[0][0][:], data[1][0][:], data[2][0][:])
dot_txt = nmp.ones(nmp.size(data,2), dtype=str)
for n in range(0,nmp.size(data, 2)):
dot_txt[n] = ax.text(data[0][0][n], data[1][0][n], data[2][0][n],'%s'%(n))
ani = animation.FuncAnimation(fig, update, update_cnt, fargs=(dots, data, dot_txt, ax), interval=300)
plt.show()
and the animation update function:
def update(num, dots, data, dot_txt, ax):
y = data[0][num][:]
x = data[1][num][:]
z = data[2][num][:]
dots._offsets3d = (x, y, z)
#dot_txt._something_to_update_text_here()
dot_txt = nmp.ones(nmp.size(data,2), dtype=str)
for n in range(0,nmp.size(data, 2)):
dot_txt[n] = ax.text(data[1][num][n], data[0][num][n], data[2][num][n],'%s'%(n))
and the current plot output:
I found a solution.
I think it's important to note that I could not use the more common solutions for 3D animated scatter plots because I need different marker styles for various points. This forces me to iteratively plot each scatter point, rather than passing a list to the update function. However in doing so, the problem of animating the text is solved nicely.
frame_list contains the x,y,z coordinates and styling for each data point in every frame.
def graph(frame_list):
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
frame_cnt = len(frame_list)
ani = animation.FuncAnimation(fig, update_graph, frame_cnt,
fargs=(frame_list, ax, frame_cnt), interval=600)
plt.show()
The biggest contributor the success of this is the ax.clear() call before every frame.
def update_graph(f, frame_list, ax, cnt):
ax.clear()
f = nmp.mod(f, cnt)
frame = frame_list[f]
for n in range(len(frame.marker)):
x, y, z, s, c, m, name = frame.get_params(n)
ax.scatter(x, y, z, s=s, c=c, marker=m)
ax.text(x, y, z, '%s'%(name))
The get_params function returns all of the relevant data for that frame.
I'm not a beginner, but I'm also not advanced dev of python code.
I'm been trying to animate points movement in scatter plot and to put annotation on every point. All I have done is animation of one point with no annotation. I've searched similar solutions, but it's so confusing. Any help is welcome. This is what I've done.
from mpl_toolkits.mplot3d import axes3d
import matplotlib.pyplot as plt
import matplotlib.animation as animation
frame_count = 0
points = reading_file("some_data") # this method is not of intrest
def make_one_point(i):
global frame_count, points
ex = [1]
ey = [1]
ez = [1]
point = points[i]
frame = point[frame_count]
ex[0] = frame[0]
ey[0] = frame[1]
ez[0] = frame[2]
frame_count += 1
return ex, ey, ez
def update(i):
global frame_count, points
if frame_count < len(points[i]):
return make_one_point(i)
else:
frame_count = 0
return make_one_point(i)
fig = plt.figure()
ax1 = fig.add_subplot(111, projection='3d')
ax1.set_xlim3d(-500, 2000)
ax1.set_ylim3d(-500, 2000)
ax1.set_zlim3d(0, 2000)
x = [1]
y = [1]
z = [1]
scat = ax1.scatter(x,y,z)
def animate(i):
scat._offsets3d = update(0)
ani = animation.FuncAnimation(fig, animate,
frames=len(points[10]),
interval=100, repeat=True)
plt.show()
How to animate more points at the same time, and put annontation on every one of them? There are 50 points, and I'm not so consern about efficiency, just to make it work.
This code output is moving one point animation
It turns out animating Text in 3D was harder than I anticipated. Not surprisingly, I was able to find the solution to the problem in an answer from #ImportanceOfBeingErnest. I then simply adapted the code I had already written in a previous answer, and produced the following code:
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D, proj3d
import matplotlib.animation as animation
N_points = 10
def update(num, my_ax):
# the following corresponds to whatever logic must append in your code
# to get the new coordinates of your points
# in this case, we're going to move each point by a quantity (dx,dy,dz)
dx, dy, dz = np.random.normal(size=(3,N_points), loc=0, scale=1)
debug_text.set_text("{:d}".format(num)) # for debugging
x,y,z = graph._offsets3d
new_x, new_y, new_z = (x+dx, y+dy, z+dz)
graph._offsets3d = (new_x, new_y, new_z)
for t, new_x_i, new_y_i, new_z_i in zip(annots, new_x, new_y, new_z):
# animating Text in 3D proved to be tricky. Tip of the hat to #ImportanceOfBeingErnest
# for this answer https://stackoverflow.com/a/51579878/1356000
x_, y_, _ = proj3d.proj_transform(new_x_i, new_y_i, new_z_i, my_ax.get_proj())
t.set_position((x_,y_))
return [graph,debug_text]+annots
# create N_points initial points
x,y,z = np.random.normal(size=(3,N_points), loc=0, scale=10)
fig = plt.figure(figsize=(5, 5))
ax = fig.add_subplot(111, projection="3d")
graph = ax.scatter(x, y, z, color='orange')
debug_text = fig.text(0, 1, "TEXT", va='top') # for debugging
annots = [ax.text2D(0,0,"POINT") for _ in range(N_points)]
# Creating the Animation object
ani = animation.FuncAnimation(fig, update, fargs=[ax], frames=100, interval=50, blit=True)
plt.show()
I'm trying to animate a stem plot in matplotlib and I can't find the necessary documentation to help me. I have a series of data files which each look like this:
1 0.345346
2 0.124325
3 0.534585
and I want plot each file as a separate frame.
According to this and this other tutorial, I should create a function which updates the data contained in each plot object (artist? I'm not sure about the terminology)
From the second link, this is the update function
def update(frame):
global P, C, S
# Every ring is made more transparent
C[:,3] = np.maximum(0, C[:,3] - 1.0/n)
# Each ring is made larger
S += (size_max - size_min) / n
# Reset ring specific ring (relative to frame number)
i = frame % 50
P[i] = np.random.uniform(0,1,2)
S[i] = size_min
C[i,3] = 1
# Update scatter object
scat.set_edgecolors(C)
scat.set_sizes(S)
scat.set_offsets(P)
# Return the modified object
return scat,
How can I adapt this kind of update function for a stem plot? The documentation for stem is horribly brief (in fact this is a recurring issue as I'm learning matplotlib), but the example code shows that the output of stem is a tuple markerline, stemlines, baseline rather than an artist object like for plt.plot or plt.imshow.
So when I write my update function for the animation, how can I update the data inside the stem plot?
Here you go!
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation
import numpy as np
fig, ax = plt.subplots()
x = np.linspace(0.1, 2*np.pi, 10)
markerline, stemlines, baseline = ax.stem(x, np.cos(x), '-.')
def update(i):
ax.cla()
markerline, stemlines, baseline = ax.stem(x, np.cos(x+i/10), '-.')
ax.set_ylim((-1, 1))
anim = FuncAnimation(fig, update, frames=range(10, 110, 10), interval=500)
anim.save('so.gif', dpi=80, writer='imagemagick')
I think there can be better ways of achieving this- not requiring to clear the plot each time. However, this works!
When using the keyword use_line_collection=True (default behavior since Matplotlib 3.3) one can update the three elements
markerline
stemlines
baseline
individualy. Here is the code for the sine wave example:
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation
fig, ax = plt.subplots()
x = np.linspace(0.1, 2*np.pi, 10)
y = np.cos(x)
bottom = 0
h_stem = ax.stem(x, y, bottom=bottom, use_line_collection=True, linefmt='-.')
def update(i):
y = np.cos(x+i/10)
# markerline
h_stem[0].set_ydata(y)
h_stem[0].set_xdata(x) # not necessary for constant x
# stemlines
h_stem[1].set_paths([np.array([[xx, bottom],
[xx, yy]]) for (xx, yy) in zip(x, y)])
# baseline
h_stem[2].set_xdata([np.min(x), np.max(x)])
h_stem[2].set_ydata([bottom, bottom]) # not necessary for constant bottom
anim = FuncAnimation(fig, update, frames=range(10, 110, 10), interval=1)
anim.save('so.gif', dpi=80, writer='imagemagick')
Depending on what values (x, y, bottom) should be updated you can omit some parts of this update or reuse the current values. I wrote a more general function, where you can pass an arbitrary combination of these values:
def update_stem(h_stem, x=None, y=None, bottom=None):
if x is None:
x = h_stem[0].get_xdata()
else:
h_stem[0].set_xdata(x)
h_stem[2].set_xdata([np.min(x), np.max(x)])
if y is None:
y = h_stem[0].get_ydata()
else:
h_stem[0].set_ydata(y)
if bottom is None:
bottom = h_stem[2].get_ydata()[0]
else:
h_stem[2].set_ydata([bottom, bottom])
h_stem[1].set_paths([np.array([[xx, bottom],
[xx, yy]]) for (xx, yy) in zip(x, y)])
I produced an animation of a contour field with pyplot and would like to save it as movie file. Unfortunately, when I save it I get a file of the right format and I can open it with, say, VLC, but it contains just one or two frames.
This a simplified version of the code I'm using:
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation
# Initialize figure and axes
fig = plt.figure()
ax = fig.add_subplot(111)
# Initialize mesh
x = np.r_[-5:5:100j]
X, Y = np.meshgrid(x, x)
# Computes the data and yields them
def data_generator(X, Y, dt):
for t in np.r_[:10:dt]:
yield t, np.tanh(Y*(2+t)) + np.sqrt(np.abs(X))
# Plots the given solution on the given axes
def plotter(data, ax, X, Y):
ax.clear()
t, field = data
C = ax.contour(X, Y, field)
ax.set_title('Time: {0:.1f}'.format(t))
return C
# Animation object
anim = FuncAnimation(fig, plotter, frames=data_generator(X, Y, 0.1),
interval=100, repeat=False, fargs=(ax,X,Y))
anim.save('animation.mp4')
plt.show()
I'm using python-3.5.0 on Arch linux, with matplotlib-1.5.0. I have ffmpeg-2.8.2 installed. I tried with different formats (e.g. avi, mkv, mov), and I also tried with explicitly selecting the ffmpeg and ffmpeg_file writers. The problem is not with my hard disk running out of space. What else should I check?
I'm having issues with redrawing the figure here. I allow the user to specify the units in the time scale (x-axis) and then I recalculate and call this function plots(). I want the plot to simply update, not append another plot to the figure.
def plots():
global vlgaBuffSorted
cntr()
result = collections.defaultdict(list)
for d in vlgaBuffSorted:
result[d['event']].append(d)
result_list = result.values()
f = Figure()
graph1 = f.add_subplot(211)
graph2 = f.add_subplot(212,sharex=graph1)
for item in result_list:
tL = []
vgsL = []
vdsL = []
isubL = []
for dict in item:
tL.append(dict['time'])
vgsL.append(dict['vgs'])
vdsL.append(dict['vds'])
isubL.append(dict['isub'])
graph1.plot(tL,vdsL,'bo',label='a')
graph1.plot(tL,vgsL,'rp',label='b')
graph2.plot(tL,isubL,'b-',label='c')
plotCanvas = FigureCanvasTkAgg(f, pltFrame)
toolbar = NavigationToolbar2TkAgg(plotCanvas, pltFrame)
toolbar.pack(side=BOTTOM)
plotCanvas.get_tk_widget().pack(side=TOP)
You essentially have two options:
Do exactly what you're currently doing, but call graph1.clear() and graph2.clear() before replotting the data. This is the slowest, but most simplest and most robust option.
Instead of replotting, you can just update the data of the plot objects. You'll need to make some changes in your code, but this should be much, much faster than replotting things every time. However, the shape of the data that you're plotting can't change, and if the range of your data is changing, you'll need to manually reset the x and y axis limits.
To give an example of the second option:
import matplotlib.pyplot as plt
import numpy as np
x = np.linspace(0, 6*np.pi, 100)
y = np.sin(x)
# You probably won't need this if you're embedding things in a tkinter plot...
plt.ion()
fig = plt.figure()
ax = fig.add_subplot(111)
line1, = ax.plot(x, y, 'r-') # Returns a tuple of line objects, thus the comma
for phase in np.linspace(0, 10*np.pi, 500):
line1.set_ydata(np.sin(x + phase))
fig.canvas.draw()
fig.canvas.flush_events()
You can also do like the following:
This will draw a 10x1 random matrix data on the plot for 50 cycles of the for loop.
import matplotlib.pyplot as plt
import numpy as np
plt.ion()
for i in range(50):
y = np.random.random([10,1])
plt.plot(y)
plt.draw()
plt.pause(0.0001)
plt.clf()
This worked for me. Repeatedly calls a function updating the graph every time.
import matplotlib.pyplot as plt
import matplotlib.animation as anim
def plot_cont(fun, xmax):
y = []
fig = plt.figure()
ax = fig.add_subplot(1,1,1)
def update(i):
yi = fun()
y.append(yi)
x = range(len(y))
ax.clear()
ax.plot(x, y)
print i, ': ', yi
a = anim.FuncAnimation(fig, update, frames=xmax, repeat=False)
plt.show()
"fun" is a function that returns an integer.
FuncAnimation will repeatedly call "update", it will do that "xmax" times.
This worked for me:
from matplotlib import pyplot as plt
from IPython.display import clear_output
import numpy as np
for i in range(50):
clear_output(wait=True)
y = np.random.random([10,1])
plt.plot(y)
plt.show()
I have released a package called python-drawnow that provides functionality to let a figure update, typically called within a for loop, similar to Matlab's drawnow.
An example usage:
from pylab import figure, plot, ion, linspace, arange, sin, pi
def draw_fig():
# can be arbitrarily complex; just to draw a figure
#figure() # don't call!
plot(t, x)
#show() # don't call!
N = 1e3
figure() # call here instead!
ion() # enable interactivity
t = linspace(0, 2*pi, num=N)
for i in arange(100):
x = sin(2 * pi * i**2 * t / 100.0)
drawnow(draw_fig)
This package works with any matplotlib figure and provides options to wait after each figure update or drop into the debugger.
In case anyone comes across this article looking for what I was looking for, I found examples at
How to visualize scalar 2D data with Matplotlib?
and
http://mri.brechmos.org/2009/07/automatically-update-a-figure-in-a-loop
(on web.archive.org)
then modified them to use imshow with an input stack of frames, instead of generating and using contours on the fly.
Starting with a 3D array of images of shape (nBins, nBins, nBins), called frames.
def animate_frames(frames):
nBins = frames.shape[0]
frame = frames[0]
tempCS1 = plt.imshow(frame, cmap=plt.cm.gray)
for k in range(nBins):
frame = frames[k]
tempCS1 = plt.imshow(frame, cmap=plt.cm.gray)
del tempCS1
fig.canvas.draw()
#time.sleep(1e-2) #unnecessary, but useful
fig.clf()
fig = plt.figure()
ax = fig.add_subplot(111)
win = fig.canvas.manager.window
fig.canvas.manager.window.after(100, animate_frames, frames)
I also found a much simpler way to go about this whole process, albeit less robust:
fig = plt.figure()
for k in range(nBins):
plt.clf()
plt.imshow(frames[k],cmap=plt.cm.gray)
fig.canvas.draw()
time.sleep(1e-6) #unnecessary, but useful
Note that both of these only seem to work with ipython --pylab=tk, a.k.a.backend = TkAgg
Thank you for the help with everything.
All of the above might be true, however for me "online-updating" of figures only works with some backends, specifically wx. You just might try to change to this, e.g. by starting ipython/pylab by ipython --pylab=wx! Good luck!
Based on the other answers, I wrapped the figure's update in a python decorator to separate the plot's update mechanism from the actual plot. This way, it is much easier to update any plot.
def plotlive(func):
plt.ion()
#functools.wraps(func)
def new_func(*args, **kwargs):
# Clear all axes in the current figure.
axes = plt.gcf().get_axes()
for axis in axes:
axis.cla()
# Call func to plot something
result = func(*args, **kwargs)
# Draw the plot
plt.draw()
plt.pause(0.01)
return result
return new_func
Usage example
And then you can use it like any other decorator.
#plotlive
def plot_something_live(ax, x, y):
ax.plot(x, y)
ax.set_ylim([0, 100])
The only constraint is that you have to create the figure before the loop:
fig, ax = plt.subplots()
for i in range(100):
x = np.arange(100)
y = np.full([100], fill_value=i)
plot_something_live(ax, x, y)