Figures piling up in Tkinter GUI (1.2.0rc2)

Hello everybody,

I'm building a small Tkinter GUI using matplotlib, in which I have to change/update plots quite often depending on user input (with different contents & sizes, in different places in the GUI, etc.; but always only one figure at a time).

As a first resort, I regenerated the figures with plt.figure(...) whenever necessary; unfortunately, the program happily accumulated memory with every new figure until the computer would no longer cooperate in a timely fashion. The following minimal script should demonstrate the tendency:

--- start of script ---

import math
from Tkinter import Tk, Button
import Tkconstants
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
from matplotlib.figure import Figure
from matplotlib import pyplot as plt

def replot():
     global globalCanvas, globalFigure, plotShift

     # any variety of clean-up calls
     if globalFigure is not None:
         plt.close()
         globalCanvas.get_tk_widget().destroy()
         globalFigure.clf()

     globalFigure = Figure(dpi=120, figsize=(4, 4))

     globalCanvas = FigureCanvasTkAgg(globalFigure, master=root)
     globalCanvas.get_tk_widget().grid(row=0, column=1)

     xVals = xrange(100)
     ax = globalFigure.add_subplot(111)
     ax.plot(xVals, [math.sin(x + plotShift) for x in xVals])
     plotShift += 10

# MAIN
globalCanvas = None
globalFigure = None
plotShift = 0 # just to see the plot change

root = Tk()

draw_button = Button(root, text="Replot", command=replot)
draw_button.grid(row=0, column=0, sticky=Tkconstants.N)

root.mainloop()

--- end of script ---

I have tried various clean-up calls, but the effect (of memory piling up) is always the same. Using objgraph (http://mg.pov.lt/objgraph/), I took a look at object counts by adding the following snippet at the end of the "replot" call:

--- start of insertion ---

     import gc
     import objgraph
     gc.collect()
     print "---"
     for c in ('FigureCanvasTkAgg', 'Figure'):
         print "{}\t{}".format(len(objgraph.by_type(c)), c)

--- end of insertion ---

The output shows that the total number of both Figures and FigureCanvasTkAggs increases constantly (i.e., one each after the first call, then two each, etc.), whereas I had expected that old ones get released, and that the count remains at one each.

Now I am wondering if I am missing some detail, e.g., some other clean-up procedure? Or should this work & could be a memory leak in matplotlib or Tkinter? And/or is this approach (of generating a new figure every time) not recommended in the first place? I tried reusing the figure, but some aspects like changing the layout in the GUI and applying new size and dpi then proved tricky in their own ways.

Many thanks in advance,
Hans

2012/11/1 Hans Bering <hans.bering@...2007...>:

Hello everybody,

I'm building a small Tkinter GUI using matplotlib, in which I have to
change/update plots quite often depending on user input (with different
contents & sizes, in different places in the GUI, etc.; but always only
one figure at a time).

As a first resort, I regenerated the figures with plt.figure(...) whenever
necessary; unfortunately, the program happily accumulated memory with
every new figure until the computer would no longer cooperate in a timely
fashion. The following minimal script should demonstrate the tendency:

...

Now I am wondering if I am missing some detail, e.g., some other clean-up
procedure? Or should this work & could be a memory leak in matplotlib or
Tkinter? And/or is this approach (of generating a new figure every time)
not recommended in the first place? I tried reusing the figure, but some
aspects like changing the layout in the GUI and applying new size and dpi
then proved tricky in their own ways.

Many thanks in advance,
Hans

Hi,
I'd recommend to use an embedded plot and only clear and replace its
content (rather than tu use pyplot and recreate the figure multiple
times. I believe, changing of the figure should be possible too.

see e.g. the sample for tkinter
http://matplotlib.org/examples/user_interfaces/embedding_in_tk.html

I only roughly adapted that source to use your function and the memory
usage appears to be more effective (although there is some increase
too - as displayed in Process Explorer). Would some variation of the
following work for you?

hth,
  vbr

···

########################################
#!/usr/bin/env python

import matplotlib
matplotlib.use('TkAgg')
import math

from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg,
NavigationToolbar2TkAgg
from matplotlib.figure import Figure

import Tkinter

root = Tkinter.Tk()
root.wm_title("Embedding in TK")

fig = Figure(figsize=(5,4), dpi=100)
ax = fig.add_subplot(111)

canvas = FigureCanvasTkAgg(fig, master=root)
canvas.show()
canvas.get_tk_widget().pack(side=Tkinter.TOP, fill=Tkinter.BOTH, expand=1)

toolbar = NavigationToolbar2TkAgg( canvas, root )
toolbar.update()
canvas._tkcanvas.pack(side=Tkinter.TOP, fill=Tkinter.BOTH, expand=1)

plotShift = 0
def replot():
    global plotShift, a, f
    ax.clear()
    xVals = xrange(100)
    ax.plot(xVals, [math.sin(x + plotShift) for x in xVals])
    fig.canvas.draw()
    plotShift += 10

button = Tkinter.Button(master=root, text='replot', command=replot)
button.pack(side=Tkinter.BOTTOM)
Tkinter.mainloop()