Documentation Menu

Integration Guide

Django

Django's ImageField depends on Pillow for image validation. For more advanced tasks like generating thumbnails or watermarking on upload, use Django signals or a dedicated library like django-imagekit.

Django: Process Upload
# models.py from django.db import models class Photo(models.Model): image = models.ImageField(upload_to="photos/") # signals.py — auto-generate thumbnail on upload from django.db.models.signals import post_save from PIL import Image import os def create_thumbnail(sender, instance, **kwargs): if instance.image: with Image.open(instance.image.path) as img: img.thumbnail((300, 300), Image.Resampling.LANCZOS) thumb_path = instance.image.path.replace("/photos/", "/thumbs/") img.save(thumb_path) post_save.connect(create_thumbnail, sender=Photo)

FastAPI

FastAPI's file upload handling works seamlessly with Pillow via UploadFileBytesIOImage.open().

FastAPI Image Upload Handler
from fastapi import FastAPI, UploadFile, File from fastapi.responses import StreamingResponse from PIL import Image import io app = FastAPI() @app.post("/resize") async def resize_image(file: UploadFile = File(...)): contents = await file.read() img = Image.open(io.BytesIO(contents)) img.thumbnail((800, 800), Image.Resampling.LANCZOS) buf = io.BytesIO() img.save(buf, format="WEBP", quality=85) buf.seek(0) return StreamingResponse(buf, media_type="image/webp")

Flask

Flask Upload + Process
from flask import Flask, request, send_file from PIL import Image import io app = Flask(__name__) @app.post("/convert") def convert(): file = request.files["image"] img = Image.open(file.stream).convert("RGB") buf = io.BytesIO() img.save(buf, "JPEG", quality=90) buf.seek(0) return send_file(buf, mimetype="image/jpeg")

NumPy

Pillow and NumPy arrays can be converted freely. This allows you to apply arbitrary mathematical operations on pixel data and then render back to an image.

Pillow ↔ NumPy
import numpy as np from PIL import Image # PIL → NumPy array img = Image.open("photo.jpg") arr = np.array(img) print(arr.shape) # (height, width, 3) for RGB # Apply NumPy operations inv = 255 - arr # invert all values # NumPy array → PIL Image result = Image.fromarray(inv.astype(np.uint8)) result.save("inverted.jpg")

PyTorch / torchvision (AI & Deep Learning)

PyTorch uses Pillow as its default image loader. torchvision.transforms natively operates directly on PIL Images for data augmentation and neural network preprocessing pipelines.

torchvision Data Augmentation & Tensor Pipeline
from torchvision import transforms from PIL import Image # 1. Training Augmentation Pipeline (PIL → Tensor) train_transform = transforms.Compose([ transforms.RandomResizedCrop(224, scale=(0.8, 1.0)), transforms.RandomHorizontalFlip(p=0.5), transforms.ColorJitter(brightness=0.2, contrast=0.2), transforms.ToTensor(), # PIL Image [0, 255] → FloatTensor [0.0, 1.0] transforms.Normalize( mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225] ) ]) pil_img = Image.open("training_sample.jpg") tensor = train_transform(pil_img) print(tensor.shape) # torch.Size([3, 224, 224]) # 2. Reverse: Tensor → PIL Image for inspection/saving to_pil = transforms.ToPILImage() reconstructed_img = to_pil(tensor) reconstructed_img.save("inspected.png")

OpenCV

OpenCV and Pillow use different colour channel orders. OpenCV is BGR, Pillow is RGB. You must convert when passing data between libraries.

Pillow ↔ OpenCV
import cv2 import numpy as np from PIL import Image # Pillow → OpenCV (RGB → BGR) pil_img = Image.open("photo.jpg") cv_img = cv2.cvtColor(np.array(pil_img), cv2.COLOR_RGB2BGR) # OpenCV → Pillow (BGR → RGB) cv_img = cv2.imread("photo.jpg") pil_img = Image.fromarray(cv2.cvtColor(cv_img, cv2.COLOR_BGR2RGB))

Tesseract OCR + OpenCV + Pillow Pipeline

The standard Python Computer Vision stack for document intelligence and OCR projects combines all three libraries:

  • OpenCV: High-speed image pre-processing (grayscale conversion, Otsu thresholding, noise removal).
  • Tesseract OCR (PyTesseract): Optical Character Recognition engine to extract text strings and word coordinate bounding boxes (test images online with the Tesseract Online OCR Tool).
  • Pillow: High-fidelity TrueType text overlays, bounding box annotations (ImageDraw), and final multi-format export.
Pillow + OpenCV + Tesseract OCR Pipeline
import cv2 import numpy as np import pytesseract from PIL import Image, ImageDraw, ImageFont # Step 1: Pre-process with OpenCV (Grayscale + Adaptive Otsu Thresholding) cv_img = cv2.imread("receipt.png") gray = cv2.cvtColor(cv_img, cv2.COLOR_BGR2GRAY) thresh = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY | cv2.THRESH_OTSU)[1] # Step 2: Extract text and bounding boxes with PyTesseract ocr_data = pytesseract.image_to_data(thresh, output_type=pytesseract.Output.DICT) # Step 3: Convert OpenCV array back to Pillow Image for annotation pil_img = Image.fromarray(cv2.cvtColor(cv_img, cv2.COLOR_BGR2RGB)) draw = ImageDraw.Draw(pil_img) for i in range(len(ocr_data["text"])): confidence = int(ocr_data["conf"][i]) text = ocr_data["text"][i].strip() if confidence > 50 and text: x, y, w, h = ocr_data["left"][i], ocr_data["top"][i], ocr_data["width"][i], ocr_data["height"][i] # Draw bounding box draw.rectangle([x, y, x + w, y + h], outline="lime", width=2) # Overlay detected label draw.text((x, max(0, y - 14)), text, fill="lime") pil_img.save("ocr_annotated.png")

Tkinter

Tkinter's native PhotoImage only supports GIF and PGM. Use ImageTk.PhotoImage from Pillow to display any image format in a Tkinter window.

Tkinter Display
import tkinter as tk from PIL import Image, ImageTk root = tk.Tk() img = Image.open("photo.jpg").resize((400, 300)) photo = ImageTk.PhotoImage(img) label = tk.Label(root, image=photo) label.pack() root.mainloop()

Qt (PyQt5 / PySide6)

Convert Pillow images to Qt's QImage via NumPy as an intermediate layer.

Pillow → QImage
from PyQt5.QtGui import QImage, QPixmap from PIL import Image import numpy as np img = Image.open("photo.jpg").convert("RGBA") data = np.array(img) h, w, ch = data.shape qimage = QImage(data.tobytes(), w, h, ch * w, QImage.Format_RGBA8888) pixmap = QPixmap.fromImage(qimage)