Speichern interaktiver Matplotlib-Figuren

119

Gibt es eine Möglichkeit, eine Matplotlib-Figur so zu speichern, dass sie wieder geöffnet und die typische Interaktion wiederhergestellt werden kann? (Wie das .fig-Format in MATLAB?)

Ich stelle fest, dass ich viele Male dieselben Skripte ausführe, um diese interaktiven Figuren zu generieren. Oder ich sende meinen Kollegen mehrere statische PNG-Dateien, um verschiedene Aspekte eines Diagramms anzuzeigen. Ich möchte lieber das Figurenobjekt senden und sie selbst damit interagieren lassen.

Matt
quelle

Antworten:

30

Dies wäre eine großartige Funktion, aber AFAIK ist nicht in Matplotlib implementiert und aufgrund der Art und Weise, wie Zahlen gespeichert werden, wahrscheinlich schwierig, sich selbst zu implementieren.

Ich würde vorschlagen, entweder (a) die Verarbeitung der Daten von der Generierung der Figur (die Daten mit einem eindeutigen Namen speichert) zu trennen und ein Skript zur Generierung der Figur (Laden einer bestimmten Datei der gespeicherten Daten) zu schreiben und nach Belieben zu bearbeiten oder (b ) als PDF / SVG / PostScript- Format speichern und in einem ausgefallenen Figureneditor wie Adobe Illustrator (oder Inkscape ) bearbeiten .

BEARBEITEN nach Herbst 2012 : Wie andere weiter unten betonten (obwohl dies hier als akzeptierte Antwort erwähnt wird), konnten Sie mit Matplotlib seit Version 1.2 Zahlen auswählen. Als Release Notes Zustand ist es eine experimentelle Funktion und bietet keine Unterstützung für eine Figur in einer matplotlib Version und Öffnung in einer anderen zu speichern. Es ist im Allgemeinen auch unsicher, eine Gurke aus einer nicht vertrauenswürdigen Quelle wiederherzustellen.

Für das Teilen / spätere Bearbeiten von Plots (die zuerst eine erhebliche Datenverarbeitung erfordern und möglicherweise Monate später angepasst werden müssen, z. B. während der Begutachtung für eine wissenschaftliche Veröffentlichung), empfehle ich weiterhin, dass der Workflow von (1) ein Datenverarbeitungsskript enthält, das vor dem Generieren eines Plots erstellt wird speichert die verarbeiteten Daten (die in Ihren Plot eingehen) in einer Datei und (2) verfügt über ein separates Plotgenerierungsskript (das Sie nach Bedarf anpassen), um den Plot neu zu erstellen. Auf diese Weise können Sie für jedes Diagramm schnell ein Skript ausführen und neu generieren (und Ihre Diagrammeinstellungen schnell mit neuen Daten kopieren). Das Beizen einer Figur könnte jedoch für die kurzfristige / interaktive / explorative Datenanalyse praktisch sein.

Dr. Jimbob
quelle
2
Etwas überrascht, dass dies nicht implementiert ist. Aber ok, ich werde die verarbeiteten Daten in einer Zwischendatei speichern und diese und ein Skript zum Plotten an Kollegen senden. Vielen Dank.
Matt
2
Ich vermute, dass die Implementierung schwierig ist, weshalb es bei MATLAB so schlecht funktioniert. Damals, als ich es benutzte, konnten Zahlen MATLAB zum Absturz bringen, und selbst leicht unterschiedliche Versionen konnten die .fig-Dateien der anderen nicht lesen.
Adrian Ratnapala
6
picklefunktioniert jetzt mit MPL-Figuren, so dass dies möglich ist und vernünftig zu funktionieren scheint - fast wie eine Matlab-Figurendatei ".fig". In meiner Antwort unten (vorerst?) Finden Sie ein Beispiel dafür.
Demis
@ Demis: Wie ptomato in seiner Antwort unten hervorhob, existierte es bereits zu diesem Zeitpunkt.
Strpeter
@strpeter - Matlab-Zahlen waren 2010 nicht auswählbar, wie in diesem Kommentar ausgeführt . Das experimentelle Feature wurde mit Matplotlib 1.2 hinzugefügt, das im Herbst 2012 veröffentlicht wurde . Wie dort erwähnt, sollten Sie nicht erwarten, dass es zwischen matplotlib-Versionen funktioniert, und Sie sollten keine Gurken öffnen, die von einer nicht vertrauenswürdigen Quelle stammen.
Dr. Jimbob
63

Ich habe gerade herausgefunden, wie das geht. Die von @pelson erwähnte "experimentelle Gurkenunterstützung" funktioniert recht gut.

Versuche dies:

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

Speichern Sie das Figurenobjekt nach Ihrer interaktiven Optimierung als Binärdatei:

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

Öffnen Sie später die Abbildung und die Optimierungen sollten gespeichert werden und die GUI-Interaktivität sollte vorhanden sein:

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

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

Sie können sogar die Daten aus den Plots extrahieren:

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

(Es funktioniert für Linien, pcolor & imshow - pcolormesh arbeitet mit einigen Tricks, um die abgeflachten Daten zu rekonstruieren .)

Den hervorragenden Tipp habe ich beim Speichern von Matplotlib-Figuren mit Pickle erhalten .

Demis
quelle
Ich glaube, dies ist nicht robust gegenüber Versionsänderungen usw. und nicht kreuzkompatibel zwischen py2.x und py3.x. Ich dachte auch pickle, dass in der Dokumentation angegeben ist, dass wir die Umgebung ähnlich wie import matplotlib.pyplot as pltbeim Einlegen (Speichern) des Objekts einrichten müssen, aber ich habe festgestellt, dass dies beim Entpicken (Laden) nicht erforderlich ist - die Importanweisungen werden in der eingelegten Datei gespeichert .
Demis
5
Sie sollten in Betracht ziehen, geöffnete Dateien zu schließen: zBwith open('FigureObject.fig.pickle', 'rb') as file: figx = pickle.load(file)
Strpeter
1
Ich bekomme nur: 'AttributeError:' Figure 'Objekt hat kein Attribut' _cachedRenderer ''
user2673238
Wenn Sie nicht möchten, dass das Skript fortgesetzt und wahrscheinlich sofort danach beendet wird figx.show(), sollten Sie plt.show()stattdessen aufrufen , was blockiert.
Maechler
38

Ab Matplotlib 1.2 haben wir jetzt experimentelle Pickle- Unterstützung. Probieren Sie es aus und sehen Sie, ob es für Ihren Fall gut funktioniert. Wenn Sie Probleme haben, teilen Sie uns dies bitte auf der Matplotlib-Mailingliste oder durch Öffnen eines Problems unter github.com/matplotlib/matplotlib mit .

Pelson
quelle
2
Jeder Grund, warum diese nützliche Funktion zum "Speichern unter" der Figur selbst hinzugefügt werden könnte. Vielleicht .pkl hinzufügen?
Bindestrich
Gute Idee @dashesy. Ich würde das unterstützen, wenn Sie die Implementierung ausprobieren möchten?
Pelson
1
Funktioniert dies nur für eine Teilmenge von Backends? Wenn ich versuche, eine einfache Figur in OSX zu finden, bekomme ich PicklingError: Can't pickle <type '_macosx.GraphicsContext'>: it's not found as _macosx.GraphicsContext.
Farenorth
Das Obige PicklingErrortritt nur auf, wenn Sie plt.show()vor dem Beizen anrufen . So einfach legen plt.show()nach pickle.dump().
Salomonvh
Auf meinem py3.5 unter MacOS 10.11 fig.show()scheint die Reihenfolge von keine Rolle zu spielen - vielleicht wurde dieser Fehler behoben. Ich kann vorher / nachher show()ohne Probleme einlegen.
Demis
7

Warum nicht einfach das Python-Skript senden? Für die .fig-Dateien von MATLAB muss der Empfänger über MATLAB verfügen, damit sie angezeigt werden können. Dies entspricht in etwa dem Senden eines Python-Skripts, für dessen Anzeige Matplotlib erforderlich ist.

Alternativ (Haftungsausschluss: Ich habe dies noch nicht versucht) können Sie versuchen, die Figur zu beizen:

import pickle
output = open('interactive figure.pickle', 'wb')
pickle.dump(gcf(), output)
output.close()
Ptomato
quelle
3
Leider sind Matplotlib-Figuren nicht auswählbar, so dass dieser Ansatz nicht funktioniert. Hinter den Kulissen gibt es zu viele C-Erweiterungen, die das Beizen nicht unterstützen. Ich stimme jedoch vollkommen zu, nur das Skript + die Daten zu senden ... Ich glaube, ich habe den Sinn der von matlab gespeicherten .fig's nie wirklich gesehen, also habe ich sie nie verwendet. Nach meiner Erfahrung war es auf lange Sicht sowieso am einfachsten, jemandem eigenständigen Code und Daten zu senden. Trotzdem wäre es schön, wenn Matplotlibs Figurenobjekte pickelbar wären.
Joe Kington
1
Sogar unsere vorverarbeiteten Daten sind etwas groß und das Plotverfahren ist komplex. Sieht aber nach dem einzigen Rückgriff aus. Vielen Dank.
Matt
1
Zahlen sind anscheinend jetzt pickelbar - es funktioniert ganz gut! Beispiel unten.
Demis
Das Beizen hat den Vorteil, dass Sie nicht alle Abstände / Positionen der Figuren / Nebenhandlungen programmgesteuert anpassen müssen. Sie können die GUI des MPL-Plots verwenden, um die Figur schön aussehen zu lassen usw., und dann die MyPlot.fig.pickleDatei speichern. So bleibt die spätere Möglichkeit erhalten, die Plotpräsentation nach Bedarf anzupassen. Dies ist auch das Tolle an Matlabs .figDateien. Besonders nützlich, wenn Sie das Größen- / Seitenverhältnis einer Feige ändern müssen (zum Einfügen in Präsentationen / Papiere).
Demis
1

Gute Frage. Hier ist der Dokumenttext von pylab.save:

pylab bietet keine Speicherfunktion mehr, obwohl die alte pylab-Funktion weiterhin als matplotlib.mlab.save verfügbar ist (Sie können sie in pylab weiterhin als "mlab.save" bezeichnen). Für reine Textdateien empfehlen wir jedoch numpy.savetxt. Zum Speichern von numpy-Arrays empfehlen wir numpy.save und dessen analoge numpy.load, die in pylab als np.save und np.load verfügbar sind.

Steve Tjoa
quelle
Dadurch werden die Daten von einem Pylab-Objekt gespeichert, Sie können die Figur jedoch nicht neu generieren.
Dr. Jimbob
Richtig. Ich sollte klarstellen, dass die Antwort keine Empfehlung war pylab.save. Aus dem Dokumenttext geht hervor, dass man ihn nicht verwenden sollte.
Steve Tjoa
Gibt es eine externe Methode zum Senden einer 3D-Figur? Möglicherweise sogar eine einfache GUI zu exe ..
CromeX
0

Ich fand einen relativ einfachen (aber etwas unkonventionellen) Weg, um meine Matplotlib-Figuren zu speichern. Es funktioniert so:

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

mit einer so save_plotdefinierten Funktion (einfache Version zum Verständnis der Logik):

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'

oder eine solche Funktion definieren save_plot(bessere Version mit Zip-Komprimierung, um leichtere Figurendateien zu erstellen):

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'

Hierfür wird ein libscripteigenes Modul verwendet , das hauptsächlich auf Modulen inspectund basiert ast. Ich kann versuchen, es auf Github zu teilen, wenn Interesse geäußert wird (es würde zuerst eine Bereinigung erfordern und ich, um mit Github zu beginnen).

Die Idee hinter dieser save_plotFunktion und diesem libscriptModul besteht darin, die Python-Anweisungen abzurufen, mit denen die Figur erstellt wird (mithilfe des Moduls inspect), sie zu analysieren (mithilfe des Moduls ast), um alle Variablen, Funktionen und Module zu extrahieren, auf die sie sich verlassen, diese aus dem Ausführungskontext zu extrahieren und sie zu serialisieren als Python-Anweisungen (Code für Variablen ist wie t=[0.0,2.0,0.01]... und Code für Module ist wieimport matplotlib.pyplot as plt ...) vor den Abbildungen. Die resultierenden Python-Anweisungen werden als Python-Skript gespeichert, dessen Ausführung die ursprüngliche Matplotlib-Figur neu erstellt.

Wie Sie sich vorstellen können, funktioniert dies gut für die meisten (wenn nicht alle) Matplotlib-Figuren.

Astrum42
quelle