ImageMorph module¶
The ImageMorph module provides morphological operations for
binary and grayscale images. Morphology is a family of image-processing
techniques based on the shape and structure of regions in an image. Basic
uses are dilation, erosion, edge detection, and hit or miss pattern
matching.
ImageMorph works by applying a lookup table (LUT) to a binary representation
of the input image. Patterns used for these operations are defined using small
ASCII masks, which are converted into LUTs through LutBuilder. The
resulting LUTs can then be applied to an image using MorphOp.
This module is useful for tasks such as noise cleanup, detecting specific pixel shapes, extracting boundaries, thinning, or locating features defined by small structuring elements.
Supported image modes¶
Morphological operations in Pillow operate on images in mode "L" (8-bit
grayscale). A nonzero pixel is treated as “on”, and a zero-valued pixel as
“off”. To apply morphology to a binary image, ensure that the image is first
converted to mode "L":
im = im.convert("L")
Defining structuring element patterns¶
A structuring pattern is defined using a small ASCII mask consisting of the characters:
1— pixel must be “on”0— pixel must be “off”.orX— “don’t care” value (ignored during matching)
For example, this mask detects a 2×2 corner shape:
pattern = [
"10",
"11",
]
Multiple patterns can be combined into a single LUT. Patterns must all be the
same size, and Pillow builds a lookup table from them using
LutBuilder.
Using LutBuilder¶
The LutBuilder class constructs a LUT that defines how a morphological
operation should behave. A LUT maps every possible 3×3 neighborhood around a
pixel to an output pixel value (either “on” or “off”).
Basic uses like dilation and erosion can be achieved by specifying preset operation
names ("corner", "dilation4", "dilation8", "erosion4", "erosion8",
"edge"), or you may define custom patterns.
For example, creating a LUT for a 2×2 corner detector:
from PIL import ImageMorph
patterns = [
"10",
"11",
]
lb = ImageMorph.LutBuilder(op_name="corner")
lb.add_patterns(patterns)
lut = lb.build_lut()
You can inspect, save, or reuse the LUT with LutBuilder.get_lut(),
MorphOp.load_lut(), or MorphOp.save_lut().
Applying morphology with MorphOp¶
Once a LUT is created, the MorphOp class applies it to an image. The
MorphOp.apply() method performs the morphological operation and returns
a tuple (count, out_image) where:
countis the number of pixels that changed, andout_imageis the resulting processed image.
Example: applying a simple dilation operation:
from PIL import Image, ImageMorph
with Image.open("input.png") as im:
im = im.convert("L")
# Built-in 8-connected dilation
op = ImageMorph.MorphOp(op_name="dilation8")
count, out = op.apply(im)
out.save("dilated.png")
You could also use the method MorphOp.match() to check where a pattern
matches without modifying the image, and MorphOp.get_on_pixels() to
get the coordinates of “on” pixels after pattern matching.
Example: pattern matching without modifying the image:
op = ImageMorph.MorphOp(op_name="edge")
result = op.match(im)
# result is a list of (x, y) coordinates
print("Edge pixels found:", len(result))
Saving and loading LUTs¶
LUTs created by LutBuilder can be serialized and reused later. This
is helpful when repeatedly applying the same pattern in a batch-processing
workflow.
Example:
lb = ImageMorph.LutBuilder(op_name="custom")
lb.add_patterns(patterns)
lb.build_lut()
lb.save_lut("custom.lut")
# Later...
op = ImageMorph.MorphOp()
op.load_lut("custom.lut")
count, out = op.apply(im)
- class PIL.ImageMorph.LutBuilder(patterns: list[str] | None = None, op_name: str | None = None)[source]¶
Bases:
objectA class for building a MorphLut from a descriptive language
The input patterns is a list of a strings sequences like these:
4:(... .1. 111)->1
(whitespaces including linebreaks are ignored). The option 4 describes a series of symmetry operations (in this case a 4-rotation), the pattern is described by:
. or X - Ignore
1 - Pixel is on
0 - Pixel is off
The result of the operation is described after “->” string.
The default is to return the current pixel value, which is returned if no other match is found.
Operations:
4 - 4 way rotation
N - Negate
1 - Dummy op for no other operation (an op must always be given)
M - Mirroring
Example:
lb = LutBuilder(patterns = ["4:(... .1. 111)->1"]) lut = lb.build_lut()
- class PIL.ImageMorph.MorphOp(lut: bytearray | None = None, op_name: str | None = None, patterns: list[str] | None = None)[source]¶
Bases:
objectA class for binary morphological operators
- apply(image: Image) tuple[int, Image][source]¶
Run a single morphological operation on an image
Returns a tuple of the number of changed pixels and the morphed image
- get_on_pixels(image: Image) list[tuple[int, int]][source]¶
Get a list of all turned on pixels in a binary image
Returns a list of tuples of (x,y) coordinates of all matching pixels. See Coordinate system.