-
-
Notifications
You must be signed in to change notification settings - Fork 2.4k
Added documentation for ImageMorph module #9302
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 8 commits
f06d2f7
aa33633
eb0e973
76b1115
feb6955
961447b
b249a2a
8fa1c8c
1f0e4ed
0ae5091
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -4,10 +4,144 @@ | |||||||||||||||||||||
| :py:mod:`~PIL.ImageMorph` module | ||||||||||||||||||||||
| ================================ | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| The :py:mod:`~PIL.ImageMorph` module provides morphology operations on images. | ||||||||||||||||||||||
| The :py:mod:`~PIL.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. | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| .. automodule:: PIL.ImageMorph | ||||||||||||||||||||||
| ``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 :class:`LutBuilder`. The | ||||||||||||||||||||||
| resulting LUTs can then be applied to an image using :class:`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” | ||||||||||||||||||||||
| * ``-`` — “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 | ||||||||||||||||||||||
| :class:`LutBuilder`. | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
|
|
||||||||||||||||||||||
| Using :class:`LutBuilder` | ||||||||||||||||||||||
| ------------------------- | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| The :class:`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 created by specifying | ||||||||||||||||||||||
| preset names (``"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() | ||||||||||||||||||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This example doesn't work. It fails with It is, for one thing, missing the 4/N/1/M operation. It feels like you haven't read Lines 51 to 60 in 00e2198
If you haven't read the source code, that's kind of a red flag with a documentation suggestion. Could you explain how you came up with this code? |
||||||||||||||||||||||
|
|
||||||||||||||||||||||
| You can inspect, save, or reuse the LUT with :meth:`LutBuilder.get_lut`, | ||||||||||||||||||||||
| :meth:`MorphOp.load_lut`, or :meth:`MorphOp.save_lut`. | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
|
|
||||||||||||||||||||||
| Applying morphology with :class:`MorphOp` | ||||||||||||||||||||||
| ----------------------------------------- | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| Once a LUT is created, the :class:`MorphOp` class applies it to an image. The | ||||||||||||||||||||||
| :meth:`MorphOp.apply` method performs the morphological operation and returns | ||||||||||||||||||||||
| a tuple ``(count, out_image)`` where: | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| * ``count`` is the number of pixels that changed, and | ||||||||||||||||||||||
| * ``out_image`` is 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 :meth:`MorphOp.match` to check where a pattern | ||||||||||||||||||||||
| matches without modifying the image, and :meth:`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 :class:`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) | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| .. autoclass:: LutBuilder | ||||||||||||||||||||||
| :members: | ||||||||||||||||||||||
| :undoc-members: | ||||||||||||||||||||||
| :show-inheritance: | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| .. autoclass:: MorphOp | ||||||||||||||||||||||
| :members: | ||||||||||||||||||||||
| :undoc-members: | ||||||||||||||||||||||
| :show-inheritance: | ||||||||||||||||||||||
| :noindex: | ||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@johnnygitgud considering that our existing documentation says
what made you think that
-was the ignore value?