Question about the affine transform

Hi,

Thank you for your amazing library. I am impressed by the functionalities and the speed.

I am trying to understand what the affine transform is doing. I have a nii file with following information:

shell> mrinfo -all fodf.nii
************************************************
Image name:          "fodf.nii"
************************************************
  Dimensions:        145 x 174 x 145 x 45
  Voxel size:        1.25 x 1.25 x 1.25 x 1
  Data strides:      [ -1 2 3 4 ]
  Format:            NIfTI-1.1
  Data type:         32 bit float (little endian)
  Intensity scaling: offset = 0, multiplier = 1
  Transform:                    1           0           0         -90
                               -0           1           0        -126
                               -0           0           1         -72
  comments:          FSL5.0
  mrtrix_version:    3.0.4

NIBabel reports the following affine transform

array([[  -1.25,    0.  ,    0.  ,   90.  ],
       [   0.  ,    1.25,    0.  , -126.  ],
       [   0.  ,    0.  ,    1.25,  -72.  ],
       [   0.  ,    0.  ,    0.  ,    1.  ]])

From mrview, I can see the following:

voxel index: [85, 62, 64, 0]
position = [16.38, -48.68, 8.507]

Doing affine @ np.array([85, 62, 64, 1]), I get:

 array([-16.25, -48.5 ,   8.  ,   1.  ])

which looks quite similar to position but there is a minus discrepancy in front of the x value.

How come mrtrix gives this sign?

Thank you a lot for your help,

Best regards

Hi @rveltz,

This is indeed one of the most common sources of confusion amongst users. I’ll try to give a brief explanation, but I have a feeling it’ll end up huge anyway… Here goes:

First off, have a look at our documentation for how we handle the transform information. You’ll note two ways our handling differs from straight NIfTI (and by extension, other packages, including NIBabel):

  • the transform we report is a unitary transformation, without the scaling by the voxel size. You can see the voxel size is 1.25 isotropic, but you can also see that this is included in the diagonal of the sform reported by NIBabel¹. In MRtrix, we store the transform as a unitary matrix giving the unit vectors for each axis, indepedent of the voxel size.

  • We perform an internal ‘reshuffling’ of the transform to convert it to a near-axial form, where the x axis is closest to anatomical left → right, the y axis is closest to anatomical posterior → anterior, and the z axis is closest to anatomical inferior → superior. We can do that because we have the flexibility to manipulate the strides – and you can see, the strides reported by mrinfo include a -1 along the x axis, indicating that the voxels are stored right → left (not the true NIfTI standard frame, but equivalent to the older Analyze format)².

So that should account for all the differences you see. For that last discrepancy in position, the voxel index you’re using is not equivalent between MRtrix and NIBabel: NIBabel would index strictly along the axis specified in the NIfTI transform, which runs right → left, whereas MRtrix would index along the axis it has identified as closest to left → right (in this case, it is pure left → right since your transform is pure axial).

I expect you’ll get the equivalent real position is you use voxel index with the x component = xdim-1-x = 145-1-85 = 59 for NIBabel. I also recommend you turn off linear interpolation in mrview, in which case you should get the position of the nearest voxel, rather than the exact position of the crosshairs.

Hopefully that’ll clear up any confusion… :confused:
All the best,

Donald.


¹ By the way, the voxel size is not be encoded in the NIfTI qform, since it can only represent a pure rigid rotation – so there’s a discrepancy between the sform & the qform anyway, even though both encode the transform…

² Alternatively, you might see this described as FSL-style, radiological convention, or LAS – whereas a pure NIfTI might be labelled as neurological convention or RAS (beware the terms radiological & neurological in this context though, they really aren’t the right way to think about it – but it’s often how people talk about it…).

Thank you a lot for your detailed answer. It makes more sense. Indeed

affine @ np.array([59, 62, 64, 1]),

reports the mrview result.

I need to read your links now.

Best regards

1 Like