Files
remove-ai-watermarks/tests/test_synthid_pixel_probe.py
T
test-user 7dcc922617 feat(probe): solid-fill SynthID carrier probe; corpus reconfirms no pixel detector
scripts/synthid_pixel_probe.py is an experimental/diagnostic tool for the
one pixel-domain question that isn't a dead-end: on solid-color fills the
zero-mean residual IS essentially the watermark carrier. Two modes:
'consistency' (mean pairwise NCC of carriers across fills vs random
baseline) and 'removal' (does the pipeline drop the carrier toward
baseline?). Logic validated synthetically (injected carrier correlates,
random noise doesn't, simulated removal collapses it) -- no real fills or
GPU needed.

Running its metric on the corpus independently re-confirms the documented
dead-end for real content: at matched resolution SynthID positives do not
cluster apart from negatives (within-Gemini 0.07; at 1024 px pos-vs-neg
>= pos-vs-pos). An apparent 0.62 among 1254px ChatGPT positives turned out
to be near-duplicate content (5 renders of one prompt at ~0.92; a distinct
ChatGPT image scored ~0 against them), not a shared carrier. The probe is
solid-fills-only; do not use on real content.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-24 16:35:39 -07:00

105 lines
4.1 KiB
Python

"""Tests for the SynthID pixel-carrier probe.
The probe's logic is validated synthetically: a fixed carrier injected into
uniform fills must correlate strongly, random noise must not, and simulated
removal (dropping the carrier) must collapse the correlation. No real SynthID
fills or GPU are needed.
"""
from __future__ import annotations
import sys
from pathlib import Path
import numpy as np
from click.testing import CliRunner
from PIL import Image
# scripts/ is not an installed package; add it to the path for import.
sys.path.insert(0, str(Path(__file__).resolve().parent.parent / "scripts"))
import synthid_pixel_probe as probe
_SHAPE = (64, 64)
def _fixed_carrier(seed: int = 7) -> np.ndarray:
"""A reproducible 2-D carrier pattern (the stand-in for the SynthID signal)."""
rng = np.random.default_rng(seed)
return rng.standard_normal(_SHAPE)
def _fill_with_carrier(base: float, pattern: np.ndarray, noise_seed: int, noise: float = 0.05) -> np.ndarray:
"""A solid fill at brightness ``base`` carrying ``pattern`` plus light noise."""
rng = np.random.default_rng(noise_seed)
return base + pattern + noise * rng.standard_normal(_SHAPE)
class TestCarrier:
def test_flat_image_has_zero_carrier(self):
# A perfectly uniform fill (std 0, like the synthetic refs) carries nothing.
flat = np.zeros(_SHAPE)
assert not np.any(probe.carrier(flat))
def test_carrier_is_unit_norm(self):
c = probe.carrier(_fixed_carrier() + 100.0)
assert np.isclose(np.linalg.norm(c), 1.0)
def test_ncc_self_is_one(self):
c = probe.carrier(_fixed_carrier())
assert np.isclose(probe.ncc(c, c), 1.0)
def test_ncc_mismatched_shape_is_zero(self):
a = probe.carrier(np.random.default_rng(1).standard_normal((8, 8)))
b = probe.carrier(np.random.default_rng(2).standard_normal((16, 16)))
assert probe.ncc(a, b) == 0.0
class TestConsistency:
def test_shared_carrier_correlates_high(self):
pattern = _fixed_carrier()
carriers = [probe.carrier(_fill_with_carrier(b, pattern, s)) for s, b in enumerate((10, 60, 120, 200))]
assert probe.mean_pairwise_ncc(carriers) > 0.8
def test_random_fills_near_zero(self):
rng = np.random.default_rng(0)
carriers = [probe.carrier(rng.standard_normal(_SHAPE)) for _ in range(5)]
assert abs(probe.mean_pairwise_ncc(carriers)) < 0.2
def test_random_baseline_near_zero(self):
assert abs(probe.random_baseline(_SHAPE, 6)) < 0.2
class TestRemoval:
def test_removed_carrier_collapses_correlation(self):
pattern = _fixed_carrier()
pos = [probe.carrier(_fill_with_carrier(b, pattern, s)) for s, b in enumerate((20, 90, 160))]
tmpl = probe.template(pos)
# "Cleaned" fills keep the base + noise but lose the shared pattern.
rng = np.random.default_rng(99)
cleaned = [probe.carrier(b + 0.05 * rng.standard_normal(_SHAPE)) for b in (20, 90, 160)]
pos_corr = float(np.mean([probe.ncc(c, tmpl) for c in pos]))
cleaned_corr = float(np.mean([probe.ncc(c, tmpl) for c in cleaned]))
assert pos_corr > 0.8
assert cleaned_corr < 0.2
class TestCli:
def _solid_fill_png(self, tmp_path: Path, name: str, base: int, pattern: np.ndarray, seed: int) -> Path:
arr = np.clip(_fill_with_carrier(base, pattern, seed), 0, 255).astype(np.uint8)
path = tmp_path / name
Image.fromarray(np.stack([arr] * 3, axis=2)).save(path)
return path
def test_consistency_command_runs(self, tmp_path: Path):
pattern = _fixed_carrier()
paths = [str(self._solid_fill_png(tmp_path, f"f{i}.png", b, pattern, i)) for i, b in enumerate((40, 120, 200))]
result = CliRunner().invoke(probe.cli, ["consistency", *paths])
assert result.exit_code == 0, result.output
assert "mean pairwise NCC" in result.output
def test_consistency_needs_two_images(self, tmp_path: Path):
path = str(self._solid_fill_png(tmp_path, "only.png", 100, _fixed_carrier(), 0))
result = CliRunner().invoke(probe.cli, ["consistency", path])
assert result.exit_code != 0