MorphologicalAttributeFilters
Public API documentation
Loading...
Searching...
No Matches
AdjacencyRelation.hpp
1#pragma once
2
3#include "Image.hpp"
4
5#include <cmath>
6#include <cstdint>
7#include <iterator>
8#include <numbers>
9#include <stdexcept>
10#include <vector>
11
12
13namespace mmcfilters {
14
29private:
30 int id;
31
32 int row;
33 int col;
34 int numCols;
35 int numRows;
36 double radius;
37 double radius2;
38 int n;
39 bool forwardOnly = false;
40
41 std::vector<int> offsetRow;
42 std::vector<int> offsetCol;
43 std::vector<uint8_t> forwardMask;
44
45
46public:
55 AdjacencyRelation(int numRows, int numCols, double radius){
56 this->numRows = numRows;
57 this->numCols = numCols;
58 this->radius = radius;
59 this->radius2 = radius * radius;
60
61 int i, j, k, dx, dy, r0, r2, i0 = 0;
62 this->n = 0;
63 r0 = (int) radius;
64 r2 = (int) radius2;
65 for (dy = -r0; dy <= r0; dy++)
66 for (dx = -r0; dx <= r0; dx++)
67 if (((dx * dx) + (dy * dy)) <= r2)
68 this->n++;
69
70 i = 0;
71 this->offsetCol.resize(this->n);
72 this->offsetRow.resize(this->n);
73
74 for (dy = -r0; dy <= r0; dy++) {
75 for (dx = -r0; dx <= r0; dx++) {
76 if (((dx * dx) + (dy * dy)) <= r2) {
77 this->offsetCol[i] =dx;
78 this->offsetRow[i] =dy;
79 if ((dx == 0) && (dy == 0))
80 i0 = i;
81 i++;
82 }
83 }
84 }
85
86 float aux;
87 std::vector<float> da(n);
88 std::vector<float> dr(n);
89
90 /* Set clockwise */
91 for (i = 0; i < n; i++) {
92 dx = this->offsetCol[i];
93 dy = this->offsetRow[i];
94 dr[i] = std::sqrt((dx * dx) + (dy * dy));
95 if (i != i0) {
96 da[i] = (std::atan2(-dy, -dx) * 180.0 / std::numbers::pi);
97 if (da[i] < 0.0)
98 da[i] += 360.0;
99 }
100 }
101 da[i0] = 0.0;
102 dr[i0] = 0.0;
103
104 /* place central pixel at first */
105 aux = da[i0];
106 da[i0] = da[0];
107 da[0] = aux;
108
109 aux = dr[i0];
110 dr[i0] = dr[0];
111 dr[0] = aux;
112
113 int auxX, auxY;
114 auxX = this->offsetCol[i0];
115 auxY = this->offsetRow[i0];
116 this->offsetCol[i0] = this->offsetCol[0];
117 this->offsetRow[i0] = this->offsetRow[0];
118
119 this->offsetCol[0] = auxX;
120 this->offsetRow[0] = auxY;
121
122
123 /* sort by angle */
124 for (i = 1; i < n - 1; i++) {
125 k = i;
126 for (j = i + 1; j < n; j++)
127 if (da[j] < da[k]) {
128 k = j;
129 }
130 aux = da[i];
131 da[i] = da[k];
132 da[k] = aux;
133 aux = dr[i];
134 dr[i] = dr[k];
135 dr[k] = aux;
136
137 auxX = this->offsetCol[i];
138 auxY = this->offsetRow[i];
139 this->offsetCol[i] = this->offsetCol[k];
140 this->offsetRow[i] = this->offsetRow[k];
141
142 this->offsetCol[k] = auxX;
143 this->offsetRow[k] = auxY;
144 }
145
146 /* sort by radius for each angle */
147 for (i = 1; i < n - 1; i++) {
148 k = i;
149 for (j = i + 1; j < n; j++)
150 if ((dr[j] < dr[k]) && (da[j] == da[k])) {
151 k = j;
152 }
153 aux = dr[i];
154 dr[i] = dr[k];
155 dr[k] = aux;
156
157 auxX = this->offsetCol[i];
158 auxY = this->offsetRow[i];
159 this->offsetCol[i] = this->offsetCol[k];
160 this->offsetRow[i] = this->offsetRow[k];
161
162 this->offsetCol[k] = auxX;
163 this->offsetRow[k] = auxY;
164
165 }
166
167 // Forward-only mask: keep only offsets in the positive half-plane.
168 forwardMask.resize(n, 0);
169 for (int k = 1; k < n; ++k) {
170 int dx = offsetCol[k], dy = offsetRow[k];
171 forwardMask[k] = (dy > 0 || (dy == 0 && dx > 0)) ? 1 : 0;
172 }
173 forwardMask[0] = 0;
174 }
175
176
181 int nextValid() {
182 id += 1;
183 while (id < n) {
184
185 // Apply the forward-only mask when required.
186 if (forwardOnly && !forwardMask[id]) { id += 1; continue; }
187
188 // Candidate neighbour coordinates.
189 const int newRow = row + offsetRow[id];
190 const int newCol = col + offsetCol[id];
191
192 if (newRow >= 0 && newRow < numRows && newCol >= 0 && newCol < numCols) {
193 return id;
194 }
195 id += 1;
196 }
197 return n;
198 }
199
205 int getSize() const {
206 return this->n;
207 }
208
213 return numRows;
214 }
215
220 return numCols;
221 }
222
231 AdjacencyRelation& getAdjPixels(int row, int col){
232 if (row < 0 || row >= this->numRows || col < 0 || col >= this->numCols) {
233 throw std::out_of_range("Index out of bounds.");
234 }
235 this->row = row;
236 this->col = col;
237 this->id = -1;
238 this->forwardOnly = false;
239
240 return *this;
241 }
242
251 return getAdjPixels(indexVector / this->numCols, indexVector % this->numCols);
252 }
253
254
264 if (row < 0 || row >= this->numRows || col < 0 || col >= this->numCols) {
265 throw std::out_of_range("Index out of bounds.");
266 }
267 this->row = row;
268 this->col = col;
269 this->id = 0;
270 this->forwardOnly = false;
271 return *this;
272 }
273
282 return getNeighborPixels(indexVector / this->numCols, indexVector % this->numCols);
283 }
284
297 if (row < 0 || row >= this->numRows || col < 0 || col >= this->numCols) {
298 throw std::out_of_range("Index out of bounds.");
299 }
300 this->row = row;
301 this->col = col;
302 this->id = 0;
303 this->forwardOnly = true;
304 return *this;
305 }
306
317
318
325 inline bool isAdjacent(int p, int q) const noexcept {
326 int py = p / numCols, px = p % numCols;
327 int qy = q / numCols, qx = q % numCols;
328
329 return isAdjacent(px, py, qx, qy);
330 }
331
339 inline bool isAdjacent(int px, int py, int qx, int qy) const noexcept {
340 int dx = px - qx;
341 int dy = py - qy;
342 return double(dx)*dx + double(dy)*dy <= radius2;
343 }
344
348 double getRadius() const {
349 return this->radius;
350 }
351
355 bool is4connectivity() const { return this->radius == 1;}
356
360 bool is8connectivity() const {return this->radius == 1.5;}
361
368 bool isBorderDomainImage(int index){
369 auto[row, col] = ImageUtils::to2D(index, this->numCols);
370 return isBorderDomainImage(row, col);
371 }
372
378 bool isBorderDomainImage(int row, int col){
379 return row == 0 || col == 0 || row == this->numRows - 1 || col == this->numCols - 1;
380 }
381
387 int getOffsetRow(int index){
388 return offsetRow[index];
389 }
390
396 int getOffsetCol(int index){
397 return offsetCol[index];
398 }
399
400
411 private:
412 int index;
413 AdjacencyRelation* instance;
414
415 public:
417 using iterator_category = std::input_iterator_tag;
418
421
425 IteratorAdjacency(AdjacencyRelation* obj, int id) : index(id), instance(obj) { }
426
430 AdjacencyRelation* getInstance() { return instance; }
431
436 this->index = instance->nextValid();
437 return *this;
438 }
439
443 bool operator==(const IteratorAdjacency& other) const {
444 return index == other.index;
445 }
446
450 bool operator!=(const IteratorAdjacency& other) const {
451 return !(*this == other);
452 }
453
457 int operator*() const {
458 return (instance->row + instance->offsetRow[index]) * instance->numCols + (instance->col + instance->offsetCol[index]);
459 }
460 };
467 return IteratorAdjacency(this, nextValid());
468 }
469
474 return IteratorAdjacency(this, this->n);
475 }
476};
477
478} // namespace mmcfilters
Contiguous row-major image container and coordinate helpers.
Lightweight iterator over the currently configured traversal.
IteratorAdjacency(AdjacencyRelation *obj, int id)
Creates an iterator over obj at stencil position id.
bool operator!=(const IteratorAdjacency &other) const
Returns true when the iterators point to different stencil positions.
bool operator==(const IteratorAdjacency &other) const
Returns true when both iterators point to the same stencil position.
IteratorAdjacency & operator++()
Advances to the next valid neighbour in the current traversal.
int operator*() const
Returns the current neighbour as a linear pixel index.
std::input_iterator_tag iterator_category
Input-iterator category for range-based traversal.
AdjacencyRelation * getInstance()
Returns the adjacency relation currently being traversed.
Two-dimensional adjacency relation with configurable radius and efficient iteration.
bool isBorderDomainImage(int row, int col)
Returns whether (row, col) lies on the image border.
IteratorAdjacency begin()
Returns the beginning of the current traversal.
bool isAdjacent(int px, int py, int qx, int qy) const noexcept
Tests adjacency between two pixel coordinates.
int getOffsetRow(int index)
Returns the row offset stored at stencil position index.
int getOffsetCol(int index)
Returns the column offset stored at stencil position index.
AdjacencyRelation(int numRows, int numCols, double radius)
Builds an adjacency relation for a numRows by numCols image.
AdjacencyRelation & getNeighborPixels(int row, int col)
Prepares iteration over valid neighbouring pixels, excluding the origin.
bool isAdjacent(int p, int q) const noexcept
Tests adjacency between two linear pixel indices.
double getRadius() const
Returns the configured neighbourhood radius.
AdjacencyRelation & getNeighborPixels(int indexVector)
Prepares iteration over valid neighbouring pixels from a linear index, excluding the origin.
IteratorAdjacency end()
Returns the end sentinel of the current traversal.
int getNumCols() const noexcept
Returns the number of columns in the attached image domain.
int nextValid()
Advances to the next valid offset under the current bounds and mode.
AdjacencyRelation & getNeighborPixelsForward(int row, int col)
Prepares forward-only neighbour iteration at (row, col).
bool is8connectivity() const
Returns true when the stencil represents 8-connectivity.
bool is4connectivity() const
Returns true when the stencil represents 4-connectivity.
AdjacencyRelation & getAdjPixels(int indexVector)
Prepares iteration over adjacent pixels from a linear index, including the origin.
int getNumRows() const noexcept
Returns the number of rows in the attached image domain.
AdjacencyRelation & getAdjPixels(int row, int col)
Prepares iteration over adjacent pixels, including the origin.
AdjacencyRelation & getNeighborPixelsForward(int indexVector)
Prepares forward-only neighbour iteration from a linear index.
bool isBorderDomainImage(int index)
Returns whether a linear pixel index lies on the image border.
int getSize() const
Returns the number of offsets in the current stencil.
static std::pair< int, int > to2D(int index, int numCols) noexcept
Converts a row-major linear index to (row, col).
Definition Image.hpp:283
Owning result for one computed scalar attribute layout and buffer.