Keyframing with Vapor

In this notebook, we will create an animation where our camera moves to view our visualization from different angles. We will achieve this using an animation technique called keyframing. This technique involves selecting several key scenes, known as keyframes, and then having the computer generate the intermediate frames between these keyframes. This process creates a smooth transition from one keyframe to the next, resulting in a fluid animation.

Setup and data download

from vapor import session, dataset, renderer
from vapor.animation import Animation
Warning: sysroot "/Applications/Xcode_12.4.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk" not found (ignoring for now).
Vapor 3.9.3
Python 3.9.19 (/opt/anaconda3/envs/vapor)
OpenGL 4.1 Metal - 83.1

The following cell will download the data from NCAR’s Research Data Archives.

import os
import requests
import zipfile
url = 'https://data.rda.ucar.edu/ds897.7/Katrina.zip'
extract_to = './data'
zip_name = "Katrina.zip"
data_file = './data/wrfout_d02_2005-08-29_02.nc'

# Check if the data file already exists
if not os.path.exists(data_file):
    # Download zip
    with requests.get(url, stream=True) as r:
        r.raise_for_status()
        with open(zip_name, 'wb') as f:
            for chunk in r.iter_content(chunk_size=8192):
                f.write(chunk)
    # Extract the file
    with zipfile.ZipFile(zip_name, 'r') as zip_ref:
        zip_ref.extractall(extract_to)

    # Clean up the zip file
    os.remove(zip_name)

    print(f"Data downloaded and extracted to {data_file}")
else:
    print(f"Data file already exists at {data_file}, skipping download and extraction.")
Data downloaded and extracted to ./data/wrfout_d02_2005-08-29_02.nc

Create keyframes

To begin creating our animation, we first create our keyframes. Each keyframe corresponds to a specific moment in our animation, defined by the camera’s position and orientation. We can create these keyframes using either Python or the Vapor application, and save them as session files. For the purpose of this notebook, we’ll input our camera information directly in Python, but in practice, it’s easier to fine-tune the camera settings within the application.

First we’ll create our static visualization. A detailed guide on how this is created can be found in the quickstart notebook

ses = session.Session()
data = ses.OpenDataset(dataset.WRF, ["data/wrfout_d02_2005-08-29_02.nc"])
land = data.NewRenderer(renderer.ImageRenderer) # Render land image
land.GetTransform().SetTranslations([0,0,100])
land.SetHeightVariableName("HGT")
wind = data.NewRenderer(renderer.TwoDDataRenderer) # Render U10
wind.SetVariableName("U10")
clouds = data.NewRenderer(renderer.VolumeRenderer) # Render clouds
clouds.SetVariableName("QCLOUD")
clouds_tf = clouds.GetTransferFunction("QCLOUD")
clouds_tf.LoadBuiltinColormap("Sequential/BlackWhite")
clouds_tf.SetColorRGBList([(r, g, b) for r, g, b, _ in 
                           list(reversed(clouds_tf.GetMatPlotLibColormap().colors))])
clouds_tf.SetOpacityControlPoints([[0,0],[0.00001,0.01], [0.0001, 0.1], [0.0010,0.9]])

Now, we’ll define the camera settings for each of our keyframes. These numbers are tricky to get right in Python, so it is recommended that they are selected within the application.

# The camera position of each keyframe
positions = [
    [-1190444.44426004, 1882360.85954653, 770176.40842364], # Keyframe 1
    [-1172384.15238047, 2813172.26639064, 355291.41877028], # Keyframe 2
    [-968784.32993129, 3056725.58106798, -34317.16158186], # ...
    [-733144.08018801, 2929790.21696698, -32984.22588893],
    [-691781.20449513, 2442083.68616993, -47289.68751812]
]

# The camera target for each keyframe
targets = [
    [-420811.28125, 2737271.75, 5699.78515597], # Keyframe 1
    [-420811.28125, 2737271.75, 15699.78515597], # ...
    [-420811.28125, 2737271.75, 15699.78515597],
    [-420811.28125, 2737271.75, 15699.78515597],
    [-420811.28125, 2737271.75, 15699.78515597]
]

# The up vector for each keyframe
ups = [
    [0.41853764, 0.35630071, 0.83538976],
    [ 0.39861183, -0.08972356, 0.91272027],
    [-0.08058301, 0.02890014, 0.99632884],
    [-0.080583, 0.02890014, 0.99632884],
    [-0.0964622, -0.0632074, 0.99332767]
]

Now, we will save each of these camera settings to a session file to represent our keyframes.

os.makedirs("keyframes", exist_ok=True) # Make directory for keyframes if it doesn't exist
for i, position, target, up in zip(range(1, len(positions)+1), positions, targets, ups):
    ses.GetCamera().LookAt(position, target, up)
    ses.Save(f"./keyframes/keyframe{i}.vs3")

Generate Animation

Specify the paths to your session files (keyframes) and the number of interpolation steps between each keyframe.

sessions = [
    "keyframes/keyframe1.vs3",
    "keyframes/keyframe2.vs3",
    "keyframes/keyframe3.vs3",
    "keyframes/keyframe4.vs3",
    "keyframes/keyframe5.vs3",
]
steps = [40,30,30,30]

Generate and Display Animation

Now, we will create the animation using the defined keyframes and steps, then display it. Vapor’s utils.keyframing.animate_camera_keyframes utility function generates an animation using keyframing from a list of session paths and a corresponding list of integers that describe the number of frames between each keyframe. It creates and returns an animation that can be displayed.

from vapor.utils import keyframing
anim = keyframing.animate_camera_keyframes(sessions, steps)
anim.Show()
Dataset: 'wrfout_d02_2005-08-29_02.nc'
Dataset: 'wrfout_d02_2005-08-29_02.nc'
Dataset: 'wrfout_d02_2005-08-29_02.nc'
Dataset: 'wrfout_d02_2005-08-29_02.nc'
Dataset: 'wrfout_d02_2005-08-29_02.nc'
UNSUPPORTED (log once): POSSIBLE ISSUE: unit 5 GLD_TEXTURE_INDEX_3D is unloadable and bound to sampler type (Float) - using zero texture because texture unloadable
Rendering Animation [########################################] 100%