Existe-t-il un moyen de détacher les tracés matplotlib pour que le calcul puisse continuer?


258

Après ces instructions dans l'interpréteur Python, on obtient une fenêtre avec un tracé:

from matplotlib.pyplot import *
plot([1,2,3])
show()
# other code

Malheureusement, je ne sais pas comment continuer à explorer de manière interactive le chiffre créé par show()pendant que le programme effectue d'autres calculs.

Est-ce possible du tout? Parfois, les calculs sont longs et il serait utile qu'ils se poursuivent lors de l'examen des résultats intermédiaires.


5
Je ne peux pas confirmer que la solution sélectionnée de nosklo le 16:52 fonctionne. Pour moi, draw n'ouvre pas de fenêtre pour afficher l'intrigue, seul le show de blocage à la fin affiche la solution. Cependant, sa réponse de 17h00 est correcte. L'activation du mode interactif via ion()résout le problème.
H. Brandsmeier

si vous êtes un programmeur avancé, vous pouvez utiliser os.fork()mais gardez à l'esprit que l'utilisation os.fork()peut être délicate car vous créez un nouveau processus en copiant l'ancien processus.
Trevor Boyd Smith

Réponses:


214

Utilisez matplotlibles appels qui ne bloquent pas:

En utilisant draw():

from matplotlib.pyplot import plot, draw, show
plot([1,2,3])
draw()
print 'continue computation'

# at the end call show to ensure window won't close.
show()

Utilisation du mode interactif:

from matplotlib.pyplot import plot, ion, show
ion() # enables interactive mode
plot([1,2,3]) # result shows immediatelly (implicit draw())

print 'continue computation'

# at the end call show to ensure window won't close.
show()

28
Avec matplotlib 0.98.3, la bonne importation provient de matplotlib.pyplot import plot, draw, show
meteore

112
draw()ne fonctionne pas pour moi, il n'ouvre aucune fenêtre. Cependant, utiliser show(block=False)au lieu de draw()semble faire l'affaire dans matplotlib 1.1.
rumpel

4
@nosklo, avez-vous vu? Vous l' avez fait dans un python tutoriel
Jan

4
@noskolo et si j'ai plusieurs figures, comment tracer et montrer Fig1 tout en continuant le fond pour continuer? J'aimerais que cette figure soit ouverte jusqu'à la prochaine figure générée, donc à la fin j'ai toutes les figues ouvertes et le code est terminé. Avec votre solution actuelle, cela me fait attendre de fermer Fig1, puis le code continue. Merci!!
Physiker

9
draw()n'a pas fonctionné pour moi non plus, seulement pause(0.001): stackoverflow.com/questions/28269157/…
NumesSanguis

133

Utilisez le mot-clé «block» pour remplacer le comportement de blocage, par exemple

from matplotlib.pyplot import show, plot

plot(1)  
show(block=False)

# your code

pour continuer votre code.


17
mais cela fermera la fenêtre de tracé immédiatement, ne gardera pas le tracé ouvert.
LWZ

8
Oui, c'est vrai si vous appelez votre script à partir de la ligne de commande. Si vous êtes dans le shell Ipython, la fenêtre ne sera pas fermée.
Jan

1
vérifiez la réponse de @Nico pour une astuce qui laissera la fenêtre ouverte dans le cas général.
Jan

2
Pour moi, cela ne ferme pas la fenêtre immédiatement, uniquement lorsque le script est terminé (vous pouvez donc bloquer manuellement à la fin du script si vous souhaitez qu'il reste ouvert).
luator

Oui, les fenêtres non bloquées se fermeront à la fin du script . Vous pouvez soit (a) autoriser le blocage sur votre dernier tracé, soit (b) ne pas quitter le script (peut-être demander une entrée: "appuyez sur <Entrée> pour quitter le tracé" ou quelque chose comme ça).
Daniel Goldfarb

29

Il est préférable de toujours vérifier auprès de la bibliothèque que vous utilisez si elle prend en charge l'utilisation de manière non bloquante .

Mais si vous voulez une solution plus générique, ou s'il n'y a pas d'autre moyen, vous pouvez exécuter tout ce qui bloque dans un processus séparé en utilisant le multprocessingmodule inclus dans python. Le calcul se poursuivra:

from multiprocessing import Process
from matplotlib.pyplot import plot, show

def plot_graph(*args):
    for data in args:
        plot(data)
    show()

p = Process(target=plot_graph, args=([1, 2, 3],))
p.start()

print 'yay'
print 'computation continues...'
print 'that rocks.'

print 'Now lets wait for the graph be closed to continue...:'
p.join()

Cela a pour frais généraux de lancer un nouveau processus et est parfois plus difficile à déboguer sur des scénarios complexes, donc je préfère l'autre solution (en utilisant matplotlibles appels d'API non bloquants de )


Merci! Comme je n'ai pas encore Python 2.6 sur mon système, j'ai utilisé le threading.Thread comme substitut de Process. J'ai observé que les instructions d'impression suivantes devenaient insupportablement lentes (troisième impression, j'ai émis KeyboardInterrupt après 1 minute d'attente). Est-ce un effet de l'utilisation du threading au lieu du multitraitement?
météore le

@meteore: Oui, le filetage est nul. Vous pouvez toujours obtenir un multitraitement pour python <2.6 à partir d'ici: pyprocessing.berlios.de
nosklo

C'est absolument excellent. Avez-vous une idée pourquoi les instructions d'impression ne sont pas exécutées en mode Emacs (mode python) jusqu'à la fermeture de la fenêtre de traçage?
météore le

Dans Ubuntu 8.10 (Intrepid), le paquet (pour python <2.6) s'appelle python-processing et vous l'importez avec 'import processing'
meteore

1
Vous n'avez pas manqué le if __name__ == '__main__':?
Wernight

25

Essayer

import matplotlib.pyplot as plt
plt.plot([1,2,3])
plt.show(block=False)
# other code
# [...]

# Put
plt.show()
# at the very end of your script to make sure Python doesn't bail out
# before you finished examining.

La show()documentation dit:

En mode non interactif, affichez toutes les figures et bloquez jusqu'à ce que les figures soient fermées; en mode interactif, il n'a aucun effet à moins que les chiffres aient été créés avant le passage du mode non interactif au mode interactif (non recommandé). Dans ce cas, il affiche les chiffres mais ne bloque pas.

Un seul argument de mot-clé expérimental, block, peut être défini sur True ou False pour remplacer le comportement de blocage décrit ci-dessus.


pourquoi ne pas utiliser draw (); [.autre code]; spectacle() ? Pour autant que je sache, block = False a été déprécié
Bogdan

Pourquoi pensez-vous qu'il est obsolète? Je le vois documenté ici .
Nico Schlömer

11

IMPORTANT : Juste pour clarifier quelque chose. Je suppose que les commandes sont à l'intérieur d'un .pyscript et le script est appelé en utilisant par exemple à python script.pypartir de la console.

Un moyen simple qui fonctionne pour moi est:

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

Exemple de script.py fichier:

plt.imshow(*something*)                                                               
plt.colorbar()                                                                             
plt.xlabel("true ")                                                                   
plt.ylabel("predicted ")                                                              
plt.title(" the matrix")  

# Add block = False                                           
plt.show(block = False)

################################
# OTHER CALCULATIONS AND CODE HERE ! ! !
################################

# the next command is the last line of my script
plt.show()



8

Dans mon cas, je voulais que plusieurs fenêtres s'affichent pendant leur calcul. Pour référence, voici la voie:

from matplotlib.pyplot import draw, figure, show
f1, f2 = figure(), figure()
af1 = f1.add_subplot(111)
af2 = f2.add_subplot(111)
af1.plot([1,2,3])
af2.plot([6,5,4])
draw() 
print 'continuing computation'
show()

PS. Un guide très utile pour l'interface OO de matplotlib .


6

Eh bien, j'ai eu beaucoup de mal à comprendre les commandes non bloquantes ... Mais finalement, j'ai réussi à retravailler l'exemple " Cookbook / Matplotlib / Animations - Animation des éléments de tracé sélectionnés ", donc cela fonctionne avec les threads ( et transmet les données entre les threads soit via des variables globales ou via un multiprocessusPipe ) sur Python 2.6.5 sur Ubuntu 10.04.

Le script peut être trouvé ici: Animating_selected_plot_elements-thread.py - sinon collé ci-dessous ( avec moins de commentaires ) pour référence:

import sys
import gtk, gobject
import matplotlib
matplotlib.use('GTKAgg')
import pylab as p
import numpy as nx 
import time

import threading 



ax = p.subplot(111)
canvas = ax.figure.canvas

# for profiling
tstart = time.time()

# create the initial line
x = nx.arange(0,2*nx.pi,0.01)
line, = ax.plot(x, nx.sin(x), animated=True)

# save the clean slate background -- everything but the animated line
# is drawn and saved in the pixel buffer background
background = canvas.copy_from_bbox(ax.bbox)


# just a plain global var to pass data (from main, to plot update thread)
global mypass

# http://docs.python.org/library/multiprocessing.html#pipes-and-queues
from multiprocessing import Pipe
global pipe1main, pipe1upd
pipe1main, pipe1upd = Pipe()


# the kind of processing we might want to do in a main() function,
# will now be done in a "main thread" - so it can run in
# parallel with gobject.idle_add(update_line)
def threadMainTest():
    global mypass
    global runthread
    global pipe1main

    print "tt"

    interncount = 1

    while runthread: 
        mypass += 1
        if mypass > 100: # start "speeding up" animation, only after 100 counts have passed
            interncount *= 1.03
        pipe1main.send(interncount)
        time.sleep(0.01)
    return


# main plot / GUI update
def update_line(*args):
    global mypass
    global t0
    global runthread
    global pipe1upd

    if not runthread:
        return False 

    if pipe1upd.poll(): # check first if there is anything to receive
        myinterncount = pipe1upd.recv()

    update_line.cnt = mypass

    # restore the clean slate background
    canvas.restore_region(background)
    # update the data
    line.set_ydata(nx.sin(x+(update_line.cnt+myinterncount)/10.0))
    # just draw the animated artist
    ax.draw_artist(line)
    # just redraw the axes rectangle
    canvas.blit(ax.bbox)

    if update_line.cnt>=500:
        # print the timing info and quit
        print 'FPS:' , update_line.cnt/(time.time()-tstart)

        runthread=0
        t0.join(1)   
        print "exiting"
        sys.exit(0)

    return True



global runthread

update_line.cnt = 0
mypass = 0

runthread=1

gobject.idle_add(update_line)

global t0
t0 = threading.Thread(target=threadMainTest)
t0.start() 

# start the graphics update thread
p.show()

print "out" # will never print - show() blocks indefinitely! 

J'espère que cela aide quelqu'un,
Cheers!


5

Dans de nombreux cas, il est plus pratique d'enregistrer l'image sous forme de fichier .png sur le disque dur. Voici pourquoi:

Avantages:

  • Vous pouvez l'ouvrir, le consulter et le fermer à tout moment du processus. Ceci est particulièrement pratique lorsque votre application est en cours d'exécution depuis longtemps.
  • Rien n'apparaît et vous n'êtes pas obligé d'ouvrir les fenêtres. Ceci est particulièrement pratique lorsque vous avez affaire à de nombreux chiffres.
  • Votre image est accessible pour référence ultérieure et n'est pas perdue lors de la fermeture de la fenêtre de la figure.

Inconvénient:

  • La seule chose à laquelle je peux penser, c'est que vous devrez aller chercher le dossier et ouvrir l'image vous-même.

Si vous essayez de générer beaucoup d'images, je suis tout à fait d'accord.
fantastique

6
Les png de retrait ne sont pas interactifs: \
Inverse

5

Si vous travaillez dans la console, c'est- IPythonà- dire que vous pouvez utiliser plt.show(block=False)comme indiqué dans les autres réponses. Mais si vous êtes paresseux, vous pouvez simplement taper:

plt.show(0)

Ce sera la même chose.


5

J'ai également dû ajouter plt.pause(0.001)à mon code pour le faire vraiment fonctionner dans une boucle for (sinon il ne montrerait que le premier et le dernier tracé):

import matplotlib.pyplot as plt

plt.scatter([0], [1])
plt.draw()
plt.show(block=False)

for i in range(10):
    plt.scatter([i], [i+1])
    plt.draw()
    plt.pause(0.001)

Cela a fonctionné pour moi avec matplotlib3 sur macOS. Génial!
Jerry Ma

4

Sur mon système, show () ne bloque pas, même si je voulais que le script attende que l'utilisateur interagisse avec le graphique (et recueille des données à l'aide des rappels 'pick_event') avant de continuer.

Afin de bloquer l'exécution jusqu'à la fermeture de la fenêtre de traçage, j'ai utilisé ce qui suit:

fig = plt.figure()
ax = fig.add_subplot(1,1,1)
ax.plot(x,y)

# set processing to continue when window closed
def onclose(event):
    fig.canvas.stop_event_loop()
fig.canvas.mpl_connect('close_event', onclose)

fig.show() # this call does not block on my system
fig.canvas.start_event_loop_default() # block here until window closed

# continue with further processing, perhaps using result from callbacks

Notez cependant que canvas.start_event_loop_default () a généré l'avertissement suivant:

C:\Python26\lib\site-packages\matplotlib\backend_bases.py:2051: DeprecationWarning: Using default event loop until function specific to this GUI is implemented
  warnings.warn(str,DeprecationWarning)

bien que le script soit toujours exécuté.


Je vous remercie! Spyder importe -pylab au démarrage, ce qui est généralement utile, mais signifie que show () ne se bloquera pas lorsque ioff () - cela vous permet de corriger ce problème!
perdu

3

Je voulais également que mes tracés s'affichent, exécutent le reste du code (puis continuent à s'afficher) même s'il y a une erreur (j'utilise parfois des tracés pour le débogage). J'ai codé ce petit hack pour que tous les complots à l'intérieur de cette withdéclaration se comportent comme tels.

C'est probablement un peu trop non standard et déconseillé pour le code de production. Il y a probablement beaucoup de "gotchas" cachés dans ce code.

from contextlib import contextmanager

@contextmanager
def keep_plots_open(keep_show_open_on_exit=True, even_when_error=True):
    '''
    To continue excecuting code when plt.show() is called
    and keep the plot on displaying before this contex manager exits
    (even if an error caused the exit).
    '''
    import matplotlib.pyplot
    show_original = matplotlib.pyplot.show
    def show_replacement(*args, **kwargs):
        kwargs['block'] = False
        show_original(*args, **kwargs)
    matplotlib.pyplot.show = show_replacement

    pylab_exists = True
    try:
        import pylab
    except ImportError: 
        pylab_exists = False
    if pylab_exists:
        pylab.show = show_replacement

    try:
        yield
    except Exception, err:
        if keep_show_open_on_exit and even_when_error:
            print "*********************************************"
            print "Error early edition while waiting for show():" 
            print "*********************************************"
            import traceback
            print traceback.format_exc()
            show_original()
            print "*********************************************"
            raise
    finally:
        matplotlib.pyplot.show = show_original
        if pylab_exists:
            pylab.show = show_original
    if keep_show_open_on_exit:
        show_original()

# ***********************
# Running example
# ***********************
import pylab as pl
import time
if __name__ == '__main__':
    with keep_plots_open():
        pl.figure('a')
        pl.plot([1,2,3], [4,5,6])     
        pl.plot([3,2,1], [4,5,6])
        pl.show()

        pl.figure('b')
        pl.plot([1,2,3], [4,5,6])
        pl.show()

        time.sleep(1)
        print '...'
        time.sleep(1)
        print '...'
        time.sleep(1)
        print '...'
        this_will_surely_cause_an_error

Si / quand j'implémente un bon "garder les tracés ouverts (même si une erreur se produit) et permettre à de nouveaux tracés d'être affichés", je voudrais que le script se ferme correctement si aucune interférence utilisateur ne le dit autrement (à des fins d'exécution par lots).

Je peux utiliser quelque chose comme une question de délai d'attente "Fin du script! \ NAppuyez sur p si vous souhaitez suspendre la sortie de traçage (vous disposez de 5 secondes):" sur /programming/26704840/corner -cases-for-my-wait-for-user-input-interruption-implementation .


2
plt.figure(1)
plt.imshow(your_first_image)

plt.figure(2)
plt.imshow(your_second_image)

plt.show(block=False) # That's important 

raw_input("Press ENTER to exist") # Useful when you run your Python script from the terminal and you want to hold the running to see your figures until you press Enter

16
Comment appuyer sur Entrée avant d'exister?
grovina

2

Le PO pose des questions sur le détachement des matplotlibparcelles. La plupart des réponses supposent l'exécution de commandes à partir d'un interpréteur python. Le cas d'utilisation présenté ici est ma préférence pour tester le code dans un terminal (par exemple bash) où unfile.py est exécuté et vous voulez que le ou les tracés apparaissent mais que le script python se termine et revienne à une invite de commande.

Ce fichier autonome permet multiprocessingde lancer un processus distinct de traçage des données avec matplotlib. Le thread principal se termine en utilisant le os._exit(1)mentionné dans ce post. Les os._exit()forces se retirent mais quittent lematplotlib processus enfant vivant et réactif jusqu'à la fermeture de la fenêtre de tracé. C'est un processus entièrement distinct.

Cette approche est un peu comme une session de développement Matlab avec des fenêtres de figures qui proposent une invite de commande réactive. Avec cette approche, vous avez perdu tout contact avec le processus de la fenêtre de figure, mais c'est correct pour le développement et le débogage. Fermez simplement la fenêtre et continuez à tester.

multiprocessingest conçu pour l'exécution de code uniquement en python, ce qui le rend peut-être mieux adapté que subprocess. multiprocessingest multi-plateforme donc cela devrait bien fonctionner sous Windows ou Mac avec peu ou pas d'ajustement. Il n'est pas nécessaire de vérifier le système d'exploitation sous-jacent. Cela a été testé sur Linux, Ubuntu 18.04LTS.

#!/usr/bin/python3

import time
import multiprocessing
import os

def plot_graph(data):
    from matplotlib.pyplot import plot, draw, show
    print("entered plot_graph()")
    plot(data)
    show() # this will block and remain a viable process as long as the figure window is open
    print("exiting plot_graph() process")

if __name__ == "__main__":
    print("starting __main__")
    multiprocessing.Process(target=plot_graph, args=([1, 2, 3],)).start()
    time.sleep(5)
    print("exiting main")
    os._exit(0) # this exits immediately with no cleanup or buffer flushing

L'exécution file.pyfait apparaître une fenêtre de chiffre, puis se __main__ferme, mais la fenêtre de chiffre multiprocessing+ matplotlibreste sensible au zoom, au panoramique et à d'autres boutons car c'est un processus indépendant.

Vérifiez les processus à l'invite de commande bash avec:

ps ax|grep -v grep |grep file.py


J'essayais d'utiliser votre solution mais cela ne semble pas fonctionner pour moi et j'essaie de comprendre pourquoi. Je n'exécute pas le code via le terminal mais depuis Pycharm IDE si cela fait une différence, bien que cela ne devrait pas.
ttsesm

1
ok, ce qui a finalement fonctionné pour moi a été de définir le processus enfant .daemon=Falsecomme décrit ici stackoverflow.com/a/49607287/1476932 Cependant, sys.exit()je n'ai pas terminé le processus parent comme décrit ici jusqu'à ce que je ferme la fenêtre enfant. D'un autre côté, l'utilisation os._exit(0)de l'exemple ci-dessus a fonctionné.
ttsesm

1

À mon avis, les réponses dans ce fil fournissent des méthodes qui ne fonctionnent pas pour tous les systèmes et dans des situations plus complexes comme les animations. Je suggère de jeter un œil à la réponse de MiKTeX dans le fil suivant, où une méthode robuste a été trouvée: Comment attendre la fin de l'animation matplotlib?


0

Si vous voulez ouvrir plusieurs figures, tout en les gardant toutes ouvertes, ce code a fonctionné pour moi:

show(block=False)
draw()

show (block = False) est obsolète et ne fonctionne plus
Bogdan

0

Sans répondre directement à la demande des PO, je publie cette solution de contournement car cela peut aider quelqu'un dans cette situation:

  • Je crée un .exe avec pyinstaller car je ne peux pas installer python là où je dois générer les tracés, j'ai donc besoin du script python pour générer le tracé, l'enregistrer au format .png, le fermer et continuer avec le suivant, implémenté comme plusieurs tracés dans une boucle ou en utilisant une fonction.

pour cet Im en utilisant:

import matplotlib.pyplot as plt
#code generating the plot in a loop or function
#saving the plot
plt.savefig(var+'_plot.png',bbox_inches='tight', dpi=250) 
#you can allways reopen the plot using
os.system(var+'_plot.png') # unfortunately .png allows no interaction.
#the following avoids plot blocking the execution while in non-interactive mode
plt.show(block=False) 
#and the following closes the plot while next iteration will generate new instance.
plt.close() 

Où "var" identifie l'intrigue dans la boucle afin qu'elle ne soit pas écrasée.


-1

Utilisez plt.show(block=False), et à la fin de votre appel de script plt.show().

Cela garantira que la fenêtre ne sera pas fermée lorsque le script sera terminé.


Voir la réponse de @ nico-schlömer
Josh Wolff
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.