Main Content

Register 3-D Multimodal Medical Image Volumes Using Anatomical Landmarks

This example shows how to use control point registration to align CT and MRI scans of the head. Control point registration involves selecting the same landmarks, or control points, in two images and calculating the transformation that aligns the control points. The control points can be identifiable anatomical structures or artificial markers that are visible in both images. You can perform control point registration by using the fitgeotform3d function.

Control point registration algorithms operate on only the control point coordinates and not the image intensities, which generally makes control point registration simpler, faster, and more robust to variations in image intensity than intensity-based registration techniques. However, control point registration is highly sensitive to the accuracy of the control point selection, and is not suitable for image pairs that do not contain the same clearly visible landmarks.

This image shows a pair of CT and MRI volumes before and after control point registration.

CT and MRI volumes before and after control point registration

Load Images

The data used in this example is a modified version of the 3-D CT and MRI data sets from The Retrospective Image Registration Evaluation (RIRE) Dataset, provided by Dr. Michael Fitzpatrick. For more information, see the RIRE Project homepage. The modified data set contains one CT scan and one MRI scan from the same patient stored in the NRRD file format. The size of the entire data set is approximately 35 MB. Download the data set from the MathWorks® website, then unzip the folder.

zipFile = matlab.internal.examples.downloadSupportFile("medical", ...
    "MedicalRegistrationNRRDdataLPS.zip");
filepath = fileparts(zipFile);
unzip(zipFile,filepath)

Read the CT image into a medicalVolume object.

filenameCT = fullfile(filepath,"supportfilesNRRD","Patient007CT.nrrd");
movingCTVolume = medicalVolume(filenameCT);

Read the MRI image into a medicalVolume object.

filenameMRI = fullfile(filepath,"supportfilesNRRD","Patient007MRT1.nrrd");
fixedMRIVolume = medicalVolume(filenameMRI);

Define Control Point Pairs

Specify the control point coordinates. In this example, you use control points corresponding to anatomical landmarks. The coordinates represent slice numbers, in voxels, and have been picked by using orthosliceViewer.

leftEyeCT = [153 303 6];
leftEyeMRI = [64 154 2];

rightEyeCT = [154 209 5];
rightEyeMRI = [64 106 4];

straightSinusCT = [291 254 13];
straightSinusMRI = [134 129 11];

leftEarAttachCT = [264 368 7];
leftEarAttachMRI = [119 186 5];

rightEarAttachCT = [266 142 6];
rightEarAttachMRI = [120 71 4];

movingCTPoints = [leftEyeCT; rightEyeCT; straightSinusCT; 
    leftEarAttachCT; rightEarAttachCT];
fixedMRIPoints = [leftEyeMRI; rightEyeMRI; straightSinusMRI; 
    leftEarAttachMRI; rightEarAttachMRI];

This representative image shows how coordinates have been picked for the right-eye control point.

Annotated image of side-by-side orthosliceViewer windows showing selection of the right-eye control point in the CT (left) and MRI (Right) images

Optionally, if you want to select different control points, use this code to open orthosliceViewer figures for the moving and fixed volumes, enabling you to compare the volumes side by side.

orthosliceViewer(movingCTVolume.Voxels,DisplayRange=[-130 275]);
figure
orthosliceViewer(fixedMRIVolume.Voxels);

Convert Control Points to Patient Coordinate System

The orthosliceViewer uses a coordinate system that labels row indices as Y and column indices as X. To specify coordinates in the format [row, column, slice], which is required for the intrinsicToWorld object function, reorder the first two dimensions of the control point coordinates.

movingCTPoints = movingCTPoints(:,[2 1 3]);
fixedMRIPoints = fixedMRIPoints(:,[2 1 3]);

For each volume, convert the control point locations from intrinsic coordinates to patient coordinates by using the intrinsicToWorld function.

[movX,movY,movZ] = intrinsicToWorld(movingCTVolume.VolumeGeometry, ...
    movingCTPoints(:,1),movingCTPoints(:,2),movingCTPoints(:,3));
movingCTPointsPatient = [movX movY movZ];

[fixedX,fixedY,fixedZ] = intrinsicToWorld(fixedMRIVolume.VolumeGeometry, ...
    fixedMRIPoints(:,1),fixedMRIPoints(:,2),fixedMRIPoints(:,3));
fixedMRIPointsPatient = [fixedX fixedY fixedZ];

Display Unregistered Volumes

Create a 3-D viewer display of both volumes.

viewerUnregistered = viewer3d(BackgroundColor="black",BackgroundGradient="off");

Display the medicalVolume objects as 3-D isosurfaces using volshow. Display the CT volume in green and the MRI volume in magenta. The volshow function uses the spatial metadata of the medicalVolume objects to display the volumes in patient coordinates.

volshow(movingCTVolume, ...
    Parent=viewerUnregistered, ...
    RenderingStyle="Isosurface", ...
    Colormap="green");

volshow(fixedMRIVolume, ...
    Parent=viewerUnregistered, ...
    RenderingStyle="Isosurface", ...
    Colormap="magenta");

Display the control points as Point annotation objects.

for i = 1:size(fixedMRIPointsPatient,1)
    pmoving(i) = images.ui.graphics3d.roi.Point(Position=movingCTPointsPatient(i,:), ...
        Label=num2str(i), ...
        Color=[0.0 0.4 0.0]);
    pfixed(i) = images.ui.graphics3d.roi.Point(Position=fixedMRIPointsPatient(i,:), ...
        Label=num2str(i), ...
        Color=[0.5 0.2 0.5]);
end

viewerUnregistered.Annotations = [pmoving pfixed];
pause(0.5)
viewerUnregistered.CameraZoom = 2;

Figure contains an object of type images.ui.graphics3d.viewer3d.

pause(0.5)

Calculate Patient-to-Patient Transformation

Fit the transformation that maps movingCTPointsPatient to fixedMRIPointsPatient. Because the control points are in patient coordinates, tform maps from the patient coordinates of the CT volume to the patient coordinates of the MRI volume.

You can specify the type of transform to fit as translation, rigid, or affine. The transform type affects the minimum number of control points required, as well as the type of misalignment the registration can correct for. For example, a rigid transform requires at least three control point pairs and can correct for translation and rotation errors, while an affine transform requires at least four control point pairs and can additionally correct for scaling, reflection, and shear. Given that the CT and MRI volumes in this example originate from the same patient at the same time, we do not expect scaling or shear errors. Therefore, a rigid transformation is sufficient to correct for translations and rotations caused by patient positioning within the different scanners.

tform = fitgeotform3d(movingCTPointsPatient,fixedMRIPointsPatient,"rigid");

Calculate Composite Transformation

To align the CT volume with the MRI volume, calculate the composite transformation that

  1. Defines the initial position of the CT volume in patient coordinates.

  2. Applies the registration transformation, tform.

Obtain the initial position of the CT volume in patient coordinates by using the intrinsicToWorldMapping object function.

movingIntr2World = intrinsicToWorldMapping(movingCTVolume.VolumeGeometry);

Create the composite transformation by multiplying the transformation matrices. To apply movingIntr2World and then tform, list tform first and movingIntr2World second in the multiply operation.

A = tform.A*movingIntr2World.A;

Create an affine transformation object for the transformation matrix.

tformComposite = affinetform3d(A);

Update Spatial Referencing of Moving Volume

In this section, you create a medicalVolume that contains the CT volume image with updated spatial referencing. First, create a new medicalref3d object that defines a volume the same size as the CT data, in voxels, with spatial referencing defined by the composite transformation tformComposite.

volSizeCT = size(movingCTVolume.Voxels);
newref3d = medicalref3d(volSizeCT,tformComposite);

Next, match the PatientCoordinateSystem property of newref3d to that of the CT and MRI volumes.

newref3d.PatientCoordinateSystem = movingCTVolume.VolumeGeometry.PatientCoordinateSystem;

Finally, create a new medicalVolume object that contains the CT volume voxels and the new medicalref3d object. Note that the fixed and transformed moving volumes are aligned when you display them in patient coordinates, but their underlying voxel data still has different voxel spacing and a different number of voxels in each dimension.

transformedCTVolume = medicalVolume(movingCTVolume.Voxels,newref3d);

Resample Voxels of Moving Volume

To overlay 2-D slices from each volume, or use array indexing to analyze the same region in both volumes, you must resample the voxel data of the transformed CT volume so that it has the same number of rows, columns, and slices as the MRI volume.

Resample the transformed CT volume in the coordinate system of the medicalref3d object from the MRI volume by using the resample object function. To avoid edge artifacts, specify the fill value as the minimum of the CT data.

transformedCTVolumeRes = resample(transformedCTVolume, ...
    fixedMRIVolume.VolumeGeometry, ...
    FillValue=-1024);

Display Registered Volumes

Display the registered volumes in a new 3-D viewer. Display the CT volume in green and the MRI volume in magenta.

viewerRegistered = viewer3d(BackgroundColor="black",BackgroundGradient="off");

volshow(transformedCTVolumeRes, ...
    RenderingStyle="Isosurface", ...
    Parent=viewerRegistered, ...
    Colormap="green");

volshow(fixedMRIVolume, ...
    RenderingStyle="Isosurface", ...
    Parent=viewerRegistered, ...
    Colormap="magenta");

To visually check that the control points are now aligned, apply the patient-to-patient transform to the CT control points, and display them alongside the MRI control points.

transformedCTPoints = transformPointsForward(tform,movingCTPointsPatient);

for i = 1:size(fixedMRIPointsPatient,1)
    pmoving(i) = images.ui.graphics3d.roi.Point(Position=transformedCTPoints(i,:), ...
        Label=num2str(i), ...
        Color=[0.0 0.4 0.0]);
    pfixed(i) = images.ui.graphics3d.roi.Point(Position=fixedMRIPointsPatient(i,:), ...
        Label=num2str(i), ...
        Color=[0.5 0.2 0.5]);
end

viewerRegistered.Annotations = [pmoving pfixed];
pause(0.5)
viewerRegistered.CameraZoom = 2;

Figure contains an object of type images.ui.graphics3d.viewer3d.

pause(0.5)

Display Registered Volumes as 2-D Slices

To examine the alignment of the volumes in each plane, display the registered volumes as 2-D slices. First, create a fused RGB image volume that overlays the CT volume in green and the MRI volume in magenta. Loop through the transverse slices of the volumes, using the imfuse function to create the fused image for the current slice, and store the fused slices to a new variable, fusedIm.

for i = 1:size(transformedCTVolumeRes.Voxels,3)
    fusedIm(:,:,:,i) = imfuse(transformedCTVolumeRes.Voxels(:,:,i), ...
        fixedMRIVolume.Voxels(:,:,i),method="falsecolor");
end

Permute the dimensions of fusedIm from spatial-spatial-channel-spatial to spatial-spatial-spatial-channel, where channel corresponds to the number of color channels in the fused RGB image.

fusedIm2 = permute(fusedIm,[1 2 4 3]);

Create a medicalVolume object that contains the fused image data and the spatial referencing from the MRI volume.

fusedVol = medicalVolume(fusedIm2,fixedMRIVolume.VolumeGeometry);

Display the fused volume in patient coordinates. To scroll through slices, pause on one of the slices until it highlights, then drag the slice along the axis perpendicular to the plane.

volshow(fusedVol,RenderingStyle="SlicePlanes")

Figure contains an object of type images.ui.graphics3d.viewer3d.

See Also

| | |

Related Examples

More About