Files
momentry_core/scripts/test_mediapipe_pose_alignment.py
T
Accusys 39a2cbc65b fix: face group name read consistency, sync_file_status fix, cleanup ghost records, identity_agent replaced with face_dedup
- get_face_groups_handler: COALESCE(tp.name, tn.label) for name consistency
- sync_file_status: compare JSON vs pre_chunks (not chunk table)
- face consistency: compare frames.len() not total_faces
- cleanup 2 ghost records with NULL file_name/file_path
- replace identity_agent with face_dedup in pipeline stages
- remove identity_agent_api.rs and all references
- update required_processors to match actual processors
- update AGENTS.md with team responsibilities
- add Studio pipeline changes documentation
2026-07-27 02:15:51 +08:00

345 lines
12 KiB
Python

#!/opt/homebrew/bin/python3.11
"""
Test MediaPipe Pose alignment on face trace key frames
Tests 50 random key frames to determine alignment rate.
Handles multiple poses by matching only the correct one with face bbox.
Usage:
python3 scripts/test_mediapipe_pose_alignment.py --file-uuid <uuid> --sample-size 50
"""
import argparse
import json
import os
import random
import sys
import time
from pathlib import Path
try:
import cv2
import mediapipe as mp
from mediapipe.tasks.python.vision import PoseLandmarker, PoseLandmarkerOptions
from mediapipe.tasks.python.core.base_options import BaseOptions
except ImportError as e:
print(f"Missing dependency: {e}", file=sys.stderr)
sys.exit(1)
LANDMARK_NAMES = [
"nose", "left_eye_inner", "left_eye", "left_eye_outer",
"right_eye_inner", "right_eye", "right_eye_outer",
"left_ear", "right_ear", "mouth_left", "mouth_right",
"left_shoulder", "right_shoulder", "left_elbow", "right_elbow",
"left_wrist", "right_wrist", "left_pinky", "right_pinky",
"left_index", "right_index", "left_thumb", "right_thumb",
"left_hip", "right_hip", "left_knee", "right_knee",
"left_ankle", "right_ankle", "left_heel", "right_heel",
"left_foot_index", "right_foot_index",
]
def point_in_bbox(x, y, bbox):
"""Check if point (in pixels) is inside bbox (in pixels)."""
return (bbox['x'] <= x <= bbox['x'] + bbox['width'] and
bbox['y'] <= y <= bbox['y'] + bbox['height'])
def get_face_bbox(face_data, trace_id):
"""Get face bbox for a trace from face_traced.json."""
traces = face_data.get('traces', {})
frames = face_data.get('frames', {})
if trace_id not in traces:
return None
trace = traces[trace_id]
start_frame = str(trace.get('start_frame', 0))
if start_frame not in frames:
return None
frame_data = frames[start_frame]
faces = frame_data.get('faces', [])
# Find the face that belongs to this trace
for face in faces:
if face.get('trace_id') == int(trace_id):
return {
'x': face['x'],
'y': face['y'],
'width': face['width'],
'height': face['height']
}
# Fallback: use first face
if faces:
face = faces[0]
return {
'x': face['x'],
'y': face['y'],
'width': face['width'],
'height': face['height']
}
return None
def check_pose_alignment(pose_landmarks, face_bbox, img_width, img_height):
"""
Check if pose face keypoints align with face bbox.
Returns: (aligned: bool, face_keypoints: dict)
"""
# Get face keypoints from pose
kp_dict = {}
for idx, landmark in enumerate(pose_landmarks):
if idx < len(LANDMARK_NAMES):
name = LANDMARK_NAMES[idx]
if name in ['nose', 'left_eye', 'right_eye']:
# Convert normalized coords to pixels
kp_dict[name] = {
'x': landmark.x * img_width,
'y': landmark.y * img_height,
'confidence': landmark.visibility if hasattr(landmark, 'visibility') else 1.0
}
# Check if all 3 keypoints are in bbox
required = ['nose', 'left_eye', 'right_eye']
if not all(k in kp_dict for k in required):
return False, kp_dict
for name in required:
kp = kp_dict[name]
if not point_in_bbox(kp['x'], kp['y'], face_bbox):
return False, kp_dict
return True, kp_dict
def test_alignment(file_uuid, output_dir, sample_size=None):
"""Test MediaPipe pose alignment on key frames.
Args:
sample_size: If None, process all key frames. Otherwise, sample N.
"""
# Load face data
face_json = Path(output_dir) / f"{file_uuid}.face_traced.json"
if not face_json.exists():
print(f"[pose_keyframe] Face file not found: {face_json}", file=sys.stderr)
return
with open(face_json) as f:
face_data = json.load(f)
# Get all trace IDs with key_frame.jpg
output_path = Path(output_dir) / file_uuid
trace_dirs = sorted(output_path.glob("trace_*"))
valid_traces = []
for trace_dir in trace_dirs:
key_frame = trace_dir / "key_frame.jpg"
if key_frame.exists():
trace_id = trace_dir.name.replace("trace_", "")
valid_traces.append(trace_id)
print(f"[pose_keyframe] Found {len(valid_traces)} traces with key_frame.jpg")
# Sample or process all
if sample_size and len(valid_traces) > sample_size:
sample_traces = random.sample(valid_traces, sample_size)
else:
sample_traces = valid_traces
print(f"[pose_keyframe] Processing {len(sample_traces)} key frames...")
# Download model
model_path = os.path.expanduser("~/.mediapipe/models/pose_landmarker_heavy.task")
if not os.path.exists(model_path):
os.makedirs(os.path.dirname(model_path), exist_ok=True)
print(f"[test] Downloading model...")
import urllib.request
url = "https://storage.googleapis.com/mediapipe-models/pose_landmarker/pose_landmarker_heavy/float16/1/pose_landmarker_heavy.task"
urllib.request.urlretrieve(url, model_path)
# Initialize MediaPipe
options = PoseLandmarkerOptions(
base_options=BaseOptions(model_asset_path=model_path),
running_mode=mp.tasks.vision.RunningMode.IMAGE,
)
detector = PoseLandmarker.create_from_options(options)
# Test each sample
results = []
for i, trace_id in enumerate(sample_traces):
key_frame_path = output_path / f"trace_{trace_id}" / "key_frame.jpg"
# Get face bbox
face_bbox = get_face_bbox(face_data, trace_id)
if not face_bbox:
results.append({
'trace_id': trace_id,
'status': 'no_face_bbox',
'aligned': False
})
continue
# Read image
img = cv2.imread(str(key_frame_path))
if img is None:
results.append({
'trace_id': trace_id,
'status': 'cannot_read_image',
'aligned': False
})
continue
img_height, img_width = img.shape[:2]
# Detect pose
rgb_img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
mp_image = mp.Image(mp.ImageFormat.SRGB, rgb_img)
results_mp = detector.detect(mp_image)
if not results_mp.pose_landmarks:
results.append({
'trace_id': trace_id,
'status': 'no_pose_detected',
'aligned': False
})
continue
# Check each pose for alignment
aligned_poses = []
for pose_landmarks in results_mp.pose_landmarks:
aligned, face_kps = check_pose_alignment(
pose_landmarks, face_bbox, img_width, img_height
)
if aligned:
# Get all keypoints
all_keypoints = []
for idx, landmark in enumerate(pose_landmarks):
if idx < len(LANDMARK_NAMES):
name = LANDMARK_NAMES[idx]
all_keypoints.append({
'name': name,
'x': landmark.x * img_width,
'y': landmark.y * img_height,
'confidence': landmark.visibility if hasattr(landmark, 'visibility') else 1.0
})
aligned_poses.append({
'face_keypoints': face_kps,
'keypoints': all_keypoints
})
if aligned_poses:
# Get all keypoints from the best aligned pose
best_pose = aligned_poses[0]
all_keypoints = []
for idx, landmark in enumerate(pose_landmarks):
if idx < len(LANDMARK_NAMES):
name = LANDMARK_NAMES[idx]
all_keypoints.append({
'name': name,
'x': landmark.x * img_width,
'y': landmark.y * img_height,
'confidence': landmark.visibility if hasattr(landmark, 'visibility') else 1.0
})
pose_data = {
'keypoints': all_keypoints,
'face_keypoints': best_pose['face_keypoints'],
'num_poses_detected': len(results_mp.pose_landmarks),
'num_aligned': len(aligned_poses)
}
results.append({
'trace_id': trace_id,
'status': 'aligned',
'aligned': True,
'pose_data': pose_data
})
else:
results.append({
'trace_id': trace_id,
'status': 'pose_not_aligned',
'aligned': False,
'num_poses_detected': len(results_mp.pose_landmarks)
})
if (i + 1) % 10 == 0:
print(f"[test] Processed {i+1}/{len(sample_traces)} samples...")
detector.close()
# Calculate statistics
total = len(results)
aligned_count = sum(1 for r in results if r.get('aligned'))
status_counts = {}
for r in results:
status = r.get('status', 'unknown')
status_counts[status] = status_counts.get(status, 0) + 1
print(f"\n[pose_keyframe] === RESULTS ===")
print(f"[pose_keyframe] Total processed: {total}")
print(f"[pose_keyframe] Aligned: {aligned_count} ({aligned_count/total*100:.1f}%)")
print(f"[pose_keyframe] Not aligned: {total - aligned_count} ({(total-aligned_count)/total*100:.1f}%)")
print(f"\n[pose_keyframe] Status breakdown:")
for status, count in sorted(status_counts.items()):
print(f"[pose_keyframe] {status}: {count} ({count/total*100:.1f}%)")
# Save results
output_file = Path(output_dir) / f"{file_uuid}.pose_keyframe_results.json"
with open(output_file, 'w') as f:
json.dump({
'total_processed': total,
'aligned': aligned_count,
'alignment_rate': f"{aligned_count/total*100:.1f}%",
'status_counts': status_counts,
'results': results
}, f, indent=2)
print(f"\n[pose_keyframe] Results saved to: {output_file}")
# Update trace_profile.json for aligned poses
print(f"\n[pose_keyframe] Updating trace_profile.json for aligned poses...")
updated_count = 0
for r in results:
if r.get('aligned') and r.get('pose_data'):
trace_id = r['trace_id']
profile_path = output_path / f"trace_{trace_id}" / "trace_profile.json"
if profile_path.exists():
with open(profile_path) as f:
profile = json.load(f)
profile['pose'] = r['pose_data']
profile['pose_aligned'] = True
with open(profile_path, 'w') as f:
json.dump(profile, f, indent=2)
updated_count += 1
print(f"[pose_keyframe] Updated {updated_count} trace_profile.json files")
def main():
parser = argparse.ArgumentParser(description="Process MediaPipe pose on key frames")
parser.add_argument("--file-uuid", "-u", required=True, help="File UUID")
parser.add_argument("--output-dir", "-o", default="/Users/accusys/momentry/output", help="Output directory")
parser.add_argument("--sample-size", "-s", type=int, default=None, help="Sample size (default: all)")
parser.add_argument("--all", "-a", action="store_true", help="Process all key frames")
args = parser.parse_args()
sample_size = None if args.all else args.sample_size
test_alignment(args.file_uuid, args.output_dir, sample_size)
if __name__ == "__main__":
main()