When working with images in a Python notebook I like to visualize them on a grid. Just calling display
is not enough as it renders the images underneath each other. Let's use Matplotlib to generate a grid of PIL images.
Install dependencies
Let's install Pillow and Mathplotlib first:
!pip --disable-pip-version-check --quiet install pillow matplotlib
Download an image into PIL
First, let's create a helper to download images and convert them to PIL images. You'll probably won't need this, but it shows that PIL images are used (instead of files). To make things easier, I'm adding the last part of the URL path as a filename.
import io
from urllib.request import urlopen, Request
from urllib.parse import urlparse
from PIL import Image
from PIL.Image import Image as PilImage
def url_to_pil(url:str) -> PilImage:
headers = { 'User-Agent': 'Mozilla/6.0'}
data = urlopen(Request(url, headers=headers)).read()
bts = io.BytesIO(data)
im = Image.open(bts)
im.filename = urlparse(url).path
return im
We can now map a bunch or URLs to PIL images like this:
urls = [
"https://images.wehkamp.nl/i/wehkamp/16420094_eb_01",
"https://images.wehkamp.nl/i/wehkamp/16454613_mb_01",
"https://images.wehkamp.nl/i/wehkamp/16454613_mb_01",
"https://images.wehkamp.nl/i/wehkamp/16179041_mb_01",
"https://images.wehkamp.nl/i/wehkamp/16392908_mb_01",
"https://images.wehkamp.nl/i/wehkamp/16430906_eb_01",
"https://images.wehkamp.nl/i/wehkamp/16433387_eb_01",
"https://images.wehkamp.nl/i/wehkamp/16439873_eb_02",
"https://images.wehkamp.nl/i/wehkamp/16353572_eb_04",
"https://images.wehkamp.nl/i/wehkamp/16441968_eb_04",
"https://images.wehkamp.nl/i/wehkamp/16443748_eb_03"]
images = [url_to_pil(u) for u in urls]
Display images helper
Let's create a helper function that will plot the images:
import matplotlib.pyplot as plt
from PIL.Image import Image as PilImage
import textwrap, os
def display_images(
images: [PilImage],
columns=5, width=20, height=8, max_images=15,
label_wrap_length=50, label_font_size=8):
if not images:
print("No images to display.")
return
if len(images) > max_images:
print(f"Showing {max_images} images of {len(images)}:")
images=images[0:max_images]
height = max(height, int(len(images)/columns) * height)
plt.figure(figsize=(width, height))
for i, image in enumerate(images):
plt.subplot(int(len(images) / columns + 1), columns, i + 1)
plt.imshow(image)
if hasattr(image, 'filename'):
title=image.filename
if title.endswith("/"): title = title[0:-1]
title=os.path.basename(title)
title=textwrap.wrap(title, label_wrap_length)
title="\n".join(title)
plt.title(title, fontsize=label_font_size);
The width and height are in inches, so you might want to specify different values based on your need. Now let's call the helper for our image array:
display_images(images)
Which results in the following plot image:
How sweet is that? I've added the notebook to GitHub.
Changelog
2021-04-15: Added the os
import. Fixed the mathplotlib warning of the sub plot.
2020-05-08: Fixed bug with displaying a list of images that is less than halve the number of columns. Now making sure there is at least 1 row rendered if we have images.
2020-05-07: Added labels with the filename to the grid. This makes it easier to distinguish between images. Added option for wrapping long image names. This is especially useful if file names are long.
It’s nice if you can include the entire code as a file or a github link. Also, you’re using os without importing it. Also, on python 3.8 this results in no window of images popping up (I can plot normally with matplotlib in other scripts).
Thanks for your feedback, I’ve added the missing code and a link to the notebook itself: https://github.com/KeesCBakker/blogs/blob/master/plotting-a-grid-of-pil-images-in-jupyter/main.ipynb
I’m not sure if I understand what you are looking for. In my notebook it is just rendered as a cell. If I click on the top-left icon, I get a new tab in my VS Code with the image: https://uploads.disquscdn.com/images/bd78d86896bc784a578e2addff4e326b4208ed506afa6bb83688050ef4e84dfa.png