Record pipeline-lattice reframing and registered-v3 phase lock

Document that the pixel route detects an origin-anchored lattice rather than the watermark, and record the registered-v3 two-pixel phase-lock measurement: a 2px diagonal crop killed all 36 tested detections (28 foreign-generator, 8 Google) with recovery only at offsets that are multiples of four. Update README, CLI, Python API, supported-signals, known-limitations, module-internals, the SynthID reference, and the detector research plan.
This commit is contained in:
Victor Kuznetsov
2026-08-16 21:53:50 -07:00
parent 8eb9c06265
commit e74b06d1a0
8 changed files with 2679 additions and 134 deletions
+53 -21
View File
@@ -57,36 +57,58 @@ remove-ai-watermarks identify image.png --no-visible
```
Despite the historical option name, `--no-visible` skips all pixel detectors,
including the supported SynthID carrier, visible marks, open DWT-DCT, and
including the pipeline lattice described below, visible marks, open DWT-DCT, and
TrustMark. Metadata inspection still runs.
## Detect the supported SynthID pixel carrier
## Detect the generation-pipeline pixel lattice (experimental)
```bash
remove-ai-watermarks detect-synthid image.png
remove-ai-watermarks detect-synthid image.png --json
remove-ai-watermarks detect-synthid resized.png --register-scale
remove-ai-watermarks detect-synthid native-period.png --fixed-period
```
The command returns one of `detected`, `not_detected`, or `unsupported`. The
runtime detector covers one frozen periodic carrier family in the
This route is experimental. Signed provenance, read by `identify` and confirmed
against the provider by `verify-openai-synthid`, remains the supported way to
establish SynthID. The command returns one of `detected`, `indeterminate`, or
`unsupported`, and it does not detect the SynthID watermark: its statistic disappears when the image
is cropped off the tile grid, and it changes when the generator's pipeline
changes, so read a positive as evidence about the pipeline and never as a
watermark claim. The JSON carries `identifies_watermark` and
`tile_aligned_crop_required` for exactly this reason. The
runtime detector covers one frozen periodic lattice family in the
[calibrated image-size range](synthid.md#32-how-our-tool-detects-the-supported-carrier)
and needs the `pixels` extra. The native default uses the fixed fold from
1,000,000 through 10,000,000 decoded pixels and the separately challenged
opponent-color large branch above 10,000,000 through 18,000,000 pixels when
both sides are at least 2,048 pixels. It never resizes the input and does not
register a carrier whose sampling period changed through spatial resampling.
`--register-scale` enables a substantially slower bounded search over measured
carrier periods for images from 250,000 through 10,000,000 decoded pixels, with
both sides at least 64 pixels. It is opt-in and is not used by `identify`.
The measured positive scale range is approximately 0.65 through 1.5; 0.5x
resizes are not reliably detected.
The native large branch is also codec-sensitive: same-size JPEG-95 and JPEG-90
re-encoding reduced its seven official large positives from 7/7 to 0/7. A miss
on a lossy re-encode is therefore inconclusive.
It is positive-only: `not_detected` means the score stayed below this detector's
threshold, while `unsupported` means the image geometry is outside its scope.
Neither result proves that another SynthID epoch or payload is absent.
and needs the `pixels` extra. The production default uses registered-v3 from
250,000 through 10,000,000 decoded pixels with both sides at least 256 pixels.
An opponent-registered-v1 fallback covers 1 through 10 megapixels, both sides
at least 768 pixels, and selected carrier periods 7.9 through 12.0. Period-8
candidates must also pass an opponent-color block-edge veto for the native JPEG
lattice. The
separately challenged opponent-color large-v1 branch covers above 10,000,000
through 18,000,000 pixels when both sides are at least 2,048 pixels.
Registered-v3 performs a bounded carrier-period search and independent split-
patch confirmation. Its measured positive
scale range is approximately 0.65 through 1.5. The fallback recovered 49/49
lossless 0.5x-0.75x views from seven official positives. Its period-8 veto
rejected 1,790 codec-lattice crossings, and 350 matched 0.5x controls produced no
base crossing. The earlier period-band rule accepted 0/1,000 post-freeze Picsum
controls. `identify` uses this production router.
`--fixed-period` explicitly selects the faster legacy fixed-v2 diagnostic below
10 megapixels. It does not resize or register the carrier and is not a
production positive route. `--register-scale` forces the registered-v3 cascade,
including its opponent fallback, on geometry where the default would select
large-v1.
The native large and opponent-registered branches are codec-sensitive. The
large branch fell from 7/7 to 0/7 after same-size JPEG-95 or JPEG-90; the
fallback retained 0/63 JPEG-95, JPEG-85, and WebP-95 views. A miss on a lossy
re-encode is therefore inconclusive.
It is positive-only: `indeterminate` means the score stayed below this
detector's threshold, while `unsupported` means the image geometry is outside
its scope. Neither result proves that another SynthID epoch or payload is
absent.
JSON output includes the exact reason plus provider scope, backend, pixel
preservation, and metadata-use audit fields.
## Verify OpenAI SynthID from pixels
@@ -110,6 +132,16 @@ Retention, `--acknowledge-upload` is mandatory. This command is never called by
`identify`. `not_detected` means only that OpenAI's verifier did not recognize a
supported watermark in this file; it is not proof of human authorship.
The built-in client bounds the request at 120 seconds and disables automatic
SDK retries, so one acknowledgement cannot silently upload the media multiple
times. A timeout, disconnect, malformed response, access failure, or rate limit
is an error, never a negative watermark verdict. API failures expose status,
error code, request id, `Retry-After`, and whether an explicit caller-controlled
retry is appropriate through `OpenAIProvenanceError`; the verifier itself never
retries an upload.
The JSON result uses the same provider-scope, backend, pixel-preservation, and
metadata-use audit fields as the local detector.
The Python API enforces the same boundary with the required explicit intent
flag `verify_openai_synthid(path, acknowledge_upload=True)`.