How to use dilate in Julia

Hi! I am looking for something similar to python’s dilation function in skimage.morphology. I want to achieve precisely the following functionality:

>>> import numpy as np
>>> from skimage.morphology import dilation
>>> from skimage.morphology import disk

>>> bright_pixel = np.array([[0, 0, 0, 0, 0], 
                             [0, 0, 0, 0, 0], 
                             [0, 0, 1, 0, 0], 
                             [0, 0, 0, 0, 0], 
                             [0, 0, 0, 0, 0]], dtype=np.uint8)
>>> dilation(bright_pixel, disk(1))
array([[0, 0, 0, 0, 0],
       [0, 0, 1, 0, 0],
       [0, 1, 1, 1, 0],
       [0, 0, 1, 0, 0],
       [0, 0, 0, 0, 0]], dtype=uint8)

Can you suggest how to implement the same in Julia?

Images.jl’s morphology tools currently only allow operations on rectangular windows (see https://github.com/JuliaImages/ImageMorphology.jl/issues/11). If a rectangular window is acceptable,

julia> using Images

julia> bright_pixel = zeros(Gray{N0f8}, 5, 5); bright_pixel[3,3] = 1;

julia> dilate(bright_pixel)
5×5 Array{Gray{N0f8},2} with eltype Gray{Normed{UInt8,8}}:
 Gray{N0f8}(0.0)  Gray{N0f8}(0.0)  …  Gray{N0f8}(0.0)  Gray{N0f8}(0.0)
 Gray{N0f8}(0.0)  Gray{N0f8}(1.0)     Gray{N0f8}(1.0)  Gray{N0f8}(0.0)
 Gray{N0f8}(0.0)  Gray{N0f8}(1.0)     Gray{N0f8}(1.0)  Gray{N0f8}(0.0)
 Gray{N0f8}(0.0)  Gray{N0f8}(1.0)     Gray{N0f8}(1.0)  Gray{N0f8}(0.0)
 Gray{N0f8}(0.0)  Gray{N0f8}(0.0)     Gray{N0f8}(0.0)  Gray{N0f8}(0.0)

julia> Int.(ans)
5×5 Array{Int64,2}:
 0  0  0  0  0
 0  1  1  1  0
 0  1  1  1  0
 0  1  1  1  0
 0  0  0  0  0
1 Like

I tried using the same function but I couldn’t find how to specify squares for dilation. For instance, can you tell me how do I modify the above code to get dilation of mode than one pixel on each side?

Sorry, I assumed it allowed rectangular windows, but all you can do is include/exclude dimensions from dilation:

help?> dilate
search: dilate dilate! assert_timedim_last AdaptiveEqualization

  imgd = dilate(img, [region])

  perform a max-filter over nearest-neighbors. The default is 8-connectivity
  in 2d, 27-connectivity in 3d, etc. You can specify the list of dimensions
  that you want to include in the connectivity, e.g., region = [1,2] would
  exclude the third dimension from filtering.

You could instead use mapwindow as follows:

julia> mapwindow(maximum, bright_pixel, (3, 5))
5×5 Array{Gray{N0f8},2} with eltype Gray{Normed{UInt8,8}}:
 Gray{N0f8}(0.0)  Gray{N0f8}(0.0)  …  Gray{N0f8}(0.0)  Gray{N0f8}(0.0)
 Gray{N0f8}(1.0)  Gray{N0f8}(1.0)     Gray{N0f8}(1.0)  Gray{N0f8}(1.0)
 Gray{N0f8}(1.0)  Gray{N0f8}(1.0)     Gray{N0f8}(1.0)  Gray{N0f8}(1.0)
 Gray{N0f8}(1.0)  Gray{N0f8}(1.0)     Gray{N0f8}(1.0)  Gray{N0f8}(1.0)
 Gray{N0f8}(0.0)  Gray{N0f8}(0.0)     Gray{N0f8}(0.0)  Gray{N0f8}(0.0)

julia> Int.(ans)
5×5 Array{Int64,2}:
 0  0  0  0  0
 1  1  1  1  1
 1  1  1  1  1
 1  1  1  1  1
 0  0  0  0  0
1 Like

Thanks for your pointer. I think I can work with this for now. However, do you have an idea how to implement the max filter in case I want to implement it myself? Any pointer in that direction will be helpful.

You can always inspect how Julia’s functions are implemented with @edit, e.g. @edit dilate(bright_pixel). You may want to look at StaticKernels.jl or LoopVectorization.jl if you need a particular dilation region.

julia> using StaticKernels

julia> k = StaticKernels.Kernel{(-1:1, -2:2)}(w -> maximum(Tuple(w)))
Kernel{(-1:1, -2:2)} with window function #13

julia> map(k, extend(bright_pixel, StaticKernels.ExtensionNothing())) .|> Int
5×5 Array{Int64,2}:
 0  0  0  0  0
 1  1  1  1  1
 1  1  1  1  1
 1  1  1  1  1
 0  0  0  0  0

Can you post a MWE? It works fine for me:

julia> bright_pixel = zeros(N0f8, 5300, 3050); bright_pixel[3,3] = 1;

julia> mapwindow(maximum, bright_pixel, (3, 5))
5300×3050 Array{N0f8,2} with eltype Normed{UInt8,8}:
 0.0  0.0  0.0  0.0  0.0  0.0  0.0  0.0  …  0.0  0.0  0.0  0.0  0.0  0.0  0.0
 1.0  1.0  1.0  1.0  1.0  0.0  0.0  0.0     0.0  0.0  0.0  0.0  0.0  0.0  0.0
 1.0  1.0  1.0  1.0  1.0  0.0  0.0  0.0     0.0  0.0  0.0  0.0  0.0  0.0  0.0
 1.0  1.0  1.0  1.0  1.0  0.0  0.0  0.0     0.0  0.0  0.0  0.0  0.0  0.0  0.0
 0.0  0.0  0.0  0.0  0.0  0.0  0.0  0.0     0.0  0.0  0.0  0.0  0.0  0.0  0.0
 0.0  0.0  0.0  0.0  0.0  0.0  0.0  0.0  …  0.0  0.0  0.0  0.0  0.0  0.0  0.0
 0.0  0.0  0.0  0.0  0.0  0.0  0.0  0.0     0.0  0.0  0.0  0.0  0.0  0.0  0.0
 0.0  0.0  0.0  0.0  0.0  0.0  0.0  0.0     0.0  0.0  0.0  0.0  0.0  0.0  0.0
 0.0  0.0  0.0  0.0  0.0  0.0  0.0  0.0     0.0  0.0  0.0  0.0  0.0  0.0  0.0
 0.0  0.0  0.0  0.0  0.0  0.0  0.0  0.0     0.0  0.0  0.0  0.0  0.0  0.0  0.0
 ⋮                        ⋮              ⋱            ⋮
 0.0  0.0  0.0  0.0  0.0  0.0  0.0  0.0  …  0.0  0.0  0.0  0.0  0.0  0.0  0.0
 0.0  0.0  0.0  0.0  0.0  0.0  0.0  0.0     0.0  0.0  0.0  0.0  0.0  0.0  0.0
 0.0  0.0  0.0  0.0  0.0  0.0  0.0  0.0     0.0  0.0  0.0  0.0  0.0  0.0  0.0
 0.0  0.0  0.0  0.0  0.0  0.0  0.0  0.0     0.0  0.0  0.0  0.0  0.0  0.0  0.0
 0.0  0.0  0.0  0.0  0.0  0.0  0.0  0.0     0.0  0.0  0.0  0.0  0.0  0.0  0.0
 0.0  0.0  0.0  0.0  0.0  0.0  0.0  0.0  …  0.0  0.0  0.0  0.0  0.0  0.0  0.0
 0.0  0.0  0.0  0.0  0.0  0.0  0.0  0.0     0.0  0.0  0.0  0.0  0.0  0.0  0.0
 0.0  0.0  0.0  0.0  0.0  0.0  0.0  0.0     0.0  0.0  0.0  0.0  0.0  0.0  0.0
 0.0  0.0  0.0  0.0  0.0  0.0  0.0  0.0     0.0  0.0  0.0  0.0  0.0  0.0  0.0
 0.0  0.0  0.0  0.0  0.0  0.0  0.0  0.0     0.0  0.0  0.0  0.0  0.0  0.0  0.0

I did not assign the value to any variable and was losing the progress. I thought mapwindow changed the array in place. My bad.

If you have binary images you can emulate dilation with linear filtering.

using ImageFiltering
kernel = centered([0 1 0;1 1 1;0 1 0])
dilated = imfilter(bright_pixel, kernel) .> 0
1 Like

There is a dilate function doing exactly what you need in LocalFilters.jl: dilate(array, 5) for a rectangular kernel of size 5, or dilate(array, kernel) for an arbitrary kernel that is represented as an array.

1 Like