Guardar figuras interactivas de Matplotlib

119

¿Hay alguna forma de guardar una figura de Matplotlib de modo que se pueda volver a abrir y restaurar la interacción típica? (¿Como el formato .fig en MATLAB?)

Me encuentro ejecutando los mismos scripts muchas veces para generar estas figuras interactivas. O les envío a mis colegas varios archivos PNG estáticos para mostrar diferentes aspectos de un gráfico. Prefiero enviar el objeto figura y hacer que interactúen con él.

Mate
fuente

Respuestas:

30

Esta sería una gran característica, pero AFAIK no está implementada en Matplotlib y probablemente sería difícil de implementar usted mismo debido a la forma en que se almacenan las cifras.

Sugeriría (a) procesar los datos por separado de la generación de la figura (que guarda los datos con un nombre único) y escribir un script de generación de figuras (cargando un archivo específico de los datos guardados) y editarlos como mejor le parezca o (b ) guarde como formato PDF / SVG / PostScript y edítelo en algún editor de figuras elegante como Adobe Illustrator (o Inkscape ).

EDITAR post Otoño de 2012 : como otros señalaron a continuación (aunque mencionaron aquí que esta es la respuesta aceptada), Matplotlib desde la versión 1.2 le permitió seleccionar cifras. Como indican las notas de la versión , es una función experimental y no admite guardar una figura en una versión de matplotlib y abrirla en otra. Por lo general, tampoco es seguro restaurar un pepinillo de una fuente que no sea de confianza.

Para compartir / editar gráficos posteriores (que primero requieren un procesamiento significativo de datos y es posible que deban modificarse meses después, por ejemplo, durante la revisión por pares para una publicación científica), todavía recomiendo el flujo de trabajo de (1) tener un script de procesamiento de datos que antes de generar un gráfico guarda los datos procesados ​​(que entran en su diagrama) en un archivo, y (2) tiene un script de generación de diagrama separado (que usted ajusta según sea necesario) para recrear el diagrama. De esta manera, para cada trama, puede ejecutar rápidamente un script y volver a generarlo (y copiar rápidamente la configuración de la trama con nuevos datos). Dicho esto, el decapado de una figura podría ser conveniente para el análisis de datos a corto plazo / interactivo / exploratorio.

Dr. jimbob
fuente
2
Me sorprende un poco que esto no se haya implementado. Pero bueno, guardaré los datos procesados ​​en un archivo intermedio y lo enviaré y un script para trazar a los colegas. Gracias.
Matt
2
Sospecho que la implementación es difícil, por lo que funciona tan mal en MATLAB. Cuando lo usé, las cifras solían bloquear MATLAB, e incluso versiones ligeramente diferentes no podían leer los archivos .fig de las demás.
Adrian Ratnapala
6
pickleahora funciona con figuras MPL, por lo que se puede hacer y parece funcionar razonablemente bien, casi como un archivo de figura ".fig" de Matlab. Vea mi respuesta a continuación (¿por ahora?) Para ver un ejemplo de cómo hacerlo.
Demis
@Demis: como señaló ptomato en su respuesta a continuación, ya existía en ese momento.
strpeter
@strpeter: las cifras de Matlab no se podían encurtir en 2010 como se señala en este comentario . La función experimental se agregó con matplotlib 1.2 lanzado en otoño de 2012 . Como se indica allí, no debe esperar que funcione entre las versiones de matplotlib y no debe abrir pickles que provengan de una fuente que no sea de confianza.
dr jimbob
63

Acabo de descubrir cómo hacer esto. El "soporte de pickle experimental" mencionado por @pelson funciona bastante bien.

Prueba esto:

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

Después de su ajuste interactivo, guarde el objeto de figura como un archivo binario:

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

Más tarde, abra la figura y los ajustes deben guardarse y la interactividad de la GUI debe estar presente:

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

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

Incluso puede extraer los datos de los gráficos:

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

(Funciona para líneas, pcolor e imshow; pcolormesh funciona con algunos trucos para reconstruir los datos aplanados ).

Obtuve el excelente consejo de Guardar figuras de Matplotlib usando Pickle .

Demis
fuente
Creo que esto no es robusto a los cambios de versión, etc., y no es compatible entre py2.x y py3.x. También pensé que la pickledocumentación indicaba que necesitamos configurar el entorno de manera similar a cuando el objeto fue encurtido (guardado), pero descubrí que no es necesario import matplotlib.pyplot as pltal despegar (cargar): guarda las declaraciones de importación en el archivo encurtido .
Demis
5
Debería considerar cerrar los archivos abiertos: por ejemplowith open('FigureObject.fig.pickle', 'rb') as file: figx = pickle.load(file)
strpeter
1
Acabo de obtener: 'AttributeError: el objeto' Figura 'no tiene atributo' _cachedRenderer '' '
user2673238
Si no desea que el script continúe y probablemente termine inmediatamente después figx.show(), debe llamar en su plt.show()lugar, lo cual está bloqueando.
maechler
38

A partir de Matplotlib 1.2, ahora tenemos soporte de pickle experimental . Pruébelo y vea si funciona bien para su caso. Si tiene algún problema, háganoslo saber en la lista de correo de Matplotlib o abriendo un problema en github.com/matplotlib/matplotlib .

Pelson
fuente
2
Por cualquier motivo, esta función útil podría agregarse al "Guardar como" de la figura. ¿Añadiendo .pkl quizás?
dashesy
Buena idea @dashesy. ¿Lo apoyaría si quisiera implementarlo?
pelson
1
¿Esto solo funciona en un subconjunto de backends? Cuando trato de elegir una figura simple en OSX, obtengo PicklingError: Can't pickle <type '_macosx.GraphicsContext'>: it's not found as _macosx.GraphicsContext.
Farenorth
Lo anterior PicklingErrorsolo ocurre si llamas plt.show()antes de hacer el pepinillo. Así que solo coloca plt.show()después pickle.dump().
salomonvh
En mi py3.5 en MacOS 10.11, el orden de fig.show()no parece importar, tal vez ese error se solucionó. Puedo encurtir antes / después show()sin problema.
Demis
7

¿Por qué no enviar el script de Python? Los archivos .fig de MATLAB requieren que el destinatario tenga MATLAB para mostrarlos, por lo que eso es equivalente a enviar un script de Python que requiere que se muestre Matplotlib.

Alternativamente (descargo de responsabilidad: aún no he probado esto), puede intentar encurtir la figura:

import pickle
output = open('interactive figure.pickle', 'wb')
pickle.dump(gcf(), output)
output.close()
tomate
fuente
3
Desafortunadamente, las figuras de matplotlib no se pueden encurtir, por lo que ese enfoque no funcionará. Detrás de escena, hay demasiadas extensiones C que no admiten el decapado. Sin embargo, estoy completamente de acuerdo en enviar el script + datos ... Supongo que nunca he visto el sentido de los .fig guardados de matlab, así que nunca los usé. Enviar a alguien código y datos independientes ha sido lo más fácil a largo plazo, en mi experiencia, de todos modos. Aún así, sería bueno si los objetos de figura de matplotlib fueran encurtidos.
Joe Kington
1
Incluso nuestros datos preprocesados ​​son algo grandes y el procedimiento de trazado es complejo. Sin embargo, parece que es el único recurso. Gracias.
Matt
1
Aparentemente, las figuras ahora se pueden encurtir, ¡funciona bastante bien! Ejemplo a continuación.
Demis
El beneficio de pickle es que no tiene que ajustar programáticamente todos los espacios / posiciones de figuras / subtramas. Puede usar la GUI del gráfico MPL para que la figura se vea bien, etc., luego guarde el MyPlot.fig.picklearchivo, conservando la capacidad posterior de ajustar la presentación del gráfico según sea necesario. Esto también es lo bueno de los .figarchivos de Matlab . Especialmente útil cuando necesita cambiar el tamaño / relación de aspecto de una figura (para insertar en presentaciones / documentos).
Demis
1

Buena pregunta. Aquí está el texto del documento de pylab.save:

pylab ya no proporciona una función de guardado, aunque la antigua función de pylab todavía está disponible como matplotlib.mlab.save (todavía puede referirse a ella en pylab como "mlab.save"). Sin embargo, para archivos de texto sin formato, recomendamos numpy.savetxt. Para guardar matrices numpy, recomendamos numpy.save y su numpy.load analógico, que están disponibles en pylab como np.save y np.load.

Steve Tjoa
fuente
Esto guarda los datos del objeto a pylab, pero no le permite regenerar la figura.
dr jimbob
Correcto. Debo aclarar que la respuesta no fue una recomendación de uso pylab.save. De hecho, a partir del texto del documento, parece que uno no debería usarlo.
Steve Tjoa
¿Existe algún método externo para enviar una figura 3D? Es posible incluso una simple GUI para
ejecutar
0

Descubrí una forma relativamente simple (aunque un poco poco convencional) de guardar mis figuras de matplotlib. Funciona así:

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 función save_plotdefinida así (versión simple para entender la 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'

o definir una función save_plotcomo esta (mejor versión que usa compresión zip para producir archivos de figuras más livianos):

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'

Esto hace que use un módulo libscriptpropio, que se basa principalmente en módulos inspecty ast. Puedo intentar compartirlo en Github si se expresa interés (primero requeriría una limpieza y yo comenzaría con Github).

La idea detrás de esta save_plotfunción y libscriptmódulo es buscar las instrucciones de Python que crean la figura (usando el módulo inspect), analizarlas (usando el módulo ast) para extraer todas las variables, funciones y módulos en los que se basa la importación, extraerlos del contexto de ejecución y serializarlos. como instrucciones de Python (el código para las variables será como t=[0.0,2.0,0.01]... y el código para los módulos será como import matplotlib.pyplot as plt...) antepuesto a las instrucciones de la figura. Las instrucciones de Python resultantes se guardan como un script de Python cuya ejecución reconstruirá la figura original de matplotlib.

Como puede imaginar, esto funciona bien para la mayoría (si no todas) las figuras de matplotlib.

Astrum42
fuente