Рассмотрим следующий код, непосредственно взятый из документации 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)
Что происходит, так это то, что программа зависает. По-видимому, класс "Animation" Matplotlib запускает анимацию в отдельном потоке. Поэтому у меня есть следующие два вопроса:
1) Если процесс запускается в отдельном потоке, почему он нарушен последующим циклом?
2) Как сказать python подождать, пока анимация не закончится?
Ответ 1
Благодаря помощи 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()
Ответ 2
Для меня копирование в ipython работает как ожидалось (анимация сначала воспроизводит бесконечный цикл), но при запуске script он зависает.
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)
. Для меня script затем ведет себя как и ожидалось с анимацией, а затем цикл. Полный код:
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: альтернатива - просто использовать интерактивный режим,
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)
Ответ 3
Мы можем запустить функцию анимации в отдельном потоке. Затем запустите этот поток. После создания нового потока выполнение будет продолжено.
Затем мы используем p.join()
, чтобы подождать, пока наш ранее созданный поток завершит выполнение. Как только выполнение закончится (или по какой-то причине прекратится), код продолжит работу дальше.
Также matplotlib работает по-разному в оболочках Interactive Python и оболочках командной строки, нижеприведенный код должен работать для обоих этих сценариев:
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
Надеюсь, это помогло! Вы можете найти более подробную информацию здесь: Есть ли способ отсоединить графики matplotlib, чтобы можно было продолжить вычисление?
Ура!:)