Description:
The morphological closing must be extensive, and the opening anti-extensive. This means that, applying the closing to an image, the result is either equal or larger than the input; for the opening it's either equal or smaller.
scikit-image's operators do not satisfy these properties because the kernel is not mirrored in between the dilation and erosion operations that compose the closing and opening.
Working on #6665, I ran into the following comment:
# Inside ndi.grey_dilation, the footprint is inverted,
# e.g. `footprint = footprint[::-1, ::-1]` for 2D [1]_, for reasons unknown
# to this author (@jni). To "patch" this behaviour, we invert our own
# footprint before passing it to `ndi.grey_dilation`.
This worried me, because the reason for this mirroring is exactly so that the composition of erosion and dilation lead to a correct closing and opening. Looking at the code for the opening and closing, it indeed does not mirror the structuring element.
There are two different definitions for morphological operators:
- The dilation and erosion are implemented as in ndimage, where one of the two operators mirrors the structuring element. The closing and opening are defined as the composition of the two using the same structuring element.
- The dilation and erosion are implemented as in skimage, where neither operator mirrors the structuring element. The closing and opening are defined as the composition of the two, with one of them using the mirrored structuring element.
scikit-image, by undoing the mirroring in ndimage's dilation, puts itself in definition 2, but implements the closing and opening as in definition 1, leading to incorrect operators.
There are two possible solutions:
- remove the mirroring in the dilation, or
- add a mirroring in the opening and the closing.
Note that the binary version of these functions is correct. Picking solution 1 would make the gray-scale and binary versions of the dilation consistent (currently one mirrors the structuring element and the other doesn't).
Way to reproduce:
import skimage
import numpy as np
# A test image
img = np.random.rand(300, 300)
img = skimage.filters.gaussian(img, 5)
img = (img > 0.5).astype(np.uint8)
img *= 255
# A non-symmetric SE
se = np.array([[0,0,1],[0,1,1],[1,1,1]])
# Closing must be extensive, but it isn't
closing = skimage.morphology.closing(img, se)
assert(np.all(closing >= img))
# Opening must be anti-extensive, but it isn't
opening = skimage.morphology.opening(img, se)
assert(np.all(img >= opening))
# Again for the binary case
img = img > 0
# Closing must be extensive, it is
closing = skimage.morphology.binary_closing(img, se)
assert(np.all(closing >= img))
# Opening must be anti-extensive, it is
opening = skimage.morphology.binary_opening(img, se)
assert(np.all(img >= opening))
Version information:
3.10.9 (main, Dec 15 2022, 10:44:50) [Clang 14.0.0 (clang-1400.0.29.202)]
macOS-12.6.2-arm64-arm-64bit
scikit-image version: 0.19.3
numpy version: 1.23.5
Description:
The morphological closing must be extensive, and the opening anti-extensive. This means that, applying the closing to an image, the result is either equal or larger than the input; for the opening it's either equal or smaller.
scikit-image's operators do not satisfy these properties because the kernel is not mirrored in between the dilation and erosion operations that compose the closing and opening.
Working on #6665, I ran into the following comment:
This worried me, because the reason for this mirroring is exactly so that the composition of erosion and dilation lead to a correct closing and opening. Looking at the code for the opening and closing, it indeed does not mirror the structuring element.
There are two different definitions for morphological operators:
scikit-image, by undoing the mirroring in ndimage's dilation, puts itself in definition 2, but implements the closing and opening as in definition 1, leading to incorrect operators.
There are two possible solutions:
Note that the binary version of these functions is correct. Picking solution 1 would make the gray-scale and binary versions of the dilation consistent (currently one mirrors the structuring element and the other doesn't).
Way to reproduce:
Version information: