Skip to content

Commit

Permalink
Merge pull request #86 from JuliaImages/teh/borders
Browse files Browse the repository at this point in the history
Provide better support for manually-specified borders (fixes #85)
  • Loading branch information
timholy authored Nov 30, 2018
2 parents 73e73bc + 7afa780 commit 3b66231
Show file tree
Hide file tree
Showing 6 changed files with 135 additions and 19 deletions.
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

0 comments on commit 3b66231

Please sign in to comment.