Download scripts/debug_face_landmarks.py from nenae18/leechard: direct link, hf CLI and curl.
- Browser
- Download file 5.13 kB
-
https://huggingface.co/spaces/nenae18/leechard/resolve/main/scripts/debug_face_landmarks.py
- Command line
-
hf download hf://spaces/nenae18/leechard/scripts/debug_face_landmarks.py
-
curl -L -o debug_face_landmarks.py https://huggingface.co/spaces/nenae18/leechard/resolve/main/scripts/debug_face_landmarks.py
5.13 kB
| """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()) | |