From f06d2f7f12f51a32cac2a9a957197bc06b9e9052 Mon Sep 17 00:00:00 2001 From: johnnygitgud Date: Sat, 15 Nov 2025 16:59:28 -0800 Subject: [PATCH 01/10] Added documentation for Image Morph module [ci skip] --- docs/reference/ImageMorph.rst | 144 +++++++++++++++++++++++++++++++++- 1 file changed, 143 insertions(+), 1 deletion(-) diff --git a/docs/reference/ImageMorph.rst b/docs/reference/ImageMorph.rst index 30b89a54df5..ce2562d7df0 100644 --- a/docs/reference/ImageMorph.rst +++ b/docs/reference/ImageMorph.rst @@ -4,7 +4,149 @@ :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. + +`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") + +For best results, use images with values restricted to 0 (black) and 255 +(white), though intermediate grayscale values are also supported. + + +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. + +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 :meth:`LutBuilder.get_lut`, +:meth:`LutBuilder.load_lut`, or :meth:`LutBuilder.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 + + im = Image.open("input.png").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) + + +Relationship to `_imagingmorph` +------------------------------- + +The :mod:`PIL.ImageMorph` module provides a Python interface, while the +low-level implementation of morphological lookup table evaluation is handled +by the C extension module :mod:`_imagingmorph`. Users should interact +with :class:`LutBuilder` and :class:`MorphOp`, not with the C extension +directly. .. automodule:: PIL.ImageMorph :members: From aa336336c3854d365a7a9bbc18dc49e033d4a09f Mon Sep 17 00:00:00 2001 From: Andrew Murray <3112309+radarhere@users.noreply.github.com> Date: Mon, 24 Nov 2025 22:59:15 +1100 Subject: [PATCH 02/10] Use two backticks Co-authored-by: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> --- docs/reference/ImageMorph.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/reference/ImageMorph.rst b/docs/reference/ImageMorph.rst index ce2562d7df0..a123b33059c 100644 --- a/docs/reference/ImageMorph.rst +++ b/docs/reference/ImageMorph.rst @@ -10,7 +10,7 @@ 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 +``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`. From eb0e973749ac211ef309fc4e797a8c7bebea20e0 Mon Sep 17 00:00:00 2001 From: Andrew Murray <3112309+radarhere@users.noreply.github.com> Date: Sat, 29 Nov 2025 22:20:13 +1100 Subject: [PATCH 03/10] Removed _imagingmorph mention Co-authored-by: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> --- docs/reference/ImageMorph.rst | 9 --------- 1 file changed, 9 deletions(-) diff --git a/docs/reference/ImageMorph.rst b/docs/reference/ImageMorph.rst index a123b33059c..0ba2c72cf37 100644 --- a/docs/reference/ImageMorph.rst +++ b/docs/reference/ImageMorph.rst @@ -139,15 +139,6 @@ Example:: count, out = op.apply(im) -Relationship to `_imagingmorph` -------------------------------- - -The :mod:`PIL.ImageMorph` module provides a Python interface, while the -low-level implementation of morphological lookup table evaluation is handled -by the C extension module :mod:`_imagingmorph`. Users should interact -with :class:`LutBuilder` and :class:`MorphOp`, not with the C extension -directly. - .. automodule:: PIL.ImageMorph :members: :undoc-members: From 76b1115d56a7a3e572bfeec2397ec895dc2c3f06 Mon Sep 17 00:00:00 2001 From: Andrew Murray <3112309+radarhere@users.noreply.github.com> Date: Sat, 29 Nov 2025 22:21:15 +1100 Subject: [PATCH 04/10] Updated text Co-authored-by: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> --- docs/reference/ImageMorph.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/reference/ImageMorph.rst b/docs/reference/ImageMorph.rst index 0ba2c72cf37..e9b3e62cbb6 100644 --- a/docs/reference/ImageMorph.rst +++ b/docs/reference/ImageMorph.rst @@ -67,7 +67,7 @@ Basic uses like dilation and erosion can be created by specifying preset names (``"dilation4"``, ``"dilation8"``, ``"erosion4"``, ``"erosion8"``, ``"edge"``), or you may define custom patterns. -Example: creating a LUT for a 2×2 corner detector:: +For example, creating a LUT for a 2×2 corner detector:: from PIL import ImageMorph From feb6955e3c11884b19f8fe9799a02b58755aa866 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 29 Nov 2025 22:33:09 +1100 Subject: [PATCH 05/10] Use context manager --- docs/reference/ImageMorph.rst | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/docs/reference/ImageMorph.rst b/docs/reference/ImageMorph.rst index e9b3e62cbb6..4ce1ae7dd9a 100644 --- a/docs/reference/ImageMorph.rst +++ b/docs/reference/ImageMorph.rst @@ -98,12 +98,13 @@ Example: applying a simple dilation operation:: from PIL import Image, ImageMorph - im = Image.open("input.png").convert("L") + with Image.open("input.png") as im: + im = im.convert("L") - # Built-in 8-connected dilation - op = ImageMorph.MorphOp(op_name="dilation8") + # Built-in 8-connected dilation + op = ImageMorph.MorphOp(op_name="dilation8") - count, out = op.apply(im) + count, out = op.apply(im) out.save("dilated.png") You could also use the method :meth:`MorphOp.match` to check where a pattern From 961447b95d39b552b872056ab06460cf83007762 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 6 Dec 2025 13:54:17 +1100 Subject: [PATCH 06/10] Corrected documentation errors --- docs/reference/ImageMorph.rst | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/docs/reference/ImageMorph.rst b/docs/reference/ImageMorph.rst index 4ce1ae7dd9a..74bae8436df 100644 --- a/docs/reference/ImageMorph.rst +++ b/docs/reference/ImageMorph.rst @@ -81,7 +81,7 @@ For example, creating a LUT for a 2×2 corner detector:: lut = lb.build_lut() You can inspect, save, or reuse the LUT with :meth:`LutBuilder.get_lut`, -:meth:`LutBuilder.load_lut`, or :meth:`LutBuilder.save_lut`. +:meth:`MorphOp.load_lut`, or :meth:`MorphOp.save_lut`. Applying morphology with :class:`MorphOp` @@ -139,9 +139,12 @@ Example:: op.load_lut("custom.lut") count, out = op.apply(im) +.. autoclass:: LutBuilder + :members: + :undoc-members: + :show-inheritance: -.. automodule:: PIL.ImageMorph +.. autoclass:: MorphOp :members: :undoc-members: :show-inheritance: - :noindex: From b249a2a38c69cd2542f168d00215d7393c919a35 Mon Sep 17 00:00:00 2001 From: Andrew Murray <3112309+radarhere@users.noreply.github.com> Date: Mon, 15 Dec 2025 16:02:52 +1100 Subject: [PATCH 07/10] Updated indentation --- docs/reference/ImageMorph.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/reference/ImageMorph.rst b/docs/reference/ImageMorph.rst index 74bae8436df..347c3c883f5 100644 --- a/docs/reference/ImageMorph.rst +++ b/docs/reference/ImageMorph.rst @@ -101,10 +101,10 @@ Example: applying a simple dilation operation:: with Image.open("input.png") as im: im = im.convert("L") - # Built-in 8-connected dilation - op = ImageMorph.MorphOp(op_name="dilation8") + # Built-in 8-connected dilation + op = ImageMorph.MorphOp(op_name="dilation8") - count, out = op.apply(im) + count, out = op.apply(im) out.save("dilated.png") You could also use the method :meth:`MorphOp.match` to check where a pattern From 8fa1c8c575c9fefe2c61c8a435b7cfc6a677499b Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Mon, 22 Dec 2025 10:10:39 +1100 Subject: [PATCH 08/10] Removed redundant text --- docs/reference/ImageMorph.rst | 3 --- 1 file changed, 3 deletions(-) diff --git a/docs/reference/ImageMorph.rst b/docs/reference/ImageMorph.rst index 347c3c883f5..97b1e1a253b 100644 --- a/docs/reference/ImageMorph.rst +++ b/docs/reference/ImageMorph.rst @@ -30,9 +30,6 @@ converted to mode ``"L"``:: im = im.convert("L") -For best results, use images with values restricted to 0 (black) and 255 -(white), though intermediate grayscale values are also supported. - Defining structuring element patterns ------------------------------------- From 1f0e4ed789f4f31024af3cdf7127205603e4efaf Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Mon, 22 Dec 2025 12:58:26 +1100 Subject: [PATCH 09/10] Added missing preset --- docs/reference/ImageMorph.rst | 25 ++++++++++--------------- 1 file changed, 10 insertions(+), 15 deletions(-) diff --git a/docs/reference/ImageMorph.rst b/docs/reference/ImageMorph.rst index 97b1e1a253b..1b76880aa7e 100644 --- a/docs/reference/ImageMorph.rst +++ b/docs/reference/ImageMorph.rst @@ -19,27 +19,25 @@ 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 +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) +* ``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:: @@ -52,17 +50,16 @@ 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”). +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. +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:: @@ -80,7 +77,6 @@ For example, creating a LUT for a 2×2 corner detector:: 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` ----------------------------------------- @@ -106,7 +102,7 @@ Example: applying a simple dilation operation:: 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. +get the coordinates of "on" pixels after pattern matching. Example: pattern matching without modifying the image:: @@ -116,7 +112,6 @@ Example: pattern matching without modifying the image:: # result is a list of (x, y) coordinates print("Edge pixels found:", len(result)) - Saving and loading LUTs ----------------------- From 0ae50918ffc388218e5addba54a180acdf96c012 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Mon, 22 Dec 2025 12:54:46 +1100 Subject: [PATCH 10/10] Corrected ignore value --- docs/reference/ImageMorph.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/reference/ImageMorph.rst b/docs/reference/ImageMorph.rst index 1b76880aa7e..f09df3e1e3a 100644 --- a/docs/reference/ImageMorph.rst +++ b/docs/reference/ImageMorph.rst @@ -37,7 +37,7 @@ characters: * ``1`` — pixel must be "on" * ``0`` — pixel must be "off" -* ``-`` — "don’t care" value (ignored during matching) +* ``.`` or ``X`` — "don’t care" value (ignored during matching) For example, this mask detects a 2×2 corner shape::