【问题标题】:How to wait until matplotlib animation ends?如何等到 matplotlib 动画结束?
【发布时间】:2016-03-29 16:36:48
【问题描述】:

考虑以下直接取自 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()

这在我的系统上运行良好。现在,尝试将以下代码附加到上面的代码中:

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

会发生什么是程序冻结。显然,Matplotlib 的“动画”类在单独的线程中运行动画。所以我有以下两个问题:

1)如果进程在单独的线程中运行,为什么会被后续循环干扰?

2) 如何让 python 等到动画结束?

【问题讨论】:

  • 我在这里看不到重复,最多只是一些远的提示。您能否解释一下您引用的主题中我的问题的答案在哪里?

标签: python multithreading animation matplotlib


【解决方案1】:

对我来说,复制到 ipython 中按预期工作(动画先播放,然后是无限循环),但是在运行脚本时它会冻结。

1) 我不确定 cpython 如何处理多线程,尤其是与特定的 matplotlib 后端结合使用时,但它似乎在这里失败了。一种可能性是通过使用

来明确说明您希望如何使用线程
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()

它定义了两个线程并设置一个用于动画,一个用于循环。

2) 如@Mitesh Shah 建议的那样,要解决此问题,您可以使用plt.show(block=True)。对我来说,脚本然后按照预期的动画表现,然后循环。完整代码:

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

更新:替代方法是简单地使用交互模式,

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)

【讨论】:

  • 感谢您回答我。我已经在我的 leptop 上尝试了您的第二个解决方案,但它不起作用:循环立即执行,并且图形冻结。关于您的第一个解决方案,我不明白(“工人”功能在哪里以及如何使用?)
  • Strange 2) 不起作用,可能是因为后端或matplotlib 的版本不同(我使用1.4.3Qt4Agg 后端)以Agg 后端为例, 2) 的行为也如你为我描述的那样。也许尝试更改后端(plt.switch_backend("Qt4Agg")。第一个解决方案使用多线程,定义两个线程,每个线程调用根据其编号分配任务的工作人员。参见此处的示例(pymotw.com/2/threading
  • 使用 Qt4Agg 后端,动画实际发生,但在循环之后执行(使用 for 循环和循环内的 time.sleep(1) 来可视化此效果)。注意:即使动画与循环并行执行(这对我仍然有用),这也不是问题的答案,因为我问了如何阻止程序在动画之后执行,直到它已结束。
  • 在动画结束前,plt.show() 应该自动阻止。这对我来说很难进一步帮助您的特定实现/后端。以我的经验,matplotlib 中的动画与脚本中的其他任何东西都不能很好地配合,除了简单的例子。另一种选择是避免动画并使用plt.ion() 的交互模式,我将添加一个示例。请注意,您需要暂停以便重绘以更新图形,这可能就是 sleep 适合您的原因。
  • 是的,当然,最后一个解决方案有效。它根本不使用 Animation 类,而是从崩溃中构建自己的动画。我有最后一个问题:如果在上面的示例中没有输入循环,则动画会在我的系统上无休止地运行。那么,你怎么可能在上面的第二个解决方案中结束动画并在 show(block=True) 之后传递到循环?
【解决方案2】:

感谢 Ed Smith 和 MiteshNinja 的帮助,我终于成功地找到了一种可靠的方法,它不仅适用于 Ipython 控制台,而且适用于 Python 控制台和命令行。此外,它允许对动画过程进行完全控制。代码是不言自明的。

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

【讨论】:

    【解决方案3】:

    我们可以在单独的线程中运行动画功能。然后启动那个线程。创建新线程后,将继续执行。
    然后我们使用p.join() 等待我们之前创建的线程完成执行。一旦执行完成(或由于某种原因终止),代码将继续执行。

    matplotlib 在交互式 Python shell 和系统命令行 shell 中的工作方式也不同,下面的代码应该适用于这两种情况:

    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
    

    我希望这会有所帮助!你可以在这里找到更多信息:Is there a way to detach matplotlib plots so that the computation can continue?

    干杯! :)

    【讨论】:

    • 我已经在我的小型机(Windows 7)中尝试过您的代码。循环被执行,但影片在循环结束前冻结,因此看不到动画。但更令我惊讶的是命令 p.join() 停止执行动画,而根据文档,它应该停止执行后续代码,直到线程结束。你能解释一下吗?
    • @MikeTeX 请问你是如何运行脚本的? iPython/cmd/idle 等?看看这个,它可能会有所帮助:matplotlib.org/users/shell.html
    • 我正在从带有 anaconda 的 spyder 2.3.8(最新版本)的 IPython 控制台运行脚本。我会阅读您提供的文档。
    • 我尝试运行此代码,但我的动画从未完成,并且程序挂在 p.join() 上。我的意思是,动画结束了,但我猜多处理模块仍然认为线程正在运行并且连接永远不会发生。怎么办?
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-01-30
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多