immich-app/immich · warning · HTTPException

Image has zero width or height

Error message

Image has zero width or height

What it means

predict validates the uploaded image before inference. If decode_pil produces an image whose width or height is 0, the endpoint raises HTTP 400 'Image has zero width or height' because downstream models cannot process a degenerate image.

Solutions

  1. Check the source file is a fully written, non-empty image before sending it to the ML endpoint.
  2. Re-encode or re-fetch the asset; the original data is corrupt.
  3. On the client, pre-validate image dimensions before calling predict.

Example fix

# before
requests.post(f"{ml}/predict", files={"image": open(p, 'rb')})
# after
from PIL import Image
img = Image.open(p); img.verify()
with Image.open(p) as im:
    if im.width == 0 or im.height == 0:
        skip(p)
    else:
        requests.post(f"{ml}/predict", files={"image": open(p, 'rb')})
Defensive patterns

Strategy: validation

Validate before calling

from PIL import Image
with Image.open(path) as im:
    im.verify()
with Image.open(path) as im:
    assert im.width > 0 and im.height > 0

Try / catch

try:
    r = requests.post(f"{ml}/predict", files={"image": f})
    r.raise_for_status()
except requests.HTTPError as e:
    if e.response.status_code == 400: skip_asset(asset_id)

Prevention

When it happens

Trigger: Uploading a corrupted/truncated image, an image whose header declares zero dimensions, or an empty/partial byte stream that PIL decodes into a zero-sized image.

Common situations: Thumbnailing a file that was still being written; interrupted uploads; exotic formats PIL partially decodes; test fixtures with empty buffers.

Understand the failure class

Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.

Related errors


AI-assisted analysis of immich-app/immich@e55ac299a4 (2026-09-15). Data as JSON: /api/errors/7ba158ca3dfc78fa. Report an issue: GitHub.

Appendix: source

Thrown at machine-learning/immich_ml/main.py:175

async def root() -> ORJSONResponse:
    return ORJSONResponse({"message": "Immich ML"})


@app.get("/ping")
def ping() -> PlainTextResponse:
    return PlainTextResponse("pong")


@app.post("/predict", dependencies=[Depends(update_state)])
async def predict(
    entries: InferenceEntries = Depends(get_entries),
    image: bytes | None = File(default=None),
    text: str | None = Form(default=None),
) -> Any:
    if image is not None:
        decoded = await run(lambda: decode_pil(image))
        if decoded.width == 0 or decoded.height == 0:
            raise HTTPException(400, "Image has zero width or height")
        inputs: Image | str = decoded
    elif text is not None:
        inputs = text
    else:
        raise HTTPException(400, "Either image or text must be provided")
    response = await run_inference(inputs, entries)
    return ORJSONResponse(response)


async def run_inference(payload: Image | str, entries: InferenceEntries) -> InferenceResponse:
    outputs: dict[ModelIdentity, Any] = {}
    response: InferenceResponse = {}

    async def _run_inference(entry: InferenceEntry) -> None:
        model = await model_cache.get(
            entry["name"], entry["type"], entry["task"], ttl=settings.model_ttl, **entry["options"]
        )
        inputs = [payload]

View on GitHub (pinned to e55ac299a4)