How to create Animations#

Section author: Florian Zill (Helmholtz Centre for Environmental Research GmbH - UFZ)

To demonstrate the creation of an animated plot we use a component transport example from the ogs benchmark gallery (https://www.opengeosys.org/docs/benchmarks/hydro-component/elder/).

import numpy as np

import ogstools as ot
from ogstools import examples

ms = examples.load_meshseries_CT_2D_XDMF().scale(time=("s", "yrs"))
saturation = ot.variables.saturation

To read your own data as a mesh series you can do:

from ogstools.meshlib import MeshSeries
mesh_series = MeshSeries("filepath/filename_pvd_or_xdmf")

You can choose which timesteps to render by either slicing the MeshSeries or by resampling it to new timesteps (this interpolates between existing timesteps).

this would only use every second timesteps for animation ms = ms[::2] this uses equally spaced timesteps for a smooth animation

timevalues = np.linspace(ms.timevalues[0], ms.timevalues[-1], num=21)
ms = ot.MeshSeries.resample(ms, timevalues)

Now, let’s animate the saturation solution. Note that rendering many frames in conjunction with large meshes might take a really long time. You need to setup a matplotlib figure first and a function, which is executed on each frame. This function has to take the individual values of the sequences passed as additional arguments, in this case the timevalues and the MeshSeries.

clip to the right half

ms_r = ms.transform(lambda mesh: mesh.clip("-x", [-1, 0, 0]))

# create initial figure with fixed colorbar
fig = ot.plot.contourf(ms_r[0], saturation, vmin=0, vmax=100, dpi=50)
fig.axes[0].set_title(f"{0} yrs", fontsize=32)


def plot_contourf(timevalue: float, mesh: ot.Mesh) -> None:
    fig.axes[0].clear()
    ot.plot.contourf(mesh, saturation, ax=fig.axes[0], dpi=50)
    fig.axes[0].set_title(f"{timevalue:.1f} yrs", fontsize=32)


anim = ot.plot.animate(fig, plot_contourf, ms_r.timevalues, ms_r)
  0%|          | 0/20 [00:00<?, ?it/s]
  5%|▌         | 1/20 [00:00<00:06,  2.75it/s]
 10%|█         | 2/20 [00:00<00:06,  2.76it/s]
 15%|█▌        | 3/20 [00:01<00:06,  2.76it/s]
 20%|██        | 4/20 [00:01<00:05,  2.77it/s]
 25%|██▌       | 5/20 [00:01<00:05,  2.76it/s]
 30%|███       | 6/20 [00:02<00:05,  2.76it/s]
 35%|███▌      | 7/20 [00:02<00:04,  2.76it/s]
 40%|████      | 8/20 [00:02<00:04,  2.76it/s]
 45%|████▌     | 9/20 [00:03<00:03,  2.76it/s]
 50%|█████     | 10/20 [00:03<00:03,  2.76it/s]
 55%|█████▌    | 11/20 [00:03<00:03,  2.75it/s]
 60%|██████    | 12/20 [00:04<00:02,  2.76it/s]
 65%|██████▌   | 13/20 [00:04<00:02,  2.75it/s]
 70%|███████   | 14/20 [00:05<00:02,  2.75it/s]
 75%|███████▌  | 15/20 [00:05<00:01,  2.75it/s]
 80%|████████  | 16/20 [00:05<00:01,  2.74it/s]
 85%|████████▌ | 17/20 [00:06<00:01,  2.74it/s]
 90%|█████████ | 18/20 [00:06<00:00,  2.74it/s]
 95%|█████████▌| 19/20 [00:06<00:00,  2.74it/s]
100%|██████████| 20/20 [00:07<00:00,  2.74it/s]
100%|██████████| 20/20 [00:07<00:00,  2.62it/s]

You can also use any other function to create an animation this way. Just make sure, that the function arguments and those passed to the animation call fit together.

ms_x = ms.transform(
    lambda mesh: mesh.sample_over_line([0, 0, 60], [150, 0, 60])
)
fig = ot.plot.line(ms_x[0], ot.variables.saturation)


def plot_line(mesh: ot.Mesh) -> None:
    fig.axes[0].clear()
    ot.plot.line(mesh, saturation, ax=fig.axes[0])
    fig.axes[0].set_ylim([0, 100])
    fig.tight_layout()


anim = ot.plot.animate(fig, plot_line, ms_x)
  0%|          | 0/20 [00:00<?, ?it/s]
  5%|▌         | 1/20 [00:00<00:05,  3.45it/s]
 10%|█         | 2/20 [00:00<00:05,  3.40it/s]
 15%|█▌        | 3/20 [00:00<00:04,  3.41it/s]
 20%|██        | 4/20 [00:01<00:04,  3.46it/s]
 25%|██▌       | 5/20 [00:01<00:04,  3.47it/s]
 30%|███       | 6/20 [00:01<00:04,  2.89it/s]
 35%|███▌      | 7/20 [00:02<00:04,  3.05it/s]
 40%|████      | 8/20 [00:02<00:03,  3.16it/s]
 45%|████▌     | 9/20 [00:02<00:03,  3.24it/s]
 50%|█████     | 10/20 [00:03<00:03,  3.29it/s]
 55%|█████▌    | 11/20 [00:03<00:02,  3.31it/s]
 60%|██████    | 12/20 [00:03<00:02,  3.32it/s]
 65%|██████▌   | 13/20 [00:03<00:02,  3.30it/s]
 70%|███████   | 14/20 [00:04<00:01,  3.30it/s]
 75%|███████▌  | 15/20 [00:04<00:01,  3.29it/s]
 80%|████████  | 16/20 [00:04<00:01,  3.27it/s]
 85%|████████▌ | 17/20 [00:05<00:00,  3.27it/s]
 90%|█████████ | 18/20 [00:05<00:00,  3.28it/s]
 95%|█████████▌| 19/20 [00:05<00:00,  2.85it/s]
100%|██████████| 20/20 [00:06<00:00,  2.96it/s]
100%|██████████| 20/20 [00:06<00:00,  3.04it/s]

The animation can be saved (as mp4) like so:

ot.plot.utils.save_animation(anim, "Saturation", fps=5)

Total running time of the script: (0 minutes 31.689 seconds)