From 681d513cfe296caed5e512f36887ac5465d1a984 Mon Sep 17 00:00:00 2001 From: bricksdont Date: Fri, 19 Jun 2026 11:11:14 +0200 Subject: [PATCH 1/2] Add COCO Wholebody 133 keypoint schema and generic.py support Introduces cocowholebody133_header.py as a shared schema definition for the 133-keypoint COCO Wholebody format (23 body + 68 face + 21 left hand + 21 right hand), and registers it as a known pose format in generic.py with full support across detection, leg hiding, shoulder/hand utilities, and normalization helpers. Also fixes several bugs present in the upstream fork: incorrect component name ("pose_keypoints_2d" instead of "BODY") and string-index point names in pose_hide_legs, wrong hand component name suffix in get_hand_wrist_index, and the missing coco_wholebody_133 case in get_standard_components_for_known_format. Co-Authored-By: catherine-o-brien Co-Authored-By: GerrySant Co-Authored-By: Claude Sonnet 4.6 --- .../utils/cocowholebody133_header.py | 121 ++++++++++++++++++ src/python/pose_format/utils/generic.py | 36 +++++- src/python/pose_format/utils/generic_test.py | 38 ++++++ 3 files changed, 194 insertions(+), 1 deletion(-) create mode 100644 src/python/pose_format/utils/cocowholebody133_header.py diff --git a/src/python/pose_format/utils/cocowholebody133_header.py b/src/python/pose_format/utils/cocowholebody133_header.py new file mode 100644 index 0000000..8afb116 --- /dev/null +++ b/src/python/pose_format/utils/cocowholebody133_header.py @@ -0,0 +1,121 @@ +from ..pose_header import PoseHeaderComponent + +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_POINTS = [f"face-{i}" for i in range(68)] +LEFT_HAND_POINTS = [f"left_hand_{i}" for i in range(21)] +RIGHT_HAND_POINTS = [f"right_hand_{i}" for i in range(21)] + + +def cocowholebody_components(): + """ + Creates a list of COCO-Wholebody 133 components. + + Returns + ------- + list of PoseHeaderComponent + List of COCO-Wholebody 133 components. + """ + + def map_limbs(points, limbs): + index_map = {name: idx for idx, name in enumerate(points)} + return [ + (index_map[a], index_map[b]) + for (a, b) in limbs + ] + + 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"), + ] + + LEFT_HAND_LIMBS_NAMES = [ + ("left_hand_0", "left_hand_1"), ("left_hand_1", "left_hand_2"), + ("left_hand_2", "left_hand_3"), ("left_hand_3", "left_hand_4"), + ("left_hand_0", "left_hand_5"), ("left_hand_5", "left_hand_6"), + ("left_hand_6", "left_hand_7"), ("left_hand_7", "left_hand_8"), + ("left_hand_0", "left_hand_9"), ("left_hand_9", "left_hand_10"), + ("left_hand_10", "left_hand_11"), ("left_hand_11", "left_hand_12"), + ("left_hand_0", "left_hand_13"), ("left_hand_13", "left_hand_14"), + ("left_hand_14", "left_hand_15"), ("left_hand_15", "left_hand_16"), + ("left_hand_0", "left_hand_17"), ("left_hand_17", "left_hand_18"), + ("left_hand_18", "left_hand_19"), ("left_hand_19", "left_hand_20"), + ] + + RIGHT_HAND_LIMBS_NAMES = [ + ("right_hand_0", "right_hand_1"), ("right_hand_1", "right_hand_2"), + ("right_hand_2", "right_hand_3"), ("right_hand_3", "right_hand_4"), + ("right_hand_0", "right_hand_5"), ("right_hand_5", "right_hand_6"), + ("right_hand_6", "right_hand_7"), ("right_hand_7", "right_hand_8"), + ("right_hand_0", "right_hand_9"), ("right_hand_9", "right_hand_10"), + ("right_hand_10", "right_hand_11"), ("right_hand_11", "right_hand_12"), + ("right_hand_0", "right_hand_13"), ("right_hand_13", "right_hand_14"), + ("right_hand_14", "right_hand_15"), ("right_hand_15", "right_hand_16"), + ("right_hand_0", "right_hand_17"), ("right_hand_17", "right_hand_18"), + ("right_hand_18", "right_hand_19"), ("right_hand_19", "right_hand_20"), + ] + + components = [ + PoseHeaderComponent( + name="BODY", + points=BODY_POINTS, + limbs=map_limbs(BODY_POINTS, BODY_LIMBS_NAMES), + colors=[(0, 255, 0)], + point_format="XYC" + ), + + PoseHeaderComponent( + name="FACE", + points=FACE_POINTS, + limbs=[], + colors=[(255, 255, 255)], + point_format="XYC" + ), + + PoseHeaderComponent( + name="LEFT_HAND", + points=LEFT_HAND_POINTS, + limbs=map_limbs(LEFT_HAND_POINTS, LEFT_HAND_LIMBS_NAMES), + colors=[(0, 255, 0)], + point_format="XYC" + ), + + PoseHeaderComponent( + name="RIGHT_HAND", + points=RIGHT_HAND_POINTS, + limbs=map_limbs(RIGHT_HAND_POINTS, RIGHT_HAND_LIMBS_NAMES), + colors=[(255, 128, 0)], + point_format="XYC" + ), + ] + return components diff --git a/src/python/pose_format/utils/generic.py b/src/python/pose_format/utils/generic.py index 357aedb..f123a8c 100644 --- a/src/python/pose_format/utils/generic.py +++ b/src/python/pose_format/utils/generic.py @@ -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]: @@ -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}" ) @@ -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}" @@ -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}" ) @@ -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", "left_hand_9"), + pose_header.get_point_index("RIGHT_HAND", "right_hand_9"), + ] raise NotImplementedError( f"Unsupported pose header schema {known_pose_format} for {hands_indexes.__name__}: {pose_header}" ) @@ -198,6 +222,10 @@ 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": + # Note: LEFT_HAND uses "left_hand_N" naming, RIGHT_HAND uses "right_hand_N". + # normalize_hands_3d only supports left hand normalization with these plane/line names. + return ("LEFT_HAND", "RIGHT_HAND"), ("left_hand_0", "left_hand_17", "left_hand_5"), ("left_hand_0", "left_hand_9") raise NotImplementedError( f"Unsupported pose header schema '{known_pose_format}' for {hands_components.__name__}: {pose_header}" ) @@ -247,6 +275,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}") @@ -315,6 +345,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", f"{hand.lower()}_hand_0") raise NotImplementedError( f"Unsupported pose header schema {known_pose_format} for {get_hand_wrist_index.__name__}: {pose.header}" ) @@ -329,6 +361,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}" ) diff --git a/src/python/pose_format/utils/generic_test.py b/src/python/pose_format/utils/generic_test.py index c072bc8..63f57aa 100644 --- a/src/python/pose_format/utils/generic_test.py +++ b/src/python/pose_format/utils/generic_test.py @@ -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, @@ -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 @@ -290,6 +306,8 @@ 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 @@ -297,3 +315,23 @@ def test_fake_pose(known_pose_format: KnownPoseFormat): 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})" From 8f24bd220ec87eaebdd97a7fa5e986f582653e79 Mon Sep 17 00:00:00 2001 From: bricksdont Date: Fri, 19 Jun 2026 14:28:45 +0200 Subject: [PATCH 2/2] Consolidate COCO Wholebody 133 schema: one canonical definition, alphapose_133 derives from it Research confirmed that AlphaPose 133, MMPose wholebody, OpenPifPaf wholebody, and SDPose all output the same COCO Wholebody 133 keypoints in the same order. The previous alphapose_133.py duplicated the schema definition with minor naming inconsistencies (face_N underscore, side-prefixed hand point names, no face limbs). cocowholebody133_header.py is now the single source of truth for the 133-keypoint COCO Wholebody format: - HAND_POINTS uses shared "hand_N" naming (same for both left/right components), matching the AlphaPose convention and enabling normalize_hands_3d to work correctly - FACE_LIMBS_NAMES adds full face connectivity (jaw, brows, eyes, nose, lips) - All limb index pairs are pre-computed as module-level constants (BODY_LIMBS, FACE_LIMBS, HAND_LIMBS) and exported for downstream importers alphapose_133.py now imports BODY_POINTS, FACE_POINTS, HAND_POINTS and pre-computed limb pairs from cocowholebody133_header.py, keeping only its _133-suffixed component names for backward compatibility. generic.py hand point lookups for coco_wholebody_133 updated to use "hand_N" names. Co-Authored-By: catherine-o-brien Co-Authored-By: GerrySant Co-Authored-By: Claude Sonnet 4.6 --- src/python/pose_format/utils/alphapose_133.py | 50 ++---- .../utils/cocowholebody133_header.py | 163 +++++++++--------- src/python/pose_format/utils/generic.py | 10 +- 3 files changed, 98 insertions(+), 125 deletions(-) diff --git a/src/python/pose_format/utils/alphapose_133.py b/src/python/pose_format/utils/alphapose_133.py index 8ceec93..78168d4 100644 --- a/src/python/pose_format/utils/alphapose_133.py +++ b/src/python/pose_format/utils/alphapose_133.py @@ -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" ), diff --git a/src/python/pose_format/utils/cocowholebody133_header.py b/src/python/pose_format/utils/cocowholebody133_header.py index 8afb116..d242e16 100644 --- a/src/python/pose_format/utils/cocowholebody133_header.py +++ b/src/python/pose_format/utils/cocowholebody133_header.py @@ -1,121 +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", + "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)] -LEFT_HAND_POINTS = [f"left_hand_{i}" for i in range(21)] -RIGHT_HAND_POINTS = [f"right_hand_{i}" for i in range(21)] + +# 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 a list of COCO-Wholebody 133 components. + Creates the four PoseHeaderComponent objects for COCO Wholebody 133. Returns ------- list of PoseHeaderComponent - List of COCO-Wholebody 133 components. + [BODY (23 pts), FACE (68 pts), LEFT_HAND (21 pts), RIGHT_HAND (21 pts)] """ - - def map_limbs(points, limbs): - index_map = {name: idx for idx, name in enumerate(points)} - return [ - (index_map[a], index_map[b]) - for (a, b) in limbs - ] - - 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"), - ] - - LEFT_HAND_LIMBS_NAMES = [ - ("left_hand_0", "left_hand_1"), ("left_hand_1", "left_hand_2"), - ("left_hand_2", "left_hand_3"), ("left_hand_3", "left_hand_4"), - ("left_hand_0", "left_hand_5"), ("left_hand_5", "left_hand_6"), - ("left_hand_6", "left_hand_7"), ("left_hand_7", "left_hand_8"), - ("left_hand_0", "left_hand_9"), ("left_hand_9", "left_hand_10"), - ("left_hand_10", "left_hand_11"), ("left_hand_11", "left_hand_12"), - ("left_hand_0", "left_hand_13"), ("left_hand_13", "left_hand_14"), - ("left_hand_14", "left_hand_15"), ("left_hand_15", "left_hand_16"), - ("left_hand_0", "left_hand_17"), ("left_hand_17", "left_hand_18"), - ("left_hand_18", "left_hand_19"), ("left_hand_19", "left_hand_20"), - ] - - RIGHT_HAND_LIMBS_NAMES = [ - ("right_hand_0", "right_hand_1"), ("right_hand_1", "right_hand_2"), - ("right_hand_2", "right_hand_3"), ("right_hand_3", "right_hand_4"), - ("right_hand_0", "right_hand_5"), ("right_hand_5", "right_hand_6"), - ("right_hand_6", "right_hand_7"), ("right_hand_7", "right_hand_8"), - ("right_hand_0", "right_hand_9"), ("right_hand_9", "right_hand_10"), - ("right_hand_10", "right_hand_11"), ("right_hand_11", "right_hand_12"), - ("right_hand_0", "right_hand_13"), ("right_hand_13", "right_hand_14"), - ("right_hand_14", "right_hand_15"), ("right_hand_15", "right_hand_16"), - ("right_hand_0", "right_hand_17"), ("right_hand_17", "right_hand_18"), - ("right_hand_18", "right_hand_19"), ("right_hand_19", "right_hand_20"), - ] - - components = [ + return [ PoseHeaderComponent( name="BODY", points=BODY_POINTS, - limbs=map_limbs(BODY_POINTS, BODY_LIMBS_NAMES), + limbs=BODY_LIMBS, colors=[(0, 255, 0)], point_format="XYC" ), - PoseHeaderComponent( name="FACE", points=FACE_POINTS, - limbs=[], + limbs=FACE_LIMBS, colors=[(255, 255, 255)], point_format="XYC" ), - PoseHeaderComponent( name="LEFT_HAND", - points=LEFT_HAND_POINTS, - limbs=map_limbs(LEFT_HAND_POINTS, LEFT_HAND_LIMBS_NAMES), + points=HAND_POINTS, + limbs=HAND_LIMBS, colors=[(0, 255, 0)], point_format="XYC" ), - PoseHeaderComponent( name="RIGHT_HAND", - points=RIGHT_HAND_POINTS, - limbs=map_limbs(RIGHT_HAND_POINTS, RIGHT_HAND_LIMBS_NAMES), + points=HAND_POINTS, + limbs=HAND_LIMBS, colors=[(255, 128, 0)], point_format="XYC" ), ] - return components diff --git a/src/python/pose_format/utils/generic.py b/src/python/pose_format/utils/generic.py index f123a8c..5316a1c 100644 --- a/src/python/pose_format/utils/generic.py +++ b/src/python/pose_format/utils/generic.py @@ -194,8 +194,8 @@ def hands_indexes(pose_header: PoseHeader)-> List[int]: ] if known_pose_format == "coco_wholebody_133": return [ - pose_header.get_point_index("LEFT_HAND", "left_hand_9"), - pose_header.get_point_index("RIGHT_HAND", "right_hand_9"), + 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}" @@ -223,9 +223,7 @@ def hands_components(pose_header: PoseHeader)-> Tuple[Tuple[str, str], Tuple[str 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": - # Note: LEFT_HAND uses "left_hand_N" naming, RIGHT_HAND uses "right_hand_N". - # normalize_hands_3d only supports left hand normalization with these plane/line names. - return ("LEFT_HAND", "RIGHT_HAND"), ("left_hand_0", "left_hand_17", "left_hand_5"), ("left_hand_0", "left_hand_9") + 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}" ) @@ -346,7 +344,7 @@ def get_hand_wrist_index(pose: Pose, hand: str)-> int: 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", f"{hand.lower()}_hand_0") + 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}" )