Memory usage

Hi All, I put a small test case together based on

    > wxcursor_demo. When clicking the button1 it should show a
    > new plot, but it does not.

It does for me...

Do you mean a new plot window, or just redraw in the current figure?
You may not see that it has redrawn the figure unless you somehow
change the plot (ed pan/zoom). I modified your example so that each
time you redraw the frequency of the sine wave changes to make it more
apparent.

    > I think my memory issue has to do with this as I created a
    > new self.canvas each time, instead of doing a clear, but
    > the big question is what do I need to call to show the new
    > plot.

For one thing, add a gc.collect after you call clear to trigger the
garbage collector.

I added a memory report function and noticed something surprising. If
I just plotted the same sine wave over and over, memory usage was
flat. If I changed the sine wave frequency (still creating arrays of
the same size though) memory usage increased with each plot.

matplotlib does cache some information, mainly text and font stuff)
between draws which can cause memory to grow, but usually this is a
small amount. I'll explore further.

Here is my lightly modified version of your test script. The
report_memory function only works on unix like systems.

JDH

#!/usr/bin/env python
"""
Show how to have wx draw a cursor over an axes that moves with the
mouse and reports the data coords
"""

from matplotlib.numerix import arange, sin, pi

import matplotlib
matplotlib.use('WXAgg')
from matplotlib.backends.backend_wxagg import FigureCanvasWxAgg as FigureCanvas
from matplotlib.backends.backend_wx import NavigationToolbar2Wx

from matplotlib.figure import Figure
import wx

import os, sys, time, gc

def report_memory(i):
    pid = os.getpid()
    a2 = os.popen('ps -p %d -o rss,sz' % pid).readlines()
    print i, ' ', a2[1],
    return int(a2[1].split()[1])

class CanvasFrame(wx.Frame):

    def __init__(self):
        wx.Frame.__init__(self,None,-1,
                         'CanvasFrame',size=(550,350))

        self.SetBackgroundColour(wx.NamedColor("WHITE"))

        self.figure = Figure()
        self.axes = self.figure.add_subplot(111)
        t = arange(0.0,3.0,0.01)
        s = sin(2*pi*t)

        self.axes.plot(t,s)
        self.axes.set_xlabel('Time (s)')
        self.axes.set_ylabel('Price ($)')
        self.canvas = FigureCanvas(self, -1, self.figure)
        self.canvas.mpl_connect('motion_notify_event', self.mouse_move)
        self.sizer =wx.BoxSizer(wx.VERTICAL)
        self.sizer.Add(self.canvas, 1, wx.LEFT | wx.TOP | wx.GROW)
        buttonId = wx.NewId()
        self.button1 = wx.Button(id=buttonId, label='Clear and do figure again',
                        parent=self)
        self.button1.Bind(wx.EVT_BUTTON, self.OnButton1Button, id=buttonId)
        self.sizer.Add(self.button1, 0, wx.LEFT | wx.TOP)
        
        self.SetSizer(self.sizer)
        self.Fit()

        self.statusBar =wx.StatusBar(self, -1)
        self.statusBar.SetFieldsCount(1)
        self.SetStatusBar(self.statusBar)

        self.add_toolbar() # comment this out for no toolbar

        if wx.VERSION_STRING >= '2.5':
            self.Bind(wx.EVT_PAINT, self.OnPaint)
        else:
            wx.EVT_PAINT(self, self.OnPaint)

        self.cnt = 0
    def OnButton1Button(self, event):
        print 'done it'
        self.figure.clear()
        self.axes = self.figure.add_subplot(111)
        t = arange(0.0,4.0,0.01)
        s = sin(2*pi*t*(self.cnt+1))
        #s = sin(2*pi*t)

        self.axes.plot(t,s)
        self.axes.set_xlabel('Time 2 (s)')
        self.axes.set_ylabel('Price 2 ($)')

        report_memory(self.cnt)
        self.cnt+=1
        gc.collect()
    def mouse_move(self, event):
        self.draw_cursor(event)

    def add_toolbar(self):
        self.toolbar = NavigationToolbar2Wx(self.canvas)
        self.toolbar.Realize()
        tw, th = self.toolbar.GetSizeTuple()
        fw, fh = self.canvas.GetSizeTuple()
        self.toolbar.SetSize(wx.Size(fw, th))
        self.sizer.Add(self.toolbar, 0, wx.LEFT | wx.EXPAND)
        # update the axes menu on the toolbar
        self.toolbar.update()

    def OnPaint(self, event):
        self.erase_cursor()
        try: del self.lastInfo
        except AttributeError: pass
        self.canvas.draw()
        event.Skip()

    def draw_cursor(self, event):
        'event is a MplEvent. Draw a cursor over the axes'
        if event.inaxes is None:
            self.erase_cursor()
            try: del self.lastInfo
            except AttributeError: pass
            return
        canvas = self.canvas
        figheight = canvas.figure.bbox.height()
        ax = event.inaxes
        left,bottom,width,height = ax.bbox.get_bounds()
        bottom = figheight-bottom
        top = bottom - height
        right = left + width
        x, y = event.x, event.y
        y = figheight-y

        dc =wx.ClientDC(canvas)
        dc.SetLogicalFunction(wx.XOR)
        wbrush =wx.Brush(wx.Colour(255,255,255), wx.TRANSPARENT)
        wpen =wx.Pen(wx.Colour(200, 200, 200), 1, wx.SOLID)
        dc.SetBrush(wbrush)
        dc.SetPen(wpen)

        dc.ResetBoundingBox()
        dc.BeginDrawing()

        x, y, left, right, bottom, top = [int(val) for val in x, y, left, right, bottom, top]

        self.erase_cursor()
        line1 = (x, bottom, x, top)
        line2 = (left, y, right, y)
        self.lastInfo = line1, line2, ax, dc
        dc.DrawLine(*line1) # draw new
        dc.DrawLine(*line2) # draw new
        dc.EndDrawing()

        time, price = event.xdata, event.ydata
        self.statusBar.SetStatusText("Time=%f Price=%f"% (time, price), 0)

    def erase_cursor(self):
        try: lastline1, lastline2, lastax, lastdc = self.lastInfo
        except AttributeError: pass
        else:
            lastdc.DrawLine(*lastline1) # erase old
            lastdc.DrawLine(*lastline2) # erase old

class App(wx.App):

    def OnInit(self):
        'Create the main window and insert the custom frame'
        frame = CanvasFrame()
        frame.Show(True)

        return True

app = App(0)
app.MainLoop()

Hi John,

I had to add a “self.canvas.Refresh()” to the end of OnButton1Button,
i.e. after changing the plot within the same figure.

The “gc” and “report_memory” stuff does not work on Bill’s stuff.
In my app I still had a problem that figures overlapped each other.
I attach the new sample, but the code which seems to cause a problem is
self.figure = Figure(frameon=False)
if I remove the frameon=False then things work, but I obviously get the
grey background.
BTW, I don’t see any memory issue using the technic shown in this
sample (besides a few bytes here or there, before it grew by nearly 1
MB for each figure display.
See you
Werner
John Hunter wrote:

wxcursor_demoWClear.py (5.24 KB)

···

:slight_smile:

"Werner" == Werner F Bruhin writes:
    Werner> Hi All, I put a small test case together based on
Werner> wxcursor_demo. When clicking the button1 it should show a
Werner> new plot, but it does not.
It does for me...
Do you mean a new plot window, or just redraw in the current figure?
You may not see that it has redrawn the figure unless you somehow
change the plot (ed pan/zoom). I modified your example so that each
time you redraw the frequency of the sine wave changes to make it more
apparent.
Werner> I think my memory issue has to do with this as I created a
Werner> new self.canvas each time, instead of doing a clear, but
Werner> the big question is what do I need to call to show the new
Werner> plot.
For one thing, add a gc.collect after you call clear to trigger the
garbage collector.
I added a memory report function and noticed something surprising. If
I just plotted the same sine wave over and over, memory usage was
flat. If I changed the sine wave frequency (still creating arrays of
the same size though) memory usage increased with each plot.
matplotlib does cache some information, mainly text and font stuff)
between draws which can cause memory to grow, but usually this is a
small amount. I'll explore further.
Here is my lightly modified version of your test script. The
report_memory function only works on unix like systems.
JDH

<werner.bruhin@…185…>

Hi Werner,

I think what you may want is something like this:

    def OnButton1Button(self, event):
        print 'done it'
        ### self.figure.clear()
        ### self.axes = self.figure.add_subplot(111)
        self.axes.cla() # <-- clear the axes
        t = arange(0.0,4.0,0.01)
        s = sin(2*pi*t)

        self.axes.plot(t,s)
        self.axes.set_xlabel('Time 2 (s)')
        self.axes.set_ylabel('Price 2 ($)')
        self.canvas.draw() # <-- force a redraw

Is that OK? It works for me on Windows and Linux. I do the
same thing for 'make a fresh plot' in my own codes that I know
work OK on Mac OS X as well.

Cheers,

--Matt

Hi Matt,

Matt Newville wrote:

Hi Werner,

I think what you may want is something like this:

    def OnButton1Button(self, event):
        print 'done it'
        ### self.figure.clear()
        ### self.axes = self.figure.add_subplot(111)
        self.axes.cla() # <-- clear the axes
        t = arange(0.0,4.0,0.01)
        s = sin(2*pi*t)

        self.axes.plot(t,s)
        self.axes.set_xlabel('Time 2 (s)')
        self.axes.set_ylabel('Price 2 ($)')
        self.canvas.draw() # <-- force a redraw

Is that OK? It works for me on Windows and Linux. I do the
same thing for 'make a fresh plot' in my own codes that I know
work OK on Mac OS X as well.

Yes, that works too.

So, I can do:
self.figure.clear()
...
self.canvas.Refresh()

or:
self.axes.cla()
...
self.canvas.draw()

or:
self.figure.clear()
...
self.canvas.draw()

Note that all of the above still have problems if I do:
         self.figure = Figure(frameon=False)

i.e. the "frameon=False" bit causes the figure/axes not to get totally cleared.

Cheers,

--Matt

See you
Werner