Skip to content

Commit 2cda26b

Browse files
committed
added documentation to mri scan
1 parent d986169 commit 2cda26b

1 file changed

Lines changed: 157 additions & 81 deletions

File tree

examples/mri_testscan.py

Lines changed: 157 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -1,43 +1,106 @@
1+
"""
2+
MRI affine transformation demo using Heat.
3+
4+
This example loads a sample MRI volume, extracts a middle slice,
5+
applies various affine transformations (rotation, scaling,
6+
translation, shear), and visualizes the results.
7+
8+
MRI sample data © 2018 Adam Wolf, used under the MIT License.
9+
See heat/datasets/mri_sample_LICENSE.txt for details.
10+
"""
11+
112
import numpy as np
213
import matplotlib.pyplot as plt
314
import nibabel as nib
415
import heat as ht
516
from heat.ndimage.affine import affine_transform
6-
"""
7-
MRI sample data © 2018 Adam Wolf, used under MIT License.
8-
See heat/datasets/mri_sample_LICENSE.txt for details.
9-
"""
1017

1118

1219
# ============================================================
1320
# Load MRI volume
1421
# ============================================================
22+
1523
def load_mri(path):
24+
"""
25+
Load an MRI volume from a NIfTI file.
26+
27+
Parameters
28+
----------
29+
path : str
30+
Path to the .nii or .nii.gz MRI file.
31+
32+
Returns
33+
-------
34+
numpy.ndarray
35+
MRI volume as a float32 array of shape (D, H, W),
36+
normalized to the range [0, 255].
37+
"""
1638
nii = nib.load(path)
17-
vol = nii.get_fdata().astype(np.float32) # (D,H,W)
39+
vol = nii.get_fdata().astype(np.float32)
1840
vol = vol / np.max(vol) * 255
1941
return vol
2042

2143

2244
# ============================================================
2345
# Slice helper
2446
# ============================================================
47+
2548
def middle_slice(volume):
49+
"""
50+
Extract the middle axial slice from a 3D volume.
51+
52+
Parameters
53+
----------
54+
volume : numpy.ndarray
55+
3D array of shape (D, H, W).
56+
57+
Returns
58+
-------
59+
numpy.ndarray
60+
2D slice of shape (H, W).
61+
"""
2662
mid = volume.shape[0] // 2
2763
return volume[mid, :, :]
2864

2965

3066
# ============================================================
3167
# Convert numpy MRI slice → Heat array
3268
# ============================================================
69+
3370
def to_heat_slice(slice2d):
71+
"""
72+
Convert a 2D NumPy array into a Heat array.
73+
74+
Parameters
75+
----------
76+
slice2d : numpy.ndarray
77+
2D image slice.
78+
79+
Returns
80+
-------
81+
ht.DNDarray
82+
Heat array representation of the slice.
83+
"""
3484
return ht.array(slice2d, dtype=ht.float32)
3585

3686

3787
# ============================================================
3888
# Plot grid
3989
# ============================================================
90+
4091
def show_results(titles, images, save_path=None):
92+
"""
93+
Display a grid of images with titles.
94+
95+
Parameters
96+
----------
97+
titles : list of str
98+
Titles for each subplot.
99+
images : list of numpy.ndarray
100+
Images to display.
101+
save_path : str, optional
102+
If given, the figure is saved to this path.
103+
"""
41104
n = len(images)
42105
cols = 3
43106
rows = (n + cols - 1) // cols
@@ -62,107 +125,121 @@ def show_results(titles, images, save_path=None):
62125
# ============================================================
63126
# MAIN
64127
# ============================================================
128+
65129
def main():
130+
"""
131+
Run the MRI affine transformation demo.
132+
133+
This function:
134+
1. Loads a sample MRI volume
135+
2. Extracts the middle slice
136+
3. Applies multiple affine transformations using Heat
137+
4. Visualizes and saves the results
138+
"""
66139
# -------- LOAD MRI --------
67-
vol = load_mri("/Users/marka.k/1900_Image_transformations/heat/heat/datasets/flair.nii.gz")
140+
vol = load_mri(
141+
"/Users/marka.k/1900_Image_transformations/heat/heat/datasets/flair.nii.gz"
142+
)
68143
print("Loaded MRI:", vol.shape)
69144

70-
orig = middle_slice(vol) # (H,W)
145+
orig = middle_slice(vol)
71146
x = to_heat_slice(orig)
72147

73148
H, W = orig.shape
74149

75150
# ========================================================
76-
# Define transforms (all center-aware)
151+
# Define transforms (center-aware)
77152
# ========================================================
78153

79-
cx, cy = W/2, H/2
80-
81-
# Helper to shift center for rotation/scale
82154
def recenter(M):
83-
"""
84-
Input: 2x3 affine matrix.
85-
Output: 2x3 affine matrix recentered around the image center.
86-
"""
87-
88-
cx, cy = W/2, H/2
89-
90-
# Convert 2×3 → 3×3 homogeneous
91-
M3 = np.array([
92-
[M[0,0], M[0,1], M[0,2]],
93-
[M[1,0], M[1,1], M[1,2]],
94-
[0, 0, 1 ]
95-
], dtype=np.float32)
96-
97-
# Center shift matrices
98-
T1 = np.array([
99-
[1, 0, -cx],
100-
[0, 1, -cy],
101-
[0, 0, 1 ]
102-
], np.float32)
103-
104-
T2 = np.array([
105-
[1, 0, cx],
106-
[0, 1, cy],
107-
[0, 0, 1 ]
108-
], np.float32)
109-
110-
# Recenter: T2 * M * T1
111-
M_centered = T2 @ M3 @ T1
112-
113-
# Return as 2×3
114-
return np.array([
115-
[M_centered[0,0], M_centered[0,1], M_centered[0,2]],
116-
[M_centered[1,0], M_centered[1,1], M_centered[1,2]],
117-
], dtype=np.float32)
155+
"""
156+
Recenter a 2D affine matrix around the image center.
157+
158+
Parameters
159+
----------
160+
M : numpy.ndarray
161+
Affine matrix of shape (2, 3).
162+
163+
Returns
164+
-------
165+
numpy.ndarray
166+
Recentered affine matrix of shape (2, 3).
167+
"""
168+
cx, cy = W / 2, H / 2
169+
170+
M3 = np.array(
171+
[
172+
[M[0, 0], M[0, 1], M[0, 2]],
173+
[M[1, 0], M[1, 1], M[1, 2]],
174+
[0, 0, 1],
175+
],
176+
dtype=np.float32,
177+
)
178+
179+
T1 = np.array(
180+
[[1, 0, -cx], [0, 1, -cy], [0, 0, 1]],
181+
dtype=np.float32,
182+
)
183+
184+
T2 = np.array(
185+
[[1, 0, cx], [0, 1, cy], [0, 0, 1]],
186+
dtype=np.float32,
187+
)
188+
189+
M_centered = T2 @ M3 @ T1
190+
191+
return np.array(
192+
[
193+
[M_centered[0, 0], M_centered[0, 1], M_centered[0, 2]],
194+
[M_centered[1, 0], M_centered[1, 1], M_centered[1, 2]],
195+
],
196+
dtype=np.float32,
197+
)
118198

119199
# ROTATION
120200
angle = np.radians(20)
121-
M_rot = np.array([
122-
[np.cos(angle), -np.sin(angle), 0],
123-
[np.sin(angle), np.cos(angle), 0]
124-
], np.float32)
125-
M_rot = recenter(M_rot)
201+
M_rot = recenter(
202+
np.array(
203+
[[np.cos(angle), -np.sin(angle), 0],
204+
[np.sin(angle), np.cos(angle), 0]],
205+
dtype=np.float32,
206+
)
207+
)
126208

127209
# SCALE
128210
s = 1.2
129-
M_scale = np.array([
130-
[s, 0, 0],
131-
[0, s, 0]
132-
], np.float32)
133-
M_scale = recenter(M_scale)
134-
135-
# TRANSLATE
136-
M_trans = np.array([
137-
[1, 0, 20],
138-
[0, 1, -20]
139-
], np.float32)
211+
M_scale = recenter(
212+
np.array([[s, 0, 0], [0, s, 0]], dtype=np.float32)
213+
)
214+
215+
# TRANSLATION
216+
M_trans = np.array([[1, 0, 20], [0, 1, -20]], dtype=np.float32)
140217

141218
# SHEAR
142219
sh = 0.3
143-
M_shear = np.array([
144-
[1, sh, 0],
145-
[0, 1, 0]
146-
], np.float32)
147-
M_shear = recenter(M_shear)
220+
M_shear = recenter(
221+
np.array([[1, sh, 0], [0, 1, 0]], dtype=np.float32)
222+
)
148223

149-
# 3D ROTATION ABOUT Z AXIS APPLIED TO 2D SLICE (equivalent)
224+
# ROTATION (Z-axis equivalent)
150225
angle = np.radians(35)
151-
M_rotZ = np.array([
152-
[np.cos(angle), -np.sin(angle), 0],
153-
[np.sin(angle), np.cos(angle), 0]
154-
], np.float32)
155-
M_rotZ = recenter(M_rotZ)
226+
M_rotZ = recenter(
227+
np.array(
228+
[[np.cos(angle), -np.sin(angle), 0],
229+
[np.sin(angle), np.cos(angle), 0]],
230+
dtype=np.float32,
231+
)
232+
)
156233

157234
# ========================================================
158235
# Run transformations
159236
# ========================================================
160237

161-
out_rot = affine_transform(x, M_rot, order=1).numpy()
162-
out_scale = affine_transform(x, M_scale, order=1).numpy()
163-
out_trans = affine_transform(x, M_trans, order=1).numpy()
164-
out_shear = affine_transform(x, M_shear, order=1).numpy()
165-
out_rotZ = affine_transform(x, M_rotZ, order=1).numpy()
238+
out_rot = affine_transform(x, M_rot, order=1).numpy()
239+
out_scale = affine_transform(x, M_scale, order=1).numpy()
240+
out_trans = affine_transform(x, M_trans, order=1).numpy()
241+
out_shear = affine_transform(x, M_shear, order=1).numpy()
242+
out_rotZ = affine_transform(x, M_rotZ, order=1).numpy()
166243

167244
# ========================================================
168245
# Show + save
@@ -172,15 +249,14 @@ def recenter(M):
172249
"Original (middle slice)",
173250
"Rotate 20°",
174251
"Scale ×1.2",
175-
"Translate (+20, -20)",
252+
"Translate (+20, 20)",
176253
"Shear (0.3)",
177-
"3D Rotation (Z-axis 35°)",
254+
"Rotation (Z-axis 35°)",
178255
]
179256

180257
imgs = [orig, out_rot, out_scale, out_trans, out_shear, out_rotZ]
181258

182259
save_path = "mri_affine_demo.png"
183-
184260
show_results(titles, imgs, save_path=save_path)
185261

186262
print(f"\nSaved result figure → {save_path}")

0 commit comments

Comments
 (0)