Skip to content

Commit 4b181ed

Browse files
committed
implement DiffView and fdiv
1 parent d66aa8a commit 4b181ed

File tree

5 files changed

+191
-2
lines changed

5 files changed

+191
-2
lines changed

Project.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ julia = "1"
1414
[extras]
1515
Aqua = "4c88cf16-eb10-579e-8560-4a9242c79595"
1616
Documenter = "e30172f5-a6a5-5a46-863b-614d45cd2de4"
17+
ImageFiltering = "6a3955dd-da59-5b1f-98d4-e7296123deb5"
1718
ImageIO = "82e4d734-157c-48bb-816b-45c225c6df19"
1819
ImageMagick = "6218d12a-5da1-5696-b52f-db25d2ecc6d1"
1920
OffsetArrays = "6fe1bfb0-de20-5000-8ca7-80f57d26f881"
@@ -23,4 +24,4 @@ Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"
2324
TestImages = "5e47fb64-e119-507b-a336-dd2b206d9990"
2425

2526
[targets]
26-
test = ["Aqua", "Documenter", "Test", "ImageIO", "ImageMagick", "OffsetArrays", "Statistics", "StackViews", "TestImages"]
27+
test = ["Aqua", "Documenter", "Test", "ImageFiltering", "ImageIO", "ImageMagick", "OffsetArrays", "Statistics", "StackViews", "TestImages"]

src/ImageBase.jl

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,9 @@ export
99
# originally from Images.jl
1010
fdiff,
1111
fdiff!,
12+
fdiv,
13+
fdiv!,
14+
DiffView,
1215

1316
# basic image statistics, from Images.jl
1417
minimum_finite,

src/diff.jl

Lines changed: 140 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,94 @@
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+
192
# TODO: add keyword `shrink` to give a consistant result on Base
293
# when this is done, then we can propose this change to upstream Base
394
"""
@@ -52,13 +143,16 @@ julia> fdiff(A, dims=2, boundary=:zero) # fill boundary with zeros
52143
```
53144
54145
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.
55149
"""
56150
fdiff(A::AbstractArray; kwargs...) = fdiff!(similar(A, maybe_floattype(eltype(A))), A; kwargs...)
57151

58152
"""
59153
fdiff!(dst::AbstractArray, src::AbstractArray; dims::Int, rev=false, boundary=:periodic)
60154
61-
The in-place version of [`ImageBase.fdiff`](@ref)
155+
The in-place version of [`fdiff`](@ref).
62156
"""
63157
function fdiff!(dst::AbstractArray, src::AbstractArray;
64158
dims=_fdiff_default_dims(src),
@@ -106,3 +200,48 @@ _fdiff_default_dims(A::AbstractVector) = 1
106200
maybe_floattype(::Type{T}) where T = T
107201
maybe_floattype(::Type{T}) where T<:FixedPoint = floattype(T)
108202
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]

test/diff.jl

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,3 +107,48 @@
107107
@test fdiff(A, dims=1) == fdiff(float.(A), dims=1)
108108
end
109109
end
110+
111+
@testset "DiffView" begin
112+
A = rand(6)
113+
@test DiffView(A) ===
114+
DiffView(A, Val(false), ImageBase.Periodic()) ===
115+
DiffView(A, ImageBase.Periodic(), Val(false)) ===
116+
@test_logs((:warn, "Please use `Val(false)` for performance"), DiffView(A, false))
117+
118+
for T in generate_test_types([N0f8, Float32], [Gray, RGB])
119+
A = rand(T, 6)
120+
Av = DiffView(A)
121+
@test Av == DiffView(A, ImageBase.Periodic(), Val(false))
122+
@test eltype(Av) == floattype(T)
123+
@test axes(Av) == axes(A)
124+
@test Av == fdiff(A)
125+
@test DiffView(A, Val(true)) == fdiff(A; rev=true)
126+
@test DiffView(A, ImageBase.ZeroFill()) == fdiff(A; boundary=:zero)
127+
@test DiffView(A, ImageBase.ZeroFill(), Val(true)) == fdiff(A; boundary=:zero, rev=true)
128+
129+
A = rand(T, 6, 6)
130+
Av = DiffView(A, dims=1)
131+
@test eltype(Av) == floattype(T)
132+
@test axes(Av) == axes(A)
133+
@test Av == fdiff(A, dims=1)
134+
@test DiffView(A, Val(true), dims=1) == fdiff(A; dims=1, rev=true)
135+
@test DiffView(A, ImageBase.ZeroFill(), dims=1) == fdiff(A; boundary=:zero, dims=1)
136+
@test DiffView(A, ImageBase.ZeroFill(), Val(true), dims=1) == fdiff(A; boundary=:zero, rev=true, dims=1)
137+
end
138+
139+
A = OffsetArray(rand(6, 6), -1, -1)
140+
Av = DiffView(A, dims=1)
141+
@test axes(Av) == axes(A)
142+
@test Av == fdiff(A, dims=1)
143+
end
144+
145+
@testset "fdiv" begin
146+
laplacian(X) = fdiv(ntuple(i->DiffView(X, dims=i), ndims(X))...)
147+
ref_laplacian(X) = imfilter(X, Kernel.Laplacian(ntuple(x->true, ndims(X))), "circular")
148+
for T in generate_test_types([N0f8, Float32], [Gray, RGB])
149+
for sz in [(7,), (7, 7), (7, 7, 7)]
150+
A = rand(T, sz...)
151+
@test laplacian(A) ref_laplacian(A)
152+
end
153+
end
154+
end

test/runtests.jl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using ImageBase, OffsetArrays, StackViews
2+
using ImageFiltering
23
using Test, TestImages, Aqua, Documenter
34

45
using OffsetArrays: IdentityUnitRange

0 commit comments

Comments
 (0)