Img2Num C++ (Internal Developer Docs) dev
API Documentation
Loading...
Searching...
No Matches
graph.cpp
1#include "internal/graph.h"
2
3#include <algorithm>
4#include <iterator>
5#include <queue>
6#include <string>
7
8#include "internal/Pixel.h"
9#include "internal/bezier.h"
10/*
11 Graph class - manages Node class
12*/
13
14static inline float colorDistance(const ImageLib::RGBPixel<uint8_t> &a,
16 ImageLib::RGBPixel<float> af{static_cast<float>(a.red), static_cast<float>(a.green),
17 static_cast<float>(a.blue)};
18 ImageLib::RGBPixel<float> bf{static_cast<float>(b.red), static_cast<float>(b.green),
19 static_cast<float>(b.blue)};
20 return std::sqrt((af.red - bf.red) * (af.red - bf.red) +
21 (af.green - bf.green) * (af.green - bf.green) +
22 (af.blue - bf.blue) * (af.blue - bf.blue));
23}
24
25/*
26To quickly search m_nodes (std::vector) for the index of a node id
27create an std::unordered_map of node id - index pairs
28indexing time of std::vector by value is O(N)
29lookup time of std::unordered_map by key is O(log(N))
30*/
31void Graph::hash_node_ids() {
32 for (int32_t i{0}; i < m_nodes->size(); i++) {
33 const int32_t key{m_nodes->at(i)->id()};
34 m_node_ids[key] = i;
35 }
36}
37
38bool Graph::all_areas_bigger_than(int32_t min_area) {
39 for (auto &n : *m_nodes) {
40 if (n->area() < min_area) {
41 return false;
42 }
43 }
44
45 return true;
46}
47
48bool Graph::add_edge(int32_t node_id1, int32_t node_id2) {
49 auto end_node_ids{m_node_ids.end()};
50 auto node1_it{m_node_ids.find(node_id1)};
51 auto node2_it{m_node_ids.find(node_id2)};
52
53 if (node1_it == end_node_ids || node2_it == end_node_ids) {
54 return false;
55 }
56
57 const int32_t idx1{node1_it->second};
58 const int32_t idx2{node2_it->second};
59
60 m_nodes->at(idx1)->add_edge(m_nodes->at(idx2));
61 m_nodes->at(idx2)->add_edge(m_nodes->at(idx1));
62 return true;
63}
64
65bool Graph::merge_nodes(const Node_ptr &node_to_keep, const Node_ptr &node_to_remove) {
66 auto end_node_ids{m_node_ids.end()};
67 auto node1_it{m_node_ids.find(node_to_keep->id())};
68 auto node2_it{m_node_ids.find(node_to_remove->id())};
69
70 if (node1_it == end_node_ids || node2_it == end_node_ids) {
71 return false;
72 }
73
74 const int32_t idx_k{node1_it->second};
75 const int32_t idx_r{node2_it->second};
76
77 // transfer edges from node_to_remove to node_to_keep
78 for (Node_ptr n : m_nodes->at(idx_r)->edges()) {
79 if (n->id() != node_to_keep->id()) {
80 // prevents self referencing
81 n->remove_edge(node_to_remove);
82 n->add_edge(node_to_keep);
83 node_to_keep->add_edge(n);
84 }
85 }
86
87 node_to_keep->add_pixels(node_to_remove->get_pixels());
88
89 node_to_remove->clear_all();
90
91 return true;
92}
93
94void Graph::clear_unconnected_nodes() {
95 std::vector<Node_ptr> &nodes{*m_nodes};
96
97 nodes.erase(std::remove_if(nodes.begin(), nodes.end(),
98 [](const Node_ptr &n) { return n->area() == 0; }),
99 nodes.end());
100
101 hash_node_ids();
102}
103
104void Graph::discover_edges(const std::vector<int32_t> &region_labels, const int32_t width,
105 const int32_t height) {
106 // Moore 8-connected neighbourhood
107 constexpr int8_t dirs[8][2]{{1, 0}, {-1, 0}, {0, 1}, {0, -1},
108 {1, 1}, {-1, -1}, {-1, 1}, {1, -1}};
109
110 int32_t rneigh[8];
111
112 for (int32_t y{0}; y < height; ++y) {
113 for (int32_t x{0}; x < width; ++x) {
114 const int32_t idx{y * width + x};
115 const int32_t rid{region_labels[idx]};
116
117 for (int32_t k{0}; k < 8; ++k) {
118 const int32_t nx{x + dirs[k][0]};
119 const int32_t ny{y + dirs[k][1]};
120
121 if (nx >= 0 && nx < width && ny >= 0 && ny < height) {
122 rneigh[k] = region_labels[ny * width + nx];
123 } else {
124 rneigh[k] = rid; // ignore out-of-bounds
125 }
126 }
127
128 for (int32_t r : rneigh) {
129 if (r != rid) {
130 add_edge(rid, r);
131 }
132 }
133 }
134 }
135}
136
137void Graph::process_overlapping_edges() {
138 // 1. Build the Global Label Map ONCE (0 = background, else = node->id())
139 std::vector<int32_t> label_map(m_width * m_height, 0);
140
141 for (const Node_ptr &n : get_nodes()) {
142 if (n->area() == 0) continue;
143
144 for (auto &[_, p] : n->get_pixels()) {
145 label_map[p.y * m_width + p.x] = n->id();
146 }
147 }
148
149 constexpr int8_t dirs[8][2]{{1, 0}, {-1, 0}, {0, 1}, {0, -1},
150 {1, 1}, {-1, -1}, {-1, 1}, {1, -1}};
151
152 // 2. Iterate directly over the pixels of each node
153 for (const Node_ptr &n : get_nodes()) {
154 if (n->area() == 0) continue;
155 int32_t val = n->id();
156
157 for (auto &[_, p] : n->get_pixels()) {
158 int x = p.x;
159 int y = p.y;
160
161 // Check 8 neighbors in the global map
162 for (int k = 0; k < 8; ++k) {
163 int nx = x + dirs[k][0];
164 int ny = y + dirs[k][1];
165
166 // Fast boundary check (replaces std::clamp)
167 if (nx < 0 || nx >= m_width || ny < 0 || ny >= m_height) continue;
168
169 int32_t n_val = label_map[ny * m_width + nx];
170
171 // Is it a neighbor? AND have we not processed this pairing yet?
172 if (n_val != 0 && n_val != val && val < n_val) {
173 bool is_too_thin = false;
174
175 // Check around the neighbor pixel for a 3rd region (pinching)
176 for (int mk = 0; mk < 8; ++mk) {
177 int mx = nx + dirs[mk][0];
178 int my = ny + dirs[mk][1];
179
180 if (mx < 0 || mx >= m_width || my < 0 || my >= m_height) continue;
181
182 int32_t m_val = label_map[my * m_width + mx];
183
184 if (m_val != 0 && m_val != val && m_val != n_val) {
185 is_too_thin = true;
186 break; // CRITICAL: Stop checking immediately once proven thin!
187 }
188 }
189
190 if (is_too_thin) {
191 // Give our pixel to the neighbor
192 Node_ptr neighbor_node =
193 m_nodes->at(m_node_ids[n_val]); // get_node_by_id(n_val); // Assuming
194 // you have this lookup
195 if (neighbor_node) {
196 neighbor_node->add_edge_pixel(XY{x, y});
197 }
198 } else {
199 // Take the neighbor's pixel
200 n->add_edge_pixel(XY{nx, ny});
201 }
202 }
203 }
204 }
205 }
206}
207
208void Graph::compute_contours() {
209 // overlap edge pixels
210 // then compute contours
211 process_overlapping_edges();
212 // ask each Node to compute contours
213 for (const Node_ptr &n : get_nodes()) {
214 if (n->area() == 0) continue;
215 n->compute_contour();
216 }
217
218 // smoothing
219 std::vector<std::vector<Point>> all_contours;
220 for (const Node_ptr &n : get_nodes()) {
221 if (n->area() == 0) continue;
222
223 ColoredContours *c0 = &n->m_contours;
224 for (size_t i = 0; i < c0->contours.size(); ++i) {
225 all_contours.push_back(c0->contours[i]);
226 }
227 }
228
229 contours::coupled_smooth(
230 all_contours, Rect{0.0f, 0.0f, static_cast<float>(m_width), static_cast<float>(m_height)});
231
232 std::vector<std::vector<QuadBezier>> all_curves;
233 fit_curve_reduction(all_contours, all_curves, 0.5f);
234
235 int j = 0;
236 for (const Node_ptr &n : get_nodes()) {
237 if (n->area() == 0) continue;
238
239 ColoredContours *c0 = &n->m_contours;
240 for (size_t i = 0; i < c0->contours.size(); ++i) {
241 std::copy(all_contours[j].begin(), all_contours[j].end(), c0->contours[i].begin());
242
243 c0->curves[i].resize(all_curves[j].size());
244 std::copy(all_curves[j].begin(), all_curves[j].end(), c0->curves[i].begin());
245 j++;
246 }
247 }
248}
249
250void Graph::merge_small_area_nodes(const int32_t min_area) {
251 int32_t counter{0};
252 while (!all_areas_bigger_than(min_area)) {
253 for (const Node_ptr &n : get_nodes()) {
254 if (n->area() < min_area) {
255 std::vector<Node_ptr> neighbors;
256 neighbors.reserve(n->num_edges());
257 std::copy(n->edges().begin(), n->edges().end(), std::back_inserter(neighbors));
258
259 ImageLib::RGBPixel<uint8_t> col = n->color();
260
261 Node_ptr best_neighbor = nullptr;
262 float best_score = std::numeric_limits<float>::max();
263 for (const Node_ptr &ne : n->edges()) {
264 if (ne->area() > 0) {
265 float cdist = ImageLib::RGBPixel<uint8_t>::colorDistance(ne->color(), col);
266 float score = static_cast<float>(ne->area()) + 10.f * cdist;
267 if (score < best_score) {
268 best_score = score;
269 best_neighbor = ne;
270 }
271 }
272 }
273
274 // no valid neighbor found, skip this node
275 if (!best_neighbor) {
276 continue;
277 }
278
279 if (best_neighbor->area() >= n->area()) {
280 merge_nodes(best_neighbor, n);
281 } else {
282 merge_nodes(n, best_neighbor);
283 }
284 }
285 }
286
287 clear_unconnected_nodes();
288 ++counter;
289 }
290}
Definition node.h:38