jupytext | kernelspec | ||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
X-rays are a form of electromagnetic radiation with higher energy and therefore shorter wavelength compared to visible or ultra-violet light. They were first described systematically by Wilhelm Röntgen in 1895. As their true nature was unclear to Röntgen at the beginning, he called them X-radiation referring to the letter X often used for the unknown in mathematical formulas. X-rays are produced whenever charged particles, typically electrons, hit a material with a sufficient kinetic energy. This can either happen if the electrons stimulate characteristic X-ray emission or if they are scattered and slowed down by the strong electric field near nuclei with a high proton number (a radiation still refereed to by the German name Bremsstrahlung). If X-rays transverse through material, they interact with it in three main ways:
-
Photoelectric absorption: occurs if the incoming X-ray photon is absorbed by an atom of the material it hits. In this process, it transfers all its energy to one of the atom's electrons. This typically allows the electron to leave the atom as a free (negative) charge carrier and leaves the positively charged atom behind.
-
Compton scattering: refers to the inelastic scattering of the X-ray photon by an electron of the outer atom shell. In this process, part of the energy of the X-ray photon is transferred to the electron, again allowing it to leave the atom. Due to momentum and energy conservation, the X-ray photon changes its direction and has a longer wavelength.
-
Rayleigh scattering: refers to an elastic scattering of the X-ray photon, i.e., without loosing energy.
Depending on the energy spectrum of the X-rays and the material, these different processes contribute in different proportions to the overall interaction. As X-rays are ionizing radiation, their administration to biological tissue is harmful and needs to be well justified. An overview of where X-rays fit in the EM spectrum is shown in {numref}spectrum
Shortly after their discovery, images obtained by placing a target between an X-ray source and a photon detector such as shown Figure {numref}xhand
were used for medical diagnosis and projectional radiography is still one of the most routinely applied examinations.
+++
:container: container-fluid
:column: col-lg-6 col-md-6 col-sm-6 col-xs-12
:card: shadow-none border-0
```{figure} images/tomography/EM_Spectrum3-new.jpg
:height: 150px
:name: spectrum
Overview of the electromagnetic spectrum.
```
---
```{figure} images/tomography/hand.jpg
:height: 150px
:name: xhand
X-ray photograph of the hand of Röntgen's wife.
```
+++
Now we will describe the attenuation of the intensity of a monochromatic ray of photons traveling through a material by the above processes mathematically. For this, we first assume that the photons are emitted at
In the limit
which is solved by
If we now assume that the X-rays travel along an arbitrary ray
:label: lineInt
P_\ell = - \log \left(\frac{I_1}{I_0} \right) = \int_\ell u(x) dx.
So we see that in this formulation, we measure an integral of the unknown attenuation coefficient
---
height: 250px
name: radon
---
Left: sketch of the auxiliary variables used to describe the two dimensional Radon transform. Right: Sketch of $R_\theta u(t)$, the projection of a function $u(x_1,x_2)$ onto a detector at angle $\theta$
In projectional radiography, only a particular two dimensional projection of a three dimensional object radon
). Both the point of contact of this detection line with the unit sphere and its normal in this point are given as
We can now eliminate
:label: LinePara
\ell(\theta, t) = \{x \in \mathbb{R} \, | \, x_1 \cos(\theta) + x_2 \sin(\theta) = t\},
where we stressed that lineInt
yields a linear mapping
The transform above describes all possible X-ray measurements of LinePara
), where it is one. That means that
:tags: [hide-input]
import numpy as np
import matplotlib.pyplot as plt
import matplotlib as mpl
mpl.rcParams['figure.dpi'] = 300
from skimage.data import shepp_logan_phantom
from skimage.transform import radon, rescale
theta = np.linspace(0., 180., 160, endpoint=False)
image1 = np.zeros((160,160))
image1[80:90,80:90] = 1
image1[50:60,100:110] = 2
image1[100:110,60:70] = 1.5
image2 = np.zeros((160,160))
image2[20:80,60:100] = 1
image3 = rescale(shepp_logan_phantom(), scale=0.4, mode='reflect', multichannel=False)
sinogram1 = radon(image1, theta=theta, circle = False)
sinogram2 = radon(image2, theta=theta, circle = False)
sinogram3 = radon(image3, theta=theta, circle = False)
fig, ax = plt.subplots(2, 3, figsize=(8, 4.5), sharey=True)
ax[0,0].imshow(image1, cmap=plt.cm.Greys_r, extent=(0, 1, 0, 1))
ax[0,1].imshow(image2, cmap=plt.cm.Greys_r, extent=(0, 1, 0, 1))
ax[0,2].imshow(image3, cmap=plt.cm.Greys_r, extent=(0, 1, 0, 1))
ax[1,0].imshow(sinogram1, cmap=plt.cm.Greys_r,
extent=(0, 180, 0, 1), aspect=1.1*160)
ax[1,0].set_xlabel("Projection angle (deg)")
ax[1,0].set_ylabel("Projection position (pixels)")
ax[1,1].imshow(sinogram2, cmap=plt.cm.Greys_r,
extent=(0, 180, 0, 1), aspect=1.1*160)
ax[1,1].set_xlabel("Projection angle (deg)")
ax[1,2].imshow(sinogram3, cmap=plt.cm.Greys_r,
extent=(0, 180, 0, 1), aspect=1.1*160)
ax[1,2].set_xlabel("Projection angle (deg)")
fig.tight_layout()
plt.show()
Now that we can describe all possible X-ray measurements
:class: important
The one-dimensional Fourier transform of $f(\theta,\cdot) = R_{\theta} u$ is related to the two-dimensional Fourier transform of $u$ as
```{math}
:label: FourierSliceTheo
\widehat{f}(\theta,\omega) = \widehat{u}(\omega v),
```
with $v = (\cos \theta, \sin \theta)$.
This implies that the projection data for a particular angle $\theta$ fully determines the radial slice of the Fourier transform of $u$ with the same angle. Thereby, the complete Fourier transform of $u$ can be computed from $f(\theta, t)$ and as the Fourier transform is invertible, we can recover $u$ from $f$.
:class: dropdown
The proof of the Fourier slice theorem is short and instructive. First, we write out the rhs of {eq}`FourierSliceTheo`:
$$
\widehat{R_\theta u}(\omega) = \int_{-\infty}^{\infty} R_\theta[u] (t) e^{- 2 \pi i \omega t} dt = \int_{-\infty}^{\infty} \int_{\ell(\theta, t)} u(x_1,x_2) dx \;\, e^{- 2 \pi i \omega t} dt
$$
Then, we use the description of $\ell(\theta, t)$, {eq}`LinePara`, to substitute $t = x_1 \cos \theta + x_2 \sin \theta$:
$$
\int_{-\infty}^{\infty} \int_{-\infty}^{\infty} u(x_1,x_2) e^{- 2 \pi i \omega (x_1 \cos \theta + x_2 \sin \theta)} dx_1 dx_2 \\
:=
\int_{-\infty}^{\infty} \int_{-\infty}^{\infty} u(x_1,x_2) e^{- 2 \pi i (x_1 k_1 + x_2 k_2)} dx_1 dx_2 = \hat{u}(k),
$$
where we defined $k = [k_1, k_2]^T := \omega v = [\omega \cos \theta, \omega \sin \theta]^T$.
The Fourier slice theorem suggests a simple strategy to invert the Radon transform: First, we compute the one dimensional Fourier transform of each angular projection FourierSampling
, this means that the density of the frequency samples decreases towards the high frequencies, which contain information about the small scale image features.
---
height: 250px
name: FourierSampling
---
Sampling in Fourier space: The Fourier slice theorem {eq}`FourierSliceTheo` states that every angular projection $R_\theta[u](t)$ contains the full information about $\hat{u}(k_1,k_2)$ along one radial slice (red lines). A finite, regular sampling of $t$ translates into a finite, regular sampling along these lines (blue dots). It becomes apparent that lower frequencies ($\sqrt{k_1^2 + k_2^2}$ small) are sampled more densely than high frequencies.
The Fourier slice theorem is the basis the most widely used CT reconstruction technique, the filtered back projection (FBP). As the name suggests, FBP consists of two steps, a filtering step and a back projection step. We start with the back projection, which is a simple operation to map a sinogram
:label: BP
u_{BP}(x_1, x_2) = \int_0^\pi f(\theta, x_1 \cos \theta + x_2 \sin \theta) d\theta.
It turns out that the back projection operator is the adjoint
i.e., it only recovers FourierSampling
): In proportion, too many low frequencies are projected back, and low frequencies represent the smooth features of the image. A reasonable idea to improve upon this result is therefore to introduce weights that counter-act this imbalance in favour of the high-frequencies before the back projecting step. This corresponds to a high-pass filtering of the sinogram data
Below, we illustrate that if the filtered sinogram is now inserted into the back projection {eq}BP
, we converge to the true solution if the number of angles increases, and one can indeed show that
:tags: [hide-input]
import numpy as np
import matplotlib.pyplot as plt
import matplotlib as mpl
mpl.rcParams['figure.dpi'] = 300
from skimage.data import shepp_logan_phantom
from skimage.transform import radon, rescale, iradon
theta1 = np.linspace(0., 180., 4, endpoint=False)
theta2 = np.linspace(0., 180., 10, endpoint=False)
theta3 = np.linspace(0., 180., 160, endpoint=False)
image = rescale(shepp_logan_phantom(), scale=0.4, mode='reflect', multichannel=False)
sinogram1 = radon(image, theta=theta1, circle = False)
sinogram2 = radon(image, theta=theta2, circle = False)
sinogram3 = radon(image, theta=theta3, circle = False)
BP1 = iradon(sinogram1, theta=theta1, filter=None, circle = False)
BP2 = iradon(sinogram2, theta=theta2, filter=None, circle = False)
BP3 = iradon(sinogram3, theta=theta3, filter=None, circle = False)
FBP1 = iradon(sinogram1, theta=theta1, filter='ramp', circle = False)
FBP2 = iradon(sinogram2, theta=theta2, filter='ramp', circle = False)
FBP3 = iradon(sinogram3, theta=theta3, filter='ramp', circle = False)
fig, ax = plt.subplots(2, 3, figsize=(8, 4.5), sharey=True)
ax[0,0].imshow(BP1, cmap=plt.cm.Greys_r, extent=(0, 1, 0, 1))
ax[0,0].set_title('4 angles')
ax[0,1].imshow(BP2, cmap=plt.cm.Greys_r, extent=(0, 1, 0, 1))
ax[0,1].set_title('10 angles')
ax[0,2].imshow(BP3, cmap=plt.cm.Greys_r, extent=(0, 1, 0, 1))
ax[0,2].set_title('160 angles')
ax[1,0].imshow(FBP1, cmap=plt.cm.Greys_r, extent=(0, 1, 0, 1))
ax[1,1].imshow(FBP2, cmap=plt.cm.Greys_r, extent=(0, 1, 0, 1))
ax[1,2].imshow(FBP3, cmap=plt.cm.Greys_r, extent=(0, 1, 0, 1))
fig.tight_layout()
plt.show()
Alternatively, the filter may be applied after back projection. In this case the filter multiplies the two-dimensional Fourier transform of the back projected image by
The previous section showed that in order to reconstruct the image from the sinogram we have to multiply either by
:tags: [hide-input]
import numpy as np
import matplotlib.pyplot as plt
import matplotlib as mpl
mpl.rcParams['figure.dpi'] = 300
from skimage.data import shepp_logan_phantom
from skimage.transform import radon, rescale, iradon
theta = np.linspace(0., 180., 160, endpoint=False)
sigma = 1
u = rescale(shepp_logan_phantom(), scale=0.4, mode='reflect', multichannel=False)
f = radon(u, theta=theta, circle = False)
f_delta = f + np.random.normal(0,sigma,size=f.shape)
FBP_ramp = iradon(f_delta, theta=theta, filter='ramp', circle = False)
FBP_cosine = iradon(f_delta, theta=theta, filter='cosine', circle = False)
FBP_hann = iradon(f_delta, theta=theta, filter='hann', circle = False)
fig, ax = plt.subplots(1, 3, figsize=(8, 4.5), sharey=True)
ax[0].imshow(FBP_ramp, cmap=plt.cm.Greys_r, extent=(0, 1, 0, 1))
ax[0].set_title('ramp filter')
ax[1].imshow(FBP_cosine, cmap=plt.cm.Greys_r, extent=(0, 1, 0, 1))
ax[1].set_title('cosine filter')
ax[2].imshow(FBP_hann, cmap=plt.cm.Greys_r, extent=(0, 1, 0, 1))
ax[2].set_title('hann filter')
fig.tight_layout()
plt.show()
As we've seen in the previous sections, the Fourier Slice Theorem only applies when sufficient angular samples are available. An alternative to FBP are so-called algebraic reconstruction techniques which discretize the Radon transform using numerical quadrature and sets up a system of equations
where ANDERSEN198481
:
where
:tags: [hide-input]
import numpy as np
import matplotlib.pyplot as plt
import matplotlib as mpl
mpl.rcParams['figure.dpi'] = 300
from skimage.data import shepp_logan_phantom
from skimage.transform import radon, rescale, iradon, iradon_sart
theta = np.linspace(0., 180., 20, endpoint=False)
sigma = .1
niter = 10
u = rescale(shepp_logan_phantom(), scale=0.4, mode='reflect', multichannel=False)
f = radon(u, theta=theta)
f_delta = f + np.random.normal(0,sigma,size=f.shape)
u_fbp = iradon(f_delta, theta=theta, filter='hann')
u_sart = np.zeros((niter,u.shape[0],u.shape[1]))
u_sart[0] = iradon_sart(f_delta,theta=theta)
for k in range(niter-1):
u_sart[k+1] = iradon_sart(f_delta,theta=theta,image=u_sart[k])
fig, ax = plt.subplots(1, 2, figsize=(8, 4.5), sharey=True)
ax[0].imshow(u_fbp, cmap=plt.cm.Greys_r, extent=(0, 1, 0, 1))
ax[0].set_title('FBP')
ax[1].imshow(u_sart[-1], cmap=plt.cm.Greys_r, extent=(0, 1, 0, 1))
ax[1].set_title('SART')
fig.tight_layout()
plt.show()
Up to now, both the function ModernCT
. Then, the whole three dimensional attenuation map can be reconstructed slice-by-slice, which also explains the word tomography, which is derived from the ancient Greek tomos, which means slice or section.
+++
+++
Given the Radon transform
Show that (for sufficiently regular functions)
Given a discretisation of the Radon transform
- Show that a stationary point of the SART iteration solves
with
One implementation of the Radon transform and the FBP can be found in the scikit-image
toolbox. An example of how to load a simple image, compute its Radon transform to simulate sinogram data and how to run a FBP to obtain a reconstruction from this data is shown below.
# import libraries
import numpy as np
import matplotlib.pyplot as plt
from skimage import data
from skimage.transform import radon,iradon,resize
from skimage.util import random_noise
# settings
n = 128
theta = np.linspace(0., 180.,n, endpoint=False)
sigma = 1e-1
# ground truth image
image = resize(data.shepp_logan_phantom(),(n,n))
# transform and add noise
sinogram = radon(image, theta=theta, circle = False)
sinogram_noisy = random_noise(sinogram,mode='gaussian',var=sigma,clip=False)
# reconstruction, type help(iradon) for more options regarding the filter
image_reconstructed = iradon(sinogram_noisy, theta=theta, filter='ramp', circle = False)
# plot
fig, ax = plt.subplots(1,2)
ax[0].imshow(image)
ax[0].set_title('Ground truth image')
ax[1].imshow(image_reconstructed)
ax[1].set_title('Reconstructed image')
plt.show()
-
Now we examine what happens if we add noise to the sinogram data. Let
$f = K u$ be the clean sinogram data arranged as a$m \times 1$ vector and generate noisy data by setting$f^\delta = f + \sigma \varepsilon$ , where$\varepsilon$ is a$m\times 1$ vector of standard normal distributed random variables and$\sigma$ determines the noise level. Examine how the FBP reacts to different noise levels and how changing the frequency filter affects the results. -
In many applications, the range of available projection angles is restricted
$\theta \in [\theta_{min}, \theta_{max}]$ ,$\theta_{min} > 0$ ,$\theta_{max} < \pi$ or the angular resolution is very coarse. Examine the effects of these restrictions on FBP reconstructions. In limited angle tomography, which parts of the image are lost?
Define the Radon transform
with
- Introduce
$$|f|{\mathcal{F}}^2 = \int_0^\pi \int{-1}^1 (1-t)^{-1/2} |f(\theta,t)|^2\mathrm{d}t,$$
and
$$|u|{\mathcal{U}}^2 = \int{\Omega} |u(x)|^2\mathrm{d}x.$$
Show that $|Ru|{\mathcal{F}} \leq 2\sqrt{\pi}|u|{\mathcal{U}}$.
-
Derive the adjoint of
$R$ (with respect to the weighted inner product defined above). -
Show that the operator
$RR^*$ has eigenfunctions
with eigenvalues
Here,
- Now compute the eigenfunctions of
$R^*R$ .
Discretise the Radon transform by approximating
with