Skip to content

Commit 65fafe8

Browse files
authored
Parker weighting for CBCT (#46)
* Test f32 * Construct :short cases * Move parker shape to plan * 2D parker weights * Parker weighting for 3d * Test Parker weighting * Add short scan example
1 parent f18f5b4 commit 65fafe8

File tree

19 files changed

+233
-120
lines changed

19 files changed

+233
-120
lines changed

docs/lit/examples/07-fdk.jl

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,3 +133,20 @@ jim(axes(ig), err_flat, "Error image (flat)"; clim = (-1,1) .* (0.05μ))
133133
As expected for CBCT,
134134
the largest errors are in the end slices.
135135
=#
136+
137+
138+
#=
139+
## Short scan
140+
=#
141+
cg = CtFanFlat(:short ; p...)
142+
proj_short = radon(rays(cg), ob)
143+
jim(cg.s, cg.t, proj_short ;
144+
title="Shepp-Logan projections (flat,short)", xlabel="s", ylabel="t")
145+
146+
plan_short = plan_fbp(cg, ig; window = Window(Hamming(), 1.0))
147+
fdk_short = fdk(plan_short, proj_short)
148+
jim(axes(ig), fdk_short, "FDK image (flat,short)"; clim)
149+
150+
#
151+
err_short = fdk_short - true_image
152+
jim(axes(ig), err_flat, "Error image (flat,short)"; clim = (-1,1) .* (0.05μ))

src/fbp2/fbp.jl

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
export fbp
44

55
using ImageGeoms: ImageGeom
6-
# using Sinograms: SinoGeom, SinoPar, SinoFan, SinoMoj, parker_weight, _reale, FBPplan
6+
# using Sinograms: SinoGeom, SinoPar, SinoFan, SinoMoj, _reale, FBPplan
77

88

99
"""
@@ -21,6 +21,8 @@ out
2121
- `sino_filt::Matrix{<:Number}` filtered sinogram(s)
2222
2323
"""
24+
fbp
25+
2426
function fbp(
2527
plan::FBPNormalPlan{<:SinoPar},
2628
sino::AbstractMatrix{<:Number},
@@ -62,14 +64,11 @@ function fbp(
6264
ig::ImageGeom,
6365
sino::AbstractMatrix{<:Number},
6466
filter::AbstractVector{<:Number},
65-
parker_weight::AbstractArray{<:Number} = ones(size(sino,2)),
67+
parker_weight::AbstractMatrix{<:Number} = ones(1,1),
6668
)
6769
dims(sg) == size(sino) || error("bad sino size")
68-
parker = length(parker_weight) == sg.na ? parker_weight' :
69-
size(parker_weight) == dims(sg) ? parker_weight :
70-
error("bad parker size $(size(parker_weight))")
7170

72-
sino_filt = sino .* parker
71+
sino_filt = sino .* parker_weight
7372
sino_filt = fbp_sino_filter(sino_filt, filter)
7473

7574
image = fbp_back(sg, ig, sino_filt)

src/fbp2/parker.jl

Lines changed: 52 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -6,28 +6,41 @@ export parker_weight
66
using LazyGrids: ndgrid
77

88

9-
# parallel-beam case: returns a Vector
9+
"""
10+
parker_weight(sg::SinoGeom ; T = Float32)
11+
Compute Parker weighting for non-360° orbits.
12+
See http://doi.org/10.1118/1.595078.
13+
Returns `Matrix{T}` of size:
14+
- (1,1) for `SinoPar` with typical 180 or 360 orbit
15+
- (1,na) for `SinoPar` with atypical orbit
16+
- (1,1) for `SinoFan` with typical 360 orbit
17+
- (ns,na) for `SinoFan` with typical 360 orbit
18+
"""
19+
parker_weight
20+
21+
22+
# parallel-beam case
1023
function parker_weight_par(
1124
orbit::RealU,
1225
ad::AbstractVector{<:RealU}, # angles in degrees: sg.ad - sg.orbit_start
1326
;
1427
T::Type{<:Real} = Float32,
1528
)
1629

17-
wt = ones(T, length(ad))
18-
1930
if (orbit ÷ 180) * 180 == orbit
20-
return wt # no weighting needed
31+
return ones(T, 1, 1) # no weighting needed
2132
end
2233

23-
if orbit < 180
34+
if abs(orbit) < 180
2435
@warn("orbit $orbit < 180")
25-
return wt # nonuniform weighting would not help
36+
return ones(T, 1, 1) # nonuniform weighting would not help
2637
end
2738

39+
orbit = abs(orbit)
2840
orbit > 360 && error("only 180 ≤ |orbit| ≤ 360 supported for Parker weighting")
2941
extra = orbit - 180 # extra beyond 180
3042

43+
wt = ones(T, 1, length(ad)) # (1,na)
3144
ad = abs.(ad)
3245
ii = ad .< extra
3346
@. wt[ii] = abs2(sin(ad[ii] / extra * π/2))
@@ -38,17 +51,11 @@ function parker_weight_par(
3851
end
3952

4053

41-
"""
42-
parker_weight(sg::SinoGeom ; T = Float32)
43-
Compute Parker weighting for non-360° orbits.
44-
Returns `Vector{T}` of length `sg.na` for `SinoPar`.
45-
Returns `Matrix{T}` of size `dims(sg)` for `SinoFan`.
46-
"""
47-
parker_weight(sg::SinoPar ; T::Type{<:Real} = Float32)::Vector{T} =
54+
parker_weight(sg::SinoPar ; T::Type{<:Real} = Float32)::Matrix{T} =
4855
parker_weight_par(sg.orbit, sg.ad .- sg.orbit_start ; T)
4956

5057

51-
function parker_weight_fan(
58+
function parker_weight_fan_short(
5259
nb::Int,
5360
na::Int,
5461
orbit::RealU,
@@ -65,7 +72,7 @@ function parker_weight_fan(
6572
@warn("orbit $orbit is less than a short scan $orbit_short")
6673

6774
orbit > orbit_short + rad2deg(ar[2] - ar[1]) &&
68-
@warn("orbit $orbit exeeds short scan $orbit_short by %g views")
75+
@warn("orbit $orbit exceeds short scan $orbit_short by %g views")
6976
# (orbit - orbit_short) / rad2deg(ar[2] - ar[1]))
7077

7178
bet = ar .- ar[1] # trick: force 0 start, so this ignores orbit_start!
@@ -96,22 +103,47 @@ end
96103

97104

98105
function parker_weight(sg::SinoFan; T::Type{<:Real} = Float32)::Matrix{T}
99-
wt = ones(T, dims(sg))
100106
if (sg.orbit ÷ 360) * 360 == sg.orbit
101-
return wt
107+
return ones(T, 1, 1) # no weighting needed
102108
end
103-
return parker_weight_fan(
109+
return parker_weight_fan_short(
104110
sg.nb, sg.na, sg.orbit, sg.orbit_short,
105111
sg.ar, sg.gamma, sg.gamma_max; T
106112
)
107113
end
108114

109115

110-
function parker_weight(sg::SinoMoj ; T::Type{<:Real} = Float32)::Vector{T}
116+
function parker_weight(sg::SinoMoj ; T::Type{<:Real} = Float32)
111117
orbit = abs(sg.orbit)
112118
na = sg.na
113119
((sg.orbit ÷ 180) * 180 == orbit) ||
114120
throw("No Parker weighting for Mojette geometry with orbit=$orbit")
115-
wt = ones(T, na)
121+
wt = ones(T, 1, 1)
116122
return wt
117123
end
124+
125+
126+
function parker_weight_fan_short(cg::CtFan; kwargs...)
127+
weight = parker_weight_fan_short(
128+
cg.ns, cg.na, cg.orbit, cg.orbit_short,
129+
cg.ar, cg.gamma, cg.gamma_max; kwargs...,
130+
)
131+
weight .*= 360 / cg.orbit_short # trick due to scaling in cbct-back
132+
return weight
133+
end
134+
135+
136+
"""
137+
parker_weight(cg::CtFan; T::Type{<:Real} = Float32, kwargs...)
138+
For 3D case, return `Array{T,3}` where size is
139+
- `(1,1,1)` typical fan case with 360° orbit
140+
- `(ns,1,na)` atypical fan case including short scan
141+
"""
142+
function parker_weight(cg::CtFan; T::Type{<:Real} = Float32, kwargs...)
143+
if (cg.orbit ÷ 360) * 360 == cg.orbit
144+
return ones(T, 1, 1, 1) # (1,1,1) for type stability
145+
end
146+
weight = parker_weight_fan_short(cg; kwargs...)
147+
weight = reshape(weight, dims(cg)[1], 1, dims(cg)[3]) # (ns,1,na)
148+
return weight
149+
end

src/fbp2/plan2.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ struct FBPNormalPlan{
3535
sg::S
3636
ig::I
3737
filter::H # frequency response Hk of apodized ramp filter, length npad
38-
parker_weight::P # typically a 1D or 2D array of nonnegative reals
38+
parker_weight::P # typically a Matrix of nonnegative reals
3939
# moj::Moj # todo
4040

4141
#=

src/fbp3/cbct-back.jl

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ function cbct_back(
3535
(oneunit(Td) * oneunit(To) / oneunit(Td) + oneunit(Toffset)))
3636

3737
return cbct_back_fan(proj,
38-
cg.ar,
38+
cg.ar, # "betas"
3939
cg.dsd, cg.dso,
4040
# cg.offset_source::RealU,
4141
cg.ds, cg.dt,
@@ -129,7 +129,7 @@ function cbct_back_fan!(
129129
cosβ = cos.(betas)
130130

131131
# scale projections by dβ for Riemann-like integration
132-
= diff(betas)
132+
= diff(betas) # typically 2π/na for 360° orbit
133133
= [dβ[1]; dβ] # todo
134134
proj = proj .* reshape(dβ, 1, 1, :) / 2
135135

src/fbp3/fdk.jl

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,10 @@ function fdk_weight_cyl_arc(
1414
t::RealU,
1515
dsd::RealU,
1616
dso::RealU,
17+
# T::Type{<:AbstractFloat} = Float32,
1718
)
18-
return (dso/dsd) * cos(s / dsd) / sqrt(abs2(t / dsd) + 1)
19+
T = Float32
20+
return T((dso/dsd) * cos(s / dsd) / sqrt(abs2(t / dsd) + 1))
1921
end
2022

2123
function fdk_weight_cyl_flat(
@@ -24,15 +26,18 @@ function fdk_weight_cyl_flat(
2426
dsd::RealU,
2527
dso::RealU,
2628
)
27-
return dso / sqrt(abs2(s) + abs2(t) + abs2(dsd))
29+
T = Float32
30+
return T(dso / sqrt(abs2(s) + abs2(t) + abs2(dsd)))
2831
end
2932

3033
# (ns,nt) matrix
3134
fdk_weight_cyl(cg::CtFanArc) =
32-
fdk_weight_cyl_arc.(cg.s, cg.t', cg.dsd, cg.dso)
35+
fdk_weight_cyl_arc.(cg.s, cg.t', cg.dsd, cg.dso)::Matrix{Float32}
36+
# Iterators.map((st) -> fdk_weight_cyl_arc(st..., cg.dsd, cg.dso),
37+
# Iterators.product(ct_geom_s(cg), ct_geom_t(cg))) # eltype = Any !?
3338

3439
fdk_weight_cyl(cg::CtFanFlat) =
35-
fdk_weight_cyl_flat.(cg.s, cg.t', cg.dsd, cg.dso)
40+
fdk_weight_cyl_flat.(cg.s, cg.t', cg.dsd, cg.dso)::Matrix{Float32}
3641

3742

3843
"""
@@ -55,8 +60,12 @@ References: Feldkamp, Davis, Kress, JOSA-A, 1(6):612-9, June 1984.
5560
function fdk(plan::FDKplan, proj::AbstractArray{<:Number,3})
5661
cg = plan.cg
5762

58-
# step 1: apply weights
63+
size(proj) == dims(plan.cg) ||
64+
error("size mismatch $(size(proj)) $(dims(plan.cg))")
65+
66+
# step 1: apply cone-beam weights
5967
proj = proj .* fdk_weight_cyl(cg) # todo: precompute with plan!
68+
proj .*= plan.parker_weight # todo: before or after filtering?
6069

6170
# step 2: filter each projection view
6271
proj = fbp_sino_filter(proj, plan.filter)

src/fbp3/plan3.jl

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -35,14 +35,6 @@ struct FDKplan{
3535
end
3636

3737

38-
function parker_weight(cg::CtFan; kwargs...)
39-
return parker_weight_fan(
40-
cg.ns, cg.na, cg.orbit, cg.orbit_short,
41-
cg.ar, cg.gamma, cg.gamma_max; kwargs...,
42-
)
43-
end
44-
45-
4638
"""
4739
plan = plan_fbp(cg, ig; window=Window(), ...)
4840

src/geom/common.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ export angles
1212
Base.show(io::IO, ::MIME"text/plain", st::RayGeom) = _show(io, MIME("text/plain"), st)
1313

1414

15-
function _getproperty(st::RayGeom, s::Symbol, arg)
15+
function _getproperty(st::RayGeom, s::Symbol, arg) # not type stable :(
1616
d = Dict(arg)
1717
return haskey(d, s) ? d[s](st) : getfield(st, s)
1818
end

0 commit comments

Comments
 (0)