odysseus-dev/odysseus · error · HTTPException

LaMa / MI-GAN inpainting requires an image mask. Use the edi

Error message

LaMa / MI-GAN inpainting requires an image mask. Use the editor inpaint/object-removal tool so Odysseus can send the mask.

What it means

Raised by _run_inpaint_bridge in scripts/mlx_image_server.py with HTTP 422 when the LaMa / MI-GAN inpainting path is invoked with no mask bytes. These models are pure inpainters: unlike diffusion img2img they cannot operate on the whole image, so the server requires the multipart 'mask' upload.

Source

Thrown at scripts/mlx_image_server.py:234

    if not bridge:
        raise _unsupported_swift_mlx_runtime(model)
    with tempfile.TemporaryDirectory(prefix="odysseus-ddcolor-") as td:
        inp = Path(td) / "input.png"
        _write_bridge_input_image(image_raw, inp)
        weights = _weights_path(model)
        tier = "tiny" if "tiny" in model.lower() else "large"
        _run_bridge([
            bridge,
            "--model", str(weights),
            "--image", str(inp),
            "--output", str(out_path),
            "--tier", tier,
        ])


def _run_inpaint_bridge(model: str, image_raw: bytes, mask_raw: bytes | None, out_path: Path) -> None:
    if not mask_raw:
        raise HTTPException(
            422,
            "LaMa / MI-GAN inpainting requires an image mask. Use the editor inpaint/object-removal tool so Odysseus can send the mask.",
        )
    bridge = _resolve_bridge(["odysseus-mlx-inpaint", "mlx-lama-serve"])
    if not bridge:
        raise _unsupported_swift_mlx_runtime(model)
    with tempfile.TemporaryDirectory(prefix="odysseus-mlx-inpaint-") as td:
        inp = Path(td) / "input.png"
        mask = Path(td) / "mask.png"
        _write_bridge_input_image(image_raw, inp)
        _write_bridge_mask(mask_raw, mask)
        weights = _weights_path(model)
        mode = "fast" if ("mi-gan" in model.lower() or "migan" in model.lower()) else "best"
        _run_bridge([
            bridge,
            "--model", str(weights),
            "--image", str(inp),
            "--mask", str(mask),

View on GitHub (pinned to f9235ebbf1)

Solutions

  1. Send a mask: multipart 'mask' file (RGBA PNG with transparent = regenerate, or L-mode) marking the region to inpaint
  2. Use an editor tool that produces and uploads the mask (the message points at Odysseus's inpaint/object-removal tool)
  3. For whole-image restoration/colorize instead of region edit, switch to a DDColor model which needs no mask

Example fix

# before
files = {'image': open('photo.png','rb'), 'prompt': 'remove person'}
# after
files = {'image': open('photo.png','rb'), 'mask': open('person-mask.png','rb'), 'prompt': 'remove person'}
Defensive patterns

Strategy: validation

Validate before calling

def validate_edit_request(image: bytes, mask: bytes | None, model: str) -> None:
    assert image, 'image required'
    if is_inpaint_model(model):
        assert mask and len(mask) > 0, f'{model} requires a mask'

Type guard

def requires_mask(model: str) -> bool:
    m = model.lower()
    return 'lama' in m or 'migan' in m or 'mi-gan' in m

Try / catch

try:
    post_edit(image=img, mask=None, model='lama')
except HTTPException as e:
    if e.status_code == 422 and 'requires an image mask' in e.detail:
        mask = build_mask_from_selection(); post_edit(image=img, mask=mask, model='lama')

Prevention

When it happens

Trigger: POST /v1/images/edits with an 'image' but no 'mask' field (or an empty mask file) while serving a LaMa/MI-GAN model. Also harmonize requests where body_mask and mask are both null for a LaMa model.

Common situations: Client built for diffusion edit endpoints that treat masks as optional; generic OpenAI SDK images.edit call without mask; UI tool that only sends the image for 'enhance' style operations; mask field sent but empty because the user drew nothing.

Related errors


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