连续3D绘图(即图形更新)
我有一个模拟程序,它会在每次迭代时计算表面数据。我想要在同一个窗口里不断绘制这些数据的表面图(在每次迭代时更新图形),这样我就能看到数据是如何变化的,并检查算法的效果。
我的想法是创建一个类,这个类可以初始化窗口和图形,然后在模拟循环中重新绘制到这个窗口里。以下是我想到的这个类:
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
from matplotlib import cm
from matplotlib.ticker import LinearLocator, FixedLocator, FormatStrFormatter
import matplotlib
matplotlib.interactive( False )
class plot3dClass( object ):
def __init__( self, systemSideLength, lowerCutoffLength ):
self.systemSideLength = systemSideLength
self.lowerCutoffLength = lowerCutoffLength
self.fig = plt.figure()
self.ax = self.fig.add_subplot( 111, projection='3d' )
self.ax.set_zlim3d( -10e-9, 10e9 )
X = np.arange( 0, self.systemSideLength, self.lowerCutoffLength )
Y = X
self.X, self.Y = np.meshgrid(X, Y)
self.ax.w_zaxis.set_major_locator( LinearLocator( 10 ) )
self.ax.w_zaxis.set_major_formatter( FormatStrFormatter( '%.03f' ) )
heightR = np.zeros( self.X.shape )
self.surf = self.ax.plot_surface( self.X, self.Y, heightR, rstride=1, cstride=1, cmap=cm.jet, linewidth=0, antialiased=False )
#~ self.fig.colorbar( self.surf, shrink=0.5, aspect=5 )
plt.show()
def drawNow( self, heightR ):
self.surf = self.ax.plot_surface( self.X, self.Y, heightR, rstride=1, cstride=1, cmap=cm.jet, linewidth=0, antialiased=False )
plt.draw() # redraw the canvas
time.sleep(1)
我在这个代码中遇到的问题是,代码在'plt.show()'这一行停住了,只有在我关闭图形窗口后才会继续执行。而且我不确定'self.ax.plot_surface( ... )'和'plt.draw()'的调用是否能像我希望的那样更新图形。
那么,这个类的方向对吗?
如果对:需要做哪些修改?
如果不对:能不能请大家给我一些建议,告诉我怎么才能实现我想要的效果?
我知道这个问题对其他人来说可能很简单,但我(老实说)昨天花了一整天在谷歌上查找和尝试,现在真的有点无从下手了……
任何帮助都将非常感谢,这样我就可以回到我的实际工作中。
提前谢谢大家。
作为参考:
我还找到了一段代码,它实现了我想要的效果,但它是2D的,所以对我没有直接帮助:
from pylab import *
import time
ion()
tstart = time.time() # for profiling
x = arange(0,2*pi,0.01) # x-array
line, = plot(x,sin(x))
for i in arange(1,200):
line.set_ydata(sin(x+i/10.0)) # update the data
draw() # redraw the canvas
print 'FPS:' , 200/(time.time()-tstart)
3 个回答
我很感谢Paul的回答,虽然我还没试过。
与此同时,我找到了一种可以用OpenGL和MayaVI实现的解决方案,这对我来说挺好,因为我只需要实时快速的视觉反馈。不过,我在Ubuntu上安装了以下软件包:python-enthoughtbase和mayavi2。
这是代码:
import numpy as np
import time
from enthought.mayavi import mlab
from enthought.tvtk.tools import visual
class plot3dClass( object ):
def __init__( self, systemSideLength, lowerCutoffLength ):
self.systemSideLength = systemSideLength
self.lowerCutoffLength = lowerCutoffLength
rangeMax = self.systemSideLength
X = np.arange( 0, self.systemSideLength, self.lowerCutoffLength )
Y = X
matrixSize = int( round( self.systemSideLength / self.lowerCutoffLength ) )
heightR = np.zeros( ( matrixSize, matrixSize ) )
fig = mlab.figure(size=(500,500))
visual.set_viewer(fig)
self.surf = mlab.surf( X, Y, heightR, warp_scale = 1e1 ) # NOTE: the warp_scale factor is relative to the scale of the x- and y-axes
box_extent = ( 0,rangeMax, 0,rangeMax, -1e-7,1e-7 ) # NOTE: the extent options refers to the size and position in the 3D space relative to the origin
mlab.outline(self.surf, color=(0.7, .7, .7), extent = box_extent )
def drawNow( self, heightR ):
self.surf.mlab_source.scalars = heightR
time.sleep(0.033)
这个类还不是我想要的样子,我有两个立刻想解决的问题:
- 过一会儿,窗口会被Ubuntu变灰,因为(我猜)Ubuntu认为这个程序没有响应。这可能是Ubuntu的问题,但还是挺烦人的。
- 我一直在尝试找出如何在动画播放时用鼠标旋转图形。
我会在另一个讨论中尝试解决这些问题。
编辑: 好的,我刚刚试了Paul建议的代码,对我也有效。不过,在尝试的过程中,我意识到MatPlotLib可能不是做实时动画的最佳选择。对我来说,它非常慢(可能只是3D的原因?)。
所以最后我还是会坚持使用上面提到的MayaVI实现,除了前面提到的两个问题,它运行得很好。
编辑:如果你选择MatPlotLib的解决方案,我发现你可以在绘图类的声明中加入这一行 matplotlib.interactive(True)
。这样你就可以让MatPlotLib只在绘图类中定义。
我之前也遇到过类似的问题,这个方法对我有效:
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
plt.ion()
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
for k in xrange(0,X_range):
ax.plot(x_input, y_input, z_input)
plt.draw()
plt.pause(0.02)
ax.cla()
对你来说,我想解决办法可能和这个最优答案差不多,只不过把time.sleep()
换成plt.pause()
,这样可以在暂停之前先完成图形的绘制。
如果你在做动画(互动)的图表,就不需要使用 plt.show()
这个命令了。同时,你要把互动设置为 True,而不是 False,这样就相当于在你的二维示例中调用了 ion()
。另外,如果你不想看到之前帧的表面图,就需要用 remove()
把它们去掉。
总的来说,你的思路已经很接近了。
这个方法对我有效:
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
from matplotlib import cm
from matplotlib.ticker import LinearLocator, FixedLocator, FormatStrFormatter
import matplotlib, time
class plot3dClass( object ):
def __init__( self, systemSideLength, lowerCutoffLength ):
self.systemSideLength = systemSideLength
self.lowerCutoffLength = lowerCutoffLength
self.fig = plt.figure()
self.ax = self.fig.add_subplot( 111, projection='3d' )
self.ax.set_zlim3d( -10e-9, 10e9 )
rng = np.arange( 0, self.systemSideLength, self.lowerCutoffLength )
self.X, self.Y = np.meshgrid(rng,rng)
self.ax.w_zaxis.set_major_locator( LinearLocator( 10 ) )
self.ax.w_zaxis.set_major_formatter( FormatStrFormatter( '%.03f' ) )
heightR = np.zeros( self.X.shape )
self.surf = self.ax.plot_surface(
self.X, self.Y, heightR, rstride=1, cstride=1,
cmap=cm.jet, linewidth=0, antialiased=False )
# plt.draw() maybe you want to see this frame?
def drawNow( self, heightR ):
self.surf.remove()
self.surf = self.ax.plot_surface(
self.X, self.Y, heightR, rstride=1, cstride=1,
cmap=cm.jet, linewidth=0, antialiased=False )
plt.draw() # redraw the canvas
time.sleep(1)
matplotlib.interactive(True)
p = plot3dClass(5,1)
for i in range(2):
p.drawNow(np.random.random(p.X.shape))