From f9732088f9e81c7dc35f883e36c2df4a29f8a6c4 Mon Sep 17 00:00:00 2001 From: Roman Grothausmann Date: Thu, 24 Jan 2019 11:40:06 +0100 Subject: [PATCH] ENH: new tests to verify streaming capabilities of itkResampleImageFilter New tests are expected to fail as long as itkResampleFilter does not support streaming for linear transforms GH PR #82. Also, tests for comparing output with stream driven imaging (SDI) and without based on itkWarpImageFilterTest2.cxx and itkResampleImageTest.cxx. --- .../Baseline/ResampleImageTest2.mha.sha512 | 1 + ...pleImageTest2NearestExtrapolate.mha.sha512 | 1 + .../Filtering/ImageGrid/test/CMakeLists.txt | 14 ++ .../ImageGrid/test/Input/cthead1.mha.sha512 | 1 + .../test/itkResampleImageTest2Streaming.cxx | 209 ++++++++++++++++++ .../ImageGrid/test/itkResampleImageTest7.cxx | 190 ++++++++++++++++ 6 files changed, 416 insertions(+) create mode 100644 Modules/Filtering/ImageGrid/test/Baseline/ResampleImageTest2.mha.sha512 create mode 100644 Modules/Filtering/ImageGrid/test/Baseline/ResampleImageTest2NearestExtrapolate.mha.sha512 create mode 100644 Modules/Filtering/ImageGrid/test/Input/cthead1.mha.sha512 create mode 100644 Modules/Filtering/ImageGrid/test/itkResampleImageTest2Streaming.cxx create mode 100644 Modules/Filtering/ImageGrid/test/itkResampleImageTest7.cxx diff --git a/Modules/Filtering/ImageGrid/test/Baseline/ResampleImageTest2.mha.sha512 b/Modules/Filtering/ImageGrid/test/Baseline/ResampleImageTest2.mha.sha512 new file mode 100644 index 00000000000..3d8c0b91c51 --- /dev/null +++ b/Modules/Filtering/ImageGrid/test/Baseline/ResampleImageTest2.mha.sha512 @@ -0,0 +1 @@ +6dcda44ce36e39fb6a792e26c8cd93830bab8edbe9f33385dc59b24bd6b40c99bbadf9b6ada967b1d1b6624e986f1161960d86db63b2d6b71da58eec0a2b911c diff --git a/Modules/Filtering/ImageGrid/test/Baseline/ResampleImageTest2NearestExtrapolate.mha.sha512 b/Modules/Filtering/ImageGrid/test/Baseline/ResampleImageTest2NearestExtrapolate.mha.sha512 new file mode 100644 index 00000000000..b4c87795633 --- /dev/null +++ b/Modules/Filtering/ImageGrid/test/Baseline/ResampleImageTest2NearestExtrapolate.mha.sha512 @@ -0,0 +1 @@ +bbc1a32a82e88334df11c79f54ba4260ea291f8316c882d6360e7546739eb92109a169cc07abe32250a4e12e49a4bb8bad22c45580ccb78c4ee482fa68675e4f diff --git a/Modules/Filtering/ImageGrid/test/CMakeLists.txt b/Modules/Filtering/ImageGrid/test/CMakeLists.txt index 52b0327745b..a0c39ea3d41 100644 --- a/Modules/Filtering/ImageGrid/test/CMakeLists.txt +++ b/Modules/Filtering/ImageGrid/test/CMakeLists.txt @@ -46,10 +46,12 @@ itkMirrorPadImageTest.cxx itkMirrorPadImageFilterTest.cxx itkResampleImageTest.cxx itkResampleImageTest2.cxx +itkResampleImageTest2Streaming.cxx itkResampleImageTest3.cxx itkResampleImageTest4.cxx itkResampleImageTest5.cxx itkResampleImageTest6.cxx +itkResampleImageTest7.cxx itkResamplePhasedArray3DSpecialCoordinatesImageTest.cxx itkPushPopTileImageFilterTest.cxx itkShrinkImageStreamingTest.cxx @@ -266,6 +268,15 @@ itk_add_test(NAME itkResampleImageTest2UseRefImageOn ${ITK_TEST_OUTPUT_DIR}/ResampleImageTest2cUseRefImageOn.png ${ITK_TEST_OUTPUT_DIR}/ResampleImageTest2dUseRefImageOn.png 1) +itk_add_test(NAME itkResampleImageTest2Streaming + COMMAND ITKImageGridTestDriver + itkResampleImageTest2Streaming DATA{Input/cthead1.mha} + DATA{Input/cthead1.mha} + ${ITK_TEST_OUTPUT_DIR}/ResampleImageTest2aStreaming.mha + ${ITK_TEST_OUTPUT_DIR}/ResampleImageTest2bStreaming.mha + ${ITK_TEST_OUTPUT_DIR}/ResampleImageTest2cStreaming.mha + ${ITK_TEST_OUTPUT_DIR}/ResampleImageTest2dStreaming.mha) +set_tests_properties(itkResampleImageTest2Streaming PROPERTIES WILL_FAIL TRUE) # should fail as long as itkResampleFilter does not support streaming for linear transforms GH PR #82 itk_add_test(NAME itkResampleImageTest3 COMMAND ITKImageGridTestDriver --compare DATA{Baseline/ResampleImageTest3.png} @@ -284,6 +295,9 @@ itk_add_test(NAME itkResampleImageTest6 --compare DATA{Baseline/ResampleImageTest6.png} ${ITK_TEST_OUTPUT_DIR}/ResampleImageTest6.png itkResampleImageTest6 10 ${ITK_TEST_OUTPUT_DIR}/ResampleImageTest6.png) +itk_add_test(NAME itkResampleImageTest7 + COMMAND ITKImageGridTestDriver itkResampleImageTest7) +set_tests_properties(itkResampleImageTest7 PROPERTIES WILL_FAIL TRUE) # should fail as long as itkResampleFilter does not support streaming for linear transforms GH PR #82 itk_add_test(NAME itkResamplePhasedArray3DSpecialCoordinatesImageTest COMMAND ITKImageGridTestDriver itkResamplePhasedArray3DSpecialCoordinatesImageTest) itk_add_test(NAME itkPushPopTileImageFilterTest diff --git a/Modules/Filtering/ImageGrid/test/Input/cthead1.mha.sha512 b/Modules/Filtering/ImageGrid/test/Input/cthead1.mha.sha512 new file mode 100644 index 00000000000..1cfc6d8d0da --- /dev/null +++ b/Modules/Filtering/ImageGrid/test/Input/cthead1.mha.sha512 @@ -0,0 +1 @@ +bca480e13f66b2b85a3513c8c8ca6b38b3d9df7a24ab9a35654858173001f29323471f27632dc42a5714dc5c7d4dbcb1fbf5ba0b9699fe7bd73080cdcd262270 diff --git a/Modules/Filtering/ImageGrid/test/itkResampleImageTest2Streaming.cxx b/Modules/Filtering/ImageGrid/test/itkResampleImageTest2Streaming.cxx new file mode 100644 index 00000000000..9e313f51fe3 --- /dev/null +++ b/Modules/Filtering/ImageGrid/test/itkResampleImageTest2Streaming.cxx @@ -0,0 +1,209 @@ +/*========================================================================= + * + * Copyright Insight Software Consortium + * + * 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 + +#include "itkAffineTransform.h" +#include "itkImageFileReader.h" +#include "itkImageFileWriter.h" +#include "itkResampleImageFilter.h" +#include "itkNearestNeighborExtrapolateImageFunction.h" +#include "itkPipelineMonitorImageFilter.h" +#include "itkTestingMacros.h" + +/* Further testing of itkResampleImageFilter + * Output is compared with baseline image using the cmake itk_add_test + * '--compare' option. + */ + +namespace { + +template +class NonlinearAffineTransform: + public itk::AffineTransform +{ +public: + /** Standard class type aliases. */ + using Self = NonlinearAffineTransform; + using Superclass = itk::AffineTransform< TCoordRepType, NDimensions >; + using Pointer = itk::SmartPointer< Self >; + using ConstPointer = itk::SmartPointer< const Self >; + + /** New macro for creation of through a smart pointer. */ + itkSimpleNewMacro(Self); + + /** Run-time type information (and related methods). */ + itkTypeMacro(NonlinearAffineTransform, AffineTransform); + + /** Override this. See test below. */ + bool IsLinear() const override { return false; } +}; +} + +int itkResampleImageTest2Streaming( int argc, char * argv [] ) +{ + if( argc < 7 ) + { + std::cerr << "Missing arguments ! " << std::endl; + std::cerr << "Usage : " << std::endl; + std::cerr << argv[0] << "inputImage referenceImage " + << "resampledImageLinear resampledImageNonLinear " + << "resampledImageLinearNearestExtrapolate" + << "resampledImageNonLinearNearestExtrapolate"; + std::cerr << std::endl; + return EXIT_FAILURE; + } + + constexpr unsigned int NDimensions = 2; + + using PixelType = unsigned char; + using ImageType = itk::Image; + using CoordRepType = double; + + using AffineTransformType = itk::AffineTransform; + using NonlinearAffineTransformType = NonlinearAffineTransform; + using InterpolatorType = itk::LinearInterpolateImageFunction; + using ExtrapolatorType = itk::NearestNeighborExtrapolateImageFunction; + + using ReaderType = itk::ImageFileReader< ImageType >; + using WriterType = itk::ImageFileWriter< ImageType >; + + ReaderType::Pointer reader1 = ReaderType::New(); + ReaderType::Pointer reader2 = ReaderType::New(); + + WriterType::Pointer writer1 = WriterType::New(); + WriterType::Pointer writer2 = WriterType::New(); + WriterType::Pointer writer3 = WriterType::New(); + WriterType::Pointer writer4 = WriterType::New(); + + reader1->SetFileName( argv[1] ); + reader2->SetFileName( argv[2] ); + + writer1->SetFileName( argv[3] ); + writer2->SetFileName( argv[4] ); + writer3->SetFileName( argv[5] ); + writer4->SetFileName( argv[6] ); + + // Create an affine transformation + AffineTransformType::Pointer affineTransform = AffineTransformType::New(); + affineTransform->Scale(2.0); + + // Create a linear interpolation image function + InterpolatorType::Pointer interpolator = InterpolatorType::New(); + + // Create a nearest neighbor extrapolate image function + ExtrapolatorType::Pointer extrapolator = ExtrapolatorType::New(); + + // Create and configure a resampling filter + using ResampleFilterType = itk::ResampleImageFilter< ImageType, ImageType >; + ResampleFilterType::Pointer resample = ResampleFilterType::New(); + + using MonitorFilterType = itk::PipelineMonitorImageFilter< ImageType >; + MonitorFilterType::Pointer monitor = MonitorFilterType::New(); + + EXERCISE_BASIC_OBJECT_METHODS( resample, ResampleImageFilter, ImageToImageFilter ); + + monitor->SetInput( reader1->GetOutput() ); + resample->SetInput( monitor->GetOutput() ); + TEST_SET_GET_VALUE( reader1->GetOutput(), monitor->GetInput() ); + + resample->SetReferenceImage( reader2->GetOutput() ); + TEST_SET_GET_VALUE( reader2->GetOutput(), resample->GetReferenceImage() ); + + resample->UseReferenceImageOn(); + TEST_EXPECT_TRUE( resample->GetUseReferenceImage() ); + + resample->SetTransform( affineTransform ); + TEST_SET_GET_VALUE( affineTransform, resample->GetTransform() ); + + resample->SetInterpolator( interpolator ); + TEST_SET_GET_VALUE( interpolator, resample->GetInterpolator() ); + + resample->SetInput( monitor->GetOutput() ); + writer1->SetInput( resample->GetOutput() ); + + // Check GetReferenceImage + if( resample->GetReferenceImage() != reader2->GetOutput() ) + { + std::cerr << "GetReferenceImage() failed ! " << std::endl; + std::cerr << "Test failed." << std::endl; + return EXIT_FAILURE; + } + + // Run the resampling filter with the normal, linear, affine transform. + // This will use ResampleImageFilter::LinearThreadedGenerateData(). + std::cout << "Test with normal AffineTransform." << std::endl; + writer1->SetNumberOfStreamDivisions(8); //split into 8 pieces for streaming. + TRY_EXPECT_NO_EXCEPTION( writer1->Update() ); + + // this verifies that the pipeline was executed as expected along + // with correct region propagation and output information + if (!monitor->VerifyAllInputCanStream(8)) + { + std::cerr << "Streaming failed to execute as expected!" << std::endl; + std::cerr << monitor; + std::cerr << "Test failed." << std::endl; + return EXIT_FAILURE; + } + + // Assign an affine transform that returns + // false for IsLinear() instead of true, to force + // the filter to use the NonlinearThreadedGenerateData method + // instead of LinearThreadedGenerateData. This will test that + // we get the same results for both methods. + std::cout << "Test with NonlinearAffineTransform." << std::endl; + NonlinearAffineTransformType::Pointer nonlinearAffineTransform = + NonlinearAffineTransformType::New(); + + nonlinearAffineTransform->Scale(2.0); + resample->SetTransform( nonlinearAffineTransform ); + writer2->SetInput( resample->GetOutput() ); + monitor->ClearPipelineSavedInformation(); + writer2->SetNumberOfStreamDivisions(8); //demand splitting into 8 pieces for streaming, but faked non-linearity will disable streaming + TRY_EXPECT_NO_EXCEPTION( writer2->Update() ); + + // check that streaming is not possible for non-linear case + if (!monitor->VerifyAllInputCanStream(8)) + { + std::cerr << "Streaming succedded for non-linear transform which should not be the case!" << std::endl; + std::cerr << monitor; + std::cerr << "Test failed." << std::endl; + return EXIT_FAILURE; + } + + // Instead of using the default pixel when sampling outside the input image, + // we use a nearest neighbor extrapolator. + resample->SetTransform( affineTransform ); + resample->SetExtrapolator( extrapolator ); + writer3->SetInput( resample->GetOutput() ); + std::cout << "Test with nearest neighbor extrapolator, affine transform." << std::endl; + writer3->SetNumberOfStreamDivisions(8); //split into 8 pieces for streaming. + TRY_EXPECT_NO_EXCEPTION( writer3->Update() ); + + // Instead of using the default pixel when sampling outside the input image, + // we use a nearest neighbor extrapolator. + resample->SetTransform( nonlinearAffineTransform ); + writer4->SetInput( resample->GetOutput() ); + std::cout << "Test with nearest neighbor extrapolator, nonlinear transform." << std::endl; + writer4->SetNumberOfStreamDivisions(8); //demand splitting into 8 pieces for streaming, but faked non-linearity will disable streaming + TRY_EXPECT_NO_EXCEPTION( writer4->Update() ); + + std::cout << "Test passed." << std::endl; + return EXIT_SUCCESS; + +} diff --git a/Modules/Filtering/ImageGrid/test/itkResampleImageTest7.cxx b/Modules/Filtering/ImageGrid/test/itkResampleImageTest7.cxx new file mode 100644 index 00000000000..b0fdd26e7e2 --- /dev/null +++ b/Modules/Filtering/ImageGrid/test/itkResampleImageTest7.cxx @@ -0,0 +1,190 @@ +/*========================================================================= + * + * Copyright Insight Software Consortium + * + * 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 + +#include "itkAffineTransform.h" +#include "itkResampleImageFilter.h" +#include "itkPipelineMonitorImageFilter.h" +#include "itkStreamingImageFilter.h" +#include "itkTestingMacros.h" +#include "itkMath.h" + +/* itkResampleImageFilter output compared to streamed output + */ + +int itkResampleImageTest7( int , char *[] ) +{ + + constexpr unsigned int NDimensions = 2; + + using PixelType = float; + + using ImageType = itk::Image; + using ImageIndexType = ImageType::IndexType; + using ImagePointerType = ImageType::Pointer; + using ImageRegionType = ImageType::RegionType; + using ImageSizeType = ImageType::SizeType; + + using CoordRepType = double; + + using AffineTransformType = itk::AffineTransform; + + using InterpolatorType = itk::LinearInterpolateImageFunction; + + // Create and configure an image + ImagePointerType image = ImageType::New(); + ImageIndexType index = {{0, 0}}; + ImageSizeType size = {{64,64}}; + ImageRegionType region; + region.SetSize ( size ); + region.SetIndex( index ); + image->SetLargestPossibleRegion( region ); + image->SetBufferedRegion( region ); + image->Allocate(); + + // Fill image with a ramp + itk::ImageRegionIteratorWithIndex iter(image, region); + PixelType value; + for (iter.GoToBegin(); !iter.IsAtEnd(); ++iter) + { + index = iter.GetIndex(); + value = index[0] + index[1]; + iter.Set(value); + } + + // Create an affine transformation + AffineTransformType::Pointer aff = AffineTransformType::New(); + aff->Scale(0.9); + + // Create a linear interpolation image function + InterpolatorType::Pointer interp = InterpolatorType::New(); + interp->SetInputImage(image); + + // Create and configure a resampling filter + itk::ResampleImageFilter< ImageType, ImageType >::Pointer resample = + itk::ResampleImageFilter< ImageType, ImageType >::New(); + + EXERCISE_BASIC_OBJECT_METHODS( resample, ResampleImageFilter, ImageToImageFilter ); + + using MonitorFilter = itk::PipelineMonitorImageFilter; + MonitorFilter::Pointer monitor = MonitorFilter::New(); + monitor->SetInput( image ); + + resample->SetInterpolator(interp); + + resample->SetInput( monitor->GetOutput() ); + TEST_SET_GET_VALUE( monitor->GetOutput(), resample->GetInput() ); + + resample->SetSize( size ); + TEST_SET_GET_VALUE( size, resample->GetSize() ); + + resample->SetTransform(aff); + TEST_SET_GET_VALUE( aff, resample->GetTransform() ); + + resample->SetInterpolator(interp); + TEST_SET_GET_VALUE( interp, resample->GetInterpolator() ); + + index.Fill( 0 ); + resample->SetOutputStartIndex( index ); + TEST_SET_GET_VALUE( index, resample->GetOutputStartIndex() ); + + ImageType::PointType origin; + origin.Fill( 0.0 ); + resample->SetOutputOrigin( origin ); + TEST_SET_GET_VALUE( origin, resample->GetOutputOrigin() ); + + ImageType::SpacingType spacing; + spacing.Fill( 1.0 ); + resample->SetOutputSpacing( spacing ); + TEST_SET_GET_VALUE( spacing, resample->GetOutputSpacing() ); + + using StreamerType = itk::StreamingImageFilter; + StreamerType::Pointer streamer = StreamerType::New(); + + std::cout << "Test with normal AffineTransform." << std::endl; + streamer->SetInput( resample->GetOutput() ); + + unsigned char numStreamDiv; + + // Run the resampling filter without streaming, i.e. 1 StreamDivisions + numStreamDiv= 1; // do not split, i.e. do not stream + streamer->SetNumberOfStreamDivisions(numStreamDiv); + TRY_EXPECT_NO_EXCEPTION( streamer->Update() ); + + if (!monitor->VerifyAllInputCanStream(numStreamDiv)) + { + std::cerr << "Avoiding streaming failed to execute as expected!" << std::endl; + std::cerr << monitor; + std::cerr << "Test failed." << std::endl; + return EXIT_FAILURE; + } + monitor->ClearPipelineSavedInformation(); + + ImagePointerType outputNoSDI= streamer->GetOutput(); // save output for later comparison + outputNoSDI->DisconnectPipeline(); // disconnect to create new output + + // Run the resampling filter with streaming + numStreamDiv= 8; // split into numStream pieces for streaming. + image->Modified(); // enforce re-execution even though nothing of filter changed + streamer->SetNumberOfStreamDivisions(numStreamDiv); + TRY_EXPECT_NO_EXCEPTION( streamer->Update() ); + + if (!monitor->VerifyAllInputCanStream(numStreamDiv)) + { + std::cerr << "Streaming failed to execute as expected!" << std::endl; + std::cerr << monitor; + std::cerr << "Test failed." << std::endl; + return EXIT_FAILURE; + } + monitor->ClearPipelineSavedInformation(); + + ImagePointerType outputSDI= streamer->GetOutput(); + outputSDI->DisconnectPipeline(); + + itk::ImageRegionIterator + itNoSDI(outputNoSDI, outputNoSDI->GetLargestPossibleRegion()), + itSDI(outputSDI, outputSDI->GetLargestPossibleRegion()); + for(itNoSDI.GoToBegin(), itSDI.GoToBegin(); + !itNoSDI.IsAtEnd() && !itSDI.IsAtEnd(); + ++itNoSDI, ++itSDI) + { + if(itk::Math::NotAlmostEquals( itNoSDI.Value(), itSDI.Value() )) + { + std::cout << "Pixels differ " + << itNoSDI.Value() << " " + << itSDI.Value() << std::endl; + std::cerr << "Test failed!" << std::endl; + std::cerr << "Error in pixel value at index [" << itNoSDI.GetIndex() << "]" << std::endl; + std::cerr << "Expected difference " << itNoSDI.Get() - itSDI.Get() << std::endl; + std::cerr << " differs from 0 "; + return EXIT_FAILURE; + } + } + if(itNoSDI.IsAtEnd() != itSDI.IsAtEnd()) + { + std::cerr << "Test failed!" << std::endl; + std::cerr << "Iterators don't agree on end of image" << std::endl; + std::cerr << "at index [" << itNoSDI.GetIndex() << "]" << std::endl; + return EXIT_FAILURE; + } + + std::cout << "Test passed." << std::endl; + return EXIT_SUCCESS; + +}