#!/opt/homebrew/bin/python3.11 """ Face landmark QC: verify eyes/nose are within face bounding box. Flags faces in DB where landmarks don't match the bbox. Usage: python3 face_landmark_qc.py [--threshold 0.5] [--apply] """ import sys, json, psycopg2, argparse, os parser = argparse.ArgumentParser() parser.add_argument("uuid") parser.add_argument("--threshold", "-t", type=float, default=0.5, help="Fraction of landmark points that must be inside bbox (default: 0.5)") parser.add_argument("--apply", action="store_true", help="Write qc_ok to face_detections.metadata in DB") parser.add_argument("--schema", default="dev", help="DB schema (default: dev)") args = parser.parse_args() UUID = args.uuid THRESHOLD = args.threshold SCHEMA = args.schema OUTPUT_DIR = os.environ.get("MOMENTRY_OUTPUT_DIR", f"/Users/accusys/momentry/output_dev") FACE_PATH = f"{OUTPUT_DIR}/{UUID}.face.json" print(f"=== Face Landmark QC ===") print(f"UUID: {UUID}") print(f"Schema: {SCHEMA}") print(f"Face file: {FACE_PATH}") print(f"Threshold: {THRESHOLD * 100:.0f}% points must be inside bbox") # Load face.json with open(FACE_PATH) as f: data = json.load(f) total_faces = 0 faces_with_lm = 0 good_faces = 0 bad_faces = 0 qc_results = [] # list of (frame, face_idx, qc_ok, x, y, w, h) # Build frame lookup for fast access frame_map = {} for frm in data['frames']: frame_map[frm['frame']] = frm for frame_num, frm in frame_map.items(): for fi, face in enumerate(frm.get('faces', [])): total_faces += 1 lm = face.get('landmarks') if not lm: bbox = face.get('bbox', {}) qc_results.append((frame_num, fi, False, bbox.get('x'), bbox.get('y'), bbox.get('width'), bbox.get('height'))) bad_faces += 1 continue faces_with_lm += 1 bbox = face.get('bbox', {}) x, y, w, h = bbox.get('x'), bbox.get('y'), bbox.get('width'), bbox.get('height') if None in (x, y, w, h): qc_results.append((frame_num, fi, False, x, y, w, h)) bad_faces += 1 continue inside_pts = 0 total_pts = 0 eye_nose_inside = 0 for lm_type in ['left_eye', 'right_eye', 'nose']: points = lm.get(lm_type, []) total_pts += len(points) any_inside = False for pt in points: px, py = pt[0], pt[1] if (x <= px <= x + w) and (y <= py <= y + h): inside_pts += 1 any_inside = True if any_inside: eye_nose_inside += 1 ratio = inside_pts / max(1, total_pts) qc_ok = (ratio >= THRESHOLD and eye_nose_inside >= 2) qc_results.append((frame_num, fi, qc_ok, x, y, w, h)) if qc_ok: good_faces += 1 else: bad_faces += 1 print(f"\nTotal faces: {total_faces:,}") print(f"Faces with landmarks: {faces_with_lm:,}") print(f"✅ Good (≥{THRESHOLD*100:.0f}% inside + ≥2 features): {good_faces:,}") print(f"❌ Bad (no eyes or insufficient landmarks): {bad_faces:,}") print(f"Quality pass rate: {100 * good_faces / max(1, faces_with_lm):.1f}%") # Apply mode: write qc_ok to face_detections.metadata if args.apply: print(f"\n=== Applying QC results to {SCHEMA}.face_detections ===") db_url = os.environ.get("DATABASE_URL", "postgres://accusys@localhost:5432/momentry") conn = psycopg2.connect(db_url) cur = conn.cursor() updated = 0 for frame_num, fi, qc_ok, x, y, w, h in qc_results: qc_str = "true" if qc_ok else "false" cur.execute( f"UPDATE {SCHEMA}.face_detections " f"SET metadata = jsonb_set(COALESCE(metadata, '{{}}'::jsonb), '{{qc_ok}}', '\"{qc_str}\"'::jsonb) " f"WHERE file_uuid = %s AND frame_number = %s AND x = %s AND y = %s AND width = %s AND height = %s", (UUID, frame_num, x, y, w, h) ) if cur.rowcount > 0: updated += 1 conn.commit() cur.close() conn.close() print(f"Updated {updated} rows in {SCHEMA}.face_detections") print(f"Skipped {len(qc_results) - updated} rows (no matching face_detections row)")