/*========================================================================= * * 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 "itkHDF5TransformIOFactory.h" #include "itkHDF5TransformIO.h" #include "itkTransformFileWriter.h" #include "itkTransformFileReader.h" #include "itkAffineTransform.h" #include "itkTransformFactory.h" #include "itkSimilarity2DTransform.h" #include "itkBSplineTransform.h" #include "itkTestingMacros.h" #include "itksys/SystemTools.hxx" #include // Transforms from Filtering/DisplacementField/include #include "itkBSplineExponentialDiffeomorphicTransform.h" #include "itkBSplineSmoothingOnUpdateDisplacementFieldTransform.h" #include "itkConstantVelocityFieldTransform.h" #include "itkDisplacementFieldTransform.h" #include "itkGaussianExponentialDiffeomorphicTransform.h" #include "itkGaussianSmoothingOnUpdateDisplacementFieldTransform.h" #include "itkMath.h" template static int ReadWriteTest(const std::string fileName, const bool isRealDisplacementField, const bool useCompression) { // First make a DisplacementField with known values const double aNumberThatCanNotBeRepresentedInFloatingPoint = 1e-5 + 1e-7 + 1e-9 + 1e-13; const double requiredSpacing = 1.2 + aNumberThatCanNotBeRepresentedInFloatingPoint; const double requiredOrigin = 23.0 + aNumberThatCanNotBeRepresentedInFloatingPoint; auto displacementTransform = DisplacementTransformType::New(); using FieldType = typename DisplacementTransformType::DisplacementFieldType; auto knownField = FieldType::New(); // This is based on itk::Image { constexpr int dimLength = 20; typename FieldType::SizeType size; size.Fill(dimLength); typename FieldType::IndexType start; start.Fill(0); typename FieldType::RegionType region; region.SetSize(size); region.SetIndex(start); knownField->SetRegions(region); typename FieldType::SpacingType spacing; spacing.Fill(requiredSpacing); knownField->SetSpacing(spacing); typename FieldType::PointType origin; origin.Fill(requiredOrigin); knownField->SetOrigin(origin); knownField->Allocate(); typename DisplacementTransformType::OutputVectorType zeroVector; zeroVector.Fill(aNumberThatCanNotBeRepresentedInFloatingPoint); knownField->FillBuffer(zeroVector); displacementTransform->SetDisplacementField(knownField); } // Now test reading/writing many different transform types. using TransformReaderType = itk::TransformFileReaderTemplate; auto reader = TransformReaderType::New(); ITK_EXERCISE_BASIC_OBJECT_METHODS(reader, TransformFileReaderTemplate, LightProcessObject); reader->SetFileName(fileName); ITK_TEST_SET_GET_VALUE(fileName, reader->GetFileName()); using TransformIOType = itk::HDF5TransformIOTemplate; auto transformIO = TransformIOType::New(); reader->SetTransformIO(transformIO); ITK_TEST_SET_GET_VALUE(transformIO, reader->GetTransformIO()); using TransformWriterType = itk::TransformFileWriterTemplate; auto writer = TransformWriterType::New(); ITK_EXERCISE_BASIC_OBJECT_METHODS(writer, TransformFileWriterTemplate, LightProcessObject); writer->SetFileName(fileName); ITK_TEST_SET_GET_VALUE(fileName, writer->GetFileName()); writer->SetTransformIO(transformIO); ITK_TEST_SET_GET_VALUE(transformIO, writer->GetTransformIO()); ITK_TEST_SET_GET_BOOLEAN(writer, UseCompression, useCompression); try { writer->AddTransform(displacementTransform); writer->Update(); } catch (const itk::ExceptionObject & excp) { std::cerr << "Error while saving the transforms" << std::endl; std::cerr << excp << std::endl; std::cout << "[FAILED]" << std::endl; return EXIT_FAILURE; } try { reader->Update(); } catch (const itk::ExceptionObject & excp) { std::cerr << "Error while reading the transforms" << std::endl; std::cerr << excp << std::endl; std::cout << "[FAILED]" << std::endl; return EXIT_FAILURE; } if (isRealDisplacementField) { // New verify that the transform was read properly from disk without loss // Read first transform const typename itk::TransformFileReaderTemplate::TransformListType * list = reader->GetTransformList(); typename DisplacementTransformType::ConstPointer readDisplacementTransform = static_cast((*(list->begin())).GetPointer()); if (readDisplacementTransform.IsNull()) { std::cerr << " ERROR: Read DisplacementTransform is null! " << std::endl; std::cerr << typeid(TParametersValueType).name() << std::endl; std::cerr << typeid(DisplacementTransformType).name() << std::endl; return EXIT_FAILURE; } typename DisplacementTransformType::DisplacementFieldType::ConstPointer readDisplacement = readDisplacementTransform->GetDisplacementField(); if (readDisplacement.IsNull()) { std::cerr << " ERROR: GetDisplacementField failed! " << std::endl; std::cerr << typeid(TParametersValueType).name() << std::endl; std::cerr << typeid(DisplacementTransformType).name() << std::endl; std::cerr << "\n\n\n" << std::endl; (*list->begin())->Print(std::cerr); return EXIT_FAILURE; } if ((readDisplacement->GetSpacing() != knownField->GetSpacing())) // || ( readDisplacement->GetSpacing()[0] != requiredSpacing ) ) { std::cerr << "Error invalid spacing restored from disk" << std::endl; std::cerr << std::setprecision(17) << '\n' << readDisplacement->GetSpacing() << " != " << knownField->GetSpacing() << '\n' << requiredSpacing << "It is likely going trough a float truncation " << static_cast(requiredSpacing) << std::endl; return EXIT_FAILURE; } if ((readDisplacement->GetOrigin() != knownField->GetOrigin()) || (itk::Math::NotExactlyEquals(readDisplacement->GetOrigin()[0], requiredOrigin))) { std::cerr << "Error invalid origin restored from disk" << std::endl; std::cerr << std::setprecision(17) << '\n' << readDisplacement->GetOrigin() << " != " << knownField->GetOrigin() << '\n' << requiredOrigin << std::endl; return EXIT_FAILURE; } } return EXIT_SUCCESS; } template static int oneTest(const std::string goodname, const std::string badname, const bool useCompression) { using AffineTransformType = typename itk::AffineTransform; using AffineTransformTypeNotRegistered = typename itk::AffineTransform; auto affine = AffineTransformType::New(); // Set its parameters { typename AffineTransformType::ParametersType p = affine->GetParameters(); for (unsigned int i = 0; i < p.GetSize(); ++i) { p[i] = i; } affine->SetParameters(p); } { typename AffineTransformType::FixedParametersType p = affine->GetFixedParameters(); for (unsigned int i = 0; i < p.GetSize(); ++i) { p[i] = i; } affine->SetFixedParameters(p); } typename itk::TransformFileWriterTemplate::Pointer writer = itk::TransformFileWriterTemplate::New(); writer->SetUseCompression(useCompression); typename itk::TransformFileReaderTemplate::Pointer reader = itk::TransformFileReaderTemplate::New(); writer->AddTransform(affine); writer->SetFileName(goodname); reader->SetFileName(goodname); // Testing writing std::cout << "Testing write : "; affine->Print(std::cout); try { writer->Update(); std::cout << std::endl; std::cout << "Testing read : " << std::endl; reader->Update(); } catch (const itk::ExceptionObject & excp) { std::cerr << "Error while saving the transforms" << std::endl; std::cerr << excp << std::endl; std::cout << "[FAILED]" << std::endl; return EXIT_FAILURE; } try { const typename itk::TransformFileReaderTemplate::TransformListType * list = reader->GetTransformList(); auto lit = list->begin(); while (lit != list->end()) { (*lit)->Print(std::cout); ++lit; } } catch (const itk::ExceptionObject & excp) { std::cerr << "Error while saving the transforms" << std::endl; std::cerr << excp << std::endl; std::cout << "[FAILED]" << std::endl; return EXIT_FAILURE; } std::cout << "Creating bad writer" << std::endl; auto Bogus = AffineTransformTypeNotRegistered::New(); // Set its parameters { typename AffineTransformType::ParametersType p = Bogus->GetParameters(); for (unsigned int i = 0; i < p.GetSize(); ++i) { p[i] = i; } Bogus->SetParameters(p); } { typename AffineTransformType::FixedParametersType p = Bogus->GetFixedParameters(); for (unsigned int i = 0; i < p.GetSize(); ++i) { p[i] = i; } Bogus->SetFixedParameters(p); } typename itk::TransformFileWriterTemplate::Pointer badwriter = itk::TransformFileWriterTemplate::New(); badwriter->SetUseCompression(useCompression); typename itk::TransformFileReaderTemplate::Pointer badreader = itk::TransformFileReaderTemplate::New(); badwriter->AddTransform(Bogus); badwriter->SetFileName(badname); badreader->SetFileName(badname); // Testing writing std::cout << "Testing write of non register transform : " << std::endl; std::cout << std::flush; try { badwriter->Update(); } catch (const itk::ExceptionObject & excp) { std::cerr << "Error while saving the transforms" << std::endl; std::cerr << excp << std::endl; std::cout << "[FAILED]" << std::endl; return EXIT_FAILURE; } // Testing writing std::cout << "Testing read of non register transform : " << std::endl; std::cout << std::flush; bool caught = false; try { badreader->Update(); } catch (const itk::ExceptionObject & excp) { caught = true; std::cout << "Caught exception as expected" << std::endl; std::cout << excp << std::endl; } if (!caught) { std::cerr << "Did not catch non registered transform" << std::endl; std::cout << "[FAILED]" << std::endl; return EXIT_FAILURE; } int error_sum = 0; error_sum += ReadWriteTest>( "f" + goodname, true, useCompression); error_sum += ReadWriteTest>( "f" + goodname, true, useCompression); error_sum += ReadWriteTest>( goodname, true, useCompression); error_sum += ReadWriteTest>( goodname, true, useCompression); error_sum += ReadWriteTest>("f" + goodname, true, useCompression); error_sum += ReadWriteTest>("f" + goodname, true, useCompression); error_sum += ReadWriteTest>(goodname, true, useCompression); error_sum += ReadWriteTest>(goodname, true, useCompression); error_sum += ReadWriteTest>( "f" + goodname, true, useCompression); error_sum += ReadWriteTest>( "f" + goodname, true, useCompression); error_sum += ReadWriteTest>( goodname, true, useCompression); error_sum += ReadWriteTest>( goodname, true, useCompression); error_sum += ReadWriteTest>( "f" + goodname, true, useCompression); error_sum += ReadWriteTest>( "f" + goodname, true, useCompression); error_sum += ReadWriteTest>( goodname, true, useCompression); error_sum += ReadWriteTest>( goodname, true, useCompression); error_sum += ReadWriteTest>("f" + goodname, false, useCompression); error_sum += ReadWriteTest>("f" + goodname, false, useCompression); error_sum += ReadWriteTest>(goodname, false, useCompression); error_sum += ReadWriteTest>(goodname, false, useCompression); error_sum += ReadWriteTest>( "f" + goodname, false, useCompression); error_sum += ReadWriteTest>( "f" + goodname, false, useCompression); error_sum += ReadWriteTest>(goodname, false, useCompression); error_sum += ReadWriteTest>(goodname, false, useCompression); if (error_sum > 0) { std::cerr << "Atleast 1 transform type could not be read/written " << error_sum << std::endl; std::cout << "[FAILED]" << std::endl; return EXIT_FAILURE; } std::cout << "[PASSED]" << std::endl; return EXIT_SUCCESS; } int itkIOTransformHDF5Test(int argc, char * argv[]) { itk::ObjectFactoryBase::RegisterFactory(itk::HDF5TransformIOFactory::New()); if (argc > 2) { itksys::SystemTools::ChangeDirectory(argv[2]); } if (argc > 1) { const std::string testType{ argv[1] }; if (testType == "uncompressed") { const int result1 = oneTest("Transforms_float.h5", "TransformsBad_float.h5", false); const int result2 = oneTest("Transforms_double.hdf5", "TransformsBad_double.hdf5", false); return (!(result1 == EXIT_SUCCESS && result2 == EXIT_SUCCESS)); } else if (testType == "compressed") { const int result1 = oneTest("Transforms_float_compressed.h5", "TransformsBad_float_compressed.h5", true); const int result2 = oneTest("Transforms_double_compressed.hdf5", "TransformsBad_double_compressed.hdf5", true); return (!(result1 == EXIT_SUCCESS && result2 == EXIT_SUCCESS)); } else if (itksys::SystemTools::FileExists(testType)) // Assume the final parameter is a filename to be read { // This test only verifies that the test can read the transform. using TFM_READER_TYPE = typename itk::TransformFileReaderTemplate; auto reader = TFM_READER_TYPE::New(); reader->SetFileName(testType); reader->Update(); TFM_READER_TYPE::TransformListType const * const myTransformList = reader->GetTransformList(); if (myTransformList->size() != 1) { return EXIT_FAILURE; } if (myTransformList->front()->GetTransformTypeAsString() != "VersorRigid3DTransform_double_3_3") { std::cerr << "Incorrect transform type identified " << myTransformList->front()->GetTransformTypeAsString() << " != VersorRigid3DTransform_double_3_3" << std::endl; return EXIT_FAILURE; } return EXIT_SUCCESS; } } std::cerr << "ERROR: first argument must be one of [uncompressed|compressed|]" << std::endl; return EXIT_FAILURE; }