vtkImageDilateErode3D elliptical footprint is approximated as a square/cube

vtkImageDilateErode3D uses an elliptical footprint. But, for a 3x3 kernel, it seems that the ellipse is approximated as a square (or cube for 3D). Is this a bug? I would have expected more of a cross (i.e. rounded corners).

Output from the code below:

Input image:
[[  0   0   0   0   0]
 [  0   0   0   0   0]
 [  0   0 255   0   0]
 [  0   0   0   0   0]
 [  0   0   0   0   0]]
Dilated image:
[[  0   0   0   0   0]
 [  0 255 255 255   0]
 [  0 255 255 255   0]
 [  0 255 255 255   0]
 [  0   0   0   0   0]]

Expected output:

Dilated image:
[[  0   0   0   0   0]
 [  0   0 255   0   0]
 [  0 255 255 255   0]
 [  0   0 255   0   0]
 [  0   0   0   0   0]]
import vtk
import numpy as np

# Create a 5x5 image with one bright pixel in the center
size = 5
image = vtk.vtkImageData()
image.SetDimensions(size, size, 1)
image.AllocateScalars(vtk.VTK_UNSIGNED_CHAR, 1)

# Fill background with 0
for y in range(size):
    for x in range(size):
        image.SetScalarComponentFromFloat(x, y, 0, 0, 0)

# Set center pixel to 255
image.SetScalarComponentFromFloat(size // 2, size // 2, 0, 0, 255)

# Dilate image
dilate = vtk.vtkImageDilateErode3D()
dilate.SetInputData(image)
dilate.SetKernelSize(3, 3, 1)
dilate.SetDilateValue(255)
dilate.SetErodeValue(0)
dilate.Update()


# Show input and output arrays
def vtk_to_array(img):
    dims = img.GetDimensions()
    arr = np.zeros((dims[1], dims[0]), dtype=np.uint8)
    for y in range(dims[1]):
        for x in range(dims[0]):
            arr[y, x] = int(img.GetScalarComponentAsFloat(x, y, 0, 0))
    return arr

input_arr = vtk_to_array(image)
output_arr = vtk_to_array(dilate.GetOutput())

print("Input image:")
print(input_arr)
print("\nDilated image:")
print(output_arr)

Both cases are a coarse approximation of an ellipse. If we compute the area, we have

  • true area: pi*1.5^2 = 7.07
  • area of square: 3x3 = 9
  • area of cross: 5

So it seems that a square is a slightly better approximation of the ellipse.

I compared this to scikit-image’s elliptical footprint, and the result matches VTK, so this does seem like the correct approximation.

>>> from skimage.morphology import footprints
>>> footprints.ellipse(1,1)
array([[1, 1, 1],
       [1, 1, 1],
       [1, 1, 1]], dtype=uint8)

However, it would be nice to be able to manually specify the footprint, instead of only having ellipse as an option for these filters.

I haven’t really seen any demand for morphological operations with non-ellipsoidal kernels. A cross-shaped 3x3 kernel could be achieved by changing the interface to allow the diameter to be a non-integer value, so that might be the best approach.

The reason that a radius of 3 pixels gives a 3x3 square is simple math. The corner pixels are at a distance of sqrt(2) from the center, and sqrt(2) = 1.414. This is less than the radius of 1.5 of a 3x3 elliptical kernel, therefore, the corner pixels are considered to be inside the ellipse. A cross could be achieved by specifying any radius with a value greater than 1.0 and less than sqrt(2).