feat: harden pipeline UX — preprocess, rembg alpha, remix/lightbox

Downscale to 2000px JPEG before rembg, random blend opacity, timeout
retries, per-variant remix prefill, lightbox, and longer SFTP idle.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Frank Schwenk
2026-07-16 23:35:06 +02:00
parent 8ef123393a
commit 83d4468b69
16 changed files with 594 additions and 50 deletions
+30
View File
@@ -0,0 +1,30 @@
# Background-removal comparison
Inbox originals from `live.f12.rocks` vs. backends.
## Layout
- `inputs/` — originals from boka inbox
- `outputs/` — results, naming:
```
<stem>__rembg-u2net__no-alpha.png
<stem>__rembg-u2net__alpha.png
<stem>__rembg-default__no-alpha.png
<stem>__rembg-default__alpha.png
<stem>__rembg-birefnet-general__no-alpha.png
<stem>__rembg-birefnet-general__alpha.png
<stem>__withoutbg-open-weights.png
```
- `rembg-default` = `rembg.remove()` without session (library default = u2net)
- `rembg-u2net` = explicit `new_session("u2net")` — expect identical to default
- `withoutbg` open-weights includes matting in-graph (~2GB RAM claimed)
## Re-run
```bash
./run.sh
```
Skips existing non-empty outputs. Models cached in `model-cache/`.
+28
View File
@@ -0,0 +1,28 @@
#!/usr/bin/env bash
# Run rembg/withoutbg comparison in Python 3.11 (host is 3.14-only).
set -euo pipefail
ROOT="$(cd "$(dirname "$0")" && pwd)"
IMG="livef12-bg-compare:py311"
LOG="$ROOT/compare.log"
docker build -t "$IMG" - <<'EOF'
FROM python:3.11-slim-bookworm
RUN apt-get update && apt-get install -y --no-install-recommends libgl1 libglib2.0-0 \
&& rm -rf /var/lib/apt/lists/*
RUN pip install --no-cache-dir 'rembg>=2.0.59,<2.1' 'pillow>=10.4,<11' \
'onnxruntime>=1.19,<1.20' withoutbg
WORKDIR /work
EOF
echo "Starting comparison; log: $LOG"
docker run --rm \
--name livef12-bg-compare \
-e TQDM_DISABLE=1 \
-e COMPARE_INPUT=/data/inputs \
-e COMPARE_OUTPUT=/data/outputs \
-e HOME=/cache \
-v "$ROOT/inputs:/data/inputs:ro" \
-v "$ROOT/outputs:/data/outputs" \
-v "$ROOT/run_compare.py:/work/run_compare.py:ro" \
-v "$ROOT/model-cache:/cache" \
"$IMG" python -u /work/run_compare.py 2>&1 | tee "$LOG"
+115
View File
@@ -0,0 +1,115 @@
#!/usr/bin/env python3
"""Compare background-removal backends on the live.f12.rocks inbox uploads.
Outputs land next to each other with explicit names so Fränky can pick a winner:
<stem>__rembg-u2net__no-alpha.png
<stem>__rembg-u2net__alpha.png
<stem>__rembg-default__no-alpha.png
<stem>__rembg-default__alpha.png
<stem>__rembg-birefnet-general__no-alpha.png
<stem>__rembg-birefnet-general__alpha.png
<stem>__withoutbg-open-weights.png
`rembg-default` = rembg.remove() with no session (library default model).
`rembg-u2net` = rembg.new_session("u2net") explicitly.
withoutbg embeds matting in its open-weights graph — one output only.
"""
from __future__ import annotations
import os
import sys
import time
import traceback
from pathlib import Path
os.environ.setdefault("TQDM_DISABLE", "1")
INPUT_DIR = Path(os.environ.get("COMPARE_INPUT", "/data/inputs"))
OUTPUT_DIR = Path(os.environ.get("COMPARE_OUTPUT", "/data/outputs"))
EXTS = {".jpg", ".jpeg", ".png", ".webp"}
def log(msg: str) -> None:
print(msg, flush=True)
def run_rembg_named(data: bytes, label: str, model: str | None, alpha: bool, out: Path) -> None:
from rembg import new_session, remove
t0 = time.monotonic()
kwargs: dict = {"alpha_matting": alpha}
if model is None:
result = remove(data, **kwargs)
model_note = "default"
else:
session = new_session(model)
result = remove(data, session=session, **kwargs)
model_note = model
out.write_bytes(result)
log(f" OK {label} model={model_note} alpha={alpha} {time.monotonic()-t0:.1f}s -> {out.name} ({out.stat().st_size} bytes)")
def run_withoutbg(path: Path, out: Path) -> None:
from withoutbg import WithoutBG
t0 = time.monotonic()
model = WithoutBG.open_weights()
result = model.remove_background(str(path))
result.save(out)
log(f" OK withoutbg-open-weights {time.monotonic()-t0:.1f}s -> {out.name} ({out.stat().st_size} bytes)")
def process_one(path: Path) -> None:
stem = path.stem
log(f"=== {path.name} ===")
data = path.read_bytes()
jobs = [
("rembg-u2net", "u2net", False),
("rembg-u2net", "u2net", True),
("rembg-default", None, False),
("rembg-default", None, True),
("rembg-birefnet-general", "birefnet-general", False),
("rembg-birefnet-general", "birefnet-general", True),
]
for label, model, alpha in jobs:
tag = "alpha" if alpha else "no-alpha"
out = OUTPUT_DIR / f"{stem}__{label}__{tag}.png"
if out.exists() and out.stat().st_size > 0:
log(f" SKIP {out.name} (exists)")
continue
try:
run_rembg_named(data, label, model, alpha, out)
except Exception as exc: # noqa: BLE001
err = OUTPUT_DIR / f"{stem}__{label}__{tag}.ERROR.txt"
err.write_text(f"{type(exc).__name__}: {exc}\n\n{traceback.format_exc()}", encoding="utf-8")
log(f" FAIL {label} alpha={alpha}: {type(exc).__name__}: {exc}")
out_w = OUTPUT_DIR / f"{stem}__withoutbg-open-weights.png"
if out_w.exists() and out_w.stat().st_size > 0:
log(f" SKIP {out_w.name} (exists)")
else:
try:
run_withoutbg(path, out_w)
except Exception as exc: # noqa: BLE001
err = OUTPUT_DIR / f"{stem}__withoutbg-open-weights.ERROR.txt"
err.write_text(f"{type(exc).__name__}: {exc}\n\n{traceback.format_exc()}", encoding="utf-8")
log(f" FAIL withoutbg: {type(exc).__name__}: {exc}")
def main() -> int:
OUTPUT_DIR.mkdir(parents=True, exist_ok=True)
inputs = sorted(p for p in INPUT_DIR.iterdir() if p.is_file() and p.suffix.lower() in EXTS)
if not inputs:
log(f"No images in {INPUT_DIR}")
return 1
log(f"Comparing {len(inputs)} image(s); outputs -> {OUTPUT_DIR}")
for path in inputs:
process_one(path)
log("DONE")
return 0
if __name__ == "__main__":
raise SystemExit(main())