Skip to content

Commit 173890b

Browse files
committed
New methods localmap and localmap!
1 parent 5aea2bf commit 173890b

File tree

5 files changed

+147
-1
lines changed

5 files changed

+147
-1
lines changed

NEWS.md

+4
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@
22

33
## Version 2.1.0
44

5+
* New methods `localmap(f, A, B)` and `localmap!(f, dst, A, B)` yield the result of
6+
applying the function `f` to the values of `A` taken in a neighborhood defined by `B`
7+
around each position in `A` for `localmap` or in `dst` for `localmap!`.
8+
59
* `LocalFilters.ball(DimS{N}, r)` now yields a *centered* `N`-dimensional ball where
610
values are set according to whether the distance to the center is `≤ r`. Compared to the
711
previous versions, add `1//2` to `r` to get a similar shape. The algorithm is faster and

TODO.md

+3
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@
22

33
- Implement other boundary conditions.
44

5+
- `localmap` can extract ROI as a vector or a `N`-dimensional sub-array with
6+
indices relative to the sliding window.
7+
58
- Consistency: more possibilities for `strel` (like building a kernel) and
69
`is_morpho_math_box` should yield `true` for any acceptable arguments for `box`.
710

src/LocalFilters.jl

+3-1
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,8 @@ export
4343
strel,
4444

4545
# Generic filters.
46-
localfilter, localfilter!
46+
localfilter, localfilter!,
47+
localmap, localmap!
4748

4849
using OffsetArrays, StructuredArrays, EasyRanges, TypeUtils
4950
using EasyRanges: ranges
@@ -58,6 +59,7 @@ function bilateralfilter! end
5859
include("types.jl")
5960
include("utils.jl")
6061
include("generic.jl")
62+
include("localmap.jl")
6163
include("linear.jl")
6264
include("morphology.jl")
6365
include("bilateral.jl")

src/localmap.jl

+109
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
"""
2+
localmap(f, A, B=3; eltype=eltype(A), null=zero(eltype), order=FORWARD_FILTER)
3+
4+
for each position in `A`, applies the function `f` to the values of `A` extracted from the
5+
neighborhood defined by `B`.
6+
7+
Keyword `eltype` may be used to specify the the element type `T` of the result.
8+
By default, it is the same as that of `A`.
9+
10+
The function `f` is never called with an empty vector of values. Keyword `null` may be
11+
used to specify the value of the result where the neighborhood is empty. By default, `null
12+
= zero(T)` with `T` the element type of the result.
13+
14+
Keyword `order` specifies the filter direction, `FORWARD_FILTER` by default.
15+
16+
For example, applying a median filter of the 2-dimensional image `img` in a sliding `5×5`
17+
window can be done by:
18+
19+
``` julia
20+
using Statistics
21+
med = localmap(median!, img, 5; eltype=float(Base.eltype(A)), null=NaN)
22+
```
23+
24+
As another example, with argument `f` set to `minimum` or `maximum` respectively yield the
25+
erosion and the dilation of the input array. However [`erode`](@ref) and [`dilate`](@) are
26+
much faster.
27+
28+
"""
29+
function localmap(f::Function, A::AbstractArray{T,N}, B::Window{N};
30+
eltype::Type=T, kwds...) where {T,N}
31+
return localmap!(f, similar(A, eltype), A, B; kwds...)
32+
end
33+
34+
"""
35+
localmap!(f, dst, A, B=3; null=zero(eltype(dst)), order=FORWARD_FILTER)
36+
37+
set each entry of `dst`, to the result of applying the function `f` to the values of `A`
38+
extracted from the neighborhood defined by `B`.
39+
40+
The function `f` is never called with an empty vector of values. Keyword `null` may be
41+
used to specify the value of the result where the neighborhood is empty. By default, `null
42+
= zero(T)` with `T` the element type of the result.
43+
44+
Keyword `order` specifies the filter direction, `FORWARD_FILTER` by default.
45+
46+
"""
47+
function localmap!(f::Function, dst::AbstractArray{<:Any,N},
48+
A::AbstractArray{<:Any,N}, B::Window{N}; kwds...) where {N}
49+
return localmap!(f, dst, A, kernel(Dims{N}, B); kwds...)
50+
end
51+
52+
function localmap!(f::Function, dst::AbstractArray{<:Any,N},
53+
A::AbstractArray{<:Any,N}, B::AbstractArray{Bool,N};
54+
work::AbstractVector = Vector{eltype(A)}(undef, count(B)),
55+
null = zero(eltype(dst)),
56+
order::FilterOrdering = FORWARD_FILTER) where {N}
57+
maxlen = count(B)
58+
if maxlen == 0
59+
fill!(dst, null)
60+
else
61+
sizehint!(work, maxlen; shrink=false)
62+
indices = Indices(dst, A, B)
63+
if maxlen == length(B)
64+
# `B` is a simple box.
65+
indices = Indices(dst, A, B)
66+
@inbounds for i in indices(dst)
67+
J = localindices(indices(A), order, indices(B), i)
68+
len = length(J)
69+
if len > 0
70+
length(work) == len || resize!(work, len)
71+
k = 0
72+
for j in J
73+
work[k += 1] = A[j]
74+
end
75+
k == len || throw(AssertionError("unexpected count"))
76+
dst[i] = f(work)
77+
else
78+
dst[i] = null
79+
end
80+
end
81+
else
82+
# `B` has "holes".
83+
indices = Indices(dst, A, B)
84+
@inbounds for i in indices(dst)
85+
J = localindices(indices(A), order, indices(B), i)
86+
len = min(length(J), maxlen)
87+
if len > 0
88+
length(work) len || resize!(work, len)
89+
k = 0
90+
for j in J
91+
if B[order(i,j)]
92+
work[k += 1] = A[j]
93+
end
94+
end
95+
k len || throw(AssertionError("unexpected count"))
96+
len = k
97+
if len > 0
98+
length(work) == len || resize!(work, len)
99+
dst[i] = f(work)
100+
end
101+
end
102+
if len == 0
103+
dst[i] = null
104+
end
105+
end
106+
end
107+
end
108+
return dst
109+
end

test/runtests.jl

+28
Original file line numberDiff line numberDiff line change
@@ -1086,6 +1086,34 @@ f2(x) = x > 0.5
10861086
end
10871087
end # @testset "Morphology"
10881088

1089+
@testset "`localmap` and `localmap!` ($win, $func)" for win in (:box, :ball, :win),
1090+
func in (:min, :max)
1091+
T = Float32
1092+
dims = (30, 20)
1093+
A = rand(T, dims)
1094+
Aref = copy(A)
1095+
W = win === :box ? 3 :
1096+
win === :ball ? ball(Dims{ndims(A)}, 2.5) :
1097+
centered(rand(Bool, ntuple(Returns(6), ndims(A))))
1098+
f, f_ref = func === :minimum ? (minimum, erode) : (maximum, dilate)
1099+
Bref = @inferred f_ref(A, W)
1100+
B = @inferred localmap(f, A, W)
1101+
@test A == Aref # check that A is left unchanged
1102+
@test B == Bref # check result
1103+
@test B === @inferred localmap!(f, zerofill!(B), A, W)
1104+
@test A == Aref # check that A is left unchanged
1105+
@test B == Bref # check result
1106+
if win === :win
1107+
Bref = @inferred f_ref(A, W; order=REVERSE_FILTER)
1108+
B = @inferred localmap(f, A, W; order=REVERSE_FILTER)
1109+
@test A == Aref # check that A is left unchanged
1110+
@test B == Bref # check result
1111+
@test B === @inferred localmap!(f, zerofill!(B), A, W; order=REVERSE_FILTER)
1112+
@test A == Aref # check that A is left unchanged
1113+
@test B == Bref # check result
1114+
end
1115+
end
1116+
10891117
@testset "van Herk / Gil & Werman algorithm" begin
10901118
A = rand(Float32, 40)
10911119
Aref = copy(A)

0 commit comments

Comments
 (0)