-
-
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
ENH: Added permissive mode to NIFTI reader #4009
Changes from all commits
3d3f3e5
3e45ceb
7d31d0c
cf7e1f5
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -22,6 +22,8 @@ | |
#include <nifti1_io.h> | ||
#include "itkNiftiImageIOConfigurePrivate.h" | ||
#include "itkMakeUniqueForOverwrite.h" | ||
#include "itksys/SystemTools.hxx" | ||
#include "itksys/SystemInformation.hxx" | ||
|
||
namespace itk | ||
{ | ||
|
@@ -449,6 +451,7 @@ NiftiImageIO::NiftiImageIO() | |
: m_NiftiImageHolder(new NiftiImageProxy(nullptr)) | ||
, m_NiftiImage(*m_NiftiImageHolder.get()) | ||
, m_LegacyAnalyze75Mode{ ITK_NIFTI_IO_ANALYZE_FLAVOR_DEFAULT } | ||
, m_SFORM_Permissive{ ITK_NIFTI_IO_SFORM_PERMISSIVE_DEFAULT } | ||
{ | ||
this->SetNumberOfDimensions(3); | ||
nifti_set_debug_level(0); // suppress error messages | ||
|
@@ -460,6 +463,12 @@ NiftiImageIO::NiftiImageIO() | |
this->AddSupportedWriteExtension(ext); | ||
this->AddSupportedReadExtension(ext); | ||
} | ||
std::string envVar; | ||
if (itksys::SystemTools::GetEnv("ITK_NIFTI_SFORM_PERMISSIVE", envVar)) | ||
{ | ||
envVar = itksys::SystemTools::UpperCase(envVar); | ||
this->SetSFORM_Permissive(envVar != "NO" && envVar != "OFF" && envVar != "FALSE"); | ||
} | ||
} | ||
|
||
NiftiImageIO::~NiftiImageIO() | ||
|
@@ -481,6 +490,7 @@ NiftiImageIO::PrintSelf(std::ostream & os, Indent indent) const | |
os << indent << "ConvertRASDisplacementVectors: " << (m_ConvertRASDisplacementVectors ? "On" : "Off") << std::endl; | ||
os << indent << "OnDiskComponentType: " << m_OnDiskComponentType << std::endl; | ||
os << indent << "LegacyAnalyze75Mode: " << m_LegacyAnalyze75Mode << std::endl; | ||
os << indent << "SFORM permissive: " << (m_SFORM_Permissive ? "On" : "Off") << std::endl; | ||
} | ||
|
||
bool | ||
|
@@ -899,6 +909,8 @@ NiftiImageIO::SetImageIOMetadataFromNIfTI() | |
// Necessary to clear dict if ImageIO object is re-used | ||
thisDic.Clear(); | ||
|
||
EncapsulateMetaData<std::string>(thisDic, "ITK_sform_corrected", m_SFORM_Corrected ? "YES" : "NO"); | ||
|
||
std::ostringstream nifti_type; | ||
nifti_type << nim->nifti_type; | ||
EncapsulateMetaData<std::string>(thisDic, "nifti_type", nifti_type.str()); | ||
|
@@ -2031,6 +2043,7 @@ NiftiImageIO::SetImageIOOrientationFromNIfTI(unsigned short dims) | |
// Only orthonormal matricies have transpose as inverse | ||
const vnl_matrix_fixed<float, 3, 3> candidate_identity = rotation * rotation.transpose(); | ||
const bool is_orthonormal = candidate_identity.is_identity(1.0e-4); | ||
|
||
return is_orthonormal; | ||
} | ||
}(); | ||
|
@@ -2089,15 +2102,59 @@ NiftiImageIO::SetImageIOOrientationFromNIfTI(unsigned short dims) | |
}(); | ||
prefer_sform_over_qform = sform_and_qform_are_very_similar; | ||
} | ||
} // sform is orthonormal | ||
} // sform not NIFTI_XFORM_UNKNOWN | ||
} | ||
else if (m_SFORM_Permissive) // sform is orthonormal | ||
{ | ||
// Fix to deal with non-orthogonal matrixes | ||
// this approach uses SVD decomposition | ||
// maybe it is better to use nifti_make_orthog_mat44 | ||
// to be consistent with other software? | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Unfortunately the block below this line doesn't produce orthogonal matrix. The test image NonOrthoSform.nii.gz ends up with ITK image:
The matrix is not orthogonal. I have tested the alternative you mentioned above and it works very good, Updated post! Also the correct origin is Probably this works: else if (m_SFORM_Permissive)
{
// nifti_make_orthog_mat44 does 3x3 orthogonal matrix, other components are 0 and [3][3] is 1
mat44 _mat = nifti_make_orthog_mat44(this->m_NiftiImage->sto_xyz.m[0][0],
this->m_NiftiImage->sto_xyz.m[0][1],
this->m_NiftiImage->sto_xyz.m[0][2],
this->m_NiftiImage->sto_xyz.m[1][0],
this->m_NiftiImage->sto_xyz.m[1][1],
this->m_NiftiImage->sto_xyz.m[1][2],
this->m_NiftiImage->sto_xyz.m[2][0],
this->m_NiftiImage->sto_xyz.m[2][1],
this->m_NiftiImage->sto_xyz.m[2][2]);
_mat.m[0][3] = this->m_NiftiImage->sto_xyz.m[0][3]; // Origin X
_mat.m[1][3] = this->m_NiftiImage->sto_xyz.m[1][3]; // Origin Y
_mat.m[2][3] = this->m_NiftiImage->sto_xyz.m[2][3]; // Origin Z
// Probably not required to copy?
/*
_mat.m[3][3] = this->m_NiftiImage->sto_xyz.m[3][3]; // 1
_mat.m[3][0] = this->m_NiftiImage->sto_xyz.m[3][0]; // 0
_mat.m[3][1] = this->m_NiftiImage->sto_xyz.m[3][1]; // 0
_mat.m[3][2] = this->m_NiftiImage->sto_xyz.m[3][2]; // 0
*/
// Copying scales seems to be not required for spacing, it is not taken from the matrix
/*
const vnl_matrix_fixed<float, 4, 4> sform_as_matrix{ &(this->m_NiftiImage->sto_xyz.m[0][0]) };
const vnl_matrix_fixed<float, 3, 3> rotation = sform_as_matrix.extract(3, 3, 0, 0);
_mat.m[0][0] *= rotation.get_column(0).magnitude();
_mat.m[1][0] *= rotation.get_column(0).magnitude();
_mat.m[2][0] *= rotation.get_column(0).magnitude();
_mat.m[0][1] *= rotation.get_column(1).magnitude();
_mat.m[1][1] *= rotation.get_column(1).magnitude();
_mat.m[2][1] *= rotation.get_column(1).magnitude();
_mat.m[0][1] *= rotation.get_column(2).magnitude();
_mat.m[1][1] *= rotation.get_column(2).magnitude();
_mat.m[2][1] *= rotation.get_column(2).magnitude();
*/
itkWarningMacro(<< this->GetFileName() << " has non-orthogonal sform, corrected");
this->m_SFORM_Corrected = true;
return _mat;
} It is still in testing! The current code takes the whole 4x4 matrix, but 4th column is an origin (translation) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
OK, it was my mistake - I should have done it only on 3x3 upper left submatrix. Fixed now. |
||
itkWarningMacro(<< this->GetFileName() << " has non-orthogonal sform"); | ||
|
||
vnl_matrix_fixed<double, 3, 3> mat; | ||
|
||
for (int i = 0; i < 3; ++i) | ||
for (int j = 0; j < 3; ++j) | ||
{ | ||
mat[i][j] = double{ this->m_NiftiImage->sto_xyz.m[i][j] }; | ||
} | ||
|
||
vnl_svd<double> svd(mat.as_ref(), 1e-8); | ||
|
||
if (svd.singularities() == 0) | ||
{ | ||
mat = svd.V() * svd.U().conjugate_transpose(); | ||
mat44 _mat; | ||
|
||
for (int i = 0; i < 3; ++i) | ||
for (int j = 0; j < 3; ++j) | ||
{ | ||
_mat.m[i][j] = static_cast<float>(mat[i][j]); | ||
} | ||
|
||
// preserve origin | ||
for (int i = 0; i < 3; ++i) | ||
{ | ||
_mat.m[i][3] = this->m_NiftiImage->sto_xyz.m[i][3]; | ||
} | ||
for (int i = 0; i < 4; ++i) // should be 0 0 0 1 | ||
{ | ||
_mat.m[3][i] = this->m_NiftiImage->sto_xyz.m[3][i]; | ||
} | ||
|
||
this->m_SFORM_Corrected = true; | ||
return _mat; | ||
} | ||
} | ||
} // sform not NIFTI_XFORM_UNKNOWN | ||
if (prefer_sform_over_qform) | ||
{ | ||
this->m_SFORM_Corrected = false; | ||
return this->m_NiftiImage->sto_xyz; | ||
} | ||
else if (this->m_NiftiImage->qform_code != NIFTI_XFORM_UNKNOWN) | ||
{ | ||
this->m_SFORM_Corrected = false; | ||
return this->m_NiftiImage->qto_xyz; | ||
} | ||
|
||
|
@@ -2279,6 +2336,15 @@ NiftiImageIO::SetNIfTIOrientationFromImageIO(unsigned short origdims, unsigned s | |
itkWarningMacro("Non-orthogonal direction matrix coerced to orthogonal"); | ||
} | ||
|
||
// also issue a warning if original file had non-orthogonal matrix? | ||
{ | ||
const MetaDataDictionary & thisDic = this->GetMetaDataDictionary(); | ||
std::string temp; | ||
if (itk::ExposeMetaData<std::string>(thisDic, "nifti_sform_corrected", temp) && temp == "YES") | ||
{ | ||
itkWarningMacro("Non-orthogonal direction matrix in original nifti file was non-orthogonal"); | ||
} | ||
} | ||
// Fill in origin. | ||
matrix.m[0][3] = static_cast<float>(-this->GetOrigin(0)); | ||
matrix.m[1][3] = (origdims > 1) ? static_cast<float>(-this->GetOrigin(1)) : 0.0f; | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
8ee6cb2fa2f8c95f33d8b34f1556779f2a5c793680bee872531beaaa7660ef0b7d3f50fc90fb905d79e96bb37dfc63158f9b39560241ca5cc55852d76921b745 |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
c12c74bdb7fa32dfbee00db9009110fa1734c7702ae5002f7dd5fb6740341b0fb94c4e5f1a53bde9f1bebd45ad8b64b7643f944a66fa25b44cff5dcc0054974b |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Very minor comment. IMHO, the comment
// sform is orthonormal
marks the end of the previous block. At this line it might be a little bit misleading, IMHO.