Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions Modules/Core/Common/include/itkImageConstIteratorWithIndex.h
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@

#include "itkIndex.h"
#include "itkImage.h"

#include <cstdint>
#include <memory>
#include <type_traits> // For remove_const_t.

Expand Down Expand Up @@ -286,6 +288,15 @@ class ITK_TEMPLATE_EXPORT ImageConstIteratorWithIndex
}

protected: // made protected so other iterators can access
/** Moves the position by `n` pixels with integer arithmetic, so that a marker past the buffer is not formed by
* pointer arithmetic; the position is dereferenced only at a pixel of the buffer. */
void
MovePosition(const OffsetValueType n)

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The step is done on the pointer's integer value so that no pointer arithmetic produces a value outside the buffer ([expr.add]/4). Measured against main with the slice loop: Clang (arm64 23, x86 18) folds this back to the original pointer arithmetic, same instructions per pixel; GCC 13 keeps one extra instruction per pixel (10.3 -> 11.3) at +0% wall time on long lines and +2% to +9% on 2-pixel lines.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@gdevenyi This seems like a lot of compiled away conversion to do the same math to avoid an un-observed problem.

This solution does not have the performance degredation that your PR 7032 had.

{
m_Position = reinterpret_cast<const InternalPixelType *>(
reinterpret_cast<std::uintptr_t>(m_Position) + static_cast<std::uintptr_t>(n) * sizeof(InternalPixelType));
}

typename TImage::ConstWeakPointer m_Image{};

IndexType m_PositionIndex{ { 0 } }; // Index where we currently are
Expand Down
20 changes: 10 additions & 10 deletions Modules/Core/Common/include/itkImageLinearConstIteratorWithIndex.h
Original file line number Diff line number Diff line change
Expand Up @@ -216,7 +216,7 @@ class ITK_TEMPLATE_EXPORT ImageLinearConstIteratorWithIndex : public ImageConstI
operator++()
{
this->m_PositionIndex[m_Direction]++;
this->m_Position += m_Jump;
this->MovePosition(m_Jump);

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same pattern as the slice iterator: the last ++ of a line along a direction with stride > 1 lands one stride past the last pixel. 43 of 1368 operations, up to 29 pixels beyond one-past-the-end (direction 2 on the full region: 29 of the 30 lines).

return *this;
}

Expand All @@ -226,7 +226,7 @@ class ITK_TEMPLATE_EXPORT ImageLinearConstIteratorWithIndex : public ImageConstI
operator--()
{
this->m_PositionIndex[m_Direction]--;
this->m_Position -= m_Jump;
this->MovePosition(-m_Jump);

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

47 of 1368 operations put the pointer below the buffer, up to 30 pixels; direction 0 reaches buffer - 1 at the first line.

return *this;
}

Expand All @@ -248,8 +248,8 @@ template <typename TImage>
inline void
ImageLinearConstIteratorWithIndex<TImage>::NextLine()
{
this->m_Position -=
this->m_OffsetTable[m_Direction] * (this->m_PositionIndex[m_Direction] - this->m_BeginIndex[m_Direction]);
this->MovePosition(-this->m_OffsetTable[m_Direction] *

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

NextLine starts from the end-of-line marker, so this first step is computed from an out-of-buffer pointer whenever the previous comment applies (reasoned). The call itself always ends on a pixel or, when exhausted, at the region's begin, so the audit sees no out-of-buffer result from NextLine.

(this->m_PositionIndex[m_Direction] - this->m_BeginIndex[m_Direction]));

this->m_PositionIndex[m_Direction] = this->m_BeginIndex[m_Direction];

Expand All @@ -265,12 +265,12 @@ ImageLinearConstIteratorWithIndex<TImage>::NextLine()
this->m_PositionIndex[n]++;
if (this->m_PositionIndex[n] < this->m_EndIndex[n])
{
this->m_Position += this->m_OffsetTable[n];
this->MovePosition(this->m_OffsetTable[n]);
this->m_Remaining = true;
break;
}

this->m_Position -= this->m_OffsetTable[n] * (this->m_Region.GetSize()[n] - 1);
this->MovePosition(-this->m_OffsetTable[n] * static_cast<OffsetValueType>(this->m_Region.GetSize()[n] - 1));
this->m_PositionIndex[n] = this->m_BeginIndex[n];
}
}
Expand All @@ -282,8 +282,8 @@ template <typename TImage>
inline void
ImageLinearConstIteratorWithIndex<TImage>::PreviousLine()
{
this->m_Position +=
this->m_OffsetTable[m_Direction] * (this->m_EndIndex[m_Direction] - 1 - this->m_PositionIndex[m_Direction]);
this->MovePosition(this->m_OffsetTable[m_Direction] *

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mirror for PreviousLine: starts from the reverse end-of-line marker, below the buffer after the last -- (reasoned).

(this->m_EndIndex[m_Direction] - 1 - this->m_PositionIndex[m_Direction]));

this->m_PositionIndex[m_Direction] = this->m_EndIndex[m_Direction] - 1;

Expand All @@ -299,12 +299,12 @@ ImageLinearConstIteratorWithIndex<TImage>::PreviousLine()
this->m_PositionIndex[n]--;
if (this->m_PositionIndex[n] >= this->m_BeginIndex[n])
{
this->m_Position -= this->m_OffsetTable[n];
this->MovePosition(-this->m_OffsetTable[n]);
this->m_Remaining = true;
break;
}

this->m_Position += this->m_OffsetTable[n] * (this->m_Region.GetSize()[n] - 1);
this->MovePosition(this->m_OffsetTable[n] * static_cast<OffsetValueType>(this->m_Region.GetSize()[n] - 1));
this->m_PositionIndex[n] = this->m_EndIndex[n] - 1;
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ ImageLinearConstIteratorWithIndex<TImage>::GoToReverseBeginOfLine()
{
const OffsetValueType distanceToEnd = this->m_EndIndex[m_Direction] - this->m_PositionIndex[m_Direction] - 1;

this->m_Position += m_Jump * distanceToEnd;
this->MovePosition(m_Jump * distanceToEnd);

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

GoToReverseBeginOfLine and GoToBeginOfLine (line 57) always end on a pixel; the step may start from an end-of-line marker outside the buffer when called there (reasoned).

this->m_PositionIndex[m_Direction] = this->m_EndIndex[m_Direction] - 1;
}

Expand All @@ -54,7 +54,7 @@ ImageLinearConstIteratorWithIndex<TImage>::GoToBeginOfLine()
{
const OffsetValueType distanceToBegin = this->m_PositionIndex[m_Direction] - this->m_BeginIndex[m_Direction];

this->m_Position -= m_Jump * distanceToBegin;
this->MovePosition(-m_Jump * distanceToBegin);

this->m_PositionIndex[m_Direction] = this->m_BeginIndex[m_Direction];
}
Expand All @@ -68,7 +68,7 @@ ImageLinearConstIteratorWithIndex<TImage>::GoToEndOfLine()
{
const OffsetValueType distanceToEnd = this->m_EndIndex[m_Direction] - this->m_PositionIndex[m_Direction];

this->m_Position += m_Jump * distanceToEnd;
this->MovePosition(m_Jump * distanceToEnd);

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

GoToEndOfLine deliberately places the pointer one stride past the last pixel: along direction 2 from a line that does not end at the buffer end that is beyond one-past-the-end (2 events in the audit, up to 21 pixels). From the last line of the full region it is exactly one-past-the-end, which is allowed.


this->m_PositionIndex[m_Direction] = this->m_EndIndex[m_Direction];
}
Expand Down
31 changes: 18 additions & 13 deletions Modules/Core/Common/include/itkImageSliceConstIteratorWithIndex.hxx
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,12 @@ ImageSliceConstIteratorWithIndex<TImage>::NextLine()
{
// Move to next line
this->m_PositionIndex[m_Direction_B]++;
this->m_Position += m_LineJump;
this->MovePosition(static_cast<OffsetValueType>(m_LineJump));

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

NextLine starts from the end-of-line marker of the previous comment, so this intermediate value is computed from a pointer that is already outside the buffer (reasoned; the audit observes only the result of the whole call).


// Move to beginning of line
this->m_PositionIndex[m_Direction_A] = this->m_BeginIndex[m_Direction_A];
this->m_Position -= m_PixelJump * (this->m_EndIndex[m_Direction_A] - this->m_BeginIndex[m_Direction_A]);
this->MovePosition(

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

After the last line of a slice this lands on the end-of-slice marker along the second direction: 15 of the 48 configurations end NextLine with the pointer up to 27 pixels beyond one-past-the-end.

-static_cast<OffsetValueType>(m_PixelJump * (this->m_EndIndex[m_Direction_A] - this->m_BeginIndex[m_Direction_A])));
}

//----------------------------------------------------------------------
Expand All @@ -46,11 +47,12 @@ ImageSliceConstIteratorWithIndex<TImage>::PreviousLine()
{
// Move to previous line
this->m_PositionIndex[m_Direction_B]--;
this->m_Position -= m_LineJump;
this->MovePosition(-static_cast<OffsetValueType>(m_LineJump));

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mirror of NextLine: starts from the reverse end-of-line marker, which is below the buffer after the last -- of a line (reasoned).


// Move to end of line
this->m_PositionIndex[m_Direction_A] = this->m_EndIndex[m_Direction_A] - 1;
this->m_Position += m_PixelJump * (this->m_EndIndex[m_Direction_A] - this->m_BeginIndex[m_Direction_A]);
this->MovePosition(

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

After the first line of a slice this lands on the reverse end-of-slice marker: 18 configurations end PreviousLine with the pointer up to 28 pixels below the buffer.

static_cast<OffsetValueType>(m_PixelJump * (this->m_EndIndex[m_Direction_A] - this->m_BeginIndex[m_Direction_A])));
}

//----------------------------------------------------------------------
Expand All @@ -62,7 +64,8 @@ ImageSliceConstIteratorWithIndex<TImage>::GoToBeginOfSlice()
{
// Move to beginning of Slice
this->m_PositionIndex[m_Direction_B] = this->m_BeginIndex[m_Direction_B];
this->m_Position -= m_LineJump * (this->m_EndIndex[m_Direction_B] - this->m_BeginIndex[m_Direction_B]);
this->MovePosition(

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Called from the end-of-slice marker, which can be beyond the buffer (see NextLine), so the subtraction starts from an out-of-buffer pointer (reasoned; not exercised by the audit loop).

-static_cast<OffsetValueType>(m_LineJump * (this->m_EndIndex[m_Direction_B] - this->m_BeginIndex[m_Direction_B])));
}

//----------------------------------------------------------------------
Expand All @@ -73,7 +76,8 @@ void
ImageSliceConstIteratorWithIndex<TImage>::NextSlice()
{
// Move to beginning of Slice
this->m_Position -= m_LineJump * (this->m_PositionIndex[m_Direction_B] - this->m_BeginIndex[m_Direction_B]);
this->MovePosition(-static_cast<OffsetValueType>(

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Starts from the end-of-slice marker (beyond the buffer in 15 configurations) and steps back from it (reasoned, see line 37).

m_LineJump * (this->m_PositionIndex[m_Direction_B] - this->m_BeginIndex[m_Direction_B])));
this->m_PositionIndex[m_Direction_B] = this->m_BeginIndex[m_Direction_B];

for (unsigned int n = 0; n < TImage::ImageDimension; ++n)
Expand All @@ -88,12 +92,12 @@ ImageSliceConstIteratorWithIndex<TImage>::NextSlice()
this->m_PositionIndex[n]++;
if (this->m_PositionIndex[n] < this->m_EndIndex[n])
{
this->m_Position += this->m_OffsetTable[n];
this->MovePosition(this->m_OffsetTable[n]);
this->m_Remaining = true;
break;
}

this->m_Position -= this->m_OffsetTable[n + 1] - this->m_OffsetTable[n];
this->MovePosition(this->m_OffsetTable[n] - this->m_OffsetTable[n + 1]);

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When the traversal is exhausted the per-dimension resets leave the IsAtEnd marker far outside: 8 configurations end NextSlice with the pointer up to 60 pixels below the buffer start (two full slabs of 30).

this->m_PositionIndex[n] = this->m_BeginIndex[n];
}
}
Expand All @@ -107,7 +111,8 @@ ImageSliceConstIteratorWithIndex<TImage>::PreviousSlice()
{
// Move to end of Slice
this->m_PositionIndex[m_Direction_B] = this->m_EndIndex[m_Direction_B] - 1;
this->m_Position += m_LineJump * (this->m_EndIndex[m_Direction_B] - this->m_BeginIndex[m_Direction_B]);
this->MovePosition(

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Starts from the reverse end-of-slice marker (below the buffer in 18 configurations) and steps forward from it (reasoned, see line 54).

static_cast<OffsetValueType>(m_LineJump * (this->m_EndIndex[m_Direction_B] - this->m_BeginIndex[m_Direction_B])));

for (unsigned int n = 0; n < TImage::ImageDimension; ++n)
{
Expand All @@ -121,12 +126,12 @@ ImageSliceConstIteratorWithIndex<TImage>::PreviousSlice()
this->m_PositionIndex[n]--;
if (this->m_PositionIndex[n] >= this->m_BeginIndex[n])
{
this->m_Position -= this->m_OffsetTable[n];
this->MovePosition(-this->m_OffsetTable[n]);
this->m_Remaining = true;
break;
}

this->m_Position += this->m_OffsetTable[n + 1] - this->m_OffsetTable[n];
this->MovePosition(this->m_OffsetTable[n + 1] - this->m_OffsetTable[n]);

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mirror of NextSlice: 8 configurations end PreviousSlice with the IsAtReverseEnd marker up to 59 pixels beyond one-past-the-end.

this->m_PositionIndex[n] = this->m_EndIndex[n] - 1;
}
}
Expand Down Expand Up @@ -211,7 +216,7 @@ ImageSliceConstIteratorWithIndex<TImage> &
ImageSliceConstIteratorWithIndex<TImage>::operator++()
{
this->m_PositionIndex[m_Direction_A]++;
this->m_Position += m_PixelJump;
this->MovePosition(static_cast<OffsetValueType>(m_PixelJump));

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The last ++ of every line. Along a first direction with stride > 1 the marker lands one stride past the last pixel: 86 of 2744 operations, up to 29 pixels beyond one-past-the-end (direction 2 on the full region: 29 of the 30 lines). Along direction 0 on a full region it lands on the next line's first pixel or exactly one-past-the-end, which the standard allows; on a sub-region ending before the buffer end it is 1 to 3 pixels beyond.

return *this;
}

Expand All @@ -223,7 +228,7 @@ ImageSliceConstIteratorWithIndex<TImage> &
ImageSliceConstIteratorWithIndex<TImage>::operator--()
{
this->m_PositionIndex[m_Direction_A]--;
this->m_Position -= m_PixelJump;
this->MovePosition(-static_cast<OffsetValueType>(m_PixelJump));

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The last -- of every reversed line: 94 of 2744 operations put the pointer below the buffer, up to 30 pixels (direction 2 reversed). Even direction 0 on the full region reaches buffer - 1 at the first line (2 events).

return *this;
}
} // end namespace itk
Expand Down
2 changes: 2 additions & 0 deletions Modules/Core/Common/test/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -1518,9 +1518,11 @@ set(
itkImageGTest.cxx
itkImageIORegionGTest.cxx
itkImageIteratorsGTest.cxx
itkImageLinearIteratorWithIndexGTest.cxx
itkImageNeighborhoodOffsetsGTest.cxx
itkImageRandomConstIteratorWithIndexGTest.cxx
itkImageRandomNonRepeatingIteratorWithIndexGTest.cxx
itkImageSliceIteratorWithIndexGTest.cxx
itkImageRegionGTest.cxx
itkImageRegionRangeGTest.cxx
itkImageTransformGTest.cxx
Expand Down
110 changes: 110 additions & 0 deletions Modules/Core/Common/test/itkImageLinearIteratorWithIndexGTest.cxx
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
/*=========================================================================
*
* 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 "itkImage.h"
#include "itkImageLinearConstIteratorWithIndex.h"
#include "itkImageLinearIteratorWithIndex.h"
#include "itkImageBufferRange.h"
#include "itkGTest.h"

#include <algorithm>
#include <vector>

namespace
{
using ImageType = itk::Image<float, 3>;
using RegionType = ImageType::RegionType;

ImageType::Pointer
MakeImage()
{
auto image = ImageType::New();
image->SetRegions(RegionType({ { 10, 20, 30 } }, { { 6, 5, 4 } }));
image->Allocate();
float value = 1.0F;
for (float & pixel : itk::ImageBufferRange<ImageType>{ *image })
{
pixel = value++;
}
return image;
}

// Visits every pixel of `region` once, in both orders, checking index, value and position agreement.
template <typename TIterator>
void
ExpectTraversal(ImageType * image, const RegionType & region, unsigned int direction)
{
TIterator it(image, region);
it.SetDirection(direction);
for (const bool reverse : { false, true })
{
SCOPED_TRACE(reverse ? "reverse" : "forward");
std::vector<unsigned int> visits(image->GetBufferedRegion().GetNumberOfPixels(), 0);
reverse ? it.GoToReverseBegin() : it.GoToBegin();
while (!(reverse ? it.IsAtReverseEnd() : it.IsAtEnd()))
{
while (!(reverse ? it.IsAtReverseEndOfLine() : it.IsAtEndOfLine()))
{
const auto index = it.GetIndex();
ASSERT_TRUE(region.IsInside(index));
EXPECT_EQ(it.Get(), image->GetPixel(index));
EXPECT_EQ(visits[image->ComputeOffset(index)]++, 0U);
reverse ? --it : ++it;
}
reverse ? it.PreviousLine() : it.NextLine();
}
EXPECT_EQ(std::count(visits.cbegin(), visits.cend(), 1U), region.GetNumberOfPixels());
}
}

const RegionType regions[] = { RegionType({ { 10, 20, 30 } }, { { 6, 5, 4 } }),
RegionType({ { 11, 21, 31 } }, { { 4, 3, 2 } }),
RegionType({ { 10, 20, 30 } }, { { 3, 2, 2 } }),
RegionType({ { 13, 23, 32 } }, { { 3, 2, 2 } }) };
} // namespace

TEST(ImageLinearIteratorWithIndex, VisitsEveryPixelOnceInEveryDirection)
{
const auto image = MakeImage();
for (const auto & region : regions)
{
for (unsigned int direction = 0; direction < 3; ++direction)
{
SCOPED_TRACE(testing::Message() << "region " << region << " direction " << direction);
ExpectTraversal<itk::ImageLinearConstIteratorWithIndex<ImageType>>(image, region, direction);
ExpectTraversal<itk::ImageLinearIteratorWithIndex<ImageType>>(image, region, direction);
}
}
}

TEST(ImageLinearIteratorWithIndex, GoToBeginAndEndOfLineLandOnTheLine)
{
const auto image = MakeImage();
const auto region = regions[1];
itk::ImageLinearConstIteratorWithIndex<ImageType> it(image, region);
it.SetDirection(2);
it.GoToBegin();
it.NextLine();
it.GoToEndOfLine();
EXPECT_EQ(it.GetIndex()[2], region.GetIndex(2) + static_cast<itk::IndexValueType>(region.GetSize(2)));
it.GoToReverseBeginOfLine();
EXPECT_EQ(it.Get(), image->GetPixel(it.GetIndex()));
it.GoToBeginOfLine();
EXPECT_EQ(it.GetIndex()[2], region.GetIndex(2));
EXPECT_EQ(it.Get(), image->GetPixel(it.GetIndex()));
}
Loading
Loading