2010-11-19 22 views
6

Chcę utworzyć animację z matplotlib do monitorowania zbieżności algorytmu grupowania. Powinien narysować wykres rozrzutu moich danych, gdy zostanie wywołany po raz pierwszy i narysować elipsy błędów za każdym razem, gdy wykres zostanie zaktualizowany. Próbuję użyć canvas_copy_from_bbox() i restore_region(), aby zapisać wykres rozrzutu, a następnie narysować nowy zestaw elips, gdy aktualizuję wykres. Jednak kod po prostu rysuje nowe elipsy na starych, bez uprzedniego wyczyszczenia poprzedniego wydruku. Podejrzeń, w pewien sposób to podejście nie działa dobrze z poleceniami Ellipse() i add_path(), ale nie wiem jak to naprawić.Czyszczenie tła w matplotlib za pomocą wxPython

Oto kod:

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) 

Odpowiedz

5

Co się dzieje jest to, że jesteś dodając nowe łaty na działce za każdym razem, a następnie rysunek wszystkie z nich podczas rozmowy self.canvas.draw().

Najszybszym rozwiązaniem jest po prostu zadzwoń self.canvas.draw_artist(ellipsePlot) po dodaniu Każdy plaster i usunąć wywołanie self.canvas.draw()

jako prosty, stand-alone przykład:

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

Jednak to będzie prawdopodobnie powodować zużycie pamięci problemy z czasem, ponieważ obiekt osi będzie śledził wszystkich dodanych do niego artystów. Jeśli twoje osie nie kręcą się przez długi czas, może to być nieistotne, ale powinieneś przynajmniej mieć świadomość, że spowoduje to wyciek pamięci. Jednym ze sposobów obejścia tego problemu jest usunięcie artystów elipsy z osi poprzez wywołanie ax.remove(ellipsePlot) dla każdej elipsy po ich narysowaniu. Jest to jednak wciąż mało wydajne, ponieważ ciągle tworzysz i niszczysz wykonawców elipsy, kiedy tylko możesz je zaktualizować. (Tworzenie i niszczenie ich nie ma zbyt dużego nakładu, ale jest to głównie problem stylistyczny ...)

Jeśli liczba elips pozostała niezmieniona z upływem czasu, łatwiej jest po prostu zaktualizować właściwości każdy obiekt artysty elipsy zamiast tworzyć i dodawać nowe. Pozwoli to uniknąć usunięcia "starych" elips z osi, ponieważ tylko liczba, której potrzebujesz, będzie kiedykolwiek istnieć.

Jako prosty, autonomicznej przykład w ten sposób:

# 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

zamiast 'fig.canvas.blit (ax.bbox)' może również rozważyć 'fig.canvas.update()'. Według [tego bloga] (http://bastibe.de/2013-05-30-speeding-up-matplotlib.html) jest równie szybki. Dla mnie rozwiązał problem przepełnienia pamięci. –

+1

@LucM - To, co jest warte, zależy w dużej mierze od zaplecza. 'fig.canvas.blit (updated_region)' będzie znacznie szybszy i będzie działał idealnie z 'TkAgg' i kilkoma innymi popularnymi backendami. Jednak w innych systemach zapleczowych funkcja 'fig.canvas.update()' (która odświeża całe płótno) jest szybsza, ponieważ zabrudzanie podregionu nie jest w pełni obsługiwane. Bez względu na to, dobrze jest wiedzieć o obu. –

+0

To będzie naprawdę przydatne wiedzieć, czy przełączam backend. Podczas korzystania z PyQt miałem przepełnienie pamięci blitem. –