odysseus-dev/odysseus · error · HTTPException

SAM mask failed: {exc}

Error message

SAM mask failed: {exc}

What it means

HTTP 500 catch-all for the SAM smart-mask endpoint: any exception other than an HTTPException during processor encoding, model inference (torch), post_process_masks, or mask-to-PIL conversion is logged with logger.exception and re-raised as 'SAM mask failed: {exc}'. The chained exception preserves the root cause.

Source

Thrown at routes/gallery/gallery_routes.py:1961

            mask_img = Image.fromarray(mask_array, mode="L")
            if mask_img.size != image.size:
                mask_img = mask_img.resize(image.size, Image.NEAREST)
            bbox = mask_img.getbbox()
            result = {
                "mask": _pil_image_to_b64(mask_img),
                "bbox": list(bbox) if bbox else None,
                "model": backend["model_id"],
                "device": device,
            }
            if grounded:
                result["grounding"] = grounded
            return result
        except HTTPException:
            raise
        except Exception as exc:
            logger.exception("smart_mask failed")
            raise HTTPException(500, f"SAM mask failed: {exc}") from exc

    @router.post("/api/image/remove-bg")
    async def remove_background(request: Request):
        """Remove background from an image. If the client passes a `hint_mask`
        (white-where-the-user-wants-the-subject PNG, same dims as the
        image), we constrain the output:

          1. Crop the image to the mask's bounding box (with padding) so
             the model only sees the region the user cares about.
          2. Run rembg on that crop.
          3. Paste the result back at the original offset.
          4. Multiply the final alpha by the user's mask, so anything
             outside the hint becomes transparent regardless of what the
             model thought was foreground.
        """
        require_privilege(request, "can_generate_images")
        body = await request.json()
        image_b64 = body.get("image")

View on GitHub (pinned to f9235ebbf1)

Solutions

  1. Read the server log's 'smart_mask failed' traceback — the message suffix {exc} plus the stack identify the true failure
  2. For CUDA OOM, downscale the image before sending or free GPU memory / use the CPU device
  3. Pin compatible torch/transformers versions per requirements.txt and re-download model weights
  4. Retry once after verifying the image decodes locally with PIL

Example fix

# client: surface the server's chained cause
resp = requests.post(url, json=payload)
if resp.status_code == 500 and 'SAM mask failed' in resp.text:
    logging.error('server traceback: %s', resp.text)
Defensive patterns

Strategy: try-catch

Validate before calling

# pre-flight the pipeline's fragile assumptions
from PIL import Image
img = Image.open(io.BytesIO(base64.b64decode(b64)))
img.convert('RGB').save(io.BytesIO(), format='PNG')  # decodable check
if img.width * img.height > 4_000_000:
    img.thumbnail((2048, 2048))  # reduce CUDA OOM risk

Try / catch

try:
    result = smart_mask(image=b64, points=pts)
except HTTPException as e:
    if e.status_code == 500:
        logger.exception('SAM failed; server log has full traceback')
        if 'out of memory' in str(e.detail):
            result = smart_mask(image=downscaled_b64, points=pts)  # one retry, smaller
        else:
            raise

Prevention

When it happens

Trigger: POST a valid prompt when CUDA OOMs during model(**model_inputs), transformers version incompatibility in post_process_masks, corrupt image that PIL opens but processor rejects, or model weights failing to download mid-request.

Common situations: Large images on a small GPU (OOM); mismatched transformers/torch versions after an upgrade; first-request model download interrupted by a proxy; device mismatch after toggling CUDA availability.

Related errors


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