74 lines
2.8 KiB
Python
74 lines
2.8 KiB
Python
#!/usr/bin/env python3
|
|
"""Verify normalized Phase 7 full-body identity anchors."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import argparse
|
|
import json
|
|
from pathlib import Path
|
|
|
|
from PIL import Image
|
|
|
|
|
|
def parse_args() -> argparse.Namespace:
|
|
parser = argparse.ArgumentParser(description=__doc__)
|
|
parser.add_argument("--input-dir", type=Path, required=True)
|
|
parser.add_argument("--output", type=Path, required=True)
|
|
parser.add_argument("--expected-count", type=int, default=4)
|
|
parser.add_argument("--pixels", type=int, default=512)
|
|
return parser.parse_args()
|
|
|
|
|
|
def inspect(path: Path, pixels: int) -> dict[str, object]:
|
|
image = Image.open(path).convert("RGBA")
|
|
visible = [(r, g, b, a) for r, g, b, a in image.getdata() if a >= 16]
|
|
bound = image.getchannel("A").point(lambda value: 255 if value >= 16 else 0).getbbox()
|
|
corners = [
|
|
image.getpixel((0, 0))[3],
|
|
image.getpixel((pixels - 1, 0))[3],
|
|
image.getpixel((0, pixels - 1))[3],
|
|
image.getpixel((pixels - 1, pixels - 1))[3],
|
|
] if image.size == (pixels, pixels) else []
|
|
green_spill = sum(1 for red, green, blue, _ in visible if green - red > 50 and green - blue > 50)
|
|
errors: list[str] = []
|
|
if image.size != (pixels, pixels):
|
|
errors.append(f"dimensions {image.size}")
|
|
if not visible or not bound:
|
|
errors.append("empty visible content")
|
|
if corners and any(corners):
|
|
errors.append(f"opaque corners {corners}")
|
|
if bound and not 0.82 <= (bound[3] - bound[1]) / pixels <= 0.90:
|
|
errors.append(f"height fraction {(bound[3] - bound[1]) / pixels:.4f}")
|
|
if bound and bound[3] != round(pixels * 0.94):
|
|
errors.append(f"feet baseline {bound[3]}")
|
|
if green_spill:
|
|
errors.append(f"green spill {green_spill}")
|
|
return {
|
|
"path": path.as_posix(),
|
|
"ok": not errors,
|
|
"errors": errors,
|
|
"dimensions": list(image.size),
|
|
"content_bound": list(bound) if bound else None,
|
|
"visible_pixels": len(visible),
|
|
"green_spill_pixels": green_spill,
|
|
}
|
|
|
|
|
|
def main() -> None:
|
|
args = parse_args()
|
|
files = sorted(args.input_dir.glob("*-anchor.png"))
|
|
items = [inspect(path, args.pixels) for path in files]
|
|
errors = [error for item in items for error in item["errors"]]
|
|
if len(files) != args.expected_count:
|
|
errors.append(f"expected {args.expected_count} anchors, found {len(files)}")
|
|
report = {"ok": not errors, "count": len(files), "errors": errors, "items": items}
|
|
args.output.parent.mkdir(parents=True, exist_ok=True)
|
|
args.output.write_text(json.dumps(report, indent=2) + "\n", encoding="utf-8")
|
|
if errors:
|
|
raise SystemExit("IDENTITY_ANCHOR_VERIFY_FAILED " + "; ".join(errors[:8]))
|
|
print(f"IDENTITY_ANCHOR_VERIFY_OK anchors={len(files)} output={args.output}")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|