Tracer de manière non bloquante avec Matplotlib

138

J'ai joué avec Numpy et matplotlib ces derniers jours. J'ai des problèmes en essayant de faire de matplotlib plot une fonction sans bloquer l'exécution. Je sais qu'il y a déjà beaucoup de fils ici sur SO posant des questions similaires, et j'ai beaucoup cherché sur Google mais je n'ai pas réussi à faire fonctionner cela.

J'ai essayé d'utiliser show (block = False) comme certains le suggèrent, mais tout ce que j'obtiens est une fenêtre figée. Si j'appelle simplement show (), le résultat est tracé correctement mais l'exécution est bloquée jusqu'à ce que la fenêtre soit fermée. À partir d 'autres threads que j'ai lus, je soupçonne que si show (block = False) fonctionne ou non dépend du backend. Est-ce correct? Mon back-end est Qt4Agg. Pourriez-vous jeter un œil à mon code et me dire si quelque chose ne va pas? Voici mon code. Merci pour toute aide.

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. J'ai oublié de dire que je voudrais mettre à jour la fenêtre existante chaque fois que je trace quelque chose, au lieu d'en créer une nouvelle.

opétroch
la source
1
avez - vous essayer le mode matplotlib interactif avec plt.ion()avant plt.show()? Il doit alors être non bloquant car chaque tracé est généré dans un thread enfant.
Anzel
@Anzel Je viens de l'essayer, mais cela ne semble pas faire de différence.
opetroch
3
Comment exécutez-vous votre script? Si j'exécute votre exemple de code à partir du terminal / invite de commande, cela semble fonctionner correctement, mais je pense que j'ai eu des problèmes dans le passé en essayant de faire des choses comme celle-ci à partir de IPython QtConsole ou des IDE.
Marius
1
@Marius Aha !! Vous avez raison. En effet je l'exécute depuis la console de mon IDE (PyCharm). Lorsque vous l'exécutez à partir de l'invite cmd, plt.show (block = False), fonctionne très bien! Vais-je trop demander si je vous demande si vous avez trouvé une idée / solution à cela? Merci beaucoup!
opetroch
Je ne sais pas vraiment désolé. Je ne comprends pas vraiment les détails de la façon dont matplotlib interagit avec la console, donc je passe généralement à une exécution à partir de l'invite de commande si je dois faire ce genre de choses avec matplotlib.
Marius

Réponses:

167

J'ai passé un long moment à chercher des solutions et j'ai trouvé cette réponse .

Il semble que, pour obtenir ce que vous (et moi) voulons, vous avez besoin de la combinaison de plt.ion(), plt.show()(pas avec block=False) et, surtout, plt.pause(.001)(ou à tout moment que vous voulez). La pause est nécessaire car les événements GUI se produisent pendant que le code principal est en veille, y compris le dessin. Il est possible que cela soit implémenté en récupérant le temps d'un thread endormi, alors peut-être que les IDE gâchent cela - je ne sais pas.

Voici une implémentation qui fonctionne pour moi sur 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
la source
3
Votre réponse m'a beaucoup aidé à résoudre un problème similaire que j'avais. Auparavant, j'avais plt.drawsuivi plt.show(block = False)mais cela a cessé de fonctionner: la figure ne répond pas, la fermeture a planté iPython. Ma solution consistait à supprimer chaque instance de plt.draw()et à la remplacer par plt.pause(0.001). Au lieu de l'avoir suivi de plt.show(block = False)comme plt.drawavant, il était précédé de plt.ion()et plt.show(). J'en ai maintenant un MatplotlibDeprecationWarningmais cela m'a permis de tracer mes chiffres, donc je suis content de cette solution.
blue_chip
3
Notez que dans python 2.7, vous devez utiliser raw_inputnot input. Voir ici
Chris
Solution de contournement vraiment utile lorsque l'approche "animée" réactive n'est pas possible! Quelqu'un sait comment se débarrasser de l'avertissement d'obsolescence?
Frederic Fortier
Quelqu'un peut-il me dire pourquoi j'obtiens une invite de commande gelée lorsque j'essaye d'ajouter plt.ion avant plt.show?
Gabriel Augusto
@GabrielAugusto Je ne suis pas sûr de ce qui pourrait causer cela, et je ne suis pas sûr de ce que vous voulez dire. Je viens de tester cet exemple en Python 3.6 et cela fonctionne toujours. Si vous avez utilisé le même modèle et qu'il se fige, il se peut qu'il y ait un problème avec votre installation. Vous devez d'abord vérifier si le traçage normal fonctionne. Si vous avez essayé quelque chose de différent, il n'y a pas grand chose à faire à ce sujet dans les commentaires. Dans les deux cas, vous pourriez envisager de poser une question distincte.
krs013
23

Une astuce simple qui fonctionne pour moi est la suivante:

  1. Utilisez l' argument block = False dans show: plt.show (block = False)
  2. Utilisez un autre plt.show () à la fin du script .py.

Exemple :

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

Remarque : plt.show()est la dernière ligne de mon script.

seralouk
la source
8
Cela produit (pour moi, sous Linux, Anaconda, Python 2.7, backend par défaut) une fenêtre vide qui reste vide jusqu'à la toute fin de l'exécution, quand elle est enfin remplie. Inutile pour mettre à jour un tracé en cours d'exécution. :-(
sh37211
@ sh37211 Je ne sais pas quel est votre objectif. Dans certains cas où vous essayez de tracer quelque chose mais après la commande plot, vous avez d'autres commandes, cela est utile car cela vous permet de tracer et d'exécuter les autres commandes. Voir cet article pour en savoir plus: stackoverflow.com/questions/458209/… . Si vous souhaitez mettre à jour un tracé, il devrait en être autrement.
seralouk
17

Vous pouvez éviter de bloquer l'exécution en écrivant le tracé dans un tableau, puis en affichant le tableau dans un thread différent. Voici un exemple de génération et d'affichage simultanés de tracés à l'aide de 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()

Résultat:

Animation sinusoïdale

Clause de non-responsabilité: je suis le mainteneur de pyformulas.

Référence: Matplotlib: enregistrer le tracé dans le tableau numpy

Image par défaut
la source
9

Beaucoup de ces réponses sont super gonflées et d'après ce que je peux trouver, la réponse n'est pas si difficile à comprendre.

Vous pouvez utiliser plt.ion()si vous le souhaitez, mais j'ai trouvé l'utilisation plt.draw()tout aussi efficace

Pour mon projet spécifique, je trace des images, mais vous pouvez utiliser plot()ou scatter()ou quoi que ce soit à la place de figimage(), cela n'a pas d'importance.

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

Ou

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

Si vous utilisez un chiffre réel.
J'ai utilisé les réponses de @ krs013 et de @Default Picture
pour comprendre cela.

iggy12345
la source
3

Tracé en direct

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 quantité de données est trop importante, vous pouvez réduire le taux de mise à jour avec un simple compteur

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

Maintenir le tracé après la sortie du programme

C'était mon problème réel pour lequel je ne trouvais pas de réponse satisfaisante, je voulais un tracé qui ne se fermait pas une fois le script terminé (comme MATLAB),

Si vous y réfléchissez, une fois le script terminé, le programme se termine et il n'y a pas de moyen logique de tenir le tracé de cette façon, il y a donc deux options

  1. bloquer la sortie du script (c'est plt.show () et pas ce que je veux)
  2. exécuter l'intrigue sur un thread séparé (trop compliqué)

ce n'était pas satisfaisant pour moi alors j'ai trouvé une autre solution en dehors de la boîte

SaveToFile et View dans un visualiseur externe

Pour cela, l'enregistrement et la visualisation doivent être à la fois rapides et le spectateur ne doit pas verrouiller le fichier et doit mettre à jour le contenu automatiquement

Sélection du format pour l'enregistrement

les formats vectoriels sont à la fois petits et rapides

  • SVG est bon mais je ne trouve pas de bon visualiseur pour cela, sauf le navigateur Web qui par défaut a besoin d'une actualisation manuelle
  • Le PDF peut prendre en charge les formats vectoriels et il existe des visionneuses légères qui prennent en charge la mise à jour en direct

Visionneuse rapide et légère avec mise à jour en direct

Pour PDF, il existe plusieurs bonnes options

  • Sous Windows, j'utilise SumatraPDF qui est gratuit, rapide et léger (n'utilise que 1,8 Mo de RAM pour mon cas)

  • Sous Linux, il existe plusieurs options telles que Evince (GNOME) et Ocular (KDE)

Exemple de code et résultats

Exemple de code pour la sortie du tracé dans un fichier

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

après la première exécution, ouvrez le fichier de sortie dans l'un des visualiseurs mentionnés ci-dessus et profitez-en.

Voici une capture d'écran de VSCode aux côtés de SumatraPDF, également le processus est assez rapide pour obtenir un taux de mise à jour semi-live (je peux me rapprocher de 10Hz sur ma configuration juste utiliser time.sleep()entre les intervalles) pyPlot, non bloquant

Ali80
la source
2

La réponse d'Iggy a été la plus simple à suivre pour moi, mais j'ai eu l'erreur suivante en exécutant une subplotcommande ultérieure qui n'était pas là lorsque je faisais juste show:

MatplotlibDeprecationAvertissement: L'ajout d'un axe en utilisant les mêmes arguments qu'un axe précédent réutilise actuellement l'instance précédente. Dans une version future, une nouvelle instance sera toujours créée et renvoyée. Pendant ce temps, cet avertissement peut être supprimé et le comportement futur assuré, en passant une étiquette unique à chaque instance d'axes.

Afin d'éviter cette erreur, il est utile de fermer (ou d' effacer ) le tracé une fois que l'utilisateur a cliqué sur Entrée.

Voici le code qui a fonctionné pour moi:

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
la source
0

Le package Python drawnow permet de mettre à jour un tracé en temps réel de manière non bloquante.
Il fonctionne également avec une webcam et OpenCV par exemple pour tracer des mesures pour chaque image.
Voir l'article original .

Ismael EL ATIFI
la source