Quick fix

Replace misc.imread(path) with np.asarray(Image.open(path)) (or imageio.v3.imread(path)), misc.imresize with Image.resize (note: Pillow sizes are width, height), and misc.imsave/misc.toimage with Image.fromarray(arr).save(path), scaling float data to 0–255 yourself.

The error

old_tutorial.py
from scipy import misc

img = misc.imread("photo.jpg")
Tested on ✓ Linux ✓ Windows ✓ macOS
Output
example.py:1: DeprecationWarning: scipy.misc is deprecated and will be removed in 2.0.0
  from scipy import misc
Traceback (most recent call last):
  ...
AttributeError: module 'scipy.misc' has no attribute 'imread'

Or, with a direct import:

old_tutorial.py
from scipy.misc import imread, imresize
Tested on ✓ Linux ✓ Windows ✓ macOS
Output
ImportError: cannot import name 'imread' from 'scipy.misc' (/home/you/.venv/lib/python3.13/site-packages/scipy/misc/__init__.py)

Both come from SciPy 1.18 here. On current SciPy, the whole scipy.misc module is deprecated and empty, so installing an older SciPy is not a real option either: versions that still had imread do not install on current Python.

imread

imread_replacement.py
import numpy as np
from PIL import Image

img = np.asarray(Image.open("photo.jpg"))                     # misc.imread(path)
gray = np.asarray(Image.open("photo.jpg").convert("L"))       # misc.imread(path, mode="L")
flat = np.asarray(Image.open("photo.jpg").convert("F"))       # misc.imread(path, flatten=True)
print(img.dtype, img.shape)
print(gray.dtype, gray.shape)
print(flat.dtype, flat.shape, f"{flat.max():.1f}")
Tested on ✓ Linux ✓ Windows ✓ macOS
Output
uint8 (1067, 1600, 3)
uint8 (1067, 1600)
float32 (1067, 1600) 254.8

mode="L" and flatten=True map to Pillow conversions: "L" gives 8-bit greyscale, "F" gives 32-bit float greyscale in the 0–255 range, which is what flatten=True returned.

Which reader returns what

Three libraries can read images into NumPy. They agree on JPEGs, not on PNGs:

compare_readers.py
import imageio.v3 as iio
import matplotlib.pyplot as plt
import numpy as np
from PIL import Image

print(f"{'file':<10} {'Pillow':<14} {'imageio':<14} {'plt.imread'}")
for f in ["photo.jpg", "photo.png", "logo.png"]:
    a, b, c = np.asarray(Image.open(f)), iio.imread(f), plt.imread(f)
    print(f"{f:<10} {str(a.dtype):<14} {str(b.dtype):<14} {c.dtype}, max {c.max()}")
Tested on ✓ Linux ✓ Windows ✓ macOS
Output
file       Pillow         imageio        plt.imread
photo.jpg  uint8          uint8          uint8, max 255
photo.png  uint8          uint8          float32, max 1.0
logo.png   uint8          uint8          float32, max 1.0

matplotlib.pyplot.imread returns PNGs as float32 in 0–1 but JPEGs as uint8 in 0–255. Code that thresholds at 128 works on one and silently fails on the other. Pillow and imageio both return uint8, like the old SciPy function did.

imresize

The old function took a scale factor, a percentage, or a size in NumPy order (rows first). Pillow's resize() takes (width, height). Passing a NumPy-style shape straight to Pillow transposes the size without any error, so this replacement keeps the old convention:

imresize_replacement.py
import numpy as np
from PIL import Image

RESAMPLE = {"nearest": Image.NEAREST, "bilinear": Image.BILINEAR, "bicubic": Image.BICUBIC, "lanczos": Image.LANCZOS}


def imresize(arr, size, interp="bilinear"):
    """Replacement for scipy.misc.imresize for uint8 images.

    size: a float (scale factor), an int (percentage) or a tuple (rows, cols): height first, like NumPy."""
    img = Image.fromarray(arr)
    if isinstance(size, float):
        size = (round(arr.shape[0] * size), round(arr.shape[1] * size))
    elif isinstance(size, int):
        size = (round(arr.shape[0] * size / 100), round(arr.shape[1] * size / 100))
    rows, cols = size[:2]
    return np.asarray(img.resize((cols, rows), RESAMPLE[interp]))   # Pillow wants (width, height)


a = np.asarray(Image.open("photo.jpg"))
print(imresize(a, 0.5).shape)
print(imresize(a, 25).shape)
print(imresize(a, (300, 400)).shape)        # 300 rows x 400 columns
Tested on ✓ Linux ✓ Windows ✓ macOS
Output
(534, 800, 3)
(267, 400, 3)
(300, 400, 3)

Two more differences: imresize always returned uint8, and its default filter was bilinear. If you are porting code that compares results numerically, keep interp="bilinear"; for new code, "lanczos" looks better when shrinking (see resizing).

imsave and toimage

These rescaled any array to the full 0–255 range before saving. Image.fromarray() does not, so data in 0–1 comes out black, and float colour (3-channel) arrays raise TypeError: Cannot handle this data type:

imsave_replacement.py
import numpy as np
from PIL import Image

depth = np.linspace(0.0, 1.0, 200 * 300, dtype=np.float32).reshape(200, 300)   # e.g. a model output in 0..1

# misc.imsave / misc.toimage stretched the data to 0..255 automatically ("bytescale")
lo, hi = depth.min(), depth.max()
scaled = ((depth - lo) / (hi - lo) * 255).round().astype(np.uint8)
Image.fromarray(scaled).save("depth.png")
print("saved", Image.open("depth.png").getextrema())

# Without the scaling step, the same values become an almost black image:
print("unscaled:", Image.fromarray((depth).astype(np.uint8)).getextrema())
Tested on ✓ Linux ✓ Windows ✓ macOS
Output
saved (0, 255)
unscaled: (0, 1)

For float and 16-bit data, see Cannot handle this data type. misc.face() and misc.ascent(), the sample images, moved to scipy.datasets, which downloads them on first use and needs the pooch package.

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.