Skip to content

Commit 349f412

Browse files
committed
ENH: benchmark for iterators doing a static cast of pixel
1 parent 7817cf6 commit 349f412

File tree

2 files changed

+290
-0
lines changed

2 files changed

+290
-0
lines changed

examples/Core/CMakeLists.txt

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,3 +29,14 @@ add_test(
2929
set_property(TEST VectorIterationBenchmark APPEND PROPERTY LABELS Core)
3030
## performance tests should not be run in parallel
3131
set_tests_properties(VectorIterationBenchmark PROPERTIES RUN_SERIAL TRUE)
32+
33+
add_executable(CopyIterationBenchmark itkCopyIterationBenchmark.cxx )
34+
target_link_libraries(CopyIterationBenchmark ${ITK_LIBRARIES})
35+
add_test(
36+
NAME CopyIterationBenchmark
37+
COMMAND CopyIterationBenchmark
38+
${BENCHMARK_RESULTS_OUTPUT_DIR}/__DATESTAMP__CopyIterationBenchmark.json
39+
25 128 )
40+
set_property(TEST CopyIterationBenchmark APPEND PROPERTY LABELS Core)
41+
## performance tests should not be run in parallel
42+
set_tests_properties(CopyIterationBenchmark PROPERTIES RUN_SERIAL TRUE)
Lines changed: 279 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,279 @@
1+
/*=========================================================================
2+
*
3+
* Copyright NumFOCUS
4+
*
5+
* Licensed under the Apache License, Version 2.0 (the "License");
6+
* you may not use this file except in compliance with the License.
7+
* You may obtain a copy of the License at
8+
*
9+
* https://www.apache.org/licenses/LICENSE-2.0.txt
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*
17+
*=========================================================================*/
18+
19+
// This Benchmark compares the performance of ImageRegionIterator, ImageScanlineIterator,
20+
// and ImageRegionRange for simple pixel copying with static_cast operations.
21+
22+
#include <iostream>
23+
#include "itkImage.h"
24+
#include "itkVectorImage.h"
25+
#include "itkFixedArray.h"
26+
#include "itkImageRegionRange.h"
27+
#include "itkImageScanlineIterator.h"
28+
#include "itkImageRegionIterator.h"
29+
#include "itkHighPriorityRealTimeProbesCollector.h"
30+
#include "PerformanceBenchmarkingUtilities.h"
31+
#include <iomanip>
32+
#include <fstream>
33+
34+
35+
// Helper function to initialize an image with random values
36+
template <typename TImage>
37+
typename TImage::Pointer
38+
CreateAndInitializeImage(const typename TImage::SizeType & size, unsigned int numberOfComponentsPerPixel = 0)
39+
{
40+
auto image = TImage::New();
41+
typename TImage::RegionType region{ size };
42+
image->SetRegions(region);
43+
if (numberOfComponentsPerPixel > 0)
44+
{
45+
image->SetNumberOfComponentsPerPixel(numberOfComponentsPerPixel);
46+
}
47+
image->Allocate();
48+
49+
// Initialize with simple pattern (pixel index-based)
50+
using PixelType = typename TImage::PixelType;
51+
unsigned int count = 0;
52+
53+
itk::ImageRegionIterator<TImage> it(image, region);
54+
for (it.GoToBegin(); !it.IsAtEnd(); ++it)
55+
{
56+
it.Set(static_cast<PixelType>(count));
57+
++count;
58+
}
59+
60+
return image;
61+
}
62+
63+
64+
// Method 1: ImageRegionIterator approach
65+
template <typename TInputImage, typename TOutputImage>
66+
void
67+
CopyRegionIterator(const TInputImage * inputPtr, TOutputImage * outputPtr)
68+
{
69+
using InputPixelType = typename TInputImage::PixelType;
70+
using OutputPixelType = typename TOutputImage::PixelType;
71+
using ImageRegionConstIterator = itk::ImageRegionConstIterator<TInputImage>;
72+
using ImageRegionIterator = itk::ImageRegionIterator<TOutputImage>;
73+
74+
const typename TOutputImage::RegionType outputRegion = outputPtr->GetRequestedRegion();
75+
typename TInputImage::RegionType inputRegion = outputRegion;
76+
77+
ImageRegionConstIterator inputIt(inputPtr, inputRegion);
78+
ImageRegionIterator outputIt(outputPtr, outputRegion);
79+
80+
for (inputIt.GoToBegin(), outputIt.GoToBegin(); !inputIt.IsAtEnd(); ++inputIt, ++outputIt)
81+
{
82+
outputIt.Set(static_cast<OutputPixelType>(inputIt.Get()));
83+
}
84+
}
85+
86+
87+
// Method 2: ImageScanlineIterator approach
88+
template <typename TInputImage, typename TOutputImage>
89+
void
90+
CopyScanlineIterator(const TInputImage * inputPtr, TOutputImage * outputPtr)
91+
{
92+
using InputPixelType = typename TInputImage::PixelType;
93+
using OutputPixelType = typename TOutputImage::PixelType;
94+
using ImageScanlineConstIterator = itk::ImageScanlineConstIterator<TInputImage>;
95+
using ImageScanlineIterator = itk::ImageScanlineIterator<TOutputImage>;
96+
97+
const typename TOutputImage::RegionType outputRegion = outputPtr->GetRequestedRegion();
98+
typename TInputImage::RegionType inputRegion = outputRegion;
99+
100+
ImageScanlineConstIterator inputIt(inputPtr, inputRegion);
101+
ImageScanlineIterator outputIt(outputPtr, outputRegion);
102+
103+
while (!inputIt.IsAtEnd())
104+
{
105+
while (!inputIt.IsAtEndOfLine())
106+
{
107+
outputIt.Set(static_cast<OutputPixelType>(inputIt.Get()));
108+
++inputIt;
109+
++outputIt;
110+
}
111+
inputIt.NextLine();
112+
outputIt.NextLine();
113+
}
114+
}
115+
116+
117+
// Method 3: ImageRegionRange approach
118+
template <typename TInputImage, typename TOutputImage>
119+
void
120+
CopyImageRegionRange(const TInputImage * inputPtr, TOutputImage * outputPtr)
121+
{
122+
using InputPixelType = typename TInputImage::PixelType;
123+
using OutputPixelType = typename TOutputImage::PixelType;
124+
125+
const typename TOutputImage::RegionType outputRegion = outputPtr->GetRequestedRegion();
126+
typename TInputImage::RegionType inputRegion = outputRegion;
127+
128+
auto inputRange = itk::ImageRegionRange<const TInputImage>(*inputPtr, inputRegion);
129+
auto outputRange = itk::ImageRegionRange<TOutputImage>(*outputPtr, outputRegion);
130+
131+
auto inputIt = inputRange.begin();
132+
auto outputIt = outputRange.begin();
133+
const auto inputEnd = inputRange.end();
134+
135+
while (inputIt != inputEnd)
136+
{
137+
*outputIt = OutputPixelType(InputPixelType(*inputIt));
138+
++inputIt;
139+
++outputIt;
140+
}
141+
}
142+
143+
144+
// Method 4: ImageRegionRange with range-based for loop
145+
template <typename TInputImage, typename TOutputImage>
146+
void
147+
CopyImageRegionRangeForLoop(const TInputImage * inputPtr, TOutputImage * outputPtr)
148+
{
149+
using InputPixelType = typename TInputImage::PixelType;
150+
using OutputPixelType = typename TOutputImage::PixelType;
151+
152+
const typename TOutputImage::RegionType outputRegion = outputPtr->GetRequestedRegion();
153+
typename TInputImage::RegionType inputRegion = outputRegion;
154+
155+
auto outputRange = itk::ImageRegionRange<TOutputImage>(*outputPtr, outputRegion);
156+
auto outputIt = outputRange.begin();
157+
158+
for (const InputPixelType & inputPixel : itk::ImageRegionRange<const TInputImage>(*inputPtr, inputRegion))
159+
{
160+
*outputIt = static_cast<OutputPixelType>(inputPixel);
161+
++outputIt;
162+
}
163+
}
164+
165+
166+
// Helper function to time a single method
167+
template <typename TInputImage, typename TOutputImage, typename TCopyFunction>
168+
void
169+
TimeMethod(itk::HighPriorityRealTimeProbesCollector & collector,
170+
const std::string & methodName,
171+
TCopyFunction copyFunc,
172+
const TInputImage * inputImage,
173+
typename TOutputImage::Pointer & outputImage,
174+
int iterations)
175+
{
176+
// Allocate output image
177+
outputImage = TOutputImage::New();
178+
outputImage->SetRegions(inputImage->GetLargestPossibleRegion());
179+
if (inputImage->GetNumberOfComponentsPerPixel() > 0)
180+
{
181+
outputImage->SetNumberOfComponentsPerPixel(inputImage->GetNumberOfComponentsPerPixel());
182+
}
183+
outputImage->Allocate();
184+
185+
// Warm-up run
186+
copyFunc(inputImage, outputImage.GetPointer());
187+
188+
// Timed runs
189+
for (int ii = 0; ii < iterations; ++ii)
190+
{
191+
collector.Start(methodName.c_str());
192+
copyFunc(inputImage, outputImage.GetPointer());
193+
collector.Stop(methodName.c_str());
194+
}
195+
}
196+
197+
198+
// Performance testing function
199+
template <typename TInputImage, typename TOutputImage>
200+
void
201+
TimeIterationMethods(itk::HighPriorityRealTimeProbesCollector & collector,
202+
const typename TInputImage::SizeType & size,
203+
const std::string & description,
204+
int iterations,
205+
unsigned int numberOfComponentsPerPixel = 0)
206+
{
207+
// Create and initialize input image
208+
auto inputImage = CreateAndInitializeImage<TInputImage>(size, numberOfComponentsPerPixel);
209+
210+
typename TOutputImage::Pointer outputImage;
211+
212+
// Test Method 1: Region Iterator
213+
TimeMethod<TInputImage, TOutputImage>(collector,
214+
description + "-RegionIterator",
215+
CopyRegionIterator<TInputImage, TOutputImage>,
216+
inputImage.GetPointer(),
217+
outputImage,
218+
iterations);
219+
220+
// Test Method 2: Scanline Iterator
221+
TimeMethod<TInputImage, TOutputImage>(collector,
222+
description + "-ScanlineIterator",
223+
CopyScanlineIterator<TInputImage, TOutputImage>,
224+
inputImage.GetPointer(),
225+
outputImage,
226+
iterations);
227+
228+
// Test Method 3: ImageRegionRange
229+
TimeMethod<TInputImage, TOutputImage>(collector,
230+
description + "-Range",
231+
CopyImageRegionRange<TInputImage, TOutputImage>,
232+
inputImage.GetPointer(),
233+
outputImage,
234+
iterations);
235+
236+
// Test Method 4: ImageRegionRange with range-based for loop
237+
TimeMethod<TInputImage, TOutputImage>(collector,
238+
description + "-RangeForLoop",
239+
CopyImageRegionRangeForLoop<TInputImage, TOutputImage>,
240+
inputImage.GetPointer(),
241+
outputImage,
242+
iterations);
243+
}
244+
245+
246+
int
247+
main(int argc, char * argv[])
248+
{
249+
if (argc < 4)
250+
{
251+
std::cerr << "Usage: " << std::endl;
252+
std::cerr << argv[0] << " timingsFile iterations imageSize" << std::endl;
253+
return EXIT_FAILURE;
254+
}
255+
const std::string timingsFileName = ReplaceOccurrence(argv[1], "__DATESTAMP__", PerfDateStamp());
256+
const int iterations = std::stoi(argv[2]);
257+
const int imageSize = std::stoi(argv[3]);
258+
259+
constexpr unsigned int Dimension = 3;
260+
const auto size = itk::Size<Dimension>::Filled(imageSize);
261+
262+
itk::HighPriorityRealTimeProbesCollector collector;
263+
264+
// Test 1: uint16 to int16
265+
TimeIterationMethods<itk::Image<unsigned short, Dimension>, itk::Image<short, Dimension>>(
266+
collector, size, "Iu2->Ii2", iterations);
267+
268+
// Test 2: FixedArray<float,3> to FixedArray<double,3>
269+
TimeIterationMethods<itk::Image<itk::FixedArray<float, 3>, Dimension>,
270+
itk::Image<itk::FixedArray<double, 3>, Dimension>>(collector, size, "IFf3->IFd3", iterations);
271+
272+
// Test 3: VectorImage<float> to VectorImage<double> with 3 components
273+
TimeIterationMethods<itk::VectorImage<float, Dimension>, itk::VectorImage<double, Dimension>>(
274+
collector, size, "IVf->IVd", iterations, 3);
275+
276+
WriteExpandedReport(timingsFileName, collector, true, true, false);
277+
278+
return EXIT_SUCCESS;
279+
}

0 commit comments

Comments
 (0)