Skip to content

Commit 22f6d27

Browse files
authored
Merge pull request #6054 from hjmjohnson/backport-bresenham-fix
BUG: Backport BresenhamLine integer-only algorithm and double precision fix
2 parents 5934cc8 + 2003fb4 commit 22f6d27

1 file changed

Lines changed: 70 additions & 85 deletions

File tree

Modules/Core/Common/include/itkBresenhamLine.hxx

Lines changed: 70 additions & 85 deletions
Original file line numberDiff line numberDiff line change
@@ -21,125 +21,110 @@
2121
#include "itkPoint.h"
2222
#include "itkMath.h"
2323

24+
#include <algorithm>
25+
#include <iterator>
26+
2427
namespace itk
2528
{
2629
template <unsigned int VDimension>
2730
auto
2831
BresenhamLine<VDimension>::BuildLine(LType Direction, IdentifierType length) -> OffsetArray
2932
{
30-
// copied from the line iterator
31-
/** Variables that drive the Bresenham-Algorithm */
32-
// The dimension with the largest difference between start and end
33-
unsigned int m_MainDirection;
34-
35-
// Accumulated error for the other dimensions
36-
IndexType m_AccumulateError;
37-
38-
// Increment for the error for each step. Two times the difference between
39-
// start and end
40-
IndexType m_IncrementError;
41-
42-
// If enough is accumulated for a dimension, the index has to be
43-
// incremented. Will be the number of pixels in the line
44-
IndexType m_MaximalError;
45-
46-
// Direction of increment. -1 or 1
47-
IndexType m_OverflowIncrement;
48-
49-
// After an overflow, the accumulated error is reduced again. Will be
50-
// two times the number of pixels in the line
51-
IndexType m_ReduceErrorAfterIncrement;
33+
Direction.Normalize();
5234

53-
OffsetArray result(length);
35+
// The dimension with the largest absolute component
36+
const unsigned int maxDistanceDimension = std::distance(
37+
Direction.Begin(), std::max_element(Direction.Begin(), Direction.End(), [](const auto a, const auto b) {
38+
return itk::Math::abs(a) < itk::Math::abs(b);
39+
}));
5440

55-
IndexType m_CurrentImageIndex, LastIndex;
41+
// compute actual line length because the shorter distance
42+
// the larger deviation due to rounding to integers
43+
const IdentifierType mainDirectionLen = length - 1;
44+
const double euclideanLineLen =
45+
static_cast<double>(mainDirectionLen) / static_cast<double>(itk::Math::abs(Direction[maxDistanceDimension]));
5646

57-
Direction.Normalize();
5847
// we are going to start at 0
59-
m_CurrentImageIndex.Fill(0);
60-
constexpr IndexType StartIndex = { { 0 } };
48+
constexpr IndexType StartIndex{ 0 };
49+
IndexType LastIndex;
6150
for (unsigned int i = 0; i < VDimension; ++i)
6251
{
63-
LastIndex[i] = (IndexValueType)(length * Direction[i]);
52+
// round to closest voxel center to minimize the deviation from Direction
53+
LastIndex[i] = Math::RoundHalfIntegerUp<IndexValueType>(euclideanLineLen * Direction[i]);
6454
}
65-
// Find the dominant direction
66-
IndexValueType maxDistance = 0;
67-
unsigned int maxDistanceDimension = 0;
68-
for (unsigned int i = 0; i < VDimension; ++i)
55+
56+
const IndexArray indices = this->BuildLine(StartIndex, LastIndex);
57+
OffsetArray offsets;
58+
offsets.reserve(indices.size());
59+
for (const IndexType & index : indices)
6960
{
70-
auto distance = static_cast<long>(itk::Math::abs(LastIndex[i]));
71-
if (distance > maxDistance)
72-
{
73-
maxDistance = distance;
74-
maxDistanceDimension = i;
75-
}
76-
m_IncrementError[i] = 2 * distance;
77-
m_OverflowIncrement[i] = (LastIndex[i] < 0 ? -1 : 1);
61+
offsets.push_back(index - StartIndex);
7862
}
79-
m_MainDirection = maxDistanceDimension;
80-
m_MaximalError.Fill(maxDistance);
81-
m_ReduceErrorAfterIncrement.Fill(2 * maxDistance);
82-
m_AccumulateError.Fill(0);
83-
unsigned int steps = 1;
84-
result[0] = m_CurrentImageIndex - StartIndex;
85-
while (steps < length)
86-
{
87-
// This part is from ++ in LineConstIterator
88-
// We need to modify m_AccumulateError, m_CurrentImageIndex, m_IsAtEnd
89-
for (unsigned int i = 0; i < VDimension; ++i)
90-
{
91-
if (i == m_MainDirection)
92-
{
93-
m_CurrentImageIndex[i] += m_OverflowIncrement[i];
94-
}
95-
else
96-
{
97-
m_AccumulateError[i] += m_IncrementError[i];
98-
if (m_AccumulateError[i] >= m_MaximalError[i])
99-
{
100-
m_CurrentImageIndex[i] += m_OverflowIncrement[i];
101-
m_AccumulateError[i] -= m_ReduceErrorAfterIncrement[i];
102-
}
103-
}
104-
}
105-
106-
result[steps] = m_CurrentImageIndex - StartIndex; // produce an offset
10763

108-
++steps;
109-
}
110-
return (result);
64+
return offsets;
11165
}
11266

11367
template <unsigned int VDimension>
11468
auto
11569
BresenhamLine<VDimension>::BuildLine(IndexType p0, IndexType p1) -> IndexArray
11670
{
117-
itk::Point<float, VDimension> point0;
118-
itk::Point<float, VDimension> point1;
119-
IdentifierType maxDistance = 0;
71+
// Integer-only N-dimensional Bresenham, guaranteeing exact start and end
72+
// points. This avoids floating-point direction conversion entirely.
73+
// Reference: classic Bresenham extended to N-D as used by scikit-image,
74+
// OpenCV, and VTK.
75+
76+
// Compute displacements and find the dominant axis
77+
IndexType absDeltas;
78+
IndexType step; // +1 or -1 per dimension
79+
IndexValueType maxAbsDelta = 0;
80+
unsigned int mainAxis = 0;
12081
for (unsigned int i = 0; i < VDimension; ++i)
12182
{
122-
point0[i] = p0[i];
123-
point1[i] = p1[i];
124-
IdentifierType distance = itk::Math::abs(p0[i] - p1[i]) + 1;
125-
if (distance > maxDistance)
83+
const IndexValueType delta = p1[i] - p0[i];
84+
step[i] = (delta >= 0) ? 1 : -1;
85+
absDeltas[i] = static_cast<IndexValueType>(itk::Math::abs(delta));
86+
87+
if (absDeltas[i] > maxAbsDelta)
12688
{
127-
maxDistance = distance;
89+
maxAbsDelta = absDeltas[i];
90+
mainAxis = i;
12891
}
12992
}
13093

131-
OffsetArray offsets = this->BuildLine(point1 - point0, maxDistance + 1);
94+
// Number of pixels = steps along dominant axis + 1
95+
const IdentifierType numPixels = static_cast<IdentifierType>(maxAbsDelta) + 1;
13296

13397
IndexArray indices;
134-
indices.reserve(offsets.size()); // we might not have to use the last one
135-
for (unsigned int i = 0; i < offsets.size(); ++i)
98+
indices.reserve(numPixels);
99+
100+
// Error accumulators for each secondary axis (2 * absDelta[i] per step,
101+
// overflow when accumulated error >= maxAbsDelta)
102+
auto accumulateError = MakeFilled<IndexType>(0);
103+
auto currentIndex = p0;
104+
105+
for (IdentifierType s = 0; s < numPixels; ++s)
136106
{
137-
indices.push_back(p0 + offsets[i]);
138-
if (indices.back() == p1)
107+
indices.push_back(currentIndex);
108+
109+
// Advance along each dimension
110+
for (unsigned int i = 0; i < VDimension; ++i)
139111
{
140-
break;
112+
if (i == mainAxis)
113+
{
114+
currentIndex[i] += step[i];
115+
}
116+
else
117+
{
118+
accumulateError[i] += 2 * absDeltas[i];
119+
if (accumulateError[i] >= maxAbsDelta)
120+
{
121+
currentIndex[i] += step[i];
122+
accumulateError[i] -= 2 * maxAbsDelta;
123+
}
124+
}
141125
}
142126
}
127+
143128
return indices;
144129
}
145130

0 commit comments

Comments
 (0)