diff --git a/detailing.py b/detailing.py index 96adea14..43918672 100644 --- a/detailing.py +++ b/detailing.py @@ -224,8 +224,13 @@ def _clipseg_heat(frames, prompts, debug=False): with torch.no_grad(): for _, img in frames: pil = Image.fromarray((img.numpy() * 255.0).astype(np.uint8)) + # padding=True is mandatory once len(prompts) > 1: CLIPSeg's tokenizer + # otherwise returns one variable-length id list per prompt and torch can't + # stack them into a batch ("excessive nesting... type list where int is + # expected"). truncation=True guards the rare very-long user-typed target. inputs = processor( - text=list(prompts), images=[pil] * len(prompts), return_tensors="pt") + text=list(prompts), images=[pil] * len(prompts), return_tensors="pt", + padding=True, truncation=True) logits = model(**inputs).logits # [P, 352, 352] (or [352, 352] for one prompt) if logits.dim() == 2: logits = logits[None] diff --git a/tests/test_detailing.py b/tests/test_detailing.py index 1430600f..b3705287 100644 --- a/tests/test_detailing.py +++ b/tests/test_detailing.py @@ -99,6 +99,41 @@ def test_find_tube_enforces_minimum_edge(): assert (x1 - x0) >= detailing.MIN_TUBE_EDGE +# --------------------------------------------------------------------------- +# _clipseg_heat +# --------------------------------------------------------------------------- + +def test_clipseg_heat_passes_padding_and_truncation(monkeypatch): + """Regression: a multi-prompt call without padding=True crashes CLIPSeg's + tokenizer ("excessive nesting... type list where int is expected") the moment + detail_targets has more than one entry — caught live on a real rental run. + truncation=True is the same call, cheap to require alongside it.""" + calls = [] + + class _FakeInputs(dict): + def to(self, *a, **k): + return self + + class _FakeProcessor: + def __call__(self, text, images, return_tensors, **kwargs): + calls.append(kwargs) + return _FakeInputs() + + class _FakeModel: + def __call__(self, **inputs): + n = 2 + return types.SimpleNamespace(logits=torch.zeros(n, 8, 8)) + + def eval(self): + return self + + monkeypatch.setattr(detailing, "_get_clipseg", lambda: (_FakeProcessor(), _FakeModel())) + frames = [(0, torch.zeros(16, 16, 3))] + detailing._clipseg_heat(frames, ["hands", "feet"]) + assert calls and calls[0].get("padding") is True + assert calls[0].get("truncation") is True + + # --------------------------------------------------------------------------- # resolve_upsampler_name # ---------------------------------------------------------------------------