2D Branchless Distance-driven Projection and Backprojection

This page describes the 2D branchless distance-driven projection and backprojection method of Basu & De Man, 2006 for fan-beam geometries with a "flat" detector using the Julia package Sinograms.jl.

This page compares the results to the radon transform method.

This page comes from a single Julia file: 08-bdd2d.jl.

You can access the source code for such Julia documentation using the 'Edit on GitHub' link in the top right. You can view the corresponding notebook in nbviewer here: 08-bdd2d.ipynb, or open it in binder here: 08-bdd2d.ipynb.

Setup

Packages needed here.

using ImageGeoms: ImageGeom, fovs, MaskCircle
import ImageGeoms # downsample
using ImagePhantoms: shepp_logan, SheppLoganToft, radon, phantom
using MIRTjim: jim, prompt
import Plots
using Sinograms: project_bdd, backproject_bdd
import Sinograms # downsample, _ar, _dso
using Sinograms: SinoFanFlat, rays, plan_fbp, fbp, Window, Hamming, sino_geom_plot!, angles
using Unitful: mm

The following line is helpful when running this file as a script; this way it will prompt user to hit a key after each figure is displayed.

isinteractive() ? jim(:prompt, true) : prompt(:draw);

Fan-beam sinogram of SheppLoganToft phantom

For illustration, we start by synthesizing a fan-beam sinogram of the SheppLoganToft phantom.

For completeness, we use units (from Unitful), but units are optional.

Define the sinogram geometry

down = 4 # save time
rg = SinoFanFlat( ; nb = 910, d = 1.0239mm, na = 360, dsd = 949mm, dod = 408mm)
rg = Sinograms.downsample(rg, down)
SinoFanFlat{Unitful.Quantity{Float64, 𝐋, Unitful.FreeUnits{(mm,), 𝐋, nothing}}, Float32} :
 nb::Int64 226
 d::Unit{Float64} 4.0956 mm
 offset::Float32 0.0
 na::Int64 90
 orbit::Float32 360.0
 orbit_start::Float32 0.0
 source_offset::Unit{Float64} 0.0 mm
 dsd::Unit{Float64} 949.0 mm
 dod::Unit{Float64} 408.0 mm

Define the image geometry

ig = ImageGeom(MaskCircle(); dims=(512,512), deltas = (1mm,1mm))
ig = ImageGeoms.downsample(ig, down)
ImageGeoms.ImageGeom{2, NTuple{2,Unitful.Quantity{Int64, 𝐋, Unitful.FreeUnits{(mm,), 𝐋, nothing}}}, BitMatrix}
 dims::NTuple{2,Int64} (128, 128)
 deltas::NTuple{2,Unitful.Quantity{Int64, 𝐋, Unitful.FreeUnits{(mm,), 𝐋, nothing}}} (4 mm, 4 mm)
 offsets::NTuple{2,Float32} (0.0f0, 0.0f0)
 mask: 128×128 BitMatrix {12956 of 16384}

Make a tuple to define imaging geometry for bdd code. todo

geo = (DSD = rg.dsd, DS0 = Sinograms._dso(rg), pSize = ig.deltas[1],
    dSize = rg.d, nPix = ig.dims[1], nDet = rg.nb, angle = Sinograms._ar(rg))
(DSD = 949.0 mm, DS0 = 541.0 mm, pSize = 4 mm, dSize = 4.0956 mm, nPix = 128, nDet = 226, angle = 0.0f0:0.06981317f0:6.213372f0)

Examine the geometry to verify the FOV:

pa = jim(axes(ig), ig.mask; prompt=false)
sino_geom_plot!(rg, ig)
Example block output
prompt()

Ellipse parameters for SheppLoganToft phantom:

μ = 0.01 / mm # typical linear attenuation coefficient
ob = shepp_logan(SheppLoganToft(); fovs = fovs(ig), u = (1, 1, μ))
testimage = phantom(axes(ig)..., ob) # Create a phantom image
pt = jim(axes(ig), testimage) # Show the true phantom image
Example block output

Fan-beam sinograms for phantom:

if !@isdefined(sinogramR)
    @time sinogramR = radon(rays(rg), ob)
    # Ensure that sinogram is not truncated
    @assert all(==(0), sum(abs, sinogramR, dims=2)[[1,end]])
end;

if !@isdefined(sinogramB)
    @time sinogramB = project_bdd(reverse(rot180(testimage'), dims=2), geo)
    sinogramB = sinogramB' # todo
end;

p1 = jim(axes(rg), sinogramB; prompt=false,
 title="bdd_2d sinogram", xlabel="r", ylabel="ϕ")
p2 = jim(axes(rg), sinogramR; prompt=false,
 title="Radon test sinogram", xlabel="r", ylabel="ϕ")
p12 = jim(p1,p2)
Example block output

Difference of sinogram using bdd_2d versus radon method.

pd = jim(axes(rg), sinogramR - sinogramB;
 title="Difference sinogram", xlabel="r", ylabel="ϕ")
Example block output

Backprojection with bdd_2d

(The un-filtered back-projected image is not a useful reconstruction. See next section for image reconstruction via FBP.)

if !@isdefined(imageB)
    @time imageB = backproject_bdd(sinogramB'/1mm, geo) # todo
    imageB = rotr90(imageB) # todo
end
pb = jim(axes(ig), imageB;
 title="bdd_2d backprojection", xlabel="x", ylabel="y")
Example block output

Image reconstruction via FBP

Compare the reconstructed image using the bdd_2d sinogram and the radon sinogram.

Here we start with a "plan" that would save work if we were reconstructing many images. For illustration we include Hamming window.

plan = plan_fbp(rg, ig; window = Window(Hamming(), 1.0))
fbp_image_b = fbp(plan, sinogramB)
fbp_image_r = fbp(plan, sinogramR);

A narrow color window is needed to see the soft tissue structures:

clim = (0.04, 1.1) .* μ
r1 = jim(axes(ig), fbp_image_b, "FBP image with bdd_2d"; clim)
r2 = jim(axes(ig), fbp_image_r, "FBP image with radon"; clim)
r12 = jim(r1,r2; size=(1000,300))
Example block output

For comparison, here is the difference image.

pe = jim(axes(ig), fbp_image_b - fbp_image_r, "Difference image")
Example block output

This page was generated using Literate.jl.