| """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 |
|
|
| |
| |
| 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, |
| ) |
|
|