Matplotlib,savefig() 的替代品以提高保存到 CString 对象时的性能?

Posted

技术标签:

【中文标题】Matplotlib,savefig() 的替代品以提高保存到 CString 对象时的性能?【英文标题】:Matplotlib, alternatives to savefig() to improve performance when saving into a CString object? 【发布时间】:2011-07-20 10:44:00 【问题描述】:

我正在尝试加快将图表保存为图像的过程。现在我正在创建一个 cString 对象,我使用 savefig 将图表保存到其中;但我真的非常感谢任何有助于改进这种保存图像的方法。这个操作我要做几十次,savefig命令非常非常慢;必须有更好的方法来做到这一点。我读了一些关于将其保存为未压缩的原始图像的内容,但我不知道该怎么做。如果我也可以切换到另一个更快的后端,我真的不在乎 agg。

即:

RAM = cStringIO.StringIO()

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

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

我一直在使用带有 FigureCanvasAgg 后端的 matplotlib。

谢谢!

【问题讨论】:

我对此了解不多。但是您可以查看以下帮助:format='raw'format='rgba'。看起来它们产生了相同的输出。 您是否尝试过分析代码以查看 savefig 大部分时间花在哪里?您是否尝试过降低分辨率(dpi 参数)或其他图像类型(jpeg、gif、tif,如果支持)? 【参考方案1】:

如果你只想要一个原始缓冲区,试试fig.canvas.print_rgbfig.canvas.print_raw等(两者的区别在于raw是rgba,而rgb是rgb。还有print_png、@987654328 @等)

这将使用fig.dpi 而不是savefig 的默认dpi 值(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()

如果我们查看原始绘制时间:

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 秒!显然,压缩图像有很多开销。

无论如何,这可能是一个不必要的复杂示例...我想我只是想避免实际工作...

【讨论】:

很好的例子乔,即使它可能是矫枉过正。我想知道您是否将每次迭代绘制的帧保存在磁盘上,然后将它们离线编译成动画 gif,或者是否有某种方式将绘制的帧“in-stream”编译成动画 gif?我不是说使用 $animation$ 模块,因为我想保存由交互式(鼠标事件驱动)绘图产生的动画。 好吧,做了一些搜索,我想你的建议可能是这里显示的:***.com/a/14986894/467522,对吧? 实际上,这个特殊的 gif 是通过保存每个迭代并离线编译它们制作的(使用 imagemagick 的convert)。 (我认为这个例子早于带有 animation 模块的 matplotlib 版本的发布。)无论如何,应该可以使用 ffmpeg 创建动画 gif,但如果我没记错的话,使用保存为 gif animation 模块无法正常工作。 (我可能记错了,无论如何,它现在可能已经修复了。我已经有一段时间没有尝试过了。) 意识到这是一个旧线程,但想知道是否有办法避免 cStringIO。任何纯 Matplotlib 解决方案?【参考方案2】:

我还需要快速生成大量图。我发现多处理通过可用的内核数量提高了绘图速度。例如,如果 100 个绘图在一个进程中需要 10 秒,那么将任务拆分到 4 个内核时需要大约 3 秒。

【讨论】:

以上是关于Matplotlib,savefig() 的替代品以提高保存到 CString 对象时的性能?的主要内容,如果未能解决你的问题,请参考以下文章

matplotlib savefig() 绘图不同于 show()

Matplotlib.savefig 忽略轴并在图像周围绘制黑色边框

matplotlib savefig() 大小控制

样式文件中的 matplotlib savefig.directory 不起作用

为啥 Matplotlib savefig 图像重叠?

matplotlib之直接保存图片