Trazado sin bloqueos con Matplotlib

138

He estado jugando con Numpy y matplotlib en los últimos días. Tengo problemas para intentar que matplotlib plot sea una función sin bloquear la ejecución. Sé que ya hay muchos hilos aquí en SO que hacen preguntas similares, y he buscado mucho en Google pero no he logrado que esto funcione.

He intentado usar show (block = False) como sugieren algunas personas, pero todo lo que obtengo es una ventana congelada. Si simplemente llamo a show (), el resultado se traza correctamente pero la ejecución se bloquea hasta que se cierra la ventana. De otros hilos que he leído, sospecho que si show (block = False) funciona o no depende del backend. ¿Es esto correcto? Mi back end es Qt4Agg. ¿Podrías echar un vistazo a mi código y decirme si ves algo mal? Aquí está mi código. Gracias por cualquier ayuda.

from math import *
from matplotlib import pyplot as plt
print plt.get_backend()



def main():
    x = range(-50, 51, 1)
    for pow in range(1,5):   # plot x^1, x^2, ..., x^4

        y = [Xi**pow for Xi in x]
        print y

        plt.plot(x, y)
        plt.draw()
        #plt.show()             #this plots correctly, but blocks execution.
        plt.show(block=False)   #this creates an empty frozen window.
        _ = raw_input("Press [enter] to continue.")


if __name__ == '__main__':
    main()

PD. Olvidé decir que me gustaría actualizar la ventana existente cada vez que trazo algo, en lugar de crear una nueva.

opetroch
fuente
1
¿Has probado el modo interactivo matplotlib plt.ion()antes plt.show()? Entonces no debe ser bloqueante ya que cada trama se genera en un hilo secundario.
Anzel
@Anzel Lo acabo de probar, pero parece que no hay diferencia.
opetroch
3
¿Cómo estás ejecutando tu script? Si ejecuto su código de ejemplo desde el terminal / símbolo del sistema, parece funcionar bien, pero creo que he tenido problemas en el pasado al intentar hacer cosas como esta desde IPython QtConsole o IDEs.
Marius
1
@ Mario Aha !! Tienes razón. De hecho, lo estoy ejecutando desde la consola de mi IDE (PyCharm). Al ejecutarlo desde el indicador de cmd, plt.show (block = False), ¡funciona bien! ¿Preguntaré demasiado si le pregunto si ha encontrado alguna idea / solución para eso? ¡Muchas gracias!
opetroch
Realmente no sé lo siento. Realmente no entiendo los detalles de cómo interactúa matplotlib con la consola, por lo que generalmente solo cambio a ejecutar desde el símbolo del sistema si necesito hacer esto matplotlib.
Marius

Respuestas:

167

Pasé mucho tiempo buscando soluciones y encontré esta respuesta .

Parece que, con el fin de conseguir lo que (y yo) quiere, lo que necesita la combinación de plt.ion(), plt.show()(no con block=False) y, lo más importante, plt.pause(.001)(o lo que sea el tiempo que desee). La pausa es necesaria porque los eventos de la GUI ocurren mientras el código principal está inactivo, incluido el dibujo. Es posible que esto se implemente recogiendo el tiempo de un hilo inactivo, por lo que tal vez los IDE se metan con eso, no lo sé.

Aquí hay una implementación que funciona para mí en python 3.5:

import numpy as np
from matplotlib import pyplot as plt

def main():
    plt.axis([-50,50,0,10000])
    plt.ion()
    plt.show()

    x = np.arange(-50, 51)
    for pow in range(1,5):   # plot x^1, x^2, ..., x^4
        y = [Xi**pow for Xi in x]
        plt.plot(x, y)
        plt.draw()
        plt.pause(0.001)
        input("Press [enter] to continue.")

if __name__ == '__main__':
    main()
krs013
fuente
3
Su respuesta me ayudó mucho a resolver un problema similar que estaba teniendo. Anteriormente, lo plt.drawseguí plt.show(block = False)pero luego dejó de funcionar: la figura no respondió, cerrando se bloqueó iPython. Mi solución fue eliminar cada instancia plt.draw()y reemplazarla por plt.pause(0.001). En lugar de que lo siguieran plt.show(block = False)como plt.drawantes, fue precedido por plt.ion()y plt.show(). Ahora tengo un MatplotlibDeprecationWarningpero me permitió trazar mis cifras, así que estoy contento con esta solución.
blue_chip
3
Tenga en cuenta que en Python 2.7, debe usar raw_inputnot input. Ver aquí
Chris
Solución alternativa realmente útil cuando el enfoque reactivo "animado" no es posible. ¿Alguien sabe cómo deshacerse de la advertencia de desaprobación?
Frederic Fortier
¿Puede alguien decirme por qué recibo un símbolo del sistema de congelación cuando intento agregar plt.ion antes de plt.show?
Gabriel Augusto el
@GabrielAugusto No estoy seguro de qué podría causar eso, y no estoy seguro de lo que quieres decir. Acabo de probar este ejemplo en Python 3.6 y aún funciona. Si usó el mismo patrón y se congela, puede haber algo mal con su instalación. Debe verificar si el trazado normal funciona primero. Si intentaste algo diferente, no hay mucho que hacer al respecto en los comentarios. En cualquier caso, puede considerar hacer una pregunta por separado.
krs013
23

Un truco simple que funciona para mí es el siguiente:

  1. Use el argumento block = False dentro de show: plt.show (block = False)
  2. Use otro plt.show () al final del script .py.

Ejemplo :

import matplotlib.pyplot as plt

plt.imshow(add_something)
plt.xlabel("x")
plt.ylabel("y")

plt.show(block=False)

#more code here (e.g. do calculations and use print to see them on the screen

plt.show()

Nota : plt.show()es la última línea de mi script.

seralouk
fuente
77
Esto produce (para mí, en Linux, Anaconda, Python 2.7, backend predeterminado) una ventana en blanco que permanece en blanco hasta el final de la ejecución, cuando finalmente se completa. No es útil para actualizar un diagrama en medio de la ejecución. :-(
sh37211
@ sh37211 No estoy seguro de cuál es su objetivo. En algunos casos que intentas trazar algo, pero después del comando de trazado tienes otros comandos, entonces esto es útil ya que te permite trazar y ejecutar los otros comandos. Vea esta publicación para obtener más información sobre esto: stackoverflow.com/questions/458209/… . Si desea actualizar una trama, entonces debería ser de otra manera.
seralouk
17

Puede evitar el bloqueo de la ejecución escribiendo el diagrama en una matriz y luego mostrando la matriz en un hilo diferente. Aquí hay un ejemplo de generación y visualización de gráficos simultáneamente usando pf.screen de pyformulas 0.2.8 :

import pyformulas as pf
import matplotlib.pyplot as plt
import numpy as np
import time

fig = plt.figure()

canvas = np.zeros((480,640))
screen = pf.screen(canvas, 'Sinusoid')

start = time.time()
while True:
    now = time.time() - start

    x = np.linspace(now-2, now, 100)
    y = np.sin(2*np.pi*x) + np.sin(3*np.pi*x)
    plt.xlim(now-2,now+1)
    plt.ylim(-3,3)
    plt.plot(x, y, c='black')

    # If we haven't already shown or saved the plot, then we need to draw the figure first...
    fig.canvas.draw()

    image = np.fromstring(fig.canvas.tostring_rgb(), dtype=np.uint8, sep='')
    image = image.reshape(fig.canvas.get_width_height()[::-1] + (3,))

    screen.update(image)

#screen.close()

Resultado:

Animación sinusoidal

Descargo de responsabilidad: soy el mantenedor de pyformulas.

Referencia: Matplotlib: guardar la trama en una matriz numpy

Foto por defecto
fuente
9

Muchas de estas respuestas están súper infladas y, por lo que puedo encontrar, la respuesta no es tan difícil de entender.

Puedes usarlo plt.ion()si quieres, pero encontré que usarlo plt.draw()es igual de efectivo

Para mi proyecto específico que estoy trazando imágenes, pero se puede usar plot()o scatter()o lo que sea en lugar de figimage(), no importa.

plt.figimage(image_to_show)
plt.draw()
plt.pause(0.001)

O

fig = plt.figure()
...
fig.figimage(image_to_show)
fig.canvas.draw()
plt.pause(0.001)

Si estás usando una figura real.
Utilicé @ krs013, y las respuestas de @Default Picture para resolver esto
Espero que esto salve a alguien de haber lanzado cada figura en un hilo separado, o de tener que leer estas novelas solo para resolver esto

iggy12345
fuente
3

Trazado en vivo

import numpy as np
import matplotlib.pyplot as plt

x = np.linspace(0, 2 * np.pi, 100)
# plt.axis([x[0], x[-1], -1, 1])      # disable autoscaling
for point in x:
    plt.plot(point, np.sin(2 * point), '.', color='b')
    plt.draw()
    plt.pause(0.01)
# plt.clf()                           # clear the current figure

Si la cantidad de datos es demasiado, puede reducir la velocidad de actualización con un contador simple

cnt += 1
if (cnt == 10):       # update plot each 10 points
    plt.draw()
    plt.pause(0.01)
    cnt = 0

Mantener el diagrama después de la salida del programa

Este era mi problema real para el que no podía encontrar una respuesta satisfactoria, quería trazar que no se cerró después de que se terminó el guión (como MATLAB),

Si lo piensa, una vez finalizado el script, el programa finaliza y no hay una forma lógica de mantener la trama de esta manera, por lo que hay dos opciones

  1. bloquear la salida del script (eso es plt.show () y no lo que quiero)
  2. ejecutar la trama en un hilo separado (demasiado complicado)

esto no fue satisfactorio para mí, así que encontré otra solución fuera de la caja

Guardar en archivo y ver en visor externo

Para esto, el guardado y la visualización deben ser rápidos y el espectador no debe bloquear el archivo y debe actualizar el contenido automáticamente

Selección de formato para guardar

los formatos basados ​​en vectores son pequeños y rápidos

  • SVG es bueno, pero no podría encontrar un buen visor para él, excepto el navegador web, que por defecto necesita una actualización manual
  • PDF puede admitir formatos vectoriales y hay visores livianos que admiten la actualización en vivo

Visor rápido y ligero con actualización en vivo

Para PDF hay varias buenas opciones

  • En Windows uso SumatraPDF, que es gratis, rápido y ligero (solo usa 1.8 MB de RAM para mi caso)

  • En Linux hay varias opciones como Evince (GNOME) y Ocular (KDE)

Código de muestra y resultados

Código de muestra para enviar el trazado a un archivo

import numpy as np
import matplotlib.pyplot as plt

x = np.linspace(0, 2 * np.pi, 100)
y = np.sin(2 * x)
plt.plot(x, y)
plt.savefig("fig.pdf")

después de la primera ejecución, abra el archivo de salida en uno de los visores mencionados anteriormente y disfrute.

Aquí hay una captura de pantalla de VSCode junto con SumatraPDF, también el proceso es lo suficientemente rápido como para obtener una tasa de actualización semi-en vivo (puedo obtener cerca de 10Hz en mi configuración, solo uso time.sleep()entre intervalos) pyPlot, sin bloqueo

Ali80
fuente
2

La respuesta de Iggy fue la más fácil de seguir para mí, pero recibí el siguiente error al hacer un subplotcomando posterior que no estaba allí cuando solo estaba haciendo show:

MatplotlibDeprecationWarning: al agregar ejes utilizando los mismos argumentos que los ejes anteriores, actualmente se reutiliza la instancia anterior. En una versión futura, siempre se creará y devolverá una nueva instancia. Mientras tanto, esta advertencia puede ser suprimida y el comportamiento futuro asegurado, pasando una etiqueta única a cada instancia de los ejes.

Para evitar este error, ayuda a cerrar (o borrar ) el gráfico después de que el usuario presiona enter.

Aquí está el código que funcionó para mí:

def plt_show():
    '''Text-blocking version of plt.show()
    Use this instead of plt.show()'''
    plt.draw()
    plt.pause(0.001)
    input("Press enter to continue...")
    plt.close()
Pro Q
fuente
0

El paquete Python dibujado ahora permite actualizar una trama en tiempo real de forma no bloqueante.
También funciona con una cámara web y OpenCV, por ejemplo, para trazar medidas para cada cuadro.
Ver la publicación original .

Ismael EL ATIFI
fuente