Convert the array to uint8 in the 0–255 range before calling Image.fromarray():
Image.fromarray((np.clip(arr, 0, 1) * 255).astype(np.uint8)) for floats in 0..1, or
Image.fromarray(arr.astype(np.uint8)) for integers that are already 0..255.
What the error looks like
The two versions we see most: a colour image stored as floats, and an integer mask built with NumPy's default int type.
import numpy as np
from PIL import Image
rgb = np.random.default_rng(0).random((64, 64, 3), dtype=np.float32) # float32, values 0..1
Image.fromarray(rgb)
TypeError: Cannot handle this data type: (1, 1, 3), <f4
import numpy as np
from PIL import Image
mask = np.zeros((64, 64), dtype=int) # int64 on Linux and macOS
mask[16:48, 16:48] = 255
Image.fromarray(mask)
TypeError: Cannot handle this data type: (1, 1), <i8
How to read the message
The tuple and code after "data type" are what Pillow looked up in its table of supported array layouts:
(1, 1, 3): the array has a third dimension of 3 (three channels).(1, 1)means a plain 2-D array.<f4: the NumPy type string.<is little-endian;f4= float32,f8= float64,i8= int64,u1= uint8,u2= uint16,b1= bool.
import numpy as np
for arr in [np.zeros((2, 2, 3), np.float32), np.zeros((2, 2), np.int64), np.zeros((3, 2, 2), np.uint8)]:
# This is the (shape-suffix, typestr) pair Pillow looks up:
print(arr.dtype, arr.shape, "->", (1, 1) + arr.shape[2:], arr.__array_interface__["typestr"])
float32 (2, 2, 3) -> (1, 1, 3) <f4
int64 (2, 2) -> (1, 1) <i8
uint8 (3, 2, 2) -> (1, 1, 2) |u1
Supported combinations include u1 with 1, 2, 3 or 4 channels (L, LA, RGB, RGBA), 2-D b1 (mode 1),
2-D u2 (I;16), 2-D i4 (I) and 2-D f4/f8 (F). Colour floats and 64-bit
integers are not. The full table is on the NumPy guide.
Fix 1: float arrays
Scale to 0–255, clip, and convert. Clipping matters: values slightly outside 0..1 (common after augmentation or model output) would otherwise wrap around:
import numpy as np
from PIL import Image
rgb = np.random.default_rng(0).random((64, 64, 3), dtype=np.float32) # 0..1
img = Image.fromarray((np.clip(rgb, 0, 1) * 255).round().astype(np.uint8))
print(img.mode, img.size)
RGB (64, 64)
If the floats are already in 0..255, skip the * 255.
Fix 2: int64 (or other wide integers)
import numpy as np
from PIL import Image
mask = np.zeros((64, 64), dtype=int)
mask[16:48, 16:48] = 255
img = Image.fromarray(mask.astype(np.uint8)) # values already 0..255
print(img.mode, img.getextrema())
L (0, 255)
If the values can exceed 255, decide what they mean first: clip with np.clip(arr, 0, 255), or rescale with
arr * 255 // arr.max(). For 16-bit data such as depth maps or medical images, arr.astype(np.uint16)
gives a 16-bit I;16 image you can save as PNG or TIFF without losing precision.
Fix 3: channel-first arrays (PyTorch, some ML libraries)
Arrays shaped (3, height, width) fail for a different reason: Pillow reads the last axis as channels.
import numpy as np
from PIL import Image
# PyTorch tensors and some libraries store images channel-first: (channels, height, width)
chw = np.zeros((3, 224, 224), dtype=np.uint8)
try:
Image.fromarray(chw)
except TypeError as e:
print("channel-first:", e)
hwc = np.transpose(chw, (1, 2, 0)) # -> (height, width, channels)
print("transposed:", Image.fromarray(hwc).mode, Image.fromarray(hwc).size)
channel-first: Cannot handle this data type: (1, 1, 224), |u1
transposed: RGB (224, 224)
With a PyTorch tensor, torchvision.transforms.functional.to_pil_image(tensor) handles the transpose and scaling for you.
A helper that handles all of these
import numpy as np
from PIL import Image
def to_image(arr):
"""Convert common array layouts (float 0..1, any int dtype, CHW, single-channel 3-D) to a PIL image."""
arr = np.asarray(arr)
if arr.ndim == 3 and arr.shape[0] in (1, 3, 4) and arr.shape[-1] not in (1, 3, 4):
arr = np.transpose(arr, (1, 2, 0)) # CHW -> HWC
if arr.ndim == 3 and arr.shape[-1] == 1:
arr = arr[..., 0] # (h, w, 1) -> (h, w)
if arr.dtype.kind == "f":
scale = 255 if arr.max() <= 1.0 else 1 # treat floats as 0..1 if they look like it
arr = np.clip(arr * scale, 0, 255).round()
return Image.fromarray(arr.astype(np.uint8))
for a in [np.random.rand(32, 32, 3), np.ones((3, 32, 32), np.float32), np.full((32, 32), 300), np.zeros((32, 32, 1), np.int64)]:
print(a.dtype, a.shape, "->", to_image(a).mode)
float64 (32, 32, 3) -> RGB
float32 (3, 32, 32) -> RGB
int64 (32, 32) -> L
int64 (32, 32, 1) -> L
Related
The outputs and images on this page are the real results of running every example with Pillow 12.3.0 on Python 3.13 (Linux) on 2026-10-15. Every example is also re-run automatically on macOS (Darwin 25.6.0, Pillow 12.3.0), Linux (Linux 6.17.0-1022-azure, Pillow 12.3.0), Windows (Windows 2025Server, Pillow 12.3.0); the line under each example shows the result per operating system. The DejaVu Sans font files sit next to each script so font examples behave the same everywhere. Sample photos: NASA/Sam Lott, public domain. Spotted something wrong? Tell us.