{"record":{"id":"5ca84c0cc97dd9a6","repo":"odysseus-dev/odysseus","slug":"invalid-mask-image-e","errorCode":null,"errorMessage":"Invalid mask image: {e}","messagePattern":"Invalid mask image: (.+?)","errorType":"http","errorClass":"HTTPException","httpStatus":400,"severity":"error","filePath":"scripts/mlx_image_server.py","lineNumber":202,"sourceCode":"\n\ndef _write_bridge_mask(raw: bytes, out_path: Path) -> None:\n    try:\n        from PIL import Image\n        import io\n    except Exception as e:\n        raise HTTPException(503, \"Pillow is required for MLX image edit bridge masks.\") from e\n    try:\n        img = Image.open(io.BytesIO(raw))\n        if img.mode == \"RGBA\":\n            # OpenAI edits mask convention: transparent = regenerate.\n            alpha = img.getchannel(\"A\")\n            mask = alpha.point(lambda p: 255 if p < 128 else 0)\n        else:\n            mask = img.convert(\"L\")\n        mask.save(out_path, format=\"PNG\")\n    except Exception as e:\n        raise HTTPException(400, f\"Invalid mask image: {e}\") from e\n\n\ndef _run_bridge(cmd: list[str]) -> None:\n    env = os.environ.copy()\n    proc = subprocess.run(cmd, env=env, text=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)\n    if proc.returncode != 0:\n        detail = (proc.stderr or proc.stdout or \"MLX Swift bridge failed\").strip()\n        logger.error(\"MLX Swift bridge failed (%s): %s\\n%s\", proc.returncode, \" \".join(cmd), detail[-4000:])\n        raise HTTPException(500, detail[-4000:])\n\n\ndef _run_ddcolor_bridge(model: str, image_raw: bytes, out_path: Path) -> None:\n    bridge = _resolve_bridge([\"odysseus-mlx-colorize\", \"mlx-ddcolor-serve\"])\n    if not bridge:\n        raise _unsupported_swift_mlx_runtime(model)\n    with tempfile.TemporaryDirectory(prefix=\"odysseus-ddcolor-\") as td:\n        inp = Path(td) / \"input.png\"\n        _write_bridge_input_image(image_raw, inp)","sourceCodeStart":184,"sourceCodeEnd":220,"githubUrl":"https://github.com/odysseus-dev/odysseus/blob/f9235ebbf13f693a6fd29ce70b097f6ec83705bf/scripts/mlx_image_server.py#L184-L220","documentation":"Raised by _write_bridge_mask in scripts/mlx_image_server.py when PIL cannot decode the uploaded mask, cannot extract the alpha channel, or cannot save the normalized binary mask PNG; the chained exception text is embedded in an HTTP 400. RGBA masks are binarized via alpha (<128 -> 255), other modes are converted to grayscale.","triggerScenarios":"Uploading a mask that is not a decodable image: corrupt bytes, empty upload, wrong field, or an RGBA file whose processing raises (e.g. truncated data stream detected only at getchannel/save time).","commonSituations":"Client sends the mask as raw base64 text instead of binary in multipart; mask exported from an editor with an unusual bit depth or ICC profile that PIL rejects; field name mismatch sends prompt text as the mask.","solutions":["Verify locally: PIL can open the mask and it is grayscale or RGBA; re-save as PNG before upload","Ensure the multipart field is named 'mask' and contains binary image data","Send an RGBA PNG where transparent pixels mark the region to regenerate (OpenAI convention), or a plain L-mode mask","If no mask is needed, omit it for DDColor, but note LaMa/MI-GAN requires one (error 948)"],"exampleFix":"# before\nfiles = {'image': img, 'mask': io.StringIO('mask data')}\n# after\nfiles = {'image': open('img.png','rb'), 'mask': open('mask.png','rb')}  # real PNG mask","handlingStrategy":"validation","validationCode":"from PIL import Image\nm = Image.open(io.BytesIO(mask_bytes))\nassert m.mode in ('L','RGBA','RGB'), m.mode\nImage.open(io.BytesIO(mask_bytes)).verify()\nImage.frombytes(m.mode, m.size, m.tobytes()).save(io.BytesIO(), format='PNG')","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Export masks as L-mode or RGBA PNG from the editor","Binary-encode the mask in multipart, never as string","Test mask round-trip locally before each integration run"],"tags":["pillow","validation","inpainting","http-400"],"backgroundTag":null,"analyzedSha":"f9235ebbf13f693a6fd29ce70b097f6ec83705bf","analyzedAt":"2026-08-14T21:47:48.359Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}