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>
This commit is contained in:
2026-07-04 22:34:08 +08:00
parent 11c591e5b5
commit 8417653598
3 changed files with 51 additions and 11 deletions
+16 -7
View File
@@ -24,9 +24,9 @@ class PipelineManager:
ViTMatte is the matting model shared by both segmentation backends, so it is
loaded once and reused. Each segmentation backend is lazily loaded once on
first use and cached by name. Per-request parameters (``screen_color``,
``seg_model``) never trigger a model reload -- ``screen_color`` only affects
this request's chroma/despill, and ``seg_model`` just selects an already-cached
(or first-time-loaded) segmenter.
``seg_model``, ``cross_check``) never trigger a model reload -- ``screen_color``
only affects this request's chroma/despill, and ``seg_model``/``cross_check``
select an already-cached (or first-time-loaded, then cached) model.
"""
def __init__(self, base_settings: PipelineSettings, preload: bool = True):
@@ -40,8 +40,8 @@ class PipelineManager:
self._get_runner()
if base_settings.segmentation.enabled:
self._get_segmenter(base_settings.segmentation.backend)
if base_settings.cross_check.enabled:
self._get_cross_checker()
# The cross-check model is deliberately NOT preloaded: it is opt-in
# per request, so the first request that enables it pays the load.
@property
def default_seg_model(self) -> str:
@@ -110,12 +110,21 @@ class PipelineManager:
return self._cross_checker
def process(
self, rgb: np.ndarray, *, screen_color: str | None, seg_model: str
self,
rgb: np.ndarray,
*,
screen_color: str | None,
seg_model: str,
cross_check: bool | None = None,
) -> MattingResult:
if seg_model not in _SEG_MODELS:
raise ValueError(f"invalid seg_model '{seg_model}'")
settings = override_settings(
self._base, screen_color=screen_color, seg_backend=seg_model
self._base,
screen_color=screen_color,
seg_backend=seg_model,
# None = keep the service default; True/False = per-request override.
cross_check=cross_check,
)
runner = (
self._get_runner()