|
21 | 21 | #include "itkPoint.h" |
22 | 22 | #include "itkMath.h" |
23 | 23 |
|
| 24 | +#include <algorithm> |
| 25 | +#include <iterator> |
| 26 | + |
24 | 27 | namespace itk |
25 | 28 | { |
26 | 29 | template <unsigned int VDimension> |
27 | 30 | auto |
28 | 31 | BresenhamLine<VDimension>::BuildLine(LType Direction, IdentifierType length) -> OffsetArray |
29 | 32 | { |
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(); |
52 | 34 |
|
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 | + })); |
54 | 40 |
|
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])); |
56 | 46 |
|
57 | | - Direction.Normalize(); |
58 | 47 | // 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; |
61 | 50 | for (unsigned int i = 0; i < VDimension; ++i) |
62 | 51 | { |
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]); |
64 | 54 | } |
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) |
69 | 60 | { |
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); |
78 | 62 | } |
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 |
107 | 63 |
|
108 | | - ++steps; |
109 | | - } |
110 | | - return (result); |
| 64 | + return offsets; |
111 | 65 | } |
112 | 66 |
|
113 | 67 | template <unsigned int VDimension> |
114 | 68 | auto |
115 | 69 | BresenhamLine<VDimension>::BuildLine(IndexType p0, IndexType p1) -> IndexArray |
116 | 70 | { |
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; |
120 | 81 | for (unsigned int i = 0; i < VDimension; ++i) |
121 | 82 | { |
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) |
126 | 88 | { |
127 | | - maxDistance = distance; |
| 89 | + maxAbsDelta = absDeltas[i]; |
| 90 | + mainAxis = i; |
128 | 91 | } |
129 | 92 | } |
130 | 93 |
|
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; |
132 | 96 |
|
133 | 97 | 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) |
136 | 106 | { |
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) |
139 | 111 | { |
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 | + } |
141 | 125 | } |
142 | 126 | } |
| 127 | + |
143 | 128 | return indices; |
144 | 129 | } |
145 | 130 |
|
|
0 commit comments