leechard / scripts /debug_face_landmarks.py
nenae18's picture
Deploy LeeChard
5d3c2a9 verified
Raw
History Blame Contribute Delete
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())