Mon, 06 May 2024 20:03:44 -0500
Add arXiv link for new paper to README
################################ # Code relevant to optical flow ################################ __precompile__() module OpticalFlow using AlgTools.Util using ImageTools.Gradient import ImageTools.Translate using ImageTools.ImFilter # using ImageTransformations # using Images, CoordinateTransformations, Rotations, OffsetArrays # using Interpolations import Images: center, warp import CoordinateTransformations: recenter import Rotations: RotMatrix import Interpolations: Flat ########## # Exports ########## export flow!, pdflow!, flow_grad!, flow_interp!, estimate_Λ², estimate_linear_Λ², pointwise_gradiprod_2d!, pointwise_gradiprod_2dᵀ!, horn_schunck_reg_prox!, horn_schunck_reg_prox_op!, mldivide_step_plus_sym2x2!, linearised_optical_flow_error, Image, AbstractImage, ImageSize, Gradient, Displacement, DisplacementFull, DisplacementConstant, HornSchunckData, filter_hs, petpdflow!, DualScaling, Greedy, Rotation, ZeroDual, PrimalOnly, ActivatedDual, identifier ############################################### # Types (several imported from ImageTools.Translate) ############################################### Image = Translate.Image AbstractImage = AbstractArray{Float64,2} Displacement = Translate.Displacement DisplacementFull = Translate.DisplacementFull DisplacementConstant = Translate.DisplacementConstant Gradient = Array{Float64,3} ImageSize = Tuple{Int64,Int64} ################################# # Struct for flow ################################# struct DualScaling activation :: Function factor :: Float64 threshold :: Real DualScaling(a = x -> x, f = 1.0, t = 1e-12) = new(a, f, t) end struct Greedy end struct Rotation end struct ZeroDual end struct PrimalOnly end struct ActivatedDual end function identifier(::DualScaling) "pdps_known_dualscaling" end function identifier(::Rotation) "pdps_known_rotation" end function identifier(::Greedy) "pdps_known_greedy" end function identifier(::ZeroDual) "pdps_known_zerodual" end function identifier(::PrimalOnly) "pdps_known_primalonly" end function identifier(::ActivatedDual) "pdps_known_activateddual" end ################################# # Displacement field based flow ################################# function flow_interp!(x::AbstractImage, u::Displacement, tmp::AbstractImage; threads = false) tmp .= x Translate.translate_image!(x, tmp, u; threads=threads) end function flow_interp!(x::AbstractImage, u::Displacement; threads = false) tmp = copy(x) Translate.translate_image!(x, tmp, u; threads=threads) end flow! = flow_interp! function pdflow!(x, Δx, y, Δy, u, dual_flow; threads=:none) if dual_flow #flow!((x, @view(y[1, :, :]), @view(y[2, :, :])), diffu, # (Δx, @view(Δy[1, :, :]), @view(Δy[2, :, :]))) @backgroundif (threads==:outer) begin flow!(x, u, Δx; threads=(threads==:inner)) end begin flow!(@view(y[1, :, :]), u, @view(Δy[1, :, :]); threads=(threads==:inner)) flow!(@view(y[2, :, :]), u, @view(Δy[2, :, :]); threads=(threads==:inner)) end else flow!(x, u, Δx) end end function pdflow!(x, Δx, y, Δy, z, Δz, u, dual_flow :: Bool; threads=:none) if dual_flow @backgroundif (threads==:outer) begin flow!(x, u, Δx; threads=(threads==:inner)) flow!(z, u, Δz; threads=(threads==:inner)) end begin flow!(@view(y[1, :, :]), u, @view(Δy[1, :, :]); threads=(threads==:inner)) flow!(@view(y[2, :, :]), u, @view(Δy[2, :, :]); threads=(threads==:inner)) end else flow!(x, u, Δx; threads=(threads==:inner)) flow!(z, u, Δz; threads=(threads==:inner)) end end # Additional method for Greedy function pdflow!(x, Δx, y, Δy, u, flow :: Greedy; threads=:none) @assert(size(u)==(2,)) Δx .= x Δy .= y flow!(x, u; threads=(threads==:inner)) Dxx = similar(Δy) DΔx = similar(Δy) ∇₂!(Dxx, x) ∇₂!(DΔx, Δx) inds = abs.(Dxx) .≤ 1e-1 Dxx[inds] .= 1 DΔx[inds] .= 1 y .= y.* DΔx ./ Dxx end # Additional method for Rotation function pdflow!(x, Δx, y, Δy, u, flow :: Rotation; threads=:none) @assert(size(u)==(2,)) Δx .= x flow!(x, u; threads=(threads==:inner)) (m,n) = size(x) dx = similar(y) dx_banana = similar(y) ∇₂!(dx, Δx) ∇₂!(dx_banana, x) for i=1:m for j=1:n ndx = @views sum(dx[:, i, j].^2) ndx_banana = @views sum(dx_banana[:, i, j].^2) if ndx > 1e-4 && ndx_banana > 1e-4 A = dx[:, i, j] B = dx_banana[:, i, j] theta = atan(B[1] * A[2] - B[2] * A[1], B[1] * A[1] + B[2] * A[2]) # Oriented angle from A to B cos_theta = cos(theta) sin_theta = sin(theta) a = cos_theta * y[1, i, j] - sin_theta * y[2, i, j] b = sin_theta * y[1, i, j] + cos_theta * y[2, i, j] y[1, i, j] = a y[2, i, j] = b end end end end # Additional method for Dual Scaling function pdflow!(x, Δx, y, Δy, u, flow :: DualScaling; threads=:none) @assert(size(u)==(2,)) oldx = copy(x) flow!(x, u; threads=(threads==:inner)) C = similar(y) cc = abs.(x-oldx) cm = max(flow.threshold,maximum(cc)) c = 1.0 .- flow.factor.*flow.activation.(cc./cm) C[1,:,:] .= c C[2,:,:] .= c y .= C.*y end function activation(x :: Real) return (1/(1 + exp(-1000(x - 0.05)))) # best for shepp logan #return -abs(x-1)^1/5 + 1 # best for lighthouse and brainphantom # return x^(5) # return (1/(1 + exp(-1000(x - 0.075)))) # return 4*(x-0.5)^3 + 0.5 # return (1/(1 + exp(-1000(x - 0.05)))) # return -3(x-0.5)^2 + 0.75 # return (x-1)^51 + 1 # return -abs(x-1)^1/3 + 1 # return 16*(x-0.5)^5 + 0.5 end function pdflow!(x, Δx, y, Δy, u, :: ZeroDual; threads=:none) @assert(size(u)==(2,)) flow!(x, u; threads=(threads==:inner)) y .= 0.0 end function pdflow!(x, Δx, y, Δy, u, :: PrimalOnly; threads=:none) @assert(size(u)==(2,)) flow!(x, u; threads=(threads==:inner)) end ########################## # PET ########################## function petflow_interp!(x::AbstractImage, tmp::AbstractImage, u::DisplacementConstant, theta_known::DisplacementConstant; threads = false) tmp .= x center_point = center(x) .+ u tform = recenter(RotMatrix(theta_known[1]), center_point) tmp = warp(x, tform, axes(x), fillvalue=Flat()) x .= tmp end petflow! = petflow_interp! function petpdflow!(x, Δx, y, Δy, u, theta_known, dual_flow :: Bool; threads=:none) if dual_flow @backgroundif (threads==:outer) begin petflow!(x, Δx, u, theta_known; threads=(threads==:inner)) end begin petflow!(@view(y[1, :, :]), @view(Δy[1, :, :]), u, theta_known; threads=(threads==:inner)) petflow!(@view(y[2, :, :]), @view(Δy[2, :, :]), u, theta_known; threads=(threads==:inner)) end else petflow!(x, Δx, u, theta_known) end end # Method for greedy predictor function petpdflow!(x, Δx, y, Δy, u, theta_known, flow :: Greedy; threads=:none) oldx = copy(x) center_point = center(x) .+ u tform = recenter(RotMatrix(theta_known[1]), center_point) Δx = warp(x, tform, axes(x), fillvalue=Flat()) @. x = Δx @. Δy = y Dxx = copy(Δy) DΔx = copy(Δy) ∇₂!(Dxx, x) ∇₂!(DΔx, oldx) inds = abs.(Dxx) .≤ 1e-2 Dxx[inds] .= 1 DΔx[inds] .= 1 y .= y.* DΔx ./ Dxx end # Method for dual scaling predictor function petpdflow!(x, Δx, y, Δy, u, theta_known, flow :: DualScaling; threads=:none) oldx = copy(x) center_point = center(x) .+ u tform = recenter(RotMatrix(theta_known[1]), center_point) Δx = warp(x, tform, axes(x), fillvalue=Flat()) @. x = Δx C = similar(y) cc = abs.(x-oldx) cm = max(flow.threshold,maximum(cc)) c = 1.0 .- flow.factor.*flow.activation.(cc./cm) C[1,:,:] .= c C[2,:,:] .= c y .= C.*y end # Method for rotation prediction (exploiting property of inverse rotation) function petpdflow!(x, Δx, y, Δy, u, theta_known, flow :: Rotation; threads=:none) @backgroundif (threads==:outer) begin petflow!(x, Δx, u, theta_known; threads=(threads==:inner)) end begin petflow!(@view(y[1, :, :]), @view(Δy[1, :, :]), u, -theta_known; threads=(threads==:inner)) petflow!(@view(y[2, :, :]), @view(Δy[2, :, :]), u, -theta_known; threads=(threads==:inner)) end end function petpdflow!(x, Δx, y, Δy, u, theta_known, :: ZeroDual; threads=:none) petflow!(x, Δx, u, theta_known, threads=(threads==:inner)) y .= 0.0 end function petpdflow!(x, Δx, y, Δy, u, theta_known, :: PrimalOnly; threads=:none) petflow!(x, Δx, u, theta_known, threads=(threads==:inner)) end ########################## # Linearised optical flow ########################## # ⟨⟨u, ∇b⟩⟩ function pointwise_gradiprod_2d!(y::Image, vtmp::Gradient, u::DisplacementFull, b::Image; add = false) ∇₂c!(vtmp, b) u′=reshape(u, (size(u, 1), prod(size(u)[2:end]))) vtmp′=reshape(vtmp, (size(vtmp, 1), prod(size(vtmp)[2:end]))) y′=reshape(y, prod(size(y))) if add @simd for i = 1:length(y′) @inbounds y′[i] += dot(@view(u′[:, i]), @view(vtmp′[:, i])) end else @simd for i = 1:length(y′) @inbounds y′[i] = dot(@view(u′[:, i]), @view(vtmp′[:, i])) end end end function pointwise_gradiprod_2d!(y::Image, vtmp::Gradient, u::DisplacementConstant, b::Image; add = false) ∇₂c!(vtmp, b) vtmp′=reshape(vtmp, (size(vtmp, 1), prod(size(vtmp)[2:end]))) y′=reshape(y, prod(size(y))) if add @simd for i = 1:length(y′) @inbounds y′[i] += dot(u, @view(vtmp′[:, i])) end else @simd for i = 1:length(y′) @inbounds y′[i] = dot(u, @view(vtmp′[:, i])) end end end # ∇b ⋅ y function pointwise_gradiprod_2dᵀ!(u::DisplacementFull, y::Image, b::Image) ∇₂c!(u, b) u′=reshape(u, (size(u, 1), prod(size(u)[2:end]))) y′=reshape(y, prod(size(y))) @simd for i=1:length(y′) @inbounds @. u′[:, i] *= y′[i] end end function pointwise_gradiprod_2dᵀ!(u::DisplacementConstant, y::Image, b::Image) @assert(size(y)==size(b) && size(u)==(2,)) u .= 0 ∇₂cfold!(b, nothing) do g, st, (i, j) @inbounds u .+= g.*y[i, j] return st end # Reweight to be with respect to 𝟙^*𝟙 inner product. u ./= prod(size(b)) end mutable struct ConstantDisplacementHornSchunckData M₀::Array{Float64,2} z::Array{Float64,1} Mv::Array{Float64,2} av::Array{Float64,1} cv::Float64 function ConstantDisplacementHornSchunckData() return new(zeros(2, 2), zeros(2), zeros(2,2), zeros(2), 0) end end # For DisplacementConstant, for the simple prox step # # (1) argmin_u 1/(2τ)|u-ũ|^2 + (θ/2)|b⁺-b+<<u-ŭ,∇b>>|^2 + (λ/2)|u-ŭ|^2, # # construct matrix M₀ and vector z such that we can solve u from # # (2) (I/τ+M₀)u = M₀ŭ + ũ/τ - z # # Note that the problem # # argmin_u 1/(2τ)|u-ũ|^2 + (θ/2)|b⁺-b+<<u-ŭ,∇b>>|^2 + (λ/2)|u-ŭ|^2 # + (θ/2)|b⁺⁺-b⁺+<<uʹ-u,∇b⁺>>|^2 + (λ/2)|u-uʹ|^2 # # has with respect to u the system # # (I/τ+M₀+M₀ʹ)u = M₀ŭ + M₀ʹuʹ + ũ/τ - z + zʹ, # # where the primed variables correspond to (2) for (1) for uʹ in place of u: # # argmin_uʹ 1/(2τ)|uʹ-ũʹ|^2 + (θ/2)|b⁺⁺-b⁺+<<uʹ-u,∇b⁺>>|^2 + (λ/2)|uʹ-u|^2 # function horn_schunck_reg_prox_op!(hs::ConstantDisplacementHornSchunckData, bnext::Image, b::Image, θ, λ, T) @assert(size(b)==size(bnext)) w = prod(size(b)) z = hs.z cv = 0 # Factors of symmetric matrix [a c; c d] a, c, d = 0.0, 0.0, 0.0 # This used to use ∇₂cfold but it is faster to allocate temporary # storage for the full gradient due to probably better memory and SIMD # instruction usage. g = zeros(2, size(b)...) ∇₂c!(g, b) @inbounds for i=1:size(b, 1) for j=1:size(b, 2) δ = bnext[i,j]-b[i,j] @. z += g[:,i,j]*δ cv += δ*δ a += g[1,i,j]*g[1,i,j] c += g[1,i,j]*g[2,i,j] d += g[2,i,j]*g[2,i,j] end end w₀ = λ w₂ = θ/w aʹ = w₀ + w₂*a cʹ = w₂*c dʹ = w₀ + w₂*d hs.M₀ .= [aʹ cʹ; cʹ dʹ] hs.Mv .= [w*λ+θ*a θ*c; θ*c w*λ+θ*d] hs.cv = cv*θ hs.av .= hs.z.*θ hs.z .*= w₂/T end # Solve the 2D system (I/τ+M₀)u = z @inline function mldivide_step_plus_sym2x2!(u, M₀, z, τ) a = 1/τ+M₀[1, 1] c = M₀[1, 2] d = 1/τ+M₀[2, 2] u .= ([d -c; -c a]*z)./(a*d-c*c) end function horn_schunck_reg_prox!(u::DisplacementConstant, bnext::Image, b::Image, θ, λ, T, τ) hs=ConstantDisplacementHornSchunckData() horn_schunck_reg_prox_op!(hs, bnext, b, θ, λ, T) mldivide_step_plus_sym2x2!(u, hs.M₀, (u./τ)-hs.z, τ) end function flow_grad!(x::Image, vtmp::Gradient, u::Displacement; δ=nothing) if !isnothing(δ) u = δ.*u end pointwise_gradiprod_2d!(x, vtmp, u, x; add=true) end # Error b-b_prev+⟨⟨u, ∇b⟩⟩ for Horn–Schunck type penalisation function linearised_optical_flow_error(u::Displacement, b::Image, b_prev::Image) imdim = size(b) vtmp = zeros(2, imdim...) tmp = b-b_prev pointwise_gradiprod_2d!(tmp, vtmp, u, b_prev; add=true) return tmp end ############################################## # Helper to smooth data for Horn–Schunck term ############################################## function filter_hs(b, b_next, b_next_filt, kernel) if kernel==nothing f = x -> x else f = x -> simple_imfilter(x, kernel; threads=true) end # We already filtered b in the previous step (b_next in that step) b_filt = b_next_filt==nothing ? f(b) : b_next_filt b_next_filt = f(b_next) return b_filt, b_next_filt end end # Module