Skip to course content
Free computer vision course

Computer Vision and Multimodal AI

Unit 02.04: A loader that fails loudly on a bad file

A loader that skips bad files silently gives you a training set smaller than you think.

Reject with a reason, and count the reasons

Decode, size and channel checks, each returning a specific explanation.

The code runs three inputs through it.

import io
import numpy as np
from PIL import Image, UnidentifiedImageError


def load(data, expect_channels=3, min_size=32):
    """Fail loudly, with the reason, before anything downstream sees it."""
    try:
        image = Image.open(io.BytesIO(data))
        image.load()
    except (UnidentifiedImageError, OSError) as exc:
        return None, f"undecodable: {type(exc).__name__}"
    if min(image.size) < min_size:
        return None, f"too small: {image.size}"
    array = np.asarray(image.convert("RGB"))
    if array.shape[2] != expect_channels:
        return None, f"unexpected channels: {array.shape[2]}"
    return array, None


good = io.BytesIO()
Image.fromarray(np.zeros((64, 64, 3), dtype="uint8")).save(good, format="PNG")
tiny = io.BytesIO()
Image.fromarray(np.zeros((8, 8, 3), dtype="uint8")).save(tiny, format="PNG")

for label, data in [("valid", good.getvalue()), ("tiny", tiny.getvalue()),
                    ("not an image", b"hello world")]:
    array, error = load(data)
    print(f"{label:14} {'ok ' + str(array.shape) if error is None else 'REJECT: ' + error}")

# Silently skipping bad files means a training set that is quietly smaller than
# you think. Return the reason and count the rejections by kind.

Each rejection names its cause. That is what lets you count rejections by kind - and a sudden rise in one kind is a signal about an upstream change rather than a mysterious drop in dataset size.

The size check matters more than it looks. An 8×8 image decodes perfectly and contains nothing, and it will sit in the training set contributing noise.

The mistake this prevents

The mistake is a bare try/except: continue. It works, it is one line, and it means nobody ever finds out that 12% of one source's images fail to decode.

Takeaway

Return the reason for every rejection and count them by kind. Silent skipping hides both dataset shrinkage and upstream changes.