Resampling in SimpleITK

Oblique slices

SimpleITK

SimpleITK has a fantastic python API for medical image processing, but it has a steep learning curve. In this post I will document how to resample images using the Resample filter via the complicated, but really useful task of extracting oblique slices in a 3D image. If you imagine a 3D image as a cube then an oblique slice is formed by sampling the image on a plane that has an arbitrary normal and does not align with the canonical axial, sagittal, coronal planes (see Fig. 1).

Figure 1 shows the typical setup in SimpleITK where the image field of view is denoted by the dotted cube. Let p=[px,py,pz] be the physical coordinates of a point around which the oblique slice is centered. Let x=[x1,x2,x3], y=[y1,y2,y3], and z=[z1,z2,z3] be the reference frame of the slice. z will also be the normal to the slice. For the sampled slice, let o be the SimpleITK origin of the slice (bottom left voxel).

I am assuming that we are given p and the unit vectors x, y, z.

Oblique slice in an image FOV.
Oblique slice in an image FOV.

Given a SimpleITK img, I typically do the following to resample it:

resample = sitk.ResampleImageFilter()
resample.SetOutputDirection(output_direction)
resample.SetOutputOrigin(output_origin)
resample.SetOutputSpacing(output_spacing)
resample.SetSize(output_size)
resample.SetTransform(sitk.Transform())
resample.SetDefaultPixelValue(0)
resample.SetInterpolator(sitk.sitkLinear)
resample.Execute(img)

We have to specify the direction, origin, spacing, and size (in voxels) for the final resampled slice. direction in a SimpleITK header is a (9, 1)-shaped array that expresses the rotation matrix (in a row-major form) that takes the physical reference frame (i=[1,0,0], y=[0,1,0], k=[0,0,1]) to the frame of the image.

Let the rotation matrix be R, then notice that Rk=[r13,r23,r33]–the third column of R. Applying the rotation R to the physical z axis should give us the new z axis which is z. Therefore, we have [r13,r23,r33]=[z1,z2,z3]. Similarly the first and second columns of R will be x and y respectively.

R = np.zeros((3,3))
R[:,0] = x
R[:,1] = y
R[:,2] = z

The SimpleITK direction part of the header of the output slice will therefore be,

direction = [r11,r12,r13,r21,r22,r23,r31,r32,r33]. Or in python:

output_direction = R.ravel()

or

output_direction_ = [x[0], y[0], z[0], x[1], y[1], z[1], x[2], y[2], z[2]]

To identify the physical coordinates of the origin o we have to do some vector arithmetic. From Figure 1, it is clear that

o=pdu,

where the unit vector u in the direction from o to p is given by u=x+y2

d is the distance between o and p. If w is the slice width, then d=w2. w is in millimetres, so if the output slice size is in voxels s, then w=sr, where r is the voxel spacing in millimetres. Here we assume that we have the same voxel spacing in x-y directions.

u = (x + y)/np.sqrt(2)
d = (s * voxel_spacing)/np.sqrt(2)
output_origin = p - d*u

That’s about it. We can specify the rest of the output image parameters and execute the resample filter to extract an oblique slice centered at a point p and oriented along the frame xyz, with the slice normal being z.

SimpleITK has a nice set of notebooks here and perhaps if I had done the homework exercises my learning curve would not have been so steep.

Amod Jog
Amod Jog

My research interests include medical image analysis, computer vision, and machine learning/artificial intelligence.

Related