Deep Learning for Iterative Spectral CT Reconstruction: Replacing Statistical Iterations with an Attention-Based U-Net
Generating a phantom and a sinogram
Generate a phantom where each pixel consists of part bone and part water described as (bone, water)
- Background values should be low for both to simulate air (0, 0.05)
- main body should be elliptical and mainly consist of water (0.20, 0.80)
- elliptical inserts should simulate different organs and/or body featurs (0.80, 0.80)
Generate a sinogram from the projections
- The function will return a 3D object containing the sinograms for the height of the detector. We want to select a slice to display
- print a 2D cross section of the phantom with a corresponding sinogram
#settings
objectSize = 32 # size of the object
nPixelsY = 64 # number of pixels in Y direction detector
nPixelsZ = 44 # number of pixels in Z direction detector
projections = 64 # number of projections
iterations = 40 # number of iterations
generate phantom¶
import random, numpy as np
def generate_phantom(n, h, num_features=1):
h = random.randint(1, 3) # Random height offset for the torso
# Initialize 3D bone and water matrices with zeros
bone_matrix = np.zeros((n, n, n), dtype=float)
water_matrix = np.zeros((n, n, n), dtype=float)
# fill background of matrices with 0.05 bone and water
bone_matrix.fill(0.05)
water_matrix.fill(0.05)
# set values for main ellipse (simulated torso)
intensity_bone = random.uniform(0.1, 0.6)
intensity_water = random.uniform(0.1, 0.6) # Random intensity for water
offset_i = np.random.randint(-h, h + 1)
offset_j = np.random.randint(-h, h + 1)
center_i = n // 2 + offset_i
center_j = n // 2 + offset_j
# Main body ellipse (simulated torso with random offset)
for i in range(h, n - h):
for j in range(2 * h, n - 2 * h):
if ((i - center_i) / ((n - 2 * h) / 2)) ** 2 + ((j - center_j) / ((n - 6 * h) / 2)) ** 2 <= 1:
bone_matrix[2*h:-2*h, i, j] = intensity_bone
water_matrix[2*h:-2*h, i, j] = intensity_water
# Adding smaller features (e.g., organs or bone structures)
for _ in range(num_features):
# Random place in the body - h is the offset from the edge, n is the size of the matrix
a = random.randint(h, (n - h) // 4) # Semi-major axis
b = random.randint(h, (n - h) // 4) # Semi-minor axis
center_x = random.randint(h + a, n - h - a)
center_y = random.randint(3 * h + b, n - 3 * h - b)
a_water = random.randint(h, (n - h) // 4) # Semi-major axis for water
b_water = random.randint(h, (n - h) // 4) # Semi-minor axis for water
# Randomly choose the intensity for bone and water, together they add to 1.
bone_intensity = 0.8
water_intensity = 0.8
for i in range(center_x - a_water, center_x + a_water):
for j in range(center_y - b_water, center_y + b_water):
if 0 <= i < n and 0 <= j < n:
if ((i - center_x) / a_water) ** 2 + ((j - center_y) / b_water) ** 2 <= 1:
bone_matrix[2*h:-2*h, i, j] = bone_intensity
center_x = random.randint(h + a, n - h - a)
center_y = random.randint(3 * h + b, n - 3 * h - b)
for i in range(center_x - a, center_x + a):
for j in range(center_y - b, center_y + b):
if 0 <= i < n and 0 <= j < n:
if ((i - center_x) / a) ** 2 + ((j - center_y) / b) ** 2 <= 1:
water_matrix[2*h:-2*h, i, j] = water_intensity
water_matrix *= 1 # Water density is 1
bone_matrix *= 1.92 # Apply bone density
return bone_matrix, water_matrix
bone, water = generate_phantom(objectSize, 2, num_features=1)
x = np.column_stack((bone.ravel(), water.ravel()))
# show phantom for bone
plt.imshow(bone[objectSize // 2, :, :], cmap='gray')
plt.show()

Running projection algorithm¶
from libs.simulatepreps import projectMatrix
#generate proection matrices
y, S, M, A = projectMatrix(x, objectSize, nPixelsY, nPixelsZ, 1, projections)
Generating sinogram¶
def getSinogram(y, energy_bin_index=0, z_index=32, nProj=100, detector_shape=(64, 44)):
nX, nZ = detector_shape
y_bin = y[energy_bin_index]
y_proj_zx = y_bin.reshape((nProj, nZ, nX)) # shape: (100, 64, 44)
sinogram = y_proj_zx[:, z_index, :] # shape: (100, 44)
return sinogram
sinogram = getSinogram(y, energy_bin_index=0, z_index=objectSize // 2, nProj=projections, detector_shape=(nPixelsY, nPixelsZ))
Make figure¶
import matplotlib.pyplot as plt
# plot bone density and phantom at objectSize //2
plt.figure(figsize=(12, 6))
plt.subplot(1, 2, 1)
plt.imshow(bone[objectSize // 2, :, :], cmap='gray')
plt.title('Bone Density at z = {}'.format(objectSize // 2))
plt.xlabel('X')
plt.ylabel('Y')
plt.subplot(1, 2, 2)
plt.imshow(sinogram, cmap='gray')
plt.title('Sinogram at z = {}'.format(objectSize // 2))
plt.ylabel('Angle')
plt.xlabel('Y')
plt.tight_layout()
plt.show()

Visualising a 2D convolution¶
# we want to put the phantom through a pytorch 2d convolution layer
import torch
bone_array = bone[objectSize // 2, :, :]
bone_tensor = torch.tensor(bone[objectSize // 2, :, :], dtype=torch.float32)
# create a 2D convolution layer with a kernel size of 3x3
conv_layer = torch.nn.Conv2d(in_channels=1, out_channels=1, kernel_size=3, padding=1)
# apply the convolution layer to the bone tensor
output_tensor = conv_layer(bone_tensor.unsqueeze(0).unsqueeze(0)) # Add batch and channel dimensions
output_image = output_tensor.squeeze(0).squeeze(0).detach().numpy() # Remove batch and channel dimensions
# plot 2 plots, one of the original bone image with a square showing one of the convolution kernels, and next to it the output image
plt.figure(figsize=(12, 6))
#make sure xlabel and ylabel are larger
plt.rcParams['axes.labelsize'] = 14
plt.subplot(1, 2, 1)
plt.imshow(bone_tensor.numpy(), cmap='gray')
# draw a square representing the convolution kernel
kernel_size = conv_layer.kernel_size[0]
plt.gca().add_patch(plt.Rectangle((10.5, 20.5), kernel_size, kernel_size, edgecolor='red', facecolor='none', lw=2))
# fill square with rectengles for each pixel
for i in range(kernel_size):
for j in range(kernel_size):
plt.gca().add_patch(plt.Rectangle((10.5 + i, 20.5 + j), 1, 1, edgecolor='red', facecolor='none', lw=0.5))
plt.xlabel('X')
plt.ylabel('Y')
plt.subplot(1, 2, 2)
plt.imshow(output_image, cmap='gray')
# draw little square representing the pixel on which the convolution was applied
plt.gca().add_patch(plt.Rectangle((10.5+2, 20.5+2), 1, 1, edgecolor='red', facecolor='none', lw=2))
plt.xlabel('X')
plt.ylabel('Y')
plt.show()

Deep Learning for Iterative Spectral CT Reconstruction: Replacing Statistical Iterations with an Attention-Based U-Net
Deep Learning for Iterative Spectral CT Reconstruction: Replacing Statistical Iterations with an Attention-Based U-NetDeep Learning for Iterative Spectral CT Reconstruction: Replacing Statistical Iterations with an Attention-Based U-Net
Reconstruction