Skip to content

Commit 1a1a8cc

Browse files
committed
ENH: Issue warning when NIFTI IO coerces matrix to orthonormal
NIFTI IO silently coerced non-orhonormal matrices to be orthonormal. We now issue a warning when this is done. addresses #3476
1 parent 5b206ec commit 1a1a8cc

3 files changed

Lines changed: 90 additions & 0 deletions

File tree

Modules/IO/NIFTI/src/itkNiftiImageIO.cxx

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2151,6 +2151,24 @@ NiftiImageIO::SetNIfTIOrientationFromImageIO(unsigned short origdims, unsigned s
21512151
mat44 matrix =
21522152
nifti_make_orthog_mat44(dirx[0], dirx[1], dirx[2], diry[0], diry[1], diry[2], dirz[0], dirz[1], dirz[2]);
21532153
matrix = mat44_transpose(matrix);
2154+
// Check if matrix is orthogonal and issue a warning if it was
2155+
// coerced to orthogonal. Use same epsilon as used in SetImageIOOrientationFromNIfTI
2156+
const unsigned int matDim(this->GetDirection(0).size());
2157+
vnl_matrix<float> imageMat(matDim, matDim);
2158+
for (unsigned c = 0; c < matDim; ++c)
2159+
{
2160+
auto col = this->GetDirection(c);
2161+
for (unsigned r = 0; r < matDim; ++r)
2162+
{
2163+
imageMat[r][c] = col[r];
2164+
}
2165+
}
2166+
auto candidateIdentity = imageMat * imageMat.transpose();
2167+
if (!candidateIdentity.is_identity(1.0e-4))
2168+
{
2169+
itkWarningMacro("Non-orthogonal direction matrix coerced to orthogonal");
2170+
}
2171+
21542172
// Fill in origin.
21552173
matrix.m[0][3] = static_cast<float>(-this->GetOrigin(0));
21562174
matrix.m[1][3] = (origdims > 1) ? static_cast<float>(-this->GetOrigin(1)) : 0.0f;

Modules/IO/NIFTI/test/CMakeLists.txt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ itkNiftiImageIOTest13.cxx
1616
itkNiftiReadAnalyzeTest.cxx
1717
itkNiftiReadWriteDirectionTest.cxx
1818
itkExtractSlice.cxx
19+
itkNiftiWriteCoerceOrthogonalDirectionTest.cxx
1920
)
2021

2122
# For itkNiftiImageIOTest.h.
@@ -101,3 +102,8 @@ itk_add_test(NAME itkNiftiReadWriteDirectionSmallVoxelTest
101102
itk_add_test(NAME itkNiftiSmallVoxelsAffinePrecisionTest
102103
COMMAND ITKIONIFTITestDriver itkNiftiImageIOTest13
103104
DATA{Input/SmallVoxels_AffinePrecision.nii.gz})
105+
106+
itk_add_test(NAME itkNiftiWriteCoerceOrthogonalDirectionTest
107+
COMMAND ITKIONIFTITestDriver itkNiftiWriteCoerceOrthogonalDirectionTest
108+
${ITK_TEST_OUTPUT_DIR}
109+
)
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
/*=========================================================================
2+
*
3+
* Copyright NumFOCUS
4+
*
5+
* Licensed under the Apache License, Version 2.0 (the "License");
6+
* you may not use this file except in compliance with the License.
7+
* You may obtain a copy of the License at
8+
*
9+
* https://www.apache.org/licenses/LICENSE-2.0.txt
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*
17+
*=========================================================================*/
18+
#include <iostream>
19+
#include "itkIOTestHelper.h"
20+
#include "itkTestingMacros.h"
21+
#include "itkNiftiImageIO.h"
22+
23+
int
24+
itkNiftiWriteCoerceOrthogonalDirectionTest(int argc, char * argv[])
25+
{
26+
if (argc < 2)
27+
{
28+
std::cerr << "Missing Parameters." << std::endl;
29+
std::cerr << "Usage: " << itkNameOfTestExecutableMacro(argv) << "testOutputDir" << std::endl;
30+
std::cerr << "1 argument required, received " << argc << std::endl;
31+
for (int i = 0; i < argc; ++i)
32+
{
33+
std::cerr << "\t" << i << " : " << argv[i] << std::endl;
34+
}
35+
return EXIT_FAILURE;
36+
}
37+
38+
const unsigned int dim = 2;
39+
using ImageType = itk::Image<unsigned char, dim>;
40+
41+
ImageType::IndexType startIndex = { { 0, 0 } };
42+
ImageType::SizeType imageSize = { { 2, 2 } };
43+
ImageType::RegionType region;
44+
region.SetSize(imageSize);
45+
region.SetIndex(startIndex);
46+
auto image1 = ImageType::New();
47+
image1->SetRegions(region);
48+
image1->Allocate();
49+
50+
ImageType::DirectionType mat1;
51+
mat1.SetIdentity();
52+
mat1[0][0] = 0.5; // make matrix non-orthonormal
53+
image1->SetDirection(mat1);
54+
55+
const std::string outputDir = argv[1];
56+
const std::string outputFilename = outputDir + "/coercedDirection.nii.gz";
57+
itk::IOTestHelper::WriteImage<ImageType, itk::NiftiImageIO>(image1, outputFilename);
58+
auto image2 = itk::IOTestHelper::ReadImage<ImageType>(outputFilename);
59+
// Nifti image writing coerces the direction matrix to be
60+
// orthonormal, so the matrices are expected to be different
61+
if (image1->GetDirection() == image2->GetDirection())
62+
{
63+
return EXIT_FAILURE;
64+
}
65+
return EXIT_SUCCESS;
66+
}

0 commit comments

Comments
 (0)