Skip to content
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

Refactor and improve affine handling in the viewer. #7

Open
wants to merge 8 commits into
base: main
Choose a base branch
from

Conversation

grlee77
Copy link
Contributor

@grlee77 grlee77 commented Oct 6, 2021

This PR resorts axes to RAS order to avoid potential errors in affine slicing in Napari (see napari/napari#3410). Also, any rotation/shear elements are discarded so that the napari sliders just correspond to simple slicing of the array. This results is an affine that is diagonal in the upper left 3x3 matrix.

I then swap and flip signs to go from RAS to SPL order (as suggested in #5) so that upon initially opening the viewer, the axial view is displayed in a normal radiological orientation. I hope to revisit orientation of linked orthogonal views in napari itself in the coming months.

One issue not updated in this PR was what metadata we want to pass to the viewer. We were passing the affine and header from the NIFTI, but now this will no longer match after reordering/flip of the dimensions. I don't think we have to address that in this PR, thouth, as that metadata was passed along for convenience, but is not being used by napari itself.

@ch-n: if you get a chance to test this out, does it operate as you expect?

reorder data axes to RAS before passing onto Napari. This avoids
potential issues in affine handling:
napari/napari#3410

We also now drop the non-diagonal (rotation, shear) components from the
affine, since napari does not currently handle these.

oi
patient left will be to the right side of the screen
(radiological orientation)

verified that for each dimensions, moving the slider to the right moves
to S, P or L as expected, depending on the visible dimensions
@grlee77 grlee77 added the enhancement New feature or request label Oct 6, 2021
@grlee77
Copy link
Contributor Author

grlee77 commented Oct 6, 2021

Here is an example of a 3D view on 3 separate scans, each of which is a single thick slice along a particular axis through a phantom (data here)

napari T1W_cor_0_0_0_SENSE_13_1.nii T1W_trans_0_0_0_SENSE_4_1.nii T1W_sag_0_0_0_SENSE_8_1.nii &

napari_ortho_3d_view

@Chris-N-K
Copy link

Will test it on the weekend. Thanks for the effort :)
I think you'r right we should not match the meta information to the modified affine matrix. Especially, to use it if the user wants to save the image as Nifti again.

@Chris-N-K
Copy link

Chris-N-K commented Oct 10, 2021

I tested your solution with several images now and looked through your code.

I realy like your approach to realighn the image origin and to discard the currently still problematic parts of the affine transformation. This is probably the best solution as long as napari does not support non orthogonal slicing.

The images should be added in radiological orientation. In my tests the images are all added in the same orientation, but not in the expected. I'm not completely sure why, but I think it might be the wrong approach to direktly use the SPL orientation for the transform. I can produce the wanted outcome with:

target_ornt = nibabel.orientations.axcodes2ornt(('S','P','L'))
current_ornt = nibabel.orientations.io_orientation(nifti.affine)
transform_ornt = nibabel.orientations.ornt_transform(current_ornt, target_ornt)

reoriented_fdata = nibabel.orientations.apply_orientation(nii.get_fdata(),transform_ornt)
or:
reoriented_nifti =  nifti.as_reoriented(transform_ornt

This works without casting to RAS before.
What are your toughts on that?

I would further suggest to use the nibabel SpatialImage method as_closest_canonical to cast the image to RAS.
I think it might be good to use the functions already included in nibabel where ever we can.

Thanks for your work :)
I hope to have more time to work on this project again. The last months were quite a challange...

update reorder_axes to take a specified target orientation instead of assuming RAS
Use LPS rather than SPL for now so that affine_plumb[:3, :3] will be diagonal.
Once appropriate fixes for perfmuted axes have been made to napari, we can switch to SPL

Co-authored-by: Christopher Nauroth-Kreß <56394171+ch-n@users.noreply.github.com>
@grlee77
Copy link
Contributor Author

grlee77 commented Oct 15, 2021

The images should be added in radiological orientation. In my tests the images are all added in the same orientation, but not in the expected. I'm not completely sure why, but I think it might be the wrong approach to direktly use the SPL orientation for the transform. I can produce the wanted outcome with:

Yeah, I think I messed up that conversion and agree that we should convert things directly as you propose. I added a commit implementing reorientation to a target orientation, but have left this LPS rather than SPL for now until we resolve napari/napari#3410.

grlee77 and others added 3 commits October 15, 2021 16:01
use as_reoriented method as it is more concise and will also update the affine we pass on in the metadata

Co-authored-by: Christopher Nauroth-Kreß <56394171+ch-n@users.noreply.github.com>
@codecov
Copy link

codecov bot commented Oct 15, 2021

Codecov Report

Merging #7 (a3fee0f) into main (4395ab5) will increase coverage by 9.04%.
The diff coverage is 98.38%.

Impacted file tree graph

@@            Coverage Diff             @@
##             main       #7      +/-   ##
==========================================
+ Coverage   83.05%   92.09%   +9.04%     
==========================================
  Files           4        4              
  Lines         177      215      +38     
==========================================
+ Hits          147      198      +51     
+ Misses         30       17      -13     
Impacted Files Coverage Δ
napari_nibabel/nibabel.py 97.10% <96.96%> (+22.96%) ⬆️
napari_nibabel/_tests/test_nibabel.py 100.00% <100.00%> (ø)

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 4395ab5...a3fee0f. Read the comment docs.

@Chris-N-K
Copy link

Hi. I tested the new adjustments.
It looks like the translation reformating does no longer work. The origin is again inside of the image and not at the corner as before.
Furthermore, we run in a problem with naparis placement of the image origin. Nibabel expects the origin to be bottom left and not topleft.
Therefore, LPS is represented as LAS in the viewer. -.-

That aside, your solution looks good. Like this we can easily set a default orientation or even adjust the orientation later on with a gui plugin, if we like.

Some suggestions to handle it more elegant then to simply flip axis 1 after orientation?

This problem highly impacted my work on the saving function. I could not find a way to consitently reorient the data in the viewer back to the metadata.

@Chris-N-K
Copy link

@grlee77 I'm sorry I had a misunderstanding. My last comment is majorly wrong.

I revisited the code and the results and I think I understand now what goes wrong.
We cast the image to a new orientation and then use the affine matrix to extract the scale and translate. We can't do that as the affine matrix still represents the operations to cast the image to the RAS orientation and always will as of conventions.
Thus, we can either only use the zooms for scaling and maybe skip translate, what would be pointless or try to generate a matrix that casts to the desired orientation from scratch.

I tried to tinker a solution starting from the RAS image, but I could not make it work in all cases.
I used the nibabel.orientations.inv_ornt_aff() function to generate an affine matrix to cast from RAS to the desired orientation, but I did not manage to include the scaling and translation information from the original affine. At least not in a way that worked every time and is compatible with the future implementation of the entire affine matrix

Do you have an ideas?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants