Comments (26)
ITK uses LPS coordinates and NIFTI RAS. This is handled in the I/O, when ITK converts qform into a direction cosine matrix or writes a cosine matrix back out as a qform.
See methods SetImageIOOrientationFromNIfTI
and SetNIfTIOrientationFromImageIO
.
According to this page
https://nipy.org/nibabel/coordinate_systems.html
NiBabel always wants RAS coordinates so I think it should be safe to apply the same conversion steps in the to_nibabel
and from_nibabel
functions.
Some more on this:
https://itk.org/Doxygen/html/classitk_1_1ImageBase.html#a23ebc76de3b60bb1eeabf66cd4dabc48
This is the base ITK class that handles voxel to physical space conversion. It references this page
https://itk.org/Wiki/Proposals:Orientation#Some_notes_on_the_DICOM_convention_and_current_ITK_usage
from antspy.
This issue is not nibabel related, since the qform is saved within the nifti header and should be parsed the same way in all implementations. I checked the values within SPM and MRIcroN and they are the same as the nibabel output, so there has to be an issue by extracting the values via the ReadImageInformation().
As reading and writing through ITK methods results in correct qforms, my guess is that ITK stores the quatern_b, quatern_c and quatern_d values from the header as well to correct the directions.
from antspy.
thx - i am leaving this open until a test is added to help prevent this from coming up in the future.
from antspy.
consider 1e73b82
from antspy.
very nice find. a pull request would be helpful. it's probably just changing some indexing somewhere in the function get_qform
.
from antspy.
As for the pull request, I'm not really sure where to fix this issue (prolly somewhere in the itkNiftiImageIO.cxx). As far as I understand, the direction and origin are parsed from the q_form without paying respect to quatern_b, quatern_c and quatern_d.
For my current code I wrote a wrapper to replace ants.from_nibabel()
def nii_to_ants(nii_img : nib.Nifti1Image):
q_form = nii_img.get_qform()
spacing = nii_img.header['pixdim'][1:4]
origin = q_form[:3,3]
direction = q_form[:3,:3] / spacing
return ants.from_numpy(data=nii_img.get_data().astype(np.float), origin=origin.tolist(), spacing=spacing.tolist(), direction=direction)
This however assumes 3D data and Nifti1 headers.
from antspy.
indeed, i am not sure where you would fix this either. when i search:
https://github.com/ANTsX/ANTsPy/search?q=get_qform&unscoped_q=get_qform
i dont find any code in ANTsPy. thoughts?
from antspy.
When I understand the code correctly, the direction, origin and spacing is derived from the ITK function itk::ImageIOBase::ReadImageInformation() in ANTsPy/ants/lib/LOCAL_antsImageHeaderInfo.cxx So this has to be an issue in the used ITK version.
from antspy.
a reproducible / self-contained example of this problem:
import ants
import nibabel as nib
fn = ants.get_data( "ch2" )
ants_mni = ants.image_read( fn )
nii_mni = nib.load( fn )
ants_mni = ants_mni.to_nibabel()
print(ants_mni.get_qform())
print(nii_mni.get_qform())
from antspy.
the problem may be resolvable by alterations to this bit of code:
ANTsPy/ants/utils/convert_nibabel.py
Lines 18 to 21 in ad7fcd2
from antspy.
The problem is, that the metadata already mismatches upon loading the image via ants.image_read().
nii_img = nib.load('MNI152.nii.gz')
ants_img = ants.from_nibabel(nii_img) # This is the same as ants_img = ants.image_read('MNI152.nii.gz')
print(nii_img.header.get_qform()[:3,3].tolist())
print(ants_img.origin)
(90.0, -126.0, -72.0)
(-90.0, 126.0, -72.0)
from antspy.
this particular difference may be the difference between LPS vs whatever nibabel uses.
from antspy.
can you share the image you are using ( upload to issue ) so we can be on the same page.
from antspy.
https://github.com/nilearn/nilearn/raw/master/nilearn/datasets/data/avg152T1_brain.nii.gz
from antspy.
upon further reflection, i think that this is just an image orientation difference - ie a difference in coordinate systems (itk uses LPS). @cookpa - can you briefly comment and / or suggest a relevant link ?
from antspy.
more discussion here: https://discourse.itk.org/t/nifti-orientation-issues/431/2 it doesnt "convert images" ... rather, the reference frame is interpreted as LPS. the issue here is, i think, that the ANTsPy "to_nibabel" function is probably wrong.
from antspy.
To make a long story short: ITK assumes NIFTI images to be in RAS space and simply flips the first two axes upon loading (code). Therefore it is sufficient to do the same upon to_nibabel().
However, I tried to utilize ants.reorient_image2() in order to achieve this and watched again a strange behavior. When loading an NIFTI image in RAS orientation
[[1. 0. 0.]
[0. 1. 0.]
[0. 0. 1.]]
this results in a direction
[[-1. 0. 0.]
[0. -1. 0.]
[0. 0. 1.]]
as the first axes are flipped. But when applying ants.reorient_image2(orientation='RAS') to it, it results in
[[1. 0. 0.]
[0. 1. 0.]
[0. 0. -1.]]
where the Z-Axes is flipped, which looks like a bug within itkOrientImageFilter.hxx to me.
from antspy.
i do not think that is likely. behavior looks correct here:
>>> mni = ants.image_read(ants.get_data('mni'))
>>> mni
ANTsImage (RPI)
Pixel Type : float (float32)
Components : 1
Dimensions : (182, 218, 182)
Spacing : (1.0, 1.0, 1.0)
Origin : (-90.0, 126.0, -72.0)
Direction : [ 1. 0. 0. 0. -1. 0. 0. 0. 1.]
>>> mniRAS
ANTsImage (RAS)
Pixel Type : float (float32)
Components : 1
Dimensions : (182, 218, 182)
Spacing : (1.0, 1.0, 1.0)
Origin : (-90.0, -91.0, 109.0)
Direction : [ 1. 0. 0. 0. 1. 0. 0. 0. -1.]
>>> mniLPS
ANTsImage (LPS)
Pixel Type : float (float32)
Components : 1
Dimensions : (182, 218, 182)
Spacing : (1.0, 1.0, 1.0)
Origin : (91.0, 126.0, 109.0)
Direction : [-1. 0. 0. 0. -1. 0. 0. 0. -1.]
from antspy.
however, your comment:
"To make a long story short: ITK assumes NIFTI images to be in RAS space and simply flips the first two axes upon loading (code). Therefore it is sufficient to do the same upon to_nibabel()."
is probably right. i guess the question is what happens in 2D and 4D.
from antspy.
i do not think that is likely. behavior looks correct here:
>>> mni = ants.image_read(ants.get_data('mni')) >>> mni ANTsImage (RPI) Pixel Type : float (float32) Components : 1 Dimensions : (182, 218, 182) Spacing : (1.0, 1.0, 1.0) Origin : (-90.0, 126.0, -72.0) Direction : [ 1. 0. 0. 0. -1. 0. 0. 0. 1.] >>> mniRAS ANTsImage (RAS) Pixel Type : float (float32) Components : 1 Dimensions : (182, 218, 182) Spacing : (1.0, 1.0, 1.0) Origin : (-90.0, -91.0, 109.0) Direction : [ 1. 0. 0. 0. 1. 0. 0. 0. -1.] >>> mniLPS ANTsImage (LPS) Pixel Type : float (float32) Components : 1 Dimensions : (182, 218, 182) Spacing : (1.0, 1.0, 1.0) Origin : (91.0, 126.0, 109.0) Direction : [-1. 0. 0. 0. -1. 0. 0. 0. -1.]
According to nibabel documentation the z-axis value should be positive in RAS, or am I missing a point here?
from antspy.
[...] the question is what happens in 2D and 4D.
let me answer with the code from ITK:
const int max_defined_orientation_dims = ( dims > 3 ) ? 3 : dims;
It flips the first 2 dimensions in every case. And every dimension above the third is simply discarded.
from antspy.
ok - here is a fix:
>>> import ants
>>> import nibabel as nib
>>> fn = "avg152T1_brain.nii.gz"
>>> ants_img = ants.image_read( fn )
>>> nii_mni = nib.load( fn )
>>> ants_mni = ants_img.to_nibabel()
>>> print(ants_mni.get_qform())
[[ -2. 0. 0. 90.]
[ 0. 2. 0. -126.]
[ 0. 0. 2. -72.]
[ 0. 0. 0. 1.]]
>>> print(nii_mni.get_qform())
[[ -2. 0. 0. 90.]
[ 0. 2. 0. -126.]
[ 0. 0. 2. -72.]
[ 0. 0. 0. 1.]]
>>> ants.from_nibabel( nii_mni )
ANTsImage (RPI)
Pixel Type : float (float32)
Components : 1
Dimensions : (91, 109, 91)
Spacing : (2.0, 2.0, 2.0)
Origin : (-90.0, 126.0, -72.0)
Direction : [ 1. 0. 0. 0. -1. 0. 0. 0. 1.]
>>> ants_img
ANTsImage (RPI)
Pixel Type : float (float32)
Components : 1
Dimensions : (91, 109, 91)
Spacing : (2.0, 2.0, 2.0)
Origin : (-90.0, 126.0, -72.0)
Direction : [ 1. 0. 0. 0. -1. 0. 0. 0. 1.]
from antspy.
from antspy.
apologies for pushing this to master but it seems to be correct .... agree?
from antspy.
I dont think the last-ind has to be flipped. But I'll check into this.
from antspy.
I'll checked into the fix and as far as I see it is not quite correct. ITK changes the signs of the first two elements of all vectors. Fortunately, this simplifies the whole fix to:
affine[:2,:] *= -1
UPDATE: The way the affine is created, you fix works nevertheless. When considering using the real transformation in the future, tho above approach is the way to go.
from antspy.
Related Issues (20)
- How to output and save deformation fields HOT 6
- compose_ants_transforms change HOT 3
- broken b-spline fitting HOT 2
- B-spline curve fitting failures HOT 1
- Ubuntu Python 3.9 installation error (sha256 mismatch) HOT 3
- potential bug: reference leak when torch is loaded and a function errors HOT 1
- ants.iMath_histogram_equalization HOT 2
- 有没有GPU加速版本的..... HOT 1
- File errors in `ants` folder under home directory crashes ANTsPyx HOT 4
- Encountering the error "RuntimeError: Registration failed with error code 1", when MRA images and T1-weighted images are co-registered with "SyN" HOT 11
- ANTsPy crashes Python when registering two images HOT 4
- Circular import error HOT 2
- Support for HDF5/NWB HOT 1
- Difference between `ants.apply_transforms` and `transform.apply_to_image` with multiple transforms HOT 3
- An question about the return results of ants.registration HOT 6
- ants_transform.apply_to_image broken for displacement field transforms HOT 2
- 'ANTsImage' object has no attribute 'endswith' HOT 9
- to_nibabel and from_nibabel missing from version 0.5.3 api HOT 10
- ValueError: Image Size Too Large When Plotting ANTsImage Using `ants.plot()` HOT 1
- The inverse transform about ants.apply_transforms HOT 2
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
D3
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
-
Recommend Topics
-
javascript
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
-
web
Some thing interesting about web. New door for the world.
-
server
A server is a program made to process requests and deliver data to clients.
-
Machine learning
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from antspy.