Download scripts/audit_face_recompose.py from nenae18/leechard: direct link, hf CLI and curl.
- Browser
- Download file 5.18 kB
-
https://huggingface.co/spaces/nenae18/leechard/resolve/main/scripts/audit_face_recompose.py
- Command line
-
hf download hf://spaces/nenae18/leechard/scripts/audit_face_recompose.py
-
curl -L -o audit_face_recompose.py https://huggingface.co/spaces/nenae18/leechard/resolve/main/scripts/audit_face_recompose.py
5.18 kB
| """A2-4 audit runner: measure where the face transformation is lost (no API). | |
| Recomposes (original + already-generated raw crop) and writes per-stage delta | |
| heatmaps + a numeric summary so a human can see whether the generator, the warp, | |
| the alpha mask, or the color/blend is killing the transformation. No Gemini call, | |
| no key, no model weights committed. Outputs under runtime/ (gitignored). | |
| Pilot Ready: NOT CONFIRMED. | |
| """ | |
| from __future__ import annotations | |
| import argparse | |
| import json | |
| 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.audit import run_audit # noqa: E402 | |
| from app.services.face_pipeline.landmark_detector import ( # noqa: E402 | |
| detect_face_landmarks, | |
| landmark_backend, | |
| synthetic_landmarks, | |
| ) | |
| 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 _f(value): | |
| return None if value is None or value < 0 else value | |
| def main(argv: list[str] | None = None) -> int: | |
| parser = argparse.ArgumentParser(description="Face recompose alpha/delta audit (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-17-audit-piecewise-strong") | |
| parser.add_argument("--warp-mode", default="piecewise", choices=["affine", "piecewise"]) | |
| parser.add_argument("--blend-mode", default="strong", choices=["safe", "medium", "strong"]) | |
| parser.add_argument("--color-match-strength", type=float, default=None) | |
| parser.add_argument("--feature-strength", type=float, default=None) | |
| parser.add_argument("--inner-core-alpha", type=int, default=None) | |
| 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 | |
| out_dir = Path(args.evidence_root) / "gemini-smoke" / "audit" / args.tag | |
| out_dir.mkdir(parents=True, exist_ok=True) | |
| 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)) | |
| backend = landmark_backend() | |
| target = detect_face_landmarks(original_bytes) | |
| source = detect_face_landmarks(crop_bytes) | |
| if target is not None and source is not None: | |
| landmark_source = backend or "unknown" | |
| else: | |
| print(f"NOTE: landmark backend unavailable ({backend or 'none'}); synthetic fallback.") | |
| face_xywh = (crop_box[0], crop_box[1], crop_box[2] - crop_box[0], crop_box[3] - crop_box[1]) | |
| target = synthetic_landmarks(base.size, face_xywh) | |
| crop_img = Image.open(BytesIO(crop_bytes)).convert("RGB") | |
| source = synthetic_landmarks(crop_img.size, (0, 0, *crop_img.size)) | |
| landmark_source = "synthetic" | |
| result = run_audit( | |
| original_bytes, crop_bytes, crop_box, | |
| target_landmarks=target, source_landmarks=source, | |
| landmark_source=landmark_source, | |
| warp_mode=args.warp_mode, blend_mode=args.blend_mode, | |
| color_match_strength=_f(args.color_match_strength), | |
| feature_strength=_f(args.feature_strength), | |
| inner_core_alpha=_f(args.inner_core_alpha), | |
| ) | |
| for name, img in result["images"].items(): | |
| img.save(out_dir / f"{name}.png") | |
| metrics = result["metrics"] | |
| metrics["landmark_backend"] = backend or "none" | |
| metrics["crop_box"] = list(crop_box) | |
| (out_dir / "audit-summary.json").write_text( | |
| json.dumps(metrics, indent=2, ensure_ascii=False), encoding="utf-8" | |
| ) | |
| md = ["# Face recompose audit - " + args.tag, ""] | |
| md += [f"- {k}: {v}" for k, v in metrics.items()] | |
| md += ["", "> EXPERIMENTAL / NOT_PRODUCTION_READY. Human QA required.", | |
| "> Pilot Ready: NOT CONFIRMED."] | |
| (out_dir / "audit-summary.md").write_text("\n".join(md), encoding="utf-8") | |
| print(f"audit: DONE tag={args.tag} backend={backend or 'none'} verdict={metrics['verdict']}") | |
| for k in ("raw_delta_mean", "warped_delta_mean", "final_delta_mean", | |
| "feature_core_alpha_mean", "feature_core_alpha_max", | |
| "final_to_raw_transfer_ratio", "eyes_delta", "nose_delta", | |
| "mouth_delta", "cheeks_delta"): | |
| print(f"{k}={metrics[k]}") | |
| print(f"out={out_dir}") | |
| print("Pilot Ready: NOT CONFIRMED.") | |
| return 0 | |
| if __name__ == "__main__": | |
| sys.exit(main()) | |