Salvando figuras interativas do Matplotlib

119

Existe uma maneira de salvar uma figura do Matplotlib para que possa ser reaberta e ter a interação típica restaurada? (Como o formato .fig no MATLAB?)

Encontro-me executando os mesmos scripts muitas vezes para gerar essas figuras interativas. Ou estou enviando aos meus colegas vários arquivos PNG estáticos para mostrar diferentes aspectos de um gráfico. Prefiro enviar o objeto da figura e fazê-lo interagir com ele.

Matt
fonte

Respostas:

30

Esse seria um ótimo recurso, mas o AFAIK não está implementado no Matplotlib e provavelmente seria difícil de se implementar devido à maneira como os números são armazenados.

Sugiro que (a) separe o processamento dos dados da geração da figura (que salva os dados com um nome exclusivo) e escreva um script de geração da figura (carregando um arquivo especificado dos dados salvos) e edite como achar melhor ou (b ) salve como formato PDF / SVG / PostScript e edite em algum editor de figuras sofisticadas, como o Adobe Illustrator (ou Inkscape ).

EDIT após o outono de 2012 : Como outros apontaram abaixo (apesar de mencionar aqui como esta é a resposta aceita), o Matplotlib desde a versão 1.2 permitiu que você capturasse números. Como as notas de versão indicam , é um recurso experimental e não suporta salvar uma figura em uma versão do matplotlib e abrir em outra. Também geralmente não é seguro restaurar um picles de uma fonte não confiável.

Para compartilhar / editar parcelas posteriores (que requerem processamento significativo de dados primeiro e que podem precisar ser ajustados meses depois, digamos durante a revisão por pares para uma publicação científica), ainda recomendo que o fluxo de trabalho de (1) tenha um script de processamento de dados que antes de gerar uma plotagem salva os dados processados ​​(que entram em sua plotagem) em um arquivo e (2) tem um script de geração de plotagem separado (que você ajusta conforme necessário) para recriar a plotagem. Dessa forma, para cada plotagem, você pode executar rapidamente um script e gerá-lo novamente (e copiar rapidamente as configurações da plotagem com novos dados). Dito isto, escolher um número pode ser conveniente para análises de dados de curto prazo / interativas / exploratórias.

dr jimbob
fonte
2
Um tanto surpreso que isso não tenha sido implementado. Mas, ok, vou salvar os dados processados ​​em um arquivo intermediário e enviar isso e um script para plotagem aos colegas. Obrigado.
Matt
2
Eu suspeito que a implementação é difícil, e é por isso que funciona tão mal como um MATLAB. Quando o usei, os números travavam o MATLAB e até versões ligeiramente diferentes não eram capazes de ler os arquivos .fig.
Adrian Ratnapala
6
pickleagora funciona com figuras MPL, portanto, isso pode ser feito e parece funcionar razoavelmente - quase como um arquivo de figura ".fig" do Matlab. Veja minha resposta abaixo (por enquanto?) Para um exemplo de como fazê-lo.
Demis
@ Demis: como ptomato apontou em sua resposta abaixo, ele já existia naquele momento.
Strpeter 23/05
@ strpeter - Os números do Matlab não foram escolhidos em 2010, como apontado neste comentário . O recurso experimental foi adicionado com o matplotlib 1.2 lançado no outono de 2012 . Como observado aqui, você não deve esperar que funcione entre as versões do matplotlib e não deve abrir pickles provenientes de uma fonte não confiável.
dr jimbob
63

Acabei de descobrir como fazer isso. O "suporte experimental de picles" mencionado por @pelson funciona muito bem.

Tente o seguinte:

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

Após os ajustes interativos, salve o objeto de figura como um arquivo binário:

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

Posteriormente, abra a figura e os ajustes devem ser salvos e a interatividade da GUI deve estar presente:

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

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

Você pode até extrair os dados dos gráficos:

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

(Ele funciona para linhas, pcolor e imshow - o pcolormesh trabalha com alguns truques para reconstruir os dados achatados .)

Recebi a excelente dica de Saving Matplotlib Figures Using Pickle .

Demis
fonte
Acredito que isso não seja robusto para alterações de versão, etc., e não seja compatível entre py2.x e py3.x. Também achei que a pickledocumentação afirmava que precisamos configurar o ambiente de maneira semelhante a quando o objeto foi capturado (salvo), mas descobri que você não precisa fazer o import matplotlib.pyplot as pltcancelamento (carregamento) - ele salva as instruções de importação no arquivo capturado .
Demis
5
Você deve considerar o fechamento de arquivos abertos: por exemplowith open('FigureObject.fig.pickle', 'rb') as file: figx = pickle.load(file)
strpeter 17/05/19
1
Eu apenas recebo: 'AttributeError:' Figure 'objeto não tem nenhum atributo' _cachedRenderer ''
#
Se você não deseja que o script continue e provavelmente termine imediatamente após figx.show(), você deve ligar plt.show(), o que está bloqueando.
maechler
38

A partir do Matplotlib 1.2, agora temos suporte experimental a picles . Experimente e veja se funciona bem para o seu caso. Se você tiver algum problema, informe-nos na lista de discussão do Matplotlib ou abrindo um problema em github.com/matplotlib/matplotlib .

Pelson
fonte
2
Qualquer motivo para esse recurso útil poder ser adicionado ao próprio "Salvar como" da figura. Adicionando .pkl talvez?
dashesy
Boa ideia @dashesy. Eu apoiaria que, se você quisesse implementá-lo?
Pelel 30/05
1
Isso funciona apenas em um subconjunto de back-ends? Quando tento entender uma figura simples no OSX, entendo PicklingError: Can't pickle <type '_macosx.GraphicsContext'>: it's not found as _macosx.GraphicsContext.
farenorth
O exposto acima PicklingErrorocorre apenas se você ligar plt.show()antes de fazer o pickle. Então, basta colocar plt.show()depois pickle.dump().
Salomonvh
No meu py3.5 no MacOS 10.11, a ordem de fig.show()não parece importar - talvez esse bug tenha sido corrigido. Posso conservar antes / depois show()sem problemas.
Demis
7

Por que não apenas enviar o script Python? Os arquivos .fig do MATLAB exigem que o destinatário tenha o MATLAB para exibi-los, então isso é equivalente ao envio de um script Python que exige que o Matplotlib seja exibido.

Como alternativa (isenção de responsabilidade: ainda não tentei isso), você pode tentar escolher a figura:

import pickle
output = open('interactive figure.pickle', 'wb')
pickle.dump(gcf(), output)
output.close()
ptomato
fonte
3
Infelizmente, os números do matplotlib não são selecionáveis, portanto essa abordagem não funciona. Nos bastidores, existem muitas extensões C que não suportam decapagem. Eu concordo completamente em apenas enviar o script + dados, no entanto ... Acho que nunca vi o ponto dos .fig salvos do matlab, por isso nunca os usei. Enviar códigos e dados independentes a alguém tem sido o mais fácil a longo prazo, na minha experiência. Ainda assim, seria bom se os objetos da figura do matplotlib fossem selecionáveis.
Joe Kington
1
Até nossos dados pré-processados ​​são um tanto grandes e o procedimento de plotagem é complexo. Parece o único recurso, no entanto. obrigado.
Matt
1
Aparentemente, agora os números são selecionáveis ​​- funciona muito bem! Exemplo abaixo.
Demis
O benefício da decapagem é que você não precisa ajustar programaticamente todos os espaçamentos / posições da figura / subparcela. Você pode usar a GUI da plotagem MPL para tornar a figura mais bonita etc., e salvar o MyPlot.fig.picklearquivo - mantendo a capacidade posterior de ajustar a apresentação da plotagem conforme necessário. Isso também é ótimo nos .figarquivos do Matlab . Especialmente útil quando você precisa alterar o tamanho / proporção de uma figura (para inserir em apresentações / trabalhos).
Demis
1

Boa pergunta. Aqui está o texto do documento de pylab.save:

O pylab não fornece mais uma função de salvamento, embora a função antiga do pylab ainda esteja disponível como matplotlib.mlab.save (você ainda pode se referir a ela no pylab como "mlab.save"). No entanto, para arquivos de texto sem formatação, recomendamos numpy.savetxt. Para salvar matrizes numpy, recomendamos numpy.save e seu numpy.load analógico, que estão disponíveis no pylab como np.save e np.load.

Steve Tjoa
fonte
Isso salva os dados do objeto a pylab, mas não permite que você regenere a figura.
dr jimbob
Corrigir. Devo esclarecer que a resposta não foi uma recomendação a ser usada pylab.save. De fato, pelo texto do documento, parece que não se deve usá-lo.
Steve Tjoa
Existe algum método externo para enviar uma figura 3D? Possível até uma simples interface gráfica para
executar
0

Eu descobri uma maneira relativamente simples (ainda que pouco convencional) de salvar meus números do matplotlib. Funciona assim:

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()))

com função save_plotdefinida assim (versão simples para entender a lógica):

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'

ou definindo uma função save_plotcomo esta (versão melhorada usando a compactação zip para produzir arquivos de figuras mais leves):

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'

Isso faz uso de um módulo libscriptpróprio, que depende principalmente de módulos inspecte ast. Posso tentar compartilhá-lo no Github se o interesse for manifestado (primeiro seria necessário limpar e eu começar o Github).

A idéia por trás dessa save_plotfunção e libscriptmódulo é buscar as instruções python que criam a figura (usando o módulo inspect), analisá-las (usando o móduloast ) para extrair todas as variáveis, funções e módulos nos quais a importação depende, extraí-las do contexto de execução e serializá-las como instruções python (o código para variáveis ​​será como t=[0.0,2.0,0.01]... e o código para módulos será como import matplotlib.pyplot as plt...) anexado às instruções da figura. As instruções python resultantes são salvas como um script python cuja execução reconstruirá a figura original do matplotlib.

Como você pode imaginar, isso funciona bem para a maioria (senão todas) das figuras do matplotlib.

Astrum42
fonte