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.
This commit is contained in:
Victor Kuznetsov
2026-08-16 21:53:37 -07:00
parent 2d018d32ab
commit 8eb9c06265
18 changed files with 3912 additions and 102 deletions
+1
View File
@@ -25,6 +25,7 @@ class TestTopLevelExports:
assert raiw.detect_synthid is synthid_detector.detect_synthid
assert raiw.SynthIDDetection is synthid_detector.SynthIDDetection
assert raiw.verify_openai_synthid is openai_provenance.verify_openai_synthid
assert raiw.OpenAIProvenanceError is openai_provenance.OpenAIProvenanceError
assert raiw.OpenAISynthIDDetection is openai_provenance.OpenAISynthIDDetection
def test_unknown_attribute_raises(self):
+2 -1
View File
@@ -742,6 +742,7 @@ class TestDetectSynthIDCommand:
assert result.exit_code == 0
assert "calibrated image sizes" in result.output
assert "--register-scale" in result.output
assert "--fixed-period" in result.output
def test_unsupported_geometry_is_machine_readable(self, runner, tmp_clean_png):
result = runner.invoke(main, ["detect-synthid", str(tmp_clean_png), "--json"])
@@ -782,7 +783,7 @@ class TestDetectSynthIDCommand:
)
assert result.exit_code == 0, result.output
assert "Bounded spatial-scale registration was enabled" in result.output
assert "Bounded spatial-scale registration was explicitly enabled" in result.output
class TestVerifyOpenAISynthIDCommand:
+17 -9
View File
@@ -886,30 +886,38 @@ class TestIdentifyVisibleTextMarks:
# ── Caveats and serialization ───────────────────────────────────────
class TestSynthIDPixelCarrier:
def test_positive_pixel_carrier_is_high_confidence_ai_evidence(self, tmp_clean_png: Path):
class TestGenerationPipelineLattice:
def test_positive_lattice_is_ai_evidence_but_never_a_watermark(self, tmp_clean_png: Path):
"""The lattice may support an AI verdict; it may not enter the watermark list.
It accepts 24% of Adobe Firefly output and dies on a seven-pixel crop, so
reporting it beside C2PA watermark assertions would misrepresent both. The
watermark assertion is checked by absence, because that is the failure that
actually shipped.
"""
with (
patch("remove_ai_watermarks.identify._invisible_watermark", return_value=None),
patch("remove_ai_watermarks.identify._synthid_pixel_watermark", return_value=True),
patch("remove_ai_watermarks.identify._pipeline_lattice", return_value=True),
patch("remove_ai_watermarks.identify._trustmark", return_value=None),
):
report = identify(tmp_clean_png, check_visible=False, check_invisible=True)
assert report.is_ai_generated is True
assert report.confidence == "high"
assert any(signal.name == "synthid_pixel" for signal in report.signals)
assert any("positive-only" in caveat for caveat in report.caveats)
assert any(signal.name == "pipeline_lattice" for signal in report.signals)
assert not any("synthid" in watermark.lower() for watermark in report.watermarks)
assert not any("watermark" in watermark.lower() for watermark in report.watermarks)
assert any("not a watermark" in caveat for caveat in report.caveats)
def test_negative_pixel_carrier_does_not_claim_clean(self, tmp_clean_png: Path):
def test_negative_lattice_does_not_claim_clean(self, tmp_clean_png: Path):
with (
patch("remove_ai_watermarks.identify._invisible_watermark", return_value=None),
patch("remove_ai_watermarks.identify._synthid_pixel_watermark", return_value=False),
patch("remove_ai_watermarks.identify._pipeline_lattice", return_value=False),
patch("remove_ai_watermarks.identify._trustmark", return_value=None),
):
report = identify(tmp_clean_png, check_visible=False, check_invisible=True)
assert report.is_ai_generated is None
assert not any(signal.name == "synthid_pixel" for signal in report.signals)
assert not any(signal.name == "pipeline_lattice" for signal in report.signals)
@pytest.mark.skipif(not SAMPLES_DIR.exists(), reason="data/fixtures/provenance not present")
+163 -6
View File
@@ -20,7 +20,8 @@ class _Checks:
self.response = response
self.calls: list[tuple[str, bytes, str]] = []
def create(self, *, file: tuple[str, Any, str]) -> Any:
def create(self, *, file: tuple[str, Any, str], timeout: float) -> Any:
assert timeout == provenance.REQUEST_TIMEOUT_SECONDS
filename, stream, media_type = file
self.calls.append((filename, stream.read(), media_type))
return self.response
@@ -94,6 +95,8 @@ def test_detected_result_uses_only_synthid_fields(tmp_clean_png: Path) -> None:
assert result.generated_at == "2026-07-28T18:34:12Z"
assert result.api_created_at == 1_778_000_000
assert "c2pa" not in result.to_dict()
assert result.to_dict()["metadata_used_for_verdict"] is False
assert result.to_dict()["provider_scope"] == "openai"
def test_sdk_model_response_is_normalized(tmp_clean_png: Path) -> None:
@@ -109,6 +112,28 @@ def test_sdk_model_response_is_normalized(tmp_clean_png: Path) -> None:
assert result.status == "detected"
def test_unexpected_response_object_is_an_error(tmp_clean_png: Path) -> None:
response = _response(synthid="detected")
response["object"] = "future_response"
client, _checks = _client(response)
with pytest.raises(RuntimeError, match="unexpected 'object'"):
_verify(tmp_clean_png, client=client)
@pytest.mark.parametrize("entry", [None, {"outcome": "detected"}, {"type": 3, "outcome": "detected"}])
def test_malformed_result_entry_is_an_error(tmp_clean_png: Path, entry: Any) -> None:
client, _checks = _client(
{
"object": "content_provenance_check",
"results": [entry],
}
)
with pytest.raises(RuntimeError, match=r"invalid result entry|valid type"):
_verify(tmp_clean_png, client=client)
@pytest.mark.parametrize(
("image_format", "suffix", "media_type"),
[("PNG", ".png", "image/png"), ("JPEG", ".jpg", "image/jpeg"), ("WEBP", ".webp", "image/webp")],
@@ -138,7 +163,7 @@ def test_all_documented_image_formats_preserve_decoded_pixels(
@pytest.mark.parametrize("results", [[], [{"type": "c2pa", "outcome": "detected"}]])
def test_missing_synthid_result_is_an_error(tmp_clean_png: Path, results: list[dict[str, str]]) -> None:
client, _checks = _client({"results": results})
client, _checks = _client({"object": "content_provenance_check", "results": results})
with pytest.raises(RuntimeError, match="0 SynthID results"):
_verify(tmp_clean_png, client=client)
@@ -147,10 +172,11 @@ def test_missing_synthid_result_is_an_error(tmp_clean_png: Path, results: list[d
def test_duplicate_synthid_results_are_an_error(tmp_clean_png: Path) -> None:
client, _checks = _client(
{
"object": "content_provenance_check",
"results": [
{"type": "synthid", "outcome": "detected"},
{"type": "synthid", "outcome": "not_detected"},
]
],
}
)
@@ -223,6 +249,28 @@ def test_upload_limit_is_checked_after_sanitizing(
assert checks.calls == []
def test_upload_limit_allows_exact_boundary(
monkeypatch: pytest.MonkeyPatch,
tmp_clean_png: Path,
) -> None:
from remove_ai_watermarks import metadata
client, checks = _client(_response(synthid="not_detected"))
def copy_clean(source: Path, output: Path, *, keep_standard: bool) -> tuple[Path, dict[str, str]]:
assert keep_standard is True
output.write_bytes(source.read_bytes())
return output, {}
monkeypatch.setattr(metadata, "strip_and_verify", copy_clean)
monkeypatch.setattr(provenance, "MAX_UPLOAD_BYTES", tmp_clean_png.stat().st_size)
result = _verify(tmp_clean_png, client=client)
assert result.status == "not_detected"
assert len(checks.calls) == 1
def test_missing_optional_sdk_has_install_hint(
monkeypatch: pytest.MonkeyPatch,
tmp_clean_png: Path,
@@ -237,7 +285,7 @@ def test_client_configuration_error_is_actionable(
monkeypatch: pytest.MonkeyPatch,
tmp_clean_png: Path,
) -> None:
def fail() -> None:
def fail(**_kwargs: Any) -> None:
raise ValueError("OPENAI_API_KEY is missing")
monkeypatch.setattr(provenance, "is_available", lambda: True)
@@ -247,9 +295,36 @@ def test_client_configuration_error_is_actionable(
_verify(tmp_clean_png)
def test_default_client_bounds_one_acknowledged_upload(monkeypatch: pytest.MonkeyPatch) -> None:
calls: list[dict[str, Any]] = []
expected = SimpleNamespace(content_provenance_checks=object())
def factory(**kwargs: Any) -> Any:
calls.append(kwargs)
return expected
monkeypatch.setattr(provenance, "is_available", lambda: True)
monkeypatch.setattr(provenance.importlib, "import_module", lambda _name: SimpleNamespace(OpenAI=factory))
assert provenance._default_client() is expected
assert calls == [
{
"timeout": provenance.REQUEST_TIMEOUT_SECONDS,
"max_retries": 0,
}
]
@pytest.mark.parametrize(
("status_code", "message"),
[(400, "rejected"), (404, "does not have"), (429, "rate limit")],
[
(400, "rejected"),
(401, "authentication failed"),
(403, "not permitted"),
(404, "does not have"),
(429, "rate limit"),
(500, "temporary server error"),
],
)
def test_documented_api_errors_are_actionable(
tmp_clean_png: Path,
@@ -263,10 +338,92 @@ def test_documented_api_errors_are_actionable(
error.status_code = status_code # type: ignore[attr-defined]
class FailingChecks:
def create(self, *, file: tuple[str, Any, str]) -> None:
def create(self, *, file: tuple[str, Any, str], timeout: float) -> None:
assert timeout == provenance.REQUEST_TIMEOUT_SECONDS
raise error
client = SimpleNamespace(content_provenance_checks=FailingChecks())
with pytest.raises(RuntimeError, match=message):
_verify(tmp_clean_png, client=client)
@pytest.mark.parametrize(
("error_name", "message"),
[("APITimeoutError", "timed out"), ("APIConnectionError", "could not be reached")],
)
def test_transport_errors_are_actionable(
tmp_clean_png: Path,
error_name: str,
message: str,
) -> None:
error_type = type(error_name, (Exception,), {})
class FailingChecks:
def create(self, *, file: tuple[str, Any, str], timeout: float) -> None:
assert timeout == provenance.REQUEST_TIMEOUT_SECONDS
raise error_type("details")
client = SimpleNamespace(content_provenance_checks=FailingChecks())
with pytest.raises(RuntimeError, match=message):
_verify(tmp_clean_png, client=client)
def test_rate_limit_error_preserves_retry_context(tmp_clean_png: Path) -> None:
class RateLimitError(Exception):
status_code = 429
code = "rate_limit_exceeded"
request_id = "req_test"
response = SimpleNamespace(headers={"retry-after": "7"})
class FailingChecks:
def create(self, *, file: tuple[str, Any, str], timeout: float) -> None:
assert timeout == provenance.REQUEST_TIMEOUT_SECONDS
raise RateLimitError("details")
client = SimpleNamespace(content_provenance_checks=FailingChecks())
with pytest.raises(provenance.OpenAIProvenanceError, match="Retry-After: 7") as raised:
_verify(tmp_clean_png, client=client)
assert raised.value.status_code == 429
assert raised.value.error_code == "rate_limit_exceeded"
assert raised.value.request_id == "req_test"
assert raised.value.retry_after == "7"
assert raised.value.retryable is True
def test_client_error_is_not_marked_retryable(tmp_clean_png: Path) -> None:
class BadRequestError(Exception):
status_code = 400
code = "invalid_image"
class FailingChecks:
def create(self, *, file: tuple[str, Any, str], timeout: float) -> None:
assert timeout == provenance.REQUEST_TIMEOUT_SECONDS
raise BadRequestError("details")
client = SimpleNamespace(content_provenance_checks=FailingChecks())
with pytest.raises(provenance.OpenAIProvenanceError) as raised:
_verify(tmp_clean_png, client=client)
assert raised.value.status_code == 400
assert raised.value.error_code == "invalid_image"
assert raised.value.retryable is False
def test_keyboard_interrupt_is_not_wrapped_or_retried(tmp_clean_png: Path) -> None:
class InterruptingChecks:
calls = 0
def create(self, *, file: tuple[str, Any, str], timeout: float) -> None:
assert timeout == provenance.REQUEST_TIMEOUT_SECONDS
self.calls += 1
raise KeyboardInterrupt
checks = InterruptingChecks()
client = SimpleNamespace(content_provenance_checks=checks)
with pytest.raises(KeyboardInterrupt):
_verify(tmp_clean_png, client=client)
assert checks.calls == 1
+399
View File
@@ -0,0 +1,399 @@
from __future__ import annotations
import json
import sys
from dataclasses import replace
from pathlib import Path
import cv2
import numpy as np
import pytest
from click.testing import CliRunner
from PIL import Image
sys.path.insert(0, str(Path(__file__).resolve().parent.parent / "scripts"))
import synthid_affine_lattice_probe as probe
from remove_ai_watermarks._synthid_confirmation import RegisteredConfirmationComponents
def test_webp_lossless_round_trip_preserves_pixels() -> None:
rng = np.random.default_rng(20260817)
pixels = rng.integers(0, 256, (64, 64, 3), dtype=np.uint8)
restored = probe._webp_round_trip(pixels, 101)
assert np.array_equal(restored, pixels)
@pytest.fixture(scope="module")
def periodic_fixture() -> tuple[np.ndarray, np.ndarray]:
rng = np.random.default_rng(20260814)
template = rng.normal(0.0, 1.0, (16, 16, 3))
template -= np.mean(template, axis=(0, 1), keepdims=True)
template /= np.linalg.norm(template)
coarse = rng.normal(0.0, 8.0, (16, 16, 3)).astype(np.float32)
background = cv2.resize(coarse, (1024, 1024), interpolation=cv2.INTER_CUBIC) + 128.0
carrier = np.tile(template, (64, 64, 1)) * 3.0
pixels = np.clip(np.rint(background + carrier), 0, 255).astype(np.uint8)
return pixels, template
def _score(pixels: np.ndarray, template: np.ndarray) -> probe.LatticeScore:
return probe.score_lattice(
pixels,
template,
periods=np.arange(12.0, 20.01, 0.25),
rotations_degrees=np.asarray([-1.0, 0.0, 1.0]),
patch_size=256,
grid_size=4,
harmonic_count=12,
)
def test_period_alias_candidates_include_base_and_half_period_neighbors() -> None:
periods = np.arange(7.5, 24.501, 0.1)
rotations = np.zeros_like(periods)
base_index = int(np.argmin(np.abs(periods - 19.2)))
candidates = probe._period_alias_candidate_indices(periods, rotations, [base_index])
assert [periods[index] for index in candidates] == pytest.approx([19.1, 19.2, 19.3, 9.5, 9.6, 9.7])
def test_split_lattice_recovers_periodic_carrier(periodic_fixture: tuple[np.ndarray, np.ndarray]) -> None:
pixels, template = periodic_fixture
result = _score(pixels, template)
assert result.selected_period == pytest.approx(16.0, abs=0.25)
assert result.selected_rotation_degrees == 0.0
assert result.confirmation_coherence > 0.9
assert result.joint_coherence > 0.9
assert result.joint_codeword > 0.8
assert result.unknown_codeword_confirmation > 0.8
assert result.unknown_codeword_fixed_confirmation > 0.8
assert result.unknown_codeword_fixed_all > 0.8
assert result.unknown_codeword_excess_p99 > 0.0
assert result.joint_amplitude > 0.8
assert result.joint_whitened_match > 0.8
assert result.canonical_template_score > 0.8
assert result.canonical_registered_template_score > 0.8
assert result.confirmation_excess_p99 > 0.0
assert result.selection_patches == result.confirmation_patches == 8
def test_split_lattice_rejects_independent_noise(periodic_fixture: tuple[np.ndarray, np.ndarray]) -> None:
_pixels, template = periodic_fixture
rng = np.random.default_rng(20260815)
noise = rng.integers(0, 256, (1024, 1024, 3), dtype=np.uint8)
result = _score(noise, template)
assert result.confirmation_coherence < 0.8
assert result.joint_coherence < 0.8
assert result.joint_codeword < 0.8
assert result.unknown_codeword_confirmation < 0.2
assert result.unknown_codeword_fixed_confirmation < 0.2
assert result.unknown_codeword_fixed_all < 0.2
assert result.joint_amplitude < 0.2
assert result.joint_whitened_match < 0.2
assert result.canonical_template_score < 0.2
assert result.canonical_registered_template_score < 0.2
assert result.confirmation_excess_p99 < 0.0
def test_split_lattice_tracks_resized_period(periodic_fixture: tuple[np.ndarray, np.ndarray]) -> None:
pixels, template = periodic_fixture
resized = cv2.resize(pixels, (819, 819), interpolation=cv2.INTER_CUBIC)
result = probe.score_lattice(
resized,
template,
periods=np.arange(7.5, 24.501, 0.1),
rotations_degrees=np.asarray([0.0]),
patch_size=192,
grid_size=4,
harmonic_count=12,
)
assert result.selected_period == pytest.approx(12.8, abs=0.3)
assert result.confirmation_coherence > 0.8
assert result.joint_amplitude > 0.8
assert result.joint_whitened_match > 0.8
assert result.unknown_codeword_confirmation > 0.8
assert result.unknown_codeword_fixed_confirmation > 0.8
assert result.unknown_codeword_fixed_all > 0.8
def test_split_lattice_tracks_octave_aliased_resize(periodic_fixture: tuple[np.ndarray, np.ndarray]) -> None:
pixels, template = periodic_fixture
resized = cv2.resize(pixels, (614, 614), interpolation=cv2.INTER_AREA)
result = probe.score_lattice(
resized,
template,
periods=np.arange(7.5, 24.501, 0.1),
rotations_degrees=np.asarray([0.0]),
patch_size=192,
grid_size=4,
harmonic_count=12,
)
assert result.selected_period == pytest.approx(9.6, abs=0.15)
assert result.canonical_template_score > 0.4
def test_same_image_period_null_prefers_the_carrier_period(
periodic_fixture: tuple[np.ndarray, np.ndarray],
) -> None:
pixels, template = periodic_fixture
correct = probe.score_same_image_period_null(pixels, template, 16.0, harmonic_count=12)
off_period = probe.score_same_image_period_null(pixels, template, 15.0, harmonic_count=12)
assert correct.joint_excess > 0.2
assert correct.joint_excess > off_period.joint_excess
assert correct.off_period_count == len(probe.SAME_IMAGE_NULL_OFFSETS)
def test_patch_shift_consensus_confirms_global_carrier_phase(
periodic_fixture: tuple[np.ndarray, np.ndarray],
) -> None:
pixels, template = periodic_fixture
rng = np.random.default_rng(20260818)
noise = rng.integers(0, 256, pixels.shape, dtype=np.uint8)
carrier = probe.score_patch_shift_consensus(pixels, template, 16.0)
control = probe.score_patch_shift_consensus(noise, template, 16.0)
assert carrier.joint_trimmed_z > control.joint_trimmed_z
assert carrier.joint_support_fraction == 1.0
assert carrier.selection_patches == carrier.confirmation_patches == 8
def test_patch_shift_recovery_uses_frozen_mechanism_gates() -> None:
baseline = {
"amplitude_margin": 0.8,
"high_band_margin": 1.0,
"periods_agree": True,
"confirmation_passes": True,
"joint_trimmed_z": 2.5,
}
assert probe.patch_shift_recovery_passes(**baseline)
for field, failed_value in (
("amplitude_margin", 0.449),
("high_band_margin", 0.449),
("periods_agree", False),
("confirmation_passes", False),
("joint_trimmed_z", 2.499),
):
candidate = {**baseline, field: failed_value}
assert not probe.patch_shift_recovery_passes(**candidate)
assert not probe.patch_shift_recovery_passes(**{**baseline, "amplitude_margin": 0.99, "high_band_margin": 0.99})
def test_opponent_registration_recovers_resampled_carrier(
periodic_fixture: tuple[np.ndarray, np.ndarray],
) -> None:
pixels, template = periodic_fixture
resized = cv2.resize(pixels, (717, 717), interpolation=cv2.INTER_AREA)
rng = np.random.default_rng(20260819)
noise = rng.integers(0, 256, resized.shape, dtype=np.uint8)
periods = np.arange(10.0, 12.41, 0.05)
carrier = probe.score_opponent_registered(resized, template, periods=periods)
control = probe.score_opponent_registered(noise, template, periods=periods)
assert carrier.selected_period == pytest.approx(11.2, abs=0.1)
assert carrier.decision_score > 1.0
assert control.decision_score < 1.0
def test_split_lattice_aligns_cyclic_carrier_phase(periodic_fixture: tuple[np.ndarray, np.ndarray]) -> None:
pixels, template = periodic_fixture
shifted = np.roll(pixels, shift=(3, 5), axis=(0, 1))
result = _score(shifted, template)
assert (result.selected_shift_y, result.selected_shift_x) == (3, 5)
assert (result.amplitude_shift_y, result.amplitude_shift_x) == (13, 11)
assert result.canonical_template_score < 0.2
assert result.canonical_registered_template_score > 0.8
assert result.joint_whitened_match < 0.4
assert result.unknown_codeword_confirmation > 0.8
assert result.unknown_codeword_fixed_confirmation > 0.8
assert result.unknown_codeword_fixed_all > 0.8
def test_split_lattice_recovers_cropped_carrier_phase(periodic_fixture: tuple[np.ndarray, np.ndarray]) -> None:
pixels, template = periodic_fixture
cropped = pixels[37:, 53:]
result = probe.score_lattice(
cropped,
template,
periods=np.asarray([16.0]),
rotations_degrees=np.asarray([0.0]),
patch_size=256,
grid_size=4,
harmonic_count=12,
)
assert result.selected_period == pytest.approx(16.0, abs=0.25)
assert result.joint_coherence > 0.8
assert result.unknown_codeword_fixed_all > 0.8
assert result.canonical_template_score < 0.2
assert result.canonical_registered_template_score > 0.8
def test_orientation_bank_recovers_right_angle_rotation(periodic_fixture: tuple[np.ndarray, np.ndarray]) -> None:
pixels, template = periodic_fixture
rotated_clockwise = np.rot90(pixels, k=-1)
result = probe.score_orientation_bank(
rotated_clockwise,
template,
periods=np.asarray([16.0]),
rotations_degrees=np.asarray([0.0]),
patch_size=256,
grid_size=4,
harmonic_count=12,
)
assert result.selected_orientation_degrees == 90
assert result.joint_amplitude > 0.8
assert result.canonical_template_score > 0.8
def test_dihedral_bank_recovers_horizontal_reflection(periodic_fixture: tuple[np.ndarray, np.ndarray]) -> None:
pixels, template = periodic_fixture
result = probe.score_dihedral_bank(
np.fliplr(pixels),
template,
periods=np.asarray([16.0]),
rotations_degrees=np.asarray([0.0]),
patch_size=256,
grid_size=4,
harmonic_count=12,
)
assert result.selected_orientation_degrees == 0
assert result.selected_horizontal_reflection is True
assert result.joint_amplitude > 0.8
assert result.canonical_template_score > 0.8
def test_deskew_bank_recovers_small_rotation(periodic_fixture: tuple[np.ndarray, np.ndarray]) -> None:
pixels, template = periodic_fixture
rotated = probe._rotate_fixed_canvas(pixels, 1.5)
result = probe.score_deskew_bank(
rotated,
template,
periods=np.asarray([16.0]),
deskew_degrees=np.asarray([-2.0, -1.5, -1.0]),
patch_size=256,
grid_size=4,
harmonic_count=12,
)
assert result.selected_deskew_degrees == -1.5
assert result.joint_amplitude > 0.6
assert result.canonical_template_score > 0.6
assert result.deskew_direct_joint_match > 0.4
def test_registered_period_mode_uses_runtime_selected_period(
periodic_fixture: tuple[np.ndarray, np.ndarray],
monkeypatch: pytest.MonkeyPatch,
) -> None:
pixels, template = periodic_fixture
runner = CliRunner()
with runner.isolated_filesystem():
np.savez("template.npz", template=template)
Image.fromarray(pixels).save("image.png")
components = probe.RegisteredComponents(
raw_score=0.4,
amplitude_threshold=0.2,
selected_period=16.0,
spectral_period=16.0,
high_band_score=0.15,
confirmation=RegisteredConfirmationComponents(
period=16.0,
joint_coherence=0.5,
joint_amplitude=0.2,
unknown_codeword_fixed_confirmation=0.5,
selection_patches=8,
confirmation_patches=8,
),
)
monkeypatch.setattr(probe, "registered_components", lambda *_args: components)
result = runner.invoke(
probe.main,
[
"template.npz",
"image.png",
"--registered-period",
"--same-image-null",
"--patch-shift-consensus",
"--opponent-registered",
"--report-out",
"report.json",
],
)
assert result.exit_code == 0, result.output
report = json.loads(Path("report.json").read_text(encoding="utf-8"))
assert report["registered_period"] is True
assert report["same_image_null"] is True
assert report["patch_shift_consensus"] is True
assert report["opponent_registered"] is True
assert report["records"][0]["registered"]["selected_period"] == 16.0
assert report["records"][0]["registered"]["decision_score"] == 2.0
assert report["records"][0]["score"]["selected_period"] == 16.0
assert report["records"][0]["same_image_null"]["joint_excess"] > 0.2
assert report["records"][0]["patch_shift_consensus"]["joint_support_fraction"] == 1.0
assert report["records"][0]["opponent_registered"]["decision_score"] > 1.0
def test_registered_confirmation_uses_frozen_period_aware_gates(
periodic_fixture: tuple[np.ndarray, np.ndarray],
) -> None:
pixels, template = periodic_fixture
baseline = _score(pixels, template)
generic = replace(
baseline,
selected_period=16.0,
joint_coherence=0.30,
joint_amplitude=0.0,
)
assert probe.registered_confirmation_passes(generic)
assert not probe.registered_confirmation_passes(replace(generic, selected_period=9.99))
assert not probe.registered_confirmation_passes(replace(generic, joint_coherence=0.299))
assert not probe.registered_confirmation_passes(replace(generic, joint_amplitude=-0.001))
assert not probe.registered_confirmation_passes(
replace(generic, selected_period=18.28, unknown_codeword_fixed_confirmation=0.129)
)
assert probe.registered_confirmation_passes(
replace(generic, selected_period=18.28, unknown_codeword_fixed_confirmation=0.13)
)
assert not probe.registered_confirmation_passes(replace(generic, selected_period=19.14, joint_coherence=0.399))
assert probe.registered_confirmation_passes(replace(generic, selected_period=19.14, joint_coherence=0.40))
assert not probe.registered_confirmation_passes(
replace(generic, selected_period=21.31, unknown_codeword_fixed_confirmation=0.019)
)
assert probe.registered_confirmation_passes(
replace(generic, selected_period=21.31, unknown_codeword_fixed_confirmation=0.02)
)
def test_fixed_candidate_uses_frozen_precision_threshold() -> None:
assert not probe.fixed_candidate_passes(0.279999)
assert probe.fixed_candidate_passes(0.28)
assert not probe.fixed_candidate_passes(float("nan"))
+84
View File
@@ -0,0 +1,84 @@
from __future__ import annotations
import sys
from dataclasses import replace
from pathlib import Path
import cv2
import numpy as np
import pytest
sys.path.insert(0, str(Path(__file__).resolve().parent.parent / "scripts"))
import synthid_affine_lattice_probe as research_probe
from remove_ai_watermarks._synthid_confirmation import (
RegisteredConfirmationComponents,
registered_confirmation_components,
)
@pytest.fixture(scope="module")
def periodic_fixture() -> tuple[np.ndarray, np.ndarray]:
rng = np.random.default_rng(20260818)
template = rng.normal(0.0, 1.0, (16, 16, 3))
template -= np.mean(template, axis=(0, 1), keepdims=True)
template /= np.linalg.norm(template)
coarse = rng.normal(0.0, 8.0, (16, 16, 3)).astype(np.float32)
background = cv2.resize(coarse, (1024, 1024), interpolation=cv2.INTER_CUBIC) + 128.0
carrier = np.tile(template, (64, 64, 1)) * 3.0
pixels = np.clip(np.rint(background + carrier), 0, 255).astype(np.uint8)
return pixels, template
def test_runtime_components_match_frozen_research_seam(
periodic_fixture: tuple[np.ndarray, np.ndarray],
) -> None:
pixels, template = periodic_fixture
runtime = registered_confirmation_components(pixels, template, 16.0, 1.0)
research = research_probe.score_lattice(
pixels,
template,
periods=np.asarray([16.0]),
rotations_degrees=np.asarray([0.0]),
)
assert runtime.period == research.selected_period
assert runtime.joint_coherence == pytest.approx(research.joint_coherence)
assert runtime.joint_amplitude == pytest.approx(research.joint_amplitude)
assert runtime.unknown_codeword_fixed_confirmation == pytest.approx(research.unknown_codeword_fixed_confirmation)
assert runtime.selection_patches == research.selection_patches
assert runtime.confirmation_patches == research.confirmation_patches
assert runtime.passes
def test_confirmation_rejects_independent_noise(periodic_fixture: tuple[np.ndarray, np.ndarray]) -> None:
_pixels, template = periodic_fixture
pixels = np.random.default_rng(20260819).integers(0, 256, (1024, 1024, 3), dtype=np.uint8)
result = registered_confirmation_components(pixels, template, 16.0, 1.0)
assert not result.passes
def test_period_aware_confirmation_boundaries() -> None:
baseline = RegisteredConfirmationComponents(
period=16.0,
joint_coherence=0.30,
joint_amplitude=0.0,
unknown_codeword_fixed_confirmation=0.5,
selection_patches=8,
confirmation_patches=8,
)
assert baseline.passes
assert not replace(baseline, period=9.99).passes
assert not replace(baseline, joint_coherence=0.299).passes
assert not replace(baseline, joint_amplitude=-0.001).passes
assert not replace(baseline, period=18.28, unknown_codeword_fixed_confirmation=0.129).passes
assert replace(baseline, period=18.28, unknown_codeword_fixed_confirmation=0.13).passes
assert not replace(baseline, period=19.14, joint_coherence=0.399).passes
assert replace(baseline, period=19.14, joint_coherence=0.40).passes
assert not replace(baseline, period=21.31, unknown_codeword_fixed_confirmation=0.019).passes
assert replace(baseline, period=21.31, unknown_codeword_fixed_confirmation=0.02).passes
@@ -0,0 +1,65 @@
from __future__ import annotations
import sys
from pathlib import Path
import numpy as np
sys.path.insert(0, str(Path(__file__).resolve().parent.parent / "scripts"))
import synthid_cyclostationary_probe as probe
def _template() -> np.ndarray:
_y, x = np.indices((16, 16))
carrier = np.cos(2.0 * np.pi * 4.0 * x / 16.0)
template = np.stack((carrier, 0.8 * carrier, 0.6 * carrier), axis=2)
template -= np.mean(template, axis=(0, 1), keepdims=True)
return template / np.linalg.norm(template)
def test_detects_complex_spectral_coupling() -> None:
rng = np.random.default_rng(20260814)
base = rng.normal(0.0, 1.0, (1024, 1024, 3))
_y, x = np.indices(base.shape[:2])
modulation = 1.0 + 0.8 * np.cos(2.0 * np.pi * 4.0 * x / 16.0)
result = probe.score_cyclostationary(
base * modulation[:, :, None],
_template(),
period=16.0,
harmonic_count=1,
)
assert result.selection_contrast > 0.1
assert result.confirmation_contrast > 0.1
assert result.joint_contrast > 0.1
def test_rejects_independent_equal_power_noise() -> None:
rng = np.random.default_rng(20260815)
noise = rng.normal(0.0, 1.0, (1024, 1024, 3))
result = probe.score_cyclostationary(
noise,
_template(),
period=16.0,
harmonic_count=1,
)
assert result.joint_contrast < 0.01
def test_does_not_confuse_additive_carrier_with_modulation() -> None:
rng = np.random.default_rng(20260816)
noise = rng.normal(0.0, 1.0, (1024, 1024, 3))
additive = np.tile(_template(), (64, 64, 1)) * 2.0
result = probe.score_cyclostationary(
noise + additive,
_template(),
period=16.0,
harmonic_count=1,
)
assert result.joint_contrast < 0.01
+360 -19
View File
@@ -47,6 +47,63 @@ def registered_scale_positive(tmp_path_factory: pytest.TempPathFactory) -> 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
@@ -79,9 +136,11 @@ def test_geometry_outside_the_challenged_pixel_count_range_is_unsupported(
[
(500, 500, True),
(4000, 2500, True),
(64, 3907, True),
(256, 977, True),
(499, 500, False),
(4001, 2500, False),
(255, 981, False),
(64, 3907, False),
(32, 7813, False),
],
)
@@ -93,6 +152,42 @@ def test_registered_geometry_uses_its_measured_pixel_count_range(
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"),
[
@@ -162,7 +257,7 @@ def test_large_red_green_gate_mutation_changes_the_real_verdict(
assert baseline.status == "detected"
assert baseline.detector == detector.LARGE_DETECTOR_ID
assert mutated.status == "not_detected"
assert mutated.status == "indeterminate"
def test_uncalibrated_narrow_large_geometry_is_unsupported() -> None:
@@ -189,7 +284,7 @@ def test_registered_mode_rejects_a_side_too_short_for_quadrants(tmp_path: Path)
def test_detects_supported_periodic_carrier(supported_images: tuple[Path, Path]) -> None:
positive, _negative = supported_images
result = detector.detect_synthid(positive)
result = detector.detect_synthid(positive, register_scale=False)
assert result.status == "detected"
assert result.detected is True
@@ -209,7 +304,7 @@ def test_detects_unregistered_non_divisible_geometry_in_size_range(tmp_path: Pat
path = tmp_path / "non-divisible-positive.png"
Image.fromarray(pixels, "RGB").save(path)
result = detector.detect_synthid(path)
result = detector.detect_synthid(path, register_scale=False)
assert result.status == "detected"
assert (result.width, result.height) == (width, height)
@@ -218,10 +313,12 @@ def test_detects_unregistered_non_divisible_geometry_in_size_range(tmp_path: Pat
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 default.status == "unsupported"
assert fixed.status == "unsupported"
assert default == registered
assert registered.status == "detected"
assert registered.score is not None
assert registered.score > registered.threshold
@@ -229,6 +326,167 @@ def test_registered_mode_detects_a_rescaled_carrier(registered_scale_positive: P
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,
@@ -240,7 +498,7 @@ def test_registered_threshold_mutation_changes_the_real_verdict(
mutated = detector.detect_synthid(registered_scale_positive, register_scale=True)
assert mutated.status == "not_detected"
assert mutated.status == "indeterminate"
assert mutated.threshold == mutated_threshold
@@ -270,17 +528,22 @@ def test_registered_amplitude_threshold_mutation_changes_the_real_verdict(
mutated = detector.detect_synthid(registered_scale_positive, register_scale=True)
assert mutated.status == "not_detected"
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
matching = RegisteredComponents(0.5, 0.25, 12.8, 12.8, 0.15)
mismatching = RegisteredComponents(0.5, 0.25, 12.8, 12.9, 0.15)
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(
@@ -303,15 +566,40 @@ def test_registered_high_band_mutation_changes_the_real_verdict(
mutated = detector.detect_synthid(registered_scale_positive, register_scale=True)
assert mutated.status == "not_detected"
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)
result = detector.detect_synthid(negative, register_scale=False)
assert result.status == "not_detected"
assert result.status == "indeterminate"
assert result.detected is False
assert result.score == pytest.approx(0.0)
@@ -321,16 +609,16 @@ def test_threshold_mutation_changes_the_real_verdict(
supported_images: tuple[Path, Path],
) -> None:
positive, _negative = supported_images
baseline = detector.detect_synthid(positive)
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)
mutated = detector.detect_synthid(positive, register_scale=False)
assert mutated.status == "not_detected"
assert mutated.status == "indeterminate"
assert mutated.threshold == mutated_threshold
@@ -338,11 +626,14 @@ 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)
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:
@@ -352,8 +643,8 @@ def test_shared_bgr_decode_matches_file_decode(supported_images: tuple[Path, Pat
bgr = cv2.imread(str(positive))
assert bgr is not None
from_file = detector.detect_synthid(positive)
from_array = detector.detect_synthid(positive, image=bgr)
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
@@ -366,7 +657,7 @@ def test_supported_geometry_requires_pixel_dependencies(
monkeypatch.setattr(detector, "is_available", lambda: False)
with pytest.raises(RuntimeError, match="pixel extra"):
detector.detect_synthid(negative)
detector.detect_synthid(negative, register_scale=False)
def test_fold_accepts_non_divisible_geometry_without_resampling() -> None:
@@ -435,3 +726,53 @@ def test_fold_rejects_tile_larger_than_image() -> None:
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