Usando matplotlib.animate para animar un trazado de contorno en python

Tengo una matriz de datos en 3D (2 dimensiones espaciales y 1 dimensión de tiempo) y estoy tratando de producir un gráfico de contorno animado utilizando matplotlib.animate. Estoy usando este enlace como base:

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

Y aquí está mi bash:

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

Pero cuando hago esto, me sale el siguiente error:

 Traceback (most recent call last): File "showdata.py", line 16, in  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. 

Así que he intentado reemplazar todos los [] por [[], []] pero esto produce:

 Traceback (most recent call last): File "showdata.py", line 16, in  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 

¡Gracias por adelantado!

Esto es lo que tengo que trabajar:

 # 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') 

Descubrí que eliminar el argumento blit = 0 en la llamada a FuncAnimation también ayudó …

Felix Schneider está en lo cierto acerca de que la animación se está haciendo muy lenta. Su solución de configuración de ax.collections = [] elimina todos los “artistas” antiguos (y sustituidos). Un enfoque más quirúrgico es eliminar solo a los artistas involucrados en el dibujo de los contornos:

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

que es útil en casos más complicados, en lugar de reconstruir la figura completa para cada cuadro. Esto también funciona en el ejemplo de Rehman Ali; en lugar de borrar toda la figura con clf() el valor devuelto por contourf() se guarda y se utiliza en la siguiente iteración.

Aquí hay un código de ejemplo similar al de Luke del 7 de junio de 2013, que demuestra la eliminación de los contornos solamente:

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

Esta es la línea:

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

cambiar a:

  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) 

Necesitas intilizar con matrices de tamaño.

Esta es otra forma de hacer lo mismo si matplotlib.animation no funciona para usted. Si desea actualizar continuamente la barra de colores y todo lo demás en la figura, use plt.ion () al principio para habilitar el trazado interactivo y use un combo de plt.draw () y plt.clf () para actualizar continuamente el trazado. .

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

Tenga en cuenta que esto funciona con figuras que contienen diferentes subplots y varios tipos de plots (es decir, polares o cartesianas)

He estado mirando esto hace un tiempo. En mi situación tuve algunas subplots con contornos que quería animar. No quería usar la solución plt.clf () como Rehman ali sugirió, ya que usé una configuración especial de mi eje (con símbolos pi, etc.) que también se limpiaría, así que prefiero el enfoque ‘eliminar ()’ sugerir se felix El problema es que solo el uso de ‘eliminar’ no limpia la memoria y eventualmente obstruirá su computadora, por lo que necesita eliminar explícitamente los contornos al establecerla también en una lista vacía.

Para tener una rutina de eliminación genérica que pueda quitar contornos y texto, escribí la rutina ‘clean_up_artists’ que debería usar en cada paso de tiempo en todos los ejes.

Esta rutina limpia a los artistas que se pasan en una lista llamada ‘lista de artistas’ en un eje ‘eje’ dado. Esto significa que para animar múltiples subplots, necesitamos almacenar las listas de artistas para cada eje que necesitamos limpiar cada paso del tiempo.

Debajo del código completo para animar una serie de subplots de datos aleatorios. Es bastante autoexplicativo, así que espero que quede claro lo que sucede. De todos modos, solo pensé en publicarlo, ya que combina varias ideas que encontré en el desbordamiento de stack, lo que acabo de dar con este ejemplo funcional.

Cualquier persona con sugerencias para mejorar el código, por favor dispara-)

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

¡La eliminación de blit = 0 o blit = True argumento en la llamada a FuncAnimation también ayudó es importante!

Utilicé el enfoque de Lukes (desde el 7 de junio de 2013 a las 8:08), pero añadí

 ax.collections = [] 

justo antes de

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

De lo contrario, experimenté que la creación de la animación será muy lenta para los números de fotogtwigs grandes.