diff --git a/.travis.yml b/.travis.yml index 26e77ea..264f3c2 100644 --- a/.travis.yml +++ b/.travis.yml @@ -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")' @@ -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())' - diff --git a/src/border.jl b/src/border.jl index b73ed23..268c1cd 100644 --- a/src/border.jl +++ b/src/border.jl @@ -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 @@ -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) @@ -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 @@ -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) """ diff --git a/src/imfilter.jl b/src/imfilter.jl index 3f9e3e0..5ef9beb 100644 --- a/src/imfilter.jl +++ b/src/imfilter.jl @@ -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 @@ -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 @@ -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) @@ -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 @@ -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) diff --git a/src/kernelfactors.jl b/src/kernelfactors.jl index ffe89e2..9dd85b2 100644 --- a/src/kernelfactors.jl +++ b/src/kernelfactors.jl @@ -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 diff --git a/src/utils.jl b/src/utils.jl index 86fefba..7bedf88 100644 --- a/src/utils.jl +++ b/src/utils.jl @@ -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) @@ -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))) diff --git a/test/2d.jl b/test/2d.jl index abd11f9..7734782 100644 --- a/test/2d.jl +++ b/test/2d.jl @@ -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