-
-
Notifications
You must be signed in to change notification settings - Fork 668
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
Support specification of Nifti sform tolerance via environmental variable #4839
Comments
Just adding a note that I'm having trouble finding records of when we were hitting this. My memory was that there were small shears causing ITK to reject images, but it could also be differences between the sform column norms and Will keep my eyes out for the next one. When we've encountered it, it's generally been due to a failure to validate and update headers before passing to ANTs, so it's possible we won't trigger the case again until the next refactor leaves a hole for a messy image to slip through. |
I think this is already available via #4009 ITK/Modules/IO/NIFTI/src/itkNiftiImageIO.cxx Lines 2080 to 2123 in fb61d2c
This works for me:
|
BTW, the expected values of |
Sorry for not reading carefully before, I see the idea here is to add a numeric tolerance rather than the boolean we currently have - this seems like a good idea. I had a go at creating a test image. However, I noticed that the permissive mode introduces a flip. It's very possible I'm doing something wrong here, so here's R code to create the dummy images, starting from a template library(RNifti)
# the nifti class contains pointers to memory, so read them
# independently to be safe
mni <- readNifti('tpl-MNI152NLin2009cAsym_res-02_T1w.nii.gz')
mni_rotated <- readNifti('tpl-MNI152NLin2009cAsym_res-02_T1w.nii.gz')
mni_sheared <- readNifti('tpl-MNI152NLin2009cAsym_res-02_T1w.nii.gz')
qoffset_x <- mni$qoffset_x
qoffset_y <- mni$qoffset_y
qoffset_z <- mni$qoffset_z
# Create the translation vector using qoffset values
translation_vector <- c(qoffset_x, qoffset_y, qoffset_z)
spacing <- diag(c(2, 2, 2))
# blank out qform
qform(mni) <- structure(diag(4), code=0L)
qform(mni_rotated) <- structure(diag(4), code=0L)
qform(mni_sheared) <- structure(diag(4), code=0L)
writeNifti(mni, 'mni_sform.nii.gz')
rotation <- matrix(c(
0.99765903, -0.06763516, 0.01009717,
0.06808255, 0.99622887, -0.05378478,
-0.00642135, 0.05434632, 0.9985015
), nrow = 3, byrow = TRUE)
shear_matrix <- matrix(c(
1, 0.0001, 0.0001,
0.0, 1, 0.0,
0.0, 0.0, 1
), nrow = 3, byrow = TRUE)
almost_rotation <- shear_matrix %*% rotation
sform_rotated <- rbind(cbind(rotation %*% spacing, translation_vector), c(0, 0, 0, 1))
sform(mni_rotated) <- structure(sform_rotated, code=1L)
writeNifti(mni_rotated, 'mni_rotated.nii.gz')
sform_sheared <- rbind(cbind(almost_rotation %*% spacing, translation_vector), c(0, 0, 0, 1))
sform(mni_sheared) <- structure(sform_sheared, code=1L)
writeNifti(mni_sheared, 'mni_sheared.nii.gz') The shear is small but enough to make non-permissive mode fail. ITK-SNAP 4.2.0 refuses to read PrintHeader mni_sheared.nii.gz 4
cant read mni_sheared.nii.gz ITK_NIFTI_SFORM_PERMISSIVE=TRUE PrintHeader mni_sheared.nii.gz 4
WARNING: In /Users/pcook/tmp/NOT_BACKED_UP/antsMasterBuild/build/ITKv5/Modules/IO/NIFTI/src/itkNiftiImageIO.cxx, line 2111
NiftiImageIO (0x7fccec328ce0): mni_sheared.nii.gz has non-orthogonal sform
WARNING: In /Users/pcook/tmp/NOT_BACKED_UP/antsMasterBuild/build/ITKv5/Modules/IO/NIFTI/src/itkNiftiImageIO.cxx, line 2111
NiftiImageIO (0x7fccec328440): mni_sheared.nii.gz has non-orthogonal sform
-0.997662x-0.0680327x0.00647123x0.0675826x-0.996232x-0.0543497x0.0101444x-0.0537853x0.998501 To try to visualize what happens in permissive mode, I used antsApplyTransforms. This should read the image using the permissive mode code, and then resample it into the space of mni_rotated, which does not trigger the permissive mode sform formulation ITK_NIFTI_SFORM_PERMISSIVE=TRUE antsApplyTransforms \
-d 3 -i mni_sheared.nii.gz -r mni_rotated.nii.gz -o mni_shear_permissive.nii.gz \
-t Identity --verbose --float But loading all these into ITK-SNAP, library(RNifti)
permissive <- readNifti('mni_shear_permissive.nii.gz')
qform(permissive) <- structure(diag(4), code=0L)
c<- structure(diag(4), code=0L)
writeNifti(permissive, 'mni_shear_permissive_sform.nii.gz') So I think the qform is not the issue, the sform created in permissive mode seems to be flipped somehow. Perhaps some sign indeterminancy in SVD?
cc: @vfonov |
Why an environment variable and not an API to the NIfTI reader class? |
Both would be ideal. The environment variable is usable by end-users of the application, whereas the API only by the programmer. |
There's also the CMake option
Do we want a compile time numerical threshold as well? |
Well, the programmer can use the API to provide some control to the end user by some user interface. Environment variables are global state, and like a secret API, that application developers might not even know about, they could result in surprising changes in end results. With NIfTI, we are talking medical-adjacent software, which can often be highly regulated, you might explicitly want for such behaviour changes to not be possible except for via the UI you have designed and tested. |
One other thing I think we may want to correct is that in permissive mode, a non-orthonormal sform is always used even if a valid qform exists ITK/Modules/IO/NIFTI/src/itkNiftiImageIO.cxx Lines 2032 to 2123 in 9a5dade
IMO, we should only use a non-orthonormal sform if no qform exists. |
By the NIfTI standard, if |
I made a flow chart of what I think the current code does. The qform parameters are used to make a ITK/Modules/IO/NIFTI/src/itkNiftiImageIO.cxx Line 1976 in 3b04ad1
Next, the sto and qto are decomposed with SVD. The sto matrix is checked for skew (qto by design can't have any) and the axes are checked for similarity between qto and sto. I think the "sform decomposable without skew" threshold is the best target for a customizable threshold. Passing this test will guarantee the use of the sform in cases where
BTW, even when sform is used, spacing is still derived from the pixdims. Rotation and translation come from the sform. If other scaling is present in the sform, a warning is printed, but that scaling is disregarded. This is contrary to the NIFTI standard, which says pixdims should be ignored when using the sform. |
Per discussion with @effigies , it would be helpful to enable specification of a higher nifti sform tolerance via environmental variable.
There are cases where nifti headers, perhaps edited manually, that may have unintentional small shear, it would be helpful to specify an override via environmental variable a tolerance for orthogonality.
This would be used in docker images that use FSL, ANTs, etc.
Possibly related?
ITK/Modules/IO/NIFTI/src/itkNiftiImageIO.cxx
Lines 1989 to 2010 in fb61d2c
Todo:
CC @hjmjohnson
The text was updated successfully, but these errors were encountered: