Neighborhoods, structuring elements, and kernels
Definitions
Neighborhoods (a.k.a. structuring elements in mathematical morphology) and kernels are central concepts in LocalFilters
. The neighborhood defines which values are involved in a local operation for each output value of the filter. Neighborhoods are assumed to be shift invariant but may have any support shape and may have embedded weights (e.g., to implement local convolution). In this latter case, they are called kernels.
In LocalFilters
, a filtering operation, say
dst = filter(A, B)
involves, at each index i
of a source array A
, the values A[i±k]
of A
for all indices k
of B
and where:
i ± k = i + k
for operations like correlations whereA
andB
can both be accessed in forward order of their indices (which is generally faster);i ± k = i - k
for operations like convolutions where one ofA
orB
must be accessed in reverse order of its indices (which is generally slower).
In LocalFilters
, the following terminology is used for B
:
B
is called a neighborhood or a structuring element for mathematical morphology operations (see Section Non-linear morphological filters) if its purpose is to define the indices in the source relatively to a given index in the destination. Such neighborhoods are represented by arrays with Boolean entries (true
where entries are part of the neighborhood) and with as many dimensions as the source arrayA
.B
is called an hyper-rectangular box if it is a sliding window whose edges are aligned with the Cartesian axes of the array. Such simple neighborhoods are like the previous ones but with all entries equal totrue
, they are most efficiently represented by fast uniform arrays likeFastUniformArray{Bool,N}
instances from theStructuredArrays
package. Another advantage of hyper-rectangular boxes is that they define a separable structuring element which may be exploited for very fast morphological operations by the van Herk-Gil-Werman algorithm whose numerical complexity does not depend on the size of the neighborhood (seelocalfilter!
).B
is called a kernel when its values are combined by the filter with those of the source. This is typically the case of discrete convolutions and correlations. Kernels are represented byAbstractArray{T,N}
instances, withT
the numerical type of the weights andN
the number of dimensions.
From the user point of view, B
whether it is a neighborhood, a structuring element, an hyper-rectangular box, a sliding window, or a kernel is thus just an array (usually of small size) with the same number of dimensions as the source array A
In addition, B
may have arbitrary index offsets, to make it centered for example. For hyper-rectangular neighborhoods, FastUniformArray
instances (from the StructuredArrays
package) may directly have custom index offsets. For other neighborhoods and kernels, offset arrays (from the OffsetArrays
package) can be used to implement such offsets.
Simple rules for specifying neighborhoods and kernels
To facilitate specifying N
-dimensional neighborhoods and kernels in the filters provided by LocalFilters
, the following rules are applied to convert argument B
to a suitable neighborhood or kernel:
If
B
is anN
-dimensional array, it is used unchanged. Hyper-rectangular boxes or symmetries may however be automatically detected to choose the fastest filtering method.If
B
is a 2-tuple ofN
-dimensional Cartesian indices, say,(I_first,I_last)
each of typeCartesianIndex{N}
, then the corresponding neighborhood is an hyper-rectangular box whose first and last indices areI_first
andI_last
.If
B
is aN
-dimensional Cartesian range of type of typeCartesianIndices{N}
, then the corresponding neighborhood is an hyper-rectangular box whose first and last indices are given by this Cartesian range.If
B
is an integer or an integer-valued unit range, it is interpreted as the length or the range of indices of the neighborhood along each dimension. In the case of a simple length, say,len
, the index range of the neighborhood will be approximately centered using the same conventions as forfftshift
:-i:i
for odd lengths and-i:i-1
for even ones and withi = len ÷ 2
.Otherwise
B
may be specified as aN
-tuple of lengths or ranges (the two can be mixed), one for each dimension and where lengths are interpreted as in the previous case.
Note that all these cases except the first one correspond to hyper-rectangular boxes. The default neighborhood is B = 3
which corresponds to a centered hyper-rectangular sliding window of width 3 in each of its dimensions.
This assumed conversion may be explicitly performed by calling the kernel
method:
kernel(Dims{N}, B)
yields the N
-dimensional neighborhood or kernel corresponding to B
. If the number N
of dimensions can be inferred from B
, argument Dims{N}
can be omitted.
Simple operations on kernels
If B
is an N
-dimensional array representing a neighborhood or a kernel, its indices may be centered by calling the LocalFilters.centered
method:
C = LocalFilters.centered(B)
which yields an array C
with the same dimensions and values as B
but with offset axes. This even work for arrays already centered or with offset axes that are not centered. Note that this method follows the same conventions as for fftshift
(explained above) and has thus a slightly different semantic than the OffsetArrays.centered
method which assumes that the centered range of an even dimension 1-i:i
instead of -i:i-1
.
It may be convenient to reverse a kernel or a neighborhood, this is done by calling the reverse_kernel
method:
R = reverse_kernel(B)
which yields an array R
with the same dimensions and values as B
but such that R[i] = B[-i]
for any index i
such that -i
is in-bounds for B
. This may be useful to perform a discrete convolution by the kernel B
by a discrete correlation by R
which is usually faster. Note that the reverse_kernel
method reverses the order of the values of B
as the base reverse
method but also negates the axis ranges.
Hyper-balls
To build a neighborhood, or a structuring element that is a N
-dimensional hyper-ball of radius r
call:
LocalFilters.ball(Dims{N}, r)
For instance:
julia> B = LocalFilters.ball(Dims{2}, 3.5)
7×7 OffsetArray(::Matrix{Bool}, -3:3, -3:3) with eltype Bool with indices -3:3×-3:3:
0 0 1 1 1 0 0
0 1 1 1 1 1 0
1 1 1 1 1 1 1
1 1 1 1 1 1 1
1 1 1 1 1 1 1
0 1 1 1 1 1 0
0 0 1 1 1 0 0
This neighborhood is geometrically centered thanks to offset axes, to have a 1-based indices, you can do:
julia> B = LocalFilters.ball(Dims{2}, 3.5).parent
7×7 Matrix{Bool}:
0 0 1 1 1 0 0
0 1 1 1 1 1 0
1 1 1 1 1 1 1
1 1 1 1 1 1 1
1 1 1 1 1 1 1
0 1 1 1 1 1 0
0 0 1 1 1 0 0