Img2Num C++ (Internal Developer Docs) dev
API Documentation
Loading...
Searching...
No Matches
Graph Class Reference
+ Collaboration diagram for Graph:

Public Member Functions

 Graph (std::unique_ptr< std::vector< Node_ptr > > &nodes, int width, int height)
 
bool add_edge (int32_t node_id1, int32_t node_id2)
 
bool merge_nodes (const Node_ptr &node_to_keep, const Node_ptr &node_to_remove)
 
void clear_unconnected_nodes ()
 
const std::vector< Node_ptr > & get_nodes () const
 
bool all_areas_bigger_than (int32_t min_area)
 
const size_t size ()
 
void discover_edges (const std::vector< int32_t > &region_labels, const int32_t width, const int32_t height)
 
void merge_small_area_nodes (const int32_t min_area, const int32_t min_thickness=0)
 
void compute_contours ()
 

Protected Member Functions

void hash_node_ids (void)
 
void process_overlapping_edges ()
 
uint8_t getPixel (const std::vector< uint8_t > &img, int w, int h, int x, int y)
 
std::vector< uint8_t > analyzeJunctions (const std::vector< uint8_t > &skel, int w, int h)
 

Protected Attributes

int m_width
 
int m_height
 
std::unique_ptr< std::vector< Node_ptr > > m_nodes
 
std::unordered_map< int32_t, int32_t > m_node_ids
 

Detailed Description

Definition at line 33 of file graph.h.

Constructor & Destructor Documentation

◆ Graph()

Graph::Graph ( std::unique_ptr< std::vector< Node_ptr > > &  nodes,
int  width,
int  height 
)
inline

Definition at line 73 of file graph.h.

74 : m_nodes(std::move(nodes))
75 , m_width(width)
76 , m_height(height) {
77 hash_node_ids();
78 }

◆ ~Graph()

Graph::~Graph ( )
inline

Definition at line 80 of file graph.h.

80 {
81 // Break the circular references so the shared_ptrs can reach 0
82 for (auto& node : *m_nodes) {
83 node->clear_all();
84 }
85 }

Member Function Documentation

◆ add_edge()

bool Graph::add_edge ( int32_t  node_id1,
int32_t  node_id2 
)

Definition at line 55 of file graph.cpp.

55 {
56 auto end_node_ids {m_node_ids.end()};
57 auto node1_it {m_node_ids.find(node_id1)};
58 auto node2_it {m_node_ids.find(node_id2)};
59
60 if (node1_it == end_node_ids || node2_it == end_node_ids) {
61 return false;
62 }
63
64 const int32_t idx1 {node1_it->second};
65 const int32_t idx2 {node2_it->second};
66
67 m_nodes->at(idx1)->add_edge(m_nodes->at(idx2));
68 m_nodes->at(idx2)->add_edge(m_nodes->at(idx1));
69 return true;
70}

◆ all_areas_bigger_than()

bool Graph::all_areas_bigger_than ( int32_t  min_area)

Definition at line 45 of file graph.cpp.

45 {
46 for (auto& n : *m_nodes) {
47 if (n->area() < min_area) {
48 return false;
49 }
50 }
51
52 return true;
53}

◆ analyzeJunctions()

std::vector< uint8_t > Graph::analyzeJunctions ( const std::vector< uint8_t > &  skel,
int  w,
int  h 
)
protected

@brief Analyzes a skeleton image to detect junction points using 8-neighbor crossing-number.

Scans the skeleton image and marks pixels as junctions where three or more branches meet, using the crossing-number method (counts 0→1 transitions in the 8-neighbor ring).

@param skel Binary skeleton image (nonzero = skeleton pixel) @param w Image width @param h Image height @return Junction mask with nonzero entries marking junction pixels

Definition at line 223 of file graph.cpp.

223 {
224 std::vector<uint8_t> junction_map(static_cast<size_t>(w) * h, 0);
225
226 // 8-Neighbor Order (Clockwise)
227 // P9 P2 P3
228 // P8 P1 P4
229 // P7 P6 P5
230 int dx[] = {0, 1, 1, 1, 0, -1, -1, -1};
231 int dy[] = {-1, -1, 0, 1, 1, 1, 0, -1};
232
233 for (int y = 0; y < h; ++y) {
234 for (int x = 0; x < w; ++x) {
235 if (getPixel(skel, w, h, x, y) == 0)
236 continue;
237
238 // 1. Get Neighbors in Circular Order
239 int p[8];
240 for (int k = 0; k < 8; ++k) {
241 p[k] = getPixel(skel, w, h, x + dx[k], y + dy[k]) ? 1 : 0;
242 }
243
244 // 2. Count Transitions (0 -> 1)
245 // This is the Crossing Number / 2
246 int transitions = 0;
247 for (int k = 0; k < 8; ++k) {
248 if (p[k] == 0 && p[(k + 1) % 8] == 1)
249 transitions++;
250 }
251
252 // 3. Count Total Neighbors (for Endpoint check)
253 int neighbors = 0;
254 for (int k = 0; k < 8; ++k)
255 neighbors += p[k];
256
257 // 4. Classify
258 if (transitions >= 3) {
259 junction_map[y * w + x] = 1;
260 }
261 }
262 }
263 return junction_map;
264}
uint8_t getPixel(const std::vector< uint8_t > &img, int w, int h, int x, int y)
Definition graph.h:52

References getPixel().

+ Here is the call graph for this function:

◆ clear_unconnected_nodes()

void Graph::clear_unconnected_nodes ( )

Definition at line 101 of file graph.cpp.

101 {
102 std::vector<Node_ptr>& nodes {*m_nodes};
103
104 nodes.erase(
105 std::remove_if(
106 nodes.begin(), nodes.end(), [](const Node_ptr& n) { return n->area() == 0; }
107 ),
108 nodes.end()
109 );
110
111 hash_node_ids();
112}

◆ compute_contours()

void Graph::compute_contours ( )

Definition at line 266 of file graph.cpp.

266 {
267
268 /*
269 Shared-edge mode: build region boundaries on the crack grid so
270 neighbouring contours are exactly coincident along shared edges -- no
271 overlap band, no gaps
272 */
273
274 float eps = 0.25f;
275
276 std::vector<int32_t> labels(static_cast<size_t>(m_width) * m_height, -1);
277 for (const Node_ptr& n : get_nodes()) {
278 if (n->area() == 0)
279 continue;
280 for (auto& p : n->get_pixels())
281 labels[static_cast<size_t>(p.position.y) * m_width + p.position.x] = n->id();
282 }
283
284 auto loops = build_shared_loops(labels, m_width, m_height, eps);
285
286 for (const Node_ptr& n : get_nodes()) {
287 if (n->area() == 0)
288 continue;
289 n->clear_contour();
290 auto it = loops.find(n->id());
291 if (it == loops.end())
292 continue;
293 ImageLib::RGBPixel<uint8_t> c = n->color();
294 ImageLib::RGBAPixel<uint8_t> col {c.red, c.green, c.blue, 255};
295 for (std::vector<QuadBezier>& curve : it->second) {
296 std::vector<Point> anchors; // keep contours[] parallel to curves[]
297 anchors.reserve(curve.size() + 1);
298 for (const QuadBezier& q : curve)
299 anchors.push_back(q.p0);
300 if (!curve.empty())
301 anchors.push_back(curve.back().p2);
302 n->m_contours.contours.push_back(std::move(anchors));
303 n->m_contours.curves.push_back(std::move(curve));
304 n->m_contours.colors.push_back(col);
305 n->m_contours.hierarchy.push_back({-1, -1, -1, -1});
306 n->m_contours.is_hole.push_back(false);
307 }
308 }
309}

◆ discover_edges()

void Graph::discover_edges ( const std::vector< int32_t > &  region_labels,
const int32_t  width,
const int32_t  height 
)

Definition at line 114 of file graph.cpp.

116 {
117 // Moore 8-connected neighbourhood
118 constexpr int8_t dirs[8][2] {{1, 0}, {-1, 0}, {0, 1}, {0, -1},
119 {1, 1}, {-1, -1}, {-1, 1}, {1, -1}};
120
121 int32_t rneigh[8];
122
123 for (int32_t y {0}; y < height; ++y) {
124 for (int32_t x {0}; x < width; ++x) {
125 const int32_t idx {y * width + x};
126 const int32_t rid {region_labels[idx]};
127
128 for (int32_t k {0}; k < 8; ++k) {
129 const int32_t nx {x + dirs[k][0]};
130 const int32_t ny {y + dirs[k][1]};
131
132 if (nx >= 0 && nx < width && ny >= 0 && ny < height) {
133 rneigh[k] = region_labels[ny * width + nx];
134 } else {
135 rneigh[k] = rid; // ignore out-of-bounds
136 }
137 }
138
139 for (int32_t r : rneigh) {
140 if (r != rid) {
141 add_edge(rid, r);
142 }
143 }
144 }
145 }
146}

◆ get_nodes()

const std::vector< Node_ptr > & Graph::get_nodes ( ) const
inline

Definition at line 92 of file graph.h.

92 {
93 return *m_nodes;
94 }

◆ getPixel()

uint8_t Graph::getPixel ( const std::vector< uint8_t > &  img,
int  w,
int  h,
int  x,
int  y 
)
inlineprotected

@brief Safely retrieves a pixel value from a binary image with bounds checking.

@param img Binary image buffer @param w Image width @param h Image height @param x Pixel x-coordinate @param y Pixel y-coordinate @return Pixel value at (x, y), or 0 if out of bounds

Definition at line 52 of file graph.h.

52 {
53 if (x < 0 || x >= w || y < 0 || y >= h)
54 return 0; // Boundary check
55 return img[y * w + x];
56 }

Referenced by analyzeJunctions().

+ Here is the caller graph for this function:

◆ hash_node_ids()

void Graph::hash_node_ids ( void  )
protected

Definition at line 38 of file graph.cpp.

38 {
39 for (int32_t i {0}; i < m_nodes->size(); i++) {
40 const int32_t key {m_nodes->at(i)->id()};
41 m_node_ids[key] = i;
42 }
43}

◆ merge_nodes()

bool Graph::merge_nodes ( const Node_ptr &  node_to_keep,
const Node_ptr &  node_to_remove 
)

Definition at line 72 of file graph.cpp.

72 {
73 auto end_node_ids {m_node_ids.end()};
74 auto node1_it {m_node_ids.find(node_to_keep->id())};
75 auto node2_it {m_node_ids.find(node_to_remove->id())};
76
77 if (node1_it == end_node_ids || node2_it == end_node_ids) {
78 return false;
79 }
80
81 const int32_t idx_k {node1_it->second};
82 const int32_t idx_r {node2_it->second};
83
84 // transfer edges from node_to_remove to node_to_keep
85 for (Node_ptr n : m_nodes->at(idx_r)->edges()) {
86 if (n->id() != node_to_keep->id()) {
87 // prevents self referencing
88 n->remove_edge(node_to_remove);
89 n->add_edge(node_to_keep);
90 node_to_keep->add_edge(n);
91 }
92 }
93
94 node_to_keep->add_pixels(node_to_remove->get_pixels());
95
96 node_to_remove->clear_all();
97
98 return true;
99}

◆ merge_small_area_nodes()

void Graph::merge_small_area_nodes ( const int32_t  min_area,
const int32_t  min_thickness = 0 
)

Definition at line 401 of file graph.cpp.

401 {
402 // Keep merging while any pass still makes progress. Using "did this pass
403 // merge anything?" as the loop guard (instead of re-testing every node)
404 // also avoids spinning forever on a node that is too small/thin but has no
405 // valid neighbour to merge into.
406 bool merged_any = true;
407 while (merged_any) {
408 merged_any = false;
409
410 for (const Node_ptr& n : get_nodes()) {
411 if (n->area() == 0)
412 continue;
413
414 bool needs_merge = n->area() < static_cast<size_t>(min_area);
415 if (!needs_merge && min_thickness > 0) {
416 // too thin == no inscribed disk of radius min_thickness/2 fits.
417 needs_merge = 2.0f * max_inscribed_radius(n) < static_cast<float>(min_thickness);
418 }
419 if (!needs_merge)
420 continue;
421
422 ImageLib::RGBPixel<uint8_t> col = n->color();
423
424 Node_ptr best_neighbor = nullptr;
425 float best_score = std::numeric_limits<float>::max();
426 for (const Node_ptr& ne : n->edges()) {
427 if (ne->area() > 0) {
428 float cdist = ImageLib::RGBPixel<uint8_t>::colorDistance(ne->color(), col);
429 float score = static_cast<float>(ne->area()) + 10.f * cdist;
430 if (score < best_score) {
431 best_score = score;
432 best_neighbor = ne;
433 }
434 }
435 }
436
437 // no valid neighbor found, skip this node
438 if (!best_neighbor) {
439 continue;
440 }
441
442 if (best_neighbor->area() >= n->area()) {
443 merge_nodes(best_neighbor, n);
444 } else {
445 merge_nodes(n, best_neighbor);
446 }
447 merged_any = true;
448 }
449
450 clear_unconnected_nodes();
451 }
452}

◆ process_overlapping_edges()

void Graph::process_overlapping_edges ( )
protected

Definition at line 148 of file graph.cpp.

148 {
149 // 1. Build the Global Label Map ONCE (0 = background, else = node->id())
150 std::vector<int32_t> label_map(static_cast<size_t>(m_width) * static_cast<size_t>(m_height), 0);
151
152 for (const Node_ptr& n : get_nodes()) {
153 if (n->area() == 0)
154 continue;
155
156 for (auto& [_, p] : n->get_pixels()) {
157 label_map[p.y * m_width + p.x] = n->id();
158 }
159 }
160
161 constexpr int8_t dirs[8][2] {{1, 0}, {-1, 0}, {0, 1}, {0, -1},
162 {1, 1}, {-1, -1}, {-1, 1}, {1, -1}};
163
164 // 2. Iterate directly over the pixels of each node
165 for (const Node_ptr& n : get_nodes()) {
166 if (n->area() == 0)
167 continue;
168 int32_t val = n->id();
169
170 for (auto& [_, p] : n->get_pixels()) {
171 int x = p.x;
172 int y = p.y;
173
174 // Check 8 neighbors in the global map
175 for (int k = 0; k < 8; ++k) {
176 int nx = x + dirs[k][0];
177 int ny = y + dirs[k][1];
178
179 // Fast boundary check (replaces std::clamp)
180 if (nx < 0 || nx >= m_width || ny < 0 || ny >= m_height)
181 continue;
182
183 int32_t n_val = label_map[ny * m_width + nx];
184
185 // Is it a neighbor? AND have we not processed this pairing yet?
186 if (n_val != 0 && n_val != val && val < n_val) {
187 bool is_too_thin = false;
188
189 // Check around the neighbor pixel for a 3rd region (pinching)
190 for (int mk = 0; mk < 8; ++mk) {
191 int mx = nx + dirs[mk][0];
192 int my = ny + dirs[mk][1];
193
194 if (mx < 0 || mx >= m_width || my < 0 || my >= m_height)
195 continue;
196
197 int32_t m_val = label_map[my * m_width + mx];
198
199 if (m_val != 0 && m_val != val && m_val != n_val) {
200 is_too_thin = true;
201 break; // CRITICAL: Stop checking immediately once proven thin!
202 }
203 }
204
205 if (is_too_thin) {
206 // Give our pixel to the neighbor
207 Node_ptr neighbor_node =
208 m_nodes->at(m_node_ids[n_val]); // get_node_by_id(n_val); // Assuming
209 // you have this lookup
210 if (neighbor_node) {
211 neighbor_node->add_edge_pixel(XY {x, y});
212 }
213 } else {
214 // Take the neighbor's pixel
215 n->add_edge_pixel(XY {nx, ny});
216 }
217 }
218 }
219 }
220 }
221}
Definition node.h:38

◆ size()

const size_t Graph::size ( )
inline

Definition at line 97 of file graph.h.

97 {
98 return m_nodes->size();
99 }

Member Data Documentation

◆ m_height

int Graph::m_height
protected

Definition at line 35 of file graph.h.

◆ m_node_ids

std::unordered_map<int32_t, int32_t> Graph::m_node_ids
protected

Definition at line 37 of file graph.h.

◆ m_nodes

std::unique_ptr<std::vector<Node_ptr> > Graph::m_nodes
protected

Definition at line 36 of file graph.h.

◆ m_width

int Graph::m_width
protected

Definition at line 35 of file graph.h.


The documentation for this class was generated from the following files: