2015-12-23 11 views
5

Si consideri il seguente codice direttamente preso dalla documentazione Matplotlib:Come aspettare fino al termine dell'animazione matplotlib?

import numpy as np 
import matplotlib.pyplot as plt 
import matplotlib.animation as animation 
import time # optional for testing only 
import cv2 # optional for testing only 

fig = plt.figure() 

def f(x, y): 
    return np.sin(x) + np.cos(y) 

x = np.linspace(0, 2 * np.pi, 120) 
y = np.linspace(0, 2 * np.pi, 100).reshape(-1, 1) 

im = plt.imshow(f(x, y), animated=True)  

def updatefig(*args): 
    global x, y 
    x += np.pi/15. 
    y += np.pi/20. 
    im.set_array(f(x, y)) 
    return im, 

ani = animation.FuncAnimation(fig, updatefig, interval=50, blit=True) 
plt.show() 

Questo lavoro bene sul mio sistema. Ora, provate a aggiungere il seguente pezzo di codice al codice di cui sopra:

while True: 
    #I have tried any of these 3 commands, without success: 
    pass 
    #time.sleep(1) 
    #cv2.waitKey(10) 

Quello che succede è che il programma si blocca. Apparentemente, la classe "Animazione" di Matplotlib esegue l'animazione in un thread separato. Quindi ho le seguenti 2 domande:

1) Se il processo viene eseguito in un thread separato, perché viene disturbato dal ciclo successivo?

2) Come dire a python di attendere fino al termine dell'animazione?

+0

Eventuali duplicati: http://stackoverflow.com/questions/458209/is-there-a-way-to-detach-matplotlib-plots-so-that-the-computation-can-continue#458295 – oxalorg

+0

Qui non vedo duplicati, al massimo alcuni suggerimenti. Potresti spiegare dov'è la risposta alle mie domande nella discussione che hai citato? – MikeTeX

risposta

2

Grazie all'aiuto di Ed Smith e MiteshNinja, ho finalmente riuscito a trovare un metodo robusto che funziona non solo con la console Ipython, ma anche con la console Python e la riga di comando. Inoltre, consente il controllo totale sul processo di animazione. Il codice è auto esplicativo.

import numpy as np 
import matplotlib 
import matplotlib.pyplot as plt 
from multiprocessing import Process 
import time # optional for testing only 
import matplotlib.animation as animation 

# A. First we define some useful tools: 

def wait_fig(): 
    # Block the execution of the code until the figure is closed. 
    # This works even with multiprocessing. 
    if matplotlib.pyplot.isinteractive(): 
     matplotlib.pyplot.ioff() # this is necessary in mutliprocessing 
     matplotlib.pyplot.show(block=True) 
     matplotlib.pyplot.ion() # restitute the interractive state 
    else: 
     matplotlib.pyplot.show(block=True) 

    return  


def wait_anim(anim_flag, refresh_rate = 0.1):  
    #This will be used in synergy with the animation class in the example 
    #below, whenever the user want the figure to close automatically just 
    #after the animation has ended. 
    #Note: this function uses the controversial event_loop of Matplotlib, but 
    #I see no other way to obtain the desired result. 

    while anim_flag[0]: #next code extracted from plt.pause(...) 
     backend = plt.rcParams['backend'] 
     if backend in plt._interactive_bk: 
      figManager = plt._pylab_helpers.Gcf.get_active() 
      if figManager is not None: 
       figManager.canvas.start_event_loop(refresh_rate) 


def draw_fig(fig = None):  
    #Draw the artists of a figure immediately. 
    #Note: if you are using this function inside a loop, it should be less time 
    #consuming to set the interactive mode "on" using matplotlib.pyplot.ion() 
    #before the loop, event if restituting the previous state after the loop. 

    if matplotlib.pyplot.isinteractive(): 
     if fig is None: 
      matplotlib.pyplot.draw() 
     else: 
      fig.canvas.draw()    
    else: 
     matplotlib.pyplot.ion() 
     if fig is None: 
      matplotlib.pyplot.draw() 
     else: 
      fig.canvas.draw() 
     matplotlib.pyplot.ioff() # restitute the interactive state 

    matplotlib.pyplot.show(block=False) 
    return 


def pause_anim(t): #This is taken from plt.pause(...), but without unnecessary 
        #stuff. Note that the time module should be previously imported. 
        #Again, this use the controversial event_loop of Matplotlib. 
    backend = matplotlib.pyplot.rcParams['backend'] 
    if backend in matplotlib.pyplot._interactive_bk: 
     figManager = matplotlib.pyplot._pylab_helpers.Gcf.get_active() 
     if figManager is not None: 
      figManager.canvas.start_event_loop(t) 
      return 
    else: time.sleep(t) 


#-------------------------- 

# B. Now come the particular functions that will do the job. 
def f(x, y): 
    return np.sin(x) + np.cos(y) 


def plot_graph(): 
    fig = plt.figure() 
    x = np.linspace(0, 2 * np.pi, 120) 
    y = np.linspace(0, 2 * np.pi, 100).reshape(-1, 1) 
    im = fig.gca().imshow(f(x, y))  
    draw_fig(fig) 
    n_frames = 50 

    #==============================================  
    #First method - direct animation: This use the start_event_loop, so is 
    #somewhat controversial according to the Matplotlib doc. 
    #Uncomment and put the "Second method" below into comments to test. 

    '''for i in range(n_frames): # n_frames iterations  
     x += np.pi/15. 
     y += np.pi/20. 
     im.set_array(f(x, y)) 
     draw_fig(fig) 
     pause_anim(0.015) # plt.pause(0.015) can also be used, but is slower 

    wait_fig() # simply suppress this command if you want the figure to close 
       # automatically just after the animation has ended  
    '''  
    #================================================ 
    #Second method: this uses the Matplotlib prefered animation class.  
    #Put the "first method" above in comments to test it. 
    def updatefig(i, fig, im, x, y, anim_flag, n_frames): 
     x = x + i * np.pi/15. 
     y = y + i * np.pi/20. 
     im.set_array(f(x, y))   

     if i == n_frames-1: 
      anim_flag[0] = False 

    anim_flag = [True]  
    animation.FuncAnimation(fig, updatefig, repeat = False, frames = n_frames, 
     interval=50, fargs = (fig, im, x, y, anim_flag, n_frames), blit=False) 
          #Unfortunately, blit=True seems to causes problems 

    wait_fig() 
    #wait_anim(anim_flag) #replace the previous command by this one if you want the 
        #figure to close automatically just after the animation 
        #has ended                 
    #================================================   
    return 

#-------------------------- 

# C. Using multiprocessing to obtain the desired effects. I believe this 
# method also works with the "threading" module, but I haven't test that. 

def main(): # it is important that ALL the code be typed inside 
      # this function, otherwise the program will do weird 
      # things with the Ipython or even the Python console. 
      # Outside of this condition, type nothing but import 
      # clauses and function/class definitions. 
    if __name__ != '__main__': return      
    p = Process(target=plot_graph) 
    p.start() 
    print('hello', flush = True) #just to have something printed here 
    p.join() # suppress this command if you want the animation be executed in 
      # parallel with the subsequent code 
    for i in range(3): # This allows to see if execution takes place after the 
         #process above, as should be the case because of p.join(). 
     print('world', flush = True) 
     time.sleep(1)   

main() 
2

Possiamo eseguire la funzione di animazione in un thread separato. Quindi inizia quel thread. Una volta creato un nuovo thread, l'esecuzione continuerà.
Quindi si utilizza p.join() per attendere che il thread precedentemente creato completi l'esecuzione. Non appena l'esecuzione è terminata (o termina per qualche motivo) il codice continuerà ulteriormente.

anche matplotlib funziona in modo diverso in gusci Interactive Python vs. shell a riga di comando del sistema, il codice qui sotto dovrebbe funzionare per entrambi questi scenari:

import numpy as np 
import matplotlib.pyplot as plt 
import matplotlib.animation as animation 
from multiprocessing import Process 
import time # optional for testing only 
#import cv2 # optional for testing only 

fig = plt.figure() 

def f(x, y): 
    return np.sin(x) + np.cos(y) 

x = np.linspace(0, 2 * np.pi, 120) 
y = np.linspace(0, 2 * np.pi, 100).reshape(-1, 1) 

im = plt.imshow(f(x, y), animated=True)  


def plot_graph(*args): 
    def updatefig(*args): 
     global x, y 
     x += np.pi/15. 
     y += np.pi/20. 
     im.set_array(f(x, y)) 
     return im, 

    ani = animation.FuncAnimation(fig, updatefig, interval=50, blit=True) 
    plt.show() 

p = Process(target=plot_graph) 
p.start() 
# Code here computes while the animation is running 
for i in range(10): 
    time.sleep(1) 
    print('Something') 

p.join() 
print("Animation is over") 
# Code here to be computed after animation is over 

Spero che questo ha aiutato! Potete trovare maggiori informazioni qui: Is there a way to detach matplotlib plots so that the computation can continue?

Cheers! :)

+0

Ho provato il tuo codice nel mio leptop (Windows 7). Il ciclo viene eseguito ma il film si blocca fino alla fine del ciclo, quindi non è possibile visualizzare animazioni. Ma la cosa più sorprendente per me è che il comando p.join() arresta l'esecuzione dell'animazione, mentre secondo il doc, dovrebbe interrompere l'esecuzione del codice successivo fino alla fine del thread. Puoi spiegarlo? – MikeTeX

+0

@MikeTeX Posso chiedere 'come' stai eseguendo lo script? iPython/cmd/idle ecc? Date un'occhiata a questo, potrebbe essere d'aiuto: http://matplotlib.org/users/shell.html – oxalorg

+0

Sto eseguendo lo script dalla console IPython di Spyder 2.3.8 (una versione recente), in arrivo con anaconda. Leggerò il documento che hai fornito. – MikeTeX

3

Per me, la copia in ipython funziona come previsto (l'animazione viene riprodotta prima del ciclo infinito) ma quando si esegue lo script si blocca.

1) Non sono sicuro di come cpython gestisca il multi-threading, specialmente se combinato con un particolare back-end matplotlib ma sembra che stia fallendo qui. Una possibilità è quella di essere espliciti su come si desidera utilizzare le discussioni, utilizzando

import numpy as np 
import matplotlib.pyplot as plt 
import matplotlib.animation as animation 
import multiprocessing as mp 


fig = plt.figure() 

def f(x, y): 
    return np.sin(x) + np.cos(y) 

x = np.linspace(0, 2 * np.pi, 120) 
y = np.linspace(0, 2 * np.pi, 100).reshape(-1, 1) 

im = plt.imshow(f(x, y), animated=True)  

def updatefig(*args): 
    global x, y 
    x += np.pi/15. 
    y += np.pi/20. 
    im.set_array(f(x, y)) 
    return im, 

#A function to set thread number 0 to animate and the rest to loop 
def worker(num): 
    if num == 0: 
     ani = animation.FuncAnimation(fig, updatefig, interval=50, blit=True) 
     plt.show() 
    else: 
     while True: 
      print("in loop") 
      pass 

    return 


# Create two threads 
jobs = [] 
for i in range(2): 
    p = mp.Process(target=worker, args=(i,)) 
    jobs.append(p) 
    p.start() 

che definisce due thread e imposta uno di lavorare su di animazione, uno a loop.

2) Per risolvere il problema, come suggerito da @Mitesh Shah, è possibile utilizzare plt.show(block=True). Per me, lo script si comporta come previsto con animazione e quindi loop. codice completo:

import numpy as np 
import matplotlib.pyplot as plt 
import matplotlib.animation as animation 

fig = plt.figure() 

def f(x, y): 
    return np.sin(x) + np.cos(y) 

x = np.linspace(0, 2 * np.pi, 120) 
y = np.linspace(0, 2 * np.pi, 100).reshape(-1, 1) 

im = plt.imshow(f(x, y), animated=True)  

def updatefig(*args): 
    global x, y 
    x += np.pi/15. 
    y += np.pi/20. 
    im.set_array(f(x, y)) 
    return im, 

ani = animation.FuncAnimation(fig, updatefig, interval=50, blit=True) 
plt.show(block=True) 

while True: 
    print("in loop") 
    pass 

UPDATE: alternativa è di usare semplicemente la modalità interattiva,

import numpy as np 
import matplotlib.pyplot as plt 
import matplotlib.animation as animation 

fig = plt.figure() 
plt.ion() 
plt.show() 

def f(x, y): 
    return np.sin(x) + np.cos(y) 

def updatefig(*args): 
    global x, y 


x = np.linspace(0, 2 * np.pi, 120) 
y = np.linspace(0, 2 * np.pi, 100).reshape(-1, 1) 
im = plt.imshow(f(x, y))  

for i in range(500): 

    x += np.pi/15. 
    y += np.pi/20. 
    im.set_array(f(x, y)) 
    plt.draw() 
    plt.pause(0.0000001) 
+0

Grazie per avermi risposto. Ho provato la tua seconda soluzione sul mio leptop e non ha funzionato: il ciclo viene immediatamente eseguito e la figura si blocca. Per quanto riguarda la tua prima soluzione, non la capisco (dove e come viene utilizzata la funzione "lavoratore"?) – MikeTeX

+0

Strana 2) non ha funzionato, potrebbe essere a causa di una differenza nel back-end o nella versione di 'matplotlib' (I'm usando '1.4.3' con il backend' Qt4Agg') con il backend 'Agg' per esempio, 2) si comporta anche come mi descrivi per me. Forse prova a cambiare il backend ('plt.switch_backend (" Qt4Agg ")'. La prima soluzione utilizza il multi-threading, definendo due thread per ogni operatore che assegna le attività in base al loro numero.Vedi esempio qui (https://pymotw.com/ 2/threading /) –

+0

Con il backend Qt4Agg, l'animazione si verifica effettivamente, ma viene eseguita dopo il ciclo (utilizzare un ciclo for e time.sleep (1) all'interno del ciclo per visualizzare questo effetto). Nota: anche se l'animazione era eseguito in parallelo con il ciclo (che sarebbe ancora utile per me), questa non sarebbe la risposta alla domanda, perché ho chiesto come bloccare l'esecuzione del programma dopo l'animazione fino alla sua conclusione. – MikeTeX