"""Export correctly named fixed, large, and routed registered observations. The output is an input manifest for ``synthid_conformal_cascade.py``. All experts consume decoded RGB pixels only. Unsupported geometry is recorded explicitly and never represented by a synthetic score. """ from __future__ import annotations import json import logging import sys from pathlib import Path from typing import TYPE_CHECKING, TypedDict import click import numpy as np if TYPE_CHECKING: from numpy.typing import NDArray PROJECT_ROOT = Path(__file__).resolve().parent.parent sys.path.insert(0, str(PROJECT_ROOT / "scripts")) sys.path.insert(0, str(PROJECT_ROOT / "src")) from synthid_pixel_attack import load_rgb # noqa: E402 from synthid_research_manifest import artifact_sha256 # noqa: E402 from synthid_runtime import synthid_detector # noqa: E402 log = logging.getLogger(__name__) FIXED_EXPERT_NAME = synthid_detector.DETECTOR_ID REGISTERED_EXPERT_NAME = synthid_detector.REGISTERED_DETECTOR_ID LARGE_EXPERT_NAME = synthid_detector.LARGE_DETECTOR_ID OPPONENT_EXPERT_NAME = synthid_detector.OPPONENT_REGISTERED_DETECTOR_ID FINE_OPPONENT_EXPERT_NAME = synthid_detector.FINE_OPPONENT_REGISTERED_DETECTOR_ID EXPERT_NAMES = ( FIXED_EXPERT_NAME, REGISTERED_EXPERT_NAME, LARGE_EXPERT_NAME, OPPONENT_EXPERT_NAME, FINE_OPPONENT_EXPERT_NAME, ) class ExpertScore(TypedDict): """One JSON-safe runtime expert observation.""" name: str supported: bool score: float | None class ScoredImage(TypedDict): """One hash-pinned image with every runtime expert observation.""" id: str path: str width: int height: int observations: list[ExpertScore] def _observation(name: str, supported: bool, score: float | None) -> ExpertScore: return {"name": name, "supported": supported, "score": score} def score_pixels(pixels: NDArray[np.uint8]) -> list[ExpertScore]: """Return five named slots; inactive registered fallbacks remain unsupported.""" if pixels.ndim != 3 or pixels.shape[2] != 3 or pixels.dtype != np.uint8: raise ValueError("pixels must be an RGB uint8 array") bgr_pixels = np.ascontiguousarray(pixels[:, :, ::-1]) native = synthid_detector.detect_synthid("decoded-image", image=bgr_pixels, register_scale=False) registered = synthid_detector.detect_synthid("decoded-image", image=bgr_pixels, register_scale=True) observations = {name: _observation(name, False, None) for name in EXPERT_NAMES} for result, expected in ( (native, {FIXED_EXPERT_NAME, LARGE_EXPERT_NAME}), (registered, {REGISTERED_EXPERT_NAME, OPPONENT_EXPERT_NAME, FINE_OPPONENT_EXPERT_NAME}), ): if result.detector not in expected: raise RuntimeError(f"unexpected SynthID expert: {result.detector}") observations[result.detector] = _observation(result.detector, result.status != "unsupported", result.score) return list(observations.values()) def score_path(path: Path) -> ScoredImage: """Decode PATH once and return one hash-pinned observation record.""" pixels = load_rgb(path) height, width = pixels.shape[:2] return { "id": artifact_sha256(path), "path": str(path), "width": width, "height": height, "observations": score_pixels(pixels), } @click.command() @click.argument("images", nargs=-1, required=True, type=click.Path(exists=True, dir_okay=False, path_type=Path)) @click.option("--report-out", type=click.Path(dir_okay=False, path_type=Path), required=True) def main(images: tuple[Path, ...], report_out: Path) -> None: """Score IMAGES with every shipped pixel expert.""" logging.basicConfig(level=logging.INFO, format="%(message)s") records = [score_path(path) for path in images] report_out.parent.mkdir(parents=True, exist_ok=True) report_out.write_text( json.dumps( { "schema_version": 2, "experts": list(EXPERT_NAMES), "records": records, }, indent=2, ) + "\n", encoding="utf-8", ) log.info("Wrote %d runtime expert score records: %s", len(records), report_out) if __name__ == "__main__": main()