如何在 Python 3 中为动画函数分配变量 fig

How to assign variable fig for animation function in Python 3

我想制作渲染随时间变化的多个情节的动画。

我需要的文件格式如下,例如一个:

DD0043/DD0043。所以我使用技巧:f'{43:04}' 来填充每个文件的前导零(文件从 DD0000/DD0000DD0922/DD0922

这里是脚本,警告,情节是用yt-project工具完成的:

import yt
import os, sys
import numpy as np
from matplotlib.animation import FuncAnimation
from matplotlib import rc_context
from matplotlib import pyplot as plt

# animate must accept an integer frame number. We use the frame number
# to identify which dataset in the time series we want to load
def animate(i):
  plot._switch_ds(array_data[i])

# Number of files
numFiles = int(os.popen('ls -dl DD* | wc -l').read())

# Array for each data directory
array_data = np.array(numFiles)

for i in range(numFiles):
  data = yt.load('DD'+str(f'{i:04}')+'/DD'+str(f'{i:04}'))
  sc = yt.create_scene(data, lens_type='perspective')

  source = sc[0]

  source.set_field('density')
  source.set_log(True)

  # Set up the camera parameters: focus, width, resolution, and image orientation
  sc.camera.focus = ds.domain_center
  sc.camera.resolution = 1024
  sc.camera.north_vector = [0, 0, 1]
  sc.camera.position = [1.7, 1.7, 1.7]

  # You may need to adjust the alpha values to get an image with good contrast.
  # For the annotate_domain call, the fourth value in the color tuple is the
  # alpha value.
  sc.annotate_axes(alpha=.02)
  sc.annotate_domain(ds, color=[1, 1, 1, .01])

  text_string = "T = {} Gyr".format(float(array_data[i].current_time.to('Gyr')))

fig = plt.figure()
animation = FuncAnimation(fig, animate, frames=numFiles)

# Override matplotlib's defaults to get a nicer looking font
with rc_context({'mathtext.fontset': 'stix'}):
    animation.save('animation.mp4')

但是在执行时,我得到以下错误:

923
Traceback (most recent call last):
  File "vol-annotated.py", line 52, in <module>
    animation.save('animation.mp4')
  File "/Users/fab/Library/Python/3.7/lib/python/site-packages/matplotlib/animation.py", line 1135, in save
    anim._init_draw()
  File "/Users/fab/Library/Python/3.7/lib/python/site-packages/matplotlib/animation.py", line 1743, in _init_draw
    self._draw_frame(next(self.new_frame_seq()))
StopIteration

我不知道我做的是否正确,尤其是我用 :

初始化的变量 fig
fig = plt.figure()

实际上,我正在尝试根据我的情况改编这个制作电影的脚本:

make animation

即:

import yt
from matplotlib.animation import FuncAnimation
from matplotlib import rc_context

ts = yt.load('GasSloshingLowRes/sloshing_low_res_hdf5_plt_cnt_*')

plot = yt.SlicePlot(ts[0], 'z', 'density')
plot.set_zlim('density', 8e-29, 3e-26)

fig = plot.plots['density'].figure

# animate must accept an integer frame number. We use the frame number
# to identify which dataset in the time series we want to load
def animate(i):
    ds = ts[i]
    plot._switch_ds(ds)

animation = FuncAnimation(fig, animate, frames=len(ts))

# Override matplotlib's defaults to get a nicer looking font
with rc_context({'mathtext.fontset': 'stix'}):
    animation.save('animation.mp4')

更新 1: 我没有找到正确使用 animation.save 来生成动画的方法:总是这个关于 fig 变量的问题。

但我设法将每个图像对应的所有图像生成到一个输出文件 DDxxxx/DDxxxx。我是这样进行的:

import yt
import os, sys
import numpy as np
from matplotlib.animation import FuncAnimation
from matplotlib import rc_context

# Number of files
numFiles = int(os.popen('ls -dl DD* | wc -l').read())

# Loop to load input files
ts = []
for j in range(numFiles):
  ts = np.append(ts, yt.load('DD'+str(f'{j:04}')+'/DD'+str(f'{j:04}')))

plot = yt.SlicePlot(ts[0], 'z', 'density')
plot.set_zlim('density', 8e-29, 3e-26)

# create plotting figure
fig = plot.plots['density'].figure

# animate must accept an integer frame number. We use the frame number
# to identify which dataset in the time series we want to load
def animate(i):
  ds = ts[i]
  sc = yt.create_scene(ds, lens_type='perspective')

  source = sc[0]

  source.set_field('density')
  source.set_log(True)

  # Set up the camera parameters: focus, width, resolution, and image orientation
  sc.camera.focus = ds.domain_center
  sc.camera.resolution = 1024
  sc.camera.north_vector = [0, 0, 1]
  sc.camera.position = [1.7, 1.7, 1.7]

  # You may need to adjust the alpha values to get an image with good contrast.
  # For the annotate_domain call, the fourth value in the color tuple is the
  # alpha value.
  sc.annotate_axes(alpha=.02)
  sc.annotate_domain(ds, color=[1, 1, 1, .01])

  text_string = "T = {} Gyr".format(float(ds.current_time.to('Gyr')))

  ## Here the scene needs to be painted into my figure / plot. 
  sc.save('rendering_'+str(i)+'.png')

animation = FuncAnimation(fig, animate, frames=numFiles)

# Override matplotlib's defaults to get a nicer looking font
with rc_context({'mathtext.fontset': 'stix'}):
    animation.save('animation.mp4')

如果我打开一个 .png,我会得到一个代表 3D 场景的正确图像。

不幸的是,动画功能不起作用,我只得到一个显示密度投影的 2D 热图:我想要一个 3D 场景图形的动画 (rendering_xxx.png)。

看来我必须使用 ffmpeg 从多个 .png 图像生成此动画,除非我找到一种方法知道如何使用 Python FuncAnimation 函数(包含在 yt 库中?或默认包含在 Python 中?)。

更新 2: 这里是我想要得到的动画图形示例(实际上是一帧)(这是一个表示盒子内气体密度的图形,即在3D) :

不幸的是,@NightTrain 的脚本产生了这种情节:

如您所见,我不明白为什么我使用 NightTrain 的解决方案得到的是 2D 热图而不是 3D 场景。

此外,此二维热图中没有动画,电影显示的始终是相同的图形。

UPDATE3: @Night train 建议的最后一个解决方案产生以下错误:

  Traceback (most recent call last):
      File "plot_3D_enzo_with_animation_LAST.py", line 30, in <module>
        plot = yt.SlicePlot(ts[0], 'z', 'density')
      File "/Users/henry/Library/Python/3.7/lib/python/site-packages/yt/data_objects/time_series.py", line 201, in __getitem__
        o = self._pre_outputs[key]
    IndexError: list index out of range

我不明白为什么会出现这个错误。

有些问题我可以马上看到。

  1. animate 函数引用了一个未定义的 plot 变量。
  2. array_data = np.array(numFiles) 将导致单项 numpy 数组中的文件数。可能不是故意的,并且会导致 array_data[i]i>=1.
  3. 而失败
  4. array_data之后也不填充数据。
  5. 我没有看到任何阴谋。 fig = plt.figure() 只会给你一个空图。

因此,我将稍微重组您的代码:

import yt
import os, sys
import numpy as np
from matplotlib.animation import FuncAnimation
from matplotlib import rc_context
from matplotlib import pyplot as plt

# Number of files
numFiles = int(os.popen('ls -dl DD* | wc -l').read())

# create plotting figure
fig = plt.figure()

# animate must accept an integer frame number. We use the frame number
# to identify which dataset in the time series we want to load
def animate(i):
  data = yt.load('DD'+str(f'{i:04}')+'/DD'+str(f'{i:04}'))
  sc = yt.create_scene(data, lens_type='perspective')

  source = sc[0]

  source.set_field('density')
  source.set_log(True)

  # Set up the camera parameters: focus, width, resolution, and image orientation
  sc.camera.focus = ds.domain_center
  sc.camera.resolution = 1024
  sc.camera.north_vector = [0, 0, 1]
  sc.camera.position = [1.7, 1.7, 1.7]

  # You may need to adjust the alpha values to get an image with good contrast.
  # For the annotate_domain call, the fourth value in the color tuple is the
  # alpha value.
  sc.annotate_axes(alpha=.02)
  sc.annotate_domain(ds, color=[1, 1, 1, .01])

  text_string = "T = {} Gyr".format(float(data.current_time.to('Gyr')))

  ## Here the scene needs to be painted into your figure / plot. 


animation = FuncAnimation(fig, animate, frames=numFiles)

# Override matplotlib's defaults to get a nicer looking font
with rc_context({'mathtext.fontset': 'stix'}):
    animation.save('animation.mp4')

不过,我在示例中也看到yt支持一次加载多个文件: ts = yt.load('GasSloshingLowRes/sloshing_low_res_hdf5_plt_cnt_*') 所以你可能也想考虑一下。

我很清楚这不是一个 运行 示例,但我希望这能帮助您找到它。

如果您能提供更多信息,将更容易提供帮助。我修复了您的代码,现在是 运行。 您还忘记使用 text_string 变量。 由于未使用 array_data 变量,我将其删除。

import yt
import os, sys
import numpy as np
from matplotlib.animation import FuncAnimation
from matplotlib import rc_context
from matplotlib import pyplot as plt

import pathlib
import glob

base_path = "enzo_tiny_cosmology"
paths = sorted(glob.glob(base_path + "/DD*/DD[0-9][0-9][0-9][0-9]"))
# paths = [x.joinpath(x.name).as_posix() for x in sorted(pathlib.Path(base_path).glob("DD*"))]

# Array for each data directory
# array_data = np.zeros(len(paths))
# array_data = [None for x in range(len(paths))]

ts = yt.load(paths)
# ts = yt.load(base_path + "/DD*/DD[0-9][0-9][0-9][0-9]")
# print(ts.outputs)

plot = yt.SlicePlot(ts[0], 'z', 'density')
fig = plot.plots['density'].figure

# animate must accept an integer frame number. We use the frame number
# to identify which dataset in the time series we want to load
def animate(i):

  data = ts[i]
  sc = yt.create_scene(data, lens_type='perspective')

  source = sc[0]

  source.set_field('density')
  source.set_log(True)

  # Set up the camera parameters: focus, width, resolution, and image orientation
  sc.camera.focus = data.domain_center
  sc.camera.resolution = 1024
  sc.camera.north_vector = [0, 0, 1]
  sc.camera.position = [1.7, 1.7, 1.7]

  # You may need to adjust the alpha values to get an image with good contrast.
  # For the annotate_domain call, the fourth value in the color tuple is the
  # alpha value.
  sc.annotate_axes(alpha=.02)
  sc.annotate_domain(data, color=[1, 1, 1, .01])

  text_string = "T = {} Gyr".format(float(data.current_time.to('Gyr')))

  plot._switch_ds(data)

animation = FuncAnimation(fig, animate, frames = len(paths))

# Override matplotlib's defaults to get a nicer looking font
with rc_context({'mathtext.fontset': 'stix'}):
    animation.save('animation.mp4')

您可能想要使用 python 解决方案而不是计算 ls -dl 的行数。这也使您可以直接使用路径而无需稍后构造它们。您可以使用 pathlib 或 os 模块。

import pathlib
import glob

base_path = "enzo_tiny_cosmology"
paths = sorted(glob.glob(base_path + "/DD*/DD[0-9][0-9][0-9][0-9]"))
paths = [x.joinpath(x.name).as_posix() for x in sorted(pathlib.Path(base_path).glob("DD*"))]

为了测试,我下载了这些数据集:

curl -sSO https://yt-project.org/data/enzo_tiny_cosmology.tar.gz
tar xzf enzo_tiny_cosmology.tar.gz

curl -sSO https://yt-project.org/data/GasSloshingLowRes.tar.gz
tar xzf GasSloshingLowRes.tar.gz

更新:

如果您想将渲染的场景保存为视频,例如使用 imageioopencv:

import yt, glob, imageio

# animate must accept an integer frame number. We use the frame number
# to identify which dataset in the time series we want to load
def animate(data):
  sc = yt.create_scene(data, lens_type='perspective')

  source = sc[0]
  source.set_field('density')
  source.set_log(True)

  # Set up the camera parameters: focus, width, resolution, and image orientation
  sc.camera.focus = data.domain_center
  sc.camera.resolution = 1024
  sc.camera.north_vector = [0, 0, 1]
  sc.camera.position = [1.7, 1.7, 1.7]

  # You may need to adjust the alpha values to get an image with good contrast.
  # For the annotate_domain call, the fourth value in the color tuple is the
  # alpha value.
  sc.annotate_axes(alpha=.02)
  sc.annotate_domain(data, color=[1, 1, 1, .01])

  plot._switch_ds(data)
  sc.save(f'rendering_{i:04d}.png')
  return sc.render()

paths = sorted(glob.glob("/DD*/DD[0-9][0-9][0-9][0-9]"))
ts = yt.load(paths)
plot = yt.SlicePlot(ts[0], 'z', 'density')
plot.set_zlim('density', 8e-29, 3e-26)

vid_writer = imageio.get_writer("animation.mp4", fps = 10)
for frame in ts:
    rendered_image = animate(frame)
    vid_writer.append_data(rendered_image)
vid_writer.close()