feat(auto): content-adaptive --auto quality mode, Phase 1

Add `auto_config.plan(image_path) -> AutoConfig`, the first step of the
invisible/all pipeline: it inspects the input image (before the diffusion model
loads) and picks the quality modes so the run adapts to content. Quality-priority
routing -- ControlNet (text/face-structure preservation) is the default, skipped for
plain SDXL only on a clearly structure-less image; GFPGAN face restore when a face is
present; a mild sharpen + grain polish when a smoothing pass ran. Exposed as `--auto`
on `all`/`invisible` (`_apply_auto`; explicit flags override via click's parameter
source). Not wired into batch (its engine is cached per-mode).

Detection is cv2-only and torch-free (~100 MB peak RSS, a few ms): OpenCV YuNet
(`cv2.FaceDetectorYN`, MIT, 232 KB model bundled in assets/) for faces, a Canny
edge-density + MSER heuristic for text/structure (a rough Phase-1 placeholder; DBNet
via cv2.dnn is the planned upgrade). ZERO new pip deps. Designed to run wherever the
pipeline runs -- the raiw.cc Modal GPU worker -- never on the 512 MB web host.

Real-ESRGAN-via-Spandrel upscaling (a new `esrgan` extra) and an adaptive
Laplacian-variance polish are deferred to later phases.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Victor Kuznetsov
2026-06-03 20:52:17 -07:00
co-authored by Claude Opus 4.8
parent ea59bdc3e2
commit 9bd2c17cc4
6 changed files with 365 additions and 0 deletions
+54
View File
@@ -159,6 +159,48 @@ _unsharp_option = click.option(
"--unsharp", type=float, default=0.0, help="Unsharp-mask sharpening strength (0 = off, typical: 0.3-0.8)."
)
_auto_option = click.option(
"--auto",
is_flag=True,
default=False,
help="Auto-pick quality modes (pipeline, face restore, sharpen/grain) from image content. "
"Explicit flags override. EXPERIMENTAL.",
)
def _apply_auto(
ctx: click.Context,
source: Path,
pipeline: str,
restore_faces: bool,
unsharp: float,
humanize: float,
) -> tuple[str, bool, float, float]:
"""Resolve ``--auto``: plan modes from the image, overriding only the flags the
user left at their default (an explicit flag always wins). Returns the resolved
``(pipeline, restore_faces, unsharp, humanize)`` and prints the chosen plan.
"""
from remove_ai_watermarks import auto_config
cfg = auto_config.plan(source)
if cfg is None:
console.print(" Auto: could not read image; using defaults")
return pipeline, restore_faces, unsharp, humanize
def _is_default(name: str) -> bool:
return ctx.get_parameter_source(name) == click.core.ParameterSource.DEFAULT
if _is_default("pipeline"):
pipeline = cfg.pipeline
if _is_default("restore_faces"):
restore_faces = cfg.restore_faces
if _is_default("unsharp"):
unsharp = cfg.unsharp
if _is_default("humanize"):
humanize = cfg.humanize
console.print(f" Auto: {cfg.reason}")
return pipeline, restore_faces, unsharp, humanize
def _restore_faces_options(f: Any) -> Any:
"""Attach the shared GFPGAN face-restoration flags to an invisible-pipeline command."""
@@ -507,6 +549,7 @@ def cmd_erase(
@_restore_faces_options
@_min_resolution_option
@_unsharp_option
@_auto_option
@click.pass_context
def cmd_invisible(
ctx: click.Context,
@@ -525,6 +568,7 @@ def cmd_invisible(
controlnet_scale: float,
restore_faces: bool,
restore_faces_weight: float,
auto: bool,
) -> None:
"""Remove invisible AI watermarks (SynthID, StableSignature, TreeRing).
@@ -542,6 +586,10 @@ def cmd_invisible(
from remove_ai_watermarks.invisible_engine import InvisibleEngine
source = _validate_image(source)
if auto:
pipeline, restore_faces, unsharp, humanize = _apply_auto(
ctx, source, pipeline, restore_faces, unsharp, humanize
)
if output is None:
output = source.with_stem(source.stem + "_clean")
@@ -758,6 +806,7 @@ def cmd_identify(ctx: click.Context, source: Path, no_visible: bool, as_json: bo
@_restore_faces_options
@_min_resolution_option
@_unsharp_option
@_auto_option
@click.pass_context
def cmd_all(
ctx: click.Context,
@@ -779,6 +828,7 @@ def cmd_all(
controlnet_scale: float,
restore_faces: bool,
restore_faces_weight: float,
auto: bool,
) -> None:
"""Remove ALL watermarks: visible + invisible + metadata.
@@ -793,6 +843,10 @@ def cmd_all(
_banner()
source = _validate_image(source)
if auto:
pipeline, restore_faces, unsharp, humanize = _apply_auto(
ctx, source, pipeline, restore_faces, unsharp, humanize
)
if output is None:
output = source.with_stem(source.stem + "_clean")