matplotlib ArtistAnimation returns a blank video - python

I'm trying to produce an animation of a networkx graph changing over time. I'm using the networkx_draw utilities to create matplotlib figures of the graph, and matplotlib's ArtistAnimation module to create an animation from the artists networkx produces. I've made a minimum reproduction of what I'm doing here:
import numpy as np
import networkx as nx
import matplotlib.animation as animation
import matplotlib.pyplot as plt
# Instantiate the graph model
G = nx.Graph()
G.add_edge(1, 2)
# Keep track of highest node ID
G.maxNode = 2
fig = plt.figure()
nx.draw(G)
ims = []
for timeStep in xrange(10):
G.add_edge(G.maxNode,G.maxNode+1)
G.maxNode += 1
pos = nx.drawing.spring_layout(G)
nodes = nx.drawing.draw_networkx_nodes(G, pos)
lines = nx.drawing.draw_networkx_edges(G, pos)
ims.append((nodes,lines,))
plt.pause(.2)
plt.cla()
im_ani = animation.ArtistAnimation(fig, ims, interval=200, repeat_delay=3000,blit=True)
im_ani.save('im.mp4', metadata={'artist':'Guido'})
The process works fine while displaying the figures live, it produces exactly the animation I want. And it even produces a looping animation in a figure at the end of the script, again what I want, which would suggest that the animation process worked. However when I open the "im.mp4" file saved to disk, it is a blank white image which runs for the expected period of time, never showing any of the graph images which were showed live.
I'm using networkx version 1.11, and matplotlib version 2.0. I'm using ffmpeg for the animation, and am running on a Mac, OSX 10.12.3.
What am I doing incorrectly?

The short answer: If you don't want to have an empty animation, do not clear the axes! I.e. remove the line plt.cla(). You should then also remove the initial nx.draw(G), because this is not added to the ims array and would otherwise stick around in every frame of the animation.
The reasons and a longer explanation can be found in this question,
Matplotlib video creation, where a similar case is tackled.
The drawback is of course that when removing the plt.cla() you'll end up with a crowded animation on screen; so you need to decide whether to plot on screen or whether to save beforehands.

Related

Matplotlib Imshow Doesn't Update With Draw

I'm having trouble getting imshow to update with new data. For reference I'm pulling data off a serial port and trying to plot it, updating every second or so. I had been accumulating the data with a thread, so I initially thought that might be the problem as matplotlib isn't thread safe. However, I can't get the following simpler example to work:
import numpy as np
import matplotlib.pyplot as plt
import time
dat = np.random.rand(100,10)
plt.ion()
fig = plt.figure()
ax = fig.add_subplot(111)
image = ax.imshow(np.zeros((10,10)))
fig.canvas.draw()
count = 0
while count < 100:
image.set_data(dat[count:count+10])
fig.canvas.draw()
count += 10
time.sleep(1)
Using TkAgg, I just get the plot of all zeros, it never updates then quits.
With Qt5Agg, an empty window pops up before quitting.
I've tried various combinations of draw_idle(), flush_events() and plt.show(block=False), with the same results.
python 3.8.10 , matplotlib 3.2.2
Immediately after posting this I figured out the solution.
I initialized the plot with all zeros - changing this to be a random array of values fixes it fixes it.
I'm not sure why starting with all zeros broke the color scaling, though from the matplotlib documentation the default normalization scales the input data on [0,1], so I suspect that was the issue.

replacing most recent line in jupyter matplotlib chart

I'm using matplotlib to generate a chart with a dynamic line at any point on the y-axis as a threshold (that is, the user clicks on the chart somewhere and a new line will be generated that replaces the previous one). I can add new lines with axhline(), but I can't figure out how to remove the previous line. I've seen references to Artist.remove and ax.remove.line(0), but I'm still fairly new to matplotlib and haven't been able to get anything to work.
NB: I've only been working in Jupyter, so I don't know if this will work as-is outside of Jupyter. Also, I know that the separate update() procedure isn't really necessary here, but I'll most likely need it for future functionality.
%matplotlib notebook
import matplotlib.pyplot as plt
import matplotlib.figure as fig
import numpy as np
from matplotlib.axes import Axes as ax
from matplotlib.artist import Artist as art
x = np.random.normal(size = 1000)
plt.hist(x, bins=50, alpha=0.75)
plt.gcf().canvas.draw()
green = plt.axhline(35, color='g')
print('green line = {}'.format(green))
def update(threshold, lines):
plt.gca().set_title('most recent line = {}'.format(lines))
def on_press(event):
threshold = event.ydata
lines = plt.axhline(threshold, color='r')
update(threshold, lines)
plt.gcf().canvas.mpl_connect('button_press_event', on_press)
You can get at the lines in the subplot with plt.gca().lines, so you can just add something like this to the beginning of your on_press() function:
plt.gca().lines.pop()
Or adding plt.gca().pop(0) to the update() function also seems to work.
I don't know if this is the best way to do it or I just got lucky, but I found that calling plt.delaxes() before drawing the new line got me what I needed. The axhline() is the last thing I draw on the plot before the user has a chance to interact with it, so that may be why it works.

Make several figures from a dictionary without overwriting the previous figure [duplicate]

I have a Python program that generates graphs using matplotlib. I am trying to get the program to generate a bunch of plots in one program run (the user is asked if they want to generate another graph) all in separate windows. Any way I can do this?
To generate a new figure, you can add plt.figure() before any plotting that your program does.
import matplotlib.pyplot as plt
import numpy as np
def make_plot(slope):
x = np.arange(1,10)
y = slope*x+3
plt.figure()
plt.plot(x,y)
make_plot(2)
make_plot(3)
Using the latest matlibplot, I found the following to work for my purposes:
# create figure (will only create new window if needed)
plt.figure()
# Generate plot1
plt.plot(range(10, 20))
# Show the plot in non-blocking mode
plt.show(block=False)
# create figure (will only create new window if needed)
plt.figure()
# Generate plot2
plt.plot(range(10, 20))
# Show the plot in non-blocking mode
plt.show(block=False)
...
# Finally block main thread until all plots are closed
plt.show()
The easiest way to ensure all of your lines go to the correct figure window is something like:
from six.moves import input
import matplotlib.pyplot as plt
another = True
while another:
fig, ax = plt.subplots()
ax.plot(range(5))
fig.canvas.manager.show()
# this makes sure that the gui window gets shown
# if this is needed depends on rcparams, this is just to be safe
fig.canvas.flush_events()
# this make sure that if the event loop integration is not
# set up by the gui framework the plot will update
another = bool(input("would you like another? "))
If you want to run this with a non-gui backend you will need to drop the flush_events call or wrap it in a try: ... except NotImplementedError. Much of this complication is defensive programming because GUIs can be difficult and the behavior of this code may be dependent on many factors which are not obvious from the code shown.
Using the implicit axes of pyplot can cause problems as the 'current axes' is set by the last axes the user clicked on. You should really only use pyplot when interactively typing at the rpel and almost never (other than plt.subplots) in scripts/programs.
Use the .figure() function to create a new window, the following code makes two windows:
import matplotlib.pyplot as plt
plt.plot(range(10)) # Creates the plot. No need to save the current figure.
plt.draw() # Draws, but does not block
plt.figure() # New window, if needed. No need to save it, as pyplot uses the concept of current figure
plt.plot(range(10, 20))
plt.draw()
You can repeat this as many times as you want

Live plotting on bloch sphere

I am trying to plot live data on a bloch sphere using Qutip's function bloch().
So far, the code always interrupts, when I have a b.show() in there.
I found a lot of solutions online to similar problems, but most of them make use of direct matplotlib commands like matplotlib.draw() which doesn't seem to work with the bloch class.
Then, there are other solutions which make use of for example Tk or GTKagg (e.g. https://stackoverflow.com/a/15742183/3276735 or real-time plotting in while loop with matplotlib)
Can somebody please help me how to deal with the same problem in the bloch class?
Edit:
Here's a minimal example:
Basically, I want to update my plot with one point at a time, preferably in a loop. My goal is to display live data in the plot that has to be read from a file.
import qutip as qt
import numpy as np
b = qt.Bloch()
theta = np.arange(0,np.pi,0.1)
for ii in range(len(theta)):
b.add_points([np.sin(theta[ii]),0,np.cos(theta[ii])])
b.show()
I think you are breaking your plot because you are calling show for every point. Try calling show outside the loop (in the end).
import qutip as qt
import numpy as np
b = qt.Bloch()
theta = np.arange(0,np.pi,0.1)
for ii in range(len(theta)):
b.add_points([np.sin(theta[ii]),0,np.cos(theta[ii])])
b.show() # Changed here
EDIT: Animated plot
Consider show as an absolute command to call the plot into view. It's not a draw command (or redraw). If you do want to show an image every "n" seconds or so you'll need to clear the plot before calling it again. You may try this:
import qutip as qt
import numpy as np
b = qt.Bloch()
theta = np.arange(0,np.pi,0.1)
for ii in range(len(theta)):
b.clear()
b.add_points([np.sin(theta[ii]),0,np.cos(theta[ii])])
b.show()
# wait time step and load new value from file.
, I don't have QuTip in my current distribution so I can't really test it but I'm betting its heavily based in matplotlib. My best advise however is for you to use the formulation give for animation in the QuTiP docs. By following this recipe:
from pylab import *
import matplotlib.animation as animation
from mpl_toolkits.mplot3d import Axes3D
fig = figure()
ax = Axes3D(fig,azim=-40,elev=30)
sphere=Bloch(axes=ax)
def animate(i):
sphere.clear()
sphere.add_vectors([sin(theta),0,cos(theta)])
sphere.add_points([sx[:i+1],sy[:i+1],sz[:i+1]])
sphere.make_sphere()
return ax
def init():
sphere.vector_color = ['r']
return ax
ani = animation.FuncAnimation(fig, animate, np.arange(len(sx)),
init_func=init, blit=True, repeat=False)
ani.save('bloch_sphere.mp4', fps=20, clear_temp=True)
, you should be able to modify the animate function to perform all operations you need.

Combining mayavi and matplotlib in the same figure

I will be making animations. In each frame I want to contain both a mayavi plot obtained with
mlab.pipeline.iso_surface(source, some other superfluous args)
and a matplotlib plot obtained using simply
pylab.plot(args)
I have scripts to do both separately, but have no idea how to go about combining them into one figure. I want the end product to be one script which contains the code from both the scripts that I currently have.
AFAIK, there is no direct way because the backends used are so different. It does not seem possible to add matplotlib axes to mayavi.figure or vice versa.
However, there is a "kind of a way" by using the the mlab.screenshot.
import mayavi.mlab as mlab
import matplotlib.pyplot as plt
# create and capture a mlab object
mlab.test_plot3d()
img = mlab.screenshot()
mlab.close()
# create a pyplot
fig = plt.figure()
ax1 = fig.add_subplot(121)
ax1.plot([0,1], [1,0], 'r')
# add the screen capture
ax2 = fig.add_subplot(122)
ax2.imshow(img)
ax2.set_axis_off()
This is not necessarily the nicest possible way of doing things, and you may bump into resolution problems, as well (check the size of the mayavi window). However, it gets the job done in most cases.
Adding to the answer by DrV which helped me a great deal, you can work with the mlab figure to set resolution before screenshot such as with batch plotting:
mfig = mlab.figure(size=(1024, 1024))
src = mlab.pipeline.scalar_field(field_3d_numpy_array)
mlab.pipeline.iso_surface(src)
iso_surface_plot = mlab.screenshot(figure=mfig, mode='rgba', antialiased=True)
mlab.clf(mfig)
mlab.close()
# Then later in a matplotlib fig:
plt.imshow(iso_surface_plot)

Categories

Resources