In [1]:
# Anny
# Copyright (C) 2025 NAVER Corp.
# Apache License, Version 2.0

Alternative models¶

In this tutorial, we show how to use different rigs and topologies with Anny.

Imports and helper functions¶

In [2]:
import torch
import roma  # A PyTorch library useful to deal with space transformations.
import anny  # The main library for the Anny model.
import trimesh  # For 3D mesh visualization.

# Create and show multiple rigs in one cell
from IPython.display import display, Markdown
import trimesh.viewer.notebook as nb

# Some helper objects for visualization.
trimesh_scene_transform = (
    roma.Rigid(
        linear=roma.euler_to_rotmat("x", [-90.0], degrees=True), translation=None
    )
    .to_homogeneous()
    .cpu()
    .numpy()
)

mesh_material = trimesh.visual.material.PBRMaterial(
    baseColorFactor=[0.6, 0.8, 0.7, 0.5],
    metallicFactor=0.5,
    doubleSided=False,
    alphaMode="BLEND",
)

world_axis = trimesh.creation.axis(axis_length=1.0)
axis = trimesh.creation.axis(axis_length=0.1)


def add_skeleton_to_scene(scene, model, output):
    # Add bones visualization. Procrustes rigs (the default) have no bone tails,
    # so we draw each bone as a segment from its head to its parent's head.
    bone_poses = output["bone_poses"].squeeze(dim=0).cpu()
    bone_heads = bone_poses[:, :3, 3]
    bone_color = [0.8, 0.3, 0.3, 1.0]
    bone_visual = trimesh.visual.TextureVisuals(
        material=trimesh.visual.material.PBRMaterial(
            baseColorFactor=bone_color,
            metallicFactor=0.0,
            roughnessFactor=1.0,
            doubleSided=True,
            alphaMode="BLEND",
        )
    )
    for i in range(1, len(bone_heads)):
        bone_head = bone_heads[model.bone_parents[i]]
        bone_tail = bone_heads[i]
        length = torch.norm(bone_tail - bone_head).item()
        if length < 1e-6:
            continue
        cylinder = trimesh.creation.cylinder(radius=0.005, height=length, sections=16)
        t = (bone_head + bone_tail) / 2
        M = roma.special_gramschmidt(
            torch.stack(
                [
                    bone_tail - bone_head,
                    torch.tensor([0.0, 0.0, 1.0], dtype=torch.float32),
                ],
                dim=-1,
            )
        )
        R = torch.stack([M[:, 2], M[:, 1], M[:, 0]], dim=-1)
        cylinder.visual = bone_visual
        scene.add_geometry(
            cylinder,
            transform=roma.Rigid(R, t).to_homogeneous().numpy(),
            node_name=f"bone_{model.bone_labels[i]}",
        )

    # Add some spheres at the joints
    joint_sphere = trimesh.creation.icosphere(radius=0.008, subdivisions=2)
    joint_sphere.visual = trimesh.visual.TextureVisuals(
        material=trimesh.visual.material.PBRMaterial(
            baseColorFactor=[0.1, 0.1, 0.1, 1.0],
            metallicFactor=0.5,
            roughnessFactor=1.0,
            doubleSided=True,
            alphaMode="OPAQUE",
        )
    )
    for i in range(len(bone_poses)):
        scene.add_geometry(
            joint_sphere,
            transform=bone_poses[i],
            node_name=f"joint_{model.bone_labels[i]}",
        )

Rigs and topology¶

Anny supports various skeletal rigs, including:

  • "makehuman", the default rig provided by MPFB2 (https://github.com/makehumancommunity/mpfb2) with minor vertex weights fixes.
  • "anny" (the default), an adaptation of the "makehuman" rig with more stable bone orientations when the shape changes.
  • "mixamo", inspired by characters from https://www.mixamo.com/.
  • "soma", for compatibility with https://www.github.com/NVlabs/SOMA-X/.

Bones that are not useful for your application can be removed from the Anny rig. Using "anny-notoes" will ignore bones animating individual toes, for example.

Anny also supports various mesh topologies. A topology such as "notoes_collapse5pc" provides coarser mesh output for example, allowing to speed up inference and reduce memory consumption. We provide a "smplx" topology for interoperability with the SMPL-X model (https://smpl-x.is.tue.mpg.de/), for non-commercial use only.

We show below a few combinations of meshes and topologies supported by the model:

In [3]:
viewers = []

for rig, topology in [
    ("anny", "anny"),
    (
        "mixamo",
        "anny",
    ),
    (
        "anny",
        "smplx",
    ),
    (
        "anny",
        "soma",
    ),
    ("anny-notoes-noeyes", "makehuman"),
]:
    model = anny.Anny(rig=rig, topology=topology)
    output = model()

    mesh = trimesh.Trimesh(
        vertices=output["vertices"].squeeze(0).cpu().numpy(),
        faces=model.faces.cpu().numpy(),
    )
    mesh.visual.material = mesh_material
    scene = trimesh.Scene([mesh])

    add_skeleton_to_scene(scene, model, output)
    scene.apply_transform(trimesh_scene_transform)

    # Convert to a notebook widget/HTML
    viewers.append(
        Markdown(
            f"#### '{rig}' rig ({model.bone_count} bones) / '{topology}' topology "
            f"({len(output['vertices'].squeeze(0))} vertices, {len(model.faces)} faces)"
        )
    )
    viewers.append(Markdown("  - " + ", ".join([label for label in model.bone_labels])))
    viewers.append(nb.scene_to_notebook(scene))


# Display all viewers
display(*viewers)
Warp 1.13.0 initialized:
   CUDA Toolkit 12.9, Driver 13.0
   Devices:
     "cpu"      : "x86_64"
     "cuda:0"   : "NVIDIA A100 80GB PCIe" (79 GiB, sm_80, mempool enabled)
   Kernel cache:
some_path
Module anny.skinning.warp_skinning d91b4e4 load on device 'cpu' took 5.48 ms  (cached)
Module anny.skinning.warp_skinning cf0fbd4 load on device 'cpu' took 2.25 ms  (cached)
Module anny.skinning.warp_skinning ba63a59 load on device 'cpu' took 2.92 ms  (cached)
Module anny.skinning.warp_skinning 234553d load on device 'cpu' took 1.89 ms  (cached)

'anny' rig (104 bones) / 'anny' topology (13718 vertices, 27420 faces)¶

  • root, pelvis.L, upperleg01.L, upperleg02.L, lowerleg01.L, lowerleg02.L, foot.L, toe1-1.L, toe1-2.L, toe2-1.L, toe2-2.L, toe2-3.L, toe3-1.L, toe3-2.L, toe3-3.L, toe4-1.L, toe4-2.L, toe4-3.L, toe5-1.L, toe5-2.L, toe5-3.L, pelvis.R, upperleg01.R, upperleg02.R, lowerleg01.R, lowerleg02.R, foot.R, toe1-1.R, toe1-2.R, toe2-1.R, toe2-2.R, toe2-3.R, toe3-1.R, toe3-2.R, toe3-3.R, toe4-1.R, toe4-2.R, toe4-3.R, toe5-1.R, toe5-2.R, toe5-3.R, spine05, spine04, spine03, spine02, spine01, clavicle.L, shoulder01.L, upperarm01.L, upperarm02.L, lowerarm01.L, lowerarm02.L, wrist.L, finger1-1.L, finger1-2.L, finger1-3.L, metacarpal1.L, finger2-1.L, finger2-2.L, finger2-3.L, metacarpal2.L, finger3-1.L, finger3-2.L, finger3-3.L, metacarpal3.L, finger4-1.L, finger4-2.L, finger4-3.L, metacarpal4.L, finger5-1.L, finger5-2.L, finger5-3.L, clavicle.R, shoulder01.R, upperarm01.R, upperarm02.R, lowerarm01.R, lowerarm02.R, wrist.R, finger1-1.R, finger1-2.R, finger1-3.R, metacarpal1.R, finger2-1.R, finger2-2.R, finger2-3.R, metacarpal2.R, finger3-1.R, finger3-2.R, finger3-3.R, metacarpal3.R, finger4-1.R, finger4-2.R, finger4-3.R, metacarpal4.R, finger5-1.R, finger5-2.R, finger5-3.R, neck01, neck02, neck03, head, eye.L, eye.R

'mixamo' rig (52 bones) / 'anny' topology (13718 vertices, 27420 faces)¶

  • mixamorig:Hips, mixamorig:LeftUpLeg, mixamorig:LeftLeg, mixamorig:LeftFoot, mixamorig:LeftToeBase, mixamorig:RightUpLeg, mixamorig:RightLeg, mixamorig:RightFoot, mixamorig:RightToeBase, mixamorig:Spine, mixamorig:Spine1, mixamorig:Spine2, mixamorig:LeftShoulder, mixamorig:LeftArm, mixamorig:LeftForeArm, mixamorig:LeftHand, mixamorig:LeftHandIndex1, mixamorig:LeftHandIndex2, mixamorig:LeftHandIndex3, mixamorig:LeftHandMiddle1, mixamorig:LeftHandMiddle2, mixamorig:LeftHandMiddle3, mixamorig:LeftHandPinky1, mixamorig:LeftHandPinky2, mixamorig:LeftHandPinky3, mixamorig:LeftHandRing1, mixamorig:LeftHandRing2, mixamorig:LeftHandRing3, mixamorig:LeftHandThumb1, mixamorig:LeftHandThumb2, mixamorig:LeftHandThumb3, mixamorig:Neck, mixamorig:Head, mixamorig:RightShoulder, mixamorig:RightArm, mixamorig:RightForeArm, mixamorig:RightHand, mixamorig:RightHandIndex1, mixamorig:RightHandIndex2, mixamorig:RightHandIndex3, mixamorig:RightHandMiddle1, mixamorig:RightHandMiddle2, mixamorig:RightHandMiddle3, mixamorig:RightHandPinky1, mixamorig:RightHandPinky2, mixamorig:RightHandPinky3, mixamorig:RightHandRing1, mixamorig:RightHandRing2, mixamorig:RightHandRing3, mixamorig:RightHandThumb1, mixamorig:RightHandThumb2, mixamorig:RightHandThumb3

'anny' rig (104 bones) / 'smplx' topology (10475 vertices, 20908 faces)¶

  • root, pelvis.L, upperleg01.L, upperleg02.L, lowerleg01.L, lowerleg02.L, foot.L, toe1-1.L, toe1-2.L, toe2-1.L, toe2-2.L, toe2-3.L, toe3-1.L, toe3-2.L, toe3-3.L, toe4-1.L, toe4-2.L, toe4-3.L, toe5-1.L, toe5-2.L, toe5-3.L, pelvis.R, upperleg01.R, upperleg02.R, lowerleg01.R, lowerleg02.R, foot.R, toe1-1.R, toe1-2.R, toe2-1.R, toe2-2.R, toe2-3.R, toe3-1.R, toe3-2.R, toe3-3.R, toe4-1.R, toe4-2.R, toe4-3.R, toe5-1.R, toe5-2.R, toe5-3.R, spine05, spine04, spine03, spine02, spine01, clavicle.L, shoulder01.L, upperarm01.L, upperarm02.L, lowerarm01.L, lowerarm02.L, wrist.L, finger1-1.L, finger1-2.L, finger1-3.L, metacarpal1.L, finger2-1.L, finger2-2.L, finger2-3.L, metacarpal2.L, finger3-1.L, finger3-2.L, finger3-3.L, metacarpal3.L, finger4-1.L, finger4-2.L, finger4-3.L, metacarpal4.L, finger5-1.L, finger5-2.L, finger5-3.L, clavicle.R, shoulder01.R, upperarm01.R, upperarm02.R, lowerarm01.R, lowerarm02.R, wrist.R, finger1-1.R, finger1-2.R, finger1-3.R, metacarpal1.R, finger2-1.R, finger2-2.R, finger2-3.R, metacarpal2.R, finger3-1.R, finger3-2.R, finger3-3.R, metacarpal3.R, finger4-1.R, finger4-2.R, finger4-3.R, metacarpal4.R, finger5-1.R, finger5-2.R, finger5-3.R, neck01, neck02, neck03, head, eye.L, eye.R

'anny' rig (104 bones) / 'soma' topology (18056 vertices, 36108 faces)¶

  • root, pelvis.L, upperleg01.L, upperleg02.L, lowerleg01.L, lowerleg02.L, foot.L, toe1-1.L, toe1-2.L, toe2-1.L, toe2-2.L, toe2-3.L, toe3-1.L, toe3-2.L, toe3-3.L, toe4-1.L, toe4-2.L, toe4-3.L, toe5-1.L, toe5-2.L, toe5-3.L, pelvis.R, upperleg01.R, upperleg02.R, lowerleg01.R, lowerleg02.R, foot.R, toe1-1.R, toe1-2.R, toe2-1.R, toe2-2.R, toe2-3.R, toe3-1.R, toe3-2.R, toe3-3.R, toe4-1.R, toe4-2.R, toe4-3.R, toe5-1.R, toe5-2.R, toe5-3.R, spine05, spine04, spine03, spine02, spine01, clavicle.L, shoulder01.L, upperarm01.L, upperarm02.L, lowerarm01.L, lowerarm02.L, wrist.L, finger1-1.L, finger1-2.L, finger1-3.L, metacarpal1.L, finger2-1.L, finger2-2.L, finger2-3.L, metacarpal2.L, finger3-1.L, finger3-2.L, finger3-3.L, metacarpal3.L, finger4-1.L, finger4-2.L, finger4-3.L, metacarpal4.L, finger5-1.L, finger5-2.L, finger5-3.L, clavicle.R, shoulder01.R, upperarm01.R, upperarm02.R, lowerarm01.R, lowerarm02.R, wrist.R, finger1-1.R, finger1-2.R, finger1-3.R, metacarpal1.R, finger2-1.R, finger2-2.R, finger2-3.R, metacarpal2.R, finger3-1.R, finger3-2.R, finger3-3.R, metacarpal3.R, finger4-1.R, finger4-2.R, finger4-3.R, metacarpal4.R, finger5-1.R, finger5-2.R, finger5-3.R, neck01, neck02, neck03, head, eye.L, eye.R

'anny-notoes-noeyes' rig (74 bones) / 'makehuman' topology (19158 vertices, 13742 faces)¶

  • root, pelvis.L, upperleg01.L, upperleg02.L, lowerleg01.L, lowerleg02.L, foot.L, pelvis.R, upperleg01.R, upperleg02.R, lowerleg01.R, lowerleg02.R, foot.R, spine05, spine04, spine03, spine02, spine01, clavicle.L, shoulder01.L, upperarm01.L, upperarm02.L, lowerarm01.L, lowerarm02.L, wrist.L, finger1-1.L, finger1-2.L, finger1-3.L, metacarpal1.L, finger2-1.L, finger2-2.L, finger2-3.L, metacarpal2.L, finger3-1.L, finger3-2.L, finger3-3.L, metacarpal3.L, finger4-1.L, finger4-2.L, finger4-3.L, metacarpal4.L, finger5-1.L, finger5-2.L, finger5-3.L, clavicle.R, shoulder01.R, upperarm01.R, upperarm02.R, lowerarm01.R, lowerarm02.R, wrist.R, finger1-1.R, finger1-2.R, finger1-3.R, metacarpal1.R, finger2-1.R, finger2-2.R, finger2-3.R, metacarpal2.R, finger3-1.R, finger3-2.R, finger3-3.R, metacarpal3.R, finger4-1.R, finger4-2.R, finger4-3.R, metacarpal4.R, finger5-1.R, finger5-2.R, finger5-3.R, neck01, neck02, neck03, head

Interoperability with the SMPL family¶

Anny ships differentiable re-implementations of the SMPL body models as first-class model classes (from anny.models.smpl import SMPL, SMPLH, SMPLX). They follow the same construction and forward conventions as anny.Anny, but are driven by SMPL models parameters, rather than Anny phenotypes:

Class Model (bones) Additional inputs
SMPL SMPL (24) –
SMPLH SMPL+H (52) left_hand_pose, right_hand_pose (MANO, PCA by default)
SMPLX SMPL-X (55) hand poses, expression, jaw_pose, eye poses

All three accept different output topologies ("anny", "smpl", "smplx"), the default being the model's native mesh. With topology="anny" the retopology is folded into the model at construction time, so a single forward pass outputs the body directly in Anny's common mesh, and per-vertex operations (texturing, correspondences, losses) transfer between a SMPL-driven body and a phenotype-driven anny.Anny. Anny vertices with no counterpart in the source model (e.g. the internal mouth bag) are ignored. Note that the "smpl" and "smplx" topologies rely on retopology data that Anny provides for non-commercial use only.

The model files are distributed separately, under their own license terms, from the sites linked above. Point SMPLX_MODEL_PATH at a directory containing them in smpl/, smplh/ and smplx/ subfolders; the cell below skips itself otherwise.

In [4]:
import os

smplx_model_path = os.environ.get("SMPLX_MODEL_PATH")
if not smplx_model_path or not os.path.isdir(smplx_model_path):
    display(
        Markdown(
            "> **SMPL family examples skipped.** Download the model files and set the "
            "`SMPLX_MODEL_PATH` environment variable to the directory that contains them "
            "to run this cell."
        )
    )
else:
    from anny.models.smpl import SMPL, SMPLH, SMPLX

    dtype = torch.float32
    torch.manual_seed(0)
    # Shared shape and root parameters (10 betas).
    shape_kwargs = dict(
        betas=0.5 * torch.randn((1, 10), dtype=dtype),
        global_orient=0.1 * torch.randn((1, 3), dtype=dtype),
        transl=torch.zeros((1, 3), dtype=dtype),
    )
    # SMPL poses 23 body joints, the last two being the hands; SMPL+H and SMPL-X pose the
    # first 21 only and articulate the hands with MANO (6 PCA components by default).
    body_pose = 0.1 * torch.randn((1, 23 * 3), dtype=dtype)
    hand_kwargs = dict(
        left_hand_pose=torch.randn((1, 6), dtype=dtype),
        right_hand_pose=torch.randn((1, 6), dtype=dtype),
    )
    # SMPL-X adds a face: 10 expression coefficients, jaw and eye rotations.
    face_kwargs = dict(
        expression=0.5 * torch.randn((1, 10), dtype=dtype),
        jaw_pose=0.1 * torch.randn((1, 3), dtype=dtype),
        leye_pose=0.1 * torch.randn((1, 3), dtype=dtype),
        reye_pose=0.1 * torch.randn((1, 3), dtype=dtype),
    )

    for cls, pose_kwargs in [
        (SMPL, dict(body_pose=body_pose)),
        (SMPLH, dict(body_pose=body_pose[:, : 21 * 3], **hand_kwargs)),
        (SMPLX, dict(body_pose=body_pose[:, : 21 * 3], **hand_kwargs, **face_kwargs)),
    ]:
        # topology="anny" retopologizes the output onto Anny's common mesh (see above).
        model = cls(smplx_model_path, gender="neutral", topology="anny").to(dtype=dtype)
        output = model(**shape_kwargs, **pose_kwargs)

        mesh = trimesh.Trimesh(
            vertices=output["vertices"].squeeze(0).detach().cpu().numpy(),
            faces=model.faces.cpu().numpy(),
        )
        mesh.visual.material = mesh_material
        scene = trimesh.Scene([mesh])
        add_skeleton_to_scene(scene, model, output)
        scene.apply_transform(trimesh_scene_transform)

        display(
            Markdown(
                f"#### `anny.{cls.__name__}` model ({model.bone_count} bones, "
                f"{len(output['vertices'].squeeze(0))} vertices)"
            )
        )
        display(nb.scene_to_notebook(scene))
WARNING: You are using a SMPL model, with only 10 shape coefficients.
Module anny.skinning.warp_skinning 7661868 load on device 'cpu' took 1.64 ms  (cached)

anny.SMPL model (24 bones, 13188 vertices)¶

Module anny.skinning.warp_skinning 29c4109 load on device 'cpu' took 1.66 ms  (cached)

anny.SMPLH model (52 bones, 13188 vertices)¶

Module anny.skinning.warp_skinning 3cbd7a9 load on device 'cpu' took 2.04 ms  (cached)

anny.SMPLX model (55 bones, 13188 vertices)¶