Reconstructing DICOM data#

[1]:
import os
import pytomography
from pytomography.projections import SystemMatrix
from pytomography.metadata import ObjectMeta, ImageMeta, PSFMeta
from pytomography.transforms import SPECTAttenuationTransform, SPECTPSFTransform
from pytomography.algorithms import OSEMOSL
from pytomography.io import dicom_projections_to_data, dicom_CT_to_data, dicom_MEW_to_data, get_SPECT_recon_algorithm_dicom
import numpy as np
import matplotlib.pyplot as plt
import torch
import pydicom
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/julia_phantom/Cylinder_0103'

The Quick Way#

The typical file format for DICOM is as follows:

  • Raw SPECT data: a single .dcm (DICOM) file (3D sinogram)

  • CT data: a sequence of .dcm files, each representing a 2D slice

Below we obtain the corresponding files

[4]:
file_NM = os.path.join(path, 'HOLMIUM-166_ZYLINDER_03_01_2023.NM._.1000.1.2023.01.25.17.17.34.968750.4872276.IMA')
files_CT = [os.path.join(path, file) for file in os.listdir(path) if '2023.CT._.2' in file]

From there, we can create the reconstruction algorithm

[5]:
reconstruction_algorithm = get_SPECT_recon_algorithm_dicom(
    projections_file = file_NM,
    atteunation_files = files_CT,
    use_psf = True,
    scatter_type = 'DEW',
    recon_algorithm_class= OSEMOSL)
Based on primary window with range (74.93, 87.07)keV, using conversion between hounsfield to linear attenuation coefficient based on radionuclide with emission energy 77.5keV

And we can reconstruct using our desired reconstruction algorithm parameters

[6]:
reconstructed_object = reconstruction_algorithm(n_iters=4, n_subsets=8)

And we can view a slice of our reconstructed object:

[7]:
plt.figure(figsize=(4,3))
plt.pcolormesh(reconstructed_object[0].cpu()[:,:,70].T, cmap='nipy_spectral')
plt.colorbar()
plt.axis('off')
plt.title('Reconstructed Object')
plt.show()
../_images/notebooks_t_dicomdata_14_0.png

The Methodical Way#

The get_SPECT_recon_algorithm_dicom is function that allows convenient formation of a reconstruction algorithm for dicom data. We can also build the reconstruction algorithm from class, explicitly defining the components of the system matrix.

We can get the object/image metadata and corresponding projections / scatter projections using the dicom_MEW_to_data function (MEW means multiple energy window).

[8]:
object_meta, image_meta, projections, projections_scatter = dicom_MEW_to_data(file_NM)

We can load the atteunation data using the dicom_CT_to_data file, which will automatically align the attenuation object with the SPECT projections.

[9]:
CT = dicom_CT_to_data(files_CT, file_NM)
Based on primary window with range (74.93, 87.07)keV, using conversion between hounsfield to linear attenuation coefficient based on radionuclide with emission energy 77.5keV

As usual, it is always good practice to ensure the CT and projections are aligned

[10]:
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()
../_images/notebooks_t_dicomdata_21_0.png

Now we need to define all the required mappings to model \(g = Hf\)

CT Correction

  • This network 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

[11]:
ct_net = SPECTAttenuationTransform(CT.unsqueeze(dim=0).to(device), device=device)

PSF Correction

  • This network corresponds to a mapping \(A_i(\theta)\) as specified in the users manual. For PSF correction of DICOM SPECT files, we use the angular resolution to obtain an approximation for the collimator slope and intercept.

[12]:
ds = pydicom.read_file(file_NM)
angular_FWHM = ds[0x0055, 0x107f][0]
psf_meta = PSFMeta(collimator_slope = angular_FWHM/(2*np.sqrt(2*np.log(2))), collimator_intercept = 0.0)
psf_net = SPECTPSFTransform(psf_meta, device)

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\)

[14]:
system_matrix = SystemMatrix(
    obj2obj_transforms = [ct_net,psf_net],
    im2im_transforms = [],
    object_meta = object_meta,
    image_meta = image_meta)

We’ll use these FP/BP nets to create the reconstruction algorithm:

[15]:
reconstruction_algorithm = OSEMOSL(
    image = projections,
    system_matrix = system_matrix,
    scatter = projections_scatter)

Get the reconstructed object

[16]:
reconstructed_object = reconstruction_algorithm(n_iters=4, 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,3))
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()
../_images/notebooks_t_dicomdata_34_0.png