mirror of
https://github.com/wiltodelta/remove-ai-watermarks.git
synced 2026-10-14 13:30:55 +02:00
122 lines
4.3 KiB
Python
122 lines
4.3 KiB
Python
#!/usr/bin/env python3
|
|
"""Summarize paired fidelity results from the engine-selection study.
|
|
|
|
Run from the repository root after ``engine_selection_study.py``:
|
|
|
|
uv run scripts/analyze_engine_selection_study.py
|
|
|
|
The report uses per-image paired differences and exact two-sided sign tests.
|
|
It does not turn the discovery set into a production routing rule.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import argparse
|
|
import json
|
|
import logging
|
|
import math
|
|
import statistics
|
|
from collections import defaultdict
|
|
from pathlib import Path
|
|
from typing import TYPE_CHECKING, Any
|
|
|
|
if TYPE_CHECKING:
|
|
from collections.abc import Callable
|
|
|
|
log = logging.getLogger(__name__)
|
|
|
|
_METRICS: dict[str, tuple[Callable[[float], float], str]] = {
|
|
"lpips_512": (lambda value: value, "lower"),
|
|
"ssim": (lambda value: -value, "higher"),
|
|
"psnr_db": (lambda value: -value, "higher"),
|
|
"mae": (lambda value: value, "lower"),
|
|
"edge_f1": (lambda value: -value, "higher"),
|
|
"laplacian_ratio": (lambda value: abs(math.log(max(value, 1e-12))), "closer_to_one"),
|
|
}
|
|
|
|
|
|
def _sign_test_two_sided(wins: int, losses: int) -> float:
|
|
"""Return the exact two-sided sign-test p-value, excluding ties."""
|
|
trials = wins + losses
|
|
if trials == 0:
|
|
return 1.0
|
|
tail = sum(math.comb(trials, index) for index in range(min(wins, losses) + 1))
|
|
return min(1.0, 2.0 * tail / (2**trials))
|
|
|
|
|
|
def _index(rows: list[dict[str, Any]]) -> dict[tuple[str, str], dict[str, Any]]:
|
|
return {(row["provider"], row["pair_id"]): row for row in rows}
|
|
|
|
|
|
def analyze(report: dict[str, Any]) -> dict[str, Any]:
|
|
qwen = _index(report["qwen-zimage"]["rows"])
|
|
chroma = _index(report["chroma-zimage"]["rows"])
|
|
if qwen.keys() != chroma.keys():
|
|
raise ValueError("Qwen and Chroma result keys differ")
|
|
|
|
by_provider: dict[str, list[tuple[dict[str, Any], dict[str, Any]]]] = defaultdict(list)
|
|
for key in sorted(qwen):
|
|
by_provider[key[0]].append((qwen[key], chroma[key]))
|
|
|
|
providers: dict[str, Any] = {}
|
|
for provider, pairs in sorted(by_provider.items()):
|
|
metrics: dict[str, Any] = {}
|
|
for metric, (loss_transform, direction) in _METRICS.items():
|
|
deltas: list[float] = []
|
|
chroma_wins = 0
|
|
qwen_wins = 0
|
|
ties = 0
|
|
for qwen_row, chroma_row in pairs:
|
|
qwen_value = float(qwen_row["fidelity"][metric])
|
|
chroma_value = float(chroma_row["fidelity"][metric])
|
|
delta = loss_transform(chroma_value) - loss_transform(qwen_value)
|
|
deltas.append(delta)
|
|
if math.isclose(delta, 0.0, abs_tol=1e-12):
|
|
ties += 1
|
|
elif delta < 0:
|
|
chroma_wins += 1
|
|
else:
|
|
qwen_wins += 1
|
|
metrics[metric] = {
|
|
"preferred_direction": direction,
|
|
"delta_definition": "chroma_loss_minus_qwen_loss; negative favors Chroma",
|
|
"chroma_wins": chroma_wins,
|
|
"qwen_wins": qwen_wins,
|
|
"ties": ties,
|
|
"mean_delta": statistics.fmean(deltas),
|
|
"median_delta": statistics.median(deltas),
|
|
"sign_test_two_sided_p": _sign_test_two_sided(chroma_wins, qwen_wins),
|
|
}
|
|
providers[provider] = {"pairs": len(pairs), "metrics": metrics}
|
|
|
|
return {
|
|
"method": "paired per-image differences with exact two-sided sign tests; ties excluded",
|
|
"warning": "Discovery-set evidence only; challenge any proposed rule on unused generations.",
|
|
"providers": providers,
|
|
}
|
|
|
|
|
|
def main() -> None:
|
|
parser = argparse.ArgumentParser()
|
|
parser.add_argument(
|
|
"--input",
|
|
type=Path,
|
|
default=Path("out/engine-selection-study/summary.json"),
|
|
)
|
|
parser.add_argument(
|
|
"--output",
|
|
type=Path,
|
|
default=Path("out/engine-selection-study/analysis.json"),
|
|
)
|
|
args = parser.parse_args()
|
|
report = json.loads(args.input.read_text(encoding="utf-8"))
|
|
analysis = analyze(report)
|
|
args.output.parent.mkdir(parents=True, exist_ok=True)
|
|
args.output.write_text(json.dumps(analysis, indent=2) + "\n", encoding="utf-8")
|
|
log.info("Wrote %s", args.output)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
logging.basicConfig(level=logging.INFO, format="%(levelname)s: %(message)s")
|
|
main()
|