Files
remove-ai-watermarks/tests/test_synthid_ensemble_detector.py

87 lines
2.9 KiB
Python

from __future__ import annotations
import sys
from pathlib import Path
from PIL import Image
sys.path.insert(0, str(Path(__file__).resolve().parent.parent / "scripts"))
import synthid_ensemble_detector as detector
from synthid_color_space_probe import ColorPhaseScore
def _config(tmp_path: Path) -> detector.EnsembleConfig:
return detector.EnsembleConfig(
width=64,
height=64,
rgb_model_path=tmp_path / "rgb.npz",
rgb_model_sha256="0" * 64,
rgb_evidence_threshold=0.3,
rgb_active_threshold=0.5,
hsv_model_path=tmp_path / "hsv.npz",
hsv_model_sha256="1" * 64,
hsv_sv_evidence_threshold=0.3,
hsv_active_threshold=0.5,
)
def _score(
*, color_space: str, evidence: float, active: float, channels: tuple[float, float, float]
) -> ColorPhaseScore:
return ColorPhaseScore(
path="fixture.png",
color_space=color_space,
phase_score=0.8,
active_weight_fraction=active,
evidence_score=evidence,
channel_evidence=channels,
selected_peak_counts=(80, 88, 88),
peak_count=256,
)
def test_positive_requires_both_branches_and_support(tmp_path: Path) -> None:
config = _config(tmp_path)
rgb = _score(color_space="rgb", evidence=0.4, active=0.7, channels=(0.1, 0.1, 0.2))
hsv = _score(color_space="hsv", evidence=0.45, active=0.8, channels=(0.05, 0.2, 0.2))
verdict = detector.classify_scores(Path("fixture.png"), rgb, hsv, config)
assert verdict.verdict == "positive"
assert verdict.reason == "ensemble_pass"
def test_low_support_abstains_even_when_scores_pass(tmp_path: Path) -> None:
config = _config(tmp_path)
rgb = _score(color_space="rgb", evidence=0.4, active=0.49, channels=(0.1, 0.1, 0.2))
hsv = _score(color_space="hsv", evidence=0.45, active=0.8, channels=(0.05, 0.2, 0.2))
verdict = detector.classify_scores(Path("fixture.png"), rgb, hsv, config)
assert verdict.verdict == "abstain"
assert verdict.reason == "insufficient_support"
def test_branch_disagreement_abstains(tmp_path: Path) -> None:
config = _config(tmp_path)
rgb = _score(color_space="rgb", evidence=0.4, active=0.8, channels=(0.1, 0.1, 0.2))
hsv = _score(color_space="hsv", evidence=0.2, active=0.8, channels=(0.02, 0.1, 0.08))
verdict = detector.classify_scores(Path("fixture.png"), rgb, hsv, config)
assert verdict.verdict == "abstain"
assert verdict.reason == "branch_disagreement"
def test_unsupported_geometry_abstains_without_scoring(tmp_path: Path) -> None:
config = _config(tmp_path)
image_path = tmp_path / "small.png"
Image.new("RGB", (32, 32)).save(image_path)
verdict = detector.detect_image(image_path, config, None, None) # type: ignore[arg-type]
assert verdict.verdict == "abstain"
assert verdict.reason == "unsupported_geometry"
assert verdict.rgb_evidence is None