关于python:matplotlib savefig性能,在循环中保存多个png

matplotlib savefig performance, saving multiple pngs within loop

我希望找到一种方法来优化以下情况。我有一个大的轮廓图,用imshow的matplotlib创建。然后,我想使用这个轮廓图创建大量PNG图像,其中每个图像都是轮廓图像的一个小部分,通过更改X和Y限制和纵横比。

因此,在循环中没有绘图数据发生变化,只有轴限制和纵横比在每个PNG图像之间发生变化。

下面的mwe在一个"figs"文件夹中创建了70个PNG图像,演示了简化的思想。大约80%的运行时间被fig.savefig('figs/'+filename)占用。

我没有提出任何改进,而是研究了以下内容:

  • 作为一种侧重于速度的matplotlib的替代方案,我一直在努力寻找具有类似要求的轮廓/表面图的任何示例/文档。
  • 多处理——我在这里看到的类似问题似乎要求在循环中调用fig = plt.figure()ax.imshow,因为fig和ax不能被腌制。在我的例子中,这将比通过实现多处理实现的任何速度增益都要昂贵。

我很感激你的任何见解或建议。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
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))

由于这种情况下的限制是对plt.savefig()的调用,因此无法对其进行大量优化。在内部,图形是从零开始渲染的,这需要一段时间。可能减少要绘制的顶点数量可能会减少一点时间。

在我的机器上运行代码的时间是2.5秒。这似乎还不错。通过使用多处理,可以得到一些改进。

关于多处理的一个注意事项:使用multiprocessing中的pyplot状态机应该完全可以工作,这可能看起来很奇怪。但确实如此。在这里,因为每个图像都是基于相同的图形和轴对象,所以我们甚至不需要创建新的图形和轴。

我为您的案例修改了一个刚才在这里给出的答案,使用多处理和4核上的5个处理,总时间大约减半。我附加了一个显示多处理效果的条形图。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
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()

enter image description here