-1

我想制作多个情节的动画,其渲染随着时间的推移而演变。

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

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

实际上,我正在尝试适应我的情况这个创建电影的脚本:

制作动画

IE :

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图像生成这个动画,除非我找到一种方法来知道如何使用 PythonFuncAnimation函数(包含在yt库中?或默认情况下在 Python 中?)。

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

代表 3D 场景的图形

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

NightTrain 的结果:2D 热图图

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

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

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

我不明白为什么会发生此错误。

4

2 回答 2

1

有些问题我可以立即看到。

  1. animate 函数引用了一个plot未定义的变量。
  2. array_data = np.array(numFiles)将导致单项 numpy 数组中的文件数。array_data[i]可能不是故意的,并且会导致i>=1.
  3. array_data之后也不会填充数据。
  4. 我没有看到任何绘图正在完成。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_*')因此您可能也需要考虑这一点。

我很清楚这不是一个正在运行的示例,但我希望这将帮助您跟踪这一点。

于 2020-06-06T23:48:27.960 回答
1

如果您能提供更多信息,帮助会更容易。我修复了你的代码,它现在正在运行。您还忘记了使用该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')

而不是计算ls -dl你可能想要使用 python 解决方案的行数。这也使您可以直接使用路径,而无需稍后再构造它们。您可以使用 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()
于 2020-06-13T13:12:21.197 回答