#!/opt/homebrew/bin/python3.11 """ Body Action Decoder - Extended pose action analysis with body keypoints Purpose: 1. Decode face pose actions (existing) 2. Decode body actions (future MediaPipe Holistic) 3. Integrate face + body actions for comprehensive analysis Body Keypoints (MediaPipe Holistic): - Face: 468 points (eyes, mouth, nose, etc.) - Pose: 33 points (shoulders, elbows, hands, hips, knees, feet) - Hands: 21 points per hand Action Types: - Face: turn_left, turn_right, look_up, look_down, shake_head, nod_head - Eyes: blink, close, wide_open, look_left, look_right - Mouth: open, close, smile, talk, yawn - Arms: raise_left, raise_right, cross_arms, wave - Hands: point, grab, clap, thumbs_up, fist - Legs: stand, sit, walk, run, jump, kick - Feet: tap, stomp, cross Architecture: ┌─────────────────────────────────────────────────────────────────┐ │ Body Action Decoder │ ├─────────────────────────────────────────────────────────────────┤ │ │ │ ┌───────────────┐ ┌───────────────┐ ┌───────────────┐ │ │ │ Face Actions │ │ Body Actions │ │ Hand Actions │ │ │ │ (InsightFace) │ │ (MediaPipe) │ │ (MediaPipe) │ │ │ └───────────────┘ └───────────────┘ └───────────────┘ │ │ │ │ │ │ │ └──────────────────┼──────────────────┘ │ │ │ │ │ ┌───────▼───────┐ │ │ │ Action Merger│ │ │ └────────────────┘ │ │ │ │ │ ┌───────▼───────┐ │ │ │ Action Timeline│ │ │ └────────────────┘ │ │ │ └─────────────────────────────────────────────────────────────────┘ """ import json import argparse import numpy as np from typing import Dict, List # ============================================================================= # Face Action Definitions (Existing from pose_action_decoder.py) # ============================================================================= FACE_TURN_ACTIONS = { ("frontal", "three_quarter"): "turn_partial", ("frontal", "profile_left"): "turn_left", ("frontal", "profile_right"): "turn_right", ("three_quarter", "frontal"): "return_frontal", ("three_quarter", "profile_left"): "turn_left", ("three_quarter", "profile_right"): "turn_right", ("profile_left", "frontal"): "turn_to_frontal", ("profile_left", "three_quarter"): "turn_to_three_quarter", ("profile_left", "profile_right"): "turn_full", ("profile_right", "frontal"): "turn_to_frontal", ("profile_right", "three_quarter"): "turn_to_three_quarter", ("profile_right", "profile_left"): "turn_full", } FACE_PITCH_ACTIONS = { ("neutral", "tilted_up"): "look_up", ("neutral", "tilted_down"): "look_down", ("tilted_up", "neutral"): "return_neutral", ("tilted_down", "neutral"): "return_neutral", } # ============================================================================= # Eye Action Definitions # ============================================================================= EYE_ACTIONS = { "blink": { "description": "眨眼", "pattern": "eye_aspect_ratio drops < 0.2 for 1-3 frames", "min_frames": 1, "max_frames": 3, }, "close": { "description": "闭眼", "pattern": "eye_aspect_ratio < 0.15 for > 10 frames", "min_frames": 10, }, "wide_open": { "description": "睁大眼", "pattern": "eye_aspect_ratio > 0.4", }, "look_left": { "description": "向左看", "pattern": "iris_position_x < 0.3", }, "look_right": { "description": "向右看", "pattern": "iris_position_x > 0.7", }, "squint": { "description": "眯眼", "pattern": "eye_aspect_ratio 0.15-0.25", }, } # ============================================================================= # Mouth Action Definitions # ============================================================================= MOUTH_ACTIONS = { "open": { "description": "张嘴", "pattern": "mouth_aspect_ratio > 0.5", }, "close": { "description": "闭嘴", "pattern": "mouth_aspect_ratio < 0.2", }, "smile": { "description": "微笑", "pattern": "mouth_corner_distance > threshold", }, "talk": { "description": "说话", "pattern": "mouth_aspect_ratio oscillating 0.3-0.6", "min_frames": 10, }, "yawn": { "description": "打哈欠", "pattern": "mouth_aspect_ratio > 0.7 for > 20 frames", "min_frames": 20, }, "pout": { "description": "嘟嘴", "pattern": "lip_distance > threshold", }, } # ============================================================================= # Arm Action Definitions # ============================================================================= ARM_ACTIONS = { "raise_left": { "description": "举起左手", "pattern": "left_shoulder_y > left_elbow_y > left_wrist_y", }, "raise_right": { "description": "举起右手", "pattern": "right_shoulder_y > right_elbow_y > right_wrist_y", }, "raise_both": { "description": "双手举起", "pattern": "both arms raised", }, "cross_arms": { "description": "双手交叉", "pattern": "left_wrist_x > right_wrist_x AND right_wrist_x < left_wrist_x", }, "wave": { "description": "挥手", "pattern": "wrist_y oscillating ±20px for 5-15 frames", "min_frames": 5, "max_frames": 15, }, "extend_left": { "description": "伸展左臂", "pattern": "left_elbow_angle > 150°", }, "extend_right": { "description": "伸展右臂", "pattern": "right_elbow_angle > 150°", }, "fold_left": { "description": "弯曲左臂", "pattern": "left_elbow_angle < 90°", }, "fold_right": { "description": "弯曲右臂", "pattern": "right_elbow_angle < 90°", }, "point": { "description": "指向", "pattern": "index_finger extended, other fingers folded", }, } # ============================================================================= # Hand Action Definitions # ============================================================================= HAND_ACTIONS = { "grab": { "description": "抓取", "pattern": "fingers folded, thumb opposing", }, "open": { "description": "张开手", "pattern": "all fingers extended", }, "clap": { "description": "拍手", "pattern": "hands together then apart (velocity pattern)", "min_frames": 3, "max_frames": 10, }, "thumbs_up": { "description": "点赞", "pattern": "thumb extended upward, other fingers folded", }, "fist": { "description": "握拳", "pattern": "all fingers folded into palm", }, "peace": { "description": "剪刀手", "pattern": "index and middle fingers extended", }, "ok": { "description": "OK 手势", "pattern": "thumb and index finger touching", }, "touch_face": { "description": "摸脸", "pattern": "hand near face region", }, "touch_hair": { "description": "摸头发", "pattern": "hand above head region", }, "pocket_left": { "description": "左手插兜", "pattern": "left_hand in hip region", }, "pocket_right": { "description": "右手插兜", "pattern": "right_hand in hip region", }, } # ============================================================================= # Leg Action Definitions # ============================================================================= LEG_ACTIONS = { "stand": { "description": "站立", "pattern": "hip_y < knee_y < ankle_y, vertical alignment", }, "sit": { "description": "坐姿", "pattern": "hip_y ≈ knee_y, thigh horizontal", }, "walk": { "description": "行走", "pattern": "hip-knee-ankle oscillating, stride pattern", "min_frames": 10, }, "run": { "description": "奔跑", "pattern": "fast oscillating, knee_bend > 60°", "min_frames": 10, }, "jump": { "description": "跳跃", "pattern": "all keypoints moving upward then landing", "min_frames": 5, "max_frames": 20, }, "kick": { "description": "踢腿", "pattern": "one leg extended forward rapidly", "min_frames": 3, "max_frames": 15, }, "cross_left": { "description": "左腿交叉", "pattern": "left_ankle_x > right_ankle_x", }, "cross_right": { "description": "右腿交叉", "pattern": "right_ankle_x > left_ankle_x", }, "knee_bend": { "description": "弯膝", "pattern": "knee_angle < 120°", }, } # ============================================================================= # Feet Action Definitions # ============================================================================= FEET_ACTIONS = { "tap": { "description": "轻踏", "pattern": "ankle_y oscillating ±10px", "min_frames": 3, "max_frames": 15, }, "stomp": { "description": "重踏", "pattern": "ankle_y large downward movement", "min_frames": 3, }, "cross": { "description": "交叉脚", "pattern": "feet_x overlapping", }, "point_left": { "description": "左脚前伸", "pattern": "left_ankle_y < right_ankle_y", }, "point_right": { "description": "右脚前伸", "pattern": "right_ankle_y < left_ankle_y", }, } # ============================================================================= # Combined Actions (Face + Body) # ============================================================================= COMBINED_ACTIONS = { "thinking": { "description": "思考姿势", "components": ["touch_face", "look_down"], "pattern": "hand near chin + head tilted down", }, "listening": { "description": "倾听姿势", "components": ["turn_partial", "open_mouth"], "pattern": "slight turn + mouth slightly open", }, "nodding_agreement": { "description": "点头同意", "components": ["nod_head", "smile"], "pattern": "head nod + smile", }, "shaking_disagreement": { "description": "摇头不同意", "components": ["shake_head", "frown"], "pattern": "shake head + frown", }, "waving_greeting": { "description": "挥手打招呼", "components": ["wave", "smile"], "pattern": "wave hand + smile", }, "crossing_arms_defensive": { "description": "双手交叉防御", "components": ["cross_arms", "frontal_stable"], "pattern": "cross arms + frontal pose", }, "pointing_explaining": { "description": "指向解释", "components": ["point", "turn_partial"], "pattern": "pointing + slight turn", }, "stretching": { "description": "伸展", "components": ["raise_both", "look_up"], "pattern": "raise arms + look up", }, "sitting_relaxed": { "description": "放松坐姿", "components": ["sit", "cross_arms"], "pattern": "sit + cross arms", }, } # ============================================================================= # Analysis Functions # ============================================================================= def analyze_eye_actions(eye_landmarks: List, prev_eye_landmarks: List = None) -> List[Dict]: """ Analyze eye actions from landmarks Args: eye_landmarks: Current frame eye landmarks (left/right eye points) prev_eye_landmarks: Previous frame landmarks (for motion detection) Returns: List of detected eye actions """ actions = [] if not eye_landmarks or len(eye_landmarks) < 6: return actions # Calculate eye aspect ratio (EAR) # EAR = (|p2-p6| + |p3-p5|) / (2|p1-p4|) # Points: p1, p2, p3, p4, p5, p6 (6 points per eye) # For left eye left_eye = eye_landmarks[:6] if len(left_eye) == 6: # Simplified EAR calculation vertical_1 = np.linalg.norm(np.array(left_eye[1]) - np.array(left_eye[5])) vertical_2 = np.linalg.norm(np.array(left_eye[2]) - np.array(left_eye[4])) horizontal = np.linalg.norm(np.array(left_eye[0]) - np.array(left_eye[3])) left_ear = (vertical_1 + vertical_2) / (2 * horizontal) if horizontal > 0 else 0 # Detect actions if left_ear < 0.15: actions.append({"action": "close_left", "description": "闭左眼", "confidence": 1.0 - left_ear}) elif left_ear > 0.4: actions.append({"action": "wide_open_left", "description": "睁大左眼", "confidence": left_ear}) return actions def analyze_mouth_actions(mouth_landmarks: List) -> List[Dict]: """ Analyze mouth actions from landmarks Args: mouth_landmarks: Mouth region landmarks (lips, mouth corners) Returns: List of detected mouth actions """ actions = [] if not mouth_landmarks or len(mouth_landmarks) < 4: return actions # Calculate mouth aspect ratio # Upper lip - lower lip distance / mouth width upper_lip = np.array(mouth_landmarks[0]) lower_lip = np.array(mouth_landmarks[1]) left_corner = np.array(mouth_landmarks[2]) right_corner = np.array(mouth_landmarks[3]) mouth_height = np.linalg.norm(upper_lip - lower_lip) mouth_width = np.linalg.norm(left_corner - right_corner) mar = mouth_height / mouth_width if mouth_width > 0 else 0 # Detect actions if mar > 0.7: actions.append({"action": "yawn", "description": "打哈欠", "mar": mar}) elif mar > 0.5: actions.append({"action": "open", "description": "张嘴", "mar": mar}) elif mar < 0.2: actions.append({"action": "close", "description": "闭嘴", "mar": mar}) else: # Check smile (mouth corners distance) corner_distance = abs(left_corner[1] - upper_lip[1]) + abs(right_corner[1] - upper_lip[1]) if corner_distance > 10: # Threshold actions.append({"action": "smile", "description": "微笑", "corner_distance": corner_distance}) return actions def analyze_arm_actions(pose_keypoints: Dict) -> List[Dict]: """ Analyze arm actions from pose keypoints Args: pose_keypoints: Pose keypoints dict with shoulder, elbow, wrist positions Returns: List of detected arm actions """ actions = [] # Keypoint indices (MediaPipe Pose): # 11: left_shoulder, 12: right_shoulder # 13: left_elbow, 14: right_elbow # 15: left_wrist, 16: right_wrist left_shoulder = pose_keypoints.get("left_shoulder") left_elbow = pose_keypoints.get("left_elbow") left_wrist = pose_keypoints.get("left_wrist") right_shoulder = pose_keypoints.get("right_shoulder") right_elbow = pose_keypoints.get("right_elbow") right_wrist = pose_keypoints.get("right_wrist") # Left arm actions if left_shoulder and left_elbow and left_wrist: # Calculate elbow angle shoulder_elbow = np.array(left_elbow) - np.array(left_shoulder) elbow_wrist = np.array(left_wrist) - np.array(left_elbow) elbow_angle = np.arccos( np.dot(shoulder_elbow, elbow_wrist) / (np.linalg.norm(shoulder_elbow) * np.linalg.norm(elbow_wrist)) ) elbow_angle_deg = np.degrees(elbow_angle) # Detect actions if left_wrist[1] < left_elbow[1] < left_shoulder[1]: # Raised (y decreases upward) actions.append({"action": "raise_left", "description": "举起左手", "angle": elbow_angle_deg}) if elbow_angle_deg > 150: actions.append({"action": "extend_left", "description": "伸展左臂", "angle": elbow_angle_deg}) elif elbow_angle_deg < 90: actions.append({"action": "fold_left", "description": "弯曲左臂", "angle": elbow_angle_deg}) # Right arm actions if right_shoulder and right_elbow and right_wrist: shoulder_elbow = np.array(right_elbow) - np.array(right_shoulder) elbow_wrist = np.array(right_wrist) - np.array(right_elbow) elbow_angle = np.arccos( np.dot(shoulder_elbow, elbow_wrist) / (np.linalg.norm(shoulder_elbow) * np.linalg.norm(elbow_wrist)) ) elbow_angle_deg = np.degrees(elbow_angle) if right_wrist[1] < right_elbow[1] < right_shoulder[1]: actions.append({"action": "raise_right", "description": "举起右手", "angle": elbow_angle_deg}) if elbow_angle_deg > 150: actions.append({"action": "extend_right", "description": "伸展右臂", "angle": elbow_angle_deg}) elif elbow_angle_deg < 90: actions.append({"action": "fold_right", "description": "弯曲右臂", "angle": elbow_angle_deg}) # Cross arms detection if left_wrist and right_wrist: if left_wrist[0] > right_wrist[0] and right_wrist[0] < left_shoulder[0]: actions.append({"action": "cross_arms", "description": "双手交叉"}) return actions def analyze_hand_actions(hand_keypoints: List, hand_type: str = "right") -> List[Dict]: """ Analyze hand actions from hand keypoints Args: hand_keypoints: 21 hand keypoints (MediaPipe Hand) hand_type: "left" or "right" Returns: List of detected hand actions """ actions = [] if not hand_keypoints or len(hand_keypoints) < 21: return actions # MediaPipe Hand keypoint indices: # 0: wrist # 1-4: thumb (CMC, MCP, IP, TIP) # 5-8: index finger (MCP, PIP, DIP, TIP) # 9-12: middle finger # 13-16: ring finger # 17-20: pinky wrist = np.array(hand_keypoints[0]) thumb_tip = np.array(hand_keypoints[4]) index_tip = np.array(hand_keypoints[8]) middle_tip = np.array(hand_keypoints[12]) ring_tip = np.array(hand_keypoints[16]) pinky_tip = np.array(hand_keypoints[20]) # Calculate finger extensions finger_tips = [thumb_tip, index_tip, middle_tip, ring_tip, pinky_tip] finger_bases = [ np.array(hand_keypoints[2]), # thumb IP np.array(hand_keypoints[5]), # index MCP np.array(hand_keypoints[9]), # middle MCP np.array(hand_keypoints[13]), # ring MCP np.array(hand_keypoints[17]), # pinky MCP ] extensions = [] for tip, base in zip(finger_tips, finger_bases): dist = np.linalg.norm(tip - base) extensions.append(dist) # Detect actions avg_extension = np.mean(extensions) if avg_extension > 50: # Open hand actions.append({"action": f"open_{hand_type}", "description": f"张开{hand_type}手"}) elif avg_extension < 30: # Closed/fist actions.append({"action": f"fist_{hand_type}", "description": f"握{hand_type}拳"}) # Thumbs up (thumb extended upward, others folded) if extensions[0] > 40 and np.mean(extensions[1:]) < 30: actions.append({"action": f"thumbs_up_{hand_type}", "description": f"{hand_type}手点赞"}) # Peace sign (index and middle extended) if extensions[1] > 40 and extensions[2] > 40 and np.mean(extensions[3:]) < 30: actions.append({"action": f"peace_{hand_type}", "description": f"{hand_type}手剪刀手"}) # Pointing (index extended, others folded) if extensions[1] > 40 and np.mean([extensions[0], extensions[2], extensions[3], extensions[4]]) < 30: actions.append({"action": f"point_{hand_type}", "description": f"{hand_type}手指向"}) return actions def analyze_leg_actions(pose_keypoints: Dict) -> List[Dict]: """ Analyze leg actions from pose keypoints Args: pose_keypoints: Pose keypoints with hip, knee, ankle positions Returns: List of detected leg actions """ actions = [] # Keypoint indices (MediaPipe Pose): # 23: left_hip, 24: right_hip # 25: left_knee, 26: right_knee # 27: left_ankle, 28: right_ankle left_hip = pose_keypoints.get("left_hip") left_knee = pose_keypoints.get("left_knee") left_ankle = pose_keypoints.get("left_ankle") right_hip = pose_keypoints.get("right_hip") right_knee = pose_keypoints.get("right_knee") right_ankle = pose_keypoints.get("right_ankle") # Left leg actions if left_hip and left_knee and left_ankle: hip_knee = np.array(left_knee) - np.array(left_hip) knee_ankle = np.array(left_ankle) - np.array(left_knee) knee_angle = np.arccos( np.dot(hip_knee, knee_ankle) / (np.linalg.norm(hip_knee) * np.linalg.norm(knee_ankle)) ) knee_angle_deg = np.degrees(knee_angle) # Detect actions if knee_angle_deg < 120: actions.append({"action": "knee_bend_left", "description": "弯左膝", "angle": knee_angle_deg}) # Standing detection if left_hip[1] < left_knee[1] < left_ankle[1]: # Vertical alignment (y increases downward) actions.append({"action": "stand_left", "description": "左腿站立"}) # Right leg actions if right_hip and right_knee and right_ankle: hip_knee = np.array(right_knee) - np.array(right_hip) knee_ankle = np.array(right_ankle) - np.array(right_knee) knee_angle = np.arccos( np.dot(hip_knee, knee_ankle) / (np.linalg.norm(hip_knee) * np.linalg.norm(knee_ankle)) ) knee_angle_deg = np.degrees(knee_angle) if knee_angle_deg < 120: actions.append({"action": "knee_bend_right", "description": "弯右膝", "angle": knee_angle_deg}) if right_hip[1] < right_knee[1] < right_ankle[1]: actions.append({"action": "stand_right", "description": "右腿站立"}) # Sit detection (hip ≈ knee height) if left_hip and left_knee and right_hip and right_knee: hip_avg_y = (left_hip[1] + right_hip[1]) / 2 knee_avg_y = (left_knee[1] + right_knee[1]) / 2 if abs(hip_avg_y - knee_avg_y) < 30: # Hip and knee at similar height actions.append({"action": "sit", "description": "坐姿"}) return actions # ============================================================================= # Main Decoder Function # ============================================================================= def decode_body_actions( pose_data: Dict, face_data: Dict = None, hand_data: Dict = None, ) -> Dict: """ Decode all body actions from multiple data sources Args: pose_data: Pose estimation data (MediaPipe Pose) face_data: Face pose data (InsightFace pose_angle) hand_data: Hand tracking data (MediaPipe Hand) Returns: Combined action data dict """ all_actions = { "face": [], "eyes": [], "mouth": [], "arms": [], "hands": [], "legs": [], "feet": [], "combined": [], } # 1. Face actions (existing) if face_data: pose_angle = face_data.get("pose_angle", {}) prev_pose_angle = face_data.get("prev_pose_angle", {}) if pose_angle and prev_pose_angle: angle = pose_angle.get("angle", "unknown") prev_angle = prev_pose_angle.get("angle", "unknown") turn_key = (prev_angle, angle) if turn_key in FACE_TURN_ACTIONS: all_actions["face"].append({ "action": FACE_TURN_ACTIONS[turn_key], "description": f"Face: {prev_angle} → {angle}", }) # Pitch actions pitch = pose_angle.get("pitch", "neutral") prev_pitch = prev_pose_angle.get("pitch", "neutral") pitch_key = (prev_pitch, pitch) if pitch_key in FACE_PITCH_ACTIONS: all_actions["face"].append({ "action": FACE_PITCH_ACTIONS[pitch_key], "description": f"Pitch: {prev_pitch} → {pitch}", }) # 2. Eye actions (if eye landmarks available) if face_data and face_data.get("eye_landmarks"): all_actions["eyes"] = analyze_eye_actions( face_data["eye_landmarks"], face_data.get("prev_eye_landmarks") ) # 3. Mouth actions (if mouth landmarks available) if face_data and face_data.get("mouth_landmarks"): all_actions["mouth"] = analyze_mouth_actions(face_data["mouth_landmarks"]) # 4. Arm actions (if pose keypoints available) if pose_data and pose_data.get("keypoints"): all_actions["arms"] = analyze_arm_actions(pose_data["keypoints"]) # 5. Hand actions (if hand keypoints available) if hand_data: if hand_data.get("left_hand"): all_actions["hands"].extend(analyze_hand_actions(hand_data["left_hand"], "left")) if hand_data.get("right_hand"): all_actions["hands"].extend(analyze_hand_actions(hand_data["right_hand"], "right")) # 6. Leg actions (if pose keypoints available) if pose_data and pose_data.get("keypoints"): all_actions["legs"] = analyze_leg_actions(pose_data["keypoints"]) # 7. Combined actions detected_actions = [] for category, actions in all_actions.items(): if actions: detected_actions.extend([a["action"] for a in actions]) for combined_name, combined_def in COMBINED_ACTIONS.items(): components = combined_def["components"] if all(comp in detected_actions for comp in components): all_actions["combined"].append({ "action": combined_name, "description": combined_def["description"], "components": components, }) return all_actions def print_body_action_report(action_data: Dict) -> None: """ Print body action report """ print("\n" + "=" * 70) print("Body Action Decoder Report") print("=" * 70) categories = ["face", "eyes", "mouth", "arms", "hands", "legs", "feet", "combined"] for category in categories: actions = action_data.get(category, []) if actions: print(f"\n{category.upper()} Actions ({len(actions)}):") for act in actions: desc = act.get("description", act["action"]) print(f" - {act['action']}: {desc}") print("\n" + "=" * 70) # ============================================================================= # Main Entry Point # ============================================================================= def main(): parser = argparse.ArgumentParser(description="Decode body actions from pose data") parser.add_argument("--pose-json", help="Path to pose.json (MediaPipe output)") parser.add_argument("--face-json", help="Path to face.json (InsightFace output)") parser.add_argument("--hand-json", help="Path to hand.json (MediaPipe Hand output)") parser.add_argument("--output-json", help="Output action data JSON") parser.add_argument("--frame", type=int, help="Analyze specific frame") args = parser.parse_args() print("=" * 70) print("Body Action Decoder") print("=" * 70) # Load data pose_data = None face_data = None hand_data = None if args.pose_json: with open(args.pose_json) as f: pose_data = json.load(f) if args.face_json: with open(args.face_json) as f: face_data = json.load(f) if args.hand_json: with open(args.hand_json) as f: hand_data = json.load(f) # Analyze if pose_data or face_data or hand_data: action_data = decode_body_actions( pose_data=pose_data, face_data=face_data, hand_data=hand_data, ) print_body_action_report(action_data) if args.output_json: with open(args.output_json, "w") as f: json.dump(action_data, f, indent=2) print(f"\n✅ Output saved to: {args.output_json}") else: print("\n⚠️ No input data provided") print("\nAction Categories:") print(" - Face: turn_left, turn_right, look_up, look_down, shake_head, nod_head") print(" - Eyes: blink, close, wide_open, look_left, look_right") print(" - Mouth: open, close, smile, talk, yawn") print(" - Arms: raise_left, raise_right, cross_arms, wave, point") print(" - Hands: grab, open, clap, thumbs_up, fist, peace, ok") print(" - Legs: stand, sit, walk, run, jump, kick") print(" - Feet: tap, stomp, cross, point") print(" - Combined: thinking, listening, nodding_agreement, waving_greeting") if __name__ == "__main__": main()