Skip to content

Commit d7f973d

Browse files
authored
Add Tik to NUFFT example (#13)
* Add Tik to NUFFT example * K-space errors
1 parent 37667a2 commit d7f973d

File tree

1 file changed

+83
-13
lines changed

1 file changed

+83
-13
lines changed

docs/lit/mri/1-nufft.jl

Lines changed: 83 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ end
4646
# Run `Pkg.add()` in the preceding code block first, if needed.
4747

4848
using ImagePhantoms: shepp_logan, SheppLoganEmis, spectrum, phantom #, Gauss2
49+
using LinearAlgebra: I
4950
using Unitful: mm # Allows use of physical units (mm here)
5051
using Plots; default(label="", markerstrokecolor=:auto)
5152
using 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
7576
kmax = 1 / 2Δx
7677
kr = ((-N÷2):(N÷2)) / (N÷2) * kmax # radial sampling in k-space
78+
Nr = length(kr) # N+1
7779
= 3N÷2 # theoretically should be about π/2 * N
7880
= (0:-1)/* π # 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
124126
data = spectrum(object).(νx,νy)
125127
data = 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_{\phi}",
128+
dscale = 10000
129+
jimk = (args...; kwargs...) -> jim(kr, kϕ, args...;
130+
xlabel = L"k_r",
131+
ylabel = L"k_{\phi}",
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
208213
that 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.
215229
So its
216230
[adjoint](https://en.wikipedia.org/wiki/Adjoint)
217231
goes the other direction.
@@ -297,18 +311,22 @@ CG is well-suited to minimizing quadratic cost functions,
297311
but we do not expect the image to be great quality
298312
because radial sampling omits the "corners" of k-space
299313
so 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
304320
gradf = u -> u - vec(data/dx^2) # gradient of f(u) = 1/2 \| u - y \|^2
305321
curvf = u -> 1 # curvature of f(u)
306322
x0 = gridded4 # initial guess: best gridding reconstruction
307323
xls, _ = 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+
312330
To improve the results, we include regularization.
313331
Here we would like to reconstruct an image
314332
by finding the minimizer of a regularized LS cost function
@@ -318,18 +336,65 @@ such as the following:
318336
, \qquad
319337
R(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
322386
where ``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
325389
with the horizontal and vertical finite differences.
326390
=#
327391

328392
T = 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
364429
curvf = [u -> 1, u -> β] # curvature of quadratic majorizers
365430
x0 = gridded4 # initial guess is best gridding reconstruction
366431
xhat, _ = 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

Comments
 (0)