- 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
199 lines
6.1 KiB
Python
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() |