保存交互式Matplotlib图形


119

有没有一种方法可以保存Matplotlib图形,以便可以重新打开它并恢复典型的交互作用?(就像MATLAB中的.fig格式一样?)

我发现自己多次运行相同的脚本来生成这些交互式图形。或者我要向同事发送多个静态PNG文件,以显示绘图的不同方面。我宁愿发送图形对象,并让它们自己与之交互。

Answers:


30

这将是一个很棒的功能,但是AFAIK并没有在Matplotlib中实现,并且由于存储数据的方式可能很难实现自己。

我建议(a)将数据处理与生成图形分开(以唯一的名称保存数据),然后编写图形生成脚本(加载已保存数据的指定文件)并根据需要进行编辑或(b )另存为PDF / SVG / PostScript格式,并在某些精美的图形编辑器(如Adobe Illustrator(或Inkscape))中进行编辑。

编辑后,2012年秋季:正如其他人在下面指出的(尽管在此提及,因为这是公认的答案),自1.2版以来,Matplotlib允许您腌制人物。如发行说明所述,这是一项实验性功能,不支持在一个matplotlib版本中保存图形并在另一个matplotlib版本中打开图形。从不受信任的来源恢复泡菜通常也是不安全的。

对于共享/以后的编辑图(需要首先进行大量数据处理,并且可能需要在几个月后进行调整,例如在科学出版物的同行评审中进行调整),我仍然建议(1)的工作流程具有数据处理脚本,该脚本在生成图之前将处理后的数据(放入绘图中)保存到文件中,并且(2)具有单独的绘图生成脚本(您可以根据需要进行调整)以重新创建绘图。通过这种方式,您可以为每个绘图快速运行脚本并重新生成脚本(并使用新数据快速复制绘图设置)。话虽如此,腌制一个人物可能会方便短期/互动/探索性数据分析。


2
令人惊讶的是,这并没有实现。但是,好的,我将处理后的数据保存在一个中间文件中,并将其和脚本发送给同事。谢谢。
马特2010年

2
我怀疑实现很难,这就是为什么它在MATLAB上表现不佳的原因。当我使用它时,图形曾经使MATLAB崩溃,甚至略有不同的版本也无法互相读取.fig文件。
阿德里安·拉特纳帕拉

6
pickle现在可以在MPL图形上使用,因此可以完成此工作,并且看起来效果不错-几乎就像Matlab“ .fig”图形文件一样。有关如何执行此操作的示例,请参见下面的我的答案(现在?)。
Demis

@Demis:正如ptomato在下面的回答中指出的那样,它当时已经存在。
strpeter

如此评论所指出,@strpeter-Matlab的数字在2010年不会让人酸。实验性功能是在2012年秋季发布的matplotlib 1.2中添加的。如此处所述,您不应该期望它在matplotlib版本之间起作用,也不应该打开来自不可信来源的泡菜。
jimbob博士18年

63

我刚刚发现了如何做到这一点。@pelson提到的“实验泡菜支持”效果很好。

试试这个:

# Plot something
import matplotlib.pyplot as plt
fig,ax = plt.subplots()
ax.plot([1,2,3],[10,-10,30])

交互式调整后,将图形对象另存为二进制文件:

import pickle
pickle.dump(fig, open('FigureObject.fig.pickle', 'wb')) # This is for Python 3 - py2 may need `file` instead of `open`

稍后,打开图形并保存调整,并显示GUI交互性:

import pickle
figx = pickle.load(open('FigureObject.fig.pickle', 'rb'))

figx.show() # Show the figure, edit it, etc.!

您甚至可以从图中提取数据:

data = figx.axes[0].lines[0].get_data()

(它适用于线条,pcolor和imshow- pcolormesh可使用一些技巧来重建展平的数据。)

我从使用Pickle保存Matplotlib图形获得了出色的技巧。


我认为这对版本更改等并不稳健,并且py2.x和py3.x之间没有交叉兼容。我还认为pickle文档说明我们需要设置与酸洗(保存)对象时类似的环境,但是我发现import matplotlib.pyplot as plt取消酸洗(加载)时不需要用-将导入语句保存在酸洗的文件中。
Demis

5
您应该考虑关闭打开的文件:例如with open('FigureObject.fig.pickle', 'rb') as file: figx = pickle.load(file)
strpeter

1
我只是得到:“AttributeError的:‘图’对象有没有属性‘_cachedRenderer’”
user2673238

如果您不希望脚本继续运行并且可能在之后立即终止figx.show(),则应该调用plt.show(),它会阻塞。
maechler '19

38

从Matplotlib 1.2开始,我们现在具有实验性的pickle支持。试一试,看看它是否适合您的情况。如果您有任何问题,请通过Matplotlib邮件列表或通过在github.com/matplotlib/matplotlib上打开问题来告知我们


2
可以将此有用功能添加到图形的“另存为”本身的任何原因。可能添加.pkl?
破事2014年

好主意@dashesy。我支持您是否愿意实施它?
pelson

1
这仅适用于一部分后端吗?当我尝试在OSX中腌制一个简单的图形时,我得到了PicklingError: Can't pickle <type '_macosx.GraphicsContext'>: it's not found as _macosx.GraphicsContext
farenorth,2015年

PicklingError仅当您plt.show()在腌制之前致电时才会发生上述情况。所以就放在plt.show()之后pickle.dump()
salomonvh 2015年

在MacOS 10.11上的py3.5上,顺序fig.show()似乎无关紧要-也许该错误已修复。我可以在之前/之后腌制show()而没有问题。
Demis,

7

为什么不发送Python脚本呢?MATLAB的.fig文件要求收件人具有MATLAB才能显示它们,因此,这等效于发送需要Matplotlib显示的Python脚本。

或者(免责声明:我还没有尝试过),您可以尝试腌制该图:

import pickle
output = open('interactive figure.pickle', 'wb')
pickle.dump(gcf(), output)
output.close()

3
不幸的是,matplotlib的数据不容易被腌制,因此这种方法行不通。在幕后,有太多不支持酸洗的C扩展。我完全同意只发送脚本+数据,但是...我想我从来没有真正看到过matlab保存的.fig的意义,所以我从未使用过它们。从长远来看,从我的经验来看,发送独立代码和数据一直是最简单的。不过,如果matplotlib的图形对象可腌制,那将是很好的。
乔·肯顿

1
甚至我们的预处理数据也很大,并且绘图过程也很复杂。看起来是唯一的手段。谢谢。
马特2010年

1
现在看来数字很可腌-效果很好!下面的例子。
Demis,

腌制的好处是您不必以编程方式调整所有图形/子图的间距/位置。您可以使用MPL图的GUI使图形看起来更美观等,然后保存MyPlot.fig.pickle文件-保留以后根据需要调整图表示的功能。这也是Matlab .fig文件的优点。当您需要更改无花果的尺寸/长宽比(用于插入演示文稿/论文)时,该功能特别有用。
Demis,

1

好问题。这是来自的文档文本pylab.save

pylab不再提供保存功能,尽管旧的pylab函数仍然可以作为matplotlib.mlab.save使用(您仍然可以在pylab中将其称为“ mlab.save”)。但是,对于纯文本文件,我们建议使用numpy.savetxt。为了保存numpy数组,我们建议使用numpy.save及其类似的numpy.load,它们可以在pylab中以np.save和np.load的形式提供。


这会保存来自pylab对象的数据,但不允许您重新生成图形。
jimbob博士2010年

正确。我应该澄清,答案不是建议使用pylab.save。实际上,从doc文本来看,似乎应该使用它。
史蒂夫·乔阿

有没有外部方法可以发送3D图形?甚至可以执行一个简单的
GUI。

0

我想出了一种相对简单的方法(但还有些不常规)来保存我的matplotlib数字。它是这样的:

import libscript

import matplotlib.pyplot as plt
import numpy as np

t = np.arange(0.0, 2.0, 0.01)
s = 1 + np.sin(2*np.pi*t)

#<plot>
plt.plot(t, s)
plt.xlabel('time (s)')
plt.ylabel('voltage (mV)')
plt.title('About as simple as it gets, folks')
plt.grid(True)
plt.show()
#</plot>

save_plot(fileName='plot_01.py',obj=sys.argv[0],sel='plot',ctx=libscript.get_ctx(ctx_global=globals(),ctx_local=locals()))

具有这样save_plot定义的功能(了解逻辑的简单版本):

def save_plot(fileName='',obj=None,sel='',ctx={}):
    """
    Save of matplolib plot to a stand alone python script containing all the data and configuration instructions to regenerate the interactive matplotlib figure.

    Parameters
    ----------
    fileName : [string] Path of the python script file to be created.
    obj : [object] Function or python object containing the lines of code to create and configure the plot to be saved.
    sel : [string] Name of the tag enclosing the lines of code to create and configure the plot to be saved.
    ctx : [dict] Dictionary containing the execution context. Values for variables not defined in the lines of code for the plot will be fetched from the context.

    Returns
    -------
    Return ``'done'`` once the plot has been saved to a python script file. This file contains all the input data and configuration to re-create the original interactive matplotlib figure.
    """
    import os
    import libscript

    N_indent=4

    src=libscript.get_src(obj=obj,sel=sel)
    src=libscript.prepend_ctx(src=src,ctx=ctx,debug=False)
    src='\n'.join([' '*N_indent+line for line in src.split('\n')])

    if(os.path.isfile(fileName)): os.remove(fileName)
    with open(fileName,'w') as f:
        f.write('import sys\n')
        f.write('sys.dont_write_bytecode=True\n')
        f.write('def main():\n')
        f.write(src+'\n')

        f.write('if(__name__=="__main__"):\n')
        f.write(' '*N_indent+'main()\n')

return 'done'

或定义如下功能save_plot(使用zip压缩以生成更浅的图形文件的更好版本):

def save_plot(fileName='',obj=None,sel='',ctx={}):

    import os
    import json
    import zlib
    import base64
    import libscript

    N_indent=4
    level=9#0 to 9, default: 6
    src=libscript.get_src(obj=obj,sel=sel)
    obj=libscript.load_obj(src=src,ctx=ctx,debug=False)
    bin=base64.b64encode(zlib.compress(json.dumps(obj),level))

    if(os.path.isfile(fileName)): os.remove(fileName)
    with open(fileName,'w') as f:
        f.write('import sys\n')
        f.write('sys.dont_write_bytecode=True\n')
        f.write('def main():\n')
        f.write(' '*N_indent+'import base64\n')
        f.write(' '*N_indent+'import zlib\n')
        f.write(' '*N_indent+'import json\n')
        f.write(' '*N_indent+'import libscript\n')
        f.write(' '*N_indent+'bin="'+str(bin)+'"\n')
        f.write(' '*N_indent+'obj=json.loads(zlib.decompress(base64.b64decode(bin)))\n')
        f.write(' '*N_indent+'libscript.exec_obj(obj=obj,tempfile=False)\n')

        f.write('if(__name__=="__main__"):\n')
        f.write(' '*N_indent+'main()\n')

return 'done'

这使用了libscript我自己的模块,该模块主要依赖于inspectast。如果有兴趣,我可以尝试在Github上分享它(首先需要进行一些清理,然后我才能开始使用Github)。

save_plot函数和libscript模块的思想是获取创建图形的python指令(使用module inspect),对其进行分析(使用module ast)以提取依赖于其的所有变量,函数和模块,从执行上下文中提取这些变量并对其进行序列化如python指令(变量的代码将类似于t=[0.0,2.0,0.01]...,模块的代码将类似于import matplotlib.pyplot as plt...)附加在该图指令之前。生成的python指令将另存为python脚本,其执行将重新构建原始的matplotlib图。

可以想象,这对于大多数(如果不是全部)matplotlib图形都适用。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.