Matplotlib, альтернативы savefig() для повышения производительности при сохранении объекта CString?

Я пытаюсь ускорить процесс сохранения моих диаграмм на изображения. Прямо сейчас я создаю объект cString, где я сохраняю диаграмму с помощью savefig; но я действительно очень благодарен за помощь в улучшении этого способа сохранения изображения. Я должен делать эту операцию десятки раз, и команда savefig очень медленная; должен быть лучший способ сделать это. Я прочитал что-то о сохранении его как несжатого исходного изображения, но я не знаю, как это сделать. Мне не все равно, если я могу переключиться на другой более быстрый бэкэнд.

т

RAM = cStringIO.StringIO()

CHART = plt.figure(.... 
**code for creating my chart**

CHART.savefig(RAM, format='png')

Я использую matplotlib с базовым компонентом FigureCanvasAgg.

Спасибо!

Ответы

Ответ 1

Если вам нужен необработанный буфер, попробуйте fig.canvas.print_rgb, fig.canvas.print_raw и т.д. (разница между ними состоит в том, что raw - rgba, тогда как rgb - rgb. Там также print_png, print_ps и т.д.)

Это будет использовать fig.dpi вместо значения по умолчанию для значения savefig (100 dpi). Тем не менее, даже при сравнении fig.canvas.print_raw(f) и fig.savefig(f, format='raw', dpi=fig.dpi) версия print_canvas меньше незначительно быстрее, так как она не беспокоит сброс цвета патча оси и т.д., Что savefig делает по умолчанию.

Несмотря на это, большая часть времени, затрачиваемого на сохранение фигуры в необработанном формате, просто рисует фигуру, в которой нет возможности обойти.

Во всяком случае, как пример бессмысленно-весело, рассмотрим следующее:

import matplotlib.pyplot as plt
import numpy as np
import cStringIO

plt.ion()
fig = plt.figure()
ax = fig.add_subplot(111)
num = 50
max_dim = 10
x = max_dim / 2 * np.ones(num)
s, c = 100 * np.random.random(num), np.random.random(num)
scat = ax.scatter(x,x,s,c)
ax.axis([0,max_dim,0,max_dim])
ax.set_autoscale_on(False)

for i in xrange(1000):
    xy = np.random.random(2*num).reshape(num,2) - 0.5
    offsets = scat.get_offsets() + 0.3 * xy
    offsets.clip(0, max_dim, offsets)
    scat.set_offsets(offsets)
    scat._sizes += 30 * (np.random.random(num) - 0.5)
    scat._sizes.clip(1, 300, scat._sizes)
    fig.canvas.draw()

Brownian walk animation

Если мы посмотрим на необработанное время рисования:

import matplotlib.pyplot as plt
import numpy as np
import cStringIO

fig = plt.figure()
ax = fig.add_subplot(111)
num = 50
max_dim = 10
x = max_dim / 2 * np.ones(num)
s, c = 100 * np.random.random(num), np.random.random(num)
scat = ax.scatter(x,x,s,c)
ax.axis([0,max_dim,0,max_dim])
ax.set_autoscale_on(False)

for i in xrange(1000):
    xy = np.random.random(2*num).reshape(num,2) - 0.5
    offsets = scat.get_offsets() + 0.3 * xy
    offsets.clip(0, max_dim, offsets)
    scat.set_offsets(offsets)
    scat._sizes += 30 * (np.random.random(num) - 0.5)
    scat._sizes.clip(1, 300, scat._sizes)
    fig.canvas.draw()

Это займет около 25 секунд на моей машине.

Если вместо этого выгрузить необработанный буфер RGBA в буфер cStringIO, это будет на самом деле чуть быстрее в ~ 22 секунды (это верно только потому, что я использую интерактивный бэкэнд! В противном случае это будет эквивалентно.):

import matplotlib.pyplot as plt
import numpy as np
import cStringIO

fig = plt.figure()
ax = fig.add_subplot(111)
num = 50
max_dim = 10
x = max_dim / 2 * np.ones(num)
s, c = 100 * np.random.random(num), np.random.random(num)
scat = ax.scatter(x,x,s,c)
ax.axis([0,max_dim,0,max_dim])
ax.set_autoscale_on(False)

for i in xrange(1000):
    xy = np.random.random(2*num).reshape(num,2) - 0.5
    offsets = scat.get_offsets() + 0.3 * xy
    offsets.clip(0, max_dim, offsets)
    scat.set_offsets(offsets)
    scat._sizes += 30 * (np.random.random(num) - 0.5)
    scat._sizes.clip(1, 300, scat._sizes)
    ram = cStringIO.StringIO()
    fig.canvas.print_raw(ram)
    ram.close()

Если сравнить это с использованием savefig, с сопоставимым значением dpi:

import matplotlib.pyplot as plt
import numpy as np
import cStringIO

fig = plt.figure()
ax = fig.add_subplot(111)
num = 50
max_dim = 10
x = max_dim / 2 * np.ones(num)
s, c = 100 * np.random.random(num), np.random.random(num)
scat = ax.scatter(x,x,s,c)
ax.axis([0,max_dim,0,max_dim])
ax.set_autoscale_on(False)

for i in xrange(1000):
    xy = np.random.random(2*num).reshape(num,2) - 0.5
    offsets = scat.get_offsets() + 0.3 * xy
    offsets.clip(0, max_dim, offsets)
    scat.set_offsets(offsets)
    scat._sizes += 30 * (np.random.random(num) - 0.5)
    scat._sizes.clip(1, 300, scat._sizes)
    ram = cStringIO.StringIO()
    fig.savefig(ram, format='raw', dpi=fig.dpi)
    ram.close()

Это займет ~ 23,5 секунды. В принципе, savefig просто устанавливает некоторые параметры по умолчанию и вызовы print_raw, в этом случае так мало различий.

Теперь, если мы сравним формат необработанного изображения со сжатым форматом изображения (png), мы увидим гораздо более существенную разницу:

import matplotlib.pyplot as plt
import numpy as np
import cStringIO

fig = plt.figure()
ax = fig.add_subplot(111)
num = 50
max_dim = 10
x = max_dim / 2 * np.ones(num)
s, c = 100 * np.random.random(num), np.random.random(num)
scat = ax.scatter(x,x,s,c)
ax.axis([0,max_dim,0,max_dim])
ax.set_autoscale_on(False)

for i in xrange(1000):
    xy = np.random.random(2*num).reshape(num,2) - 0.5
    offsets = scat.get_offsets() + 0.3 * xy
    offsets.clip(0, max_dim, offsets)
    scat.set_offsets(offsets)
    scat._sizes += 30 * (np.random.random(num) - 0.5)
    scat._sizes.clip(1, 300, scat._sizes)
    ram = cStringIO.StringIO()
    fig.canvas.print_png(ram)
    ram.close()

Это займет около 52 секунд! Очевидно, что в сжатии изображения много накладных расходов.

Во всяком случае, это, наверное, бесполезно сложный пример... Я думаю, я просто хотел избежать фактической работы...

Ответ 2

Мне нужно было быстро создать много сюжетов. Я обнаружил, что многопроцессорность улучшает скорость печати с количеством доступных ядер. Например, если 100 графиков занимали 10 секунд в одном процессе, потребовалось ~ 3 секунды, когда задача была разделена на 4 ядра.