Skip to content

Provide better support for manually-specified borders (fixes #85) #86

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

Merged
merged 4 commits into from
Nov 30, 2018
Merged
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
3 changes: 1 addition & 2 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ script:
jobs:
include:
- stage: deploy
julia: 0.7
julia: 1.0
os: linux
script:
- julia -e 'import Pkg; Pkg.clone(pwd()); Pkg.build("ImageFiltering")'
Expand All @@ -28,4 +28,3 @@ jobs:
after_success:
# push coverage results to Codecov
- julia -e 'import Pkg; Pkg.add("Coverage"); using Coverage; Codecov.submit(Codecov.process_folder())'

14 changes: 11 additions & 3 deletions src/border.jl
Original file line number Diff line number Diff line change
Expand Up @@ -236,10 +236,10 @@ function padfft(indk::AbstractUnitRange, l::Integer)
range(first(indk), length=nextprod([2,3], l+lk)-l+1)
end

function padindices(img::AbstractArray{_,N}, border::Pad) where {_,N}
function padindices(img::AbstractArray{<:Any,N}, border::Pad) where N
throw(ArgumentError("$border lacks the proper padding sizes for an array with $(ndims(img)) dimensions"))
end
function padindices(img::AbstractArray{_,N}, border::Pad{N}) where {_,N}
function padindices(img::AbstractArray{<:Any,N}, border::Pad{N}) where N
_padindices(border, border.lo, axes(img), border.hi)
end
function padindices(img::AbstractArray, ::Type{P}) where P<:Pad
Expand Down Expand Up @@ -702,6 +702,9 @@ struct Inner{N} <: AbstractBorder
hi::Dims{N}
end

Base.ndims(::Inner{N}) where N = N
Base.ndims(::Type{Inner{N}}) where N = N

"""
NA()
NA(lo, hi)
Expand Down Expand Up @@ -895,7 +898,7 @@ end
function padarray(::Type{T}, img::AbstractArray, border::Fill) where T
throw(ArgumentError("$border lacks the proper padding sizes for an array with $(ndims(img)) dimensions"))
end
function padarray(::Type{T}, img::AbstractArray{S,N}, f::Fill{_,N}) where {T,S,_,N}
function padarray(::Type{T}, img::AbstractArray{<:Any,N}, f::Fill{<:Any,N}) where {T,N}
paxs = map((l,r,h)->first(r)-l:last(r)+h, f.lo, axes(img), f.hi)
A = similar(arraytype(img, T), paxs)
try
Expand Down Expand Up @@ -1031,6 +1034,11 @@ function allocate_output(::Type{T}, img, kernel, ::Inner{0}) where T
inds = interior(img, kernel)
similar(img, T, inds)
end
function allocate_output(::Type{T}, img, kernel, inr::Inner) where T
ndims(img) == ndims(inr) || throw(DimensionMismatch("dimensionality of img and the border must agree, got $(ndims(img)) and $(ndims(inr))"))
inds = inner.(inr.lo, axes(img), inr.hi)
similar(img, T, inds)
end
allocate_output(img, kernel, border) = allocate_output(filter_type(img, kernel), img, kernel, border)

"""
Expand Down
25 changes: 17 additions & 8 deletions src/imfilter.jl
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ function imfilter(::Type{T}, img::AbstractArray, kernel::ProcessedKernel, border
end

# Step 4: if necessary, allocate the ouput
@inline function imfilter(::Type{T}, img::AbstractArray, kernel::ProcessedKernel, border::BorderSpecAny, args...) where T
@inline function imfilter(::Type{T}, img::AbstractArray, kernel::ProcessedKernel, border::AbstractBorder, args...) where T
imfilter!(allocate_output(T, img, kernel, border), img, kernel, border, args...)
end

Expand All @@ -46,7 +46,7 @@ function imfilter(r::AbstractResource, ::Type{T}, img::AbstractArray, kernel::Pr
imfilter(r, T, img, kernel, borderinstance(border))
end

function imfilter(r::AbstractResource, ::Type{T}, img::AbstractArray, kernel::ProcessedKernel, border::BorderSpecAny) where T
function imfilter(r::AbstractResource, ::Type{T}, img::AbstractArray, kernel::ProcessedKernel, border::AbstractBorder) where T
imfilter!(r, allocate_output(T, img, kernel, border), img, kernel, border)
end

Expand Down Expand Up @@ -602,11 +602,11 @@ function imfilter!(r::AbstractResource, out::AbstractArray, img::AbstractArray,
end

# Step 5: if necessary, pick an algorithm
function imfilter!(out::AbstractArray, img::AbstractArray, kernel::ProcessedKernel, border::BorderSpecAny)
function imfilter!(out::AbstractArray, img::AbstractArray, kernel::ProcessedKernel, border::AbstractBorder)
imfilter!(out, img, kernel, border, filter_algorithm(out, img, kernel))
end

function imfilter!(out::AbstractArray, img::AbstractArray, kernel::ProcessedKernel, border::BorderSpecAny, alg::Alg)
function imfilter!(out::AbstractArray, img::AbstractArray, kernel::ProcessedKernel, border::AbstractBorder, alg::Alg)
local ret
try
ret = imfilter!(default_resource(alg_defaults(alg, out, kernel)), out, img, kernel, border)
Expand Down Expand Up @@ -692,14 +692,23 @@ function _imfilter_na!(r::AbstractResource,
end
end

# Any other kind of padding
# Any other kind of not-fully-specified padding
function imfilter!(r::AbstractResource,
out::AbstractArray{S,N},
img::AbstractArray{T,N},
kernel::ProcessedKernel,
border::BorderSpec) where {S,T,N}
border::BorderSpecAny) where {S,T,N}
bord = border(kernel, img, Alg(r)) # if it's FFT, the size of img is also relevant
A = padarray(S, img, bord)
imfilter!(r, out, img, kernel, bord)
end

# Any fully-specified padding
function imfilter!(r::AbstractResource,
out::AbstractArray{S,N},
img::AbstractArray{T,N},
kernel::ProcessedKernel,
border::AbstractBorder) where {S,T,N}
A = padarray(S, img, border)
# By specifying NoPad(), we ensure that dispatch will never
# accidentally "go back" to an earlier routine and apply more
# padding
Expand Down Expand Up @@ -1290,7 +1299,7 @@ _imfilter_inplace_tuple!(r, out, img, ::Tuple{}, Rbegin, inds, Rend, border) = o
@noinline function _imfilter_dim!(r::AbstractResource,
out, img, kernel::TriggsSdika{T,k,l},
Rbegin::CartesianIndices, ind::AbstractUnitRange,
Rend::CartesianIndices, border::BorderSpec) where {T,k,l}
Rend::CartesianIndices, border::AbstractBorder) where {T,k,l}
if iscopy(kernel)
if !(out === img)
copyto!(out, img)
Expand Down
4 changes: 2 additions & 2 deletions src/kernelfactors.jl
Original file line number Diff line number Diff line change
Expand Up @@ -137,11 +137,11 @@ end
@inline function _iterdims(indspre, ::Tuple{}, inds, v)
_iterdims((indspre..., inds[1]), (), tail(inds), v) # consume inds and push to indspre
end
@inline function _iterdims(indspre::NTuple{Npre}, ::Tuple{}, inds, v::ReshapedOneD{_,N,Npre}) where {_,N,Npre}
@inline function _iterdims(indspre::NTuple{Npre}, ::Tuple{}, inds, v::ReshapedOneD{<:Any,N,Npre}) where {N,Npre}
indspre, inds[1], tail(inds) # return the "central" and trailing dimensions
end

function indexsplit(I::CartesianIndex{N}, v::ReshapedOneD{_,N}) where {_,N}
function indexsplit(I::CartesianIndex{N}, v::ReshapedOneD{<:Any,N}) where N
ipre, i, ipost = _iterdims((), (), Tuple(I), v)
CartesianIndex(ipre), i, CartesianIndex(ipost)
end
Expand Down
6 changes: 3 additions & 3 deletions src/utils.jl
Original file line number Diff line number Diff line change
Expand Up @@ -28,13 +28,13 @@ function checkextended(inds::Indices, n)
end
checkextended(a::AbstractArray, n) = checkextended(axes(a), n)

_reshape(A::OffsetArray{_,N}, ::Val{N}) where {_,N} = A
_reshape(A::OffsetArray{<:Any,N}, ::Val{N}) where N = A
_reshape(A::OffsetArray, ::Val{N}) where {N} = OffsetArray(reshape(parent(A), Val(N)), fill_to_length(A.offsets, -1, Val(N)))
_reshape(A::AbstractArray, ::Val{N}) where {N} = reshape(A, Val(N))

_vec(a::AbstractVector) = a
_vec(a::AbstractArray) = (checkextended(a, 1); a)
_vec(a::OffsetArray{_,1}) where {_} = a
_vec(a::OffsetArray{<:Any,1}) = a
function _vec(a::OffsetArray)
inds = axes(a)
checkextended(inds, 1)
Expand All @@ -44,7 +44,7 @@ end

samedims(::Val{N}, kernel) where {N} = _reshape(kernel, Val(N))
samedims(::Val{N}, kernel::Tuple) where {N} = map(k->_reshape(k, Val(N)), kernel)
samedims(::AbstractArray{T,N}, kernel) where {T,N} = samedims(Val(N), kernel)
samedims(::AbstractArray{<:Any,N}, kernel) where {N} = samedims(Val(N), kernel)

_tail(R::CartesianIndices{0}) = R
_tail(R::CartesianIndices) = CartesianIndices(tail(axes(R)))
Expand Down
102 changes: 101 additions & 1 deletion test/2d.jl
Original file line number Diff line number Diff line change
Expand Up @@ -235,4 +235,104 @@ end
@test axes(imgf) == axes(img)
end

nothing
@testset "Borders (issue #85)" begin
A = ones(8, 8)
r1 = imfilter(A, Kernel.gaussian((1,1),(3,3)), Fill(0))
r2 = imfilter(A, Kernel.gaussian((1,1),(3,3)), Fill(10))
@test r1[4,4] == r2[4,4]
@test r1[1,1] != r2[1,1]

r1 = imfilter(A, Kernel.gaussian((1,1),(3,3)), Fill(0, (3,3)))
r2 = imfilter(A, Kernel.gaussian((1,1),(3,3)), Fill(10, (3,3)))
@test r1[4,4] == r2[4,4]
@test r1[1,1] != r2[1,1]

r1 = imfilter(A, Kernel.gaussian((1,1),(3,3)), Fill(0, (3,3),(3,3)))
r2 = imfilter(A, Kernel.gaussian((1,1),(3,3)), Fill(10, (3,3), (3,3)))
@test r1[4,4] == r2[4,4]
@test r1[1,1] != r2[1,1]

r1 = imfilter(A, Kernel.gaussian((1,1),(3,3)), Fill(0, [3,3],[3,3]))
r2 = imfilter(A, Kernel.gaussian((1,1),(3,3)), Fill(10, [3,3], [3,3]))
@test r1[4,4] == r2[4,4]
@test r1[1,1] != r2[1,1]

r1 = imfilter(A, Kernel.gaussian((1,1),(3,3)), Fill(0, Kernel.gaussian((1,1),(3,3))))
r2 = imfilter(A, Kernel.gaussian((1,1),(3,3)), Fill(10, Kernel.gaussian((1,1),(3,3))))
@test r1[4,4] == r2[4,4]
@test r1[1,1] != r2[1,1]

B = fill!(similar(A), 0)
C = fill!(similar(A), 0)
r1 = imfilter!(B, A, Kernel.gaussian((1,1),(3,3)), Fill(0))
r2 = imfilter!(C, A, Kernel.gaussian((1,1),(3,3)), Fill(10))
@test r1[4,4] == r2[4,4]
@test r1[1,1] != r2[1,1]

fill!(B, 0); fill!(C, 0)
r1 = imfilter!(B, A, Kernel.gaussian((1,1),(3,3)), Fill(0, (3,3)))
r2 = imfilter!(C, A, Kernel.gaussian((1,1),(3,3)), Fill(10, (3,3)))
@test r1[4,4] == r2[4,4]
@test r1[1,1] != r2[1,1]

fill!(B, 0); fill!(C, 0)
r1 = imfilter!(B, A, Kernel.gaussian((1,1),(3,3)), Fill(0, (3,3),(3,3)))
r2 = imfilter!(C, A, Kernel.gaussian((1,1),(3,3)), Fill(10, (3,3),(3,3)))
@test r1[4,4] == r2[4,4]
@test r1[1,1] != r2[1,1]

fill!(B, 0); fill!(C, 0)
r1 = imfilter!(B, A, Kernel.gaussian((1,1),(3,3)), Fill(0, [3,3],[3,3]))
r2 = imfilter!(C, A, Kernel.gaussian((1,1),(3,3)), Fill(10, [3,3],[3,3]))
@test r1[4,4] == r2[4,4]
@test r1[1,1] != r2[1,1]


g = collect(KernelFactors.gaussian(1,3))
r1 = imfilter(A, ( ImageFiltering.ReshapedOneD{2,0}(centered(g)),), Fill(0))
r2 = imfilter(A, ( ImageFiltering.ReshapedOneD{2,0}(centered(g)),), Fill(10))
@test r1[4,4] == r2[4,4]
@test r1[1,1] != r2[1,1]

r1 = imfilter(A, ( ImageFiltering.ReshapedOneD{2,0}(centered(g)),), Fill(0, (3,3)))
r2 = imfilter(A, ( ImageFiltering.ReshapedOneD{2,0}(centered(g)),), Fill(10, (3,3)))
@test r1[4,4] == r2[4,4]
@test r1[1,1] != r2[1,1]

r1 = imfilter(A, ( ImageFiltering.ReshapedOneD{2,0}(centered(g)),), Fill(0, (3,3),(3,3)))
r2 = imfilter(A, ( ImageFiltering.ReshapedOneD{2,0}(centered(g)),), Fill(10, (3,3),(3,3)))
@test r1[4,4] == r2[4,4]
@test r1[1,1] != r2[1,1]

r1 = imfilter(A, ( ImageFiltering.ReshapedOneD{2,0}(centered(g)),), Fill(0, [3,3],[3,3]))
r2 = imfilter(A, ( ImageFiltering.ReshapedOneD{2,0}(centered(g)),), Fill(10, [3,3],[3,3]))
@test r1[4,4] == r2[4,4]
@test r1[1,1] != r2[1,1]

r1 = imfilter(A, ( ImageFiltering.ReshapedOneD{2,0}(centered(g)),), Fill(0))
r2 = imfilter(A, ( ImageFiltering.ReshapedOneD{2,0}(centered(g)),), Fill(10))
@test r1[4,4] == r2[4,4]
@test r1[1,1] != r2[1,1]

r1 = imfilter(A, ( ImageFiltering.ReshapedOneD{2,1}(centered(g)),), Fill(0, (3,3)))
r2 = imfilter(A, ( ImageFiltering.ReshapedOneD{2,1}(centered(g)),), Fill(10, (3,3)))
@test r1[4,4] == r2[4,4]
@test r1[1,1] != r2[1,1]

r1 = imfilter(A, ( ImageFiltering.ReshapedOneD{2,1}(centered(g)),), Fill(0, (3,3),(3,3)))
r2 = imfilter(A, ( ImageFiltering.ReshapedOneD{2,1}(centered(g)),), Fill(10, (3,3),(3,3)))
@test r1[4,4] == r2[4,4]
@test r1[1,1] != r2[1,1]

r1 = imfilter(A, ( ImageFiltering.ReshapedOneD{2,1}(centered(g)),), Fill(0, [3,3],[3,3]))
r2 = imfilter(A, ( ImageFiltering.ReshapedOneD{2,1}(centered(g)),), Fill(10, [3,3],[3,3]))
@test r1[4,4] == r2[4,4]
@test r1[1,1] != r2[1,1]

err = ArgumentError("Fill{$Int,1}(0, (3,), (3,)) lacks the proper padding sizes for an array with 2 dimensions")
@test_throws err imfilter(A, Kernel.gaussian((1,1),(3,3)), Fill(0, (3,)))
err = DimensionMismatch("requested indices (1:8, 0:9) and kernel indices (Base.Slice(-1:1), Base.Slice(0:0)) do not agree with indices of padded input, (Base.Slice(0:9), Base.Slice(1:8))")
@test_throws err imfilter(A, Kernel.gaussian((1,1),(3,3)), Fill(0, (1,0)))
@test_throws DimensionMismatch imfilter(A, Kernel.gaussian((1,1),(3,3)), Fill(0, (0,1)))
@test_throws DimensionMismatch imfilter(A, Kernel.gaussian((1,1),(3,3)), Fill(0, (0,0)))
end