From 52a57199b28c74ca12456b005613899f4fc6eaa5 Mon Sep 17 00:00:00 2001 From: Philip Cook Date: Wed, 17 Apr 2024 22:48:40 -0400 Subject: [PATCH] BUG: Set NIFTI header xyzt_units field correctly Previously, the correct xyzt code was assigned to the 'xyz_units' variable, but time gets masked out because it's only for space units, resulting in xyzt_units=2. Fix by setting 'time_units' to NIFTI_UNITS_SEC. This produces images with xyzt_units == 10, which is correct for ITK output (NIFTI_UNITS_MM | NIFTI_UNITS_SEC) --- Modules/IO/NIFTI/src/itkNiftiImageIO.cxx | 11 +- Modules/IO/NIFTI/test/CMakeLists.txt | 9 ++ .../IO/NIFTI/test/itkNiftiImageIOTest14.cxx | 109 ++++++++++++++++++ 3 files changed, 124 insertions(+), 5 deletions(-) create mode 100644 Modules/IO/NIFTI/test/itkNiftiImageIOTest14.cxx diff --git a/Modules/IO/NIFTI/src/itkNiftiImageIO.cxx b/Modules/IO/NIFTI/src/itkNiftiImageIO.cxx index 767916904e3..bea83b73278 100644 --- a/Modules/IO/NIFTI/src/itkNiftiImageIO.cxx +++ b/Modules/IO/NIFTI/src/itkNiftiImageIO.cxx @@ -1503,13 +1503,14 @@ NiftiImageIO::WriteImageInformation() // dim[2] is required for 2-D volumes, // dim[3] for 3-D volumes, etc. this->m_NiftiImage->nvox = 1; - // Spacial dims in ITK are given in mm. + // Spatial dims in ITK are given in mm. // If 4D assume 4thD is in SECONDS, for all of ITK. // NOTE: Due to an ambiguity in the nifti specification, some developers // external tools believe that the time units must be set, even if there - // is only one dataset. Having the time specified for a purly spatial + // is only one dataset. Having the time specified for a purely spatial // image has no consequence, so go ahead and set it to seconds. - this->m_NiftiImage->xyz_units = static_cast(NIFTI_UNITS_MM | NIFTI_UNITS_SEC); + this->m_NiftiImage->xyz_units = static_cast(NIFTI_UNITS_MM); + this->m_NiftiImage->time_units = static_cast(NIFTI_UNITS_SEC); this->m_NiftiImage->dim[7] = this->m_NiftiImage->nw = 1; this->m_NiftiImage->dim[6] = this->m_NiftiImage->nv = 1; this->m_NiftiImage->dim[5] = this->m_NiftiImage->nu = 1; @@ -2364,7 +2365,7 @@ NiftiImageIO::Write(const void * buffer) // so will destructor of the image that really owns it. if (nifti_write_status) { - itkExceptionMacro("ERROR: nifti library failed to write image" << this->GetFileName()); + itkExceptionMacro("ERROR: nifti library failed to write image: " << this->GetFileName()); } } else /// Image intent is vector image @@ -2469,7 +2470,7 @@ NiftiImageIO::Write(const void * buffer) this->m_NiftiImage->data = nullptr; // if left pointing to data buffer if (nifti_write_status) { - itkExceptionMacro("ERROR: nifti library failed to write image" << this->GetFileName()); + itkExceptionMacro("ERROR: nifti library failed to write image: " << this->GetFileName()); } } } diff --git a/Modules/IO/NIFTI/test/CMakeLists.txt b/Modules/IO/NIFTI/test/CMakeLists.txt index fc5f7a57c27..123992d97ff 100644 --- a/Modules/IO/NIFTI/test/CMakeLists.txt +++ b/Modules/IO/NIFTI/test/CMakeLists.txt @@ -13,6 +13,7 @@ set(ITKIONIFTITests itkNiftiImageIOTest11.cxx itkNiftiImageIOTest12.cxx itkNiftiImageIOTest13.cxx + itkNiftiImageIOTest14.cxx itkNiftiLargeImageRegionReadTest.cxx itkNiftiReadAnalyzeTest.cxx itkNiftiReadWriteDirectionTest.cxx @@ -245,6 +246,14 @@ itk_add_test( itkNiftiImageIOTest13 DATA{Input/SmallVoxels_AffinePrecision.nii.gz}) +itk_add_test( + NAME + itkNiftiSpatialTemporalUnitsTest + COMMAND + ITKIONIFTITestDriver + itkNiftiImageIOTest14 + ${ITK_TEST_OUTPUT_DIR}) + itk_add_test( NAME itkNiftiQFormSFormDifferentSpacingTest diff --git a/Modules/IO/NIFTI/test/itkNiftiImageIOTest14.cxx b/Modules/IO/NIFTI/test/itkNiftiImageIOTest14.cxx new file mode 100644 index 00000000000..e3bb84b6fbb --- /dev/null +++ b/Modules/IO/NIFTI/test/itkNiftiImageIOTest14.cxx @@ -0,0 +1,109 @@ +/*========================================================================= + * + * 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 + * + * https://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 "itkNiftiImageIOTest.h" +#include "itkTestingMacros.h" + +// Test writing NIFTI xyzt_units field - should be 10 (mm, sec) +int +itkNiftiImageIOTest14(int argc, char * argv[]) +{ + + if (argc > 1) + { + char * testdir = argv[1]; + itksys::SystemTools::ChangeDirectory(testdir); + } + else + { + return EXIT_FAILURE; + } + + constexpr unsigned int Dimension = 3; + + using PixelType = float; + using ImageType = itk::Image; + + ImageType::Pointer image; + + // fill out an image and write it as nifti. + const char * filename = "xyzt_unit_check.nii"; + using OutputImageType = itk::Image; + typename OutputImageType::RegionType region; + typename OutputImageType::IndexType start; + typename OutputImageType::SizeType size; + size[0] = 8; + size[1] = 8; + size[2] = 4; + region.SetSize(size); + region.SetIndex(start); + auto outputimage = OutputImageType::New(); + outputimage->SetRegions(region); + outputimage->Allocate(); + + auto writer = itk::ImageFileWriter::New(); + auto niftiImageIO = itk::NiftiImageIO::New(); + writer->SetImageIO(niftiImageIO); + writer->SetFileName(filename); + writer->SetInput(outputimage); + try + { + writer->Update(); + } + catch (const itk::ExceptionObject & err) + { + std::cerr << "Exception Object caught: " << std::endl << err << std::endl; + throw; + } + + bool fileHasCorrectXYZTTUnits = false; + + try + { + // read the image back in + ImageType::Pointer image_from_disk = itk::IOTestHelper::ReadImage(std::string(filename)); + // check the metadata for xyzt_units + itk::MetaDataDictionary & dictionary = image_from_disk->GetMetaDataDictionary(); + std::string xyzt_units; + if (!itk::ExposeMetaData(dictionary, "xyzt_units", xyzt_units)) + { + std::cerr << "xyzt_units not found in metadata" << std::endl; + } + if (xyzt_units == "10") + { + fileHasCorrectXYZTTUnits = true; + } + else + { + std::cerr << "xyzt_units not set correctly in metadata" << std::endl; + fileHasCorrectXYZTTUnits = false; + } + } + catch (...) + { + std::cerr << "Exception caught while reading image back in" << std::endl; + } + + itk::IOTestHelper::Remove(filename); + + if (fileHasCorrectXYZTTUnits) + { + return EXIT_SUCCESS; + } + return EXIT_FAILURE; +}