4646# Run `Pkg.add()` in the preceding code block first, if needed.
4747
4848using ImagePhantoms: shepp_logan, SheppLoganEmis, spectrum, phantom # , Gauss2
49+ using LinearAlgebra: I
4950using Unitful: mm # Allows use of physical units (mm here)
5051using Plots; default(label= " " , markerstrokecolor= :auto)
5152using LaTeXStrings # for LaTeX in plot labels, e.g., L"\alpha_n"
@@ -74,6 +75,7 @@ FOV = 256mm # physical units!
7475Δx = FOV / N # pixel size
7576kmax = 1 / 2 Δx
7677kr = ((- N÷ 2 ): (N÷ 2 )) / (N÷ 2 ) * kmax # radial sampling in k-space
78+ Nr = length(kr) # N+1
7779Nϕ = 3 N÷ 2 # theoretically should be about π/2 * N
7880kϕ = (0 : Nϕ- 1 )/ Nϕ * π # angular samples
7981νx = kr * cos.(kϕ)' # N × Nϕ k-space sampling in cycles/mm
@@ -123,14 +125,17 @@ object = shepp_logan(SheppLoganEmis(); fovs=(FOV,FOV))
123125# object = [Gauss2(18mm, 0mm, 100mm, 70mm, 0, 1)] # useful for validating DCF
124126data = spectrum(object). (νx,νy)
125127data = data / oneunit(eltype(data)) # abandon units at this point
126- jim(kr, kϕ, abs.(data), title= " k-space data magnitude" ,
127- xlabel= L" k_r" ,
128- ylabel= L" k_{\p hi}" ,
128+ dscale = 10000
129+ jimk = (args... ; kwargs... ) -> jim(kr, kϕ, args... ;
130+ xlabel = L" k_r" ,
131+ ylabel = L" k_{\p hi}" ,
129132 xticks = (- 1 : 1 ) .* maximum(abs, kr),
130133 yticks = (0 ,π),
131134 ylims = (0 ,π),
132135 aspect_ratio = :none,
136+ kwargs...
133137)
138+ pk = jimk(abs.(data) / dscale; title= " k-space data magnitude / $dscale " )
134139
135140
136141#=
@@ -208,10 +213,19 @@ defined in MIRT
208213that provides a non-Cartesian Fourier encoding "matrix".
209214=#
210215
211- A = Anufft([vec(Ωx) vec(Ωy)], (N,N); n_shift = [N/ 2 ,N/ 2 ])
216+ A = Anufft([vec(Ωx) vec(Ωy)], (N,N); n_shift = [N/ 2 ,N/ 2 ]) # todo: odim=(Nr,Nϕ)
217+
218+ # Verify that the operator `A` works properly:
219+ dx = FOV / N # pixel size
220+ dx = dx / oneunit(dx) # abandon units for now
221+ Ax_to_y = Ax -> dx^ 2 * reshape(Ax, Nr, Nϕ) # trick
222+ pj1 = jimk(abs.(Ax_to_y(A * ideal)) / dscale, " |A*x|/$dscale " )
223+ pj2 = jimk(abs.(Ax_to_y(A * ideal) - data) / dscale, " |A*x-y|/$dscale " )
224+ plot(pj1, pj2)
212225
213226#=
214- This linear map is constructed to map a N × N image into `length(Ωx)` k-space samples.
227+ This linear map is constructed to map a N × N image
228+ into `length(Ωx)` k-space samples.
215229So its
216230[adjoint](https://en.wikipedia.org/wiki/Adjoint)
217231goes the other direction.
@@ -297,18 +311,22 @@ CG is well-suited to minimizing quadratic cost functions,
297311but we do not expect the image to be great quality
298312because radial sampling omits the "corners" of k-space
299313so the NUFFT operator ``A`` is badly conditioned.
314+
315+ There is a subtle point here about `dx`
316+ when converting the Fourier integral to a sum.
317+ Here `y` is `data/dx^2`.
300318=#
301319
302- dx = FOV / N # pixel size
303- dx = dx / oneunit(dx) # abandon units for now
304320gradf = u -> u - vec(data/ dx^ 2 ) # gradient of f(u) = 1/2 \| u - y \|^2
305321curvf = u -> 1 # curvature of f(u)
306322x0 = gridded4 # initial guess: best gridding reconstruction
307323xls, _ = ncg([A], [gradf], [curvf], x0; niter = 20 )
308- p5 = jim(x, y, xls, " LS-CG reconstruction" ; clim)
324+ p5 = jim(x, y, xls, " | LS-CG reconstruction| " ; clim)
309325
310326
311327#=
328+ ## Regularized MR image reconstruction
329+
312330To improve the results, we include regularization.
313331Here we would like to reconstruct an image
314332by finding the minimizer of a regularized LS cost function
@@ -318,18 +336,65 @@ such as the following:
318336, \qquad
319337R(x) = 1' \psi.(T x).
320338```
321- We focus here on the case of edge-preserving regularization
339+ =#
340+
341+
342+ #=
343+ ### Tikhonov regularization
344+
345+ The simplest option is Tikhonov regularization,
346+ where
347+ ``R(x) = (β_0/2) \| x \|_2^2,``
348+ corresponding to ``T = I``
349+ and ``ϕ(z) = (β_0/2) | z |^2``
350+ above.
351+ =#
352+
353+ β₀ = 1e-0
354+ xtik, _ = ncg([A, sqrt(β₀)* I], [gradf, x -> β₀* x], [curvf, x -> β₀], x0; niter = 80 )
355+ p6 = jim(x, y, xtik, " |Tikhonov Regularized|" ; clim)
356+
357+
358+ #=
359+ Comparing the error images
360+ with the same grayscale window,
361+ the regularized reconstruction
362+ has somewhat lower errors.
363+ =#
364+ elim = (0 , 1 )
365+ ecolor = :cividis
366+ p5e = jim(x, y, abs.(xls - ideal), " |LS-CG error|" ; clim= elim, color= ecolor)
367+ p6e = jim(x, y, abs.(xtik - ideal), " |Tik error|" ; clim= elim, color= ecolor)
368+ plot(p5e, p6e; size= (800 ,300 ))
369+
370+
371+ # Errors in k-space
372+ # src logger = (x; min=-6) -> log10.(max.(abs.(x) / maximum(abs, x), (10.)^min))
373+ # src p5f = jimk(logger(Ax_to_y(A * xls)), "|LS-CG kspace|")
374+ # src p6f = jimk(logger(Ax_to_y(A * xtik)), "|Tik. kspace|")
375+ p5f = jimk(abs.(Ax_to_y(A * xls) - data) / dscale, " |LS-CG kspace error|" )
376+ p6f = jimk(abs.(Ax_to_y(A * xtik) - data) / dscale, " |Tik. kspace error|" )
377+ # src p5f = jimk(logger(Ax_to_y(A * xls) - data) / dscale, "|LS-CG kspace error|")
378+ # src p6f = jimk(logger(Ax_to_y(A * xtik) - data) / dscale, "|Tik. kspace error|")
379+ p56f = plot(p5f, p6f)
380+
381+
382+ #=
383+ ### Edge-preserving regularization
384+
385+ Now consider edge-preserving regularization
322386where ``T`` is a 2D finite-differencing operator
323- and ``\psi `` is a potential function.
324- This operator maps a N×N image into a N×N×2 array
387+ and ``ψ `` is a potential function.
388+ This operator maps a `` N×N`` image into a `` N×N×2`` array
325389with the horizontal and vertical finite differences.
326390=#
327391
328392T = diffl_map((N,N), [1 ,2 ] ; T = ComplexF32)
329393
330394
331395# Applying this operator to the ideal image illustrated its action:
332- jim(x, y, T * ideal; ncol= 1 , title= " Horizontal and vertical finite differences" )
396+ p7 = jim(x, y, T * ideal; nrow= 1 , size = (600 , 300 ),
397+ title= " Horizontal and vertical finite differences" )
333398
334399
335400#=
@@ -364,8 +429,13 @@ gradf = [u -> u - vec(data/dx^2), # data-term gradient, correct for pixel area
364429curvf = [u -> 1 , u -> β] # curvature of quadratic majorizers
365430x0 = gridded4 # initial guess is best gridding reconstruction
366431xhat, _ = ncg(B, gradf, curvf, x0; niter = 90 )
367- p6 = jim(x, y, xhat, " Iterative reconstruction" ; clim)
432+ p8 = jim(x, y, xhat, " Iterative reconstruction" ; clim)
433+
434+
435+ # Compare the error images:
368436
437+ p8e = jim(x, y, abs.(xhat - ideal), " |Reg. error|" ; clim= elim, color= ecolor)
438+ p568e = plot(p5e, p6e, p8e; layout= (1 ,3 ), size= (1200 ,300 ))
369439
370440# Here is a comparison of the profiles.
371441
0 commit comments