From 7fb08c12defab0069028c0b41107e6a821724481 Mon Sep 17 00:00:00 2001 From: Bradley Lowekamp Date: Wed, 10 Dec 2025 09:57:27 -0500 Subject: [PATCH] ENH: benchmark for iterators doing a static cast of pixel --- examples/Core/CMakeLists.txt | 11 + examples/Core/itkCopyIterationBenchmark.cxx | 296 ++++++++++++++++++++ 2 files changed, 307 insertions(+) create mode 100644 examples/Core/itkCopyIterationBenchmark.cxx diff --git a/examples/Core/CMakeLists.txt b/examples/Core/CMakeLists.txt index 811dfcf..a1aa37a 100644 --- a/examples/Core/CMakeLists.txt +++ b/examples/Core/CMakeLists.txt @@ -29,3 +29,14 @@ add_test( set_property(TEST VectorIterationBenchmark APPEND PROPERTY LABELS Core) ## performance tests should not be run in parallel set_tests_properties(VectorIterationBenchmark PROPERTIES RUN_SERIAL TRUE) + +add_executable(CopyIterationBenchmark itkCopyIterationBenchmark.cxx ) +target_link_libraries(CopyIterationBenchmark ${ITK_LIBRARIES}) +add_test( + NAME CopyIterationBenchmark + COMMAND CopyIterationBenchmark + ${BENCHMARK_RESULTS_OUTPUT_DIR}/__DATESTAMP__CopyIterationBenchmark.json + 25 128 ) +set_property(TEST CopyIterationBenchmark APPEND PROPERTY LABELS Core) +## performance tests should not be run in parallel +set_tests_properties(CopyIterationBenchmark PROPERTIES RUN_SERIAL TRUE) diff --git a/examples/Core/itkCopyIterationBenchmark.cxx b/examples/Core/itkCopyIterationBenchmark.cxx new file mode 100644 index 0000000..43db624 --- /dev/null +++ b/examples/Core/itkCopyIterationBenchmark.cxx @@ -0,0 +1,296 @@ +/*========================================================================= + * + * 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. + * + *=========================================================================*/ + +// This Benchmark compares the performance of ImageRegionIterator, ImageScanlineIterator, +// and ImageRegionRange for simple pixel copying with static_cast operations. + +#include +#include "itkImage.h" +#include "itkVectorImage.h" +#include "itkFixedArray.h" +#include "itkImageRegionRange.h" +#include "itkImageScanlineIterator.h" +#include "itkImageRegionIterator.h" +#include "itkImageAlgorithm.h" +#include "itkHighPriorityRealTimeProbesCollector.h" +#include "PerformanceBenchmarkingUtilities.h" +#include +#include + + +// Helper function to initialize an image with random values +template +typename TImage::Pointer +CreateAndInitializeImage(const typename TImage::SizeType & size, unsigned int numberOfComponentsPerPixel = 0) +{ + auto image = TImage::New(); + typename TImage::RegionType region{ size }; + image->SetRegions(region); + if (numberOfComponentsPerPixel > 0) + { + image->SetNumberOfComponentsPerPixel(numberOfComponentsPerPixel); + } + image->Allocate(); + + // Initialize with simple pattern (pixel index-based) + using PixelType = typename TImage::PixelType; + unsigned int count = 0; + + itk::ImageRegionIterator it(image, region); + for (; !it.IsAtEnd(); ++it) + { + it.Set(static_cast(count)); + ++count; + } + + return image; +} + + +// Method 0: ImageAlgorithm::Copy +template +void +CopyImageAlgorithm(const TInputImage * inputPtr, TOutputImage * outputPtr) +{ + itk::ImageAlgorithm::Copy(inputPtr, outputPtr, inputPtr->GetBufferedRegion(), outputPtr->GetBufferedRegion()); +} + +// Method 1: ImageRegionIterator approach +template +void +CopyRegionIterator(const TInputImage * inputPtr, TOutputImage * outputPtr) +{ + using InputPixelType = typename TInputImage::PixelType; + using OutputPixelType = typename TOutputImage::PixelType; + using ImageRegionConstIterator = itk::ImageRegionConstIterator; + using ImageRegionIterator = itk::ImageRegionIterator; + + const typename TOutputImage::RegionType outputRegion = outputPtr->GetRequestedRegion(); + typename TInputImage::RegionType inputRegion = outputRegion; + + ImageRegionConstIterator inputIt(inputPtr, inputRegion); + ImageRegionIterator outputIt(outputPtr, outputRegion); + + for (; !inputIt.IsAtEnd(); ++inputIt, ++outputIt) + { + outputIt.Set(static_cast(inputIt.Get())); + } +} + + +// Method 2: ImageScanlineIterator approach +template +void +CopyScanlineIterator(const TInputImage * inputPtr, TOutputImage * outputPtr) +{ + using InputPixelType = typename TInputImage::PixelType; + using OutputPixelType = typename TOutputImage::PixelType; + using ImageScanlineConstIterator = itk::ImageScanlineConstIterator; + using ImageScanlineIterator = itk::ImageScanlineIterator; + + const typename TOutputImage::RegionType outputRegion = outputPtr->GetRequestedRegion(); + typename TInputImage::RegionType inputRegion = outputRegion; + + ImageScanlineConstIterator inputIt(inputPtr, inputRegion); + ImageScanlineIterator outputIt(outputPtr, outputRegion); + + while (!inputIt.IsAtEnd()) + { + while (!inputIt.IsAtEndOfLine()) + { + outputIt.Set(static_cast(inputIt.Get())); + ++inputIt; + ++outputIt; + } + inputIt.NextLine(); + outputIt.NextLine(); + } +} + + +// Method 3: ImageRegionRange approach +template +void +CopyImageRegionRange(const TInputImage * inputPtr, TOutputImage * outputPtr) +{ + using InputPixelType = typename TInputImage::PixelType; + using OutputPixelType = typename TOutputImage::PixelType; + + const typename TOutputImage::RegionType outputRegion = outputPtr->GetRequestedRegion(); + typename TInputImage::RegionType inputRegion = outputRegion; + + auto inputRange = itk::ImageRegionRange(*inputPtr, inputRegion); + auto outputRange = itk::ImageRegionRange(*outputPtr, outputRegion); + + auto inputIt = inputRange.begin(); + auto outputIt = outputRange.begin(); + const auto inputEnd = inputRange.end(); + + while (inputIt != inputEnd) + { + *outputIt = OutputPixelType(InputPixelType(*inputIt)); + ++inputIt; + ++outputIt; + } +} + + +// Method 4: ImageRegionRange with range-based for loop +template +void +CopyImageRegionRangeForLoop(const TInputImage * inputPtr, TOutputImage * outputPtr) +{ + using InputPixelType = typename TInputImage::PixelType; + using OutputPixelType = typename TOutputImage::PixelType; + + const typename TOutputImage::RegionType outputRegion = outputPtr->GetRequestedRegion(); + typename TInputImage::RegionType inputRegion = outputRegion; + + auto outputRange = itk::ImageRegionRange(*outputPtr, outputRegion); + auto outputIt = outputRange.begin(); + + for (const InputPixelType & inputPixel : itk::ImageRegionRange(*inputPtr, inputRegion)) + { + *outputIt = static_cast(inputPixel); + ++outputIt; + } +} + + +// Helper function to time a single method +template +void +TimeMethod(itk::HighPriorityRealTimeProbesCollector & collector, + const std::string & methodName, + TCopyFunction copyFunc, + const TInputImage * inputImage, + typename TOutputImage::Pointer & outputImage, + int iterations) +{ + // Allocate output image + outputImage = TOutputImage::New(); + outputImage->SetRegions(inputImage->GetLargestPossibleRegion()); + if (inputImage->GetNumberOfComponentsPerPixel() > 0) + { + outputImage->SetNumberOfComponentsPerPixel(inputImage->GetNumberOfComponentsPerPixel()); + } + outputImage->Allocate(); + + // Warm-up run + copyFunc(inputImage, outputImage.GetPointer()); + + // Timed runs + for (int ii = 0; ii < iterations; ++ii) + { + collector.Start(methodName.c_str()); + copyFunc(inputImage, outputImage.GetPointer()); + collector.Stop(methodName.c_str()); + } +} + + +// Performance testing function +template +void +TimeIterationMethods(itk::HighPriorityRealTimeProbesCollector & collector, + const typename TInputImage::SizeType & size, + const std::string & description, + int iterations, + unsigned int numberOfComponentsPerPixel = 0) +{ + // Create and initialize input image + auto inputImage = CreateAndInitializeImage(size, numberOfComponentsPerPixel); + + typename TOutputImage::Pointer outputImage; + + // Test Method 0: ImageAlgorithm::Copy + TimeMethod(collector, + description + "-ImageAlgorithm", + CopyImageAlgorithm, + inputImage.GetPointer(), + outputImage, + iterations); + + // Test Method 1: Region Iterator + TimeMethod(collector, + description + "-RegionIterator", + CopyRegionIterator, + inputImage.GetPointer(), + outputImage, + iterations); + + // Test Method 2: Scanline Iterator + TimeMethod(collector, + description + "-ScanlineIterator", + CopyScanlineIterator, + inputImage.GetPointer(), + outputImage, + iterations); + + // Test Method 3: ImageRegionRange + TimeMethod(collector, + description + "-Range", + CopyImageRegionRange, + inputImage.GetPointer(), + outputImage, + iterations); + + // Test Method 4: ImageRegionRange with range-based for loop + TimeMethod(collector, + description + "-RangeForLoop", + CopyImageRegionRangeForLoop, + inputImage.GetPointer(), + outputImage, + iterations); +} + + +int +main(int argc, char * argv[]) +{ + if (argc < 4) + { + std::cerr << "Usage: " << std::endl; + std::cerr << argv[0] << " timingsFile iterations imageSize" << std::endl; + return EXIT_FAILURE; + } + const std::string timingsFileName = ReplaceOccurrence(argv[1], "__DATESTAMP__", PerfDateStamp()); + const int iterations = std::stoi(argv[2]); + const int imageSize = std::stoi(argv[3]); + + constexpr unsigned int Dimension = 3; + const auto size = itk::Size::Filled(imageSize); + + itk::HighPriorityRealTimeProbesCollector collector; + + // Test 1: uint16 to int16 + TimeIterationMethods, itk::Image>( + collector, size, "Iu2->Ii2", iterations); + + // Test 2: FixedArray to FixedArray + TimeIterationMethods, Dimension>, + itk::Image, Dimension>>(collector, size, "IFf3->IFd3", iterations); + + // Test 3: VectorImage to VectorImage with 3 components + TimeIterationMethods, itk::VectorImage>( + collector, size, "IVf->IVd", iterations, 3); + + WriteExpandedReport(timingsFileName, collector, true, true, false); + + return EXIT_SUCCESS; +}