diff --git a/.github/workflows/publish-photo-classify-hf.yml b/.github/workflows/publish-photo-classify-hf.yml index 89ec6f4..b1440a3 100644 --- a/.github/workflows/publish-photo-classify-hf.yml +++ b/.github/workflows/publish-photo-classify-hf.yml @@ -3,7 +3,7 @@ name: Publish photo classifier to Hugging Face # Manual only. Not part of a library release. The Space demo # (wiltodelta/remove-ai-watermarks) stays on distribute.yml / raiw-hf-space. # -# Hub model: wiltodelta/raiw-models +# Hub model: wiltodelta/raiw-photo-classify # Secret: HF_TOKEN, write-role Hugging Face token (same secret Space sync uses). # # Weights are not in git. Full mode downloads them from a GitHub Release diff --git a/docs/cli.md b/docs/cli.md index 460beda..3a33b8c 100644 --- a/docs/cli.md +++ b/docs/cli.md @@ -129,7 +129,7 @@ The contract is AI versus camera. Receipts, UI, and digital art are out of scope. Microsoft is not a pixel class: a DALL-E Bing image scores as `openai`, an Imagen Designer image as `google`. `tc260` is the China AIGC label standard, not one producer. Weights download on first use from -[`wiltodelta/raiw-models`](https://huggingface.co/wiltodelta/raiw-models), +[`wiltodelta/raiw-photo-classify`](https://huggingface.co/wiltodelta/raiw-photo-classify), or from `RAIW_CLASSIFY_WEIGHTS` if that directory already holds the freeze files. Guide: [photo pixel classification](photo-classify.md). Hub card: @@ -175,6 +175,18 @@ Use the strict visual gate without metadata or sibling corroboration: remove-ai-watermarks visible image.png --sensitivity strict -o clean.png ``` +Act on a mark you can see but the detector will not confirm, by naming it and +skipping the gate: + +```bash +remove-ai-watermarks visible image.png --mark gemini --no-detect -o clean.png +``` + +`--detect` is the default. `--no-detect` removes the named mark's footprint +unconditionally, so it only makes sense together with an explicit `--mark`; on +an image that never carried that mark it inpaints a region for nothing. Prefer +`erase --region` when you know the geometry. + When no known mark is detected, the command does not write a new output. Use `erase` if you can identify the affected region yourself. @@ -575,7 +587,33 @@ remove-ai-watermarks invisible image.png -o clean.png \ ``` Tiling avoids the explicit downscale but each tile is regenerated separately. -It is a memory strategy, not a guarantee of better quality. +It is a memory strategy, not a guarantee of better quality. `--tile-size` and +`--tile-overlap` set the tile geometry (defaults 1024 and 128 px); a larger +overlap costs time and hides seams. + +### Every tuning option + +These apply to `invisible`, `all`, and `batch`. Each has a measured default, so +reach for one only when you know why the default is wrong for your file. + +| Option | Effect | +| --- | --- | +| `--strength` | Denoising strength, 0.0-1.0. Default is profile- and vendor-adaptive; see [choose a strength cohort](#choose-a-strength-cohort). Raising it removes more and drifts further from the original. | +| `--vendor` | Strength cohort, and an assertion that the watermark is present. | +| `--pipeline` | Profile: `qwen-zimage` (default), `sdxl-zimage`, `chroma-zimage`, `auto`. | +| `--seed` | Fixed at 0 by default: every profile is certified at that seed, and removal near the floor is seed-dependent. | +| `--controlnet-scale` | Canny ControlNet conditioning on the global stage. Higher keeps the original structure and text closer. | +| `--humanize` | Analog film grain, 0 = off, typical 2.0-6.0. | +| `--unsharp` | Unsharp-mask sharpening, 0 = off. | +| `--adaptive-polish` / `--no-adaptive-polish` | Overrides the profile's own choice. Unset lets the profile decide: off for `qwen-zimage`, whose output already matches the input's detail level, on for `sdxl-zimage`. | +| `--max-resolution` | Cap the long side before diffusion; 0 = native. | +| `--tile`, `--tile-size`, `--tile-overlap` | Tiled regeneration for large inputs, as above. | +| `--cpu-offload` | Stream model components between CPU and GPU to lower CUDA memory pressure, at the cost of speed. | +| `--hf-token` | Hugging Face token for the model download; also read from the environment and a local `.env`. | + +There is no `--model`, `--steps`, `--guidance-scale` or `--device` option; the +profile pins all four. See [exit behavior](#exit-behavior) for what each command +returns. ## Run the full pipeline diff --git a/docs/module-internals.md b/docs/module-internals.md index 53a9f72..df1da98 100644 --- a/docs/module-internals.md +++ b/docs/module-internals.md @@ -968,7 +968,7 @@ file keys the Muse Image head `meta_muse_image`. Tests in `tests/test_classify.py` pin the gate without downloads and pin that `identify` does not import this module. -Weights stay out of git. The Hub snapshot is `wiltodelta/raiw-models`. +Weights stay out of git. The Hub snapshot is `wiltodelta/raiw-photo-classify`. `RAIW_CLASSIFY_WEIGHTS` overrides it. The extra is `classify`. User guide: [photo-classify.md](photo-classify.md). Hub card: [photo-classify-hf/README.md](photo-classify-hf/README.md). @@ -1835,7 +1835,8 @@ grain, unsharp masking, and adaptive polish helpers. `upscaler.py` held an optional Real-ESRGAN path, reachable only when enlarging a small image to the minimum-resolution floor. That floor existed to lift small -inputs toward SDXL's ~1024 training size; when the SDXL profiles were removed it +inputs toward SDXL's ~1024 training size; when the standalone SDXL and ControlNet +profiles were removed (`sdxl-zimage` is a later, surviving profile) it was forced to 0 on every path, so the module, the `--min-resolution` and `--upscaler` options and the `esrgan` extra were all unreachable and went with it. Only the `max_resolution` cap can move geometry now, and it only scales down. diff --git a/docs/photo-classify.md b/docs/photo-classify.md index 6a847b1..a3fa1e0 100644 --- a/docs/photo-classify.md +++ b/docs/photo-classify.md @@ -38,7 +38,7 @@ Missing extra raises `RuntimeError` with parameter (`None` / `"auto"` / `"cpu"` / `"cuda"`), not a CLI option. Weights are not in git. First call downloads -[`wiltodelta/raiw-models`](https://huggingface.co/wiltodelta/raiw-models) +[`wiltodelta/raiw-photo-classify`](https://huggingface.co/wiltodelta/raiw-photo-classify) at the freeze revision pinned in `classify.py`, or reads `RAIW_CLASSIFY_WEIGHTS` if that directory already has `clip-l-ft.pt`, `probe-weights-clip-l-ft.npz`, `detector.pt`, and `provider.pt`. The training diff --git a/docs/release-and-distribution.md b/docs/release-and-distribution.md index e69eeb0..d78c1e3 100644 --- a/docs/release-and-distribution.md +++ b/docs/release-and-distribution.md @@ -73,7 +73,7 @@ Upgrading the Hugging Face Space is a separate, manual step: bump pin. Its callback smoke tests run in that repository's CI on every push; they are the scenario check for the demo surface. -The Hugging Face **model** `wiltodelta/raiw-models` is not part of that +The Hugging Face **model** `wiltodelta/raiw-photo-classify` is not part of that release fan-out. It holds the photo-classify freeze weights. The library extra `classify` downloads that snapshot on first use, or reads `RAIW_CLASSIFY_WEIGHTS`. Update the Hub repo only with @@ -135,9 +135,14 @@ requests an older supported one. After publication, the `verify-release.yml` workflow runs automatically once `distribute.yml` completes and checks every surface below. It can also be -dispatched manually with a specific version. The ComfyUI check verifies the -sync workflow's latest run succeeded rather than querying the registry directly -(the registry has no public JSON API). The full checklist for completeness: +dispatched manually with a specific version. Every check fails the job; none of +them warns and continues, because a surface nobody can confirm is not a verified +surface. The ComfyUI check reads the published node from the registry's JSON API +(`https://api.comfy.org/nodes/remove-ai-watermarks`) and asserts the active +version declares this library release. It deliberately does NOT proxy through +the sync workflow's run status: the node repository also runs that workflow on a +daily schedule, so a green nightly run says nothing about whether this release +was synced. The full checklist for completeness: - both wheel and source distribution exist on PyPI; - the package version matches the tag; diff --git a/scripts/README.md b/scripts/README.md index 447e298..1342e8e 100644 --- a/scripts/README.md +++ b/scripts/README.md @@ -44,7 +44,7 @@ explicitly names a tracked canonical result. | `visible_recall_sample.py` | Build an unbiased blinded sample for recall measurement. | | `visible_sheets.py` | Build blinded contact sheets for relaxation candidates. | | `retrain_photo_classify.py` | CPU-retrain the 2026-08-31 photo heads from a sha256-keyed cache pack, no images. | -| `publish_photo_classify_hf.py` | Upload the photo-classify card and freeze weights to Hugging Face `wiltodelta/raiw-models`. Manual; the Action `publish-photo-classify-hf.yml` is the write-token path. | +| `publish_photo_classify_hf.py` | Upload the photo-classify card and freeze weights to Hugging Face `wiltodelta/raiw-photo-classify`. Manual; the Action `publish-photo-classify-hf.yml` is the write-token path. | | `verify_engine_selection_fixtures.py` | Verify hashes, dimensions, and prompt pairing in the tracked auto-engine content matrix. | ## Research and diagnostic prototypes diff --git a/scripts/publish_photo_classify_hf.py b/scripts/publish_photo_classify_hf.py index ad9db51..e1e8ae7 100644 --- a/scripts/publish_photo_classify_hf.py +++ b/scripts/publish_photo_classify_hf.py @@ -1,5 +1,5 @@ #!/usr/bin/env python3 -"""Publish the photo-classify freeze to Hugging Face wiltodelta/raiw-models. +"""Publish the photo-classify freeze to Hugging Face wiltodelta/raiw-photo-classify. Manual. Not a library release step. Weights are not in git: pass --src, a directory that holds clip-l-ft.pt and the probe, with detector.pt and @@ -23,7 +23,7 @@ from pathlib import Path log = logging.getLogger(__name__) -HUB_REPO = "wiltodelta/raiw-models" +HUB_REPO = "wiltodelta/raiw-photo-classify" CLIP_FILE = "clip-l-ft.pt" PROBE_FILE = "probe-weights-clip-l-ft.npz" DETECTOR_FILE = "detector.pt" diff --git a/src/remove_ai_watermarks/classify.py b/src/remove_ai_watermarks/classify.py index 4ad9295..469b93e 100644 --- a/src/remove_ai_watermarks/classify.py +++ b/src/remove_ai_watermarks/classify.py @@ -20,7 +20,7 @@ log = logging.getLogger(__name__) CLASSIFY_EXTRA = "'remove-ai-watermarks[classify]'" WEIGHTS_ENV = "RAIW_CLASSIFY_WEIGHTS" -WEIGHTS_REPO = "wiltodelta/raiw-models" +WEIGHTS_REPO = "wiltodelta/raiw-photo-classify" WEIGHTS_REVISION = "c0ac82b6f1ae9fc0b92c467562282e9422da6da6" CLIP_FILE = "clip-l-ft.pt" PROBE_FILE = "probe-weights-clip-l-ft.npz" diff --git a/tests/test_classify.py b/tests/test_classify.py index 9c642da..de3390e 100644 --- a/tests/test_classify.py +++ b/tests/test_classify.py @@ -45,7 +45,7 @@ def _scores( def test_hub_snapshot_is_the_freeze_revision() -> None: - assert WEIGHTS_REPO == "wiltodelta/raiw-models" + assert WEIGHTS_REPO == "wiltodelta/raiw-photo-classify" assert WEIGHTS_REVISION == "c0ac82b6f1ae9fc0b92c467562282e9422da6da6" diff --git a/tests/test_publish_photo_classify_hf.py b/tests/test_publish_photo_classify_hf.py index 6f51ee6..ad1eaad 100644 --- a/tests/test_publish_photo_classify_hf.py +++ b/tests/test_publish_photo_classify_hf.py @@ -19,8 +19,8 @@ def _publish() -> Any: return module -def test_hub_id_is_raiw_models() -> None: - assert _publish().HUB_REPO == "wiltodelta/raiw-models" +def test_hub_id_is_the_photo_classify_repo() -> None: + assert _publish().HUB_REPO == "wiltodelta/raiw-photo-classify" def test_weight_paths_accepts_run1_layout(tmp_path: Path) -> None: