Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Clearing background in matplotlib using wxPython

I want to create an animation with matplotlib to monitor the convergence of a clustering algorithm. It should draw a scatterplot of my data when called the first time and draw error ellipses each time the plot is updated. I am trying to use canvas_copy_from_bbox() and restore_region() to save the scatterplot and then draw a new set of ellipses whenever I'm updating the plot. However, the code just plots the new ellipses on top of the old ones, without clearing the previous plot first. I suspect, somehow this approach doesn't work well with the Ellipse() and add_path() commands, but I don't know how to fix this.

Here is the code:

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)
like image 803
oceanhug Avatar asked Nov 19 '10 05:11

oceanhug


1 Answers

What's happening is that you're adding new patches to the plot each time, and then drawing all of them when you call self.canvas.draw().

The quickest fix is to just call self.canvas.draw_artist(ellipsePlot) after adding each patch and remove the call to self.canvas.draw()

As a simple, stand-alone example:

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

However, this will probably cause memory consumptions problems over time, as the axes object will keep track of all artists added to it. If your axes doesn't hang around for a long time, this may be negligible, but you should at least be aware that it will cause a memory leak. One way around this is to remove the ellipsis artists from the axes by calling ax.remove(ellipsePlot) for each ellipse after drawing them. However, this is still slightly inefficient, as you're constantly creating and destroying ellipse artists, when you could just update them. (Creating and destroying them doesn't have much overhead at all, though, it's mostly a stylistic issue...)

If the number of ellipses is staying the same over time it's better and easier to just update the properties of each ellipse artist object instead of creating and adding new ones. This will avoid have remove "old" ellipses from the axes, as only the number that you need will ever exist.

As a simple, stand-alone example of this:

# 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)
like image 58
Joe Kington Avatar answered Sep 20 '22 07:09

Joe Kington