Files
remove-ai-watermarks/tests/test_synthid_detector.py
T
Victor Kuznetsov 8eb9c06265 Reframe periodic pixel route as pipeline lattice; confirm and harden detection
The frozen periodic experts read an origin-anchored generation-pipeline lattice destroyed by a crop off the tile grid, not the crop-robust SynthID mark. Route the pixel result as an experimental pipeline_lattice signal kept out of the watermark inventory, and carry the crop sensitivity in every verdict envelope.

Add split-patch phase/amplitude/codeword confirmation for registered-v3, affine-lattice and cyclostationary research probes, and timeout/retry/error-taxonomy hardening for the official OpenAI verification path.
2026-08-16 21:53:37 -07:00

779 lines
28 KiB
Python

"""Runtime tests for the positive-only SynthID periodic carrier detector."""
from __future__ import annotations
import hashlib
from pathlib import Path
import numpy as np
import pytest
from PIL import Image
import remove_ai_watermarks.synthid_detector as detector
@pytest.fixture(scope="module")
def supported_images(tmp_path_factory: pytest.TempPathFactory) -> tuple[Path, Path]:
"""Create supported-geometry positive and negative synthetic fixtures."""
directory = tmp_path_factory.mktemp("synthid-detector")
template, *_model = detector._load_template()
scaled_tile = np.rint(template / np.max(np.abs(template)))
marked = np.full((detector.MODEL_HEIGHT, detector.MODEL_WIDTH, 3), 128, dtype=np.float64)
marked += np.tile(scaled_tile, (128, 128, 1))
positive = directory / "positive.png"
negative = directory / "negative.png"
Image.fromarray(np.clip(np.rint(marked), 0, 255).astype(np.uint8), "RGB").save(positive)
Image.new("RGB", (detector.MODEL_WIDTH, detector.MODEL_HEIGHT), (128, 128, 128)).save(negative)
return positive, negative
@pytest.fixture(scope="module")
def registered_scale_positive(tmp_path_factory: pytest.TempPathFactory) -> Path:
"""Create a strong period-12.8 carrier by shrinking a period-16 source."""
import cv2
directory = tmp_path_factory.mktemp("synthid-registered")
template, *_model = detector._load_template()
scaled_tile = template / np.max(np.abs(template)) * 40.0
source = np.tile(scaled_tile, (64, 64, 1)) + 128.0
pixels = cv2.resize(
np.clip(np.rint(source), 0, 255).astype(np.uint8),
(819, 819),
interpolation=cv2.INTER_AREA,
)
path = directory / "period-12.8-positive.png"
Image.fromarray(pixels, "RGB").save(path)
return path
@pytest.fixture(scope="module")
def opponent_registered_positive(tmp_path_factory: pytest.TempPathFactory) -> Path:
"""Create a strong period-10 opponent-color fallback fixture."""
import cv2
directory = tmp_path_factory.mktemp("synthid-opponent-registered")
template, *_model = detector._load_template()
scaled_tile = template / np.max(np.abs(template)) * 40.0
source = np.tile(scaled_tile, (128, 128, 1)) + 128.0
pixels = cv2.resize(
np.clip(np.rint(source), 0, 255).astype(np.uint8),
(1280, 1280),
interpolation=cv2.INTER_AREA,
)
path = directory / "period-10-positive.png"
Image.fromarray(pixels, "RGB").save(path)
return path
@pytest.fixture(scope="module")
def opponent_period8_positive(tmp_path_factory: pytest.TempPathFactory) -> Path:
"""Create a strong period-8 fallback fixture without native JPEG block edges."""
import cv2
directory = tmp_path_factory.mktemp("synthid-opponent-period8")
template, *_model = detector._load_template()
scaled_tile = template / np.max(np.abs(template)) * 40.0
source = np.tile(scaled_tile, (128, 128, 1)) + 128.0
pixels = cv2.resize(
np.clip(np.rint(source), 0, 255).astype(np.uint8),
(1024, 1024),
interpolation=cv2.INTER_AREA,
)
path = directory / "period-8-positive.png"
Image.fromarray(pixels, "RGB").save(path)
return path
@pytest.fixture(scope="module")
def fine_opponent_registered_positive(tmp_path_factory: pytest.TempPathFactory) -> Path:
"""Create a strong period-7.68 carrier missed by the coarse period grid."""
import cv2
directory = tmp_path_factory.mktemp("synthid-fine-opponent-registered")
template, *_model = detector._load_template()
scaled_tile = template / np.max(np.abs(template)) * 40.0
source = np.tile(scaled_tile, (144, 144, 1)) + 128.0
pixels = cv2.resize(
np.clip(np.rint(source), 0, 255).astype(np.uint8),
(1106, 1106),
interpolation=cv2.INTER_AREA,
)
path = directory / "period-7.68-positive.png"
Image.fromarray(pixels, "RGB").save(path)
return path
def test_bundled_model_is_the_frozen_calibrated_artifact() -> None:
model = Path(detector.__file__).parent / "assets" / detector.MODEL_FILENAME
assert hashlib.sha256(model.read_bytes()).hexdigest() == (
"ee7838da8542c206c3403284b68e98f0ac99429e82f262c1a438f50a638b488b"
)
@pytest.mark.parametrize(
("width", "height"),
[(1000, 1000), (1001, 1000), (3000, 6000), (768, 1364)],
)
def test_supported_geometry_uses_the_challenged_pixel_count_range(width: int, height: int) -> None:
assert detector._geometry_supported(width, height)
@pytest.mark.parametrize(
("width", "height"),
[(999, 1000), (3001, 6000), (64, 32)],
)
def test_geometry_outside_the_challenged_pixel_count_range_is_unsupported(
width: int,
height: int,
) -> None:
assert not detector._geometry_supported(width, height)
@pytest.mark.parametrize(
("width", "height", "supported"),
[
(500, 500, True),
(4000, 2500, True),
(256, 977, True),
(499, 500, False),
(4001, 2500, False),
(255, 981, False),
(64, 3907, False),
(32, 7813, False),
],
)
def test_registered_geometry_uses_its_measured_pixel_count_range(
width: int,
height: int,
supported: bool,
) -> None:
assert detector._registered_geometry_supported(width, height) is supported
@pytest.mark.parametrize(
("width", "height", "supported"),
[
(1000, 1000, True),
(4000, 2500, True),
(767, 1304, False),
(1000, 999, False),
(4001, 2500, False),
],
)
def test_opponent_registered_geometry_uses_its_frozen_domain(
width: int,
height: int,
supported: bool,
) -> None:
assert detector._opponent_registered_geometry_supported(width, height) is supported
@pytest.mark.parametrize(
("width", "height", "supported"),
[
(1000, 1000, True),
(2500, 2000, True),
(767, 1304, False),
(1000, 999, False),
(2501, 2000, False),
],
)
def test_fine_opponent_registered_geometry_uses_its_frozen_domain(
width: int,
height: int,
supported: bool,
) -> None:
assert detector._fine_opponent_registered_geometry_supported(width, height) is supported
@pytest.mark.parametrize(
("width", "height", "supported"),
[
(4883, 2048, True),
(3072, 5504, True),
(2048, 4882, False),
(2047, 6000, False),
(3001, 6000, False),
],
)
def test_large_geometry_requires_multiple_calibrated_windows(
width: int,
height: int,
supported: bool,
) -> None:
assert detector._large_geometry_supported(width, height) is supported
def test_large_window_starts_cover_both_edges_on_carrier_phase() -> None:
starts = detector._large_window_starts(5504)
assert starts == (0, 2048, 3456)
assert all(start % detector.LARGE_PHASE == 0 for start in starts)
assert starts[-1] + detector.LARGE_WINDOW == 5504
def test_large_components_apply_the_portrait_alias_guard_only_to_its_geometry() -> None:
values = {
"minimum_fixed_score": 0.28,
"minimum_red_green_spatial": 0.95,
"minimum_blue_yellow_spatial": 0.85,
"minimum_blue_yellow_mid_band": -0.30,
"maximum_green_mid_band": 0.061,
}
portrait = detector.LargeImageComponents(width=3072, height=5504, **values)
landscape = detector.LargeImageComponents(width=5504, height=3072, **values)
assert portrait.decision_score < detector.LARGE_THRESHOLD
assert landscape.decision_score > detector.LARGE_THRESHOLD
def test_large_red_green_gate_mutation_changes_the_real_verdict(
monkeypatch: pytest.MonkeyPatch,
) -> None:
width, height = 4883, 2048
image = np.broadcast_to(np.zeros((1, 1, 3), dtype=np.uint8), (height, width, 3))
components = detector.LargeImageComponents(
width=width,
height=height,
minimum_fixed_score=0.28,
minimum_red_green_spatial=detector.LARGE_RED_GREEN_SPATIAL_MIN,
minimum_blue_yellow_spatial=0.85,
minimum_blue_yellow_mid_band=-0.30,
maximum_green_mid_band=0.0,
)
monkeypatch.setattr(detector, "is_available", lambda: True)
monkeypatch.setattr(detector, "_load_template", lambda: (np.zeros((16, 16, 3)), 1.0, 0, 0, 0, 0))
monkeypatch.setattr(detector, "large_image_components", lambda *_args: components)
baseline = detector.detect_synthid("unused.png", image=image)
monkeypatch.setattr(
detector,
"LARGE_RED_GREEN_SPATIAL_MIN",
float(np.nextafter(components.minimum_red_green_spatial, np.inf)),
)
mutated = detector.detect_synthid("unused.png", image=image)
assert baseline.status == "detected"
assert baseline.detector == detector.LARGE_DETECTOR_ID
assert mutated.status == "indeterminate"
def test_uncalibrated_narrow_large_geometry_is_unsupported() -> None:
image = np.broadcast_to(np.zeros((1, 1, 3), dtype=np.uint8), (11_000, 1000, 3))
result = detector.detect_synthid("unused.png", image=image)
assert result.status == "unsupported"
assert result.detector == detector.LARGE_DETECTOR_ID
assert result.score is None
def test_registered_mode_rejects_a_side_too_short_for_quadrants(tmp_path: Path) -> None:
path = tmp_path / "too-narrow.png"
Image.new("RGB", (32, 7813), "white").save(path)
result = detector.detect_synthid(path, register_scale=True)
assert result.status == "unsupported"
assert result.score is None
assert result.detector == detector.REGISTERED_DETECTOR_ID
def test_detects_supported_periodic_carrier(supported_images: tuple[Path, Path]) -> None:
positive, _negative = supported_images
result = detector.detect_synthid(positive, register_scale=False)
assert result.status == "detected"
assert result.detected is True
assert result.score is not None
assert result.score > result.threshold
assert result.to_dict()["detector"] == detector.DETECTOR_ID
def test_detects_unregistered_non_divisible_geometry_in_size_range(tmp_path: Path) -> None:
width, height = 1001, 1000
template, *_model = detector._load_template()
scaled_tile = np.rint(template / np.max(np.abs(template)))
repeats_y = (height + scaled_tile.shape[0] - 1) // scaled_tile.shape[0]
repeats_x = (width + scaled_tile.shape[1] - 1) // scaled_tile.shape[1]
carrier = np.tile(scaled_tile, (repeats_y, repeats_x, 1))[:height, :width]
pixels = np.clip(np.rint(carrier + 128.0), 0, 255).astype(np.uint8)
path = tmp_path / "non-divisible-positive.png"
Image.fromarray(pixels, "RGB").save(path)
result = detector.detect_synthid(path, register_scale=False)
assert result.status == "detected"
assert (result.width, result.height) == (width, height)
assert result.score is not None
assert result.score > result.threshold
def test_registered_mode_detects_a_rescaled_carrier(registered_scale_positive: Path) -> None:
fixed = detector.detect_synthid(registered_scale_positive, register_scale=False)
default = detector.detect_synthid(registered_scale_positive)
registered = detector.detect_synthid(registered_scale_positive, register_scale=True)
assert fixed.status == "unsupported"
assert default == registered
assert registered.status == "detected"
assert registered.score is not None
assert registered.score > registered.threshold
assert registered.threshold == detector.REGISTERED_THRESHOLD
assert registered.detector == detector.REGISTERED_DETECTOR_ID
def test_registered_mode_falls_back_to_the_opponent_color_expert(
monkeypatch: pytest.MonkeyPatch,
opponent_registered_positive: Path,
) -> None:
import remove_ai_watermarks._synthid_registered as registered_detector
monkeypatch.setattr(registered_detector, "registered_score", lambda *_args: 0.0)
result = detector.detect_synthid(opponent_registered_positive, register_scale=True)
assert result.status == "detected"
assert result.detector == detector.OPPONENT_REGISTERED_DETECTOR_ID
assert result.score is not None
assert result.score >= result.threshold
def test_opponent_registered_threshold_mutation_changes_the_real_verdict(
monkeypatch: pytest.MonkeyPatch,
opponent_registered_positive: Path,
) -> None:
import remove_ai_watermarks._synthid_registered as registered_detector
monkeypatch.setattr(registered_detector, "registered_score", lambda *_args: 0.0)
baseline = detector.detect_synthid(opponent_registered_positive, register_scale=True)
assert baseline.score is not None
assert baseline.detector == detector.OPPONENT_REGISTERED_DETECTOR_ID
monkeypatch.setattr(
detector,
"OPPONENT_REGISTERED_THRESHOLD",
float(np.nextafter(baseline.score, np.inf)),
)
mutated = detector.detect_synthid(opponent_registered_positive, register_scale=True)
assert mutated.status == "indeterminate"
assert mutated.detector == detector.REGISTERED_DETECTOR_ID
def test_opponent_fallback_recovers_period8_without_codec_grid(
monkeypatch: pytest.MonkeyPatch,
opponent_period8_positive: Path,
) -> None:
import remove_ai_watermarks._synthid_registered as registered_detector
monkeypatch.setattr(registered_detector, "registered_score", lambda *_args: 0.0)
result = detector.detect_synthid(opponent_period8_positive, register_scale=True)
assert result.status == "detected"
assert result.detector == detector.OPPONENT_REGISTERED_DETECTOR_ID
def test_fine_opponent_fallback_recovers_off_grid_period(
monkeypatch: pytest.MonkeyPatch,
fine_opponent_registered_positive: Path,
) -> None:
import remove_ai_watermarks._synthid_registered as registered_detector
monkeypatch.setattr(registered_detector, "registered_score", lambda *_args: 0.0)
monkeypatch.setattr(registered_detector, "opponent_registered_score", lambda *_args: 0.0)
result = detector.detect_synthid(fine_opponent_registered_positive, register_scale=True)
assert result.status == "detected"
assert result.detector == detector.FINE_OPPONENT_REGISTERED_DETECTOR_ID
assert result.score is not None
assert result.score >= detector.FINE_OPPONENT_REGISTERED_THRESHOLD
def test_fine_opponent_threshold_mutation_changes_the_real_verdict(
monkeypatch: pytest.MonkeyPatch,
fine_opponent_registered_positive: Path,
) -> None:
import remove_ai_watermarks._synthid_registered as registered_detector
monkeypatch.setattr(registered_detector, "registered_score", lambda *_args: 0.0)
monkeypatch.setattr(registered_detector, "opponent_registered_score", lambda *_args: 0.0)
baseline = detector.detect_synthid(fine_opponent_registered_positive, register_scale=True)
assert baseline.score is not None
assert baseline.detector == detector.FINE_OPPONENT_REGISTERED_DETECTOR_ID
monkeypatch.setattr(
detector,
"FINE_OPPONENT_REGISTERED_THRESHOLD",
float(np.nextafter(baseline.score, np.inf)),
)
mutated = detector.detect_synthid(fine_opponent_registered_positive, register_scale=True)
assert mutated.status == "indeterminate"
assert mutated.detector == detector.REGISTERED_DETECTOR_ID
def test_fine_opponent_selector_recovers_the_fractional_period(
fine_opponent_registered_positive: Path,
) -> None:
import remove_ai_watermarks._synthid_registered as registered_detector
template, sigma, *_model = detector._load_template()
pixels = np.asarray(Image.open(fine_opponent_registered_positive).convert("RGB"), dtype=np.uint8)
components = registered_detector.fine_opponent_registered_components(pixels, template, sigma)
assert components.selected_period == pytest.approx(7.68, abs=0.01)
assert components.fine_decision_score >= detector.FINE_OPPONENT_REGISTERED_THRESHOLD
assert components.candidate_count >= 100
def test_period8_codec_veto_threshold_mutation_changes_real_components(
monkeypatch: pytest.MonkeyPatch,
opponent_period8_positive: Path,
) -> None:
import remove_ai_watermarks._synthid_registered as registered_detector
template, sigma, *_model = detector._load_template()
pixels = np.asarray(Image.open(opponent_period8_positive).convert("RGB"), dtype=np.uint8)
components = registered_detector.opponent_registered_components(pixels, template, sigma)
assert components.decision_score >= detector.OPPONENT_REGISTERED_THRESHOLD
assert components.red_green_p8_edge_ratio is not None
assert components.blue_yellow_p8_edge_ratio is not None
monkeypatch.setattr(registered_detector, "OPPONENT_REGISTERED_MAX_P8_EDGE_RATIO", 0.9)
assert components.decision_score == 0.0
def test_opponent_registered_period_band_and_codec_veto_are_required() -> None:
from remove_ai_watermarks._synthid_registered import OpponentRegisteredComponents
values = {
"spectral_score": 0.8,
"fixed_score": 0.32,
"red_green_spatial": 0.9,
"blue_yellow_spatial": 0.8,
"candidate_count": 3,
"red_green_p8_edge_ratio": None,
"blue_yellow_p8_edge_ratio": None,
}
matching = OpponentRegisteredComponents(10.0, 10.0, **values)
period8 = OpponentRegisteredComponents(
8.0,
8.0,
**{
**values,
"red_green_p8_edge_ratio": 1.0,
"blue_yellow_p8_edge_ratio": 1.0,
},
)
codec_alias = OpponentRegisteredComponents(
8.0,
8.0,
**{
**values,
"red_green_p8_edge_ratio": 1.2,
"blue_yellow_p8_edge_ratio": 1.2,
},
)
assert matching.decision_score > detector.OPPONENT_REGISTERED_THRESHOLD
assert period8.decision_score > detector.OPPONENT_REGISTERED_THRESHOLD
assert codec_alias.base_decision_score > detector.OPPONENT_REGISTERED_THRESHOLD
assert codec_alias.decision_score == 0.0
def test_registered_threshold_mutation_changes_the_real_verdict(
monkeypatch: pytest.MonkeyPatch,
registered_scale_positive: Path,
) -> None:
baseline = detector.detect_synthid(registered_scale_positive, register_scale=True)
assert baseline.score is not None
mutated_threshold = float(np.nextafter(baseline.score, np.inf))
monkeypatch.setattr(detector, "REGISTERED_THRESHOLD", mutated_threshold)
mutated = detector.detect_synthid(registered_scale_positive, register_scale=True)
assert mutated.status == "indeterminate"
assert mutated.threshold == mutated_threshold
def test_registered_period_thresholds_cover_the_bounded_search() -> None:
from remove_ai_watermarks._synthid_registered import _period_threshold
assert _period_threshold(7.5) == pytest.approx(0.3770629524888979)
assert _period_threshold(12.0) == pytest.approx(0.19794247706938645)
assert _period_threshold(24.5) == pytest.approx(0.3142958338390489)
with pytest.raises(ValueError, match="outside"):
_period_threshold(7.49)
def test_registered_amplitude_threshold_mutation_changes_the_real_verdict(
monkeypatch: pytest.MonkeyPatch,
registered_scale_positive: Path,
) -> None:
import remove_ai_watermarks._synthid_registered as registered_detector
baseline = detector.detect_synthid(registered_scale_positive, register_scale=True)
assert baseline.status == "detected"
monkeypatch.setattr(
registered_detector,
"_PERIOD_THRESHOLDS",
((7.5, 24.5, float("inf")),),
)
mutated = detector.detect_synthid(registered_scale_positive, register_scale=True)
assert mutated.status == "indeterminate"
def test_registered_spectral_candidate_disagreement_blocks_decision() -> None:
from remove_ai_watermarks._synthid_confirmation import RegisteredConfirmationComponents
from remove_ai_watermarks._synthid_registered import RegisteredComponents
confirmation = RegisteredConfirmationComponents(12.8, 0.5, 0.2, 0.5, 8, 8)
matching = RegisteredComponents(0.5, 0.25, 12.8, 12.8, 0.15, confirmation)
mismatching = RegisteredComponents(0.5, 0.25, 12.8, 12.9, 0.15, confirmation)
unconfirmed = RegisteredComponents(0.5, 0.25, 12.8, 12.8, 0.15)
assert matching.decision_score == pytest.approx(2.0)
assert mismatching.decision_score == pytest.approx(0.0)
assert unconfirmed.base_decision_score == pytest.approx(2.0)
assert unconfirmed.decision_score == pytest.approx(0.0)
def test_registered_high_band_mutation_changes_the_real_verdict(
monkeypatch: pytest.MonkeyPatch,
registered_scale_positive: Path,
) -> None:
import remove_ai_watermarks._synthid_registered as registered_detector
components = registered_detector.registered_components(
np.asarray(Image.open(registered_scale_positive).convert("RGB"), dtype=np.uint8),
detector._load_template()[0],
detector._load_template()[1],
)
assert components.decision_score >= detector.REGISTERED_THRESHOLD
monkeypatch.setattr(
registered_detector,
"REGISTERED_HIGH_BAND_THRESHOLD",
float(np.nextafter(components.high_band_score, np.inf)),
)
mutated = detector.detect_synthid(registered_scale_positive, register_scale=True)
assert mutated.status == "indeterminate"
def test_registered_confirmation_mutation_changes_the_real_verdict(
monkeypatch: pytest.MonkeyPatch,
registered_scale_positive: Path,
) -> None:
import remove_ai_watermarks._synthid_confirmation as confirmation_detector
import remove_ai_watermarks._synthid_registered as registered_detector
components = registered_detector.registered_components(
np.asarray(Image.open(registered_scale_positive).convert("RGB"), dtype=np.uint8),
detector._load_template()[0],
detector._load_template()[1],
)
assert components.confirmation is not None
assert components.decision_score >= detector.REGISTERED_THRESHOLD
monkeypatch.setattr(
confirmation_detector,
"MIN_COHERENCE",
float(np.nextafter(components.confirmation.joint_coherence, np.inf)),
)
mutated = detector.detect_synthid(registered_scale_positive, register_scale=True)
assert mutated.status == "indeterminate"
def test_supported_negative_does_not_claim_clean(supported_images: tuple[Path, Path]) -> None:
_positive, negative = supported_images
result = detector.detect_synthid(negative, register_scale=False)
assert result.status == "indeterminate"
assert result.detected is False
assert result.score == pytest.approx(0.0)
def test_threshold_mutation_changes_the_real_verdict(
monkeypatch: pytest.MonkeyPatch,
supported_images: tuple[Path, Path],
) -> None:
positive, _negative = supported_images
baseline = detector.detect_synthid(positive, register_scale=False)
assert baseline.score is not None
assert baseline.status == "detected"
mutated_threshold = float(np.nextafter(baseline.score, np.inf))
assert mutated_threshold > baseline.score
monkeypatch.setattr(detector, "TILE_THRESHOLD", mutated_threshold)
mutated = detector.detect_synthid(positive, register_scale=False)
assert mutated.status == "indeterminate"
assert mutated.threshold == mutated_threshold
def test_unsupported_geometry_is_distinct_from_negative(tmp_path: Path) -> None:
path = tmp_path / "small.png"
Image.new("RGB", (64, 32), "white").save(path)
result = detector.detect_synthid(path, register_scale=False)
assert result.status == "unsupported"
assert result.score is None
assert (result.width, result.height) == (64, 32)
assert result.reason is not None
assert result.to_dict()["metadata_used_for_verdict"] is False
assert result.to_dict()["provider_scope"] == "provider-neutral"
def test_shared_bgr_decode_matches_file_decode(supported_images: tuple[Path, Path]) -> None:
import cv2
positive, _negative = supported_images
bgr = cv2.imread(str(positive))
assert bgr is not None
from_file = detector.detect_synthid(positive, register_scale=False)
from_array = detector.detect_synthid(positive, image=bgr, register_scale=False)
assert from_array == from_file
def test_supported_geometry_requires_pixel_dependencies(
monkeypatch: pytest.MonkeyPatch,
supported_images: tuple[Path, Path],
) -> None:
_positive, negative = supported_images
monkeypatch.setattr(detector, "is_available", lambda: False)
with pytest.raises(RuntimeError, match="pixel extra"):
detector.detect_synthid(negative, register_scale=False)
def test_fold_accepts_non_divisible_geometry_without_resampling() -> None:
rng = np.random.default_rng(20260810)
tile = rng.normal(0.0, 8.0, size=(16, 16, 3))
repeated = np.tile(tile, (19, 20, 1)) + 128.0
divisible = detector.fold_residual_template(
repeated,
tile_height=16,
tile_width=16,
denoise_sigma=1.0,
)
non_divisible = detector.fold_residual_template(
repeated[:299, :317],
tile_height=16,
tile_width=16,
denoise_sigma=1.0,
)
divisible_unit, _ = detector.unit_tile(divisible)
non_divisible_unit, _ = detector.unit_tile(non_divisible)
assert non_divisible.shape == (16, 16, 3)
assert float(np.sum(divisible_unit * non_divisible_unit)) > 0.999
def test_non_divisible_fold_matches_modulo_cell_means() -> None:
import cv2
rng = np.random.default_rng(44041)
pixels = rng.integers(0, 256, size=(53, 71, 3), dtype=np.uint8)
source = pixels.astype(np.float32)
residual = source - cv2.GaussianBlur(
source,
(0, 0),
sigmaX=1.25,
sigmaY=1.25,
borderType=cv2.BORDER_REFLECT_101,
)
expected = np.empty((16, 16, 3), dtype=np.float64)
for tile_y in range(16):
for tile_x in range(16):
expected[tile_y, tile_x] = residual[tile_y::16, tile_x::16].mean(
axis=(0, 1),
dtype=np.float64,
)
expected -= np.mean(expected, axis=(0, 1), keepdims=True)
actual = detector.fold_residual_template(
pixels,
tile_height=16,
tile_width=16,
denoise_sigma=1.25,
)
np.testing.assert_allclose(actual, expected, rtol=0.0, atol=0.0)
def test_fold_rejects_tile_larger_than_image() -> None:
pixels = np.zeros((15, 16, 3), dtype=np.uint8)
with pytest.raises(ValueError, match="at least as large"):
detector.fold_residual_template(
pixels,
tile_height=16,
tile_width=16,
denoise_sigma=1.0,
)
def test_verdict_does_not_claim_the_watermark() -> None:
"""The result must not assert SynthID, because the statistic is not SynthID.
This was unguarded until 2026-08-16, and the claim had been wrong for months
without a single test noticing. The fields are pinned by value rather than by
presence so that a rename back to a watermark claim fails here.
"""
result = detector.SynthIDDetection(
status="detected",
width=4096,
height=2560,
score=1.0,
threshold=1.0,
)
payload = result.to_dict()
assert payload["signal_family"] == "generation-pipeline-lattice"
assert payload["identifies_watermark"] is False
assert payload["tile_aligned_crop_required"] is True
assert "synthid" not in str(payload["signal_family"]).lower()
def test_the_statistic_is_locked_to_the_image_origin() -> None:
"""A crop off the tile grid must destroy the score, and that must stay visible.
SynthID's published evaluation retains 99.97% TPR under aggressive crop and
resize. This statistic loses everything to a seven-pixel shift, measured on
the real runtime at 4096x2560 where aligned crops scored up to 1.069 and
shifted ones reached -0.438. The property is asserted here so that any future
expert claiming to read the watermark has to survive the same shift first.
"""
template, sigma, *_model = detector._load_template()
tile = template / np.max(np.abs(template))
pixels = np.full((1024, 1024, 3), 128.0)
pixels += 6.0 * np.tile(tile, (64, 64, 1))
aligned = np.clip(np.rint(pixels), 0, 255).astype(np.uint8)
aligned_score, _folded = detector.folded_template_score(aligned, template, sigma)
# Seven is deliberately coprime with the 16-pixel tile, so no residual phase survives.
shifted_score, _shifted_folded = detector.folded_template_score(
aligned[7:, 7:],
template,
sigma,
)
assert aligned_score > 0.5
assert shifted_score < 0.1 * aligned_score