Skip to content

Commit ac5652b

Browse files
slavusthjmjohnson
authored andcommitted
BUG: Implement integer-only Bresenham for BuildLine(Index, Index)
Replace the floating-point delegation through BuildLine(Direction, length) with a direct integer-only N-dimensional Bresenham that guarantees exact start and end points by construction. The previous implementation converted integer endpoints to a floating-point direction vector, computed Chebyshev distance for the line length, then reconstructed LastIndex via rounding. This introduced endpoint error for lines where the dominant-axis projection differed from the Euclidean length (e.g., {0,0,0} to {250,250,1} would overshoot to {251,251,0}). Reference: classic Bresenham extended to N-D as used by scikit-image (line_nd), OpenCV (LineIterator), and VTK (vtkLine). (cherry picked from commit 59e3c06)
1 parent 2868e1c commit ac5652b

1 file changed

Lines changed: 69 additions & 85 deletions

File tree

Modules/Core/Common/include/itkBresenhamLine.hxx

Lines changed: 69 additions & 85 deletions
Original file line numberDiff line numberDiff line change
@@ -21,125 +21,109 @@
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::Absolute(a) < itk::Math::Absolute(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 float euclideanLineLen = mainDirectionLen / itk::Math::Absolute(Direction[maxDistanceDimension]);
5645

57-
Direction.Normalize();
5846
// we are going to start at 0
59-
m_CurrentImageIndex.Fill(0);
60-
constexpr IndexType StartIndex = { { 0 } };
47+
constexpr IndexType StartIndex{ 0 };
48+
IndexType LastIndex;
6149
for (unsigned int i = 0; i < VDimension; ++i)
6250
{
63-
LastIndex[i] = (IndexValueType)(length * Direction[i]);
51+
// round to closest voxel center to minimize the deviation from Direction
52+
LastIndex[i] = Math::RoundHalfIntegerUp<IndexValueType>(euclideanLineLen * Direction[i]);
6453
}
65-
// Find the dominant direction
66-
IndexValueType maxDistance = 0;
67-
unsigned int maxDistanceDimension = 0;
68-
for (unsigned int i = 0; i < VDimension; ++i)
54+
55+
const IndexArray indices = this->BuildLine(StartIndex, LastIndex);
56+
OffsetArray offsets;
57+
offsets.reserve(indices.size());
58+
for (const IndexType & index : indices)
6959
{
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);
60+
offsets.push_back(index - StartIndex);
7861
}
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
10762

108-
++steps;
109-
}
110-
return (result);
63+
return offsets;
11164
}
11265

11366
template <unsigned int VDimension>
11467
auto
11568
BresenhamLine<VDimension>::BuildLine(IndexType p0, IndexType p1) -> IndexArray
11669
{
117-
itk::Point<float, VDimension> point0;
118-
itk::Point<float, VDimension> point1;
119-
IdentifierType maxDistance = 0;
70+
// Integer-only N-dimensional Bresenham, guaranteeing exact start and end
71+
// points. This avoids floating-point direction conversion entirely.
72+
// Reference: classic Bresenham extended to N-D as used by scikit-image,
73+
// OpenCV, and VTK.
74+
75+
// Compute displacements and find the dominant axis
76+
IndexType absDeltas;
77+
IndexType step; // +1 or -1 per dimension
78+
IndexValueType maxAbsDelta = 0;
79+
unsigned int mainAxis = 0;
12080
for (unsigned int i = 0; i < VDimension; ++i)
12181
{
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)
82+
const IndexValueType delta = p1[i] - p0[i];
83+
step[i] = (delta >= 0) ? 1 : -1;
84+
absDeltas[i] = static_cast<IndexValueType>(itk::Math::Absolute(delta));
85+
86+
if (absDeltas[i] > maxAbsDelta)
12687
{
127-
maxDistance = distance;
88+
maxAbsDelta = absDeltas[i];
89+
mainAxis = i;
12890
}
12991
}
13092

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

13396
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)
97+
indices.reserve(numPixels);
98+
99+
// Error accumulators for each secondary axis (2 * absDelta[i] per step,
100+
// overflow when accumulated error >= maxAbsDelta)
101+
auto accumulateError = MakeFilled<IndexType>(0);
102+
auto currentIndex = p0;
103+
104+
for (IdentifierType s = 0; s < numPixels; ++s)
136105
{
137-
indices.push_back(p0 + offsets[i]);
138-
if (indices.back() == p1)
106+
indices.push_back(currentIndex);
107+
108+
// Advance along each dimension
109+
for (unsigned int i = 0; i < VDimension; ++i)
139110
{
140-
break;
111+
if (i == mainAxis)
112+
{
113+
currentIndex[i] += step[i];
114+
}
115+
else
116+
{
117+
accumulateError[i] += 2 * absDeltas[i];
118+
if (accumulateError[i] >= maxAbsDelta)
119+
{
120+
currentIndex[i] += step[i];
121+
accumulateError[i] -= 2 * maxAbsDelta;
122+
}
123+
}
141124
}
142125
}
126+
143127
return indices;
144128
}
145129

0 commit comments

Comments
 (0)