odysseus-dev/odysseus · warning · HTTPException

Box must be [x1, y1, x2, y2]

Error message

Box must be [x1, y1, x2, y2]

What it means

HTTP 400 from SAM mask box validation: the 'box' field must be a JSON list of exactly 4 numbers ([x1, y1, x2, y2]). A tuple-like object, a dict, a string, or a list of length != 4 fails the isinstance/len check before any float conversion is attempted.

Source

Thrown at routes/gallery/gallery_routes.py:1880

        processor = backend["processor"]
        model = backend["model"]
        device = backend["device"]

        kwargs: Dict[str, Any] = {"return_tensors": "pt"}
        input_points = []
        if points:
            input_labels = []
            for p in points:
                try:
                    input_points.append([float(p["x"]), float(p["y"])])
                    input_labels.append(int(p.get("label", 1)))
                except Exception as exc:
                    raise HTTPException(400, "Invalid point format") from exc
            kwargs["input_points"] = [input_points]
            kwargs["input_labels"] = [input_labels]
        if box:
            if not isinstance(box, list) or len(box) != 4:
                raise HTTPException(400, "Box must be [x1, y1, x2, y2]")
            try:
                kwargs["input_boxes"] = [[[float(v) for v in box]]]
            except Exception as exc:
                raise HTTPException(400, "Invalid box format") from exc

        try:
            inputs = processor(image, **kwargs)
            model_inputs = _model_inputs_to_device(inputs, device, torch)
            with torch.no_grad():
                outputs = model(**model_inputs)
            masks = processor.image_processor.post_process_masks(
                outputs.pred_masks.detach().cpu(),
                inputs["original_sizes"].detach().cpu(),
                inputs["reshaped_input_sizes"].detach().cpu(),
            )
            mask_tensor = masks[0]
            while getattr(mask_tensor, "ndim", 0) > 3:
                mask_tensor = mask_tensor[0]

View on GitHub (pinned to f9235ebbf1)

Solutions

  1. Send box as a flat 4-element numeric array: [x1, y1, x2, y2]
  2. Slice detection outputs to the first 4 elements before sending: box = det.box[:4]
  3. Verify no wrapper nesting like box=[[0,0,100,200]] — the server adds its own nesting for the processor

Example fix

# before
box = {"x1": 10, "y1": 20, "x2": 110, "y2": 220}

# after
box = [10, 20, 110, 220]
Defensive patterns

Strategy: validation

Validate before calling

def is_valid_box(box) -> bool:
    return isinstance(box, list) and len(box) == 4 and all(
        isinstance(v, (int, float)) and not isinstance(v, bool) for v in box
    )

assert is_valid_box(body.get("box")), 'box must be [x1, y1, x2, y2]'

Type guard

function isBox4(v: unknown): v is [number, number, number, number] {
  return Array.isArray(v) && v.length === 4 && v.every(n => typeof n === 'number' && Number.isFinite(n));
}

Prevention

When it happens

Trigger: POST with box={"x1":0,...} (dict), box=[0,0,100] (3 elements), box=[0,0,100,200,5] (5 elements), or box="0,0,100,200" (string).

Common situations: Client stores the box as an object; box assembled from a bbox that sometimes returns None or an extra confidence element (e.g. [x1,y1,x2,y2,score]); grounding pipeline appending a score as a 5th value.

Related errors


AI-assisted analysis of odysseus-dev/odysseus@f9235ebbf1 (2026-08-14). Data as JSON: /api/errors/0216376901734547. Report an issue: GitHub.