-
Notifications
You must be signed in to change notification settings - Fork 262
Improve documentation of orientations module #1418
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Comments
Orientations are always a bit backwards of how I want to think of them, and I think you're probably having that happen to you as well. The orientation describes a transform from data array indices onto world coordinates, which are conventionally RAS+. When you load an LIA matrix, the orientation tells you how to map from LIA to RAS, not RAS to LIA. In [1]: from nibabel.orientations import *
In [2]: import numpy as np
In [3]: ras = io_orientation(np.eye(4))
In [4]: lia = io_orientation([[-1, 0, 0, 0], [0, 0, 1, 0], [0, -1, 0, 0], [0, 0, 0, 1]])
In [5]: ras
Out[5]:
array([[0., 1.],
[1., 1.],
[2., 1.]])
In [6]: lia
Out[6]:
array([[ 0., -1.],
[ 2., -1.],
[ 1., 1.]])
In [7]: ornt_transform(ras, lia)
Out[7]:
array([[ 0., -1.],
[ 2., 1.],
[ 1., -1.]])
In [8]: ornt_transform(lia, ras)
Out[8]:
array([[ 0., -1.],
[ 2., -1.],
[ 1., 1.]]) The general use of orientations is to reorient images to RAS: img = nb.load(...)
orig_to_ras = nb.orientations.io_orientation(img.affine)
ras_img = img.as_reoriented(orig_to_ras) Here, we don't really care what the original orientation was (though we can inspect it with If I want to target a different orientation: lpi_to_ras = nb.orientations.axcodes2ornt('LPI")
orig_to_lpi = nb.orientations.ornt_transform(orig_to_ras, lpi_to_ras)
lpi_img = img.as_reoriented(orig_to_lpi) We definitely could have written this as the inverse, mapping from RAS to the data axes. I'm not sure if there are algorithmic advantages to doing it the way we have, or this direction just fit @matthew-brett's brain when he wrote it. In any case, I don't think they were ever really meant to be worked with directly, so the choice seems arbitrary. |
Thank you! this comment has helped me better understand how orientations work. I would heavily recommend especially the documentation to I have implemented a def inv_ornt(ornt: npt.NDArray[int]) -> npt.NDArray[int]:
"""
Invert the nibabel ornt-transform.
Parameters
----------
ornt : (n,2) numpy array
The transform describing reordering and flipping operations.
Returns
-------
ornt : (n,2) numpy array
The inverse transform describing reordering and flipping operations.
See Also
--------
nibabel.orientations.inv_ornt_aff, nibabel.orientations.io_orientation
The `inv_ornt` function is equivalent to `io_orientation(inv_ornt_aff(ornt, (any,) * ornt.shape[0]))`.
"""
result = np.empty_like(ornt)
result[:, 0] = np.argsort(ornt[:, 0])
result[:, 1] = ornt[ornt[:, 0].astype(int), 1]
return result With |
I believe there is a bug in
nibabel.orientations.ornt_transform
.In specific it seems to me, that all other functions follow the format flip first then reorder as can best be seen in
lia
below.Some examples
But it seems to me
ornt_transform
and also its test (explicitly test 2 innibabel.test.test_orientations.test_ornt_transform
innibabel/nibabel/tests/test_orientations.py
Line 302 in 2fb125c
ras2lia should 1+2. flip the first and second axes, and 3. reoder swapping 2nd and 3rd axes. Instead ras2lia flips 1 and 3 and swaps 2 and 3.
Furthermore, I do not understand, why ras2lia is different than just lia. That seems wrong as ras is besically the Null transform, it does not do anything.
Similar observations for ras2asl
Finally, if we just concatenate ras2lia and lia2ras, we should be back in ras (noop).
This is correct
This is not correct
The text was updated successfully, but these errors were encountered: