Incorrect volume information when loading .nrrd

When I load a .nrrd file I believe the direction matrix under volume information might be incorrect. In my example, the space directions in the .nrrd file is:

space directions: (1.99900970198094,0.0716907704748626,-1.35699601564168E-07) (-0.0357263945043087,0.996186852455139,-0.079595185816288) (-0.00285263080149889,0.0795440226793289,0.996827244758606)

,which would by my calculations give
image spacing 1.9993, 1.0019, 1.0000
and IJK to RAS Direction Matrix

-9.99839316e-01, -7.15529825e-02,  1.35699605e-07,
 1.78691748e-02, -9.94272205e-01,  7.95951878e-02,
-1.42679269e-03,  7.93911410e-02,  9.96827270e-01

The only calculations I’ve done here is extract the spacing and convert from LPS to RAS

However, slicer shows the following:
slicer_example

I can match the values shown in slicer by transposing the original space direction matrix but the way I interpret the .nrrd-file format specification that should not be necessary.

There is no need for any transpose. The space directions field contains the I, J, K vectors in the physical space, which are the columns of the IJK to RAS matrix.

Note that you may find sign differences between the IJK to RAS matrix displayed in Slicer and the space directions field in the NRRD file header. NRRD file header can use various physical image coordinate systems, specified by the space field. Usually it contains left-posterior-superior, which means that the space direction stores IJK to LPS.

Thank you for the answer!
I’ve converted the space_directions matrix to IJK_toLPS and then to IJK_to_RAS.
In my application I load an annotations in python and extract some point, I then use the slicer API to plot annotations with points. The points are added using vtkMRMLMarkupsFiducialNode which, as I understand, needs coordinates in RAS directly.

If I compare the IJK_to_RAS matrixes when loading the .nrrd file with pynrrd they are not consistent (I need to transpose one of them for it to be consistent). This caused an error when plotting the points.

Note that I do not compare the space_directions directly with the IJK_to_RAS matrix but convert using the code below

spacing = affine_to_spacing(space_direction_matrix) # affine_to_spacing from MONAI
IJK_to_LPS = space_direction_matrix/spacing
IJK_to_RAS = np.array([-1, -1, 1]) @ IJK_to_LPS

However, looking at the format specification of .nrrd I believe it might be pynrrd that might do the loading incorrectly (Teem: nrrd: Definition of NRRD File Format)

It makes sense for pynrrd to store the vectors as they do now, because you want nrrd.read_header('myimage.nrrd')['space directions'][0] to return the first vector, ...[1] the second vector, etc.

However, it was probably not good choice of pynrrd developers to store the vectors in a 2D numpy array, because as your example shows, somebody may be tempted to interpret this matrix as the matrix transform that the nrrd documentation describes. It would have been more clear storing the vectors in a Python list. At this point, probably the best is to clarify this in the pynnrd documentation. If you have time, it would be nice if you could send a pull request to the pynrrd repository to improve its documentation.

1 Like

Makes sense, thank you for your help!
I’ll contact the pynrrd people and make them aware that this might be confusing. In this case, I guess it’s how MONAI reads the header and converts to an affine matrix that is incorrect.