Convert from spatial registration to shifts

Hello,

This is for the the experts in image registration, particularly CT to CBCT registration to obtain table shifts,

Typically the CBCT is acquired on the day of treatment with the patient on the bed and registered with the planning CT. This registration yields bed shifts (Online Match) that should move the patient to the correct planned position before treatment.

In my planning system software, I register a CT and a CBCT and I get a Dicom Spatial Registration object :slight_smile:

The planning system also reports the match computed from the Registration above as:

Now, In Slicer, I want to recreate these table shifts. When I export the registration object into slicer the transformation matrix is:

For anyone who has done this before, how can you compute these Online Match values from the transformation matrix read from the Dicom Registration object ?

Any pointers on how to compute this will be greatly appreciated.

Thanks

The transformation matrix looks like this:

The last column of the matrix (excluding the bottom element) gives the translation vector (represents the shift in X, Y, Z directions in mm)

image

The upper-left 3ร—3 part of the matrix is the rotation matrix.

FYI here is the IGRT tutorial, in case you want to also compare DVH or gamma: SlicerRtDoc/tutorials/SlicerRT_Tutorial_IGRT_4.11.pptx at master ยท SlicerRt/SlicerRtDoc ยท GitHub

Thanks @cpinter for the kind explanations. I have implemented just this :

        double[,] m = reg.TransformationMatrix;

        // Translations (in mm)
        double[] t = new double[3] { 
            m[3,0], m[3,1], m[3,2] 
        };        

        double RAD2DEG = 180.0 / Math.PI;

        double r00 = m[0,0], r01 = m[0,1], r02 = m[0,2];
        double r10 = m[1,0], r11 = m[1,1], r12 = m[1,2];
        double r20 = m[2,0], r21 = m[2,1], r22 = m[2,2];
        
        // Extract R (row-major)
        double[,] R = new double[3,3];
        // Copy row 0
        R[0, 0] = m[0, 0];
        R[0, 1] = m[0, 1];
        R[0, 2] = m[0, 2];
        // Copy row 1
        R[1, 0] = m[1, 0];
        R[1, 1] = m[1, 1];
        R[1, 2] = m[1, 2];
        // Copy row 2
        R[2, 0] = m[2, 0];
        R[2, 1] = m[2, 1];
        R[2, 2] = m[2, 2];

        // Intrinsic X-Y-Z (roll, pitch, yaw)
        double y_rad = Math.Asin(Math.Max(-1.0, Math.Min(1.0, r02)));
        double x_rad = Math.Atan2(-r12, r22);
        double z_rad = Math.Atan2(-r01, r00);

        double roll = -x_rad * RAD2DEG;  // roll
        double pitch = -y_rad * RAD2DEG;  // pitch
        double yaw = -z_rad * RAD2DEG;  // yaw
        
        // Get first frame
        var cbctframe = cbctvol.Frames.FirstOrDefault();
        var ctframe = ctvol.Frames.FirstOrDefault();
        
        //// Extract parameters from the CBCT frame
        int cbctxSize = cbctframe.XSize;
        int cbctySize = cbctframe.YSize;
        int cbctzSize = cbctframe.ZSize;

        double cbctxRes = cbctframe.XRes;
        double cbctyRes = cbctframe.YRes;
        double cbctzRes = cbctframe.ZRes;

        VVector cbctorigin = cbctframe.Origin;
        VVector cbctxDir = cbctframe.XDirection;
        VVector cbctyDir = cbctframe.YDirection;
        VVector cbctzDir = cbctframe.ZDirection;
        
        
        //// Extract parameters from the CT frame
        int ctxSize = ctframe.XSize;
        int ctySize = ctframe.YSize;
        int ctzSize = ctframe.ZSize;

        double ctxRes = ctframe.XRes;
        double ctyRes = ctframe.YRes;
        double ctzRes = ctframe.ZRes;

        VVector ctorigin = ctframe.Origin;
        VVector ctxDir = ctframe.XDirection;
        VVector ctyDir = ctframe.YDirection;
        VVector ctzDir = cbctframe.ZDirection;
        
        var ctUserOrigin = ctvol.UserOrigin;
        var cbctUserOrigin = cbctvol.UserOrigin;
        
        
        // Compute center directly from origin and half-extent
        VVector cbct_center = cbctorigin
                            + (cbctxSize * 0.5 * cbctxRes) * cbctxDir
                            + (cbctySize * 0.5 * cbctyRes) * cbctyDir
                            + (cbctzSize * 0.5 * cbctzRes) * cbctzDir;
                            
        // Compute center directly from origin and half-extent
        VVector ct_center = ctorigin
                            + (ctxSize * 0.5 * ctxRes) * ctxDir
                            + (ctySize * 0.5 * ctyRes) * ctyDir
                            + (ctzSize * 0.5 * ctzRes) * ctzDir;

Now the issue is actually recreating those Table shifts (Online Match) values from the screenshots. I am not sure how to recreate this from just the 4x4 matrix shown in slicer.

Thanks a lot for your help

Maybe I misunderstand the question but I think this is just what my comment explains. It starts from the 4x4 matrix and explains how the translation/rotation values can be calculated from that.