Files
momentry_core/scripts/utils/face_trace_visualizer.py
Warren e75c4d6f07 cleanup: remove dead code and duplicate docs
- Remove session-ses_2f27.md (161KB raw session log)
- Remove 49 ROOT_* duplicate files across REFERENCE/
- Remove 14 duplicate files between REFERENCE/ root and history/
- Remove asr_legacy.rs (dead code, replaced by asr.rs)
- Remove src/core/worker/ (duplicate JobWorker)
- Remove src/core/layers/ (empty directory)
- Remove 4 .bak files in src/
- Remove 7 dead private methods in worker/processor.rs
- Remove backup directory from git tracking
2026-05-04 01:31:21 +08:00

199 lines
6.1 KiB
Python

#!/opt/homebrew/bin/python3.11
"""
Face Trace Visualizer - Visualize face tracking paths
Output:
1. Trace path visualization (matplotlib)
2. Trace statistics CSV
"""
import json
import argparse
import numpy as np
import matplotlib.pyplot as plt
from collections import defaultdict
from typing import Dict
def visualize_traces(face_data: Dict, output_path: str = None) -> None:
"""
Visualize face trace paths
"""
frames = face_data.get("frames", {})
traces = face_data.get("traces", {})
metadata = face_data.get("metadata", {})
if not frames or not traces:
print("No frames or traces found")
return
video_width = metadata.get("width", 640)
video_height = metadata.get("height", 360)
video_duration = metadata.get("total_duration", 15)
fig, axes = plt.subplots(2, 2, figsize=(16, 12))
ax1 = axes[0, 0]
ax2 = axes[0, 1]
ax3 = axes[1, 0]
ax4 = axes[1, 1]
colors = plt.cm.tab10(np.linspace(0, 1, len(traces)))
trace_data = {}
for trace_id_str, trace in traces.items():
trace_id = int(trace_id_str)
path = trace.get("path", [])
trace_data[trace_id] = {
"frames": [p["frame"] for p in path],
"x": [p["bbox"]["x"] + p["bbox"]["width"] / 2 for p in path],
"y": [p["bbox"]["y"] + p["bbox"]["height"] / 2 for p in path],
"confidence": [p["confidence"] for p in path],
"pose": [p["pose_angle"] for p in path],
}
for trace_id, color in zip(sorted(trace_data.keys()), colors):
data = trace_data[trace_id]
ax1.plot(data["frames"], data["x"], color=color, label=f"Trace {trace_id}", linewidth=2)
ax1.scatter(data["frames"], data["x"], color=color, s=30)
ax2.plot(data["frames"], data["y"], color=color, label=f"Trace {trace_id}", linewidth=2)
ax2.scatter(data["frames"], data["y"], color=color, s=30)
ax3.plot(data["frames"], data["confidence"], color=color, label=f"Trace {trace_id}", linewidth=2)
ax3.scatter(data["frames"], data["confidence"], color=color, s=30)
ax1.set_xlabel("Frame Number")
ax1.set_ylabel("X Position (center)")
ax1.set_title("Face X Position Over Time")
ax1.legend()
ax1.grid(True, alpha=0.3)
ax2.set_xlabel("Frame Number")
ax2.set_ylabel("Y Position (center)")
ax2.set_title("Face Y Position Over Time")
ax2.legend()
ax2.grid(True, alpha=0.3)
ax3.set_xlabel("Frame Number")
ax3.set_ylabel("Detection Confidence")
ax3.set_title("Face Detection Confidence Over Time")
ax3.legend()
ax3.grid(True, alpha=0.3)
pose_colors = {
"frontal": "green",
"three_quarter": "blue",
"profile_left": "orange",
"profile_right": "red",
"unknown": "gray",
}
for trace_id, color in zip(sorted(trace_data.keys()), colors):
data = trace_data[trace_id]
poses = data["pose"]
frames = data["frames"]
pose_counts = defaultdict(int)
for pose in poses:
pose_counts[pose] += 1
ax4.bar(
[f"Trace {trace_id}\n{pose}" for pose in pose_counts.keys()],
pose_counts.values(),
color=[pose_colors.get(pose, "gray") for pose in pose_counts.keys()],
alpha=0.7,
label=f"Trace {trace_id}",
)
ax4.set_xlabel("Trace / Pose")
ax4.set_ylabel("Count")
ax4.set_title("Pose Distribution by Trace")
ax4.tick_params(axis='x', rotation=45)
plt.tight_layout()
if output_path:
plt.savefig(output_path, dpi=150, bbox_inches="tight")
print(f"\n✅ Visualization saved to: {output_path}")
else:
plt.show()
def export_trace_csv(face_data: Dict, output_path: str) -> None:
"""
Export trace statistics to CSV
"""
traces = face_data.get("traces", {})
import csv
with open(output_path, "w", newline="") as f:
writer = csv.writer(f)
writer.writerow([
"trace_id",
"start_frame",
"end_frame",
"duration_frames",
"duration_seconds",
"total_appearances",
"avg_confidence",
"pose_three_quarter",
"pose_profile_right",
"pose_profile_left",
"pose_frontal",
])
for trace_id_str, trace in sorted(traces.items(), key=lambda x: int(x[0])):
poses = trace.get("pose_angles", [])
pose_counts = defaultdict(int)
for pose in poses:
pose_counts[pose] += 1
writer.writerow([
trace["trace_id"],
trace["start_frame"],
trace["end_frame"],
trace["duration_frames"],
trace["duration_seconds"],
trace["total_appearances"],
trace["avg_confidence"],
pose_counts.get("three_quarter", 0),
pose_counts.get("profile_right", 0),
pose_counts.get("profile_left", 0),
pose_counts.get("frontal", 0),
])
print(f"\n✅ CSV exported to: {output_path}")
def main():
parser = argparse.ArgumentParser(description="Visualize face traces")
parser.add_argument("--face-json", required=True, help="Path to face_traced.json")
parser.add_argument("--output-plot", help="Output plot path (PNG)")
parser.add_argument("--output-csv", help="Output CSV path")
args = parser.parse_args()
with open(args.face_json) as f:
face_data = json.load(f)
print("=" * 60)
print("Face Trace Visualizer")
print("=" * 60)
print(f"\nInput: {args.face_json}")
print(f"Traces: {len(face_data.get('traces', {}))}")
if args.output_plot:
visualize_traces(face_data, args.output_plot)
if args.output_csv:
export_trace_csv(face_data, args.output_csv)
if not args.output_plot and not args.output_csv:
visualize_traces(face_data)
if __name__ == "__main__":
main()