feat: update Python processors and add utility scripts
- Update ASR, face, OCR, pose processors - Add release pre-flight check script - Add synonym generation, chunk processing scripts - Add face recognition, stamp search utilities
This commit is contained in:
201
scripts/utils/face_trace_visualizer.py
Normal file
201
scripts/utils/face_trace_visualizer.py
Normal file
@@ -0,0 +1,201 @@
|
||||
#!/opt/homebrew/bin/python3.11
|
||||
"""
|
||||
Face Trace Visualizer - Visualize face tracking paths
|
||||
|
||||
Output:
|
||||
1. Trace path visualization (matplotlib)
|
||||
2. Trace statistics CSV
|
||||
"""
|
||||
|
||||
import sys
|
||||
import json
|
||||
import argparse
|
||||
import numpy as np
|
||||
import matplotlib.pyplot as plt
|
||||
from matplotlib.patches import Rectangle
|
||||
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()
|
||||
Reference in New Issue
Block a user