连续3D绘图(即图形更新)

13 投票
3 回答
31946 浏览
提问于 2025-04-16 12:54

我有一个模拟程序,它会在每次迭代时计算表面数据。我想要在同一个窗口里不断绘制这些数据的表面图(在每次迭代时更新图形),这样我就能看到数据是如何变化的,并检查算法的效果。

我的想法是创建一个类,这个类可以初始化窗口和图形,然后在模拟循环中重新绘制到这个窗口里。以下是我想到的这个类:

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 个回答

2

我很感谢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)

这个类还不是我想要的样子,我有两个立刻想解决的问题:

  1. 过一会儿,窗口会被Ubuntu变灰,因为(我猜)Ubuntu认为这个程序没有响应。这可能是Ubuntu的问题,但还是挺烦人的。
  2. 我一直在尝试找出如何在动画播放时用鼠标旋转图形。

我会在另一个讨论中尝试解决这些问题。

编辑: 好的,我刚刚试了Paul建议的代码,对我也有效。不过,在尝试的过程中,我意识到MatPlotLib可能不是做实时动画的最佳选择。对我来说,它非常慢(可能只是3D的原因?)。

所以最后我还是会坚持使用上面提到的MayaVI实现,除了前面提到的两个问题,它运行得很好。

编辑:如果你选择MatPlotLib的解决方案,我发现你可以在绘图类的声明中加入这一行 matplotlib.interactive(True)。这样你就可以让MatPlotLib只在绘图类中定义。

5

我之前也遇到过类似的问题,这个方法对我有效:

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(),这样可以在暂停之前先完成图形的绘制。

9

如果你在做动画(互动)的图表,就不需要使用 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))

撰写回答