Generic local filters
Most filters provided by the LocalFilters
package are implemented by the generic in-place method localfilter!
or its out-of-place version `localfilter.
The localfilter!
method
A local filtering operation can be performed by calling the localfilter!
method:
localfilter!(dst, A, B, initial, update, final)
where dst
is the destination, A
is the source, B
defines the neighborhood, initial
is a function or the initial value of the state variable, update
is a function to update the state variable for each entry of the neighborhood, and final
is a function to yield the local result of the filter given the final value of the state variable. The localfilter!
method returns the destination dst
.
The purpose of arguments initial
, update
, and final
is explained by the following pseudo-code implementing the local filtering:
@inbounds for i ∈ indices(dst)
v = initial isa Function ? initial(A[i]) : initial
for j ∈ indices(A) ∩ (indices(B) + i)
v = update(v, A[j], B[j-i])
end
dst[i] = final(v)
end
where indices(A)
denotes the set of indices of A
while indices(B) + i
denotes the set of indices j
such that j - i ∈ indices(B)
. In other words, j ∈ indices(A) ∩ (indices(B) + i)
means all indices j
such that j ∈ indices(A)
and j - i ∈ indices(B)
, hence A[j]
and B[j-i]
are both in-bounds. In localfilter!
, indices i
and j
are Cartesian indices for multi-dimensional arrays, thus indices(A)
is the analogous of CartesianIndices(A)
in Julia in that case. For vectors, indices i
and j
are linear indices.
The behavior of the filter is fully determined by the neighborhood B
(see Section Neighborhoods, structuring elements, and kernels) and by the other arguments to deal with the state variable v
:
initial
may be a function, in which case the state variable is initially given byv = initial(A[i])
; otherwise,initial
is assumed to be the initial value of the state variable. As a consequence, ifinitial
is a function,dst
andA
must have the same axes.update(v, a, b)
yields the updated state variablev
witha = A[j]
andb = B[j-i]
.final(v)
yields the result of the filter given the state variablev
at the end of the loop on the neighborhood. If not specified,final = identity
is assumed.
The localfilter!
method takes a keyword order
to specify the ordering of the filter. By default, order = FORWARD_FILTER
which is implemented by the above pseudo-code and which corresponds to a correlation for a shift-invariant linear filter. The other possibility is order = REVERSE_FILTER
which corresponds to a convolution for a shift-invariant linear filter and which amounts to:
@inbounds for i ∈ indices(dst)
v = initial isa Function ? initial(A[i]) : initial
for j ∈ indices(A) ∩ (i - indices(B))
v = update(v, A[j], B[i-j])
end
dst[i] = final(v)
end
If B
is symmetric, in the sense that B[-j] = B[j]
for any in-bounds j
, both orderings yield the same result but FORWARD_FILTER
is generally faster which is why it is used by default.
The localfilter
method
The localfilter
method is similar to localfilter!
except that it allocates the destination:
localfilter(A, B, initial, update, final)
is the same as:
localfilter!(similar(A), A, B, initial, update, final)
The element type T
of the destination can however be specified as the first argument:
localfilter(T, A, B, initial, update, final)
which is the same as:
localfilter!(similar(A, T), A, B, initial, update, final)
Examples
Implementing a local minimum filter (that is, an erosion) with localfilter
is as simple as:
res = localfilter(A, B,
#= initial =# typemax(eltype(A)),
#= update =# (v,a,b) -> min(v,a))
This is typically how Basic morphological operations are implemented in LocalFilters
. Here B
is only used to define the neighborhood, it is usually called a structuring element in this context.
As another example, implementing a convolution of A
by B
writes:
res = localfilter(similar(A), A, B,
#= initial =# zero(eltype(A)),
#= update =# (v,a,b) -> v + a*b; order = REVERSE_FILTER)
while:
dst = localfilter!(similar(A), A, B,
#= initial =# zero(eltype(A)),
#= update =# (v,a,b) -> v + a*b; order = FORWARD_FILTER)
computes a correlation of A
by B
. The only difference is the order
keyword which may be omitted in the latter case as FORWARD_FILTER
is the default. In the case of convolutions and correlations, B
defines the neighborhood but also the weights, it is usually called a kernel in this context.
Apart from specializations to account for the type of neighborhood defined by B
, it is essentially the way the correlate
and convolve!
methods (described in the Section Linear filters) are implemented in LocalFilters
.
In the above examples, the initial
value of the state variable is always the same and directly provided while the default final = identity
is assumed. Below is a more involved example to compute the roughness defined by Wilson et al. (in Marine Geodesy 30:3-35, 2007) as the maximum absolute difference between a central cell and surrounding cells:
function roughness(A::AbstractArray{<:Real}, B=3)
initial(a) = (; result = zero(a), center = a)
update(v, a, _) = (; result = max(v.result, abs(a - v.center)), center=v.center)
final(v) = v.result
return localfilter!(similar(A), A, B, initial, update, final)
end
This example has been borrowed from the Geomorphometry
package for the analysis of Digital Elevation Models (DEM).