производительность matplotlib savefig, сохранение нескольких png в цикле

Я надеюсь найти способ оптимизировать следующую ситуацию. У меня есть большой контурный график, созданный с помощью imshow из matplotlib. Затем я хочу использовать этот контурный график для создания большого количества изображений png, где каждое изображение представляет собой небольшую часть контурного изображения, изменяя пределы x и y и соотношение сторон.

Таким образом, данные графика не меняются в цикле, меняются только пределы осей и соотношение сторон между каждым изображением png.

Следующий MWE создает 70 изображений PNG в папке «figs», демонстрируя упрощенную идею. Около 80% времени выполнения занимает fig.savefig('figs/'+filename).

Я изучил следующее, не придумав улучшения:

  • Альтернатива matplotlib с упором на скорость - я изо всех сил пытался найти какие-либо примеры/документацию контурных/поверхностных графиков с аналогичными требованиями.
  • Многопроцессорность. Подобные вопросы, которые я видел здесь, по-видимому, требуют вызова fig = plt.figure() и ax.imshow внутри цикла, поскольку fig и axe не могут быть замаринованы. В моем случае это будет дороже, чем любой прирост скорости, достигнутый за счет реализации многопроцессорности.

Я был бы признателен за любые идеи или предложения, которые у вас могут быть.

import numpy as np
import matplotlib as mpl
mpl.use('agg')
import matplotlib.pyplot as plt
import time, os

def make_plot(x, y, fix, ax):
    aspect = np.random.random(1)+y/2.0-x
    xrand = np.random.random(2)*x
    xlim = [min(xrand), max(xrand)]
    yrand = np.random.random(2)*y
    ylim = [min(yrand), max(yrand)]
    filename = '{:d}_{:d}.png'.format(x,y)

    ax.set_aspect(abs(aspect[0]))
    ax.set_xlim(xlim)
    ax.set_ylim(ylim)
    fig.savefig('figs/'+filename)

if not os.path.isdir('figs'):
    os.makedirs('figs')
data = np.random.rand(25, 25)

fig = plt.figure()
ax = fig.add_axes([0., 0., 1., 1.])
# in the real case, imshow is an expensive calculation which can't be put inside the loop
ax.imshow(data, interpolation='nearest')

tstart = time.clock()
for i in range(1, 8):
    for j in range(3, 13):
        make_plot(i, j, fig, ax)

print('took {:.2f} seconds'.format(time.clock()-tstart))

person Tim B    schedule 08.12.2016    source источник


Ответы (1)


Поскольку ограничением в данном случае является вызов plt.savefig(), его нельзя сильно оптимизировать. Внутри фигура визуализируется с нуля, и это занимает некоторое время. Возможно, уменьшение количества отрисовываемых вершин может немного сократить время.

Время запуска вашего кода на моей машине (Win 8, i5 с 4 ядрами 3,5 ГГц) составляет 2,5 секунды. Это кажется не так уж плохо. Можно добиться небольшого улучшения, используя Многопроцессорность.

Примечание о многопроцессорности: может показаться удивительным, что использование конечного автомата pyplot внутри multiprocessing вообще должно работать. Но это так. И в этом случае здесь, поскольку каждое изображение основано на одном и том же объекте фигуры и оси, даже не нужно создавать новые фигуры и оси.

Я изменил ответ, который я дал здесь некоторое время назад для вашего случае и общее время сокращается примерно вдвое при использовании многопроцессорной обработки и 5 процессов на 4 ядрах. Я добавил гистограмму, показывающую эффект многопроцессорности.

import numpy as np
#import matplotlib as mpl
#mpl.use('agg') # use of agg seems to slow things down a bit
import matplotlib.pyplot as plt
import multiprocessing
import time, os

def make_plot(d):
    start = time.clock()
    x,y=d
    #using aspect in this way causes a warning for me
    #aspect = np.random.random(1)+y/2.0-x 
    xrand = np.random.random(2)*x
    xlim = [min(xrand), max(xrand)]
    yrand = np.random.random(2)*y
    ylim = [min(yrand), max(yrand)]
    filename = '{:d}_{:d}.png'.format(x,y)
    ax = plt.gca()
    #ax.set_aspect(abs(aspect[0]))
    ax.set_xlim(xlim)
    ax.set_ylim(ylim)
    plt.savefig('figs/'+filename)
    stop = time.clock()
    return np.array([x,y, start, stop])

if not os.path.isdir('figs'):
    os.makedirs('figs')
data = np.random.rand(25, 25)

fig = plt.figure()
ax = fig.add_axes([0., 0., 1., 1.])
ax.imshow(data, interpolation='nearest')


some_list = []
for i in range(1, 8):
    for j in range(3, 13):
        some_list.append((i,j))


if __name__ == "__main__":
    multiprocessing.freeze_support()
    tstart = time.clock()
    print tstart
    num_proc = 5
    p = multiprocessing.Pool(num_proc)

    nu = p.map(make_plot, some_list)

    tooktime = 'Plotting of {} frames took {:.2f} seconds'
    tooktime = tooktime.format(len(some_list), time.clock()-tstart)
    print tooktime
    nu = np.array(nu)

    plt.close("all")
    fig, ax = plt.subplots(figsize=(8,5))
    plt.suptitle(tooktime)
    ax.barh(np.arange(len(some_list)), nu[:,3]-nu[:,2], 
            height=np.ones(len(some_list)), left=nu[:,2],  align="center")
    ax.set_xlabel("time [s]")
    ax.set_ylabel("image number")
    ax.set_ylim([-1,70])
    plt.tight_layout()
    plt.savefig(__file__+".png")
    plt.show()

введите здесь описание изображения

person ImportanceOfBeingErnest    schedule 09.12.2016