@@ -10,45 +10,53 @@ using ImageFiltering
1010using Statistics
1111using OhMyThreads
1212
13- export estimate_motion_parameters
13+ export realign!, create_rotation_matrix
1414
1515
1616# --- Top-level functions ---
17- function estimate_motion_parameters (img:: AbstractArray{Tin,4} ;
17+ function realign! (img:: AbstractArray{Tin,4} ;
1818 center= size (img)[1 : 3 ] .÷ 2 ,
1919 ref_mode= :consensus ,
2020 mask= trues (size (img)[1 : 3 ]),
21- fwhm= nothing :: Union{Nothing,NTuple{3}}
22- ) where {Tin<: Real }
21+ fwhm= nothing :: Union{Nothing,NTuple{3}} ,
22+ realign= true
23+ ) where Tin
24+
25+ if Tin <: Complex
26+ _img = abs .(img)
27+ Treal = real (Tin)
28+ else
29+ _img = img
30+ Treal = Tin
31+ end
2332 T = Float64 # lower precision results in gradient inacurracies
2433
2534 if ref_mode == :consensus
26- t_refs = axes (img , 4 )
35+ t_refs = axes (_img , 4 )
2736 elseif ref_mode == :mean
28- img = cat (img , mean (img , dims= 4 ); dims= 4 )
29- t_refs = size (img , 4 )
37+ _img = cat (_img , mean (_img , dims= 4 ); dims= 4 )
38+ t_refs = size (_img , 4 )
3039 elseif typeof (ref_mode) <: Integer
3140 t_refs = ref_mode
3241 else
3342 error (" ref_mode must either be `:consensus`, `:mean`, or an integer" )
3443 end
3544
36- img_s = (fwhm === nothing || all (fwhm .== 0 )) ? img : smooth_image (img , fwhm)
45+ img_s = (fwhm === nothing || all (fwhm .== 0 )) ? _img : smooth_image (_img , fwhm)
3746
3847 # interpolate all time frames
39- _interpolate (x) = extrapolate (interpolate (x, BSpline (Cubic ())), Interpolations. Flat ())
4048 @views Tint = typeof (_interpolate (img_s[:, :, :, 1 ]))
4149 img_itp = Vector {Tint} (undef, size (img_s, 4 ))
4250 @tasks for t ∈ eachindex (img_itp)
43- vol = @view img_s[:, :, :, t]
51+ vol = img_s[:, :, :, t]
4452 vol ./= quantile (vec (vol), T (0.9 ))
4553 img_itp[t] = _interpolate (vol)
4654 end
4755
4856 # random shifts seem to help with the speed of convertion (cf. SPM)
4957 mask_inds = [Tuple (idx) .+ rand (NTuple{3 ,T}) .- T (0.5 ) for idx ∈ findall (mask)]
5058
51- motion_params = Array {T} (undef, 6 , length (img_itp), length (t_refs))
59+ _motion_params = Array {T} (undef, 6 , length (img_itp), length (t_refs))
5260 for (i_ref, t_ref) ∈ enumerate (t_refs)
5361 reference = [img_itp[t_ref](idx[1 ], idx[2 ], idx[3 ]) for idx in mask_inds]
5462 grad_field = [gradient (img_itp[t_ref], idx[1 ], idx[2 ], idx[3 ]) for idx ∈ mask_inds]
@@ -61,34 +69,38 @@ function estimate_motion_parameters(img::AbstractArray{Tin,4};
6169
6270 fgh! = make_fgh_function (reference, img_itp[t], center, mask_inds, grad_field, hess_field, diff_vals)
6371 res = optimize (Optim. only_fgh! (fgh!), p0, NewtonTrustRegion ())
64- motion_params [:, t, i_ref] .= Optim. minimizer (res)
72+ _motion_params [:, t, i_ref] .= Optim. minimizer (res)
6573 end
6674 end
6775
68- if ref_mode == :consensus
69- return Tin .( weighted_mean_estimate! (motion_params ; r= mean (size (img)[1 : 3 ]) ))
76+ motion_params = if ref_mode == :consensus
77+ weighted_mean_estimate! (_motion_params ; r= mean (size (img)[1 : 3 ]))
7078 elseif ref_mode == :mean
71- return Tin .(motion_params [:, 1 : end - 1 , 1 ])
79+ _motion_params [:, 1 : end - 1 , 1 ]
7280 else
73- return Tin .( dropdims (motion_params , dims= 3 ) )
81+ dropdims (_motion_params , dims= 3 )
7482 end
75- end
7683
84+ if realign
85+ realign! (img, motion_params; center)
86+ end
7787
78- # TODO
79- # function realign_volumes(img::Array{<:Real,4}; ref_mode=:first, subsample=4, mask=nothing, σ=3.0)
80- # sx, sy, sz, nt = size(img)
81- # center = voxelcenter(img[:,:,:,1])
82- # aligned = Array{Float64,4}(undef, sx, sy, sz, nt)
88+ return Treal .(motion_params)
89+ end
8390
84- # @tasks for t in 1:nt
85- # println("Realigning volume $t / $nt ...")
86- # A = create_affine_matrix(motion_params[t, :], center)
87- # aligned[:,:,:,t] = warp_volume_itp(moving, A, (sx,sy,sz))
88- # end
8991
90- # return motion_params
91- # end
92+ function realign! (img:: AbstractArray{T,4} , motion_params; center= size (img)[1 : 3 ] .÷ 2 ) where T
93+ @tasks for t ∈ axes (img, 4 )
94+ vol = @view img[:, :, :, t]
95+ img_itp = _interpolate (vol)
96+ A = create_affine_matrix (motion_params[:, t], center)
97+ @inbounds for idx ∈ CartesianIndices (vol)
98+ v = A * SVector {4,Float64} (idx[1 ], idx[2 ], idx[3 ], 1 )
99+ vol[idx] = img_itp (v[1 ], v[2 ], v[3 ])
100+ end
101+ end
102+ return img
103+ end
92104
93105
94106# --- Combined f, g, h! for Optim.only_fg! or Newton trust-region ---
@@ -166,6 +178,8 @@ function smooth_image(img::AbstractArray{T,4}, fwhm::NTuple{3}) where {T<:Real}
166178 return img_s
167179end
168180
181+ _interpolate (x) = extrapolate (interpolate (x, BSpline (Cubic ())), Interpolations. Flat ())
182+
169183function create_rotation_matrix (rx, ry, rz)
170184 Rx = @SMatrix [1 0 0 ; 0 cos (rx) - sin (rx); 0 sin (rx) cos (rx)]
171185 Ry = @SMatrix [cos (ry) 0 sin (ry); 0 1 0 ; - sin (ry) 0 cos (ry)]
@@ -233,9 +247,6 @@ function params_from_rigid_affine(A, center)
233247 return @SVector [rx, ry, rz, t[1 ], t[2 ], t[3 ]]
234248end
235249
236-
237-
238-
239250function weighted_mean_estimate! (estimates; r= 1 , max_iter= 100 , tol= 1e-6 )
240251 # bring all estimates in the same frame of reference (iframe = end÷2)
241252 for iref ∈ axes (estimates, 3 )
@@ -253,7 +264,7 @@ function weighted_mean_estimate!(estimates; r=1, max_iter=100, tol=1e-6)
253264
254265 residuals = estimates .- consensus
255266 residuals[1 : 3 , :, :] .*= r # weight rotations by the radius
256- weights = 1 ./ (1 .+ sqrt .(sum (abs2, residuals; dims= 1 : 2 ))) # ! dims=1:2 calculates the weights for all 6 parameters jointly, while dims=2 calculates them for each parameter separately
267+ weights = 1 ./ (1 .+ sqrt .(sum (abs2, residuals; dims= 1 : 2 ))) # dims=1:2 calculates the weights for all 6 parameters jointly, while dims=2 calculates them for each parameter separately
257268 consensus = sum ((weights .* estimates); dims= 3 ) ./ sum (weights; dims= 3 ) # Compute new weighted mean
258269
259270 if norm (consensus .- consensus_old) < tol
0 commit comments