How to improve responsiveness of ipython interact for matplotlib 3D plot - python

Python 2.7.9, matplotlib 1.4.0, ipython 2.3.1, macbook pro retina
I am using ipython's interact() with a 3D plot in an ipython notebook, but find that the graph updates too slowly when changing a slider control. Here is example code that has this problem when run:
from IPython.html.widgets import *
import numpy as np
from matplotlib import pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
import math
%matplotlib inline
def plt3Dsin(angle):
npnts = 100
rotationangle_radians = math.radians(angle)
tab_x = np.linspace(0,3,npnts)
tab_y = np.zeros(npnts)
tab_z = np.sin(2.0*np.pi*tab_x)
yrotate = tab_z * np.sin(rotationangle_radians)
zrotate = tab_z * np.cos(rotationangle_radians)
fig = plt.figure()
ax = Axes3D(fig)
ax.plot(tab_x, yrotate, zrotate)
ax.set_xlim3d([0.0,3.0])
ax.set_ylim3d([-1.0,1.0])
ax.set_zlim3d([-1.0,1.0])
ax.set_xlabel('x')
ax.set_ylabel('y')
ax.set_zlabel('z')
interact(plt3Dsin,angle=(0,360,5));
I have tried to separate out the figure and axes creation from actual plotting in the following code, but the first time I change the slider the graph doesn't update and the 2nd time I change it the graph disappears altogether. I assume I'm doing something wrong, but have not been able to figure out what. (The use of globals below is just a quick expedient approach for this simple example code.)
npnts = 100
tab_x = np.linspace(0,3,npnts)
def init_plt3Dsin():
global fig
global ax
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
ax.plot([], [], [], lw=2)
ax.set_xlim3d([0.0,3.0])
ax.set_ylim3d([-1.0,1.0])
ax.set_zlim3d([-1.0,1.0])
ax.set_xlabel('x')
ax.set_ylabel('y')
ax.set_zlabel('z')
def plt3Dsin(angle):
global fig
global ax
rotationangle_radians = math.radians(angle)
tab_y = np.zeros(npnts)
tab_z = np.sin(2.0*np.pi*tab_x)
yrotate = tab_z * np.sin(rotationangle_radians)
zrotate = tab_z * np.cos(rotationangle_radians)
ax.plot(tab_x, yrotate, zrotate)
init_plt3Dsin()
interact(plt3Dsin,angle=(0,360,5));

tcaswell's comment suggesting the use of the nbagg backend offered a good way to address my problem in that it made the first code block run fast enough to be satisfactory.

Related

Update a chart in realtime with matplotlib

I'd like to update a plot by redrawing a new curve (with 100 points) in real-time.
This works:
import time, matplotlib.pyplot as plt, numpy as np
fig = plt.figure()
ax = fig.add_subplot(111)
t0 = time.time()
for i in range(10000000):
x = np.random.random(100)
ax.clear()
ax.plot(x, color='b')
fig.show()
plt.pause(0.01)
print(i, i/(time.time()-t0))
but there is only ~10 FPS, which seems slow.
What is the standard way to do this in Matplotlib?
I have already read How to update a plot in matplotlib and How do I plot in real-time in a while loop using matplotlib? but these cases are different because they add a new point to an existing plot. In my use case, I need to redraw everything and keep 100 points.
I do not know any technique to gain an order of magnitude. Nevertheless you can slightly increase the FPS with
update the line data instead of creating a new plot with set_ydata (and/or set_xdata)
use Figure.canvas.draw_idle() instead of Figure.canvas.draw() (cf. this question).
Thus I would recommand you to try the following:
import time
import matplotlib.pyplot as plt
import numpy as np
fig = plt.figure()
ax = fig.add_subplot(111)
t0 = time.time()
x = np.random.random(100)
l, *_ = ax.plot(x, color='b')
fig.show()
fig.canvas.flush_events()
ax.set_autoscale_on(False)
for i in range(10000000):
x = np.random.random(100)
l.set_ydata(x)
fig.canvas.draw_idle()
fig.canvas.flush_events()
print(i, i/(time.time()-t0))
Note that, as mentioned by #Bhargav in the comments, changing matplotlib backend can also help (e.g. matplotlib.use('QtAgg')).
I hope this help.

Updating legend entry using imshow in Python3

I'm attempting to add a legend to overlay an imshow() plot displaying an animated array of random numbers. I want the legend to update to display the step that we are viewing.
I attempted to follow the steps here, which shows how to create an animated legend for subplots() using FuncAnimation. I believe the only way to display animated arrays is using ArtistAnimation() and imshow(), but one or both of these is causing me an issue to follow the linked solution.
I've attached below the working code to generate the animated random array, with the legend solution (from link) double commented out.
Any help or advice to remedy would be enormously appreciated.
Thanks,
C
import matplotlib.animation as animation
from matplotlib import colors
import matplotlib.pyplot as plt
import numpy as np
N=20
steps = 100
interval_pause = 100
repeat_pause = 1000
cmap = colors.ListedColormap(['white', 'black'])
bounds=[-1,0,1]
norm = colors.BoundaryNorm(bounds, cmap.N)
fig = plt.figure()
ax = plt.gca()
ax.axes.xaxis.set_ticklabels([])
ax.axes.yaxis.set_ticklabels([])
ax.axes.xaxis.set_ticks([])
ax.axes.yaxis.set_ticks([])
#plt.colorbar(img, cmap=cmap, norm=norm, boundaries=bounds, ticks=[-1,0,1])
array = 2*(np.random.rand(N,N,steps)-0.5)
state = np.zeros(steps)
ims = []
##leg = ax.legend(loc='upper left',prop={'size':12})
for step in range(0,steps):
state = array[:,:,step]
im = plt.imshow(state,interpolation='nearest',cmap=cmap,norm=norm, animated=True)
##lab = 'step = '+str(step)
##leg.texts.set_text(lab)
ims.append([im])##+leg])
ani = animation.ArtistAnimation(fig,ims,interval=interval_pause,repeat_delay=repeat_pause)
#ani.save('animate_evolution '+str(timer())+'.mp4')
plt.show()
As shown in the question you link to it is easier to use a FuncAnimation. This allows to simply update a single legend and imshow plot instead of creating several of those.
Because it's not really clear what the legend is supposed to show for an imshow plot, I just created a blue rectangle. You can of course replace it with whatever you like.
import matplotlib.animation as animation
from matplotlib import colors
import matplotlib.pyplot as plt
import numpy as np
N=20
steps = 100
interval_pause = 100
repeat_pause = 1000
cmap = colors.ListedColormap(['white', 'black'])
bounds=[-1,0,1]
norm = colors.BoundaryNorm(bounds, cmap.N)
fig = plt.figure()
ax = plt.gca()
ax.axes.xaxis.set_ticklabels([])
ax.axes.yaxis.set_ticklabels([])
ax.axes.xaxis.set_ticks([])
ax.axes.yaxis.set_ticks([])
array = 2*(np.random.rand(N,N,steps)-0.5)
leg = ax.legend([plt.Rectangle((0,0),1,1)],["step0"], loc='upper left',prop={'size':12})
img = ax.imshow(array[:,:,0],interpolation='nearest',cmap=cmap,norm=norm, animated=True)
fig.colorbar(img, cmap=cmap, norm=norm, boundaries=bounds, ticks=[-1,0,1])
def update(step):
state = array[:,:,step]
img.set_data(state)
lab = 'step = '+str(step)
leg.texts[0].set_text(lab)
ani = animation.FuncAnimation(fig,update,frames = steps,
interval=interval_pause,repeat_delay=repeat_pause)
plt.show()

matplotlib plot points look fuzzy in Python, sharp in IPython

I'm really confused here; the same code in Python and in IPython Notebook produces two different PNG files with savefig:
import matplotlib.pyplot as plt
import numpy as np
fig = plt.figure(figsize=(5,4))
ax = fig.add_subplot(1,1,1)
abc = np.random.uniform(size=(50000,3))
print abc.shape
x = (2*abc[:,0]-abc[:,1]-abc[:,2])/3.0
y = (abc[:,1]-abc[:,2])/np.sqrt(3)
ax.plot(x,y,'.',markersize=0.25)
ax.set_aspect('equal')
ax.set_xlabel('x')
ax.set_ylabel('y')
with open('/tmp/screenshots/foo.png','wb') as f:
fig.savefig(f, format='png')
IPython Notebook:
Python:
It's the same PC with the same version of Python in both cases. Is there a way to get the image formatting in IPython using both methods? The Python version produces fuzzy dots and looks poor.
Argh -- I figured it out, the dpi parameter gets chosen somehow differently in the two cases, and if I force it to dpi=72 then it looks nice:
import matplotlib.pyplot as plt
import numpy as np
fig = plt.figure(figsize=(5,4))
ax = fig.add_subplot(1,1,1)
abc = np.random.uniform(size=(50000,3))
print abc.shape
x = (2*abc[:,0]-abc[:,1]-abc[:,2])/3.0
y = (abc[:,1]-abc[:,2])/np.sqrt(3)
ax.plot(x,y,'.',markersize=0.25)
ax.set_aspect('equal')
ax.set_xlabel('x')
ax.set_ylabel('y')
with open('/tmp/screenshots/foo.png','wb') as f:
fig.savefig(f, format='png', dpi=72)

Matplotlib copy/duplicate a 3D figure?

I've tried to find a way to copy a 3D figure in matplotlib but I didn't find a solution which is appropriate in my case.
From these posts
How do I reuse plots in matplotlib?
and
How to combine several matplotlib figures into one figure?
Using fig2._axstack.add(fig2._make_key(ax),ax) as in the code below gives quite the good result but figure 2 is not interactive I can't rotate the figure etc :
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
fig = plt.figure(1)
ax = fig.gca(projection = '3d')
ax.plot([0,1],[0,1],[0,1])
fig2 = plt.figure(2)
fig2._axstack.add(fig2._make_key(ax),ax)
plt.show()
An alternative would be to copy objects from ax to ax2 using a copy method proposed in this post How do I reuse plots in matplotlib? but executing the code below returns RuntimeError: Can not put single artist in more than one figure :
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
import numpy as np, copy
fig = plt.figure(1)
ax = fig.gca(projection = '3d')
ax.plot([0,1],[0,1],[0,1])
fig2 = plt.figure(2)
ax2 = fig2.gca(projection = '3d')
for n in range(len(ax.lines)) :
ax2.add_line(copy.copy(ax.lines[n]))
plt.show()
Those codes are pretty simple but I don't want to copy/paste part of my code for drawing similar figures
Thanks in advance for your reply !

Matplotlib Colorbar scientific notation offset

When plotting a colorbar, the top label (I guess this would be called the offset) is mis-centred. This didn't use to happen, I have examples of old code where it was centred above the colorbar, but I have no clue what has changed.
Example:
import numpy as np
import matplotlib.pyplot as plt
z = np.random.random((10,10))
fig, ax = plt.subplots()
im = ax.imshow(z)
cb = fig.colorbar(im)
cb.formatter.set_powerlimits((0, 0))
cb.update_ticks()
plt.show()
Gives this:
As an example of how it used to look (taken from one of my old papers, so
different data etc.)
Using the most recent anaconda python 2.7, on MacOSX, mpl version 1.5.0
Edit: I should also note, tight_layout() does not improve this either, though it is missing from the working example.
You can simply use set_offset_position for the y-axis of the colorbar. Compare:
fig, ax = plt.subplots()
im = ax.imshow(np.random.random((10,10)))
cb = fig.colorbar(im)
cb.formatter.set_powerlimits((0, 0))
cb.ax.yaxis.set_offset_position('right')
cb.update_ticks()
plt.show()
versus
fig, ax = plt.subplots()
im = ax.imshow(np.random.random((10,10)))
cb = fig.colorbar(im)
cb.formatter.set_powerlimits((0, 0))
cb.ax.yaxis.set_offset_position('left')
cb.update_ticks()
plt.show()
All in all, it simply looks like the default has changed from right to left.
Using your above code and matplotlib version 1.4.3 I get the following plot
So this may be a version issue. One possible work around could be to use cb.ax.text()
# -*- coding: utf-8 -*-
import numpy as np
import matplotlib.pyplot as plt
z = np.random.random((10,10))
fig, ax = plt.subplots()
im = ax.imshow(z)
cb = fig.colorbar(im)
cb.ax.text(-0.25, 1, r'$\times$10$^{-1}$', va='bottom', ha='left')
plt.show()
This way you have more control over the centring. The above code gives me the following plot
Note that I use an r at the start of the string so that $\times$ produces the correct symbol.

Categories