Salvataggio di figure Matplotlib interattive


119

Esiste un modo per salvare una figura Matplotlib in modo che possa essere riaperta e ripristinata l'interazione tipica? (Come il formato .fig in MATLAB?)

Mi ritrovo a eseguire gli stessi script molte volte per generare queste figure interattive. Oppure invio ai miei colleghi più file PNG statici per mostrare diversi aspetti di una trama. Preferisco inviare l'oggetto figura e farli interagire con esso da soli.

Risposte:


30

Questa sarebbe un'ottima funzionalità, ma AFAIK non è implementata in Matplotlib e probabilmente sarebbe difficile da implementare da soli a causa del modo in cui le cifre sono archiviate.

Suggerirei (a) separare l'elaborazione dei dati dalla generazione della figura (che salva i dati con un nome univoco) e scrivere uno script per la generazione della figura (caricando un file specificato dei dati salvati) e modificarla come meglio credi o (b ) salva in formato PDF / SVG / PostScript e modifica in qualche editor di figure fantasioso come Adobe Illustrator (o Inkscape ).

EDIT post autunno 2012 : come altri hanno sottolineato di seguito (anche se menzionando qui come questa è la risposta accettata), Matplotlib dalla versione 1.2 ti ha permesso di mettere in risalto le cifre. Come affermano le note di rilascio , è una funzionalità sperimentale e non supporta il salvataggio di una figura in una versione di matplotlib e l'apertura in un'altra. Inoltre, in genere non è sicuro ripristinare un sottaceto da una fonte non attendibile.

Per la condivisione / la successiva modifica dei grafici (che richiedono prima un'elaborazione significativa dei dati e potrebbero dover essere modificati mesi dopo, ad esempio durante la revisione tra pari per una pubblicazione scientifica), consiglio comunque il flusso di lavoro di (1) avere uno script di elaborazione dei dati che prima di generare un grafico salva i dati elaborati (che vanno nel grafico) in un file e (2) dispone di uno script di generazione del grafico separato (che si modifica secondo necessità) per ricreare il grafico. In questo modo per ogni trama è possibile eseguire rapidamente uno script e rigenerarlo (e copiare rapidamente le impostazioni della trama con nuovi dati). Detto questo, il decapaggio di una cifra potrebbe essere conveniente per l'analisi dei dati a breve termine / interattiva / esplorativa.


2
Un po 'sorpreso che non sia implementato .. Ma ok, salverò i dati elaborati in un file intermedio e invierò quello e uno script per la stampa ai colleghi. Grazie.
Matt

2
Sospetto che l'implementazione sia difficile, motivo per cui funziona così male un MATLAB. Ai tempi in cui l'ho usato, i dati utilizzati per arrestare MATLAB e anche versioni leggermente diverse non erano in grado di leggere i file .fig l'uno dell'altro.
Adrian Ratnapala

6
pickleora funziona su figure MPL, quindi questo può essere fatto e sembra funzionare abbastanza bene - quasi come un file di figure Matlab ".fig". Vedi la mia risposta di seguito (per ora?) Per un esempio di come farlo.
Demis

@Demis: come ha sottolineato ptomato nella sua risposta qui sotto, esisteva già a quel tempo.
strpeter

@strpeter - Le cifre di Matlab non erano selezionabili nel 2010 come sottolineato in questo commento . La funzionalità sperimentale è stata aggiunta con matplotlib 1.2 rilasciato nell'autunno 2012 . Come notato qui, non dovresti aspettarti che funzioni tra le versioni di matplotlib e non dovresti aprire i sottaceti provenienti da una fonte non attendibile.
dr jimbob

63

Ho appena scoperto come farlo. Il "supporto per pickle sperimentale" menzionato da @pelson funziona abbastanza bene.

Prova questo:

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

Dopo la modifica interattiva, salva l'oggetto figura come file binario:

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

Successivamente, apri la figura e le modifiche dovrebbero essere salvate e dovrebbe essere presente l'interattività della GUI:

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

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

Puoi persino estrarre i dati dai grafici:

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

(Funziona per righe, pcolor e imshow - pcolormesh funziona con alcuni trucchi per ricostruire i dati appiattiti .)

Ho ricevuto un ottimo suggerimento da Saving Matplotlib Figures Using Pickle .


Credo che questo non sia affidabile per i cambiamenti di versione ecc. E non sia compatibile con py2.x e py3.x. Inoltre, pensavo che la pickledocumentazione affermasse che abbiamo bisogno di configurare l'ambiente in modo simile a quando l'oggetto è stato decapato (salvato), ma ho scoperto che non è necessario import matplotlib.pyplot as pltquando unpickling (caricamento) - salva le istruzioni di importazione nel file pickled .
Demis

5
Dovresti considerare di chiudere i file aperti: ad esempiowith open('FigureObject.fig.pickle', 'rb') as file: figx = pickle.load(file)
strpeter

1
Ottengo solo: 'AttributeError: l'oggetto' Figure 'non ha attributo' _cachedRenderer ''
user2673238

Se non vuoi che lo script continui e probabilmente termini subito dopo figx.show(), dovresti plt.show()invece chiamare , che sta bloccando.
maechler

38

A partire da Matplotlib 1.2, ora abbiamo il supporto per pickle sperimentale . Provalo e vedi se funziona bene per il tuo caso. Se hai problemi, faccelo sapere sulla mailing list di Matplotlib o aprendo un problema su github.com/matplotlib/matplotlib .


2
Qualunque sia la ragione per cui questa utile funzione potrebbe essere aggiunta al "Salva con nome" della figura stessa. Aggiungendo .pkl forse?
dashesy

Buona idea @dashesy. Lo sosterrei se volessi provare l'implementazione?
pelson

1
Funziona solo su un sottoinsieme di backend? Quando provo a decapare una semplice figura in OSX, ottengo PicklingError: Can't pickle <type '_macosx.GraphicsContext'>: it's not found as _macosx.GraphicsContext.
dal

Quanto sopra PicklingErrorsi verifica solo se chiami plt.show()prima di fare il pickle. Quindi mettilo plt.show()dopo pickle.dump().
salomonvh

Sul mio py3.5 su MacOS 10.11, l'ordine di fig.show()non sembra avere importanza, forse quel bug è stato risolto. Posso sottaceto prima / dopo show()senza problemi.
Demis

7

Perché non inviare semplicemente lo script Python? I file .fig di MATLAB richiedono che il destinatario disponga di MATLAB per visualizzarli, quindi è quasi equivalente all'invio di uno script Python che richiede Matplotlib per essere visualizzato.

In alternativa (disclaimer: non l'ho ancora provato), potresti provare a decapare la figura:

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

3
Sfortunatamente, le cifre di matplotlib non sono selezionabili, quindi questo approccio non funzionerà. Dietro le quinte, ci sono troppe estensioni C che non supportano il decapaggio. Sono completamente d'accordo sull'invio dello script + dei dati, però ... immagino di non aver mai veramente visto il punto dei file .fig salvati da matlab, quindi non li ho mai usati. L'invio di codice e dati autonomi a qualcuno è stato il più semplice nel lungo periodo, comunque nella mia esperienza. Tuttavia, sarebbe bello se la figura di matplotlib fosse selezionabile.
Joe Kington

1
Anche i nostri dati preelaborati sono piuttosto grandi e la procedura di stampa è complessa. Tuttavia, sembra l'unica risorsa. Grazie.
Matt

1
Apparentemente le cifre sono ora selezionabili - funziona abbastanza bene! Esempio sotto.
Demis

Il vantaggio di pickle è che non è necessario regolare a livello di programmazione tutte le spaziature / posizioni di figure / sottotrame. È possibile utilizzare la GUI del grafico MPL per rendere bella la figura, ecc., Quindi salvare il MyPlot.fig.picklefile, mantenendo la possibilità successiva di regolare la presentazione del grafico secondo necessità. Questo è anche il bello dei .figfile di Matlab . Particolarmente utile quando è necessario modificare le dimensioni / proporzioni di un fico (per l'inserimento in presentazioni / documenti).
Demis

1

Buona domanda. Ecco il testo del documento da pylab.save:

pylab non fornisce più una funzione di salvataggio, anche se la vecchia funzione pylab è ancora disponibile come matplotlib.mlab.save (puoi ancora fare riferimento ad essa in pylab come "mlab.save"). Tuttavia, per i file di testo normale, consigliamo numpy.savetxt. Per salvare gli array numpy, consigliamo numpy.save e il suo analogico numpy.load, che sono disponibili in pylab come np.save e np.load.


Questo salva i dati dall'oggetto a pylab, ma non ti permette di rigenerare la figura.
dr jimbob

Corretta. Devo chiarire che la risposta non era una raccomandazione da utilizzare pylab.save. Infatti, dal testo del documento, sembra che non lo si dovrebbe usare.
Steve Tjoa

Esiste un metodo esterno per inviare una figura 3D? Possibile anche una semplice GUI da eseguire ..
CromeX

0

Ho trovato un modo relativamente semplice (ma leggermente non convenzionale) per salvare le mie figure matplotlib. Funziona così:

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

con funzione così save_plotdefinita (versione semplice per comprenderne la logica):

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'

o definendo una funzione save_plotcome questa (versione migliore che utilizza la compressione zip per produrre file di figure più leggere):

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'

Questo fa uso di un libscriptmio modulo , che si basa principalmente su moduli inspecte ast. Posso provare a condividerlo su Github se viene espresso interesse (richiederebbe prima un po 'di pulizia e io per iniziare con Github).

L'idea alla base di questa save_plotfunzione e libscriptmodulo è di recuperare le istruzioni python che creano la figura (utilizzando il modulo inspect), analizzarle (utilizzando il modulo ast) per estrarre tutte le variabili, le funzioni e i moduli su cui si basa l'importazione, estrarle dal contesto di esecuzione e serializzarle come istruzioni Python (il codice per le variabili sarà come t=[0.0,2.0,0.01]... e il codice per i moduli sarà come import matplotlib.pyplot as plt...) anteposto alle istruzioni della figura. Le istruzioni python risultanti vengono salvate come script python la cui esecuzione ricostruirà la figura matplotlib originale.

Come puoi immaginare, funziona bene per la maggior parte (se non tutte) le figure matplotlib.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.