"""Debug face-landmark detection (no Gemini call, no key). Runs the OPTIONAL landmark backend on three inputs and records, separately, why detection succeeds/fails: 1. original full image 2. original cropped to the face crop box 3. generated raw face crop For each it writes a landmark overlay (bbox + eye/nose/mouth + contour on success; size/crop/backend/error annotation on failure) and a QA markdown. No model weights are committed. EXPERIMENTAL. Pilot Ready: NOT CONFIRMED. """ from __future__ import annotations import argparse import sys from io import BytesIO from pathlib import Path sys.path.insert(0, str(Path(__file__).resolve().parents[1])) from PIL import Image # noqa: E402 from app.services.face_pipeline.landmark_detector import ( # noqa: E402 detect_landmarks_result, draw_landmark_overlay, landmark_backend, ) from app.services.gemini_client import normalize_image_orientation_bytes # noqa: E402 def _parse_norm(value: str): parts = [float(p) for p in value.split(",")] if len(parts) != 4: raise ValueError("crop-box-norm must be 'x1,y1,x2,y2'") return tuple(parts) def main(argv: list[str] | None = None) -> int: parser = argparse.ArgumentParser(description="Debug face landmark detection (no API)") parser.add_argument("--original", required=True) parser.add_argument("--generated-crop", required=True) parser.add_argument("--crop-box-norm", default="0.33,0.39,0.69,0.78") parser.add_argument("--evidence-root", default="runtime/gemini-smoke-evidence") parser.add_argument("--tag", default="salon-crop-09-landmark-debug") parser.add_argument("--min-confidence", type=float, default=0.5) args = parser.parse_args(argv) original_path = Path(args.original) crop_path = Path(args.generated_crop) if not original_path.exists(): print(f"REFUSED: original not found: {original_path}") return 2 if not crop_path.exists(): print(f"REFUSED: generated crop not found: {crop_path}") return 2 smoke = Path(args.evidence_root) / "gemini-smoke" lm_dir = smoke / "landmarks" qa_md = smoke / "qa" / f"landmark-debug-{args.tag}.md" for p in (lm_dir / "x", qa_md): p.parent.mkdir(parents=True, exist_ok=True) backend = landmark_backend() original_bytes = normalize_image_orientation_bytes(original_path.read_bytes()) crop_bytes = crop_path.read_bytes() base = Image.open(BytesIO(original_bytes)).convert("RGB") w, h = base.size nx1, ny1, nx2, ny2 = _parse_norm(args.crop_box_norm) crop_box = (int(nx1 * w), int(ny1 * h), int(nx2 * w), int(ny2 * h)) # original cropped to the face crop box orig_crop = base.crop(crop_box) orig_crop_buf = BytesIO() orig_crop.save(orig_crop_buf, format="PNG") orig_crop_bytes = orig_crop_buf.getvalue() targets = [ ("original", original_bytes, crop_box), ("original-crop", orig_crop_bytes, None), ("generated-crop", crop_bytes, None), ] rows = [] any_success = False for name, img_bytes, box in targets: res = detect_landmarks_result(img_bytes, min_detection_confidence=args.min_confidence) any_success = any_success or res.success overlay = draw_landmark_overlay(img_bytes, res, crop_box=box, label=name) out_path = lm_dir / f"landmark-debug-{args.tag}-{name}.png" out_path.write_bytes(overlay) rows.append((name, res, out_path.name)) print(f"[{name}] success={res.success} count={res.detection_count} " f"size={res.image_size} backend={res.backend} " f"error={res.error_type or '-'} hint={res.hint()}") real_eval = "YES" if any_success else "NO" lines = [f"# Landmark detection debug - {args.tag}\n", f"- landmark_backend: {backend or 'none'}", f"- crop_box: {crop_box}", f"- real_landmark_eval: {real_eval}", f"- synthetic_fallback_used: {'NO' if any_success else 'YES'}", f"- business_quality_eval_valid: {'PENDING (human QA)' if any_success else 'NO'}", f"- reason: {'' if any_success else 'real landmarks were not detected on any input'}", "- pilot_ready: NOT CONFIRMED\n", "## Per-image detection\n"] for name, res, fname in rows: lines += [ f"### {name}", f"- overlay: {fname}", f"- success: {'YES' if res.success else 'NO'}", f"- detection_count: {res.detection_count}", f"- image_size: {res.image_size}", f"- error_type: {res.error_type or '-'}", f"- error_message: {(res.error_message or '-')[:200]}", f"- attempts: {res.attempts}", f"- hint: {res.hint()}\n", ] qa_md.write_text("\n".join(lines), encoding="utf-8") print(f"landmark_debug: DONE backend={backend or 'none'} real_landmark_eval={real_eval}") print(f"overlays in {lm_dir} ; qa={qa_md.name}") print("EXPERIMENTAL. Human QA required. Pilot Ready: NOT CONFIRMED.") return 0 if __name__ == "__main__": sys.exit(main())