|
1 | 1 | module ImageReconstruction |
2 | 2 |
|
| 3 | +using Base.Threads |
| 4 | +using Interpolations |
3 | 5 | using FFTW |
4 | 6 |
|
5 | 7 | export radon, iradon |
6 | 8 |
|
7 | 9 | """ |
8 | | -radon transform |
| 10 | + radon(I::AbstractMatrix, θ::AbstractRange, t::AbstractRange) |
9 | 11 |
|
10 | | -https://en.wikipedia.org/wiki/Radon_transform |
| 12 | +Radon transform of a image `I` producing a sinogram from view angles `θ` in |
| 13 | +radians and detector sampling `t`. |
11 | 14 | """ |
12 | | -function radon end |
13 | | - |
14 | | -""" |
15 | | -inverse radon transform |
16 | | -
|
17 | | -https://en.wikipedia.org/wiki/Radon_transform |
18 | | -""" |
19 | | -function iradon end |
20 | | - |
21 | | - |
22 | | -""" |
23 | | -Radon transform of an image using a pixel-driven algorithm. |
24 | | -""" |
25 | | -function radon(image::AbstractMatrix, θ::AbstractRange, t::AbstractRange) |
26 | | - P = zeros(eltype(image), length(t), length(θ)) |
27 | | - nr, nc = size(image) |
28 | | - |
29 | | - for i in 1:nr, j in 1:nc |
30 | | - x = j - nc / 2 + 0.5 |
31 | | - y = i - nr / 2 + 0.5 |
| 15 | +function radon(I::AbstractMatrix, θ::AbstractRange, t::AbstractRange) |
| 16 | + P = zeros(eltype(I), length(t), length(θ)) |
| 17 | + ax1, ax2 = axes(I) |
| 18 | + |
| 19 | + nax1, nax2 = length(ax1), length(ax2) |
| 20 | + for j in ax2, i in ax1 |
| 21 | + x = j - nax2 / 2 + 0.5 |
| 22 | + y = i - nax1 / 2 + 0.5 |
32 | 23 | @inbounds for (k, θₖ) in enumerate(θ) |
33 | 24 | t′ = x * cos(θₖ) + y * sin(θₖ) |
34 | 25 |
|
35 | 26 | a = convert(Int, round((t′ - minimum(t)) / step(t) + 1)) |
36 | 27 |
|
37 | 28 | (a < 1 || a > length(t)) && continue |
38 | 29 | α = abs(t′ - t[a]) |
39 | | - P[a, k] += (1 - α) * image[i, j] |
| 30 | + |
| 31 | + I′ = I[i, j] |
| 32 | + P[a, k] += (1 - α) * I′ |
40 | 33 |
|
41 | 34 | (a > length(t) + 1) && continue |
42 | | - P[a + 1, k] += α * image[i, j] |
| 35 | + P[a+1, k] += α * I′ |
43 | 36 | end |
44 | 37 | end |
45 | 38 |
|
46 | 39 | P |
47 | 40 | end |
48 | 41 |
|
49 | | -function _ramp_spatial(N::Int, τ) |
50 | | - h = zeros(N) |
| 42 | +function _ramp_spatial(N::Int, τ, Npad::Int = N) |
| 43 | + @assert Npad ≥ N |
51 | 44 | N2 = N ÷ 2 |
52 | | - for i in eachindex(h) |
53 | | - n = i - N2 - 1 |
54 | | - if mod(n, 2) != 0 |
55 | | - h[i] = -1 / (π * n * τ)^2 |
56 | | - elseif n == 0 |
57 | | - h[i] = 1 / (4 * τ^2) |
58 | | - end |
59 | | - end |
60 | | - h |
| 45 | + hval(n) = n == 0 ? 1 / (4*τ^2) : - mod(n, 2)/(π * n * τ)^2 |
| 46 | + [i ≤ N ? hval(i - N2 - 1) : 0. for i = 1:Npad] |
61 | 47 | end |
62 | 48 |
|
63 | | -_zero_pad(p::AbstractVector, N::Int) = vcat(p, zeros(N)) |
64 | | - |
65 | 49 | """ |
66 | | -Inverse radon transform using a ramp filter and a pixel-driven algorithm. |
| 50 | + iradon(P::AbstractMatrix, θ::AbstractRange, t::AbstractRange) |
| 51 | +
|
| 52 | +Inverse radon transform of a sinogram `P` with view angles `θ` in radians and |
| 53 | +detector sampling `t` producing an image on a 128x128 matrix. |
67 | 54 | """ |
68 | | -function iradon(sinogram::AbstractMatrix, θ::AbstractRange, t::AbstractRange) |
| 55 | +function iradon(P::AbstractMatrix, θ::AbstractRange, t::AbstractRange) |
69 | 56 | pixels = 128 |
70 | 57 |
|
71 | 58 | N = length(t) |
72 | 59 | K = length(θ) |
73 | 60 | Npad = nextpow(2, 2 * N - 1) |
74 | 61 | τ = step(t) |
75 | | - ramp = fft(_zero_pad(_ramp_spatial(N, τ), Npad - N)) |
76 | | - i = div(N, 2) + 1 |
| 62 | + ramp = fft(_ramp_spatial(N, τ, Npad)) |
| 63 | + i = N ÷ 2 + 1 |
77 | 64 | j = i + N - 1 |
78 | 65 |
|
79 | | - image = zeros(eltype(sinogram), pixels, pixels) |
80 | | - Q = Vector{eltype(sinogram)}(undef, N) |
81 | | - for (k, θₖ) in enumerate(θ) |
| 66 | + T = eltype(P) |
| 67 | + I = [zeros(T, pixels, pixels) for _ = 1:nthreads()] |
| 68 | + P′ = [Vector{Complex{T}}(undef, Npad) for _ = 1:nthreads()] |
| 69 | + Q = [Vector{T}(undef, N) for _ = 1:nthreads()] |
| 70 | + |
| 71 | + l = SpinLock() |
| 72 | + @inbounds @threads for (k, θₖ) in collect(enumerate(θ)) |
| 73 | + id = threadid() |
| 74 | + Pid, Qid, Iid = P′[id], Q[id], I[id] |
| 75 | + |
82 | 76 | # filter projection |
83 | | - Q[:] .= τ .* real.(ifft(fft(_zero_pad(sinogram[:, k], Npad - N)) .* ramp)[i:j]) |
| 77 | + Pid[1:N] .= P[:, k] |
| 78 | + Pid[N+1:end] .= 0 |
| 79 | + |
| 80 | + # Need to prevent multiple thread execution during fft/ifft. |
| 81 | + # double free occurs otherwise. |
| 82 | + # https://github.com/JuliaMath/FFTW.jl/issues/134 |
| 83 | + lock(l) |
| 84 | + fft!(Pid) |
| 85 | + Pid .*= ramp |
| 86 | + ifft!(Pid) |
| 87 | + unlock(l) |
| 88 | + Qid .= τ .* real.(@view Pid[i:j]) |
| 89 | + |
| 90 | + Qₖ = LinearInterpolation(t, Qid) |
84 | 91 |
|
85 | 92 | # backproject |
86 | | - for c in CartesianIndices(image) |
| 93 | + for c in CartesianIndices(first(I)) |
87 | 94 | x = c.I[2] - pixels ÷ 2 + 0.5 |
88 | 95 | y = c.I[1] - pixels ÷ 2 + 0.5 |
| 96 | + x^2 + y^2 ≥ pixels^2 / 4 && continue |
89 | 97 | t′ = x * cos(θₖ) + y * sin(θₖ) |
90 | | - # linear interpolation |
91 | | - # image pixel (x, y) at θₖ is projected on t[z] and t[z+1] |
92 | | - z = convert(Int, round((t′ - minimum(t)) / step(t) + 1)) |
93 | | - α = abs(t′ - t[z]) |
94 | | - image[c] += (1 - α) * Q[z] + α * Q[z + 1] |
| 98 | + Iid[c] += Qₖ(t′) |
95 | 99 | end |
96 | 100 | end |
97 | | - @. image * π / K |
| 101 | + sum(I) .* π ./ K |
98 | 102 | end |
99 | 103 |
|
100 | 104 | end # module |
0 commit comments