|
| 1 | +abstract type BoundaryCondition end |
| 2 | +struct Periodic <: BoundaryCondition end |
| 3 | +struct ZeroFill <: BoundaryCondition end |
| 4 | + |
| 5 | +""" |
| 6 | + DiffView(A::AbstractArray, [rev=Val(false)], [bc::BoundaryCondition=Periodic()]; dims) |
| 7 | +
|
| 8 | +Lazy version of finite difference [`fdiff`](@ref). |
| 9 | +
|
| 10 | +!!! tip |
| 11 | + For performance, `rev` should be stable type `Val(false)` or `Val(true)`. |
| 12 | +
|
| 13 | +# Arguments |
| 14 | +
|
| 15 | +- `rev::Bool` |
| 16 | + If `rev==Val(true)`, then it computes the backward difference |
| 17 | + `(A[end]-A[1], A[1]-A[2], ..., A[end-1]-A[end])`. |
| 18 | +- `boundary::BoundaryCondition` |
| 19 | + By default it computes periodically in the boundary, i.e., `Periodic()`. |
| 20 | + In some cases, one can fill zero values with `ZeroFill()`. |
| 21 | +""" |
| 22 | +struct DiffView{T,N,AT<:AbstractArray,BC,REV} <: AbstractArray{T,N} |
| 23 | + data::AT |
| 24 | + dims::Int |
| 25 | +end |
| 26 | +function DiffView( |
| 27 | + data::AbstractArray{T,N}, |
| 28 | + bc::BoundaryCondition=Periodic(), |
| 29 | + rev::Union{Val, Bool}=Val(false); |
| 30 | + dims=_fdiff_default_dims(data)) where {T,N} |
| 31 | + isnothing(dims) && throw(UndefKeywordError(:dims)) |
| 32 | + rev = to_static_bool(rev) |
| 33 | + DiffView{maybe_floattype(T),N,typeof(data),typeof(bc),typeof(rev)}(data, dims) |
| 34 | +end |
| 35 | +function DiffView( |
| 36 | + data::AbstractArray, |
| 37 | + rev::Union{Val, Bool}, |
| 38 | + bc::BoundaryCondition = Periodic(); |
| 39 | + kwargs...) |
| 40 | + DiffView(data, bc, rev; kwargs...) |
| 41 | +end |
| 42 | + |
| 43 | +to_static_bool(x::Union{Val{true},Val{false}}) = x |
| 44 | +function to_static_bool(x::Bool) |
| 45 | + @warn "Please use `Val($x)` for performance" |
| 46 | + return Val(x) |
| 47 | +end |
| 48 | + |
| 49 | +Base.size(A::DiffView) = size(A.data) |
| 50 | +Base.axes(A::DiffView) = axes(A.data) |
| 51 | +Base.IndexStyle(::DiffView) = IndexCartesian() |
| 52 | + |
| 53 | +Base.@propagate_inbounds function Base.getindex(A::DiffView{T,N,AT,Periodic,Val{true}}, I::Vararg{Int, N}) where {T,N,AT} |
| 54 | + data = A.data |
| 55 | + I_prev = map(ntuple(identity, N), I, axes(data)) do i, p, r |
| 56 | + i == A.dims || return p |
| 57 | + p == first(r) && return last(r) |
| 58 | + p - 1 |
| 59 | + end |
| 60 | + return convert(T, data[I...]) - convert(T, data[I_prev...]) |
| 61 | +end |
| 62 | +Base.@propagate_inbounds function Base.getindex(A::DiffView{T,N,AT,Periodic,Val{false}}, I::Vararg{Int, N}) where {T,N,AT} |
| 63 | + data = A.data |
| 64 | + I_next = map(ntuple(identity, N), I, axes(data)) do i, p, r |
| 65 | + i == A.dims || return p |
| 66 | + p == last(r) && return first(r) |
| 67 | + p + 1 |
| 68 | + end |
| 69 | + return convert(T, data[I_next...]) - convert(T, data[I...]) |
| 70 | +end |
| 71 | +Base.@propagate_inbounds function Base.getindex(A::DiffView{T,N,AT,ZeroFill,Val{false}}, I::Vararg{Int, N}) where {T,N,AT} |
| 72 | + data = A.data |
| 73 | + I_next = I .+ ntuple(i->i==A.dims, N) |
| 74 | + if checkbounds(Bool, data, I_next...) |
| 75 | + vi = convert(T, data[I...]) # it requires the caller to pass @inbounds |
| 76 | + @inbounds convert(T, data[I_next...]) - vi |
| 77 | + else |
| 78 | + zero(T) |
| 79 | + end |
| 80 | +end |
| 81 | +Base.@propagate_inbounds function Base.getindex(A::DiffView{T,N,AT,ZeroFill,Val{true}}, I::Vararg{Int, N}) where {T,N,AT} |
| 82 | + data = A.data |
| 83 | + I_prev = I .- ntuple(i->i==A.dims, N) |
| 84 | + if checkbounds(Bool, data, I_prev...) |
| 85 | + vi = convert(T, data[I...]) # it requires the caller to pass @inbounds |
| 86 | + @inbounds vi - convert(T, data[I_prev...]) |
| 87 | + else |
| 88 | + zero(T) |
| 89 | + end |
| 90 | +end |
| 91 | + |
1 | 92 | # TODO: add keyword `shrink` to give a consistant result on Base
|
2 | 93 | # when this is done, then we can propose this change to upstream Base
|
3 | 94 | """
|
@@ -52,13 +143,16 @@ julia> fdiff(A, dims=2, boundary=:zero) # fill boundary with zeros
|
52 | 143 | ```
|
53 | 144 |
|
54 | 145 | See also [`fdiff!`](@ref) for the in-place version.
|
| 146 | +
|
| 147 | +See also [`adjoint_fdiff`](@ref), [`fdiv`](@ref), and [`flaplacian`](@ref) for other related |
| 148 | +discrete functions. |
55 | 149 | """
|
56 | 150 | fdiff(A::AbstractArray; kwargs...) = fdiff!(similar(A, maybe_floattype(eltype(A))), A; kwargs...)
|
57 | 151 |
|
58 | 152 | """
|
59 | 153 | fdiff!(dst::AbstractArray, src::AbstractArray; dims::Int, rev=false, boundary=:periodic)
|
60 | 154 |
|
61 |
| -The in-place version of [`ImageBase.fdiff`](@ref) |
| 155 | +The in-place version of [`fdiff`](@ref). |
62 | 156 | """
|
63 | 157 | function fdiff!(dst::AbstractArray, src::AbstractArray;
|
64 | 158 | dims=_fdiff_default_dims(src),
|
@@ -106,3 +200,48 @@ _fdiff_default_dims(A::AbstractVector) = 1
|
106 | 200 | maybe_floattype(::Type{T}) where T = T
|
107 | 201 | maybe_floattype(::Type{T}) where T<:FixedPoint = floattype(T)
|
108 | 202 | maybe_floattype(::Type{CT}) where CT<:Color = base_color_type(CT){maybe_floattype(eltype(CT))}
|
| 203 | + |
| 204 | + |
| 205 | +""" |
| 206 | + fdiv(Vs::AbstractArray...; boundary=:periodic) |
| 207 | +
|
| 208 | +Discrete divergence operator for vector field (V₁, V₂, ..., Vₙ). |
| 209 | +
|
| 210 | +# Example |
| 211 | +
|
| 212 | +Laplacian operator of array `A` is the divergence of its gradient vector field (∂₁A, ∂₂A, ..., ∂ₙA): |
| 213 | +
|
| 214 | +```jldoctest |
| 215 | +julia> using ImageFiltering, ImageBase |
| 216 | +
|
| 217 | +julia> X = Float32.(rand(1:9, 7, 7)); |
| 218 | +
|
| 219 | +julia> laplacian(X) = fdiv(ntuple(i->DiffView(X, dims=i), ndims(X))...) |
| 220 | +laplacian (generic function with 1 method) |
| 221 | +
|
| 222 | +julia> laplacian(X) == imfilter(X, Kernel.Laplacian(), "circular") |
| 223 | +true |
| 224 | +``` |
| 225 | +
|
| 226 | +See also [`fdiv!`](@ref) for the in-place version. |
| 227 | +""" |
| 228 | +function fdiv(V₁::AbstractArray, Vs...; kwargs...) |
| 229 | + fdiv!(similar(V₁, floattype(eltype(V₁))), V₁, Vs...; kwargs...) |
| 230 | +end |
| 231 | + |
| 232 | +""" |
| 233 | + fdiv!(dst::AbstractArray, Vs::AbstractArray...) |
| 234 | +
|
| 235 | +The in-place version of [`fdiv`](@ref). |
| 236 | +""" |
| 237 | +function fdiv!(dst::AbstractArray, Vs::AbstractArray...) |
| 238 | + ∇ = map(ntuple(identity, length(Vs)), Vs) do n, V |
| 239 | + DiffView(V, Val(true), dims=n) |
| 240 | + end |
| 241 | + @inbounds for i in CartesianIndices(dst) |
| 242 | + dst[i] = sum(x->_inbound_getindex(x, i), ∇) |
| 243 | + end |
| 244 | + return dst |
| 245 | +end |
| 246 | + |
| 247 | +@inline _inbound_getindex(x, i) = @inbounds x[i] |
0 commit comments