|
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 | | - |
33 | 33 | Direction.Normalize(); |
| 34 | + |
| 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 | + })); |
| 40 | + |
| 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 = mainDirectionLen / itk::Math::Absolute(Direction[maxDistanceDimension]); |
| 45 | + |
34 | 46 | // we are going to start at 0 |
35 | 47 | constexpr IndexType StartIndex{ 0 }; |
36 | 48 | IndexType LastIndex; |
37 | 49 | for (unsigned int i = 0; i < VDimension; ++i) |
38 | 50 | { |
39 | | - LastIndex[i] = (IndexValueType)(length * Direction[i]); |
40 | | - } |
41 | | - // Find the dominant direction |
42 | | - IndexValueType maxDistance = 0; |
43 | | - unsigned int maxDistanceDimension = 0; |
44 | | - // Increment for the error for each step. Two times the difference between |
45 | | - // start and end |
46 | | - IndexType incrementError; |
47 | | - |
48 | | - // Direction of increment. -1 or 1 |
49 | | - IndexType overflowIncrement; |
50 | | - for (unsigned int i = 0; i < VDimension; ++i) |
51 | | - { |
52 | | - auto distance = static_cast<long>(itk::Math::Absolute(LastIndex[i])); |
53 | | - if (distance > maxDistance) |
54 | | - { |
55 | | - maxDistance = distance; |
56 | | - maxDistanceDimension = i; |
57 | | - } |
58 | | - incrementError[i] = 2 * distance; |
59 | | - overflowIncrement[i] = (LastIndex[i] < 0 ? -1 : 1); |
| 51 | + // round to closest voxel center to minimize the deviation from Direction |
| 52 | + LastIndex[i] = Math::RoundHalfIntegerUp<IndexValueType>(euclideanLineLen * Direction[i]); |
60 | 53 | } |
61 | 54 |
|
62 | | - // The dimension with the largest difference between start and end |
63 | | - const unsigned int mainDirection = maxDistanceDimension; |
64 | | - // If enough is accumulated for a dimension, the index has to be |
65 | | - // incremented. Will be the number of pixels in the line |
66 | | - auto maximalError = MakeFilled<IndexType>(maxDistance); |
67 | | - // After an overflow, the accumulated error is reduced again. Will be |
68 | | - // two times the number of pixels in the line |
69 | | - auto reduceErrorAfterIncrement = MakeFilled<IndexType>(2 * maxDistance); |
70 | | - // Accumulated error for the other dimensions |
71 | | - auto accumulateError = MakeFilled<IndexType>(0); |
72 | | - |
73 | | - OffsetArray result(length); |
74 | | - auto currentImageIndex = MakeFilled<IndexType>(0); |
75 | | - result[0] = currentImageIndex - StartIndex; |
76 | | - unsigned int steps = 1; |
77 | | - while (steps < length) |
| 55 | + const IndexArray indices = this->BuildLine(StartIndex, LastIndex); |
| 56 | + OffsetArray offsets; |
| 57 | + offsets.reserve(indices.size()); |
| 58 | + for (const IndexType & index : indices) |
78 | 59 | { |
79 | | - // This part is from ++ in LineConstIterator |
80 | | - // We need to modify accumulateError, currentImageIndex, isAtEnd |
81 | | - for (unsigned int i = 0; i < VDimension; ++i) |
82 | | - { |
83 | | - if (i == mainDirection) |
84 | | - { |
85 | | - currentImageIndex[i] += overflowIncrement[i]; |
86 | | - } |
87 | | - else |
88 | | - { |
89 | | - accumulateError[i] += incrementError[i]; |
90 | | - if (accumulateError[i] >= maximalError[i]) |
91 | | - { |
92 | | - currentImageIndex[i] += overflowIncrement[i]; |
93 | | - accumulateError[i] -= reduceErrorAfterIncrement[i]; |
94 | | - } |
95 | | - } |
96 | | - } |
97 | | - |
98 | | - result[steps] = currentImageIndex - StartIndex; // produce an offset |
99 | | - |
100 | | - ++steps; |
| 60 | + offsets.push_back(index - StartIndex); |
101 | 61 | } |
102 | | - return result; |
| 62 | + |
| 63 | + return offsets; |
103 | 64 | } |
104 | 65 |
|
105 | 66 | template <unsigned int VDimension> |
106 | 67 | auto |
107 | 68 | BresenhamLine<VDimension>::BuildLine(IndexType p0, IndexType p1) -> IndexArray |
108 | 69 | { |
109 | | - itk::Point<float, VDimension> point0; |
110 | | - itk::Point<float, VDimension> point1; |
111 | | - 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; |
112 | 80 | for (unsigned int i = 0; i < VDimension; ++i) |
113 | 81 | { |
114 | | - point0[i] = p0[i]; |
115 | | - point1[i] = p1[i]; |
116 | | - const IdentifierType distance = itk::Math::Absolute(p0[i] - p1[i]) + 1; |
117 | | - 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) |
118 | 87 | { |
119 | | - maxDistance = distance; |
| 88 | + maxAbsDelta = absDeltas[i]; |
| 89 | + mainAxis = i; |
120 | 90 | } |
121 | 91 | } |
122 | 92 |
|
123 | | - 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; |
124 | 95 |
|
125 | 96 | IndexArray indices; |
126 | | - indices.reserve(offsets.size()); // we might not have to use the last one |
127 | | - 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) |
128 | 105 | { |
129 | | - indices.push_back(p0 + offsets[i]); |
130 | | - if (indices.back() == p1) |
| 106 | + indices.push_back(currentIndex); |
| 107 | + |
| 108 | + // Advance along each dimension |
| 109 | + for (unsigned int i = 0; i < VDimension; ++i) |
131 | 110 | { |
132 | | - 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 | + } |
133 | 124 | } |
134 | 125 | } |
| 126 | + |
135 | 127 | return indices; |
136 | 128 | } |
137 | 129 |
|
|
0 commit comments