@@ -3,92 +3,88 @@ struct Periodic <: BoundaryCondition end
3
3
struct ZeroFill <: BoundaryCondition end
4
4
5
5
"""
6
- DiffView(A::AbstractArray, [rev= Val(false)] , [bc::BoundaryCondition=Periodic()]; dims )
6
+ DiffView(A::AbstractArray, dims:: Val{D} , [bc::BoundaryCondition=Periodic()], [rev=Val(false)] )
7
7
8
8
Lazy version of finite difference [`fdiff`](@ref).
9
9
10
10
!!! tip
11
- For performance, `rev` should be stable type `Val(false)` or `Val(true)` .
11
+ For performance, both `dims` and `rev` require `Val` types .
12
12
13
13
# Arguments
14
14
15
+ - `dims::Val{D}`
16
+ Specify the dimension D that dinite difference is applied to.
15
17
- `rev::Bool`
16
18
If `rev==Val(true)`, then it computes the backward difference
17
19
`(A[end]-A[1], A[1]-A[2], ..., A[end-1]-A[end])`.
18
20
- `boundary::BoundaryCondition`
19
21
By default it computes periodically in the boundary, i.e., `Periodic()`.
20
22
In some cases, one can fill zero values with `ZeroFill()`.
21
23
"""
22
- struct DiffView{T,N,AT <: AbstractArray ,BC,REV} <: AbstractArray{T,N}
24
+ struct DiffView{T,N,D ,BC,REV,AT <: AbstractArray } <: AbstractArray{T,N}
23
25
data:: AT
24
- dims:: Int
25
26
end
26
27
function DiffView (
27
28
data:: AbstractArray{T,N} ,
29
+ :: Val{D} ,
28
30
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)
31
+ rev:: Val = Val (false )
32
+ ) where {T,N,D}
33
+ DiffView {maybe_floattype(T),N,D,typeof(bc),typeof(rev),typeof(data)} (data)
47
34
end
48
35
49
36
Base. size (A:: DiffView ) = size (A. data)
50
37
Base. axes (A:: DiffView ) = axes (A. data)
51
38
Base. IndexStyle (:: DiffView ) = IndexCartesian ()
52
39
53
- Base. @propagate_inbounds function Base. getindex (A:: DiffView{T,N,AT ,Periodic,Val{true}} , I:: Vararg{Int, N} ) where {T,N,AT }
40
+ Base. @propagate_inbounds function Base. getindex (A:: DiffView{T,N,D ,Periodic,Val{true}} , I:: Vararg{Int, N} ) where {T,N,D }
54
41
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
42
+ r = axes (data, D)
43
+ x = I[D]
44
+ x_prev = first (r) == x ? last (r) : x - 1
45
+ I_prev = update_tuple (I, x_prev, Val (D))
60
46
return convert (T, data[I... ]) - convert (T, data[I_prev... ])
61
47
end
62
- Base. @propagate_inbounds function Base. getindex (A:: DiffView{T,N,AT ,Periodic,Val{false}} , I:: Vararg{Int, N} ) where {T,N,AT }
48
+ Base. @propagate_inbounds function Base. getindex (A:: DiffView{T,N,D ,Periodic,Val{false}} , I:: Vararg{Int, N} ) where {T,N,D }
63
49
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
50
+ r = axes (data, D)
51
+ x = I[D]
52
+ x_next = last (r) == x ? first (r) : x + 1
53
+ I_next = update_tuple (I, x_next, Val (D))
69
54
return convert (T, data[I_next... ]) - convert (T, data[I... ])
70
55
end
71
- Base. @propagate_inbounds function Base. getindex (A:: DiffView{T,N,AT ,ZeroFill,Val{false}} , I:: Vararg{Int, N} ) where {T,N,AT }
56
+ Base. @propagate_inbounds function Base. getindex (A:: DiffView{T,N,D ,ZeroFill,Val{false}} , I:: Vararg{Int, N} ) where {T,N,D }
72
57
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
58
+ x = I[D]
59
+ if last (axes (data, D)) == x
78
60
zero (T)
61
+ else
62
+ I_next = update_tuple (I, x+ 1 , Val (D))
63
+ convert (T, data[I_next... ]) - convert (T, data[I... ])
79
64
end
80
65
end
81
- Base. @propagate_inbounds function Base. getindex (A:: DiffView{T,N,AT ,ZeroFill,Val{true}} , I:: Vararg{Int, N} ) where {T,N,AT }
66
+ Base. @propagate_inbounds function Base. getindex (A:: DiffView{T,N,D ,ZeroFill,Val{true}} , I:: Vararg{Int, N} ) where {T,N,D }
82
67
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
68
+ x = I[D]
69
+ if first (axes (data, D)) == x
88
70
zero (T)
71
+ else
72
+ I_prev = update_tuple (I, x- 1 , Val (D))
73
+ convert (T, data[I... ]) - convert (T, data[I_prev... ])
89
74
end
90
75
end
91
76
77
+ @generated function update_tuple (A:: NTuple{N, T} , x:: T , :: Val{i} ) where {T, N, i}
78
+ # This is equivalent to `ntuple(j->j==i ? x : A[j], N)` but is optimized by moving
79
+ # the if branches to compilation time.
80
+ ex = :()
81
+ for j in Base. OneTo (N)
82
+ new_x = i == j ? :(x) : :(A[$ j])
83
+ ex = :($ ex... , $ new_x)
84
+ end
85
+ return ex
86
+ end
87
+
92
88
# TODO : add keyword `shrink` to give a consistant result on Base
93
89
# when this is done, then we can propose this change to upstream Base
94
90
"""
@@ -201,45 +197,66 @@ maybe_floattype(::Type{CT}) where CT<:Color = base_color_type(CT){maybe_floattyp
201
197
202
198
203
199
"""
204
- fdiv(Vs::AbstractArray...; boundary=:periodic )
200
+ fdiv(Vs::AbstractArray...)
205
201
206
202
Discrete divergence operator for vector field (V₁, V₂, ..., Vₙ).
207
203
208
- # Example
209
-
210
- Laplacian operator of array `A` is the divergence of its gradient vector field (∂₁A, ∂₂A, ..., ∂ₙA):
211
-
212
- ```jldoctest
213
- julia> using ImageFiltering, ImageBase
214
-
215
- julia> X = Float32.(rand(1:9, 7, 7));
216
-
217
- julia> laplacian(X) = fdiv(ntuple(i->DiffView(X, dims=i), ndims(X))...)
218
- laplacian (generic function with 1 method)
219
-
220
- julia> laplacian(X) == imfilter(X, Kernel.Laplacian(), "circular")
221
- true
222
- ```
223
-
224
204
See also [`fdiv!`](@ref) for the in-place version.
225
205
"""
226
- function fdiv (V₁:: AbstractArray , Vs... ; kwargs... )
227
- fdiv! (similar (V₁, floattype (eltype (V₁))), V₁, Vs... ; kwargs... )
228
- end
206
+ fdiv (V₁:: AbstractArray , Vs... ) = fdiv! (similar (V₁, floattype (eltype (V₁))), V₁, Vs... )
229
207
230
208
"""
231
209
fdiv!(dst::AbstractArray, Vs::AbstractArray...)
232
210
233
211
The in-place version of [`fdiv`](@ref).
234
212
"""
235
213
function fdiv! (dst:: AbstractArray , Vs:: AbstractArray... )
236
- ∇ = map (ntuple (identity, length (Vs)), Vs) do n, V
237
- DiffView (V, Val (true ), dims= n)
238
- end
214
+ # negative adjoint of gradient is equivalent to the reversed finite difference
215
+ ∇ = fnegative_adjoint_gradient (Vs... )
239
216
@inbounds for i in CartesianIndices (dst)
240
- dst[i] = sum (x -> _inbound_getindex (x, i) , ∇)
217
+ dst[i] = heterogeneous_getindex_sum (i , ∇... )
241
218
end
242
219
return dst
243
220
end
244
221
245
- @inline _inbound_getindex (x, i) = @inbounds x[i]
222
+ @generated function heterogeneous_getindex_sum (i, Vs:: Vararg{<:AbstractArray, N} ) where N
223
+ # This method is equivalent to `sum(V->V[i], Vs)` but is optimized for heterogeneous arrays
224
+ ex = :(zero (eltype (Vs[1 ])))
225
+ for j in Base. OneTo (N)
226
+ ex = :($ ex + Vs[$ j][i])
227
+ end
228
+ return ex
229
+ end
230
+
231
+ """
232
+ flaplacian(X::AbstractArray)
233
+
234
+ The Laplacian operator ∇² is the divergence of the gradient operator.
235
+ """
236
+ flaplacian (X:: AbstractArray ) = flaplacian! (similar (X, maybe_floattype (eltype (X))), X)
237
+
238
+ """
239
+ flaplacian!(dst::AbstractArray, X::AbstractArray)
240
+
241
+ The in-place version of the Laplacian operator [`laplacian`](@ref).
242
+ """
243
+ flaplacian! (dst:: AbstractArray , X:: AbstractArray ) = fdiv! (dst, fgradient (X)... )
244
+
245
+ # These two functions pass dimension information `Val(i)` to DiffView so that
246
+ # we can move computations to compilation time.
247
+ @generated function fgradient (X:: AbstractArray{T, N} ) where {T, N}
248
+ ex = :()
249
+ for i in Base. OneTo (N)
250
+ new_x = :(DiffView (X, Val ($ i), Periodic (), Val (false )))
251
+ ex = :($ ex... , $ new_x)
252
+ end
253
+ return ex
254
+ end
255
+ @generated function fnegative_adjoint_gradient (Vs:: Vararg{<:AbstractArray, N} ) where N
256
+ ex = :()
257
+ for i in Base. OneTo (N)
258
+ new_x = :(DiffView (Vs[$ i], Val ($ i), Periodic (), Val (true )))
259
+ ex = :($ ex... , $ new_x)
260
+ end
261
+ return ex
262
+ end
0 commit comments