Reconstructing SIMIND Data#
[1]:
import numpy as np
import os
import pytomography
from pytomography.io import get_SPECT_recon_algorithm_simind, simind_MEW_to_data, simind_CT_to_data
from pytomography.metadata import PSFMeta
from pytomography.priors import RelativeDifferencePrior
from pytomography.projections import SystemMatrix
from pytomography.metadata import ObjectMeta, ImageMeta, PSFMeta
from pytomography.transforms import SPECTAttenuationTransform, SPECTPSFTransform
from pytomography.algorithms import OSEMOSL, OSEMBSR
import matplotlib.pyplot as plt
import torch
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
Set the default device for all reconstruction for pytomography:
[2]:
pytomography.device = device
Set the folder location for downloaded files (you will need to modify this to be the directory where you saved the files)
[3]:
path = '/home/gpuvmadm/PyTomography/test_files/quantitative'
The Quick Way#
The typical file format for SIMIND data is as follows:
Raw SPECT data: a header file (containing metadata) and a corresponding binary file containing data (3D sinogram) of photon count data
Scattered SPECT data: sinogram data collected at energy windows above/below the primary energy window, used for approximating the scatter in the primary energy window
CT data: a header file (containing metadata) and a corresponding binary file containing data (3D attenuation map)
We can create our recontruction algorithm as follows, by passing in the header files, along with the PSF parameters (must be specified using collimator_slope
and collimator_intercept
) and the prior function we wish to use. If any of scatter_headers
, CT_header
, psf_meta
, or prior
is not used as an argument, modeling of that phenomenon isn’t included in the reconstruction. Note that OSEMBSR
and OSEMOSL
correspond to OSEM
if no prior is used.
[4]:
reconstruction_algorithm = get_SPECT_recon_algorithm_simind(
projections_header = os.path.join(path, 'body1t2ew6_tot_w2.hdr'),
scatter_headers = [
os.path.join(path, 'body1t2ew6_tot_w1.hdr'),
os.path.join(path, 'body1t2ew6_tot_w3.hdr')],
CT_header = os.path.join(path, 'body1.hct'),
psf_meta=PSFMeta(collimator_slope=0.03013, collimator_intercept=0.1967),
prior = RelativeDifferencePrior(beta=1, gamma=5),
recon_algorithm_class=OSEMBSR)
From there, our object can be reconstructed
[5]:
reconstructed_object = reconstruction_algorithm(n_iters=10, n_subsets=8)
and we can view the maximum intensity projections, for example
[6]:
plt.subplots(1,2,figsize=(12,10))
plt.subplot(121)
plt.pcolormesh(reconstructed_object[0].cpu().numpy().max(axis=0).T, cmap='nipy_spectral')
plt.colorbar()
plt.axis('off')
plt.subplot(122)
plt.pcolormesh(reconstructed_object[0].cpu().numpy().max(axis=1).T, cmap='nipy_spectral')
plt.colorbar()
plt.axis('off')
[6]:
(0.0, 128.0, 0.0, 384.0)

The Methodical Way#
The get_SPECT_recon_algorithm_simind
is function that allows convenient formation of a reconstruction algorithm for simind
data. We can also build the reconstruction algorithm from scratch, explicitly defining the components of the system matrix.
[7]:
object_meta, image_meta, projections, projections_scatter = simind_MEW_to_data([
os.path.join(path, 'body1t2ew6_tot_w2.hdr'),
os.path.join(path, 'body1t2ew6_tot_w1.hdr'),
os.path.join(path, 'body1t2ew6_tot_w3.hdr')
])
The CT data is loaded in a similar fashion
[8]:
CT = simind_CT_to_data(os.path.join(path, 'body1.hct'))
As usual, it is good practice to ensure the CT and projections are aligned
[9]:
plt.subplots(1, 4, figsize=(14,4))
plt.subplot(141)
plt.pcolormesh(CT.sum(axis=1).T, cmap='Greys_r')
plt.title('CT; Coronal View')
plt.subplot(142)
plt.pcolormesh(projections[0][0].T, cmap='nipy_spectral')
plt.title(r'Spect; $\beta=0^{\circ}$')
plt.subplot(143)
plt.pcolormesh(CT.sum(axis=0).T, cmap='Greys_r')
plt.title('CT; Sagittal View')
plt.subplot(144)
plt.pcolormesh(projections[0][30].T, cmap='nipy_spectral')
plt.title(r'Spect; $\beta=90^{\circ}$')
plt.show()

Now we need to define all the required mappings to model \(g = Hf\)
CT Correction
This transform corresponds to a mapping \(A_i(\theta)\) as specified in the user manual and takes in an object/corresponding projection angle and returns a modified object
[10]:
ct_net = SPECTAttenuationTransform(CT.unsqueeze(dim=0))
PSF Correction
This transform corresponds to a mapping \(A_i(\theta)\) as specified in the users manual. The
collimator_slope
andcollimator_intercept
are parameters that need to be explicitly known for a given scanner. They can be obtained, for example, by fitting Gaussian profiles to a point source at varying distances from the detector surface, and determining \(\sigma(z)=(\text{collimator slope})\cdot z + \text{collimator intercept}\)
[11]:
psf_meta = PSFMeta(collimator_slope = 0.03013, collimator_intercept = 0.1967)
psf_net = SPECTPSFTransform(psf_meta)
Now we can build our system matrix \(H\):
The forward method
system_matrix.forward(f)
corresponds to \(Hf\) where \(H = \sum_{\theta} P(\theta)A_1(\theta)A_2(\theta) \otimes \hat{\theta}\)The backward method
system_matrix.backward(g)
corresponds to \(H^T g\)
[12]:
system_matrix = SystemMatrix(
obj2obj_transforms = [ct_net,psf_net],
im2im_transforms = [],
object_meta = object_meta,
image_meta = image_meta)
We can also define the Baysian Prior:
[13]:
prior = RelativeDifferencePrior(beta=1, gamma=5)
We’ll use these FP/BP nets to create the reconstruction algorithm:
[15]:
reconstruction_algorithm = OSEMBSR(
image = projections,
system_matrix = system_matrix,
scatter = projections_scatter,
prior = prior)
Get the reconstructed object
[16]:
reconstructed_object = reconstruction_algorithm (n_iters=10, n_subsets=8)
Plot the reconstructed object next to the CT. * The reconstructed object has units of counts, and would need to be adjusted by a proportionality factor if one wants to obtain units of MBq
[17]:
plt.subplots(1,2,figsize=(8,6))
plt.subplot(121)
plt.pcolormesh(reconstructed_object[0].cpu()[:,70].T, cmap='nipy_spectral')
plt.colorbar()
plt.axis('off')
plt.title('Reconstructed Object')
plt.subplot(122)
plt.pcolormesh(CT[:,70].T, cmap='Greys_r')
plt.colorbar()
plt.axis('off')
plt.title('$\mu$ (CT scan)')
plt.show()
