Commit Graph

16 Commits

Author SHA1 Message Date
lhk229 78ef411ad2 Silence httpx per-request INFO lines on the prose log stream
httpx logs every outbound fetch at INFO with the full presigned image_url,
leaking per-request records (and OSS credential/signature params) into the
event stream that is meant for low-volume prose only. The access JSONL
already records the URL, so cap the httpx logger at WARNING.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-20 02:35:54 +00:00
lhk229 43797eedeb Split logging: prose event stream vs structured JSONL access stream
Rule: bulk per-request data never lands in the prose log; everything with
analysis value goes to a structured file.

- Event stream (stderr, text): startup summary, warnings, tracebacks --
  what supervisord/journald already captures. Human-readable, low volume.
- Access stream: one JSONL record per request (plus a "startup" meta
  record with the instance config) written to BGFILTER_ACCESS_LOG,
  app-rotated 50MB x 5; falls back to stderr when unset (dev).
  Failed requests carry a truncated `exc` copy so the access file is
  self-contained; the full traceback stays in the event stream, joined
  by request_id.
- BGFILTER_LOG_FORMAT retired: format is now a property of the stream,
  not a global switch.
- uvicorn runs with --no-access-log everywhere (deploy script + unit
  examples): its prose per-request lines duplicated a subset of ours.
- deploy_autodl.sh --log-dir now provisions both files; docs updated
  (EN+ZH: env table, AutoDL section, run/systemd examples).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-17 13:40:33 +00:00
lhk229 3a8a1ef38a Log the full image_url in the access line
url_host alone made cross-referencing a failed fetch with the caller's
request awkward. Log the full URL too (truncated to 512 chars). Presigned
URLs do land their query-string token in the log -- acceptable for trusted
callers writing to a private log; drop the query if that assumption changes.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-17 06:32:58 +00:00
lhk229 258e0fcf6e default log sets to json 2026-07-17 04:53:50 +00:00
lhk229 dc4badd0e0 Add structured per-request access logging with a request id
Each request now emits one access line (bgfilter.access) carrying a
request_id, the effective params (source file/url, seg_model, cross_check,
background_mode, screen_color, auth), input/output sizes, and stage
timings (t_queue/fetch/decode/process/total_ms). A middleware binds the id
(reusing a sanitized inbound X-Request-ID, else uuid), a logging filter
injects it into every record, and it is echoed back as the X-Request-ID
response header on every response -- including errors, which also log the
detail. Requests that raise still produce a line (fields accumulate on
request.state; the middleware finalizes status/total/client).

request_id lives in a contextvar (not a thread-local) so a pooled worker
never inherits the previous request's id; propagating it into the executor
for deep pipeline logs is left for later. BGFILTER_LOG_FORMAT=text|json
switches human vs shipper-friendly output. /healthz is not logged.

Verified locally via TestClient (fake manager, no models): 200 path logs
all fields + timings with rid == header; inbound id echoed; over-long id
sanitized; XOR 400s log status + error.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-15 05:55:23 +00:00
lhk229 6506bdd643 Add image_url input mode (service fetches the image)
/remove-background now accepts either an uploaded `file` or an `image_url`
the service fetches itself (exactly one required). The fetch runs BEFORE
the compute gate so a slow download never holds a pool slot, and is
bounded by its own semaphore (2 x cpu_workers, hard cap 64) plus a shared
httpx client (connection reuse, matching max_connections) -- this restores
the throttling a direct upload has implicitly, so a burst of tiny URL
requests can't exhaust FDs/ephemeral ports or hammer the upstream.

Bounds: 25 MB body cap (413), 5 s connect / 15 s read timeout, <=3
redirects; MAX_IMAGE_PIXELS still applies post-decode. Deliberately NO
SSRF filtering (no private-IP/host allowlist) -- the endpoint trusts
authenticated callers; auth runs before any fetch. Do not expose to
untrusted clients without adding private-address/redirect filtering to
_fetch_image.

Adds httpx. New env knobs BGFILTER_FETCH_MAX_BYTES / BGFILTER_FETCH_TIMEOUT.
Docs (README/README_ZH/DEPLOY/DEPLOY_ZH) updated. _fetch_image unit-tested
locally (happy path, size cap ->413, bad scheme ->400, 404 ->400);
on-box end-to-end still pending (test box went offline).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-14 14:33:34 +00:00
lhk229 07ba02fdde Make the pipelining knobs config-file settable (server: section)
CPU_WORKERS / GPU_CONCURRENCY were env-only. Add a `server:` config
section (server.cpu_workers, server.gpu_concurrency) so a lane's
concurrency lives in its yaml alongside device/precision, matching how
model.device already works. Precedence mirrors BGFILTER_DEVICE: config is
the default, the env var overrides per box.

The pool + admission semaphore now build in lifespan (after the config
loads) instead of at import, and PipelineManager reads gpu_concurrency
from the settings. configs/gpu.yaml ships 8 / 2 (its target is the 32 GB
GPU boxes) with an inline VRAM caveat: gpu_concurrency>1 must fit
weights + N × forward, so keep it 1 on ≤16 GB cards.

Verified on a 4080 SUPER: startup logs cpu_workers=8 gpu_concurrency=2
from gpu.yaml with no env set, request returns a valid RGBA.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-14 12:58:15 +00:00
lhk229 c748fc22a4 Pipeline concurrent requests over a shared GPU with a narrowed lock
The single asyncio lock serialized the whole request (chroma -> seg ->
trimap -> ViTMatte -> cross-check -> foreground -> despill), so the GPU
sat idle ~70-80% of each request while the CPU stages ran. Replace it
with a thread pool that runs several requests concurrently, and move the
mutex down to just the GPU forwards: when one request steps off the GPU
to do CPU work, another steps on, keeping the device fed.

Two opt-in env knobs, both defaulting to the old strictly-serial
behaviour:
  * BGFILTER_CPU_WORKERS (pool size) attacks the CPU-bound regime -- with
    cross-check off, throughput went 0.91 -> 1.67 req/s (1.84x) at pool=8
    on a 4080S, vs a mere +6% from a second worker process, at half the
    VRAM (one model copy).
  * BGFILTER_GPU_CONCURRENCY caps how many forwards may co-schedule on the
    SMs. batch-1 underfills the GPU, so =2 recovers the SM overlap a lock
    forgoes: cross-check on went 0.83 -> 1.01 req/s, beating two worker
    processes (0.92) with one model copy (~19 GB vs 24 GB).

The CLI/single-thread path passes no lock and uses a nullcontext, so it
stays allocation- and contention-free. Concurrent output verified
identical (10x same-frame -> one hash). A per-worker admission semaphore
bounds in-flight decoded images to the pool size.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-14 12:47:50 +00:00
lhk229 c56e58affe Add complex-background matting mode to the service via background_mode
Backports master's non-flat matting (chroma.enabled: false + the hue-free
cross-check gate) into server-edition, and exposes it over HTTP without
surfacing the internal "chroma" wording: /remove-background gains a
background_mode form field (flat, default | complex). complex maps to
chroma disabled -- no colour key, segmentation alone drives the trimap and
every colour-keyed stage (auto-detect, hue split, chroma suppression,
despill) is bypassed. The cross-check veto still works in complex mode via
its second-opinion-confidence gate (cross_check.second_lo/hi) but defaults
OFF there (it costs the HR-matting forward); an explicit cross_check=on
re-enables it.

No new model weights: complex mode reuses the already-provisioned BiRefNet
seg + ViTMatte (+ optional HR-matting cross-check). Flat mode is unchanged
(bit-identical), and server-edition's own extras (cross_check.lock,
foreground.use_gpu CuPy path) are preserved -- the port is surgical, not a
copy of master's files.

- settings: ChromaSettings.enabled, CrossCheckSettings.second_lo/hi
- config: override_settings chroma passthrough
- despill/foreground: model=None safe guards (foreground keeps GPU path)
- alpha_post: cross_check_alpha hue-free gate when proj is None
- pipeline: _process_rgb complex branch (seg-only trimap, skip colour stages)
- service/app: process(chroma=), background_mode field, complex-defaults-off
  cross-check, X-BGFilter-Background-Mode header
- cli: --chroma/--no-chroma
- configs/docs: gpu.yaml + default.yaml + README/README_ZH

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-14 14:04:58 +08:00
lhk229 dbd061e119 Add cross_check lock so a locked lane ignores per-request cross_check=on
The cpu-fast lane disables cross-check to fit a low-memory box, but a request
could still send cross_check=on and override it, lazy-loading the HR-matting
model and running the 2048 forward -> the exact OOM (worker SIGKILL -> 502)
cpu-fast exists to avoid. BGFILTER_CROSS_CHECK=0 did not protect against this
because the per-request form field wins.

Add CrossCheckSettings.lock (default False). When set, PipelineManager
resolves cross-check to the base value and ignores per-request overrides, so a
locked off-lane silently honours cross_check=on as off -- no HR-matting load,
no cross-check run, no error (200), and the response header reports "off".
Enable it in cpu-fast.yaml (enabled: false, lock: true). Verified end-to-end:
cross_check=on on the locked lane returns without loading the cross-checker.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-11 10:26:38 +00:00
lhk229 4d34c96bed Add optional token auth to /remove-background
Adds app-level authentication gated on the BGFILTER_AUTH_TOKEN env var.
When set, /remove-background requires the X-Genarrative-Image-Token header
to match it (constant-time compare via hmac); missing or wrong -> 401. The
header name matches the production nginx layer, so callers only swap the
URL + token value, no interface change.

Fail-open by design: when BGFILTER_AUTH_TOKEN is unset the check is a no-op
(open), preserving current behaviour and leaving auth to an upstream proxy
(nginx in production). A startup WARNING is logged when auth is disabled.
/healthz stays unauthenticated for health checks.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-11 08:26:04 +00:00
lhk229 ad445611ad Make cross-check and reuse-as-seg the service default
Cross-check + reuse-as-seg are already the config/CLI default; align the HTTP
service with them instead of forcing cross-check off. The earlier off-by-default
was to avoid the extra inference, but reuse-as-seg now makes that forward double
as the seg mask (birefnet backend) -- it replaces the primary seg model rather
than adding to it, so the cost concern is gone.

- app.py: honour the config default (on); BGFILTER_CROSS_CHECK still forces
  either way, unset = config.
- service.py preload: warm the cross-check HR-matting model, and skip the now-
  redundant primary segmenter when reuse covers segmentation.
- README / DEPLOY(_ZH): document cross-check as default-on, BiRefNet_HR-matting
  as required (not optional), BGFILTER_CROSS_CHECK=0 to disable.

Verified: service defaults cross_check on, env forces both ways, preload picks
cross-checker and skips the primary segmenter.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-06 16:11:43 +08:00
lhk229 8417653598 Add per-request cross_check toggle to the HTTP service (lazy-loaded)
POST /remove-background gains an optional cross_check form field
(1/0/true/false/yes/no/on/off; empty = service default). The effective
state is reported in the X-BGFilter-Cross-Check response header.

The cross-check model is now never preloaded at startup: the first
request that enables it pays the one-time load (measured ~8 s on top of
the ~50 s cc inference), after which the instance is cached and shared.

Verified with a functional test against PipelineManager: cc-off requests
never load the model, cc=1 lazy-loads then reuses the cached instance,
and omitting the field falls back to the service default.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-04 22:34:08 +08:00
lhk229 11c591e5b5 Default the HTTP service to cross-check OFF (opt in via BGFILTER_CROSS_CHECK)
The cross-model veto adds one extra large-model inference per request
(measured ~35 s/image on CPU: 14.4 s -> 49.1 s), which is too slow as a
service default. The service now disables it regardless of the config
default; BGFILTER_CROSS_CHECK=1 re-enables it (GPU or quality-first
deployments). CLI behaviour is unchanged (still on by default).

Startup log and /healthz report the effective state.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-04 20:57:39 +08:00
lhk229 578bd1f4d8 Merge master: matte-confidence gate + cross-model hair-gap veto
Brings in the two algorithm optimisations from master:
- Gate chroma suppression by the matte's own confidence (639fdf0)
- Add cross-model veto of hair-gap background residue (adbbb9d)

Server-edition integration on top of the plain merge:
- PipelineManager caches the cross-check model (BiRefNet_HR-matting) and
  injects it into MattingPipeline, so HTTP requests never reload it.
- MattingResult carries cross_check_alpha; debug output matches master.
- /healthz reports crossCheck; README lists models/BiRefNet_HR-matting
  in the local-weights bundle.

Verified: CLI --no-cross-check baseline and full cross-check run both
pass on samples/TestImage3.png; cross_check_alpha.png written to debug.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-04 20:37:07 +08:00
lhk229 d24c6ed608 Add FastAPI HTTP service for background removal
Wrap the BGfilter matting pipeline in a long-running FastAPI service
(POST /remove-background, GET /healthz) exposing only screen_color and
seg_model, matching the birefnet-service request contract.

- bgfilter/pipeline.py: add in-memory run_rgb entry returning a
  MattingResult; allow injecting a shared runner/segmenter. CLI file
  path (run_image) behaviour is unchanged.
- bgfilter/service.py: PipelineManager caches one shared ViTMatte runner
  and per-backend segmenters, so screen_color/seg_model never reload a
  model.
- app.py: thin FastAPI layer -- param validation, image decode/encode,
  error codes (400/413/500), timing headers, global asyncio lock.
- requirements.txt: fastapi, uvicorn[standard], python-multipart.
- README + design doc: HTTP service usage and HF-mirror/proxy/Xet
  download notes.

Verified end-to-end (base ViTMatte + BiRefNet + anime-seg): 11/11
acceptance checks pass.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-02 12:08:25 +00:00