Debugging Pillow: mode errors, bombs and truncated files
Diagnose the exceptions Pillow actually raises, disable the decompression-bomb guard deliberately, and handle damaged files without crashing a batch.
The exceptions you will meet
| Error | Cause | Fix |
|---|---|---|
UnidentifiedImageError | Not an image, or a format with no plugin | Validate the input, check the codec is compiled in |
OSError: cannot write mode RGBA as JPEG | Alpha channel saved to a format without alpha | Composite onto an opaque background |
OSError: image file is truncated | Incomplete download or damaged file | Re-fetch, or opt into partial loading deliberately |
AttributeError: 'NoneType' object has no attribute 'size' | verify() was called and then the image used | Reopen the file after verifying |
DecompressionBombError | Image larger than MAX_IMAGE_PIXELS | Raise the limit for trusted input, reject otherwise |
ValueError: images do not match | Size or mode mismatch in paste or merge | Resize or convert before combining |
from PIL import Image, UnidentifiedImageError
try:
im = Image.open(path)
im.load() # forces the decode, so failures happen here
except UnidentifiedImageError:
print("not an image:", path)
except OSError as exc:
print("damaged or unsupported:", path, exc)
else:
print(im.format, im.mode, im.size)verify() and the reopen rule
# verify() checks the header only, then leaves the object unusable
with Image.open(path) as probe:
try:
probe.verify()
except Exception as exc:
print("header damaged:", exc)
# check the pixels with a fresh open
with Image.open(path) as im:
im.load()
print(im.size)verify()is cheap and catches truncated headers, but not damage in the middle of the file.load()decodes and catches more.- After
verify()the image object must not be used again; reopen the path. - Opening is lazy. A file can open successfully and still fail the moment you read pixels.
Bombs and truncated files
from PIL import Image, ImageFile
# the default guard: a warning above about 89 MP, an error above twice that
print(Image.MAX_IMAGE_PIXELS)
# raise it for trusted, deliberately huge inputs such as maps or scans
Image.MAX_IMAGE_PIXELS = 500_000_000
# or disable the guard entirely: only for input you control
Image.MAX_IMAGE_PIXELS = None
# accept partially downloaded files in a controlled pipeline
ImageFile.LOAD_TRUNCATED_IMAGES = True⚠️
A decompression bomb is a small file that expands to gigabytes of pixels, and it will exhaust memory before any validation code runs. Never set
MAX_IMAGE_PIXELS = None or LOAD_TRUNCATED_IMAGES = True globally in a service that accepts uploads — set them narrowly, process the image, and restore the previous values.FAQ
The batch crashed on one bad file. How do I keep going?
Catch
UnidentifiedImageError and OSError per file, record the path and the reason, and continue. Wrap im.load() inside the try so decode failures are caught too, not just open failures.Why did open succeed but the image came out black?
The header was valid but the pixel data was incomplete, usually from an interrupted transfer. Setting
LOAD_TRUNCATED_IMAGES fills the missing area, which often renders as a grey or black band; the better fix is to re-fetch the file.Related
Opening, inspecting and saving images Batch image processing scripts and performance
Last refreshed 2026-09-18.