mmcfilters
Public API documentation
Loading...
Searching...
No Matches
RegularGridAdjacency2D.hpp
1#pragma once
2
3#include <algorithm>
4#include <cmath>
5#include <cstddef>
6#include <cstdint>
7#include <initializer_list>
8#include <iterator>
9#include <limits>
10#include <numbers>
11#include <span>
12#include <stdexcept>
13#include <utility>
14#include <vector>
15
16#include "Common.hpp"
17#include "Contract.hpp"
18
19namespace mmcfilters {
20
21namespace detail {
22class CommittedGridAccess;
23}
24
30 int row = 0;
32 int column = 0;
33
39 bool operator==(const GridOffset2D&) const noexcept = default;
40};
41
45enum class RegularGridAdjacencyShape { EuclideanDisk, StructuringElement };
46
63 private:
64 friend class detail::CommittedGridAccess;
65 struct StructuringElementTag {};
66
68 int numColumns;
70 int numRows;
72 double radius;
74 double radius2;
76 int n;
78 RegularGridAdjacencyShape shape = RegularGridAdjacencyShape::EuclideanDisk;
79
81 std::vector<int> offsetRow;
83 std::vector<int> offsetColumn;
85 std::vector<int> forwardOffsetIndices;
86
93 static void requireDomainDimensions(int rows, int columns) {
94 if (rows < 0 || columns < 0) {
95 throw std::invalid_argument("RegularGridAdjacency2D grid dimensions must be non-negative.");
96 }
97 if (static_cast<std::int64_t>(rows) * columns > std::numeric_limits<PixelId>::max()) {
98 throw std::overflow_error("RegularGridAdjacency2D grid dimensions exceed the supported pixel-indexed size.");
99 }
100 }
101
109 static double checkedRadiusParameters(int rows, int columns, double radius) {
110 requireDomainDimensions(rows, columns);
111 const double maxSafeRadius = (std::sqrt(static_cast<double>(std::numeric_limits<int>::max())) - 1.0) / 2.0;
112 if (!std::isfinite(radius) || radius < 1.0 || radius > maxSafeRadius) {
113 throw std::invalid_argument("RegularGridAdjacency2D radius must be finite, at least 1.0, and representable by the integer stencil.");
114 }
115 return radius;
116 }
117
125 [[nodiscard]] bool containsOffset(int rowOffset, int columnOffset) const noexcept {
126 for (int index = 0; index < n; ++index) {
127 if (offsetRow[static_cast<std::size_t>(index)] == rowOffset && offsetColumn[static_cast<std::size_t>(index)] == columnOffset) {
128 return true;
129 }
130 }
131 return false;
132 }
133
135 void requireConnectedDomain() const {
136 if (numRows == 0 || numColumns == 0) {
137 throw std::invalid_argument("A structuring-element adjacency requires a nonempty connected grid domain.");
138 }
139 // Unit steps connect every row and column without a graph traversal.
140 if ((numRows == 1 || containsOffset(1, 0)) && (numColumns == 1 || containsOffset(0, 1))) {
141 return;
142 }
143
144 const auto numPixels = static_cast<std::size_t>(numRows) * static_cast<std::size_t>(numColumns);
145 std::vector<std::uint8_t> visited(numPixels, 0);
146 std::vector<PixelId> pending{0};
147 visited[0] = 1;
148 std::size_t reached = 1;
149 while (!pending.empty()) {
150 const PixelId pixel = pending.back();
151 pending.pop_back();
152 for (PixelId neighbor : NeighborIndexRange(*this, pixel / numColumns, pixel % numColumns)) {
153 if (!visited[static_cast<std::size_t>(neighbor)]) {
154 visited[static_cast<std::size_t>(neighbor)] = 1;
155 ++reached;
156 pending.push_back(neighbor);
157 }
158 }
159 }
160 if (reached != numPixels) {
161 throw std::invalid_argument("A structuring-element adjacency must induce one connected grid-domain graph.");
162 }
163 }
164
171 [[nodiscard]] bool matchesOffsets(std::initializer_list<GridOffset2D> expected) const noexcept {
172 if (static_cast<std::size_t>(n) != expected.size()) {
173 return false;
174 }
175 for (const GridOffset2D offset : expected) {
176 if (!containsOffset(offset.row, offset.column)) {
177 return false;
178 }
179 }
180 return true;
181 }
182
186 void buildForwardOffsetIndices() {
187 forwardOffsetIndices.clear();
188 forwardOffsetIndices.reserve(static_cast<std::size_t>(n / 2));
189 for (int index = 1; index < n; ++index) {
190 const int dx = offsetColumn[static_cast<std::size_t>(index)];
191 const int dy = offsetRow[static_cast<std::size_t>(index)];
192 if (dy > 0 || (dy == 0 && dx > 0)) {
193 forwardOffsetIndices.push_back(index);
194 }
195 }
196 }
197
205 RegularGridAdjacency2D(int rows, int columns, std::vector<GridOffset2D> offsets, StructuringElementTag)
206 : numColumns(columns), numRows(rows), radius(0.0), radius2(0.0), n(0), shape(RegularGridAdjacencyShape::StructuringElement) {
207 requireDomainDimensions(rows, columns);
208 if (offsets.empty()) {
209 throw std::invalid_argument("A structuring-element adjacency requires at least the origin offset.");
210 }
211
212 for (const GridOffset2D offset : offsets) {
213 if (offset.row == std::numeric_limits<int>::min() || offset.column == std::numeric_limits<int>::min()) {
214 throw std::invalid_argument("Structuring-element offsets must be safely negatable.");
215 }
216 }
217
218 std::sort(offsets.begin(), offsets.end(), [](const GridOffset2D& lhs, const GridOffset2D& rhs) {
219 if (lhs.row != rhs.row) {
220 return lhs.row < rhs.row;
221 }
222 return lhs.column < rhs.column;
223 });
224 const auto duplicate = std::adjacent_find(offsets.begin(), offsets.end());
225 if (duplicate != offsets.end()) {
226 throw std::invalid_argument("A structuring-element adjacency cannot contain duplicate offsets.");
227 }
228
229 const auto origin = std::find(offsets.begin(), offsets.end(), GridOffset2D{0, 0});
230 if (origin == offsets.end()) {
231 throw std::invalid_argument("A structuring-element adjacency must contain the origin offset.");
232 }
233 for (const GridOffset2D offset : offsets) {
234 if (!std::binary_search(offsets.begin(), offsets.end(), GridOffset2D{-offset.row, -offset.column},
235 [](const GridOffset2D& lhs, const GridOffset2D& rhs) {
236 if (lhs.row != rhs.row) {
237 return lhs.row < rhs.row;
238 }
239 return lhs.column < rhs.column;
240 })) {
241 throw std::invalid_argument("An adjacency-inducing structuring element must be centrally symmetric.");
242 }
243 }
244
245 std::vector<GridOffset2D> ordered;
246 ordered.reserve(offsets.size());
247 ordered.push_back({0, 0});
248 offsets.erase(origin);
249 std::sort(offsets.begin(), offsets.end(), [](const GridOffset2D& lhs, const GridOffset2D& rhs) {
250 auto angle = [](GridOffset2D offset) {
251 double value = std::atan2(-static_cast<double>(offset.row), -static_cast<double>(offset.column));
252 if (value < 0.0) {
253 value += 2.0 * std::numbers::pi;
254 }
255 return value;
256 };
257 const double lhsAngle = angle(lhs);
258 const double rhsAngle = angle(rhs);
259 if (lhsAngle != rhsAngle) {
260 return lhsAngle < rhsAngle;
261 }
262 const std::int64_t lhsRadius = static_cast<std::int64_t>(lhs.row) * lhs.row + static_cast<std::int64_t>(lhs.column) * lhs.column;
263 const std::int64_t rhsRadius = static_cast<std::int64_t>(rhs.row) * rhs.row + static_cast<std::int64_t>(rhs.column) * rhs.column;
264 return lhsRadius < rhsRadius;
265 });
266 ordered.insert(ordered.end(), offsets.begin(), offsets.end());
267
268 if (ordered.size() > static_cast<std::size_t>(std::numeric_limits<int>::max())) {
269 throw std::length_error("Structuring-element stencil exceeds the supported offset count.");
270 }
271 n = static_cast<int>(ordered.size());
272 offsetRow.reserve(ordered.size());
273 offsetColumn.reserve(ordered.size());
274 for (const GridOffset2D offset : ordered) {
275 offsetRow.push_back(offset.row);
276 offsetColumn.push_back(offset.column);
277 const double squaredDistance = static_cast<double>(offset.row) * offset.row + static_cast<double>(offset.column) * offset.column;
278 radius2 = std::max(radius2, squaredDistance);
279 }
280 radius = std::sqrt(radius2);
281 buildForwardOffsetIndices();
282 requireConnectedDomain();
283 }
284
291 void requireCoordinates(int row, int column) const {
292 MMCFILTERS_CONTRACT_REQUIRE(row >= 0 && row < numRows && column >= 0 && column < numColumns, throw std::out_of_range("Index out of bounds."));
293 }
294
300 void requireLinearIndex(PixelId index) const {
301 const std::int64_t domainSize = static_cast<std::int64_t>(numRows) * static_cast<std::int64_t>(numColumns);
302 MMCFILTERS_CONTRACT_REQUIRE(index >= 0 && static_cast<std::int64_t>(index) < domainSize, throw std::out_of_range("Index out of bounds."));
303 }
304
305 public:
314 RegularGridAdjacency2D(int numRows, int numColumns, double radius) {
315 this->radius = checkedRadiusParameters(numRows, numColumns, radius);
316 this->numRows = numRows;
317 this->numColumns = numColumns;
318 this->radius2 = radius * radius;
319
320 int i, j, k, dx, dy, r0, r2, i0 = 0;
321 this->n = 0;
322 r0 = (int)radius;
323 r2 = (int)radius2;
324 for (dy = -r0; dy <= r0; dy++)
325 for (dx = -r0; dx <= r0; dx++)
326 if (((dx * dx) + (dy * dy)) <= r2)
327 this->n++;
328
329 i = 0;
330 this->offsetColumn.resize(this->n);
331 this->offsetRow.resize(this->n);
332
333 for (dy = -r0; dy <= r0; dy++) {
334 for (dx = -r0; dx <= r0; dx++) {
335 if (((dx * dx) + (dy * dy)) <= r2) {
336 this->offsetColumn[i] = dx;
337 this->offsetRow[i] = dy;
338 if ((dx == 0) && (dy == 0))
339 i0 = i;
340 i++;
341 }
342 }
343 }
344
345 float aux;
346 std::vector<float> da(n);
347 std::vector<float> dr(n);
348
349 /* Set clockwise */
350 for (i = 0; i < n; i++) {
351 dx = this->offsetColumn[i];
352 dy = this->offsetRow[i];
353 dr[i] = std::sqrt((dx * dx) + (dy * dy));
354 if (i != i0) {
355 da[i] = (std::atan2(-dy, -dx) * 180.0 / std::numbers::pi);
356 if (da[i] < 0.0)
357 da[i] += 360.0;
358 }
359 }
360 da[i0] = 0.0;
361 dr[i0] = 0.0;
362
363 /* Place the central grid index first. */
364 aux = da[i0];
365 da[i0] = da[0];
366 da[0] = aux;
367
368 aux = dr[i0];
369 dr[i0] = dr[0];
370 dr[0] = aux;
371
372 int auxX, auxY;
373 auxX = this->offsetColumn[i0];
374 auxY = this->offsetRow[i0];
375 this->offsetColumn[i0] = this->offsetColumn[0];
376 this->offsetRow[i0] = this->offsetRow[0];
377
378 this->offsetColumn[0] = auxX;
379 this->offsetRow[0] = auxY;
380
381 /* sort by angle */
382 for (i = 1; i < n - 1; i++) {
383 k = i;
384 for (j = i + 1; j < n; j++)
385 if (da[j] < da[k]) {
386 k = j;
387 }
388 aux = da[i];
389 da[i] = da[k];
390 da[k] = aux;
391 aux = dr[i];
392 dr[i] = dr[k];
393 dr[k] = aux;
394
395 auxX = this->offsetColumn[i];
396 auxY = this->offsetRow[i];
397 this->offsetColumn[i] = this->offsetColumn[k];
398 this->offsetRow[i] = this->offsetRow[k];
399
400 this->offsetColumn[k] = auxX;
401 this->offsetRow[k] = auxY;
402 }
403
404 /* sort by radius for each angle */
405 for (i = 1; i < n - 1; i++) {
406 k = i;
407 for (j = i + 1; j < n; j++)
408 if ((dr[j] < dr[k]) && (da[j] == da[k])) {
409 k = j;
410 }
411 aux = dr[i];
412 dr[i] = dr[k];
413 dr[k] = aux;
414
415 auxX = this->offsetColumn[i];
416 auxY = this->offsetRow[i];
417 this->offsetColumn[i] = this->offsetColumn[k];
418 this->offsetRow[i] = this->offsetRow[k];
419
420 this->offsetColumn[k] = auxX;
421 this->offsetRow[k] = auxY;
422 }
423
424 // Forward-only stencil keeps one orientation of every undirected edge
425 // while preserving the complete clockwise stencil order.
426 buildForwardOffsetIndices();
427 }
428
444 [[nodiscard]] static RegularGridAdjacency2D fromStructuringElement(int numRows, int numColumns, std::span<const GridOffset2D> offsets) {
445 return RegularGridAdjacency2D(numRows, numColumns, std::vector<GridOffset2D>(offsets.begin(), offsets.end()), StructuringElementTag{});
446 }
447
457 [[nodiscard]] static RegularGridAdjacency2D rectangular(int numRows, int numColumns, int rowRadius, int columnRadius) {
458 if (rowRadius < 0 || columnRadius < 0) {
459 throw std::invalid_argument("Rectangular adjacency radii must be non-negative.");
460 }
461 const std::int64_t height = 2 * static_cast<std::int64_t>(rowRadius) + 1;
462 const std::int64_t width = 2 * static_cast<std::int64_t>(columnRadius) + 1;
463 const std::int64_t count = height * width;
464 if (count > static_cast<std::int64_t>(std::numeric_limits<int>::max())) {
465 throw std::length_error("Rectangular adjacency exceeds the supported offset count.");
466 }
467
468 std::vector<GridOffset2D> offsets;
469 offsets.reserve(static_cast<std::size_t>(count));
470 for (int rowOffset = -rowRadius; rowOffset <= rowRadius; ++rowOffset) {
471 for (int columnOffset = -columnRadius; columnOffset <= columnRadius; ++columnOffset) {
472 offsets.push_back({rowOffset, columnOffset});
473 }
474 }
475 return fromStructuringElement(numRows, numColumns, offsets);
476 }
477
490 [[nodiscard]] static RegularGridAdjacency2D line(int numRows, int numColumns, int rowExtent, int columnExtent) {
491 if (rowExtent == std::numeric_limits<int>::min() || columnExtent == std::numeric_limits<int>::min()) {
492 throw std::invalid_argument("Line adjacency extents must be safely negatable.");
493 }
494 const std::int64_t absoluteRow = std::abs(static_cast<std::int64_t>(rowExtent));
495 const std::int64_t absoluteColumn = std::abs(static_cast<std::int64_t>(columnExtent));
496 const std::int64_t steps = std::max(absoluteRow, absoluteColumn);
497 if (2 * steps + 1 > static_cast<std::int64_t>(std::numeric_limits<int>::max())) {
498 throw std::length_error("Line adjacency exceeds the supported offset count.");
499 }
500 if (steps == 0) {
501 const GridOffset2D origin{0, 0};
502 return fromStructuringElement(numRows, numColumns, std::span<const GridOffset2D>(&origin, 1));
503 }
504
505 auto roundedRatio = [](std::int64_t numerator, std::int64_t denominator) {
506 if (numerator >= 0) {
507 return (numerator + denominator / 2) / denominator;
508 }
509 return -((-numerator + denominator / 2) / denominator);
510 };
511
512 std::vector<GridOffset2D> offsets;
513 offsets.reserve(static_cast<std::size_t>(2 * steps + 1));
514 for (std::int64_t sample = -steps; sample <= steps; ++sample) {
515 offsets.push_back({static_cast<int>(roundedRatio(sample * rowExtent, steps)), static_cast<int>(roundedRatio(sample * columnExtent, steps))});
516 }
517 return fromStructuringElement(numRows, numColumns, offsets);
518 }
519
528 [[nodiscard]] static RegularGridAdjacency2D horizontalLine(int numRows, int numColumns, int halfLength) {
529 if (halfLength < 0) {
530 throw std::invalid_argument("Horizontal-line half-length must be non-negative.");
531 }
532 return line(numRows, numColumns, 0, halfLength);
533 }
534
543 [[nodiscard]] static RegularGridAdjacency2D verticalLine(int numRows, int numColumns, int halfLength) {
544 if (halfLength < 0) {
545 throw std::invalid_argument("Vertical-line half-length must be non-negative.");
546 }
547 return line(numRows, numColumns, halfLength, 0);
548 }
549
557 int getSize() const noexcept { return this->n; }
558
564 int getNumRows() const noexcept { return numRows; }
565
571 int getNumColumns() const noexcept { return numColumns; }
572
578 RegularGridAdjacencyShape getShape() const noexcept { return shape; }
579
590 inline bool isAdjacent(PixelId p, PixelId q) const noexcept {
591 if (numColumns <= 0) {
592 return false;
593 }
594 int py = p / numColumns, px = p % numColumns;
595 int qy = q / numColumns, qx = q % numColumns;
596
597 return isAdjacent(px, py, qx, qy);
598 }
599
613 inline bool isAdjacent(int px, int py, int qx, int qy) const noexcept {
614 const std::int64_t dx = static_cast<std::int64_t>(px) - qx;
615 const std::int64_t dy = static_cast<std::int64_t>(py) - qy;
616 if (shape == RegularGridAdjacencyShape::EuclideanDisk) {
617 return static_cast<double>(dx) * dx + static_cast<double>(dy) * dy <= radius2;
618 }
619 if (dx < std::numeric_limits<int>::min() || dx > std::numeric_limits<int>::max() || dy < std::numeric_limits<int>::min() ||
620 dy > std::numeric_limits<int>::max()) {
621 return false;
622 }
623 return containsOffset(static_cast<int>(dy), static_cast<int>(dx));
624 }
625
634 double getRadius() const noexcept { return this->radius; }
635
642 if (shape == RegularGridAdjacencyShape::EuclideanDisk) {
643 return radius == 1.0;
644 }
645 return matchesOffsets({{0, 0}, {-1, 0}, {0, -1}, {1, 0}, {0, 1}});
646 }
647
654 if (shape == RegularGridAdjacencyShape::EuclideanDisk) {
655 return radius == 1.5;
656 }
657 return matchesOffsets({{0, 0}, {-1, -1}, {-1, 0}, {-1, 1}, {0, -1}, {0, 1}, {1, -1}, {1, 0}, {1, 1}});
658 }
659
665 bool isCanonical4Or8Connectivity() const noexcept { return is4connectivity() || is8connectivity(); }
666
676 bool isGridBoundary(PixelId index) const {
677 requireLinearIndex(index);
678 return isGridBoundary(index / numColumns, index % numColumns);
679 }
680
690 bool isGridBoundary(int row, int column) const noexcept { return row == 0 || column == 0 || row == this->numRows - 1 || column == this->numColumns - 1; }
691
700 int getOffsetRow(int index) const noexcept { return offsetRow[index]; }
701
710 int getOffsetColumn(int index) const noexcept { return offsetColumn[index]; }
711
719 template <bool ForwardOnly> class IteratorAdjacencyT {
720 private:
722 const RegularGridAdjacency2D* relation_ = nullptr;
724 int row_ = 0;
726 int column_ = 0;
728 int index_ = 0;
729
735 [[nodiscard]] int stencilSize() const noexcept {
736 if constexpr (ForwardOnly) {
737 return static_cast<int>(relation_->forwardOffsetIndices.size());
738 }
739 return relation_->n;
740 }
741
747 [[nodiscard]] int stencilIndex() const noexcept {
748 if constexpr (ForwardOnly) {
749 return relation_->forwardOffsetIndices[static_cast<std::size_t>(index_)];
750 }
751 return index_;
752 }
753
757 void seekValid() noexcept {
758 const int size = stencilSize();
759 while (index_ < size) {
760 const int offsetIndex = stencilIndex();
761 const std::int64_t neighborRow = static_cast<std::int64_t>(row_) + relation_->offsetRow[static_cast<std::size_t>(offsetIndex)];
762 const std::int64_t neighborColumn = static_cast<std::int64_t>(column_) + relation_->offsetColumn[static_cast<std::size_t>(offsetIndex)];
764 return;
765 }
766 ++index_;
767 }
768 }
769
770 public:
772 using iterator_category = std::forward_iterator_tag;
774 using iterator_concept = std::forward_iterator_tag;
775
779 using difference_type = std::ptrdiff_t;
783 using pointer = void;
784
789
799 : relation_(relation), row_(row), column_(column), index_(index) {
800 seekValid();
801 }
802
809 ++index_;
810 seekValid();
811 return *this;
812 }
813
820 IteratorAdjacencyT previous = *this;
821 ++(*this);
822 return previous;
823 }
824
831 bool operator==(const IteratorAdjacencyT& other) const noexcept {
832 return relation_ == other.relation_ && row_ == other.row_ && column_ == other.column_ && index_ == other.index_;
833 }
834
841 bool operator!=(const IteratorAdjacencyT& other) const noexcept { return !(*this == other); }
842
849 const int offsetIndex = stencilIndex();
850 const std::int64_t neighborRow = static_cast<std::int64_t>(row_) + relation_->offsetRow[static_cast<std::size_t>(offsetIndex)];
851 const std::int64_t neighborColumn = static_cast<std::int64_t>(column_) + relation_->offsetColumn[static_cast<std::size_t>(offsetIndex)];
852 return static_cast<PixelId>(neighborRow * relation_->numColumns + neighborColumn);
853 }
854 };
855
860
864 template <bool ForwardOnly, int FirstOffset> class GridIndexRangeT {
865 private:
867 const RegularGridAdjacency2D* relation_;
869 int row_;
871 int column_;
872
873 public:
881 GridIndexRangeT(const RegularGridAdjacency2D& relation, int row, int column) noexcept : relation_(&relation), row_(row), column_(column) {}
882
885
891 [[nodiscard]] iterator begin() const noexcept { return iterator(relation_, row_, column_, FirstOffset); }
892
899 const int endIndex = [&] {
900 if constexpr (ForwardOnly) {
901 return static_cast<int>(relation_->forwardOffsetIndices.size());
902 }
903 return relation_->n;
904 }();
905 return iterator(relation_, row_, column_, endIndex);
906 }
907 };
908
915
923 [[nodiscard]] AdjacentIndexRange getAdjacentIndices(int row, int column) const {
924 requireCoordinates(row, column);
925 return AdjacentIndexRange(*this, row, column);
926 }
927
937 requireLinearIndex(gridIndex);
938 return getAdjacentIndices(gridIndex / numColumns, gridIndex % numColumns);
939 }
940
948 [[nodiscard]] NeighborIndexRange getNeighborIndices(int row, int column) const {
949 requireCoordinates(row, column);
950 return NeighborIndexRange(*this, row, column);
951 }
952
962 requireLinearIndex(gridIndex);
963 return getNeighborIndices(gridIndex / numColumns, gridIndex % numColumns);
964 }
965
974 requireCoordinates(row, column);
975 return ForwardNeighborIndexRange(*this, row, column);
976 }
977
987 requireLinearIndex(gridIndex);
988 return getForwardNeighborIndices(gridIndex / numColumns, gridIndex % numColumns);
989 }
990};
991
992} // namespace mmcfilters
Shared scalar conventions used by the C++ API.
Compile-time policy for defensive checks at untrusted API boundaries.
#define MMCFILTERS_CONTRACT_REQUIRE(condition,...)
Evaluates a caller precondition and its failure action only in checked builds.
Definition Contract.hpp:53
Small value range carrying one immutable traversal context.
GridIndexRangeT(const RegularGridAdjacency2D &relation, int row, int column) noexcept
Creates a range for one validated grid coordinate.
iterator begin() const noexcept
Returns an iterator positioned at the first valid neighbour.
iterator end() const noexcept
Returns the traversal sentinel iterator.
Allocation-free iterator over one independent grid traversal.
IteratorAdjacencyT operator++(int) noexcept
Advances the iterator and returns its previous position.
bool operator==(const IteratorAdjacencyT &other) const noexcept
Returns true when both iterators identify the same traversal position.
IteratorAdjacencyT() noexcept=default
Constructs an empty adjacency iterator.
bool operator!=(const IteratorAdjacencyT &other) const noexcept
Returns true when the traversal positions differ.
std::ptrdiff_t difference_type
Signed distance type used by iterator algorithms.
std::forward_iterator_tag iterator_concept
C++20 iterator concept for the traversal cursor.
PixelId operator*() const noexcept
Returns the current neighbour as a linear grid index.
std::forward_iterator_tag iterator_category
Forward-iterator category for independent multi-pass traversal.
IteratorAdjacencyT & operator++() noexcept
Advances this iterator without modifying the relation or other ranges.
Immutable regular-grid 2D adjacency with allocation-free traversal.
bool isGridBoundary(PixelId index) const
Returns whether a linear grid index lies on the grid boundary.
int getOffsetColumn(int index) const noexcept
Returns the column offset stored at stencil position index.
double getRadius() const noexcept
Returns the configured or bounding Euclidean radius.
AdjacentIndexRange getAdjacentIndices(PixelId gridIndex) const
Returns adjacent grid indices including the origin.
static RegularGridAdjacency2D verticalLine(int numRows, int numColumns, int halfLength)
Builds a centered vertical-line adjacency.
RegularGridAdjacency2D(int numRows, int numColumns, double radius)
Builds an adjacency relation for a numRows by numColumns grid.
AdjacentIndexRange getAdjacentIndices(int row, int column) const
Returns adjacent grid indices including the origin.
bool isCanonical4Or8Connectivity() const noexcept
Tests whether grid-topology formulas may interpret this as 4/8 connectivity.
bool isAdjacent(int px, int py, int qx, int qy) const noexcept
Tests adjacency between two grid coordinates.
bool is4connectivity() const noexcept
Returns true when the stencil represents canonical 4-connectivity.
int getSize() const noexcept
Returns the number of offsets in the current stencil.
ForwardNeighborIndexRange getForwardNeighborIndices(int row, int column) const
Returns the directed positive half of the neighbourhood.
RegularGridAdjacencyShape getShape() const noexcept
Returns how the immutable stencil was constructed.
static RegularGridAdjacency2D line(int numRows, int numColumns, int rowExtent, int columnExtent)
Builds a centered digital line from (-dr,-dc) to (dr,dc).
bool isAdjacent(PixelId p, PixelId q) const noexcept
Tests adjacency between two linear grid indices.
GridIndexRangeT< false, 1 > NeighborIndexRange
Range over neighbouring indices excluding the origin.
bool isGridBoundary(int row, int column) const noexcept
Returns whether (row, column) lies on the grid boundary.
int getNumColumns() const noexcept
Returns the number of columns in the attached grid domain.
NeighborIndexRange getNeighborIndices(PixelId gridIndex) const
Returns neighbouring grid indices excluding the origin.
ForwardNeighborIndexRange getForwardNeighborIndices(PixelId gridIndex) const
Returns the directed positive half of the neighbourhood.
static RegularGridAdjacency2D rectangular(int numRows, int numColumns, int rowRadius, int columnRadius)
Builds a centered rectangular structuring-element adjacency.
bool is8connectivity() const noexcept
Returns true when the stencil represents canonical 8-connectivity.
int getNumRows() const noexcept
Returns the number of rows in the attached grid domain.
static RegularGridAdjacency2D fromStructuringElement(int numRows, int numColumns, std::span< const GridOffset2D > offsets)
Builds adjacency induced by a symmetric structuring element.
NeighborIndexRange getNeighborIndices(int row, int column) const
Returns valid neighbouring grid indices excluding the origin.
static RegularGridAdjacency2D horizontalLine(int numRows, int numColumns, int halfLength)
Builds a centered horizontal-line adjacency.
int getOffsetRow(int index) const noexcept
Returns the row offset stored at stencil position index.
Owning result for one computed scalar attribute layout and buffer.
Integer displacement in a row-major regular 2D grid.
int column
Signed column displacement.
int row
Signed row displacement.
bool operator==(const GridOffset2D &) const noexcept=default
Compares both displacement coordinates.