繁体   English   中英

使用matplotlib.animate在python中为等高线图设置动画

[英]Using matplotlib.animate to animate a contour plot in python

我有一个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

提前致谢!

这就是我的工作:

# 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参数也有帮助......

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())

这是一行:

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)

您需要使用大小的数组进行插入。

如果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();

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

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

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

此例程清除在给定轴“轴”中名为“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()

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

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

ax.collections = [] 

就在之前

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

否则我经历过,对于大帧数,创建动画会变得非常慢。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM