Tracciare in modo non bloccante con Matplotlib


138

Ho giocato con Numpy e matplotlib negli ultimi giorni. Sto riscontrando problemi nel tentativo di rendere una trama matplotlib una funzione senza bloccare l'esecuzione. So che ci sono già molti thread qui su SO che fanno domande simili, e ho cercato su Google abbastanza ma non sono riuscito a farlo funzionare.

Ho provato a usare show (block = False) come alcuni suggeriscono, ma tutto quello che ottengo è una finestra congelata. Se chiamo semplicemente show (), il risultato viene tracciato correttamente ma l'esecuzione viene bloccata fino alla chiusura della finestra. Dagli altri thread che ho letto, ho il sospetto che se show (block = False) funzioni o meno dipende dal backend. È corretto? Il mio back-end è Qt4Agg. Potresti dare un'occhiata al mio codice e dirmi se vedi qualcosa di sbagliato? Ecco il mio codice Grazie per qualsiasi aiuto.

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

PS. Ho dimenticato di dire che vorrei aggiornare la finestra esistente ogni volta che tracciamo qualcosa, invece di crearne una nuova.


1
hai provato la modalità interattiva matplotlib con plt.ion()prima plt.show()? Dovrebbe quindi essere non bloccante poiché ogni trama viene generata in un thread figlio.
Anzel,

@Anzel L'ho appena provato, ma sembra non fare alcuna differenza.
opetroch

3
Come stai eseguendo la tua sceneggiatura? Se eseguo il tuo codice di esempio dal terminale / prompt dei comandi, sembra funzionare bene, ma penso di aver avuto problemi in passato quando provavo a fare cose del genere da Qythons o IDE IPython.
Marius,

1
@Marius Aha !! Hai ragione. In effetti lo sto eseguendo dalla console del mio IDE (PyCharm). Quando lo esegui dal prompt cmd, plt.show (block = False), funziona bene! Ti chiederò troppo se ti chiedo se hai trovato qualche idea / soluzione a questo? Molte grazie!
opetroch

Non so davvero scusa. Non capisco davvero i dettagli di come matplotlib interagisce con la console, quindi in genere passo a correre dal prompt dei comandi se devo fare queste cose con matplotlib.
Marius,

Risposte:


167

Ho trascorso molto tempo alla ricerca di soluzioni e ho trovato questa risposta .

Sembra che, al fine di ottenere quello che (e io) desidera, è necessario la combinazione di plt.ion(), plt.show()(non con block=False) e, soprattutto, plt.pause(.001)(o qualsiasi momento si desidera). La pausa è necessaria perché gli eventi della GUI si verificano mentre il codice principale è inattivo, incluso il disegno. È possibile che ciò venga implementato raccogliendo tempo da un thread inattivo, quindi forse gli IDE si scontrano con questo, non lo so.

Ecco un'implementazione che funziona per me su 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()

3
La tua risposta mi ha aiutato molto a risolvere un problema simile che stavo riscontrando. In precedenza, avevo plt.drawseguito plt.show(block = False)ma poi ha smesso di funzionare: la figura non ha risposto, chiudendolo si è bloccato iPython. La mia soluzione era rimuovere ogni istanza plt.draw()e sostituirla con plt.pause(0.001). Invece di averlo seguito da plt.show(block = False)come plt.drawera prima, è stato preceduto da plt.ion()e plt.show(). Ora ho un MatplotlibDeprecationWarningma mi permette di tracciare le mie figure, quindi sono contento di questa soluzione.
blue_chip

3
Nota che in Python 2.7 non devi usare raw_inputno input. Vedi qui
Chris,

Soluzione davvero utile quando l'approccio reattivo "animato" non è possibile! Qualcuno sa come sbarazzarsi dell'avvertimento di deprecazione?
Frederic Fortier,

Per favore qualcuno può dirmi perché ricevo un prompt dei comandi freezen quando provo ad aggiungere plt.ion prima di plt.show?
Gabriel Augusto,

@GabrielAugusto Non sono sicuro di cosa potrebbe causare questo, e non sono sicuro di cosa tu voglia dire. Ho appena provato questo esempio in Python 3.6 e funziona ancora. Se hai utilizzato lo stesso modello e si blocca, potrebbe esserci qualcosa di sbagliato nella tua installazione. È necessario verificare se la normale stampa funziona per prima. Se hai provato qualcosa di diverso, non c'è molto da fare nei commenti. In entrambi i casi, potresti considerare di porre una domanda separata.
krs013,

23

Un semplice trucco che funziona per me è il seguente:

  1. Usa l' argomento block = False all'interno di show: plt.show (block = False)
  2. Utilizzare un altro plt.show () alla fine dello script .py.

Esempio :

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()è l'ultima riga della mia sceneggiatura.


7
Questo produce (per me, su Linux, Anaconda, Python 2.7, backend predefinito) una finestra vuota che rimane vuota fino alla fine dell'esecuzione, quando alla fine viene compilata. Non utile per l'aggiornamento di una trama nel mezzo dell'esecuzione. :-(
sh37211

@ sh37211 Non sono sicuro del tuo obiettivo. In alcuni casi che provi a tracciare qualcosa ma dopo il comando trama hai altri comandi, questo è utile poiché ti consente di tracciare e ottenere gli altri comandi eseguiti. Vedi questo post per ulteriori informazioni al riguardo: stackoverflow.com/questions/458209/… . Se vuoi aggiornare una trama, dovrebbe essere un altro modo.
seralouk,

17

È possibile evitare il blocco dell'esecuzione scrivendo la trama in un array, quindi visualizzando l'array in un thread diverso. Ecco un esempio di generazione e visualizzazione di grafici simultaneamente usando pf.screen da 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()

Risultato:

Animazione sinusoidale

Disclaimer: sono il manutentore delle piformulas.

Riferimento: Matplotlib: salva la trama nell'array numpy


9

Molte di queste risposte sono super gonfiate e da quello che posso trovare, la risposta non è poi così difficile da capire.

Puoi usare plt.ion()se vuoi, ma ho trovato usandoplt.draw() altrettanto efficace

Per il mio progetto specifico che sto tracciando le immagini, ma è possibile utilizzare plot()o scatter()o qualsiasi altra cosa, invece di figimage(), non 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)

Se stai usando una figura reale.
Ho usato @ krs013 e le risposte di @Default Picture per capirlo
Speriamo che questo salvi qualcuno dall'avvio di ogni singola figura su un thread separato, o dal dover leggere questi romanzi solo per capirlo


3

Tracciato dal 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

se la quantità di dati è eccessiva, è possibile ridurre la frequenza di aggiornamento con un semplice contatore

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

Tenere premuto il diagramma dopo l'uscita dal programma

Questo era il mio vero problema per il quale non riuscivo a trovare una risposta soddisfacente, volevo un complotto che non si chiudesse al termine della sceneggiatura (come MATLAB),

Se ci pensate, dopo che lo script è terminato, il programma è terminato e non esiste un modo logico per mantenere la trama in questo modo, quindi ci sono due opzioni

  1. bloccare l'uscita dallo script (questo è plt.show () e non quello che voglio)
  2. esegui la trama su un thread separato (troppo complicato)

questo non è stato soddisfacente per me, quindi ho trovato un'altra soluzione fuori dagli schemi

SaveToFile e View nel visualizzatore esterno

Per questo il salvataggio e la visualizzazione dovrebbero essere rapidi e il visualizzatore non dovrebbe bloccare il file e dovrebbe aggiornare automaticamente il contenuto

Selezione del formato per il salvataggio

i formati basati su vettori sono sia piccoli che veloci

  • SVG è buono ma non può trovare un buon visualizzatore per esso tranne il browser Web che per impostazione predefinita necessita di aggiornamento manuale
  • Il PDF può supportare formati vettoriali e esistono visualizzatori leggeri che supportano l'aggiornamento in tempo reale

Visualizzatore leggero veloce con aggiornamento live

Per PDF ci sono diverse buone opzioni

  • Su Windows utilizzo SumatraPDF che è gratuito, veloce e leggero (per il mio caso utilizza solo RAM da 1,8 MB)

  • Su Linux ci sono diverse opzioni come Evince (GNOME) e Ocular (KDE)

Codice di esempio e risultati

Codice di esempio per l'output della trama in un file

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

dopo la prima esecuzione, apri il file di output in uno dei visualizzatori sopra menzionati e divertiti.

Ecco uno screenshot di VSCode insieme a SumatraPDF, inoltre il processo è abbastanza veloce da ottenere una frequenza di aggiornamento semi-live (posso arrivare vicino a 10Hz sulla mia configurazione usando solo time.sleep()tra gli intervalli) pyPlot, non-blocking


2

La risposta di Iggy era il più facile per me da seguire, ma ho ottenuto il seguente errore quando si fa un successivo subplotcomando che non c'era quando stavo solo facendo show:

MatplotlibDeprecationWarning: l'aggiunta di un asse utilizzando gli stessi argomenti di un asse precedente riutilizza attualmente l'istanza precedente. In una versione futura, verrà sempre creata e restituita una nuova istanza. Nel frattempo, questo avviso può essere soppresso e il comportamento futuro assicurato, passando un'etichetta univoca a ciascuna istanza degli assi.

Per evitare questo errore, aiuta a chiudere (o cancellare) ) la trama dopo che l'utente preme Invio.

Ecco il codice che ha funzionato per me:

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

0

Il pacchetto Python drawnow consente di aggiornare una trama in tempo reale in modo non bloccante.
Funziona anche con una webcam e OpenCV per esempio per tracciare misure per ciascun fotogramma.
Vedi il post originale .

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.