From 39d784a6d158d689a7cea92da4316a99383f1513 Mon Sep 17 00:00:00 2001 From: Johnny Chen Date: Tue, 12 Oct 2021 01:07:52 +0800 Subject: [PATCH 1/4] implement `DiffView` and `fdiv` --- Project.toml | 3 +- src/ImageBase.jl | 3 + src/diff.jl | 141 ++++++++++++++++++++++++++++++++++++++++++++++- test/diff.jl | 45 +++++++++++++++ test/runtests.jl | 1 + 5 files changed, 190 insertions(+), 3 deletions(-) diff --git a/Project.toml b/Project.toml index 7357fcc..139d81e 100644 --- a/Project.toml +++ b/Project.toml @@ -14,6 +14,7 @@ julia = "1" [extras] Aqua = "4c88cf16-eb10-579e-8560-4a9242c79595" Documenter = "e30172f5-a6a5-5a46-863b-614d45cd2de4" +ImageFiltering = "6a3955dd-da59-5b1f-98d4-e7296123deb5" ImageIO = "82e4d734-157c-48bb-816b-45c225c6df19" ImageMagick = "6218d12a-5da1-5696-b52f-db25d2ecc6d1" OffsetArrays = "6fe1bfb0-de20-5000-8ca7-80f57d26f881" @@ -23,4 +24,4 @@ Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" TestImages = "5e47fb64-e119-507b-a336-dd2b206d9990" [targets] -test = ["Aqua", "Documenter", "Test", "ImageIO", "ImageMagick", "OffsetArrays", "Statistics", "StackViews", "TestImages"] +test = ["Aqua", "Documenter", "Test", "ImageFiltering", "ImageIO", "ImageMagick", "OffsetArrays", "Statistics", "StackViews", "TestImages"] diff --git a/src/ImageBase.jl b/src/ImageBase.jl index cd4f0cd..9c5f56c 100644 --- a/src/ImageBase.jl +++ b/src/ImageBase.jl @@ -9,6 +9,9 @@ export # originally from Images.jl fdiff, fdiff!, + fdiv, + fdiv!, + DiffView, # basic image statistics, from Images.jl minimum_finite, diff --git a/src/diff.jl b/src/diff.jl index c2fa425..7939d2e 100644 --- a/src/diff.jl +++ b/src/diff.jl @@ -1,3 +1,94 @@ +abstract type BoundaryCondition end +struct Periodic <: BoundaryCondition end +struct ZeroFill <: BoundaryCondition end + +""" + DiffView(A::AbstractArray, [rev=Val(false)], [bc::BoundaryCondition=Periodic()]; dims) + +Lazy version of finite difference [`fdiff`](@ref). + +!!! tip + For performance, `rev` should be stable type `Val(false)` or `Val(true)`. + +# Arguments + +- `rev::Bool` + If `rev==Val(true)`, then it computes the backward difference + `(A[end]-A[1], A[1]-A[2], ..., A[end-1]-A[end])`. +- `boundary::BoundaryCondition` + By default it computes periodically in the boundary, i.e., `Periodic()`. + In some cases, one can fill zero values with `ZeroFill()`. +""" +struct DiffView{T,N,AT<:AbstractArray,BC,REV} <: AbstractArray{T,N} + data::AT + dims::Int +end +function DiffView( + data::AbstractArray{T,N}, + bc::BoundaryCondition=Periodic(), + rev::Union{Val, Bool}=Val(false); + dims=_fdiff_default_dims(data)) where {T,N} + isnothing(dims) && throw(UndefKeywordError(:dims)) + rev = to_static_bool(rev) + DiffView{maybe_floattype(T),N,typeof(data),typeof(bc),typeof(rev)}(data, dims) +end +function DiffView( + data::AbstractArray, + rev::Union{Val, Bool}, + bc::BoundaryCondition = Periodic(); + kwargs...) + DiffView(data, bc, rev; kwargs...) +end + +to_static_bool(x::Union{Val{true},Val{false}}) = x +function to_static_bool(x::Bool) + @warn "Please use `Val($x)` for performance" + return Val(x) +end + +Base.size(A::DiffView) = size(A.data) +Base.axes(A::DiffView) = axes(A.data) +Base.IndexStyle(::DiffView) = IndexCartesian() + +Base.@propagate_inbounds function Base.getindex(A::DiffView{T,N,AT,Periodic,Val{true}}, I::Vararg{Int, N}) where {T,N,AT} + data = A.data + I_prev = map(ntuple(identity, N), I, axes(data)) do i, p, r + i == A.dims || return p + p == first(r) && return last(r) + p - 1 + end + return convert(T, data[I...]) - convert(T, data[I_prev...]) +end +Base.@propagate_inbounds function Base.getindex(A::DiffView{T,N,AT,Periodic,Val{false}}, I::Vararg{Int, N}) where {T,N,AT} + data = A.data + I_next = map(ntuple(identity, N), I, axes(data)) do i, p, r + i == A.dims || return p + p == last(r) && return first(r) + p + 1 + end + return convert(T, data[I_next...]) - convert(T, data[I...]) +end +Base.@propagate_inbounds function Base.getindex(A::DiffView{T,N,AT,ZeroFill,Val{false}}, I::Vararg{Int, N}) where {T,N,AT} + data = A.data + I_next = I .+ ntuple(i->i==A.dims, N) + if checkbounds(Bool, data, I_next...) + vi = convert(T, data[I...]) # it requires the caller to pass @inbounds + @inbounds convert(T, data[I_next...]) - vi + else + zero(T) + end +end +Base.@propagate_inbounds function Base.getindex(A::DiffView{T,N,AT,ZeroFill,Val{true}}, I::Vararg{Int, N}) where {T,N,AT} + data = A.data + I_prev = I .- ntuple(i->i==A.dims, N) + if checkbounds(Bool, data, I_prev...) + vi = convert(T, data[I...]) # it requires the caller to pass @inbounds + @inbounds vi - convert(T, data[I_prev...]) + else + zero(T) + end +end + # TODO: add keyword `shrink` to give a consistant result on Base # when this is done, then we can propose this change to upstream Base """ @@ -51,14 +142,15 @@ julia> fdiff(A, dims=2, boundary=:zero) # fill boundary with zeros 12 48 0 ``` -See also [`fdiff!`](@ref) for the in-place version. +See also [`fdiff!`](@ref) for the in-place version, and [`DiffView`](@ref) for the +non-allocating version. """ fdiff(A::AbstractArray; kwargs...) = fdiff!(similar(A, maybe_floattype(eltype(A))), A; kwargs...) """ fdiff!(dst::AbstractArray, src::AbstractArray; dims::Int, rev=false, boundary=:periodic) -The in-place version of [`ImageBase.fdiff`](@ref) +The in-place version of [`fdiff`](@ref). """ function fdiff!(dst::AbstractArray, src::AbstractArray; dims=_fdiff_default_dims(src), @@ -106,3 +198,48 @@ _fdiff_default_dims(A::AbstractVector) = 1 maybe_floattype(::Type{T}) where T = T maybe_floattype(::Type{T}) where T<:FixedPoint = floattype(T) maybe_floattype(::Type{CT}) where CT<:Color = base_color_type(CT){maybe_floattype(eltype(CT))} + + +""" + fdiv(Vs::AbstractArray...; boundary=:periodic) + +Discrete divergence operator for vector field (V₁, V₂, ..., Vₙ). + +# Example + +Laplacian operator of array `A` is the divergence of its gradient vector field (∂₁A, ∂₂A, ..., ∂ₙA): + +```jldoctest +julia> using ImageFiltering, ImageBase + +julia> X = Float32.(rand(1:9, 7, 7)); + +julia> laplacian(X) = fdiv(ntuple(i->DiffView(X, dims=i), ndims(X))...) +laplacian (generic function with 1 method) + +julia> laplacian(X) == imfilter(X, Kernel.Laplacian(), "circular") +true +``` + +See also [`fdiv!`](@ref) for the in-place version. +""" +function fdiv(V₁::AbstractArray, Vs...; kwargs...) + fdiv!(similar(V₁, floattype(eltype(V₁))), V₁, Vs...; kwargs...) +end + +""" + fdiv!(dst::AbstractArray, Vs::AbstractArray...) + +The in-place version of [`fdiv`](@ref). +""" +function fdiv!(dst::AbstractArray, Vs::AbstractArray...) + ∇ = map(ntuple(identity, length(Vs)), Vs) do n, V + DiffView(V, Val(true), dims=n) + end + @inbounds for i in CartesianIndices(dst) + dst[i] = sum(x->_inbound_getindex(x, i), ∇) + end + return dst +end + +@inline _inbound_getindex(x, i) = @inbounds x[i] diff --git a/test/diff.jl b/test/diff.jl index 89ed9e0..e4f6e45 100644 --- a/test/diff.jl +++ b/test/diff.jl @@ -107,3 +107,48 @@ @test fdiff(A, dims=1) == fdiff(float.(A), dims=1) end end + +@testset "DiffView" begin + A = rand(6) + @test DiffView(A) === + DiffView(A, Val(false), ImageBase.Periodic()) === + DiffView(A, ImageBase.Periodic(), Val(false)) === + @test_logs((:warn, "Please use `Val(false)` for performance"), DiffView(A, false)) + + for T in generate_test_types([N0f8, Float32], [Gray, RGB]) + A = rand(T, 6) + Av = DiffView(A) + @test Av == DiffView(A, ImageBase.Periodic(), Val(false)) + @test eltype(Av) == floattype(T) + @test axes(Av) == axes(A) + @test Av == fdiff(A) + @test DiffView(A, Val(true)) == fdiff(A; rev=true) + @test DiffView(A, ImageBase.ZeroFill()) == fdiff(A; boundary=:zero) + @test DiffView(A, ImageBase.ZeroFill(), Val(true)) == fdiff(A; boundary=:zero, rev=true) + + A = rand(T, 6, 6) + Av = DiffView(A, dims=1) + @test eltype(Av) == floattype(T) + @test axes(Av) == axes(A) + @test Av == fdiff(A, dims=1) + @test DiffView(A, Val(true), dims=1) == fdiff(A; dims=1, rev=true) + @test DiffView(A, ImageBase.ZeroFill(), dims=1) == fdiff(A; boundary=:zero, dims=1) + @test DiffView(A, ImageBase.ZeroFill(), Val(true), dims=1) == fdiff(A; boundary=:zero, rev=true, dims=1) + end + + A = OffsetArray(rand(6, 6), -1, -1) + Av = DiffView(A, dims=1) + @test axes(Av) == axes(A) + @test Av == fdiff(A, dims=1) +end + +@testset "fdiv" begin + laplacian(X) = fdiv(ntuple(i->DiffView(X, dims=i), ndims(X))...) + ref_laplacian(X) = imfilter(X, Kernel.Laplacian(ntuple(x->true, ndims(X))), "circular") + for T in generate_test_types([N0f8, Float32], [Gray, RGB]) + for sz in [(7,), (7, 7), (7, 7, 7)] + A = rand(T, sz...) + @test laplacian(A) ≈ ref_laplacian(A) + end + end +end diff --git a/test/runtests.jl b/test/runtests.jl index 6617826..3579d58 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -1,4 +1,5 @@ using ImageBase, OffsetArrays, StackViews +using ImageFiltering using Test, TestImages, Aqua, Documenter using OffsetArrays: IdentityUnitRange From 8ca4b6fe3b2a8250080cf6ea737ff2d319fca7f7 Mon Sep 17 00:00:00 2001 From: Johnny Chen Date: Tue, 12 Oct 2021 04:56:18 +0800 Subject: [PATCH 2/4] CI compatibility fix for Julia < 1.3 --- .github/workflows/UnitTest.yml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/.github/workflows/UnitTest.yml b/.github/workflows/UnitTest.yml index 968caad..8ac1685 100644 --- a/.github/workflows/UnitTest.yml +++ b/.github/workflows/UnitTest.yml @@ -48,6 +48,14 @@ jobs: ${{ runner.os }}-test- ${{ runner.os }}- - uses: julia-actions/julia-buildpkg@v1 + - name: "Compat fix for Julia < v1.3.0" + if: ${{ matrix.version == '1.0' }} + run: | + using Pkg + Pkg.add([ + PackageSpec(name="AbstractFFTs", version="0.5"), + ]) + shell: julia --project=. --startup=no --color=yes {0} - uses: julia-actions/julia-runtest@v1 - uses: julia-actions/julia-processcoverage@v1 - uses: codecov/codecov-action@v1 From e72cb9a2d1fa91b7fa7184a54ae0cc85db1a7bea Mon Sep 17 00:00:00 2001 From: Johnny Chen Date: Tue, 12 Oct 2021 18:48:31 +0800 Subject: [PATCH 3/4] disable meta quality test in old Julia versions Maintaining old compatibility becomes quite troublesome especially when we have 1.6 as the new LTS version now. --- test/runtests.jl | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/test/runtests.jl b/test/runtests.jl index 3579d58..4269e49 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -6,19 +6,18 @@ using OffsetArrays: IdentityUnitRange include("testutils.jl") @testset "ImageBase.jl" begin - @testset "Project meta quality checks" begin - # Not checking compat section for test-only dependencies - Aqua.test_ambiguities(ImageBase) - Aqua.test_all(ImageBase; - ambiguities=false, - project_extras=true, - deps_compat=true, - stale_deps=true, - project_toml_formatting=true - ) - if VERSION >= v"1.2" - doctest(ImageBase,manual = false) + if VERSION >= v"1.3" + # Not checking compat section for test-only dependencies + Aqua.test_ambiguities(ImageBase) + Aqua.test_all(ImageBase; + ambiguities=false, + project_extras=true, + deps_compat=true, + stale_deps=true, + project_toml_formatting=true + ) + doctest(ImageBase, manual = false) end end From 39058e681884643376d6e4884ff30a52d5c5996b Mon Sep 17 00:00:00 2001 From: Johnny Chen Date: Tue, 12 Oct 2021 22:57:01 +0800 Subject: [PATCH 4/4] move part of getindex computation to compilation time Also add `laplacian` and `laplacian!` --- src/ImageBase.jl | 2 + src/diff.jl | 159 ++++++++++++++++++++++++++--------------------- test/diff.jl | 31 ++++----- 3 files changed, 102 insertions(+), 90 deletions(-) diff --git a/src/ImageBase.jl b/src/ImageBase.jl index 9c5f56c..57b60bc 100644 --- a/src/ImageBase.jl +++ b/src/ImageBase.jl @@ -11,6 +11,8 @@ export fdiff!, fdiv, fdiv!, + flaplacian, + flaplacian!, DiffView, # basic image statistics, from Images.jl diff --git a/src/diff.jl b/src/diff.jl index 7939d2e..ebd1ea3 100644 --- a/src/diff.jl +++ b/src/diff.jl @@ -3,15 +3,17 @@ struct Periodic <: BoundaryCondition end struct ZeroFill <: BoundaryCondition end """ - DiffView(A::AbstractArray, [rev=Val(false)], [bc::BoundaryCondition=Periodic()]; dims) + DiffView(A::AbstractArray, dims::Val{D}, [bc::BoundaryCondition=Periodic()], [rev=Val(false)]) Lazy version of finite difference [`fdiff`](@ref). !!! tip - For performance, `rev` should be stable type `Val(false)` or `Val(true)`. + For performance, both `dims` and `rev` require `Val` types. # Arguments +- `dims::Val{D}` + Specify the dimension D that dinite difference is applied to. - `rev::Bool` If `rev==Val(true)`, then it computes the backward difference `(A[end]-A[1], A[1]-A[2], ..., A[end-1]-A[end])`. @@ -19,76 +21,70 @@ Lazy version of finite difference [`fdiff`](@ref). By default it computes periodically in the boundary, i.e., `Periodic()`. In some cases, one can fill zero values with `ZeroFill()`. """ -struct DiffView{T,N,AT<:AbstractArray,BC,REV} <: AbstractArray{T,N} +struct DiffView{T,N,D,BC,REV,AT<:AbstractArray} <: AbstractArray{T,N} data::AT - dims::Int end function DiffView( data::AbstractArray{T,N}, + ::Val{D}, bc::BoundaryCondition=Periodic(), - rev::Union{Val, Bool}=Val(false); - dims=_fdiff_default_dims(data)) where {T,N} - isnothing(dims) && throw(UndefKeywordError(:dims)) - rev = to_static_bool(rev) - DiffView{maybe_floattype(T),N,typeof(data),typeof(bc),typeof(rev)}(data, dims) -end -function DiffView( - data::AbstractArray, - rev::Union{Val, Bool}, - bc::BoundaryCondition = Periodic(); - kwargs...) - DiffView(data, bc, rev; kwargs...) -end - -to_static_bool(x::Union{Val{true},Val{false}}) = x -function to_static_bool(x::Bool) - @warn "Please use `Val($x)` for performance" - return Val(x) + rev::Val = Val(false) + ) where {T,N,D} + DiffView{maybe_floattype(T),N,D,typeof(bc),typeof(rev),typeof(data)}(data) end Base.size(A::DiffView) = size(A.data) Base.axes(A::DiffView) = axes(A.data) Base.IndexStyle(::DiffView) = IndexCartesian() -Base.@propagate_inbounds function Base.getindex(A::DiffView{T,N,AT,Periodic,Val{true}}, I::Vararg{Int, N}) where {T,N,AT} +Base.@propagate_inbounds function Base.getindex(A::DiffView{T,N,D,Periodic,Val{true}}, I::Vararg{Int, N}) where {T,N,D} data = A.data - I_prev = map(ntuple(identity, N), I, axes(data)) do i, p, r - i == A.dims || return p - p == first(r) && return last(r) - p - 1 - end + r = axes(data, D) + x = I[D] + x_prev = first(r) == x ? last(r) : x - 1 + I_prev = update_tuple(I, x_prev, Val(D)) return convert(T, data[I...]) - convert(T, data[I_prev...]) end -Base.@propagate_inbounds function Base.getindex(A::DiffView{T,N,AT,Periodic,Val{false}}, I::Vararg{Int, N}) where {T,N,AT} +Base.@propagate_inbounds function Base.getindex(A::DiffView{T,N,D,Periodic,Val{false}}, I::Vararg{Int, N}) where {T,N,D} data = A.data - I_next = map(ntuple(identity, N), I, axes(data)) do i, p, r - i == A.dims || return p - p == last(r) && return first(r) - p + 1 - end + r = axes(data, D) + x = I[D] + x_next = last(r) == x ? first(r) : x + 1 + I_next = update_tuple(I, x_next, Val(D)) return convert(T, data[I_next...]) - convert(T, data[I...]) end -Base.@propagate_inbounds function Base.getindex(A::DiffView{T,N,AT,ZeroFill,Val{false}}, I::Vararg{Int, N}) where {T,N,AT} +Base.@propagate_inbounds function Base.getindex(A::DiffView{T,N,D,ZeroFill,Val{false}}, I::Vararg{Int, N}) where {T,N,D} data = A.data - I_next = I .+ ntuple(i->i==A.dims, N) - if checkbounds(Bool, data, I_next...) - vi = convert(T, data[I...]) # it requires the caller to pass @inbounds - @inbounds convert(T, data[I_next...]) - vi - else + x = I[D] + if last(axes(data, D)) == x zero(T) + else + I_next = update_tuple(I, x+1, Val(D)) + convert(T, data[I_next...]) - convert(T, data[I...]) end end -Base.@propagate_inbounds function Base.getindex(A::DiffView{T,N,AT,ZeroFill,Val{true}}, I::Vararg{Int, N}) where {T,N,AT} +Base.@propagate_inbounds function Base.getindex(A::DiffView{T,N,D,ZeroFill,Val{true}}, I::Vararg{Int, N}) where {T,N,D} data = A.data - I_prev = I .- ntuple(i->i==A.dims, N) - if checkbounds(Bool, data, I_prev...) - vi = convert(T, data[I...]) # it requires the caller to pass @inbounds - @inbounds vi - convert(T, data[I_prev...]) - else + x = I[D] + if first(axes(data, D)) == x zero(T) + else + I_prev = update_tuple(I, x-1, Val(D)) + convert(T, data[I...]) - convert(T, data[I_prev...]) end end +@generated function update_tuple(A::NTuple{N, T}, x::T, ::Val{i}) where {T, N, i} + # This is equivalent to `ntuple(j->j==i ? x : A[j], N)` but is optimized by moving + # the if branches to compilation time. + ex = :() + for j in Base.OneTo(N) + new_x = i == j ? :(x) : :(A[$j]) + ex = :($ex..., $new_x) + end + return ex +end + # TODO: add keyword `shrink` to give a consistant result on Base # when this is done, then we can propose this change to upstream Base """ @@ -201,31 +197,13 @@ maybe_floattype(::Type{CT}) where CT<:Color = base_color_type(CT){maybe_floattyp """ - fdiv(Vs::AbstractArray...; boundary=:periodic) + fdiv(Vs::AbstractArray...) Discrete divergence operator for vector field (V₁, V₂, ..., Vₙ). -# Example - -Laplacian operator of array `A` is the divergence of its gradient vector field (∂₁A, ∂₂A, ..., ∂ₙA): - -```jldoctest -julia> using ImageFiltering, ImageBase - -julia> X = Float32.(rand(1:9, 7, 7)); - -julia> laplacian(X) = fdiv(ntuple(i->DiffView(X, dims=i), ndims(X))...) -laplacian (generic function with 1 method) - -julia> laplacian(X) == imfilter(X, Kernel.Laplacian(), "circular") -true -``` - See also [`fdiv!`](@ref) for the in-place version. """ -function fdiv(V₁::AbstractArray, Vs...; kwargs...) - fdiv!(similar(V₁, floattype(eltype(V₁))), V₁, Vs...; kwargs...) -end +fdiv(V₁::AbstractArray, Vs...) = fdiv!(similar(V₁, floattype(eltype(V₁))), V₁, Vs...) """ fdiv!(dst::AbstractArray, Vs::AbstractArray...) @@ -233,13 +211,52 @@ end The in-place version of [`fdiv`](@ref). """ function fdiv!(dst::AbstractArray, Vs::AbstractArray...) - ∇ = map(ntuple(identity, length(Vs)), Vs) do n, V - DiffView(V, Val(true), dims=n) - end + # negative adjoint of gradient is equivalent to the reversed finite difference + ∇ = fnegative_adjoint_gradient(Vs...) @inbounds for i in CartesianIndices(dst) - dst[i] = sum(x->_inbound_getindex(x, i), ∇) + dst[i] = heterogeneous_getindex_sum(i, ∇...) end return dst end -@inline _inbound_getindex(x, i) = @inbounds x[i] +@generated function heterogeneous_getindex_sum(i, Vs::Vararg{<:AbstractArray, N}) where N + # This method is equivalent to `sum(V->V[i], Vs)` but is optimized for heterogeneous arrays + ex = :(zero(eltype(Vs[1]))) + for j in Base.OneTo(N) + ex = :($ex + Vs[$j][i]) + end + return ex +end + +""" + flaplacian(X::AbstractArray) + +The Laplacian operator ∇² is the divergence of the gradient operator. +""" +flaplacian(X::AbstractArray) = flaplacian!(similar(X, maybe_floattype(eltype(X))), X) + +""" + flaplacian!(dst::AbstractArray, X::AbstractArray) + +The in-place version of the Laplacian operator [`laplacian`](@ref). +""" +flaplacian!(dst::AbstractArray, X::AbstractArray) = fdiv!(dst, fgradient(X)...) + +# These two functions pass dimension information `Val(i)` to DiffView so that +# we can move computations to compilation time. +@generated function fgradient(X::AbstractArray{T, N}) where {T, N} + ex = :() + for i in Base.OneTo(N) + new_x = :(DiffView(X, Val($i), Periodic(), Val(false))) + ex = :($ex..., $new_x) + end + return ex +end +@generated function fnegative_adjoint_gradient(Vs::Vararg{<:AbstractArray, N}) where N + ex = :() + for i in Base.OneTo(N) + new_x = :(DiffView(Vs[$i], Val($i), Periodic(), Val(true))) + ex = :($ex..., $new_x) + end + return ex +end diff --git a/test/diff.jl b/test/diff.jl index e4f6e45..dd390d7 100644 --- a/test/diff.jl +++ b/test/diff.jl @@ -109,46 +109,39 @@ end @testset "DiffView" begin - A = rand(6) - @test DiffView(A) === - DiffView(A, Val(false), ImageBase.Periodic()) === - DiffView(A, ImageBase.Periodic(), Val(false)) === - @test_logs((:warn, "Please use `Val(false)` for performance"), DiffView(A, false)) - for T in generate_test_types([N0f8, Float32], [Gray, RGB]) A = rand(T, 6) - Av = DiffView(A) - @test Av == DiffView(A, ImageBase.Periodic(), Val(false)) + Av = DiffView(A, Val(1)) + @test Av == DiffView(A, Val(1), ImageBase.Periodic(), Val(false)) @test eltype(Av) == floattype(T) @test axes(Av) == axes(A) @test Av == fdiff(A) - @test DiffView(A, Val(true)) == fdiff(A; rev=true) - @test DiffView(A, ImageBase.ZeroFill()) == fdiff(A; boundary=:zero) - @test DiffView(A, ImageBase.ZeroFill(), Val(true)) == fdiff(A; boundary=:zero, rev=true) + @test DiffView(A, Val(1), ImageBase.Periodic(), Val(true)) == fdiff(A; rev=true) + @test DiffView(A, Val(1), ImageBase.ZeroFill()) == fdiff(A; boundary=:zero) + @test DiffView(A, Val(1), ImageBase.ZeroFill(), Val(true)) == fdiff(A; boundary=:zero, rev=true) A = rand(T, 6, 6) - Av = DiffView(A, dims=1) + Av = DiffView(A, Val(1)) @test eltype(Av) == floattype(T) @test axes(Av) == axes(A) @test Av == fdiff(A, dims=1) - @test DiffView(A, Val(true), dims=1) == fdiff(A; dims=1, rev=true) - @test DiffView(A, ImageBase.ZeroFill(), dims=1) == fdiff(A; boundary=:zero, dims=1) - @test DiffView(A, ImageBase.ZeroFill(), Val(true), dims=1) == fdiff(A; boundary=:zero, rev=true, dims=1) + @test DiffView(A, Val(1), ImageBase.Periodic(), Val(true)) == fdiff(A; dims=1, rev=true) + @test DiffView(A, Val(1), ImageBase.ZeroFill()) == fdiff(A; boundary=:zero, dims=1) + @test DiffView(A, Val(1), ImageBase.ZeroFill(), Val(true)) == fdiff(A; boundary=:zero, rev=true, dims=1) end A = OffsetArray(rand(6, 6), -1, -1) - Av = DiffView(A, dims=1) + Av = DiffView(A, Val(1)) @test axes(Av) == axes(A) @test Av == fdiff(A, dims=1) end -@testset "fdiv" begin - laplacian(X) = fdiv(ntuple(i->DiffView(X, dims=i), ndims(X))...) +@testset "fdiv/flaplacian" begin ref_laplacian(X) = imfilter(X, Kernel.Laplacian(ntuple(x->true, ndims(X))), "circular") for T in generate_test_types([N0f8, Float32], [Gray, RGB]) for sz in [(7,), (7, 7), (7, 7, 7)] A = rand(T, sz...) - @test laplacian(A) ≈ ref_laplacian(A) + @test flaplacian(A) ≈ ref_laplacian(A) end end end