Skip to content

Commit

Permalink
ENH: Add nifti sform read/write testing
Browse files Browse the repository at this point in the history
Add images and a test suite to ensure that the
sform and qform variants of the nifti direction
representations are properly interpreted.

This makes ITK more compatible with both
nibabel and dcm2niix tools with respect
to improved direction precsion representation.

```bash
\# Need uncompressed files of file with both sform and qform as converted by dcm2niix tool from DICOM
gunzip LPSLabels.nii

\# make no_qform variant
nifti_tool -mod_hdr -prefix LPSLabels_noqform.nii -infiles LPSLabels.nii -mod_field qform_code 0 -mod_field quatern_b 0 -mod_field quatern_c 0 -mod_field  quatern_d 0 -mod_field qoffset_x 0 -mod_field qoffset_y 0 -mod_field qoffset_z 0

\# make no_sform variant
nifti_tool -mod_hdr   -prefix LPSLabels_nosform.nii -infiles LPSLabels.nii -mod_field sform_code 0 -mod_field srow_x "0 0 0 0" -mod_field srow_y "0 0 0 0" -mod_field srow_z "0 0 0 0"

\# Verify results are as desired
nifti_tool -diff_hdr -in LPSLabels_noqform.nii LPSLabels.nii
nifti_tool -diff_hdr -in LPSLabels_nosform.nii LPSLabels.nii

\# Make an invalid nifti file!  pixdim is required for nibabel and ITK readings
\# nifti_tool -mod_hdr -prefix LPSLabels_noqform_nopixdim.nii -infiles LPSLabels.nii -mod_field qform_code 0 -mod_field quatern_b 0 -mod_field quatern_c 0 -mod_field  quatern_d 0 -mod_field qoffset_x 0 -mod_field qoffset_y 0 -mod_field qoffset_z 0 -mod_field pixdim "0 0 0 0 0 0 0 0"\n

gzip -9v LPSLabels.nii LPSLabels_nosform.nii LPSLabels_noqform.nii

```

``` python
import nibabel
LPSLabels=nibabel.load('LPSLabels.nii.gz')
LPSLabels_noqform =nibabel.load('LPSLabels_noqform.nii.gz')
LPSLabels_nosform =nibabel.load('LPSLabels_nosform.nii.gz')

LPSLabels.affine == LPSLabels_noqform.affine
LPSLabels.affine == LPSLabels_nosform.affine
```
  • Loading branch information
hjmjohnson committed Jul 4, 2020
1 parent 616461f commit 8e2e528
Show file tree
Hide file tree
Showing 2 changed files with 123 additions and 0 deletions.
9 changes: 9 additions & 0 deletions Modules/IO/NIFTI/test/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ itkNiftiImageIOTest10.cxx
itkNiftiImageIOTest11.cxx
itkNiftiImageIOTest12.cxx
itkNiftiReadAnalyzeTest.cxx
itkNiftiReadWriteDirectionTest.cxx
itkExtractSlice.cxx
)

Expand Down Expand Up @@ -77,3 +78,11 @@ itk_add_test(NAME itkNiftiReadAnalyzeTest
itk_add_test(NAME itkExtractSliceSlopeInterceptUCHAR
COMMAND ITKIONIFTITestDriver --compare DATA{Baseline/SlopeInterceptUCHAR-midSlice.nrrd} ${ITK_TEST_OUTPUT_DIR}/SlopeInterceptUCHAR-midSlice.nrrd
itkExtractSlice DATA{Input/SlopeInterceptUCHAR.nii.gz} ${ITK_TEST_OUTPUT_DIR}/SlopeInterceptUCHAR-midSlice.nrrd)

itk_add_test(NAME itkNiftiReadWriteDirectionTest
COMMAND ITKIONIFTITestDriver itkNiftiReadWriteDirectionTest
DATA{${ITK_DATA_ROOT}/Input/LPSLabels.nii.gz}
DATA{${ITK_DATA_ROOT}/Input/LPSLabels_noqform.nii.gz}
DATA{${ITK_DATA_ROOT}/Input/LPSLabels_nosform.nii.gz}
${ITK_TEST_OUTPUT_DIR}
)
114 changes: 114 additions & 0 deletions Modules/IO/NIFTI/test/itkNiftiReadWriteDirectionTest.cxx
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
/*=========================================================================
*
* Copyright NumFOCUS
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0.txt
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*=========================================================================*/
#include <iostream>
#include <fstream>
#include "itkNiftiImageIO.h"
#include "itkImageFileReader.h"
#include "itkImageFileWriter.h"

// debug
#include <map>

int
itkNiftiReadWriteDirectionTest(int ac, char * av[])
{
if (ac < 5)
{
std::cerr << "itkNiftiReadWriteDirectionTest: <ImageWithBoth Q and S forms> <no qform> <no sform> <TestOutputDir>"
<< std::endl;
std::cerr << "5 arguments required, recieved " << ac << std::endl;
for (int i = 0; i < ac; ++i)
{
std::cerr << "\t" << i << " : " << av[i] << std::endl;
}
return EXIT_FAILURE;
}

using TestImageType = itk::Image<float, 3>;
auto reader_lambda = [](std::string filename) -> TestImageType::Pointer {
itk::ImageFileReader<TestImageType>::Pointer reader = itk::ImageFileReader<TestImageType>::New();
std::string test(filename);
reader->SetFileName(test);
reader->Update();
return reader->GetOutput();
};

using TestImageType = itk::Image<float, 3>;
TestImageType::Pointer LPSLabel = reader_lambda(av[1]);
TestImageType::Pointer LPSLabel_noqform = reader_lambda(av[2]);
TestImageType::Pointer LPSLabel_nosform = reader_lambda(av[3]);

const auto LPSLabel_direction = LPSLabel->GetDirection();
const auto LPSLabel_noqform_direction = LPSLabel_noqform->GetDirection();
const auto LPSLabel_nosform_direction = LPSLabel_nosform->GetDirection();

// Verify that the sform from LPSLabel being used when reading an image with both sform and qform
// The sforms should be identical in both cases.
if (LPSLabel_direction != LPSLabel_noqform_direction)
{
return EXIT_FAILURE;
}
// Verify that the qform alone is not identical to the sform (due to lossy storage capacity)
// This difference is small, but periodically causes numerical precision errors,
// VERIFY THAT THE DIRECTIONS ARE ACTUALLY DIFFERENT, They are expected to be different due to lossy
// representaiton of the qform.
if (LPSLabel_direction == LPSLabel_nosform_direction)
{
return EXIT_FAILURE;
}

// Write image that originally had no sform direction representation into a file with both sform and qform
const std::string test_output_dir = av[4];
const std::string test_filename = test_output_dir + "/test_filled_sform.nii.gz";
itk::ImageFileWriter<TestImageType>::Pointer writer = itk::ImageFileWriter<TestImageType>::New();
writer->SetFileName(test_filename);
writer->SetInput(LPSLabel_nosform);
writer->Update();


// This time it should read from the newly written "sform" code in the image, which should
// be the same as reading from qform of the original iamge
TestImageType::Pointer reread = reader_lambda(test_filename);
const auto reread_direction = reread->GetDirection();
const auto mdd = reread->GetMetaDataDictionary();
std::string sform_code_from_nifti;
const bool expose_success = itk::ExposeMetaData<std::string>(mdd, "sform_code_name", sform_code_from_nifti);
if (!expose_success || sform_code_from_nifti != "NIFTI_XFORM_SCANNER_ANAT")
{
std::cerr << "Error: sform not set during writing" << std::endl;
return EXIT_FAILURE;
}
bool is_close_qform_converted = true;
for (int r = 0; r < 3; ++r)
{
for (int c = 0; c < 3; ++c)
{
const double diff = std::fabs(LPSLabel_nosform_direction[r][c] - reread_direction[r][c]);
if (diff > 1e-8)
{
is_close_qform_converted = false;
}
}
}
if (!is_close_qform_converted)
{
std::cerr << LPSLabel_nosform_direction << " \n\n" << reread_direction << std::endl;
return EXIT_FAILURE;
}
return EXIT_SUCCESS;
}

0 comments on commit 8e2e528

Please sign in to comment.