【问题标题】:Using matplotlib.animate to animate a contour plot in python使用 matplotlib.animate 在 python 中为等高线图设置动画
【发布时间】:2013-05-30 17:43:54
【问题描述】:

我有一个 3D 数据数组(2 个空间维度和 1 个时间维度),我正在尝试使用 matplotlib.animate 生成动画等高线图。我使用这个链接作为基础:

http://jakevdp.github.io/blog/2012/08/18/matplotlib-animation-tutorial/

这是我的尝试:

import numpy as np
from matplotlib import pyplot as plt
from matplotlib import animation
from numpy import array, zeros, linspace, meshgrid
from boutdata import collect

# First collect data from files
n = collect("n")   #  This is a routine to collect data
Nx = n.shape[1]
Nz = n.shape[2]
Ny = n.shape[3]
Nt = n.shape[0]

fig = plt.figure()
ax = plt.axes(xlim=(0, 200), ylim=(0, 100))
cont, = ax.contourf([], [], [], 500)

# initialisation function
def init():
    cont.set_data([],[],[])
    return cont,

# animation function
def animate(i): 
    x = linspace(0, 200, Nx)
    y = linspace(0, 100, Ny)
    x,y = meshgrid(x,y)
    z = n[i,:,0,:].T
    cont.set_data(x,y,z)
    return cont, 

anim = animation.FuncAnimation(fig, animate, init_func=init,
                           frames=200, interval=20, blit=True)

plt.show()

但是当我这样做时,我收到以下错误:

Traceback (most recent call last):
  File "showdata.py", line 16, in <module>
    cont, = ax.contourf([], [], [], 500)
  File "/usr/lib/pymodules/python2.7/matplotlib/axes.py", line 7387, in contourf
    return mcontour.QuadContourSet(self, *args, **kwargs)
  File "/usr/lib/pymodules/python2.7/matplotlib/contour.py", line 1112, in __init__
    ContourSet.__init__(self, ax, *args, **kwargs)
  File "/usr/lib/pymodules/python2.7/matplotlib/contour.py", line 703, in __init__
    self._process_args(*args, **kwargs)
  File "/usr/lib/pymodules/python2.7/matplotlib/contour.py", line 1125, in _process_args
    x, y, z = self._contour_args(args, kwargs)
  File "/usr/lib/pymodules/python2.7/matplotlib/contour.py", line 1172, in _contour_args
    x,y,z = self._check_xyz(args[:3], kwargs)
  File "/usr/lib/pymodules/python2.7/matplotlib/contour.py", line 1204, in _check_xyz
    raise TypeError("Input z must be a 2D array.")
TypeError: Input z must be a 2D array.

所以我尝试将所有 [] 替换为 [[],[]] 但这会产生:

Traceback (most recent call last):
  File "showdata.py", line 16, in <module>
    cont, = ax.contourf([[],[]], [[],[]], [[],[]],500)
  File "/usr/lib/pymodules/python2.7/matplotlib/axes.py", line 7387, in contourf
    return mcontour.QuadContourSet(self, *args, **kwargs)
  File "/usr/lib/pymodules/python2.7/matplotlib/contour.py", line 1112, in __init__
    ContourSet.__init__(self, ax, *args, **kwargs)
  File "/usr/lib/pymodules/python2.7/matplotlib/contour.py", line 703, in __init__
    self._process_args(*args, **kwargs)
  File "/usr/lib/pymodules/python2.7/matplotlib/contour.py", line 1125, in _process_args
    x, y, z = self._contour_args(args, kwargs)
  File "/usr/lib/pymodules/python2.7/matplotlib/contour.py", line 1177, in _contour_args
    self.zmax = ma.maximum(z)
  File "/usr/lib/python2.7/dist-packages/numpy/ma/core.py", line 5806, in __call__
    return self.reduce(a)
  File "/usr/lib/python2.7/dist-packages/numpy/ma/core.py", line 5824, in reduce
    t = self.ufunc.reduce(target, **kargs)
ValueError: zero-size array to maximum.reduce without identity

提前致谢!

【问题讨论】:

  • 有一个很好的例子here

标签: python matplotlib contour


【解决方案1】:

Felix Schneider 关于动画变得非常缓慢是正确的。他设置ax.collections = [] 的解决方案删除了​​所有旧的(和被取代的)“艺术家”。一种更外科手术的方法是只删除参与绘制轮廓的艺术家:

for c in cont.collections:
    c.remove()

这在更复杂的情况下很有用,而不是为每一帧重建整个图形。这也适用于 Rehman Ali 的示例;而不是用clf() 清除整个图形,而是保存contourf() 返回的值并在下一次迭代中使用。

这是一个示例代码,类似于 2013 年 6 月 7 日 Luke 的示例代码,演示了仅删除轮廓:

import pylab as plt
import numpy
import matplotlib.animation as animation
#plt.rcParams['animation.ffmpeg_path'] = r"C:\some_path\ffmpeg.exe"   # if necessary

# Generate data for plotting
Lx = Ly = 3
Nx = Ny = 11
Nt = 20
x = numpy.linspace(0, Lx, Nx)
y = numpy.linspace(0, Ly, Ny)
x,y = numpy.meshgrid(x,y)
z0 = numpy.exp(-(x-Lx/2)**2-(y-Ly/2)**2)   # 2 dimensional Gaussian

def some_data(i):   # function returns a 2D data array
    return z0 * (i/Nt)

fig = plt.figure()
ax = plt.axes(xlim=(0, Lx), ylim=(0, Ly), xlabel='x', ylabel='y')

cvals = numpy.linspace(0,1,Nt+1)      # set contour values 
cont = plt.contourf(x, y, some_data(0), cvals)    # first image on screen
plt.colorbar()

# animation function
def animate(i):
    global cont
    z = some_data(i)
    for c in cont.collections:
        c.remove()  # removes only the contours, leaves the rest intact
    cont = plt.contourf(x, y, z, cvals)
    plt.title('t = %i:  %.2f' % (i,z[5,5]))
    return cont

anim = animation.FuncAnimation(fig, animate, frames=Nt, repeat=False)
anim.save('animation.mp4', writer=animation.FFMpegWriter())

【讨论】:

    【解决方案2】:

    这就是我要工作的:

    # Generate grid for plotting
    x = linspace(0, Lx, Nx)
    y = linspace(0, Ly, Ny)
    x,y = meshgrid(x,y)
    
    fig = plt.figure()
    ax = plt.axes(xlim=(0, Lx), ylim=(0, Ly))  
    plt.xlabel(r'x')
    plt.ylabel(r'y')
    
    # animation function
    def animate(i): 
        z = var[i,:,0,:].T
        cont = plt.contourf(x, y, z, 25)
        if (tslice == 0):
            plt.title(r't = %1.2e' % t[i] )
        else:
            plt.title(r't = %i' % i)
    
        return cont  
    
    anim = animation.FuncAnimation(fig, animate, frames=Nt)
    
    anim.save('animation.mp4')
    

    我发现删除 FuncAnimation 调用中的 blit=0 参数也有帮助...

    【讨论】:

    • var[i,:,0,:].T 代表什么?这使我无法实施您的解决方案。
    【解决方案3】:

    这是行:

    cont, = ax.contourf([], [], [], 500)
    

    改为:

     x = linspace(0, 200, Nx)
     y = linspace(0, 100, Ny)
     x, y = meshgrid(x, y)
     z = n[i,:,0,:].T
     cont, = ax.contourf(x, y, z, 500)
    

    您需要使用大小数组。

    【讨论】:

    • 嘿,感谢您的回复,但我现在从同一行收到此错误:TypeError: Length of x must be number of columns in z, and length of y must be number of rows.
    • 对。只需从头开始输入数据。发生的麦芽汁,你把它们放了两次。无论如何,在动画中经常不会伤害你。更新了答案。
    【解决方案4】:

    如果 matplotlib.animation 不适合你,这是另一种做同样事情的方法。如果要不断更新颜色条和图中的其他所有内容,请在一开始使用 plt.ion() 以启用交互式绘图,并使用 plt.draw() 和 plt.clf() 的组合来不断更新绘图.

    import matplotlib.pyplot as plt
    import numpy as np
    
    plt.ion(); plt.figure(1);
    for k in range(10):
        plt.clf(); plt.subplot(121);
        plt.contourf(np.random.randn(10,10)); plt.colorbar();
        plt.subplot(122,polar=True)
        plt.contourf(np.random.randn(10,10)); plt.colorbar();
        plt.draw();
    

    请注意,这适用于包含不同子图和各种类型图(即极坐标或笛卡尔坐标)的图形

    【讨论】:

      【解决方案5】:

      我使用了 Lukes 方法(从 2013 年 6 月 7 日 8:08 开始),但添加了

      ax.collections = [] 
      

      就在

      之前
      cont = plt.contourf(x, y, z, 25).
      

      否则我体验到对于大帧数创建动画会变得很慢。

      【讨论】:

        【解决方案6】:

        我前段时间一直在看这个。在我的情况下,我有一些带有轮廓的子图,我想制作动画。我不想像 Rehman ali 建议的那样使用 plt.clf() 解决方案,因为我使用了我的轴的一些特殊设置(带有 pi 符号等),这些设置也会被清理,所以我更喜欢建议的“remove()”方法成为菲利克斯。问题是仅使用“删除”不会清理内存并最终会阻塞您的计算机,因此您还需要通过将轮廓设置为空列表来明确删除轮廓。

        为了有一个通用的删除例程,它能够删除轮廓和文本,我编写了例程“clean_up_artists”,你应该在所有轴上的每个时间步上使用它。

        此例程清理给定轴“axis”中名为“artist_list”的列表中传递的艺术家。这意味着要为多个子图设置动画,我们需要存储每个轴的艺术家列表,我们需要在每个时间步进行清理。

        在完整代码下方为随机数据的多个子图设置动画。这是不言自明的,所以希望它变得清楚会发生什么。无论如何,我只是想发布它,因为它结合了我在堆栈溢出中发现的几个想法,我只是想出这个工作示例。

        任何有改进代码的建议,请拍-)

        import matplotlib.pyplot as plt
        from matplotlib import cm
        import matplotlib.animation as animation
        import string
        import numpy as np
        
        
        def clean_up_artists(axis, artist_list):
            """
            try to remove the artists stored in the artist list belonging to the 'axis'.
            :param axis: clean artists belonging to these axis
            :param artist_list: list of artist to remove
            :return: nothing
            """
            for artist in artist_list:
                try:
                    # fist attempt: try to remove collection of contours for instance
                    while artist.collections:
                        for col in artist.collections:
                            artist.collections.remove(col)
                            try:
                                axis.collections.remove(col)
                            except ValueError:
                                pass
        
                        artist.collections = []
                        axis.collections = []
                except AttributeError:
                    pass
        
                # second attempt, try to remove the text
                try:
                    artist.remove()
                except (AttributeError, ValueError):
                    pass
        
        
        def update_plot(frame_index, data_list, fig, axis, n_cols, n_rows, number_of_contour_levels, v_min, v_max,
                        changed_artists):
            """
            Update the the contour plots of the time step 'frame_index'
        
            :param frame_index: integer required by animation running from 0 to n_frames -1. For initialisation of the plot,
            call 'update_plot' with frame_index = -1
            :param data_list: list with the 3D data (time x 2D data) per subplot
            :param fig: reference to the figure
            :param axis: reference to the list of axis with the axes per subplot
            :param n_cols: number of subplot in horizontal direction
            :param n_rows: number of subplot in vertical direction
            :param number_of_contour_levels: number of contour levels
            :param v_min: minimum global data value. If None, take the smallest data value in the 2d data set
            :param v_max: maximum global data value. If None, take the largest value in the 2d data set
            :param changed_artists: list of lists of artists which need to be updated between the time steps
            :return: the changed_artists list
            """
        
            nr_subplot = 0  # keep the index of the current subplot  (nr_subplot = 0,1,  n_cols x n_rows -1)
            # loop over the subplots
            for j_col in range(n_cols):
                for i_row in range(n_rows):
        
                    # set a short reference to the current axis
                    ax = axis[i_row][j_col]
        
                    # for the first setup call, add and empty list which can hold the artists belonging to the current axis
                    if frame_index < 0:
                        # initialise the changed artist list
                        changed_artists.append(list())
                    else:
                        # for the next calls of update_plot, remove all artists in the list stored in changed_artists[nr_subplot]
                        clean_up_artists(ax, changed_artists[nr_subplot])
        
                    # get a reference to 2d data of the current time and subplot
                    data_2d = data_list[nr_subplot][frame_index]
        
                    # manually set the levels for better contour range control
                    if v_min is None:
                        data_min = np.nanmin(data_2d)
                    else:
                        data_min = v_min
                    if v_max is None:
                        data_max = np.nanmax(data_2d)
                    else:
                        data_max = v_max
        
                    # set the contour levels belonging to this subplot
                    levels = np.linspace(data_min, data_max, number_of_contour_levels + 1, endpoint=True)
        
                    # create the contour plot
                    cs = ax.contourf(data_2d, levels=levels, cmap=cm.rainbow, zorder=0)
                    cs.cmap.set_under("k")
                    cs.cmap.set_over("k")
                    cs.set_clim(v_min, v_max)
        
                    # store the contours artists to the list of artists belonging to the current axis
                    changed_artists[nr_subplot].append(cs)
        
                    # set some grid lines on top of the contours
                    ax.xaxis.grid(True, zorder=0, color="black", linewidth=0.5, linestyle='--')
                    ax.yaxis.grid(True, zorder=0, color="black", linewidth=0.5, linestyle='--')
        
                    # set the x and y label on the bottom row and left column respectively
                    if i_row == n_rows - 1:
                        ax.set_xlabel(r"Index i ")
                    if j_col == 0:
                        ax.set_ylabel(r"Index j")
        
                    # set the changing time counter in the top left subplot
                    if i_row == 0 and j_col == 1:
                        # set a label to show the current time
                        time_text = ax.text(0.6, 1.15, "{}".format("Time index : {:4d}".format(frame_index)),
                                            transform=ax.transAxes, fontdict=dict(color="black", size=14))
        
                        # store the artist of this label in the changed artist list
                        changed_artists[nr_subplot].append(time_text)
        
                    # for the initialisation call only, set of a contour bar
                    if frame_index < 0:
                        # the first time we add this  (make sure to pass -1 for the frame_index
                        cbar = fig.colorbar(cs, ax=ax)
                        cbar.ax.set_ylabel("Random number {}".format(nr_subplot))
                        ax.text(0.0, 1.02, "{}) {}".format(string.ascii_lowercase[nr_subplot],
                                                           "Random noise {}/{}".format(i_row, j_col)),
                                                 transform=ax.transAxes, fontdict=dict(color="blue", size=12))
        
                    nr_subplot += 1
        
            return changed_artists
        
        
        def main():
            n_pixels_x = 50
            n_pixels_y = 30
            number_of_time_steps = 100
            number_of_contour_levels = 10
            delay_of_frames = 1000
            n_rows = 3  # number of subplot rows
            n_cols = 2  # number of subplot columns
        
            min_data_value = 0.0
            max_data_value = 1.0
        
            # list containing the random plot per sub plot. Insert you own data here
            data_list = list()
            for j_col in range(n_cols):
                for i_row in range(n_rows):
                    data_list.append(np.random.random_sample((number_of_time_steps, n_pixels_x, n_pixels_y)))
        
            # set up the figure with the axis
            fig, axis = plt.subplots(nrows=n_rows, ncols=n_cols, sharex=True, sharey=True, figsize=(12,8))
            fig.subplots_adjust(wspace=0.05, left=0.08, right=0.98)
        
            # a list used to store the reference to the axis of each subplot with a list of artists which belong to this subplot
            # this list will be returned and will be updated every time plot which new artists
            changed_artists = list()
        
            # create first image by calling update_plot with frame_index = -1
            changed_artists = update_plot(-1, data_list, fig, axis, n_cols, n_rows, number_of_contour_levels,
                                                         min_data_value, max_data_value, changed_artists)
        
            # call the animation function. The fargs argument equals the parameter list of update_plot, except the
            # 'frame_index' parameter.
            ani = animation.FuncAnimation(fig, update_plot, frames=number_of_time_steps,
                                          fargs=(data_list, fig, axis, n_cols, n_rows, number_of_contour_levels, min_data_value,
                                                 max_data_value, changed_artists),
                                          interval=delay_of_frames, blit=False, repeat=True)
        
            plt.show()
        
        if __name__ == "__main__":
            main()
        

        【讨论】:

        • 非常用户友好的应用程序。我用过它,它有很大帮助。非常感谢 Eelco。
        【解决方案7】:

        删除 FuncAnimation 调用中的 blit=0 或 blit = True 参数也有帮助 很重要!!!

        【讨论】:

        • this answer 中演示的技术使用blit=True 并且效果很好。
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2019-08-05
        相关资源
        最近更新 更多