Doctor Trigger: A Framework for Release-Bound Face Verification
Chia-Ming Lee1,2,
Chia-Yu Lin3, Hung-Kai Huang2, Yi-Ting Ku3, Yu-Chen Liang3,
Chih-Chung Hsu2
1University at Albany, SUNY 2National Yang Ming Chiao Tung University 3National Cheng Kung University
Check whether a face online is the one you posted.
Photos of faces get reposted, compressed, swapped and edited as they spread. Before you post a face, Doctor Trigger adds an invisible keyed signal to it and keeps a small private record. Later, any circulating copy is checked against that record: ordinary sharing (JPEG, resizing) passes, while a face swap, or the original you never posted, does not.
Why the name? Doctor: like a doctor who can make a sound call only because your history is on file, the check reads a copy against the record you kept. Trigger: the tiny, invisible signal you set in the photo before posting.
Four copies of one face, checked with this code
| Step | What happens | Paper symbols | Code |
|---|---|---|---|
| Enroll | A per-face secret key becomes a binary ±1 sign pattern on a mid-frequency ring of the face's luminance Fourier phase. The released image carries a weak shift along that pattern (51.2 dB PSNR on average); the publisher keeps the private record. | s, hs, δs, R | enrollment.py, key.py |
| Verify | A copy is read against the record twice. Spectral agreement: is this still the enrolled face? Key projection: is the keyed shift still there? | C (CB), Gs | readings.py |
| Decide | Each reading is compared with a threshold set on benign copies of your releases (about 1% benign rejection). | τC, τG | calibration.py, decision.py |
| Status | Meaning |
|---|---|
| Confirmed | C ≥ τC and Gs ≥ τG: consistent with the posted release. |
| Unconfirmed | Either check fails (a swapped face, or an original that was never posted). Flagged for human review. |
| Unavailable | The record, the face or a supported channel is missing, so no verdict is given. |
More: full verification diagram, FAQ (is it a watermark? what must stay private?).
No dataset, no GPU, a few seconds:
git clone https://github.com/ming053l/Doctor-Trigger.git && cd Doctor-Trigger
pip install -e . # Python >= 3.10; numpy, Pillow, scikit-image, ... (CPU only)
python examples/quickstart.py # enrolls a bundled AI-generated face, checks six copiescopy C G_s status
posted (released PNG) 0.9999 1.529 Confirmed
JPEG re-save, q85 0.9995 1.018 Confirmed
resized 0.75x and back 0.9998 1.539 Confirmed
unreleased original 0.9997 0.000 Unconfirmed (release signal missing)
face region replaced 0.7468 0.716 Unconfirmed (face changed, release signal missing)
center crop 90% (no record of geometry) - - Unavailable
(thresholds) 0.9989 0.868
The thresholds here are a toy calibration; examples/README.md explains each row. The paper's experiments are listed and run from the same command line:
python -m doctor_trigger list # every experiment and the table/figure it feeds
python -m doctor_trigger show threshold-transfer # its resolved command, cwd and output
python -m doctor_trigger reproduce threshold-transfer # run it (needs a pipeline root, see below)# 1. enroll: writes the image to post (out/face_released.png) and its private record
python -m doctor_trigger enroll face.png --sample-id face --out out/
# 2. verify: prints the readings H_B, C_B and G_s of a circulating copy against the record
python -m doctor_trigger verify copy.png out/face_record_PRIVATE.npz
# 3. calibrate: threshold τ from a column of scores of benign copies of your own releases
python -m doctor_trigger calibrate scores.csv --column C_B --frr 0.01
# 4. verify with the thresholds from step 3: also prints Confirmed, Unconfirmed or Unavailable
python -m doctor_trigger verify copy.png out/face_record_PRIVATE.npz --tau-c TAU_C --tau-g TAU_GEnroll a face-centred crop (the pipeline enrolls aligned face crops). Keep every *_PRIVATE.npz private: only the released image is public.
doctor_trigger/ the method, plus the command line (python -m doctor_trigger ...)
spectrum.py luminance Y, phase φ, normalized radius κ, sub-band B (κ ≤ 0.15)
key.py key s, sign pattern h_s (binary ±1), active bins, displacement δ_s
record.py private record R (NPZ) and where each enrolled unit's record lives
enrollment.py released image I′ and record R for one face, α ladder, fidelity gate
readings.py spectral agreement H / C / C_B, key projection G_s, features Φ
calibration.py thresholds τ at a target benign false-rejection rate
decision.py Confirmed / Unconfirmed / Unavailable
channels.py benign delivery channels (JPEG, resizing, one-frame codecs, ...)
metrics.py AUC, TPR at a fixed FPR, EER
record_model.py optional learned record model f_θ (needs torch)
config.py config.yaml paths; manifests.py: query manifests; cli.py: commands
examples/ dataset-free quick start (two AI-generated faces)
workflow/ pipeline: frames → faces → releases + records → queries → features → f_θ
experiments/ the paper's evaluations; registry.yaml maps each to its table or figure
configs/ record-model training configs used by the evaluations
tests/ unit and golden-value tests (pip install -e ".[test]" && pytest)
docs/ DATA, REPRODUCE, FAQ and the README figures
run.py same as python -m doctor_trigger (run = reproduce)
Every table and figure is a named entry in experiments/registry.yaml, run with python -m doctor_trigger reproduce <id> on a finished pipeline root (pip install -e ".[experiments]", then fill in config.yaml from config.example.yaml). docs/REPRODUCE.md maps each paper artifact to its entry and lists the external weights it needs.
The paper enrolls 7,366 real faces from FaceForensics++, DFDC and Celeb-DF v2; nothing is hosted here, so request each dataset from its maintainers. docs/DATA.md covers the frame layout and the commands that build the pipeline root.
- Only enrolled faces. It checks copies of faces enrolled before release, each against its own record. It is not a general deepfake detector.
- Known weak spots (paper Table 4, 200 FF++ releases): face-swap rejection drops to 63% after a repost of a repost, 11% under per-image H.264 intra coding and 6% under a 90% center crop, while benign copies still pass. Strong diffusion regeneration and a phase-ring transplant also weaken it (App. K). Validate on the channels you intend to support.
- Records are sensitive. A record's template reveals coarse facial structure; keep records, keys and the master seed private.
- Responsible use. Meant for publishers checking copies of their own releases. Use the benchmark datasets under their own terms. All faces in
examples/and in the README figures are AI-generated.
Images only for now. Video support (per-frame enrollment, clip-level decisions) is planned, and the package already has the seams for it; see Does it work on video?.
@misc{lee2026doctortrigger,
title = {Doctor Trigger: A Framework for Release-Bound Face Verification},
author = {Lee, Chia-Ming and Lin, Chia-Yu and Huang, Hung-Kai and Ku, Yi-Ting and Liang, Yu-Chen and Hsu, Chih-Chung},
year = {2026},
eprint = {2610.16336},
archivePrefix = {arXiv},
primaryClass = {cs.CR},
url = {https://arxiv.org/abs/2610.16336v2},
note = {Version 2. Project page: https://ming053l.github.io/Doctor-Trigger/}
}MIT, see LICENSE. Datasets, peer checkpoints and engine weights remain under their original licenses and terms.


