2010-11-19 29 views
6

Quiero crear una animación con matplotlib para controlar la convergencia de un algoritmo de agrupamiento. Debería dibujar un diagrama de dispersión de mis datos cuando se invoque por primera vez y dibujar elipses de error cada vez que se actualice el trazado. Intento usar canvas_copy_from_bbox() y restore_region() para guardar el diagrama de dispersión y luego dibujar un nuevo conjunto de elipses cada vez que actualizo el gráfico. Sin embargo, el código solo traza las nuevas elipsis sobre las antiguas, sin borrar primero la gráfica anterior. Sospecho que, de alguna manera, este enfoque no funciona bien con los comandos Ellipse() y add_path(), pero no sé cómo solucionarlo.Borrado de fondo en matplotlib usando wxPython

Aquí está el código:

import wx 
import math 
from math import pi 
from matplotlib.patches import Ellipse 
from matplotlib.figure import Figure 
from matplotlib.backends.backend_wxagg import \ 
    FigureCanvasWxAgg as FigureCanvas 

TIMER_ID = wx.NewId() 


class _MonitorPlot(wx.Frame): 
    def __init__(self, data, scale=1): 
     self.scale = scale 
     wx.Frame.__init__(self, None, wx.ID_ANY, 
          title="FlowVB Progress Monitor", size=(800, 600)) 
     self.fig = Figure((8, 6), 100) 
     self.canvas = FigureCanvas(self, wx.ID_ANY, self.fig) 
     self.ax = self.fig.add_subplot(111) 

     x_lims = [data[:, 0].min(), data[:, 0].max()] 
     y_lims = [data[:, 1].min(), data[:, 1].max()] 

     self.ax.set_xlim(x_lims) 
     self.ax.set_ylim(y_lims) 
     self.ax.set_autoscale_on(False) 

     self.l_data = self.ax.plot(data[:, 0], data[:, 1], color='blue', 
           linestyle='', marker='o') 

     self.canvas.draw() 
     self.bg = self.canvas.copy_from_bbox(self.ax.bbox) 

     self.Bind(wx.EVT_IDLE, self._onIdle) 

    def update_plot(self, pos, cov): 
     self.canvas.restore_region(self.bg) 

     for k in range(pos.shape[0]): 
      l_center, = self.ax.plot(pos[k, 0], pos[k, 1], 
            color='red', marker='+') 

      U, s, Vh = np.linalg.svd(cov[k, :, :]) 
      orient = math.atan2(U[1, 0], U[0, 0]) * 180/pi 
      ellipsePlot = Ellipse(xy=pos[k, :], width=2.0 * math.sqrt(s[0]), 
            height=2.0 * math.sqrt(s[1]), 
            angle=orient, facecolor='none', 
            edgecolor='red') 
      self.ax.add_patch(ellipsePlot) 

     self.canvas.draw() 
     self.canvas.blit(self.ax.bbox) 

Respuesta

5

Lo que pasa es que va a añadir nuevos parches a la trama cada vez, y luego dibujar todos ellos cuando se llama self.canvas.draw().

La solución más rápida es simplemente llamar self.canvas.draw_artist(ellipsePlot) después de añadir cada parche y retire la llamada a self.canvas.draw()

Como un simple e independiente ejemplo:

# Animates 3 ellipses overlain on a scatterplot 
import matplotlib.pyplot as plt 
from matplotlib.patches import Ellipse 
import numpy as np 

num = 10 
x = np.random.random(num) 
y = np.random.random(num) 

plt.ion() 
fig = plt.figure() 
ax = fig.add_subplot(111) 
line = ax.plot(x, y, 'bo') 

fig.canvas.draw() 
bg = fig.canvas.copy_from_bbox(ax.bbox) 

# Pseudo-main loop 
for i in range(100): 
    fig.canvas.restore_region(bg) 

    # Make a new ellipse each time... (inefficient!) 
    for i in range(3): 
     width, height, angle = np.random.random(3) 
     angle *= 180 
     ellip = Ellipse(xy=(0.5, 0.5), width=width, height=height, 
       facecolor='red', angle=angle, alpha=0.5) 
     ax.add_patch(ellip) 
     ax.draw_artist(ellip) 

    fig.canvas.blit(ax.bbox) 

Sin embargo, esto probablemente va a causar consumos de memoria problemas con el tiempo, ya que el objeto ejes hará un seguimiento de todos los artistas agregados. Si sus ejes no se mantienen durante mucho tiempo, esto puede ser insignificante, pero al menos debe tener en cuenta que causará una pérdida de memoria. Una forma de evitar esto es eliminar los artistas de puntos suspensivos de los ejes llamando al ax.remove(ellipsePlot) para cada elipse después de dibujarlos. Sin embargo, esto todavía es un poco ineficiente, ya que constantemente creas y destruyes artistas de elipses, cuando solo puedes actualizarlos. (Crearlos y destruirlos no tiene demasiados gastos generales, sin embargo, es sobre todo un problema de estilo ...)

Si el número de elipses se mantiene igual con el tiempo, es mejor y más fácil simplemente actualizar las propiedades de cada objeto del artista elipse en lugar de crear y agregar nuevos. Esto evitará eliminar las elipsis "viejas" de los ejes, ya que solo existirá el número que necesite.

Como un ejemplo sencillo, independiente de esto:

# Animates 3 ellipses overlain on a scatterplot 
import matplotlib.pyplot as plt 
from matplotlib.patches import Ellipse 
import numpy as np 

num = 10 
x = np.random.random(num) 
y = np.random.random(num) 

plt.ion() 
fig = plt.figure() 
ax = fig.add_subplot(111) 
line = ax.plot(x, y, 'bo') 

fig.canvas.draw() 
bg = fig.canvas.copy_from_bbox(ax.bbox) 

# Make and add the ellipses the first time (won't ever be drawn) 
ellipses = [] 
for i in range(3): 
    ellip = Ellipse(xy=(0.5, 0.5), width=1, height=1, 
      facecolor='red', alpha=0.5) 
    ax.add_patch(ellip) 
    ellipses.append(ellip) 

# Pseudo-main loop 
for i in range(100): 
    fig.canvas.restore_region(bg) 

    # Update the ellipse artists... 
    for ellip in ellipses: 
     ellip.width, ellip.height, ellip.angle = np.random.random(3) 
     ellip.angle *= 180 
     ax.draw_artist(ellip) 

    fig.canvas.blit(ax.bbox) 
+1

en lugar de 'fig.canvas.blit (ax.bbox)' también se podría considerar 'fig.canvas.update()'. Según [este blog] (http://bastibe.de/2013-05-30-speeding-up-matplotlib.html), es igualmente rápido. Para mí, resolvió un problema de desbordamiento de memoria. –

+1

@LucM - Por lo que vale, eso depende mucho del back-end. 'fig.canvas.blit (updated_region)' será mucho más rápido y funcionará perfectamente con 'TkAgg' y algunos otros back-end comunes. Sin embargo, en otros backends, 'fig.canvas.update()' (que actualiza todo el lienzo) es más rápido porque no es totalmente compatible con bligar una subregión. De todos modos, es bueno saber sobre ambos. –

+0

Eso será realmente útil para saber si cambio de servidor. Tuve un desbordamiento de memoria con blit cuando usé PyQt. –