Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adds support for specifying kernels using StaticArrays #254

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Project.toml
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ ImageCore = "0.9"
OffsetArrays = "1.9"
Reexport = "1.1"
StaticArrays = "0.10, 0.11, 0.12, 1.0"
TiledIteration = "0.2, 0.3, 0.4"
TiledIteration = "0.2, 0.3"
johnnychen94 marked this conversation as resolved.
Show resolved Hide resolved
julia = "1"

[extras]
Expand Down
19 changes: 19 additions & 0 deletions src/border.jl
Original file line number Diff line number Diff line change
Expand Up @@ -997,6 +997,25 @@ expand(inds::Indices, kernel) = expand(inds, calculate_padding(kernel))
expand(inds::Indices, pad::Indices) = firsttype(map_copytail(expand, inds, pad))
expand(ind::AbstractUnitRange, pad::AbstractUnitRange) = typeof(ind)(first(ind)+first(pad):last(ind)+last(pad))
expand(ind::Base.OneTo, pad::AbstractUnitRange) = expand(UnitRange(ind), pad)
expand(ind::SOneTo{T₁}, pad::AbstractUnitRange) where {T₁} = represent_unit_range_with_SOneTo(ind, pad)
expand(ind::OffsetArrays.IdOffsetRange{T₁, SOneTo{T₂}}, pad::AbstractUnitRange) where {T₁, T₂} = determine_offset_with_SOneTo(ind, pad)

function represent_unit_range_with_SOneTo(ind::SOneTo{T₁}, pad::AbstractUnitRange) where {T₁}
lo = first(ind)+first(pad)
hi = last(ind)+last(pad)
interval = 1:length(lo:hi)
T = typeof(T₁)
I = typeof(SOneTo(length(interval)))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is unfortunately a type-unstable patch: SOneTo{n} where n = length(interval) is runtime information.

IIUC, unless the image itself is also a SMatrix, we can't get a static pad, and thus we can't make it type stable.

The solution to support static array inputs might be converting it back to normal array and unit ranges.

return OffsetArrays.IdOffsetRange{T, I}(interval, lo - 1)
end

function determine_offset_with_SOneTo(ind::OffsetArrays.IdOffsetRange{T₁, SOneTo{T₂}}, pad::AbstractUnitRange) where {T₁, T₂}
lo = first(ind)+first(pad)
hi = last(ind)+last(pad)
interval = 1:length(lo:hi)
I = typeof(SOneTo(length(interval)))
return OffsetArrays.IdOffsetRange{T₁, I}(interval, lo - 1)
end

"""
shrink(inds::Indices, kernel)
Expand Down
4 changes: 2 additions & 2 deletions src/imfilter.jl
Original file line number Diff line number Diff line change
Expand Up @@ -502,8 +502,8 @@ See also: [`Pad`](@ref), [`padarray`](@ref), [`Inner`](@ref), [`NA`](@ref) and


## Choices for `alg`
The `alg` parameter allows you to choose the particular algorithm: `Algorithm.FIR()`
(finite impulse response, aka traditional digital filtering) or `Algorithm.FFT()`
The `alg` parameter allows you to choose the particular algorithm: `FIR()`
(finite impulse response, aka traditional digital filtering) or `FFT()`
(Fourier-based filtering). If no choice is specified, one will be chosen based
on the size of the image and kernel in a way that strives to deliver good
performance. Alternatively you can use a custom filter type, like
Expand Down
17 changes: 17 additions & 0 deletions test/2d.jl
Original file line number Diff line number Diff line change
Expand Up @@ -341,3 +341,20 @@ end
@test_throws DimensionMismatch imfilter(CPU1(), A, kern, Fill(0, (0,1)))
@test_throws DimensionMismatch imfilter(CPU1(), A, kern, Fill(0, (0,0)))
end

@testset "Static Arrays (issue #252)" begin
img = rand(RGB{Float32}, 9, 9)
k₁ = [0.2f0 -0.5f0 0.3f0]
k₂ = SMatrix{1, 3, Float32}(k₁)
border_styles = ["replicate", "circular", "reflect", "symmetric"]
for border_style in border_styles
A = imfilter(img, (k₁', k₁), border_style, ImageFiltering.Algorithm.FIR())
B = imfilter(img, (k₂', k₂), border_style, ImageFiltering.Algorithm.FIR())
@test A ≈ B
k₁ = centered(k₁)
k₂ = centered(k₂)
A = imfilter(img, (k₁', k₁), border_style, ImageFiltering.Algorithm.FIR())
B = imfilter(img, (k₂', k₂), border_style, ImageFiltering.Algorithm.FIR())
@test A ≈ B
end
end
131 changes: 72 additions & 59 deletions test/nd.jl
Original file line number Diff line number Diff line change
Expand Up @@ -13,38 +13,42 @@ Base.zero(::Type{WrappedFloat}) = WrappedFloat(0.0)

@testset "1d" begin
img = 1:8
# Exercise all the different ways to call imfilter
kern = centered([1/3,1/3,1/3])
imgf = imfilter(img, kern)
r = CPU1(Algorithm.FIR())
@test imfilter(Float64, img, kern) == imgf
@test imfilter(Float64, img, (kern,)) == imgf
@test imfilter(Float64, img, kern, "replicate") == imgf
@test imfilter(Float64, img, (kern,), "replicate") == imgf
@test_throws ArgumentError imfilter(Float64, img, (kern,), "inner")
@test_throws ArgumentError imfilter(Float64, img, (kern,), "nonsense")
@test imfilter(Float64, img, kern, "replicate", Algorithm.FIR()) == imgf
@test imfilter(Float64, img, (kern,), "replicate", Algorithm.FIR()) == imgf
@test imfilter(r, img, kern) == imgf
@test imfilter(r, img, kern) == imgf
@test imfilter(r, Float64, img, kern) == imgf
@test imfilter(r, Float64, img, (kern,)) == imgf
@test imfilter(r, img, kern, "replicate") == imgf
@test imfilter(r, Float64, img, kern, "replicate") == imgf
@test imfilter(r, img, (kern,), "replicate") == imgf
@test imfilter(r, Float64, img, (kern,), "replicate") == imgf
@test_throws MethodError imfilter(r, img, (kern,), "replicate", Algorithm.FIR())
@test_throws MethodError imfilter(r, Float64, img, (kern,), "replicate", Algorithm.FIR())
out = similar(imgf)
@test imfilter!(out, img, kern) == imgf
@test imfilter!(out, img, (kern,)) == imgf
@test imfilter!(out, img, (kern,), "replicate") == imgf
@test imfilter!(out, img, (kern,), "replicate", Algorithm.FIR()) == imgf
@test imfilter!(r, out, img, kern) == imgf
@test imfilter!(r, out, img, (kern,)) == imgf
@test imfilter!(r, out, img, kern, "replicate") == imgf
@test imfilter!(r, out, img, (kern,), "replicate") == imgf
@test_throws MethodError imfilter!(r, out, img, (kern,), "replicate", Algorithm.FIR())
array_types = [Vector, SVector{3}]
values = [1/3, 1/3, 1/3]
for array_type in array_types
# Exercise all the different ways to call imfilter
kern = centered(array_type(values))
imgf = imfilter(img, kern)
r = CPU1(Algorithm.FIR())
@test imfilter(Float64, img, kern) == imgf
@test imfilter(Float64, img, (kern,)) == imgf
@test imfilter(Float64, img, kern, "replicate") == imgf
@test imfilter(Float64, img, (kern,), "replicate") == imgf
@test_throws ArgumentError imfilter(Float64, img, (kern,), "inner")
@test_throws ArgumentError imfilter(Float64, img, (kern,), "nonsense")
@test imfilter(Float64, img, kern, "replicate", Algorithm.FIR()) == imgf
@test imfilter(Float64, img, (kern,), "replicate", Algorithm.FIR()) == imgf
@test imfilter(r, img, kern) == imgf
@test imfilter(r, img, kern) == imgf
@test imfilter(r, Float64, img, kern) == imgf
@test imfilter(r, Float64, img, (kern,)) == imgf
@test imfilter(r, img, kern, "replicate") == imgf
@test imfilter(r, Float64, img, kern, "replicate") == imgf
@test imfilter(r, img, (kern,), "replicate") == imgf
@test imfilter(r, Float64, img, (kern,), "replicate") == imgf
@test_throws MethodError imfilter(r, img, (kern,), "replicate", Algorithm.FIR())
@test_throws MethodError imfilter(r, Float64, img, (kern,), "replicate", Algorithm.FIR())
out = similar(imgf)
@test imfilter!(out, img, kern) == imgf
@test imfilter!(out, img, (kern,)) == imgf
@test imfilter!(out, img, (kern,), "replicate") == imgf
@test imfilter!(out, img, (kern,), "replicate", Algorithm.FIR()) == imgf
@test imfilter!(r, out, img, kern) == imgf
@test imfilter!(r, out, img, (kern,)) == imgf
@test imfilter!(r, out, img, kern, "replicate") == imgf
@test imfilter!(r, out, img, (kern,), "replicate") == imgf
@test_throws MethodError imfilter!(r, out, img, (kern,), "replicate", Algorithm.FIR())
end

# Element-type widening (issue #17)
v = fill(0xff, 10)
Expand All @@ -56,40 +60,49 @@ Base.zero(::Type{WrappedFloat}) = WrappedFloat(0.0)
@test all(x->x==0x0002fa03, vout)

# Cascades don't result in out-of-bounds values
k1 = centered([0.25, 0.5, 0.25])
k2 = OffsetArray([0.5, 0.5], 1:2)
casc = imfilter(img, (k1, k2, k1))
A0 = padarray(img, Pad(:replicate, (2,), (4,)))
A1 = imfilter(A0, k1, Inner())
@test A1 ≈ OffsetArray([1.0,1.25,2.0,3.0,4.0,5.0,6.0,7.0,7.75,8.0,8.0,8.0], 0:11)
A2 = imfilter(A1, k2, Inner())
@test A2 ≈ OffsetArray([1.625,2.5,3.5,4.5,5.5,6.5,7.375,7.875,8.0,8.0], 0:9)
A3 = imfilter(A2, k1, Inner())
@test casc ≈ A3
@test size(casc) == size(img)
# copy! kernels, presence/order doesn't matter
kc = centered([1])
@test ImageFiltering.iscopy(kc)
@test imfilter(img, (k1,)) ≈ [1.25,2.0,3.0,4.0,5.0,6.0,7.0,7.75]
@test imfilter(img, (kc, k1)) ≈ [1.25,2.0,3.0,4.0,5.0,6.0,7.0,7.75]
@test imfilter(img, (k1, kc)) ≈ [1.25,2.0,3.0,4.0,5.0,6.0,7.0,7.75]

array_types₁ = [Vector, SVector{3}]
array_types₂ = [Vector, SVector{2}]
for (array_type₁, array_type₂) in zip(array_types₁, array_types₂)
values1 = [0.25, 0.5, 0.25]
values2 = [0.5, 0.5]
k1 = centered(array_type₁(values1))
k2 = OffsetArray(array_type₂(values2), 1:2)
casc = imfilter(img, (k1, k2, k1))
A0 = padarray(img, Pad(:replicate, (2,), (4,)))
A1 = imfilter(A0, k1, Inner())
@test A1 ≈ OffsetArray([1.0,1.25,2.0,3.0,4.0,5.0,6.0,7.0,7.75,8.0,8.0,8.0], 0:11)
A2 = imfilter(A1, k2, Inner())
@test A2 ≈ OffsetArray([1.625,2.5,3.5,4.5,5.5,6.5,7.375,7.875,8.0,8.0], 0:9)
A3 = imfilter(A2, k1, Inner())
@test casc ≈ A3
@test size(casc) == size(img)
# copy! kernels, presence/order doesn't matter
kc = centered([1])
@test ImageFiltering.iscopy(kc)
@test imfilter(img, (k1,)) ≈ [1.25,2.0,3.0,4.0,5.0,6.0,7.0,7.75]
@test imfilter(img, (kc, k1)) ≈ [1.25,2.0,3.0,4.0,5.0,6.0,7.0,7.75]
@test imfilter(img, (k1, kc)) ≈ [1.25,2.0,3.0,4.0,5.0,6.0,7.0,7.75]
end
# FFT without padding
img = collect(1:8)
img[1] = img[8] = 0
out = imfilter!(CPU1(Algorithm.FFT()), similar(img, Float64), img, kern, NoPad())
@test out ≈ imfilter(img, kern)

# Inputs that have non-1 axes
img = OffsetArray(zeros(11), -5:5)
img[0] = 1
for border in ("replicate", "circular", "symmetric", "reflect",
Fill(zero(eltype(img))), Inner(1), NA())
imgf = imfilter(img, centered([0.25, 0.5, 0.25]), border)
@test imgf[-1] == imgf[1] == 0.25
@test imgf[0] == 0.5
inds = axes(imgf,1)
@test all(x->x==0, imgf[first(inds):-2]) && all(x->x==0, imgf[2:last(inds)])
array_types = [Vector, MVector{11}]
values = zeros(11)
for array_type in array_types
img = OffsetArray(array_type(values), -5:5)
img[0] = 1
for border in ("replicate", "circular", "symmetric", "reflect",
Fill(zero(eltype(img))), Inner(1), NA())
imgf = imfilter(img, centered([0.25, 0.5, 0.25]), border)
@test imgf[-1] == imgf[1] == 0.25
@test imgf[0] == 0.5
inds = axes(imgf,1)
@test all(x->x==0, imgf[first(inds):-2]) && all(x->x==0, imgf[2:last(inds)])
end
end

# Non-finite input
Expand Down