Direct Reconstruction

To implement our direct reconstruction algorithms we need to define a few more methods and types. We will start by defining the parameters for the backprojection and for the filtered backprojection. Afterwards we can implement the algorithm itself.

Parameters and Processing

For convenience we first introduce a new abstract type for the direct reconstruction paramters:

abstract type AbstractDirectRadonReconstructionParameters <: AbstractRadonReconstructionParameters end

The backprojection parameters are simple and only contain the number of angles:

Base.@kwdef struct RadonBackprojectionParameters <: AbstractDirectRadonReconstructionParameters
  angles::Vector{Float64}
end
Main.RadonBackprojectionParameters

The filtered backprojection parameters are more complex and contain the number of angles and optionally the filter which should be used:

Base.@kwdef struct RadonFilteredBackprojectionParameters <: AbstractDirectRadonReconstructionParameters
  angles::Vector{Float64}
  filter::Union{Nothing, Vector{Float64}} = nothing
end
Main.RadonFilteredBackprojectionParameters

Since we have defined no default values for the angles, they are required to be set by the user. A more advanced implementation would also allow for the geometry to be set.

Next we will implement the process steps for both of our backprojection variants. Since RadonKA.jl expects 2D or 3D arrays we have to transform our time series accordingly.

function AbstractImageReconstruction.process(algoT::Type{<:AbstractDirectRadonAlgorithm}, params::AbstractDirectRadonReconstructionParameters, data::AbstractArray{T, 4}) where {T}
  result = []
  for i = 1:size(data, 4)
    push!(result, process(algoT, params, view(data, :, :, :, i)))
  end
  return cat(result..., dims = 4)
end
AbstractImageReconstruction.process(::Type{<:AbstractDirectRadonAlgorithm}, params::RadonBackprojectionParameters, data::AbstractArray{T, 3}) where {T} = RadonKA.backproject(data, params.angles)
AbstractImageReconstruction.process(::Type{<:AbstractDirectRadonAlgorithm}, params::RadonFilteredBackprojectionParameters, data::AbstractArray{T, 3}) where {T} = RadonKA.backproject_filtered(data, params.angles; filter = params.filter)

Algorithm

The direct reconstruction algorithm has essentially no state to store between reconstructions and thus only needs its parameters as fields. We want our algorithm to accept any combination of our preprocessing and direct reconstruction parameters. This we encode in a new type:

Base.@kwdef struct DirectRadonParameters{P <: AbstractRadonPreprocessingParameters, R <: AbstractDirectRadonReconstructionParameters} <: AbstractRadonParameters
  pre::P
  reco::R
end

And the according processing step:

function AbstractImageReconstruction.process(algoT::Type{<:AbstractDirectRadonAlgorithm}, params::DirectRadonParameters{P, R}, data::AbstractArray{T, 4}) where {T, P<:AbstractRadonPreprocessingParameters, R<:AbstractDirectRadonReconstructionParameters}
  data = process(algoT, params.pre, data)
  return process(algoT, params.reco, data)
end

Now we can define the algorithm type itself. Algorithms are usually constructed with one argument passing in the user parameters:

mutable struct DirectRadonAlgorithm{D <: DirectRadonParameters} <: AbstractDirectRadonAlgorithm
  parameter::D
  output::Channel{Any}
  DirectRadonAlgorithm(parameter::D) where D = new{D}(parameter, Channel{Any}(Inf))
end

And they implement a method to retrieve the used parameters:

AbstractImageReconstruction.parameter(algo::DirectRadonAlgorithm) = algo.parameter

Algorithms are assumed to be stateful. To ensure thread safety, we need to implement the lock and unlock functions. We will use the output channel as a lock:

Base.lock(algo::DirectRadonAlgorithm) = lock(algo.output)
Base.unlock(algo::DirectRadonAlgorithm) = unlock(algo.output)

And implement the put! and take! functions, mimicking the behavior of a FIFO channel for reconstructions:

Base.take!(algo::DirectRadonAlgorithm) = Base.take!(algo.output)
function Base.put!(algo::DirectRadonAlgorithm, data::AbstractArray{T, 4}) where {T}
  lock(algo) do
    put!(algo.output, process(algo, algo.parameter, data))
  end
end

The way the behaviour is implemented here, the algorithm does not buffer any inputs and instead blocks until the currenct reconstruction is done. Outputs are stored until they are retrieved.

With wait and isready we can check if the algorithm is currently processing data or if it is ready to accept new inputs:

Base.wait(algo::DirectRadonAlgorithm) = wait(algo.output)
Base.isready(algo::DirectRadonAlgorithm) = isready(algo.output)

This page was generated using Literate.jl.