Skip to content

Commit

Permalink
Merge pull request #8 from JuliaImageRecon/nh/docs
Browse files Browse the repository at this point in the history
Add documentation
  • Loading branch information
nHackel authored Dec 11, 2024
2 parents 226fb4c + aa9b060 commit 89e3472
Show file tree
Hide file tree
Showing 27 changed files with 1,141 additions and 13 deletions.
22 changes: 22 additions & 0 deletions .github/workflows/CI.yml
Original file line number Diff line number Diff line change
Expand Up @@ -48,3 +48,25 @@ jobs:
${{ runner.os }}-
- uses: julia-actions/julia-buildpkg@v1
- uses: julia-actions/julia-runtest@v1

docs:
name: Documentation
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: julia-actions/setup-julia@v1
with:
version: '1'
- run: |
julia --project=docs -e '
using Pkg
Pkg.develop(PackageSpec(path=pwd()))
Pkg.instantiate()'
- run: |
julia --project=docs -e '
using Documenter: DocMeta, doctest
using AbstractImageReconstruction'
- run: julia --project=docs docs/make.jl
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
DOCUMENTER_KEY: ${{ secrets.DOCUMENTER_KEY }}
9 changes: 9 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,10 @@
*.jl.cov
*.jl.*.cov
*.jl.mem
docs/build/
docs/site/
docs/src/generated/

Manifest.toml

/Manifest.toml
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@

[![Build Status](https://github.com/JuliaImageRecon/AbstractImageReconstruction.jl/actions/workflows/CI.yml/badge.svg?branch=main)](https://github.com/JuliaImageRecon/AbstractImageReconstruction.jl/actions/workflows/CI.yml?query=branch%3Amain)

[![](https://img.shields.io/badge/docs-latest-blue.svg)](https://JuliaImageRecon.github.io/AbstractImageReconstruction.jl/latest)


This package contains an interface and type hierarchy for image reconstruction algorithms and their parameters, together with associated utility tools.

## Installation
Expand Down
15 changes: 15 additions & 0 deletions docs/Project.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
[deps]
AbstractImageReconstruction = "a4b4fdbf-6459-4ec9-990d-77e1fa24a91b"
BenchmarkTools = "6e4b80f9-dd63-53aa-95a3-0cdb28fa8baf"
CairoMakie = "13f3f980-e62b-5c42-98c6-ff1f3baf88f0"
Documenter = "e30172f5-a6a5-5a46-863b-614d45cd2de4"
ImageGeoms = "9ee76f2b-840d-4475-b6d6-e485c9297852"
ImagePhantoms = "71a99df6-f52c-4da1-bd2a-69d6f37f3252"
LinearOperatorCollection = "a4a2c56f-fead-462a-a3ab-85921a5f2575"
Literate = "98b081ad-f1c9-55d3-8b20-4c87d4299306"
Observables = "510215fc-4207-5dde-b226-833fc4488ee2"
RadonKA = "86de8297-835b-47df-b249-c04e8db91db5"
RegularizedLeastSquares = "1e9c538a-f78c-5de5-8ffb-0b6dbe892d23"

[compat]
Documenter = "1"
53 changes: 53 additions & 0 deletions docs/make.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
using Documenter, Literate, AbstractImageReconstruction, Observables

# Generate examples
OUTPUT_BASE = joinpath(@__DIR__(), "src/generated")
INPUT_BASE = joinpath(@__DIR__(), "src/literate")
for (_, dirs, _) in walkdir(INPUT_BASE)
for dir in dirs
OUTPUT = joinpath(OUTPUT_BASE, dir)
INPUT = joinpath(INPUT_BASE, dir)
for file in filter(f -> endswith(f, ".jl"), readdir(INPUT))
Literate.markdown(joinpath(INPUT, file), OUTPUT)
end
end
end

makedocs(
format = Documenter.HTML(;
prettyurls=get(ENV, "CI", "false") == "true",
canonical="https://github.com/JuliaImageRecon/AbstractImageReconstruction.jl",
assets=String[],
collapselevel=1,
),
repo="https://github.com/JuliaImageRecon/AbstractImageReconstruction.jl/blob/{commit}{path}#{line}",
modules = [AbstractImageReconstruction],
sitename = "AbstractImageReconstruction.jl",
authors = "Niklas Hackelberg, Tobias Knopp",
pages = [
"Home" => "index.md",
"Example: Radon Reconstruction Package" => Any[
"Introduction" => "example_intro.md",
"Radon Data" => "generated/example/0_radon_data.md",
"Interface" => "generated/example/1_interface.md",
"Direct Reconstruction" => "generated/example/2_direct.md",
"Direct Reconstruction Result" => "generated/example/3_direct_result.md",
"Iterative Reconstruction" => "generated/example/4_iterative.md",
"Iterative Reconstruction Result" => "generated/example/5_iterative_result.md",
],
"How to" => Any[
"Serialization" => "generated/howto/serialization.md",
"Caching" => "generated/howto/caching.md",
"Observables" => "generated/howto/observables.md",
],
"API Reference" => "API/api.md",
#"Regularization Terms" => "API/regularization.md"],

],
pagesonly = true,
checkdocs = :none,
doctest = false,
doctestfilters = [r"(\d*)\.(\d{4})\d+"]
)

deploydocs(repo = "github.com/JuliaImageRecon/AbstractImageReconstruction.jl.git", push_preview = true)
50 changes: 50 additions & 0 deletions docs/src/API/api.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
# API for Solvers
This page contains documentation of the public API of the AbstractImageReconstruction. In the Julia
REPL one can access this documentation by entering the help mode with `?`

## Algorithm and Parameters
```@docs
AbstractImageReconstruction.AbstractImageReconstructionAlgorithm
AbstractImageReconstruction.reconstruct
Base.put!(::AbstractImageReconstructionAlgorithm, ::Any)
Base.take!(::AbstractImageReconstructionAlgorithm)
AbstractImageReconstruction.AbstractImageReconstructionParameters
AbstractImageReconstruction.process
AbstractImageReconstruction.parameter
```

## RecoPlan
```@docs
AbstractImageReconstruction.RecoPlan
Base.propertynames(::RecoPlan)
Base.getproperty(::RecoPlan, ::Symbol)
Base.getindex(::RecoPlan, ::Symbol)
Base.setproperty!(::RecoPlan, ::Symbol, ::Any)
AbstractImageReconstruction.setAll!
AbstractImageReconstruction.clear!
Base.ismissing(::RecoPlan, ::Symbol)
Observables.on(::Any, ::RecoPlan, ::Symbol)
Observables.off(::RecoPlan, ::Symbol, ::Any)
AbstractImageReconstruction.build
AbstractImageReconstruction.toPlan
AbstractImageReconstruction.savePlan
AbstractImageReconstruction.loadPlan
AbstractImageReconstruction.loadListener!
AbstractImageReconstruction.parent(::RecoPlan)
AbstractImageReconstruction.parent!(::RecoPlan, ::RecoPlan)
AbstractImageReconstruction.parentproperty
AbstractImageReconstruction.parentproperties
```

## Miscellaneous
```@docs
AbstractImageReconstruction.LinkedPropertyListener
AbstractImageReconstruction.ProcessResultCache
Base.hash(::AbstractImageReconstructionParameters, ::UInt64)
AbstractImageReconstruction.toKwargs(::AbstractImageReconstructionParameters)
AbstractImageReconstruction.fromKwargs
AbstractImageReconstruction.toDict
AbstractImageReconstruction.toDict!
AbstractImageReconstruction.toDictValue
AbstractImageReconstruction.toDictValue!
```
42 changes: 42 additions & 0 deletions docs/src/example_intro.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
# Small Reconstruction Package for Radon projections
In this example we will implement a small image reconstruction package using `AbstractImageReconstruction.jl`. Our reconstruction package `OurRadonreco` aims to provide direct and iterative reconstruction algorithms for Radon projection data.

Most of the desired functionality is already implemented in various Julia packages. Our reconstruction package now needs to properly link these packages and transform the data into the appropriate formats for each package.

!!! note
The example is intended for developers of reconstruction packages that use `AbstractImageReconstruction`. End-users of such a package can consult the result sections of the example to see the high-level interface of `AbstractImagerReconstruction` and should otherwise consult the documentation of the concrete reconstruction package itself.

## Installation
We can install `AbstractImageReconstruction` using the Julia package manager. Open a Julia REPL and run the following command:

```julia
using Pkg
Pkg.add("AbstractImageReconstruction")
```
This will download and install AbstractImageReconstruction.jl and its dependencies. To install a different version, please consult the [Pkg documentation](https://pkgdocs.julialang.org/dev/managing-packages/#Adding-packages). In addition to AbstractImageReconstruction.jl, we will need a few more packages to get started, which we can install the same way.


[RadonKA.jl](https://github.com/roflmaostc/RadonKA.jl/tree/main) provides us with fast Radon forward and backward projections, which we can use for direct reconstructions and to prepare sample data for our package.

[LinearOperatorCollection.jl](https://github.com/JuliaImageRecon/LinearOperatorCollection.jl) wraps the functionality of RadonKA.jl into a matrix-free linear operator, that can be used in iterative solvers.

[RegularizedLeastSquares.jl](https://github.com/JuliaImageRecon/RegularizedLeastSquares.jl) offers a variety of iterative solvers and regularization options.

[ImagePhantoms.jl](https://github.com/JuliaImageRecon/ImagePhantoms.jl) and [ImageGeoms.jl](https://github.com/JuliaImageRecon/ImageGeoms.jl) allow us to define digital software "phantoms", which we will use to test our reconstruction algorithms.

Lastly, we will use [CairoMakie.jl](https://docs.makie.org/stable/) to visualize our results.

## Outline
[Radon Data](generated/example/0_radon_data.md): this section we will familiarise ourselves with RadonKA.jl and define a small data format for three-dimensional time series sinograms. We also create the inverse problem that we will solve in the rest of the example

[Interface](generated/example/1_interface.md): Here we define the abstract types we will use in our package and take a look at what we need to implement to interact with `AbstractImageReconstruction`. We also start with a first processing step of our algorithms.

[Direct Reconstruction](generated/example/2_direct.md): Now we extend our abstract types with a concrete implementation of reconstruction algorithms using the backprojection and filtered backprojection.

[Direct Reconstruction Result](generated/example/3_direct_result.md): This section shows how to use the algorithm we have just implemented.

[Iterative Reconstruction](generated/example/4_iterative.md): We finish our small example package by implementing an iterative reconstruction algorithm. For this algorithm we require more complex parametrization and data processing.

[Iterative Reconstruction Result](generated/example/5_iterative_result.md): The last section shows again how to use the just implemented algorithm. But it also highlights `RecoPlans`, which are a core utility of `AbstractImageReconstruction`. These plans allow a user to easily configure, save and load algorithms as templates.

For an even more detailed reconstruction package we refer to the magnetic particle imaging reconstruction package [MPIReco.jl](https://github.com/MagneticParticleImaging/MPIReco.jl).
49 changes: 49 additions & 0 deletions docs/src/index.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
# AbstractImageReconstruction.jl

*Abstract Interface for Medical Image Reconstruction Packages*

## Introduction

AbstractImageReconstruction.jl is a Julia package that serves as the core API for medical imaging packages. It provides implementations an interface and type hierarchy to represent and implement image reconstruction algorithms, their parameters and runtime behaviour. In particular, this package serves as the API of the Julia packages [MPIReco.jl](https://github.com/MagneticParticleImaging/MPIReco.jl).

## Features

* Reconstruction control flow defined with multiple-dispatch on extensible and exchangable type hierarchies
* Storing, loading and manipulating of reconstruction algorithms with (partially) set parameters
* Attaching callbacks to parameter changes with Observables.jl
* Various generic utilities such as transparent caching of intermediate reconstruction results

## Installation

Within Julia, use the package manager:
```julia
using Pkg
Pkg.add("AbstractImageReconstruction")
```
AbstractImageReconstruction is not intended to be used alone, but together with an image reconstruction package that implements the provided interface, such as [MPIReco.jl](https://github.com/MagneticParticleImaging/MPIReco.jl).

## Usage
The actual construction of reconstruction algorithms depends on the implementation of the reconstruction package. Once an algorithm is constructed with the given parameters, images can be reconstructed as follows:

```julia
using AbstractImageReconstruction, MPIReco

params = ... # Setup reconstruction paramter
algo = ... # Setup chosen algorithm with params
raw = ... # Setup raw data

image = reconstruct(algo, raw)
```
An algorithm can be transformed into a `RecoPlan`. These are mutable and transparent wrappers around the nested types of the algorithm and its parameters, which can be saved and restored to and from TOML files.

```julia
plan = toPlan(algo)
savePlan(MPIReco, "Example", plan)
plan = loadPlan(MPIReco, "Example", [MPIReco, RegularizedLeastSquares, MPIFiles])

algo2 = build(plan)
algo == algo2 # true
```
Unlike concrete algorithm instances, a `RecoPlan` may still be missing certain values of its properties. Futhermore, they can encode the structure of an image reconstruction algorithm without concrete parameterization.

It is also possible to attach functions to `RecoPlan` properties, that call user-specified functions if they are changed using `Observables.jl`. This allows specific `RecoPlans` to provide smart default parameter choices or embedding a plan into a GUI.
98 changes: 98 additions & 0 deletions docs/src/literate/example/0_radon_data.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
# # Radon Data

# In this example we will set up our radon data using RadonKA.jl, ImagePhantoms.jl and ImageGeoms.jl. We will start with simple 2D images and their sinograms and continue with a time series of 3D images and sinograms.

# ## Background
# The Radon transform is an integral transform that projects the values of a function(or a phantom) along straight lines onto a detector.
# These projections are recorded for a number of different angles and form the so-called sinogram. The Radon transform and its adjoint form the mathematical basis
# for computed tomography (CT) and other imaging modalities such as single photon emission computed tomography (SPECT) and positron emission tomography (PET).

# ## 2D Phantom
# We will use a simple Shepp-Logan phantom to generate our Radon data. The phantom is a standard test image for medical imaging and consists of a number of ellipses with different intensities.
# It can be generated using ImagePhantoms.jl and ImageGeoms.jl. as follows:

using ImagePhantoms, ImageGeoms
N = 256
image = shepp_logan(N, SheppLoganToft())
size(image)

# This produces a 256x256 image of a Shepp-Logan phantom. Next, we will generate the Radon data using `radon` from RadonKA.jl.
# The arguments of this function are the image or phantom to be transformed, the angles at which the projections are taken, and the used geometry of the system. For this example we will use the default parallel circle geometry.
# For more details, we refer to the RadonKA.jl documentation. We will use 256 angles for the projections, between 0 and π.
using RadonKA
angles = collect(range(0, π, 256))
sinogram = Array(RadonKA.radon(image, angles))
size(sinogram)


# To visualize our progress so far, we will use CairoMakie.jl and a small helper function:
using CairoMakie
function plot_image(figPos, img; title = "", width = 150, height = 150)
ax = CairoMakie.Axis(figPos[1, 1]; yreversed=true, title, width, height)
hidedecorations!(ax)
hm = heatmap!(ax, img)
Colorbar(figPos[1, 2], hm)
end
fig = Figure()
plot_image(fig[1,1], image, title = "Image")
plot_image(fig[1,2], sinogram, title = "Sinogram")
resize_to_layout!(fig)
fig

# ## 3D Pnantom
# RadonKA.jl also supports 3D Radon transforms. The first two dimensions are interpreted as the XY plane where the transform applied and the last dimensions is the rotational axis z of the projections.
# For that we need to create a 3D Shepp-Logan phantom. First we retrieve the parameters of the ellipsoids of the Shepp-Logan phantom:
shape = (64, 64, 64)
params = map(collect, ellipsoid_parameters(; fovs = shape));

# We then scale the intensities of the ellipsoids to [0.0, ..., 1.0]:
toft_settings = [1.0, -0.8, -0.2, -0.2, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1]
for idx in eachindex(toft_settings)
params[idx][10] = toft_settings[idx]
end

# Finally, we create the 3D Shepp-Logan phantom by defining and sampling our image geometry:
ob = ellipsoid(map(Tuple, params))
ig = ImageGeom(;dims = shape)
image = phantom(axes(ig)..., ob)
size(image)

# Now we can compute the 3D Radon transform of our phantom:
sinogram = Array(RadonKA.radon(image, angles))
size(sinogram)

# Let's visualize the 3D Radon data:
fig = Figure()
plot_image(fig[1,1], reverse(image[26,:,:]), title = "Slice YZ at z=26")
plot_image(fig[1,2], image[:,40,:], title = "Slice XZ at y=40")
plot_image(fig[2,1], reverse(image[:, :, 24]), title = "Slice XY at z=24")
plot_image(fig[2,2], reverse(sinogram[:,:,24]), title = "Sinogram at z=24")
plot_image(fig[3,1], reverse(image[:, :,16]), title = "Slice XY at z=16")
plot_image(fig[3,2], reverse(sinogram[:,:,16]), title = "Sinogram at z=16")
resize_to_layout!(fig)
fig


# ## Time Series of 3D Phantoms
# Lastly, we want to add a time dimension to our 3D phantom. For our example we will increase the intensity of the third ellipsoid every time step or frame.
images = similar(image, size(image)..., 5)
sinograms = similar(sinogram, size(sinogram)..., 5)
for (i, intensity) in enumerate(range(params[3][end], 0.3, 5))
params[3][end] = intensity
local ob = ellipsoid(map(Tuple, params))
local ig = ImageGeom(;dims = shape)
images[:, :, :, i] = phantom(axes(ig)..., ob)
sinograms[:, :, :, i] = Array(RadonKA.radon(images[:, :, :, i], angles))
end
size(sinograms)

fig = Figure()
for i = 1:5
plot_image(fig[i,1], reverse(images[:, :, 24, i]))
plot_image(fig[i,2], sinograms[:, :, 24, i])
end
resize_to_layout!(fig)
fig

# The goal of our reconstruction package is now to recover an approximation of the time-series 3D phantoms from a given time-series of sinograms.
# In the next section we will introduce our class hierarchies and explore the API of AbstractImageReconstruction.jl.
Loading

0 comments on commit 89e3472

Please sign in to comment.