Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 14 additions & 36 deletions src/python/pose_format/utils/alphapose_133.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,74 +2,52 @@
from ..numpy.pose_body import NumPyPoseBody
from ..pose import Pose
from ..pose_header import PoseHeader, PoseHeaderComponent, PoseHeaderDimensions
from .alphapose import (
FACE_POINTS, FACE_LIMBS_NAMES, GENERAL_HAND_POINTS,
LEFT_HAND_POINTS, RIGHT_HAND_POINTS, HAND_LIMBS_NAMES,
_map_limbs, load_alphapose_json, parse_keypoints_and_confidence, _apply_metadata,
from .alphapose import load_alphapose_json, parse_keypoints_and_confidence, _apply_metadata
from .cocowholebody133_header import (
BODY_POINTS, FACE_POINTS, HAND_POINTS,
BODY_LIMBS, FACE_LIMBS, HAND_LIMBS,
)

# 133-keypoint body (no neck, head_top, or pelvis compared to 136)
BODY_POINTS = [
"nose", "left_eye", "right_eye", "left_ear", "right_ear",
"left_shoulder", "right_shoulder", "left_elbow", "right_elbow",
"left_wrist", "right_wrist", "left_hip", "right_hip",
"left_knee", "right_knee", "left_ankle", "right_ankle",
"left_big_toe", "left_small_toe", "left_heel",
"right_big_toe", "right_small_toe", "right_heel",
]

BODY_LIMBS_NAMES = [
("left_ankle", "left_knee"), ("left_knee", "left_hip"),
("right_ankle", "right_knee"), ("right_knee", "right_hip"),
("left_hip", "right_hip"),
("left_shoulder", "left_hip"), ("right_shoulder", "right_hip"),
("left_shoulder", "right_shoulder"),
("left_shoulder", "left_elbow"), ("right_shoulder", "right_elbow"),
("left_elbow", "left_wrist"), ("right_elbow", "right_wrist"),
("left_eye", "right_eye"), ("nose", "left_eye"), ("nose", "right_eye"),
("left_eye", "left_ear"), ("right_eye", "right_ear"),
("left_ear", "left_shoulder"), ("right_ear", "right_shoulder"),
("left_ankle", "left_big_toe"), ("left_ankle", "left_small_toe"), ("left_ankle", "left_heel"),
("right_ankle", "right_big_toe"), ("right_ankle", "right_small_toe"), ("right_ankle", "right_heel"),
]


def get_alphapose_133_components():
"""
Returns AlphaPose WholeBody-133 component definitions.

AlphaPose 133 is the COCO Wholebody 133-keypoint format. Point lists and
limb connectivity are shared with cocowholebody133_header; only the component
names differ (suffixed with _133 for backward compatibility).

Returns
-------
list of PoseHeaderComponent
Components for body, face, left hand, and right hand.
"""
hand_limbs = _map_limbs(GENERAL_HAND_POINTS, HAND_LIMBS_NAMES)
return [
PoseHeaderComponent(
name="BODY_133",
points=BODY_POINTS,
limbs=_map_limbs(BODY_POINTS, BODY_LIMBS_NAMES),
limbs=BODY_LIMBS,
colors=[(0, 255, 0)],
point_format="XYC"
),
PoseHeaderComponent(
name="FACE_133",
points=FACE_POINTS,
limbs=_map_limbs(FACE_POINTS, FACE_LIMBS_NAMES),
limbs=FACE_LIMBS,
colors=[(255, 255, 255)],
point_format="XYC"
),
PoseHeaderComponent(
name="LEFT_HAND_133",
points=GENERAL_HAND_POINTS,
limbs=hand_limbs,
points=HAND_POINTS,
limbs=HAND_LIMBS,
colors=[(0, 255, 0)],
point_format="XYC"
),
PoseHeaderComponent(
name="RIGHT_HAND_133",
points=GENERAL_HAND_POINTS,
limbs=hand_limbs,
points=HAND_POINTS,
limbs=HAND_LIMBS,
colors=[(255, 128, 0)],
point_format="XYC"
),
Expand Down
118 changes: 118 additions & 0 deletions src/python/pose_format/utils/cocowholebody133_header.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
from ..pose_header import PoseHeaderComponent

# --- Canonical COCO Wholebody 133 point lists ---

BODY_POINTS = [
"nose", "left_eye", "right_eye", "left_ear", "right_ear",
"left_shoulder", "right_shoulder", "left_elbow", "right_elbow",
"left_wrist", "right_wrist", "left_hip", "right_hip",
"left_knee", "right_knee", "left_ankle", "right_ankle",
"left_big_toe", "left_small_toe", "left_heel",
"right_big_toe", "right_small_toe", "right_heel",
]

# Face uses dash separator ("face-N") matching the COCO Wholebody / MMPose convention.
FACE_POINTS = [f"face-{i}" for i in range(68)]

# Single hand point list shared by both LEFT_HAND and RIGHT_HAND components.
# The component name (LEFT_HAND / RIGHT_HAND) carries the side; point names are generic.
# This mirrors the AlphaPose convention and allows normalize_hands_3d to work correctly.
HAND_POINTS = [f"hand_{i}" for i in range(21)]


# --- Limb connectivity ---

def _map_limbs(points, limb_names):
index_map = {name: idx for idx, name in enumerate(points)}
return [(index_map[a], index_map[b]) for (a, b) in limb_names]


BODY_LIMBS_NAMES = [
("left_ankle", "left_knee"), ("left_knee", "left_hip"),
("right_ankle", "right_knee"), ("right_knee", "right_hip"),
("left_hip", "right_hip"),
("left_shoulder", "left_hip"), ("right_shoulder", "right_hip"),
("left_shoulder", "right_shoulder"),
("left_shoulder", "left_elbow"), ("right_shoulder", "right_elbow"),
("left_elbow", "left_wrist"), ("right_elbow", "right_wrist"),
("left_eye", "right_eye"), ("nose", "left_eye"), ("nose", "right_eye"),
("left_eye", "left_ear"), ("right_eye", "right_ear"),
("left_ear", "left_shoulder"), ("right_ear", "right_shoulder"),
("left_ankle", "left_big_toe"), ("left_ankle", "left_small_toe"), ("left_ankle", "left_heel"),
("right_ankle", "right_big_toe"), ("right_ankle", "right_small_toe"), ("right_ankle", "right_heel"),
]

# 68-point face connectivity (jaw, brows, nose bridge, eyes, lips).
FACE_LIMBS_NAMES = [
("face-0", "face-1"), ("face-1", "face-2"), ("face-2", "face-3"), ("face-3", "face-4"),
("face-4", "face-5"), ("face-5", "face-6"), ("face-6", "face-7"), ("face-7", "face-8"),
("face-8", "face-9"), ("face-9", "face-10"), ("face-10", "face-11"), ("face-11", "face-12"),
("face-12", "face-13"), ("face-13", "face-14"), ("face-14", "face-15"), ("face-15", "face-16"),
("face-17", "face-18"), ("face-18", "face-19"), ("face-19", "face-20"), ("face-20", "face-21"),
("face-22", "face-23"), ("face-23", "face-24"), ("face-24", "face-25"), ("face-25", "face-26"),
("face-27", "face-28"), ("face-28", "face-29"), ("face-29", "face-30"),
("face-31", "face-32"), ("face-32", "face-33"), ("face-33", "face-34"), ("face-34", "face-35"),
("face-36", "face-37"), ("face-37", "face-38"), ("face-38", "face-39"),
("face-39", "face-40"), ("face-40", "face-41"),
("face-42", "face-43"), ("face-43", "face-44"), ("face-44", "face-45"),
("face-45", "face-46"), ("face-46", "face-47"),
("face-48", "face-49"), ("face-49", "face-50"), ("face-50", "face-51"), ("face-51", "face-52"),
("face-52", "face-53"), ("face-53", "face-54"), ("face-54", "face-55"), ("face-55", "face-56"),
("face-56", "face-57"), ("face-57", "face-58"), ("face-58", "face-59"), ("face-59", "face-60"),
("face-60", "face-61"), ("face-61", "face-62"), ("face-62", "face-63"), ("face-63", "face-64"),
("face-64", "face-65"), ("face-65", "face-66"), ("face-66", "face-67"),
]

HAND_LIMBS_NAMES = [
("hand_0", "hand_1"), ("hand_1", "hand_2"), ("hand_2", "hand_3"), ("hand_3", "hand_4"),
("hand_0", "hand_5"), ("hand_5", "hand_6"), ("hand_6", "hand_7"), ("hand_7", "hand_8"),
("hand_0", "hand_9"), ("hand_9", "hand_10"), ("hand_10", "hand_11"), ("hand_11", "hand_12"),
("hand_0", "hand_13"), ("hand_13", "hand_14"), ("hand_14", "hand_15"), ("hand_15", "hand_16"),
("hand_0", "hand_17"), ("hand_17", "hand_18"), ("hand_18", "hand_19"), ("hand_19", "hand_20"),
]

# Pre-computed limb index pairs — import these instead of recomputing.
BODY_LIMBS = _map_limbs(BODY_POINTS, BODY_LIMBS_NAMES)
FACE_LIMBS = _map_limbs(FACE_POINTS, FACE_LIMBS_NAMES)
HAND_LIMBS = _map_limbs(HAND_POINTS, HAND_LIMBS_NAMES)


def cocowholebody_components():
"""
Creates the four PoseHeaderComponent objects for COCO Wholebody 133.

Returns
-------
list of PoseHeaderComponent
[BODY (23 pts), FACE (68 pts), LEFT_HAND (21 pts), RIGHT_HAND (21 pts)]
"""
return [
PoseHeaderComponent(
name="BODY",
points=BODY_POINTS,
limbs=BODY_LIMBS,
colors=[(0, 255, 0)],
point_format="XYC"
),
PoseHeaderComponent(
name="FACE",
points=FACE_POINTS,
limbs=FACE_LIMBS,
colors=[(255, 255, 255)],
point_format="XYC"
),
PoseHeaderComponent(
name="LEFT_HAND",
points=HAND_POINTS,
limbs=HAND_LIMBS,
colors=[(0, 255, 0)],
point_format="XYC"
),
PoseHeaderComponent(
name="RIGHT_HAND",
points=HAND_POINTS,
limbs=HAND_LIMBS,
colors=[(255, 128, 0)],
point_format="XYC"
),
]
34 changes: 33 additions & 1 deletion src/python/pose_format/utils/generic.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,12 @@
from pose_format.utils.openpose_135 import OpenPose_Components as OpenPose135_Components
from pose_format.utils.alphapose import get_alphapose_components
from pose_format.utils.alphapose_133 import get_alphapose_133_components
from pose_format.utils.cocowholebody133_header import cocowholebody_components

# from pose_format.utils.holistic import holistic_components
# The import above creates an error: ImportError: Please install mediapipe with: pip install mediapipe

KnownPoseFormat = Literal["holistic", "openpose", "openpose_135", "alphapose_133", "alphapose_136"]
KnownPoseFormat = Literal["holistic", "openpose", "openpose_135", "alphapose_133", "alphapose_136", "coco_wholebody_133"]

def get_component_names(
pose_or_header_or_components: Union[Pose,PoseHeader]) -> List[str]:
Expand Down Expand Up @@ -58,6 +59,11 @@ def detect_known_pose_format(pose_or_header: Union[Pose,PoseHeader]) -> KnownPos
if component_name in alphapose_136_components:
return "alphapose_136"

# COCO wholebody 133 uses "BODY", "FACE", "LEFT_HAND", "RIGHT_HAND" — distinct from alphapose ("BODY_133" etc.)
# When smplest-x or other formats using "BODY" are added, distinguish by body point names at that point.
if "BODY" in component_names and "LEFT_HAND" in component_names:
return "coco_wholebody_133"

raise ValueError(
f"Could not detect pose format, unknown pose header schema with component names: {component_names}"
)
Expand Down Expand Up @@ -107,6 +113,16 @@ def pose_hide_legs(pose: Pose, remove: bool = False) -> Pose:
]
points_to_remove_dict = {f"BODY_{variant}": point_names_to_remove}

elif known_pose_format == "coco_wholebody_133":
point_names_to_remove = [
"left_hip", "right_hip",
"left_knee", "right_knee",
"left_ankle", "right_ankle",
"left_big_toe", "left_small_toe", "left_heel",
"right_big_toe", "right_small_toe", "right_heel",
]
points_to_remove_dict = {"BODY": point_names_to_remove}

else:
raise NotImplementedError(
f"Unsupported pose header schema {known_pose_format} for {pose_hide_legs.__name__}: {pose.header}"
Expand Down Expand Up @@ -148,6 +164,9 @@ def pose_shoulders(pose_header: PoseHeader) -> Tuple[Tuple[str, str], Tuple[str,
variant = known_pose_format[len("alphapose_"):]
return (f"BODY_{variant}", "right_shoulder"), (f"BODY_{variant}", "left_shoulder")

if known_pose_format == "coco_wholebody_133":
return ("BODY", "right_shoulder"), ("BODY", "left_shoulder")

raise NotImplementedError(
f"Unsupported pose header schema {known_pose_format} for {pose_shoulders.__name__}: {pose_header}"
)
Expand All @@ -173,6 +192,11 @@ def hands_indexes(pose_header: PoseHeader)-> List[int]:
pose_header.get_point_index(f"LEFT_HAND_{variant}", "hand_9"),
pose_header.get_point_index(f"RIGHT_HAND_{variant}", "hand_9"),
]
if known_pose_format == "coco_wholebody_133":
return [
pose_header.get_point_index("LEFT_HAND", "hand_9"),
pose_header.get_point_index("RIGHT_HAND", "hand_9"),
]
raise NotImplementedError(
f"Unsupported pose header schema {known_pose_format} for {hands_indexes.__name__}: {pose_header}"
)
Expand All @@ -198,6 +222,8 @@ def hands_components(pose_header: PoseHeader)-> Tuple[Tuple[str, str], Tuple[str
if known_pose_format == "alphapose_133" or known_pose_format == "alphapose_136":
variant = known_pose_format[len("alphapose_"):]
return (f"LEFT_HAND_{variant}", f"RIGHT_HAND_{variant}"), ("hand_0", "hand_17", "hand_5"), ("hand_0", "hand_9")
if known_pose_format == "coco_wholebody_133":
return ("LEFT_HAND", "RIGHT_HAND"), ("hand_0", "hand_17", "hand_5"), ("hand_0", "hand_9")
raise NotImplementedError(
f"Unsupported pose header schema '{known_pose_format}' for {hands_components.__name__}: {pose_header}"
)
Expand Down Expand Up @@ -247,6 +273,8 @@ def get_standard_components_for_known_format(known_pose_format: KnownPoseFormat)
return get_alphapose_133_components()
if known_pose_format == "alphapose_136":
return get_alphapose_components()
if known_pose_format == "coco_wholebody_133":
return cocowholebody_components()

raise NotImplementedError(f"Unsupported pose header schema {known_pose_format}")

Expand Down Expand Up @@ -315,6 +343,8 @@ def get_hand_wrist_index(pose: Pose, hand: str)-> int:
if known_pose_format == "alphapose_133" or known_pose_format == "alphapose_136":
variant = known_pose_format[len("alphapose_"):]
return pose.header.get_point_index(f"{hand.upper()}_HAND_{variant}", "hand_0")
if known_pose_format == "coco_wholebody_133":
return pose.header.get_point_index(f"{hand.upper()}_HAND", "hand_0")
raise NotImplementedError(
f"Unsupported pose header schema {known_pose_format} for {get_hand_wrist_index.__name__}: {pose.header}"
)
Expand All @@ -329,6 +359,8 @@ def get_body_hand_wrist_index(pose: Pose, hand: str)-> int:
if known_pose_format == "alphapose_133" or known_pose_format == "alphapose_136":
variant = known_pose_format[len("alphapose_"):]
return pose.header.get_point_index(f"BODY_{variant}", f"{hand.lower()}_wrist")
if known_pose_format == "coco_wholebody_133":
return pose.header.get_point_index("BODY", f"{hand.lower()}_wrist")
raise NotImplementedError(
f"Unsupported pose header schema {known_pose_format} for {get_body_hand_wrist_index.__name__}: {pose.header}"
)
Expand Down
38 changes: 38 additions & 0 deletions src/python/pose_format/utils/generic_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import pytest
from pose_format.pose import Pose
from pose_format.pose_header import PoseNormalizationInfo
from pose_format.utils.cocowholebody133_header import cocowholebody_components
from pose_format.utils.generic import (
detect_known_pose_format,
get_component_names,
Expand Down Expand Up @@ -240,6 +241,21 @@ def test_pose_remove_legs(fake_poses: List[Pose]):
component_index = c_names.index(f"BODY_{known_pose_format[-3:]}")
pose_with_legs_removed = pose_hide_legs(pose, remove=True)

for point_name in points_that_should_be_removed:
assert point_name not in pose_with_legs_removed.header.components[component_index].points, f"{pose_with_legs_removed.header.components[component_index].name},{pose_with_legs_removed.header.components[component_index].points}"
assert point_name in pose.header.components[component_index].points
elif known_pose_format == "coco_wholebody_133":
c_names = [c.name for c in pose.header.components]
points_that_should_be_removed = [
"left_hip", "right_hip",
"left_knee", "right_knee",
"left_ankle", "right_ankle",
"left_big_toe", "left_small_toe", "left_heel",
"right_big_toe", "right_small_toe", "right_heel",
]
component_index = c_names.index("BODY")
pose_with_legs_removed = pose_hide_legs(pose, remove=True)

for point_name in points_that_should_be_removed:
assert point_name not in pose_with_legs_removed.header.components[component_index].points, f"{pose_with_legs_removed.header.components[component_index].name},{pose_with_legs_removed.header.components[component_index].points}"
assert point_name in pose.header.components[component_index].points
Expand Down Expand Up @@ -290,10 +306,32 @@ def test_fake_pose(known_pose_format: KnownPoseFormat):
assert point_formats[0] == "XYC"
elif detected_format == 'alphapose_133' or detected_format == 'alphapose_136':
assert point_formats[0] == "XYC"
elif detected_format == 'coco_wholebody_133':
assert point_formats[0] == "XYC"


assert detected_format == known_pose_format
assert pose.body.fps == fps
assert pose.body.data.shape == (frame_count, 1, pose.header.total_points(), data_dimension_expected)
assert pose.body.data.shape[0] == frame_count
assert pose.header.num_dims() == pose.body.data.shape[-1]


def test_cocowholebody133_total_keypoints():
components = cocowholebody_components()
total = sum(len(c.points) for c in components)
assert total == 133


def test_cocowholebody133_component_names():
components = cocowholebody_components()
names = [c.name for c in components]
assert names == ["BODY", "FACE", "LEFT_HAND", "RIGHT_HAND"]


def test_cocowholebody133_limb_indices_in_bounds():
for c in cocowholebody_components():
n = len(c.points)
for (a, b) in c.limbs:
assert 0 <= a < n, f"{c.name}: limb index {a} out of bounds (n={n})"
assert 0 <= b < n, f"{c.name}: limb index {b} out of bounds (n={n})"
Loading