"""The hard stealth gate: pass/fail against configured thresholds.""" from __future__ import annotations from typing import Callable, Optional from PIL import Image from veil_pgd.config import StealthThresholds, get_settings from veil_pgd.stealth import metrics as m from veil_pgd.types import StealthReport # A LPIPS provider is injected (klaus-3 service). If absent, LPIPS is skipped # (treated as passing) and noted so the gate degrades gracefully offline. LpipsFn = Callable[[Image.Image, Image.Image], float] def evaluate_stealth( orig: Image.Image, mod: Image.Image, box: Optional[tuple[int, int, int, int]] = None, thresholds: Optional[StealthThresholds] = None, lpips_fn: Optional[LpipsFn] = None, ) -> StealthReport: th = thresholds or get_settings().stealth psnr_v = m.psnr(orig, mod) ssim_v = m.ssim(orig, mod) de_v = m.delta_e_p95(orig, mod, box) lpips_v = lpips_fn(orig, mod) if lpips_fn is not None else float("nan") failures: list[str] = [] if psnr_v < th.psnr_min: failures.append(f"psnr {psnr_v:.1f}<{th.psnr_min}") if ssim_v < th.ssim_min: failures.append(f"ssim {ssim_v:.3f}<{th.ssim_min}") if de_v > th.delta_e_p95_max: failures.append(f"dE {de_v:.2f}>{th.delta_e_p95_max}") if lpips_fn is not None and lpips_v > th.lpips_max: failures.append(f"lpips {lpips_v:.3f}>{th.lpips_max}") return StealthReport( psnr=psnr_v, ssim=ssim_v, lpips=lpips_v, delta_e_p95=de_v, passed=len(failures) == 0, failures=failures, )