import nibabel as nib
import numpy as np
import plotly.graph_objects as go
from scipy.ndimage import gaussian_filter
from plotly.subplots import make_subplots
def load_and_preprocess_nifti(nifti_path):
print(f"Loading NIFTI file from {nifti_path}")
= nib.load(nifti_path)
nifti_img = nifti_img.get_fdata()
nifti_data print("Normalizing data")
= (nifti_data - np.min(nifti_data)) / (np.max(nifti_data) - np.min(nifti_data))
normalized_data print("Data loaded and preprocessed")
return normalized_data
def save_as_nrrd(data, file_path):
"""Save the numpy array as NRRD file if needed."""
nrrd.write(file_path, data)
def downsample_3d(data, factor):
"""Downsample 3D data by a factor"""
return data[::factor, ::factor, ::factor]
def compute_hog3d(ct_data, block_size=8, cell_size=4, stride=4, num_bins=12):
print("Starting HOG3D computation")
# Apply Gaussian smoothing to the data
# Sigma value is set to 1, increasing it will result in more smoothing
= gaussian_filter(ct_data, sigma=1)
smoothed_data
# Compute the gradient of the smoothed data
= np.gradient(smoothed_data)
gx, gy, gz
# Compute the magnitude, phi and theta values
= np.sqrt(gx**2 + gy**2 + gz**2)
magnitude
# Phi is the angle between the x-axis and the projection of the gradient vector onto the xy-plane
= np.arctan2(np.sqrt(gx**2 + gy**2), gz)
phi
# Theta is the angle between the z-axis and the gradient vector
= np.arctan2(gy, gx)
theta
# Initialize the lists to store the HOG3D features, positions and orientations
= []
hog3d_features = []
hog3d_positions = []
hog3d_orientations
# Calculate total blocks and initialize the current block counter
= ((ct_data.shape[0] - block_size) // stride + 1) * \
total_blocks 1] - block_size) // stride + 1) * \
((ct_data.shape[2] - block_size) // stride + 1)
((ct_data.shape[= 0
current_block
for x in range(0, ct_data.shape[0] - block_size + 1, stride): # Loop over the x-axis
for y in range(0, ct_data.shape[1] - block_size + 1, stride): # Loop over the y-axis
for z in range(0, ct_data.shape[2] - block_size + 1, stride): # Loop over the z-axis
= np.zeros((2 * num_bins, (block_size // cell_size) ** 3)) # Initialize the block histogram
block_hist
= 0
cell_index for i in range(0, block_size, cell_size): # Loop over the x-axis of the block
for j in range(0, block_size, cell_size): # Loop over the y-axis of the block
for k in range(0, block_size, cell_size): # Loop over the z-axis of the block
# Extract the cell from the phi, theta and magnitude arrays
= phi[x+i:x+i+cell_size, y+j:y+j+cell_size, z+k:z+k+cell_size]
cell_phi = theta[x+i:x+i+cell_size, y+j:y+j+cell_size, z+k:z+k+cell_size]
cell_theta = magnitude[x+i:x+i+cell_size, y+j:y+j+cell_size, z+k:z+k+cell_size]
cell_magnitude
# 1D histogram for phi angles
= np.histogram(
hist_phi, _
cell_phi.ravel(),=num_bins,
binsrange=(-np.pi, np.pi),
=cell_magnitude.ravel()
weights
)
# 1D histogram for theta angles
= np.histogram(
hist_theta, _
cell_theta.ravel(),=num_bins,
binsrange=(-np.pi, np.pi),
=cell_magnitude.ravel()
weights
)
# Concatenate the histograms for phi and theta
= np.concatenate([hist_phi, hist_theta])
block_hist[:, cell_index] += 1
cell_index
= block_hist.ravel() # Reshape the block_hist array
block_hist /= np.linalg.norm(block_hist) + 1e-5 # Normalize the histogram
block_hist
# Append the block histogram to the HOG3D features list
hog3d_features.append(block_hist) + block_size // 2, y + block_size // 2, z + block_size // 2]) # Append the block position to the HOG3D positions list
hog3d_positions.append([x # Append the block orientation to the HOG3D orientations list
hog3d_orientations.append([np.mean(cell_phi), np.mean(cell_theta)])
# statements to keep track of the progress
+= 1
current_block if current_block % 10000 == 0 or current_block == total_blocks:
print(f"Processed {current_block}/{total_blocks} blocks")
print("HOG3D computation completed")
return np.array(hog3d_features), np.array(hog3d_positions), np.array(hog3d_orientations)
def visualize_hog3d(ct_data, hog3d_features, hog3d_positions, hog3d_orientations, threshold=0.75, save_path=None):
print("Starting visualization")
= np.linalg.norm(hog3d_features, axis=1)
feature_magnitudes
# Use a more discriminative threshold
= np.percentile(feature_magnitudes, threshold * 100)
magnitude_threshold = feature_magnitudes > magnitude_threshold
mask
= hog3d_positions[mask]
filtered_positions = feature_magnitudes[mask]
filtered_magnitudes = hog3d_orientations[mask]
filtered_orientations
# Apply non-linear transformation to magnitudes
= np.power(filtered_magnitudes, 0.3) # Adjust the power for desired spread
filtered_magnitudes
# Find the bounding box of important features
= np.min(filtered_positions, axis=0)
min_x, min_y, min_z = np.max(filtered_positions, axis=0)
max_x, max_y, max_z
# Add some padding to the bounding box
= 10
padding = max(0, min_x - padding), max(0, min_y - padding), max(0, min_z - padding)
min_x, min_y, min_z = min(ct_data.shape[0], max_x + padding), min(ct_data.shape[1], max_y + padding), min(ct_data.shape[2], max_z + padding)
max_x, max_y, max_z
# Create subplots
= make_subplots(
fig =1, cols=3,
rows=[[{'type': 'scene'}, {'type': 'scene'}, {'type': 'scene'}]],
specs=("Feature Positions", "Orientations", "Combined")
subplot_titles
)
# 1. Feature position points
fig.add_trace(
go.Scatter3d(=filtered_positions[:, 0],
x=filtered_positions[:, 1],
y=filtered_positions[:, 2],
z='markers',
mode=dict(
marker=2,
size=filtered_magnitudes,
color='Viridis',
colorscale=1,
opacity=dict(title="Feature Magnitude", x=0.3)
colorbar
),='HOG3D Features'
name
),=1, col=1
row
)
# 2. Orientation cones
= np.sin(filtered_orientations[:, 0]) * np.cos(filtered_orientations[:, 1])
u = np.sin(filtered_orientations[:, 0]) * np.sin(filtered_orientations[:, 1])
v = np.cos(filtered_orientations[:, 0])
w
= 5
scale_factor *= scale_factor
u *= scale_factor
v *= scale_factor
w
fig.add_trace(
go.Cone(=filtered_positions[:, 0],
x=filtered_positions[:, 1],
y=filtered_positions[:, 2],
z=u,
u=v,
v=w,
w='Plotly3',
colorscale="absolute",
sizemode=0.2,
opacity=3,
sizeref
),=1, col=2
row
)
# 3. Combined visualization
fig.add_trace(
go.Scatter3d(=filtered_positions[:, 0],
x=filtered_positions[:, 1],
y=filtered_positions[:, 2],
z='markers',
mode=dict(
marker=1,
size=filtered_magnitudes,
color='Viridis',
colorscale=1,
opacity
),='HOG3D Features'
name
),=1, col=3
row
)
fig.add_trace(
go.Cone(=filtered_positions[:, 0],
x=filtered_positions[:, 1],
y=filtered_positions[:, 2],
z=u,
u=v,
v=w,
w='Plotly3',
colorscale="absolute",
sizemode=0.4,
opacity=5,
sizeref
),=1, col=3
row
)
# Update layout for all subplots
for i in range(1, 4):
fig.update_scenes(=dict(title="X", range=[min_x, max_x]),
xaxis=dict(title="Y", range=[min_y, max_y]),
yaxis=dict(title="Z", range=[min_z, max_z]),
zaxis='data',
aspectmode=1, col=i
row
)
fig.update_layout(=5000,
width=1000,
height="HOG3D Feature and Orientation Visualization (Cropped)"
title
)
fig.show()
if save_path:
fig.write_html(save_path)print(f"Visualization saved to {save_path}")
print("Visualization completed")
!wget https://raw.githubusercontent.com/Pranav-Karra-3301/CVD_HOG3D/f6a7011b2edcf5a0bbdd2094a75a0b793945dd68/Img_001.nii.gz
--2024-08-01 05:38:18-- https://raw.githubusercontent.com/Pranav-Karra-3301/CVD_HOG3D/f6a7011b2edcf5a0bbdd2094a75a0b793945dd68/Img_001.nii.gz
Resolving raw.githubusercontent.com (raw.githubusercontent.com)... 185.199.108.133, 185.199.109.133, 185.199.110.133, ...
Connecting to raw.githubusercontent.com (raw.githubusercontent.com)|185.199.108.133|:443... connected.
HTTP request sent, awaiting response... 200 OK
Length: 2590448 (2.5M) [application/octet-stream]
Saving to: ‘Img_001.nii.gz’
Img_001.nii.gz 100%[===================>] 2.47M --.-KB/s in 0.08s
2024-08-01 05:38:18 (32.8 MB/s) - ‘Img_001.nii.gz’ saved [2590448/2590448]
= "Img_001.nii.gz" # Replace with the correct file path
nifti_file = load_and_preprocess_nifti(nifti_file)
ct_data print("Processed data shape:", ct_data.shape)
= 4
downsample_factor = downsample_3d(ct_data, downsample_factor)
downsampled_ct_data print("Downsampled data shape:", downsampled_ct_data.shape)
# Calculate expected shapes
"""
Block Size: Increase to capture more information in each block
Cell Size: Increase to capture more information in each cell
Stride: Increase to reduce the number of blocks
Num Bins: Increase to capture more information in each histogram.
"""
= 4
block_size = 2
cell_size = 2
stride = 18
num_bins
= (downsampled_ct_data.shape[0] - block_size) // stride + 1
num_blocks_x = (downsampled_ct_data.shape[1] - block_size) // stride + 1
num_blocks_y = (downsampled_ct_data.shape[2] - block_size) // stride + 1
num_blocks_z = num_blocks_x * num_blocks_y * num_blocks_z
total_blocks
= (2 * num_bins) * ((block_size // cell_size) ** 3)
features_per_block
# Compute HOG3D
= compute_hog3d(downsampled_ct_data, block_size=block_size, cell_size=cell_size, stride=stride, num_bins=num_bins)
hog3d_features, hog3d_positions, hog3d_orientations
# Print expected and computed shapes alternately
print("")
print("Downsample Factor:", downsample_factor)
print("Total Blocks: ",(total_blocks))
print(f"Block Size: {block_size}, Cell Size: {cell_size}, Stride: {stride}, Num Bins: {num_bins}")
print("")
print("Expected HOG3D features shape:", (total_blocks, features_per_block))
print("Computed HOG3D features shape:", hog3d_features.shape)
print("")
print("Expected HOG3D positions shape:", (total_blocks, 3))
print("Computed HOG3D positions shape:", hog3d_positions.shape)
print("")
print("Expected HOG3D orientations shape:", (total_blocks, 2))
print("Computed HOG3D orientations shape:", hog3d_orientations.shape)
Loading NIFTI file from Img_001.nii.gz
Normalizing data
Data loaded and preprocessed
Processed data shape: (512, 512, 275)
Downsampled data shape: (128, 128, 69)
Starting HOG3D computation
Processed 10000/130977 blocks
Processed 20000/130977 blocks
Processed 30000/130977 blocks
Processed 40000/130977 blocks
Processed 50000/130977 blocks
Processed 60000/130977 blocks
Processed 70000/130977 blocks
Processed 80000/130977 blocks
Processed 90000/130977 blocks
Processed 100000/130977 blocks
Processed 110000/130977 blocks
Processed 120000/130977 blocks
Processed 130000/130977 blocks
Processed 130977/130977 blocks
HOG3D computation completed
Downsample Factor: 4
Total Blocks: 130977
Block Size: 4, Cell Size: 2, Stride: 2, Num Bins: 18
Expected HOG3D features shape: (130977, 288)
Computed HOG3D features shape: (130977, 288)
Expected HOG3D positions shape: (130977, 3)
Computed HOG3D positions shape: (130977, 3)
Expected HOG3D orientations shape: (130977, 2)
Computed HOG3D orientations shape: (130977, 2)
# Visualize HOG3D features
= "hog3d_visualization.html" # Set to None if you don't want to save
save_path =0.2, save_path=save_path) visualize_hog3d(ct_data, hog3d_features, hog3d_positions, hog3d_orientations, threshold
Starting visualization
Visualization saved to hog3d_visualization.html
Visualization completed