40

I have a fairly simple plotting routine that looks like this:

from __future__ import division
import datetime
import matplotlib
matplotlib.use('Agg')
from matplotlib.pyplot import figure, plot, show, legend, close, savefig, rcParams
import numpy
from globalconstants import *

    def plotColumns(columnNumbers, t, out, showFig=False, filenamePrefix=None, saveFig=True, saveThumb=True):
        lineProps = ['b', 'r', 'g', 'c', 'm', 'y', 'k', 'b--', 'r--', 'g--', 'c--', 'm--', 'y--', 'k--', 'g--', 'b.-', 'r.-', 'g.-', 'c.-', 'm.-', 'y.-', 'k.-']

        rcParams['figure.figsize'] = (13,11)
        for i in columnNumbers:
            plot(t, out[:,i], lineProps[i])

        legendStrings = list(numpy.zeros(NUMCOMPONENTS)) 
        legendStrings[GLUCOSE] = 'GLUCOSE'
        legendStrings[CELLULOSE] = 'CELLULOSE'
        legendStrings[STARCH] = 'STARCH'
        legendStrings[ACETATE] = 'ACETATE'
        legendStrings[BUTYRATE] = 'BUTYRATE'
        legendStrings[SUCCINATE] = 'SUCCINATE'
        legendStrings[HYDROGEN] = 'HYDROGEN'
        legendStrings[PROPIONATE] = 'PROPIONATE'
        legendStrings[METHANE] = "METHANE"

        legendStrings[RUMINOCOCCUS] = 'RUMINOCOCCUS'
        legendStrings[METHANOBACTERIUM] = "METHANOBACTERIUM"
        legendStrings[BACTEROIDES] = 'BACTEROIDES'
        legendStrings[SELENOMONAS] = 'SELENOMONAS'
        legendStrings[CLOSTRIDIUM] = 'CLOSTRIDIUM'

        legendStrings = [legendStrings[i] for i in columnNumbers]
        legend(legendStrings, loc='best')

        dt = datetime.datetime.now()
        dtAsString = dt.strftime('%d-%m-%Y_%H-%M-%S')

        if filenamePrefix is None:
            filenamePrefix = ''

        if filenamePrefix != '' and filenamePrefix[-1] != '_':
            filenamePrefix += '_'

        if saveFig: 
            savefig(filenamePrefix+dtAsString+'.eps')

        if saveThumb:
            savefig(filenamePrefix+dtAsString+'.png', dpi=300)


        if showFig: f.show()

        close('all')

When I plot this in single iterations, it works fine. However, the moment I put it in a loop, matplotlib throws a hissy fit...

Traceback (most recent call last):
  File "c4hm_param_variation_h2_conc.py", line 148, in <module>
    plotColumns(columnNumbers, timeVector, out, showFig=False, filenamePrefix='c
4hm_param_variation_h2_conc_'+str(hydrogen_conc), saveFig=False, saveThumb=True)

  File "D:\phdproject\alexander paper\python\v3\plotcolumns.py", line 48, in plo
tColumns
    savefig(filenamePrefix+dtAsString+'.png', dpi=300)
  File "C:\Python25\lib\site-packages\matplotlib\pyplot.py", line 356, in savefi
g
    return fig.savefig(*args, **kwargs)
  File "C:\Python25\lib\site-packages\matplotlib\figure.py", line 1032, in savef
ig
    self.canvas.print_figure(*args, **kwargs)
  File "C:\Python25\lib\site-packages\matplotlib\backend_bases.py", line 1476, i
n print_figure
    **kwargs)
  File "C:\Python25\lib\site-packages\matplotlib\backends\backend_agg.py", line
358, in print_png
    FigureCanvasAgg.draw(self)
  File "C:\Python25\lib\site-packages\matplotlib\backends\backend_agg.py", line
314, in draw
    self.figure.draw(self.renderer)
  File "C:\Python25\lib\site-packages\matplotlib\artist.py", line 46, in draw_wr
apper
    draw(artist, renderer, *kl)
  File "C:\Python25\lib\site-packages\matplotlib\figure.py", line 773, in draw
    for a in self.axes: a.draw(renderer)
  File "C:\Python25\lib\site-packages\matplotlib\artist.py", line 46, in draw_wr
apper
    draw(artist, renderer, *kl)
  File "C:\Python25\lib\site-packages\matplotlib\axes.py", line 1735, in draw
    a.draw(renderer)
  File "C:\Python25\lib\site-packages\matplotlib\artist.py", line 46, in draw_wr
apper
    draw(artist, renderer, *kl)
  File "C:\Python25\lib\site-packages\matplotlib\legend.py", line 374, in draw
    bbox = self._legend_box.get_window_extent(renderer)
  File "C:\Python25\lib\site-packages\matplotlib\offsetbox.py", line 209, in get
_window_extent
    px, py = self.get_offset(w, h, xd, yd)
  File "C:\Python25\lib\site-packages\matplotlib\offsetbox.py", line 162, in get
_offset
    return self._offset(width, height, xdescent, ydescent)
  File "C:\Python25\lib\site-packages\matplotlib\legend.py", line 360, in findof
fset
    return _findoffset(width, height, xdescent, ydescent, renderer)
  File "C:\Python25\lib\site-packages\matplotlib\legend.py", line 325, in _findo
ffset_best
    ox, oy = self._find_best_position(width, height, renderer)
  File "C:\Python25\lib\site-packages\matplotlib\legend.py", line 817, in _find_
best_position
    verts, bboxes, lines = self._auto_legend_data()
  File "C:\Python25\lib\site-packages\matplotlib\legend.py", line 669, in _auto_
legend_data
    tpath = trans.transform_path(path)
  File "C:\Python25\lib\site-packages\matplotlib\transforms.py", line 1911, in t
ransform_path
    self._a.transform_path(path))
  File "C:\Python25\lib\site-packages\matplotlib\transforms.py", line 1122, in t
ransform_path
    return Path(self.transform(path.vertices), path.codes,
  File "C:\Python25\lib\site-packages\matplotlib\transforms.py", line 1402, in t
ransform
    return affine_transform(points, mtx)
MemoryError: Could not allocate memory for path

This happens on iteration 2 (counting from 1), if that makes a difference. The code is running on Windows XP 32-bit with python 2.5 and matplotlib 0.99.1, numpy 1.3.0 and scipy 0.7.1.

EDIT: The code has now been updated to reflect the fact that the crash actually occurs at the call to legend(). Commenting that call out solves the problem, though obviously, I would still like to be able to put a legend on my graphs...

Chinmay Kanchi
  • 62,729
  • 22
  • 87
  • 114
  • 1
    Does this answer your question? [How can I release memory after creating matplotlib figures](https://stackoverflow.com/questions/7101404/how-can-i-release-memory-after-creating-matplotlib-figures) – losnihciL Nov 22 '20 at 13:50

5 Answers5

37

Is each loop supposed to generate a new figure? I don't see you closing it or creating a new figure instance from loop to loop.

This call will clear the current figure after you save it at the end of the loop:

pyplot.clf()

I'd refactor, though, and make your code more OO and create a new figure instance on each loop:

from matplotlib import pyplot

while True:
  fig = pyplot.figure()
  ax = fig.add_subplot(111)
  ax.plot(x,y)
  ax.legend(legendStrings, loc = 'best')
  fig.savefig('himom.png')
  # etc....
Marioanzas
  • 1,663
  • 2
  • 10
  • 33
Mark
  • 106,305
  • 20
  • 172
  • 230
  • Bah! I'd forgotten the `clf()` call... Adding that fixed it. – Chinmay Kanchi Mar 03 '10 at 12:44
  • 10
    From @geographica's answer it seems that `clf()` clears the figure, but doesn't release all references to it, unlike `close(fig)`. It won't hold much memory when cleared, but if there's many, it'll add up... – drevicko Jul 02 '13 at 23:48
  • 1
    So if I have a real time plot of about 30k values and I have other arcs,ellipses.circles that I need to plot, what will pyplot.clf() do exactly? Also for animation I have used pyplot.pause(time_duration). How do I preserve the values I have plotted before and then plot the values again and again as I get the data? – praxmon Apr 11 '14 at 14:33
  • 2
    For me it also helped to manually run Python's garbage collection from time to time to get rid of out of memory errors. For this I added `import gc` at the top of my code and then `gc.collect()` after `close()` (or every now and then, depending on the code). – Robin Mar 07 '19 at 10:38
  • 5
    I was able to fix my problem with both `fig.clf()` AND `plt.close(fig)`. Otherwise, by only using `plt.close(fig)`, I still OOMed with more than 10 Gb when plotting and saving ~8000 images in Kaggle notebook. – Hui Liu Jun 28 '20 at 21:54
  • I also had a program that created plots in a loop that was experiencing a memory leak every time a plot was created. I tried calling pyplot.close('all') and pyplot.clf(), but was still seeing a memory leak. A suggestion in https://github.com/matplotlib/matplotlib/issues/8519 to call figure.savefig() rather than pyplot.savefig() was what fixed my memory leak. – Bobby Zandavi Jan 17 '22 at 08:05
  • For the record, it is a very ugly design that `matplotlib` requires explicit closing of figures (even without any GUI usage). Losing the ability for the client program to refer to figures with integers would be a very small price to pay in comparison. – Davis Herring Aug 31 '23 at 14:40
33

I've also run into this error. what seems to have fixed it is

while True:
    fig = pyplot.figure()
    ax = fig.add_subplot(111)
    ax.plot(x,y)
    ax.legend(legendStrings, loc = 'best')
    fig.savefig('himom.png')
    #new bit here
    pylab.close(fig) #where f is the figure

running my loop stably now with fluctuating memory but no consistant increase

ninjasmith
  • 1,624
  • 1
  • 13
  • 10
  • +1 pyplot.close() released the memory in a loop I had and prevented Python from crashing. – geographika Jan 14 '12 at 13:43
  • 1
    even with low memory plots this is necessary (e.g. after generating several hundred). pyplot.close('all') is another approach to consider when plotting multiple figures in a loop – ecoe Oct 06 '13 at 18:07
15

Answer from ninjasmith worked for me too - pyplot.close() enabled my loops to work.

From the pyplot tutorial, Working with multiple figures and axes:

You can clear the current figure with clf() and the current axes with cla(). If you find this statefulness, annoying, don’t despair, this is just a thin stateful wrapper around an object oriented API, which you can use instead (see Artist tutorial)

If you are making a long sequence of figures, you need to be aware of one more thing: the memory required for a figure is not completely released until the figure is explicitly closed with close(). Deleting all references to the figure, and/or using the window manager to kill the window in which the figure appears on the screen, is not enough, because pyplot maintains internal references until close() is called.

Cristian Ciupitu
  • 20,270
  • 7
  • 50
  • 76
geographika
  • 6,458
  • 4
  • 38
  • 56
2

In my case, matplotlib version 3.5.0, As Hui Liu san says,
Following method can keep memory usage low

import matplotlib
print(matplotlib.__version__) #'3.5.0'
import matplotlib.pyplot as plt

plt.savefig('your.png')

# Add both in this order for keeping memory usage low
plt.clf()   
plt.close()
masaya
  • 410
  • 2
  • 9
  • 15
  • 1
    Plt.close() closes the figure and thus also clears the content. So I don't understand why plt.clf() is need here. – Frank_Coumans Dec 05 '22 at 10:28
  • @Frank_Coumans I suspect its because even though the window reference is being removed/garbage collected, the reference to the memory that the figure occupies is not hence the need for `plt.clf()` – DrBwts Aug 31 '23 at 17:29
2

I had a similar issue when I was using it from jupyter, putting plt.clf() and plt.close() in the loop did not work.

But this helped:

import matplotlib
matplotlib.use('Agg')

This disables interactive backend for matplotlib.

Innuendo
  • 631
  • 5
  • 9
  • 1
    Thanks! This solution solved my problem but the other answers above did not. This should be upvoted. – Noé AC May 09 '23 at 16:12