KEM#
Preliminaries#
Import required packages
[1]:
from __future__ import annotations
import os
import matplotlib.pyplot as plt
import torch
from pytomography.io.SPECT import simind
from pytomography.projectors import SPECTSystemMatrix
from pytomography.transforms import SPECTAttenuationTransform, SPECTPSFTransform
from pytomography.algorithms import OSEM, KEM
from pytomography.transforms import KEMTransform
Modify the following path to the directory where you saved the tutorial data:
[2]:
path = '/disk1/pytomography_tutorial_data/simind_tutorial/'
The first cell of code is borrowed from the SPECT: Reconstructing SIMIND Data
tutorial in the multiple regions case. For a more comprehensive description about the code below, please see that tutorial.
[3]:
organs = ['bkg', 'liver', 'l_lung', 'r_lung', 'l_kidney', 'r_kidney','salivary', 'bladder']
activities = [2500, 450, 7, 7, 100, 100, 20, 90] # MBq
headerfiles = [os.path.join(path, 'multi_projections', organ, 'photopeak.h00') for organ in organs]
headerfiles_lower = [os.path.join(path, 'multi_projections', organ, 'lowerscatter.h00') for organ in organs]
headerfiles_upper = [os.path.join(path, 'multi_projections', organ, 'upperscatter.h00') for organ in organs]
object_meta, proj_meta = simind.get_metadata(headerfiles[0]) #assumes the same for all
photopeak = simind.combine_projection_data(headerfiles, activities)
scatter = simind.combine_scatter_data_TEW(headerfiles, headerfiles_lower, headerfiles_upper, activities)
dT = 5 #s
photopeak = torch.poisson(photopeak * dT)
scatter = torch.poisson(scatter * dT)
attenuation_map = simind.get_attenuation_map(os.path.join(path, 'multi_projections', 'mu208.hct'))
att_transform = SPECTAttenuationTransform(attenuation_map)
psf_meta = simind.get_psfmeta_from_header(headerfiles[0])
psf_transform = SPECTPSFTransform(psf_meta)
system_matrix = SPECTSystemMatrix(
obj2obj_transforms = [att_transform,psf_transform],
proj2proj_transforms = [],
object_meta = object_meta,
proj_meta = proj_meta,
n_parallel=2)
For reference, let’s reconstruct an image using OSEM:
[4]:
reconstruction_algorithm = OSEM(
projections = photopeak,
system_matrix = system_matrix,
scatter = scatter)
reconstructed_object = reconstruction_algorithm(n_iters=10, n_subsets=8)
The kernelized expectation maximum algorithm can be written as
where the reconstructed object estimate is given by \(\hat{f}^{n,m+1} = K \hat{\alpha}^{n,m+1}\). This is nearly identical to OSEM, but a matrix \(K\) is now involved. Thus we need to do two things in PyTomography
Obtain the matrix \(K\)
Using the matrix \(K\) in the
KEM
reconstruction algorithm.
The matrix \(K\) is a square matrix with components
where
\(v\) corresponds to a “support object” (such as a CT scan, MRI, PET). These are denoted as
support_objects
\(S_v\) corresponds to the set of all support objects used
\(k_v\) is the support kernel for support image \(v\). These are denoted as
support_kernels
.\(x_i\) is the position of voxel \(i\)
\(k_x\) is the kernel that weights based on positional differences. This is denoted as
distance_kernel
.… are additional parameters for each kernel. They are denoted as
distance_kernel_params
orsupport_kernel_params
/
In the KEMTransform
, one gives a list of all the support objects via the support_objects
argument. By default, the support kernel for each support object will be
In this default kernel, there is one additional required argument: \(\sigma\). The values of \(\sigma\) can be given using the support_kernels_params
argument. The argument is a list of lists, where each internal list corresponds to all the additional required parameters for each kernel function. In the example below, one support object is used, and the function takes in one additional argument, so specifying \(\sigma=0.005\) cm \(^{-1}\) requires giving
support_kernels_params=[[0.005]]
. Since there is a single distance kernel, any additional parameters to that kernel are given by a single list to the distance_kernel_params
argument.
Alternative kernels can be specified using the support_kernels
and distance_kernel
arguments of the function; in this tutorial, we make use of the default ones:
[5]:
kem_transform = KEMTransform(
support_objects=[attenuation_map],
support_kernels_params=[[0.005]],
distance_kernel_params=[0.5]
)
[0.5]
We initialize the reconstruction algorithm using the KEM
class, and give the corresponding kem_transform
[6]:
reconstruction_algorithm_KEM = KEM(
projections = photopeak,
system_matrix = system_matrix,
kem_transform = kem_transform,
scatter = scatter)
Then we can reconstruct:
[7]:
recon_kem = reconstruction_algorithm_KEM(n_iters=20, n_subsets=8)
And compare to OSEM:
[10]:
plt.subplots(1,2,figsize=(5,5))
plt.subplot(121)
plt.pcolormesh(reconstructed_object[0].cpu()[:,64].T, cmap='magma')
plt.colorbar()
plt.axis('off')
plt.title('OSEM')
plt.subplot(122)
plt.pcolormesh(recon_kem.cpu()[0][:,64].T, cmap='magma')
plt.colorbar()
plt.axis('off')
plt.title('KEM')
plt.show()
