Matplotlib не хватает памяти при построении в цикле

У меня есть довольно простая подпрограмма, которая выглядит следующим образом:

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

Когда я строю это в одной итерации, он работает нормально. Тем не менее, в тот момент, когда я поместил его в цикл, matplotlib бросил шипящий приступ...

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

Это происходит на итерации 2 (считая от 1), если это имеет значение. Код работает на 32-битной Windows XP с python 2.5 и matplotlib 0.99.1, numpy 1.3.0 и scipy 0.7.1.

РЕДАКТИРОВАТЬ: код был обновлен, чтобы отразить тот факт, что сбой на самом деле происходит при вызове legend(), Комментирование этого вызова решает проблему, хотя, очевидно, я все же хотел бы добавить легенду на мои графики...

5 ответов

Решение

Должен ли каждый цикл генерировать новую фигуру? Я не вижу, как вы закрываете его или создаете новый экземпляр фигуры из цикла в цикл.

Этот вызов очистит текущий рисунок после его сохранения в конце цикла:

pyplot.clf ()

Однако я бы сделал рефакторинг и сделал бы ваш код более оригинальным и создал бы новый экземпляр фигуры в каждом цикле:

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....

Я также столкнулся с этой ошибкой. что, кажется, исправило это

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

теперь мой цикл работает стабильно с колебанием памяти, но без постоянного увеличения

Ответ от ниндзя работает для меня тоже - pyplot.close() позволил моим петлям работать.

Из учебника pyplot, Работа с несколькими фигурами и осями:

Вы можете очистить текущий показатель с clf() и текущие оси с cla(), Если вы находите это состояние, раздражающее, не отчаивайтесь, это просто тонкая обертка с сохранением состояния вокруг объектно-ориентированного API, которую вы можете использовать вместо этого (см. Учебное пособие по Artist)

Если вы делаете длинную последовательность фигур, вам нужно знать еще одну вещь: память, необходимая для фигуры, не освобождается полностью, пока фигура не будет явно закрыта close(), Удаление всех ссылок на рисунок и / или использование диспетчера окон для закрытия окна, в котором рисунок появляется на экране, недостаточно, поскольку pyplot поддерживает внутренние ссылки до close() называется.

В моем случае matplotlib версии 3.5.0. Как говорит Хуэй Лю Сан,
следующий метод может снизить использование памяти.

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

У меня была аналогичная проблема, когда я использовал ее с jupyter, поставивplt.clf()иplt.close()в цикле не получилось.

Но это помогло:

      import matplotlib
matplotlib.use('Agg')

Это отключает интерактивный бэкэнд для matplotlib.

Другие вопросы по тегам