Zapisywanie interaktywnych figur Matplotlib

119

Czy istnieje sposób na zapisanie figury Matplotlib, aby można ją było ponownie otworzyć i przywrócić typową interakcję? (Podobnie jak format .fig w MATLAB?)

Często uruchamiam te same skrypty, aby wygenerować te interaktywne liczby. Albo wysyłam moim kolegom wiele statycznych plików PNG, aby pokazać różne aspekty fabuły. Wolałbym raczej wysłać obiekt figury i pozwolić im na samodzielną interakcję.

Matt
źródło

Odpowiedzi:

30

Byłaby to świetna funkcja, ale AFAIK nie jest zaimplementowana w Matplotlib i prawdopodobnie byłaby trudna do zaimplementowania samodzielnie ze względu na sposób przechowywania liczb.

Sugerowałbym albo (a) oddzielne przetwarzanie danych od generowania figury (która zapisuje dane pod unikalną nazwą) i napisanie skryptu generującego figurę (ładowanie określonego pliku zapisanych danych) i edycję według własnego uznania lub (b ) zapisz jako format PDF / SVG / PostScript i edytuj w jakimś wymyślnym edytorze figur, takim jak Adobe Illustrator (lub Inkscape ).

EDYTUJ po jesieni 2012 : Jak inni zauważyli poniżej (choć wspominali tutaj, ponieważ jest to akceptowana odpowiedź), Matplotlib od wersji 1.2 pozwalał na trawienie liczb. Zgodnie z informacją o wydaniu jest to funkcja eksperymentalna i nie obsługuje zapisywania figury w jednej wersji matplotlib i otwierania w innej. Przywracanie marynaty z niezaufanego źródła jest również ogólnie niebezpieczne.

W przypadku udostępniania / późniejszej edycji wykresów (które najpierw wymagają znacznego przetworzenia danych i mogą wymagać poprawek po miesiącach, np. Podczas recenzowania publikacji naukowych), nadal zalecam przepływ pracy (1), aby mieć skrypt przetwarzania danych, który przed wygenerowaniem wykresu zapisuje przetworzone dane (które trafiają do twojego wykresu) do pliku i (2) ma osobny skrypt generujący wykres (który możesz dostosować w razie potrzeby), aby odtworzyć wykres. W ten sposób dla każdego wykresu można szybko uruchomić skrypt i ponownie go wygenerować (i szybko skopiować ustawienia wykresu z nowymi danymi). To powiedziawszy, wytrawianie figury może być wygodne do krótkoterminowej / interaktywnej / eksploracyjnej analizy danych.

dr jimbob
źródło
2
Trochę zdziwiony, że nie jest to zaimplementowane. Ale ok, zapiszę przetworzone dane w pliku pośrednim i wyślę je wraz ze skryptem do wykreślenia kolegom. Dzięki.
Matt
2
Podejrzewam, że implementacja jest trudna, dlatego działa tak słabo jak MATLAB. Kiedy go używałem, dane, które powodowały awarię MATLAB-a, a nawet nieco inne wersje nie były w stanie odczytać nawzajem plików .fig.
Adrian Ratnapala,
6
pickleteraz działa na figurach MPL, więc można to zrobić i wygląda na to, że działa całkiem nieźle - prawie jak plik figury Matlab ".fig". Zobacz moją odpowiedź poniżej (na razie?), Aby zobaczyć przykład, jak to zrobić.
Demis
@Demis: jak wskazał ptomato w swojej odpowiedzi poniżej, istniał już wtedy.
strpeter
@strpeter - Dane Matlab nie były trawione w 2010 roku, jak wskazano w tym komentarzu . Funkcja eksperymentalna została dodana wraz z matplotlib 1.2 wydanym jesienią 2012 roku . Jak tam wspomniano, nie powinieneś oczekiwać, że będzie działać między wersjami matplotlib i nie powinieneś otwierać pikli pochodzących z niezaufanego źródła.
dr jimbob
63

Właśnie dowiedziałem się, jak to zrobić. „Eksperymentalna obsługa marynat”, o której wspomniał @pelson, działa całkiem dobrze.

Spróbuj tego:

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

Po dostosowaniu interaktywnych zapisz obiekt figury jako plik binarny:

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

Później otwórz rysunek, a poprawki powinny zostać zapisane, a interaktywność GUI powinna być obecna:

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

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

Możesz nawet wyodrębnić dane z wykresów:

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

(Działa dla linii, pcolor i imshow - pcolormesh wykorzystuje kilka sztuczek do rekonstrukcji spłaszczonych danych .)

Otrzymałem doskonałą wskazówkę od zapisywania danych Matplotlib za pomocą marynaty .

Demis
źródło
Uważam, że nie jest to odporne na zmiany wersji itp. I nie jest kompatybilne krzyżowo między py2.x i py3.x. Pomyślałem też, że pickledokumentacja mówi, że musimy skonfigurować środowisko podobnie jak wtedy, gdy obiekt był marynowany (zapisywany), ale stwierdziłem, że nie trzeba tego robić import matplotlib.pyplot as pltpodczas rozpakowywania (ładowania) - zapisuje instrukcje importu w piklowanym pliku .
Demis
5
Powinieneś rozważyć zamknięcie otwartych plików: np.with open('FigureObject.fig.pickle', 'rb') as file: figx = pickle.load(file)
strpeter
1
Po prostu otrzymuję: `` AttributeError: 'Figure' obiekt nie ma atrybutu '_cachedRenderer' '
user2673238
Jeśli nie chcesz, aby skrypt kontynuował działanie i prawdopodobnie natychmiast po zakończeniu figx.show(), powinieneś zadzwonić plt.show()zamiast tego, co jest blokowaniem.
maechler
38

Począwszy od Matplotlib 1.2, mamy teraz eksperymentalną obsługę pikle . Spróbuj i zobacz, czy działa dobrze w Twoim przypadku. Jeśli masz jakiekolwiek problemy, daj nam znać na liście mailingowej Matplotlib lub otwierając problem na github.com/matplotlib/matplotlib .

pelson
źródło
2
Z dowolnego powodu ta przydatna funkcja mogłaby zostać dodana do samego „Zapisz jako” figury. Może dodajesz plik .pkl?
dashesy
Dobry pomysł @dashy. Poparłbym to, gdybyś chciał spróbować go wdrożyć?
pelson
1
Czy to działa tylko na podzbiorze backendów? Kiedy próbuję wytrawić prostą figurę w OSX, otrzymuję PicklingError: Can't pickle <type '_macosx.GraphicsContext'>: it's not found as _macosx.GraphicsContext.
farenorth
Powyższe PicklingErrorma miejsce tylko wtedy, gdy plt.show()sprawdzisz przed marynowaniem. Więc po prostu umieść plt.show()po pickle.dump().
salomonvh
Na moim py3.5 na MacOS 10.11 kolejność fig.show()nie wydaje się mieć znaczenia - być może ten błąd został naprawiony. Mogę marynować przed / po show()bez problemu.
Demis
7

Dlaczego po prostu nie wysłać skryptu w języku Python? Pliki .fig MATLAB-a wymagają od odbiorcy posiadania MATLAB-a, aby je wyświetlić, więc jest to prawie równoważne wysłaniu skryptu Pythona, który wymaga wyświetlenia Matplotlib.

Alternatywnie (zastrzeżenie: jeszcze tego nie próbowałem) możesz spróbować wytrawić figurę:

import pickle
output = open('interactive figure.pickle', 'wb')
pickle.dump(gcf(), output)
output.close()
ptomato
źródło
3
Niestety, dane matplotlib nie są trawione, więc to podejście nie zadziała. Za kulisami jest zbyt wiele rozszerzeń C, które nie obsługują wytrawiania. Całkowicie zgadzam się na wysłanie po prostu skryptu + danych, chociaż ... Wydaje mi się, że nigdy nie widziałem sensu zapisanych plików .fig Matlaba, więc nigdy ich nie użyłem. Z mojego doświadczenia wynika, że ​​wysyłanie komuś samodzielnego kodu i danych było na dłuższą metę najłatwiejsze. Mimo wszystko byłoby miło, gdyby figura matplotlib była możliwa do wytrawiania.
Joe Kington
1
Nawet nasze wstępnie przetworzone dane są dość duże, a procedura kreślenia jest złożona. Wygląda na to, że jest to jedyna regres. dzięki.
Matt
1
Liczby najwyraźniej teraz można marynować - działa całkiem dobrze! Przykład poniżej.
Demis
Zaletą wytrawiania jest to, że nie trzeba programowo dostosowywać wszystkich odstępów / pozycji figury / wykresu pomocniczego. Możesz użyć GUI wykresu MPL, aby nadać rysunkowi ładny wygląd itp., A następnie zapisać MyPlot.fig.pickleplik - zachowując później możliwość dostosowania prezentacji wykresu według potrzeb. To również jest świetne w .figplikach Matlaba . Szczególnie przydatne, gdy trzeba zmienić rozmiar / proporcje figury (do umieszczenia w prezentacjach / artykułach).
Demis
1

Dobre pytanie. Oto tekst dokumentu z pylab.save:

pylab nie zapewnia już funkcji zapisywania, chociaż stara funkcja pylab jest nadal dostępna jako matplotlib.mlab.save (nadal można się do niej odwoływać w pylab jako „mlab.save”). Jednak w przypadku zwykłych plików tekstowych zalecamy numpy.savetxt. Do zapisywania tablic numpy zalecamy numpy.save i jego analogowy numpy.load, które są dostępne w pylab jako np.save i np.load.

Steve Tjoa
źródło
Zapisuje to dane z obiektu pylab, ale nie pozwala na regenerację figury.
dr jimbob
Poprawny. Powinienem wyjaśnić, że odpowiedź nie była zaleceniem do użycia pylab.save. W rzeczywistości z tekstu dokumentu wynika, że nie należy go używać.
Steve Tjoa
Czy istnieje zewnętrzna metoda wysyłania figury 3D? Możliwe nawet proste GUI do wykonania ..
CromeX
0

Wymyśliłem stosunkowo prosty (ale nieco niekonwencjonalny) sposób zapisywania moich danych matplotlib. Działa to tak:

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

z funkcją save_plotzdefiniowaną w ten sposób (prosta wersja do zrozumienia logiki):

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'

lub definiując save_plottaką funkcję (lepsza wersja wykorzystująca kompresję zip do tworzenia lżejszych plików z figurami):

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'

To sprawia, że ​​używam libscriptwłasnego modułu , który w większości opiera się na modułach inspecti ast. Mogę spróbować udostępnić to na Githubie, jeśli wyrażone zostanie zainteresowanie (najpierw wymagałoby to uporządkowania i rozpoczęcia korzystania z Github).

Ideą tej save_plotfunkcji i libscriptmodułu jest pobranie instrukcji Pythona, które tworzą figurę (za pomocą modułu inspect), przeanalizowanie ich (za pomocą modułu ast) w celu wyodrębnienia wszystkich zmiennych, funkcji i modułów, na których opiera się, wyodrębnienie ich z kontekstu wykonania i ich serializacja jako instrukcje Pythona (kod dla zmiennych będzie podobny do t=[0.0,2.0,0.01]... a kod modułów będzie podobny do import matplotlib.pyplot as plt...) dołączone do instrukcji rysunku. Wynikowe instrukcje w języku Python są zapisywane jako skrypt w języku Python, którego wykonanie spowoduje odtworzenie oryginalnej figury matplotlib.

Jak możesz sobie wyobrazić, działa to dobrze w przypadku większości (jeśli nie wszystkich) figur matplotlib.

Astrum42
źródło