Matplotlib:单击启动动画的按钮后如何保存动画?
Matplotlib : How to save an animation after clicking on the button that launches the animation?
我做了一个小森林火灾动画。我的代码在问题的最后。
代码 forestfire.py
包含的函数将把火传播到所有 forest
。然后 forestfire_test.py
import forestfire.py
as ff
然后我可以用鼠标点击 matplotlib 显示的数组来设置 forest
。
在我提问之前,这里有一些信息:
- 没有树:
forest[i,j] = 0
- 一棵树:
forest[i,j] = 1
- 一棵着火的树:
forest[i,j] = 2
- 哈希值:
forest[i,j] = 3
基本上,forest
是大小为 n 的二维数组,由 m 组成,由 1 到0. 函数 onclick
点燃了 forest
,当 forest
仍然有树着火时,函数 spreadfire
将火蔓延。
使用函数 onclick
我可以点燃森林(如果我点击数组,树会变成红色)并且使用函数 start
我可以执行代码,感谢按钮 Start
.
现在的问题是,我第一次执行代码时它不知道 ani 是什么 (NameError: global name 'ani' is not defined
) – 这是正常的,因为我调用动画 ani
(通过尝试保存它)在我什至调用函数开始之前。但是,如果我尝试在函数 start
中保存 ani
,我会得到一个空白图——这也是正常的。
综上所述,我需要在函数 start
调用后保存动画,但我不能在函数 start
结束时保存它,否则我会得到一个空白图。有人可以告诉我应该怎么做吗?
PS : 我使用 Spyder 和 IPython 控制台 如果我的解释不够清楚,请告诉我。
forestfire_test.py
import forestfire as ff
import numpy as np
import matplotlib.pylab as plt
import matplotlib.colors as mcolors
from matplotlib import cm
from matplotlib.widgets import Button, Cursor
global forest
forest = np.random.rand(100,100)
# Colormap
greens = cm.Greens(np.linspace(0,1, num=50))
greensfill = cm.Greens(np.ones(25))
red = [(1,0,0,1)]*len(greens)
gray = [(.5,.5,.5,1)]*len(greens)
colors = np.vstack((greens, greensfill, red, gray))
mycmap = mcolors.LinearSegmentedColormap.from_list('my_colormap', colors)
# Figure
fig, ax = plt.subplots(figsize=(10,5))
fig.subplots_adjust(right=1.3)
im = ax.imshow(forest, animated=True, cmap = mycmap, interpolation="none", origin='lower', vmin=0, vmax=3.5)
ax.set_xlabel("X")
ax.set_ylabel("Y")
ax.tick_params(direction='out')
cursor = Cursor(ax, useblit=True, color='red', linewidth=1)
plt.show()
# Coordinates
def onclick(event):
x, y = int(event.xdata), int(event.ydata)
forest[y,x] = 2.
im.set_data(forest)
fig.canvas.draw_idle()
fig.canvas.mpl_connect('button_press_event', onclick)
# Start button
def start(event):
global ani
ani = ff.forestfire(forest)
button_ax = plt.axes([0.15, 0.45, 0.2, 0.1])
button = Button(button_ax, 'Start', color='lightgrey', hovercolor='grey')
button.on_clicked(start)
# Animation
ani.save("forestfire_test.mp4", writer = 'ffmpeg', fps=5, dpi=500)
forestfire.py
from random import random
import numpy as np
from numpy.random import choice
import matplotlib.pylab as plt
import matplotlib.colors as mcolors
from matplotlib import cm
import matplotlib.animation as animation
def hazard(p):
r=random()
assert p>=0 and p<=1
return r <= p
def chg(case):
if case > 1.:
return 1.
else:
return case
def spreadfire(forest):
n,m=forest.shape
c = np.copy(forest)
L = xrange(3)
for i in xrange(n):
for j in xrange(m):
if c[i,j] == 2.:
sautX, sautY = choice([0,1,40],p=[0.4999,0.4999,0.0002]), choice([0,1,40],p=[0.4999,0.4999,0.0002])
Y, X = xrange(max(0,i-1-sautY),min(n,i+2+sautY),sautY+1), xrange(max(0,j-1-sautX),min(m,j+2+sautX),sautX+1)
for y1,y2 in zip(Y,L):
for x1,x2 in zip(X,L):
if hazard(chg(c[y1,x1])):
forest[y1,x1] = 2.
return forest
def forestfire(forest):
fig, ax = plt.subplots()
movie, hashes = [], []
# Colormap
greens = cm.Greens(np.linspace(0,1, num=50))
greensfill = cm.Greens(np.ones(25))
red = [(1,0,0,1)]*len(greens)
gray = [(.5,.5,.5,1)]*len(greens)
colors = np.vstack((greens, greensfill, red, gray))
mycmap = mcolors.LinearSegmentedColormap.from_list('my_colormap', colors)
# Initialization
k = 0
firefront = 5
forest = spreadfire(forest)
c = np.copy(forest)
c[np.where(c==2.)] = 3.
hashes.append(c)
im = plt.imshow(forest, animated=True, cmap = mycmap, interpolation="none", origin='lower', vmin=0, vmax=3.5)
movie.append([im])
# Fire propagation
while np.count_nonzero(forest == 2.) != 0:
k += 1
print k
if k < firefront:
forest = spreadfire(forest)
c = np.copy(forest)
c[np.where(c==2.)] = 3.
hashes.append(c)
im = plt.imshow(forest, animated=True, cmap = mycmap, interpolation="none", origin='lower', vmin=0, vmax=3.5)
movie.append([im])
else:
forest = spreadfire(forest)
c = np.copy(forest)
c[np.where(c==2.)] = 3.
hashes.append(c)
forest[np.where(hashes[0]==3.)] = 3.
im = plt.imshow(forest, animated=True, cmap = mycmap, interpolation="none", origin='lower', vmin=0, vmax=3.5)
movie.append([im])
hashes.remove(hashes[0])
return animation.ArtistAnimation(fig, movie, blit=True, repeat_delay=100)
这里有两个问题,正如评论中提到的:
- 您需要将
ani.save
移动到 start 函数中。这是因为 ani
只有在按下开始按钮后才会被定义。
plt.show
应该只在脚本末尾调用。
这对我来说工作正常,运行 作为脚本(在 python 2.7.10,matplotlib 2.0.2 上)在点击开始按钮后,动画是另存为mp4文件到当前目录。
除此之外,我需要将帧率设置为 6 或更高,如 previous question 中所述。
我做了一个小森林火灾动画。我的代码在问题的最后。
代码 forestfire.py
包含的函数将把火传播到所有 forest
。然后 forestfire_test.py
import forestfire.py
as ff
然后我可以用鼠标点击 matplotlib 显示的数组来设置 forest
。
在我提问之前,这里有一些信息:
- 没有树:
forest[i,j] = 0
- 一棵树:
forest[i,j] = 1
- 一棵着火的树:
forest[i,j] = 2
- 哈希值:
forest[i,j] = 3
基本上,forest
是大小为 n 的二维数组,由 m 组成,由 1 到0. 函数 onclick
点燃了 forest
,当 forest
仍然有树着火时,函数 spreadfire
将火蔓延。
使用函数 onclick
我可以点燃森林(如果我点击数组,树会变成红色)并且使用函数 start
我可以执行代码,感谢按钮 Start
.
现在的问题是,我第一次执行代码时它不知道 ani 是什么 (NameError: global name 'ani' is not defined
) – 这是正常的,因为我调用动画 ani
(通过尝试保存它)在我什至调用函数开始之前。但是,如果我尝试在函数 start
中保存 ani
,我会得到一个空白图——这也是正常的。
综上所述,我需要在函数 start
调用后保存动画,但我不能在函数 start
结束时保存它,否则我会得到一个空白图。有人可以告诉我应该怎么做吗?
PS : 我使用 Spyder 和 IPython 控制台 如果我的解释不够清楚,请告诉我。
forestfire_test.py
import forestfire as ff
import numpy as np
import matplotlib.pylab as plt
import matplotlib.colors as mcolors
from matplotlib import cm
from matplotlib.widgets import Button, Cursor
global forest
forest = np.random.rand(100,100)
# Colormap
greens = cm.Greens(np.linspace(0,1, num=50))
greensfill = cm.Greens(np.ones(25))
red = [(1,0,0,1)]*len(greens)
gray = [(.5,.5,.5,1)]*len(greens)
colors = np.vstack((greens, greensfill, red, gray))
mycmap = mcolors.LinearSegmentedColormap.from_list('my_colormap', colors)
# Figure
fig, ax = plt.subplots(figsize=(10,5))
fig.subplots_adjust(right=1.3)
im = ax.imshow(forest, animated=True, cmap = mycmap, interpolation="none", origin='lower', vmin=0, vmax=3.5)
ax.set_xlabel("X")
ax.set_ylabel("Y")
ax.tick_params(direction='out')
cursor = Cursor(ax, useblit=True, color='red', linewidth=1)
plt.show()
# Coordinates
def onclick(event):
x, y = int(event.xdata), int(event.ydata)
forest[y,x] = 2.
im.set_data(forest)
fig.canvas.draw_idle()
fig.canvas.mpl_connect('button_press_event', onclick)
# Start button
def start(event):
global ani
ani = ff.forestfire(forest)
button_ax = plt.axes([0.15, 0.45, 0.2, 0.1])
button = Button(button_ax, 'Start', color='lightgrey', hovercolor='grey')
button.on_clicked(start)
# Animation
ani.save("forestfire_test.mp4", writer = 'ffmpeg', fps=5, dpi=500)
forestfire.py
from random import random
import numpy as np
from numpy.random import choice
import matplotlib.pylab as plt
import matplotlib.colors as mcolors
from matplotlib import cm
import matplotlib.animation as animation
def hazard(p):
r=random()
assert p>=0 and p<=1
return r <= p
def chg(case):
if case > 1.:
return 1.
else:
return case
def spreadfire(forest):
n,m=forest.shape
c = np.copy(forest)
L = xrange(3)
for i in xrange(n):
for j in xrange(m):
if c[i,j] == 2.:
sautX, sautY = choice([0,1,40],p=[0.4999,0.4999,0.0002]), choice([0,1,40],p=[0.4999,0.4999,0.0002])
Y, X = xrange(max(0,i-1-sautY),min(n,i+2+sautY),sautY+1), xrange(max(0,j-1-sautX),min(m,j+2+sautX),sautX+1)
for y1,y2 in zip(Y,L):
for x1,x2 in zip(X,L):
if hazard(chg(c[y1,x1])):
forest[y1,x1] = 2.
return forest
def forestfire(forest):
fig, ax = plt.subplots()
movie, hashes = [], []
# Colormap
greens = cm.Greens(np.linspace(0,1, num=50))
greensfill = cm.Greens(np.ones(25))
red = [(1,0,0,1)]*len(greens)
gray = [(.5,.5,.5,1)]*len(greens)
colors = np.vstack((greens, greensfill, red, gray))
mycmap = mcolors.LinearSegmentedColormap.from_list('my_colormap', colors)
# Initialization
k = 0
firefront = 5
forest = spreadfire(forest)
c = np.copy(forest)
c[np.where(c==2.)] = 3.
hashes.append(c)
im = plt.imshow(forest, animated=True, cmap = mycmap, interpolation="none", origin='lower', vmin=0, vmax=3.5)
movie.append([im])
# Fire propagation
while np.count_nonzero(forest == 2.) != 0:
k += 1
print k
if k < firefront:
forest = spreadfire(forest)
c = np.copy(forest)
c[np.where(c==2.)] = 3.
hashes.append(c)
im = plt.imshow(forest, animated=True, cmap = mycmap, interpolation="none", origin='lower', vmin=0, vmax=3.5)
movie.append([im])
else:
forest = spreadfire(forest)
c = np.copy(forest)
c[np.where(c==2.)] = 3.
hashes.append(c)
forest[np.where(hashes[0]==3.)] = 3.
im = plt.imshow(forest, animated=True, cmap = mycmap, interpolation="none", origin='lower', vmin=0, vmax=3.5)
movie.append([im])
hashes.remove(hashes[0])
return animation.ArtistAnimation(fig, movie, blit=True, repeat_delay=100)
这里有两个问题,正如评论中提到的:
- 您需要将
ani.save
移动到 start 函数中。这是因为ani
只有在按下开始按钮后才会被定义。 plt.show
应该只在脚本末尾调用。
这对我来说工作正常,运行 作为脚本(在 python 2.7.10,matplotlib 2.0.2 上)在点击开始按钮后,动画是另存为mp4文件到当前目录。
除此之外,我需要将帧率设置为 6 或更高,如 previous question 中所述。