# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: Apache-2.0
"""Concrete skeleton definitions: SOMA, G1, SMPLX with joint names and hierarchy."""
from pathlib import Path
import numpy as np
import torch
from ..tools import ensure_batched
from .base import SkeletonBase
[docs]
class SOMASkeleton77(SkeletonBase):
"""High-detail 77-joint SOMA skeleton with full finger and toe chains."""
name = "somaskel77"
right_foot_joint_names = [
"RightFoot",
"RightToeBase",
"RightToeEnd",
] # in order of chain
left_foot_joint_names = [
"LeftFoot",
"LeftToeBase",
"LeftToeEnd",
] # in order of chain
right_hand_joint_names = [
"RightHand",
"RightHandThumb1",
"RightHandThumb2",
"RightHandThumb3",
"RightHandThumbEnd",
"RightHandIndex1",
"RightHandIndex2",
"RightHandIndex3",
"RightHandIndex4",
"RightHandIndexEnd",
"RightHandMiddle1",
"RightHandMiddle2",
"RightHandMiddle3",
"RightHandMiddle4",
"RightHandMiddleEnd",
"RightHandRing1",
"RightHandRing2",
"RightHandRing3",
"RightHandRing4",
"RightHandRingEnd",
"RightHandPinky1",
"RightHandPinky2",
"RightHandPinky3",
"RightHandPinky4",
"RightHandPinkyEnd",
] # in order of chain
left_hand_joint_names = [
"LeftHand",
"LeftHandThumb1",
"LeftHandThumb2",
"LeftHandThumb3",
"LeftHandThumbEnd",
"LeftHandIndex1",
"LeftHandIndex2",
"LeftHandIndex3",
"LeftHandIndex4",
"LeftHandIndexEnd",
"LeftHandMiddle1",
"LeftHandMiddle2",
"LeftHandMiddle3",
"LeftHandMiddle4",
"LeftHandMiddleEnd",
"LeftHandRing1",
"LeftHandRing2",
"LeftHandRing3",
"LeftHandRing4",
"LeftHandRingEnd",
"LeftHandPinky1",
"LeftHandPinky2",
"LeftHandPinky3",
"LeftHandPinky4",
"LeftHandPinkyEnd",
] # in order of chain
hip_joint_names = ["RightLeg", "LeftLeg"] # in order [right, left]
bone_order_names_with_parents = [
("Hips", None),
("Spine1", "Hips"),
("Spine2", "Spine1"),
("Chest", "Spine2"),
("Neck1", "Chest"),
("Neck2", "Neck1"),
("Head", "Neck2"),
("HeadEnd", "Head"),
("Jaw", "Head"),
("LeftEye", "Head"),
("RightEye", "Head"),
("LeftShoulder", "Chest"),
("LeftArm", "LeftShoulder"),
("LeftForeArm", "LeftArm"),
("LeftHand", "LeftForeArm"),
("LeftHandThumb1", "LeftHand"),
("LeftHandThumb2", "LeftHandThumb1"),
("LeftHandThumb3", "LeftHandThumb2"),
("LeftHandThumbEnd", "LeftHandThumb3"),
("LeftHandIndex1", "LeftHand"),
("LeftHandIndex2", "LeftHandIndex1"),
("LeftHandIndex3", "LeftHandIndex2"),
("LeftHandIndex4", "LeftHandIndex3"),
("LeftHandIndexEnd", "LeftHandIndex4"),
("LeftHandMiddle1", "LeftHand"),
("LeftHandMiddle2", "LeftHandMiddle1"),
("LeftHandMiddle3", "LeftHandMiddle2"),
("LeftHandMiddle4", "LeftHandMiddle3"),
("LeftHandMiddleEnd", "LeftHandMiddle4"),
("LeftHandRing1", "LeftHand"),
("LeftHandRing2", "LeftHandRing1"),
("LeftHandRing3", "LeftHandRing2"),
("LeftHandRing4", "LeftHandRing3"),
("LeftHandRingEnd", "LeftHandRing4"),
("LeftHandPinky1", "LeftHand"),
("LeftHandPinky2", "LeftHandPinky1"),
("LeftHandPinky3", "LeftHandPinky2"),
("LeftHandPinky4", "LeftHandPinky3"),
("LeftHandPinkyEnd", "LeftHandPinky4"),
("RightShoulder", "Chest"),
("RightArm", "RightShoulder"),
("RightForeArm", "RightArm"),
("RightHand", "RightForeArm"),
("RightHandThumb1", "RightHand"),
("RightHandThumb2", "RightHandThumb1"),
("RightHandThumb3", "RightHandThumb2"),
("RightHandThumbEnd", "RightHandThumb3"),
("RightHandIndex1", "RightHand"),
("RightHandIndex2", "RightHandIndex1"),
("RightHandIndex3", "RightHandIndex2"),
("RightHandIndex4", "RightHandIndex3"),
("RightHandIndexEnd", "RightHandIndex4"),
("RightHandMiddle1", "RightHand"),
("RightHandMiddle2", "RightHandMiddle1"),
("RightHandMiddle3", "RightHandMiddle2"),
("RightHandMiddle4", "RightHandMiddle3"),
("RightHandMiddleEnd", "RightHandMiddle4"),
("RightHandRing1", "RightHand"),
("RightHandRing2", "RightHandRing1"),
("RightHandRing3", "RightHandRing2"),
("RightHandRing4", "RightHandRing3"),
("RightHandRingEnd", "RightHandRing4"),
("RightHandPinky1", "RightHand"),
("RightHandPinky2", "RightHandPinky1"),
("RightHandPinky3", "RightHandPinky2"),
("RightHandPinky4", "RightHandPinky3"),
("RightHandPinkyEnd", "RightHandPinky4"),
("LeftLeg", "Hips"),
("LeftShin", "LeftLeg"),
("LeftFoot", "LeftShin"),
("LeftToeBase", "LeftFoot"),
("LeftToeEnd", "LeftToeBase"),
("RightLeg", "Hips"),
("RightShin", "RightLeg"),
("RightFoot", "RightShin"),
("RightToeBase", "RightFoot"),
("RightToeEnd", "RightToeBase"),
]
@property
def relaxed_hands_rest_pose(self):
# lazy loading
if hasattr(self, "_relaxed_hands_rest_pose"):
return self._relaxed_hands_rest_pose
relaxed_hands_pose_path = Path(self.folder) / "relaxed_hands_rest_pose.npy"
relaxed_hands_rest_pose = torch.from_numpy(np.load(relaxed_hands_pose_path)).squeeze()
self.register_buffer(
"_relaxed_hands_rest_pose",
relaxed_hands_rest_pose,
persistent=False,
)
return self._relaxed_hands_rest_pose
[docs]
class SOMASkeleton30(SkeletonBase):
"""Compact 30-joint SOMA variant with reduced hand and end-effector detail."""
name = "somaskel30"
right_foot_joint_names = [
"RightFoot",
"RightToeBase",
] # in order of chain
left_foot_joint_names = [
"LeftFoot",
"LeftToeBase",
] # in order of chain
right_hand_joint_names = [
"RightHand",
"RightHandMiddleEnd",
] # in order of chain
left_hand_joint_names = [
"LeftHand",
"LeftHandMiddleEnd",
] # in order of chain
hip_joint_names = ["RightLeg", "LeftLeg"] # in order [right, left]
bone_order_names_with_parents = [
("Hips", None),
("Spine1", "Hips"),
("Spine2", "Spine1"),
("Chest", "Spine2"),
("Neck1", "Chest"),
("Neck2", "Neck1"),
("Head", "Neck2"),
("Jaw", "Head"),
("LeftEye", "Head"),
("RightEye", "Head"),
("LeftShoulder", "Chest"),
("LeftArm", "LeftShoulder"),
("LeftForeArm", "LeftArm"),
("LeftHand", "LeftForeArm"),
("LeftHandThumbEnd", "LeftHand"),
("LeftHandMiddleEnd", "LeftHand"),
("RightShoulder", "Chest"),
("RightArm", "RightShoulder"),
("RightForeArm", "RightArm"),
("RightHand", "RightForeArm"),
("RightHandThumbEnd", "RightHand"),
("RightHandMiddleEnd", "RightHand"),
("LeftLeg", "Hips"),
("LeftShin", "LeftLeg"),
("LeftFoot", "LeftShin"),
("LeftToeBase", "LeftFoot"),
("RightLeg", "Hips"),
("RightShin", "RightLeg"),
("RightFoot", "RightShin"),
("RightToeBase", "RightFoot"),
]
@property
def somaskel77(self):
# lazy loading
if not hasattr(self, "_somaskel77"):
self._somaskel77 = SOMASkeleton77()
return self._somaskel77
[docs]
@ensure_batched(local_joint_rots_subset=4)
def to_SOMASkeleton77(self, local_joint_rots_subset: torch.Tensor):
# Converting from 30-joint to 77-joint to have relaxed hands
device = local_joint_rots_subset.device
nF = len(local_joint_rots_subset)
local_joint_rots_mats = self.somaskel77.relaxed_hands_rest_pose.clone().to(device).repeat(nF, 1, 1, 1)
skel_slice = self.get_skel_slice(self.somaskel77)
local_joint_rots_mats[:, skel_slice] = local_joint_rots_subset
return local_joint_rots_mats
[docs]
@ensure_batched(local_joint_rots_full=4)
def from_SOMASkeleton77(self, local_joint_rots_full: torch.Tensor) -> torch.Tensor:
"""Extract the 30-joint subset from 77-joint local rotation data."""
skel_slice = self.get_skel_slice(self.somaskel77)
return local_joint_rots_full[:, skel_slice]
[docs]
def output_to_SOMASkeleton77(self, output: dict) -> dict:
"""Convert model output dict from somaskel30 to somaskel77.
Expands local_rot_mats to 77 joints, re-runs FK for global_rot_mats and posed_joints. Root
and foot-contact keys are unchanged.
"""
local_rot_mats_77 = self.to_SOMASkeleton77(output["local_rot_mats"])
root_positions = output["root_positions"]
global_rot_mats_77, posed_joints_77, _ = self.somaskel77.fk(local_rot_mats_77, root_positions)
out_77 = dict(output)
out_77["local_rot_mats"] = local_rot_mats_77
out_77["global_rot_mats"] = global_rot_mats_77
out_77["posed_joints"] = posed_joints_77
return out_77
[docs]
class G1Skeleton34(SkeletonBase):
"""Unitree G1 skeleton with 32 articulated joints plus 2 toe endpoints."""
name = "g1skel34"
right_foot_joint_names = ["right_ankle_roll_skel", "right_toe_base"]
left_foot_joint_names = ["left_ankle_roll_skel", "left_toe_base"]
right_hand_joint_names = ["right_wrist_yaw_skel", "right_hand_roll_skel"]
left_hand_joint_names = ["left_wrist_yaw_skel", "left_hand_roll_skel"]
hip_joint_names = [
"right_hip_pitch_skel",
"left_hip_pitch_skel",
] # used to calculate root orientation, only need 1 pair of hip joints
bone_order_names_with_parents = [
("pelvis_skel", None),
("left_hip_pitch_skel", "pelvis_skel"),
("left_hip_roll_skel", "left_hip_pitch_skel"),
("left_hip_yaw_skel", "left_hip_roll_skel"),
("left_knee_skel", "left_hip_yaw_skel"),
("left_ankle_pitch_skel", "left_knee_skel"),
("left_ankle_roll_skel", "left_ankle_pitch_skel"),
("left_toe_base", "left_ankle_roll_skel"),
("right_hip_pitch_skel", "pelvis_skel"),
("right_hip_roll_skel", "right_hip_pitch_skel"),
("right_hip_yaw_skel", "right_hip_roll_skel"),
("right_knee_skel", "right_hip_yaw_skel"),
("right_ankle_pitch_skel", "right_knee_skel"),
("right_ankle_roll_skel", "right_ankle_pitch_skel"),
("right_toe_base", "right_ankle_roll_skel"),
("waist_yaw_skel", "pelvis_skel"),
("waist_roll_skel", "waist_yaw_skel"),
("waist_pitch_skel", "waist_roll_skel"),
("left_shoulder_pitch_skel", "waist_pitch_skel"),
("left_shoulder_roll_skel", "left_shoulder_pitch_skel"),
("left_shoulder_yaw_skel", "left_shoulder_roll_skel"),
("left_elbow_skel", "left_shoulder_yaw_skel"),
("left_wrist_roll_skel", "left_elbow_skel"),
("left_wrist_pitch_skel", "left_wrist_roll_skel"),
("left_wrist_yaw_skel", "left_wrist_pitch_skel"),
("left_hand_roll_skel", "left_wrist_yaw_skel"),
("right_shoulder_pitch_skel", "waist_pitch_skel"),
("right_shoulder_roll_skel", "right_shoulder_pitch_skel"),
("right_shoulder_yaw_skel", "right_shoulder_roll_skel"),
("right_elbow_skel", "right_shoulder_yaw_skel"),
("right_wrist_roll_skel", "right_elbow_skel"),
("right_wrist_pitch_skel", "right_wrist_roll_skel"),
("right_wrist_yaw_skel", "right_wrist_pitch_skel"),
("right_hand_roll_skel", "right_wrist_yaw_skel"),
]
[docs]
class SMPLXSkeleton22(SkeletonBase):
"""SMPL-X skeleton with body-only 22 joints."""
name = "smplx22"
right_foot_joint_names = ["right_ankle", "right_foot"] # in order of chain
left_foot_joint_names = ["left_ankle", "left_foot"] # in order of chain
right_hand_joint_names = ["right_wrist"] # in order of chain
left_hand_joint_names = ["left_wrist"] # in order of chain
hip_joint_names = ["right_hip", "left_hip"] # in order [right, left]
bone_order_names_with_parents = [
("pelvis", None),
("left_hip", "pelvis"),
("right_hip", "pelvis"),
("spine1", "pelvis"),
("left_knee", "left_hip"),
("right_knee", "right_hip"),
("spine2", "spine1"),
("left_ankle", "left_knee"),
("right_ankle", "right_knee"),
("spine3", "spine2"),
("left_foot", "left_ankle"),
("right_foot", "right_ankle"),
("neck", "spine3"),
("left_collar", "spine3"),
("right_collar", "spine3"),
("head", "neck"),
("left_shoulder", "left_collar"),
("right_shoulder", "right_collar"),
("left_elbow", "left_shoulder"),
("right_elbow", "right_shoulder"),
("left_wrist", "left_elbow"),
("right_wrist", "right_elbow"),
]