#include "tcenterlinevectP.h" //========================================================================== //********************************* // Skeleton re-organization //********************************* //========================================================================== //---------------------------------------- // Skeleton re-organization Globals //---------------------------------------- namespace { VectorizerCoreGlobals *globals; std::vector contourFamilyOfOrganized; JointSequenceGraph *currJSGraph; ContourFamily *currContourFamily; }; //========================================================================== //-------------------------------------- // Skeleton re-organization //-------------------------------------- // EXPLANATION: The raw skeleton data obtained from StraightSkeletonizer // class need to be grouped in joints and sequences before proceding with // conversion in quadratics - which works on single sequences. // NOTE: Due to maxHeight, we have to assume that a single SkeletonGraph can // hold // more connected graphs at once. // a) Isolate graph-like part of skeleton // b) Retrieve remaining single sequences. typedef std::map> uintMap; // void organizeGraphs(SkeletonList* skeleton) void organizeGraphs(SkeletonList *skeleton, VectorizerCoreGlobals &g) { globals = &g; SkeletonList::iterator currGraphPtr; Sequence currSequence; uintMap jointsMap; UINT i, j; UINT counter = 0; // We also count current graph number, to associate // organized graphs to their contour family contourFamilyOfOrganized.clear(); for (currGraphPtr = skeleton->begin(); currGraphPtr != skeleton->end(); ++currGraphPtr) { SkeletonGraph &currGraph = **currGraphPtr; currSequence.m_graphHolder = &currGraph; // Separate single Points - can happen only when a single node gets stored // in a SkeletonGraph. if (currGraph.getNodesCount() == 1) { globals->singlePoints.push_back(*currGraph.getNode(0)); ++counter; continue; } // Discriminate between graphs, two-endpoint single sequences, and circular // ones bool has1DegreePoint = 0; for (i = 0; i < currGraph.getNodesCount(); ++i) if (currGraph.getNode(i).degree() != 2) if (currGraph.getNode(i).degree() == 1) has1DegreePoint = 1; else goto _graph; if (has1DegreePoint) goto _two_endpoint; else goto _circulars; _two_endpoint : { // Find head for (i = 0; currGraph.getNode(i).degree() != 1; ++i) ; currSequence.m_head = i; currSequence.m_headLink = 0; // Find tail for (++i; i < currGraph.getNodesCount() && currGraph.getNode(i).degree() == 2; ++i) ; currSequence.m_tail = i; currSequence.m_tailLink = 0; globals->singleSequences.push_back(currSequence); ++counter; continue; } _graph : { // Organize Graph-like part globals->organizedGraphs.push_back(JointSequenceGraph()); JointSequenceGraph &JSGraph = globals->organizedGraphs.back(); contourFamilyOfOrganized.push_back(counter); jointsMap.clear(); // Gather all sequence extremities for (i = 0; i < currGraph.getNodesCount(); ++i) { if (currGraph.getNode(i).degree() != 2) { j = JSGraph.newNode(i); // Using a map to keep one-to-one relation between j and i jointsMap.insert(uintMap::value_type(i, j)); } } // Extract Sequences for (i = 0; i < JSGraph.getNodesCount(); ++i) { UINT joint = *JSGraph.getNode(i); for (j = 0; j < currGraph.getNode(joint).getLinksCount(); ++j) { currSequence.m_head = joint; currSequence.m_headLink = j; // Seek tail UINT oldNode = joint, thisNode = currGraph.getNode(joint).getLink(j).getNext(); while (currGraph.getNode(thisNode).degree() == 2) { currGraph.node(thisNode).setAttribute( ORGANIZEGRAPHS_SIGN); // Sign thisNode as part of a JSGraph currSequence.advance(oldNode, thisNode); } currSequence.m_tail = thisNode; currSequence.m_tailLink = currGraph.getNode(thisNode).linkOfNode(oldNode); JSGraph.newLink(i, jointsMap.find(thisNode)->second, currSequence); } } } // NOTE: The following may seem uncommon - you must observe that, *WHEN // maxThicknesssingleSequences.push_back(currSequence); } } } } } //========================================================================== //****************************** // Junction Recovery //****************************** // EXPLANATION: Junction recovery attempts to reshape skeleton junctions when // possible. The original polygons shape must not be exceedingly broken, and // no visible shape alteration must found. // WARNING: Currently not working together with // globals->currConfig->m_maxThicknessgetNode(curr).getLink(currLink).getNext(); d = planeDistance(*s.m_graphHolder->getNode(curr), *s.m_graphHolder->getNode(next)); lowSlope = fabs(s.m_graphHolder->getNode(curr).getLink(currLink)->getSlope()) < roadsMaxSlope ? 1 : 0; // Entering event in a *possibile* road axis if (!roadOn && lowSlope) { length = 0; hMax = s.m_graphHolder->getNode(curr)->z; roadStart = curr; roadStartLink = currLink; } // Exit event from a """ else if (roadOn && !lowSlope && (length > hMax)) // Then sign ROAD the sequence from roadStart to curr. for (; roadStart != curr; s.next(roadStart, roadStartLink)) s.m_graphHolder->node(roadStart) .link(roadStartLink) ->setAttribute(SkeletonArc::ROAD); // Now update vars if (lowSlope) { length += d; if (hMax < s.m_graphHolder->getNode(next)->z) hMax = s.m_graphHolder->getNode(next)->z; } roadOn = lowSlope; } // At sequence end, force an exit event if (roadOn && (length > hMax)) for (; roadStart != curr; s.next(roadStart, roadStartLink)) s.m_graphHolder->node(roadStart) .link(roadStartLink) ->setAttribute(SkeletonArc::ROAD); } //-------------------------------------------------------------------------- // Find the 'roads' of the current Graph. static void findRoads(const JointSequenceGraph &JSGraph) { unsigned int i, j; // For all Sequence of currGraph, extract roads for (i = 0; i < JSGraph.getNodesCount(); ++i) { for (j = 0; j < JSGraph.getNode(i).getLinksCount(); ++j) // if(JSGraph.getNode(i).getLink(j)->isForward()) findRoads(*JSGraph.getNode(i).getLink(j)); } } //-------------------------------------------------------------------------- //---------------------------------- // Junction Recovery Classes //---------------------------------- // Entering point of a Sequence inside a Junction Area class EnteringSequence final : public Sequence { public: TPointD m_direction; // In this case, we keep double m_height; // separated (x,y) and z coords. // Also store indices toward the next (outer) joint unsigned int m_initialJoint; unsigned int m_outerLink; EnteringSequence() {} EnteringSequence(const Sequence &s) : Sequence(s) {} }; //-------------------------------------------------------------------------- class JunctionArea { public: std::vector m_enteringSequences; std::vector m_jointsAbsorbed; TPointD m_newJointPosition; JunctionArea() {} // Initialize Junction Area void expandArea(unsigned int initial); // Calculate and evaluate area bool checkShape(); bool solveJunctionPosition(); bool makeHeights(); bool calculateReconstruction(); // Substitute area over old configuration bool sequencesPullBack(); void apply(); }; //-------------------------------------------------------------------------- //---------------------------- // Expansion Procedure //---------------------------- // Junction Area expansion procedure inline void JunctionArea::expandArea(unsigned int initial) { unsigned int a; unsigned int curr, currLink; unsigned int i, iLink, iNext; m_jointsAbsorbed.push_back(initial); currJSGraph->node(initial).setAttribute( JointSequenceGraph::REACHED); // Nodes absorbed gets signed for (a = 0; a < m_jointsAbsorbed.size(); ++a) { // Extract a joint from vector curr = m_jointsAbsorbed[a]; for (currLink = 0; currLink < currJSGraph->getNode(curr).getLinksCount(); ++currLink) { Sequence &s = *currJSGraph->node(curr).link(currLink); if (s.m_graphHolder->getNode(s.m_head) .getLink(s.m_headLink) .getAccess()) { // Expand into all nearby sequences, until a ROAD is found i = s.m_head; iLink = s.m_headLink; for (; i != s.m_tail && !s.m_graphHolder->getNode(i).getLink(iLink)->hasAttribute( SkeletonArc::ROAD); s.next(i, iLink)) ; // If the sequence has been completely run, include next joint in the // expansion procedure AND sign it as 'REACHED' if (i == s.m_tail) { iNext = currJSGraph->getNode(curr).getLink(currLink).getNext(); if (!currJSGraph->node(iNext).hasAttribute( JointSequenceGraph::REACHED)) { currJSGraph->node(iNext).setAttribute(JointSequenceGraph::REACHED); m_jointsAbsorbed.push_back(iNext); } // Negate access to this sequence s.m_graphHolder->node(s.m_tail).link(s.m_tailLink).setAccess(0); s.m_graphHolder->node(s.m_head).link(s.m_headLink).setAccess(0); } else { // Initialize and copy the entering sequence found m_enteringSequences.push_back(EnteringSequence(s)); m_enteringSequences.back().m_head = i; m_enteringSequences.back().m_headLink = iLink; // Initialize entering directions iNext = s.m_graphHolder->getNode(i).getLink(iLink).getNext(); m_enteringSequences.back().m_direction = planeProjection( *s.m_graphHolder->getNode(i) - *s.m_graphHolder->getNode(iNext)); m_enteringSequences.back().m_direction = m_enteringSequences.back().m_direction * (1 / norm(m_enteringSequences.back().m_direction)); // Initialize entering height / slope m_enteringSequences.back().m_height = s.m_graphHolder->getNode(i)->z; // Also store pointer to link toward the joint at the end of sequence m_enteringSequences.back().m_initialJoint = curr; m_enteringSequences.back().m_outerLink = currLink; } } } } } //-------------------------------------------------------------------------- //----------------------- // Area Shape Test //----------------------- inline bool JunctionArea::checkShape() { std::vector::iterator a, b; unsigned int node, contour, first, last, n; TPointD A, A1, B, B1, P, P1; bool result = 1; // First, sign all outgoing arcs' m_leftGeneratingNode as end of // control procedure for (a = m_enteringSequences.begin(); a != m_enteringSequences.end(); ++a) { node = a->m_graphHolder->getNode(a->m_head) .getLink(a->m_headLink) ->getLeftGenerator(); contour = a->m_graphHolder->getNode(a->m_head) .getLink(a->m_headLink) ->getLeftContour(); (*currContourFamily)[contour][node].setAttribute(ContourNode::JR_RESERVED); } // Now, check shape for (a = m_enteringSequences.begin(), b = m_enteringSequences.end() - 1; a != m_enteringSequences.end(); b = a, ++a) { // Initialize contour check first = a->m_graphHolder->getNode(a->m_head) .getLink(a->m_headLink) ->getRightGenerator(); // last= b->m_graphHolder->getNode(b->m_head).getLink(b->m_headLink) // ->getLeftGenerator(); contour = a->m_graphHolder->getNode(a->m_head) .getLink(a->m_headLink) ->getRightContour(); n = (*currContourFamily)[contour].size(); // Better this effective way to find the last unsigned int numChecked = 0; for (last = first; !(*currContourFamily)[contour][last].hasAttribute( ContourNode::JR_RESERVED) && numChecked < n; last = (last + 1) % n, ++numChecked) ; // Security check if (numChecked == n) return 0; A = planeProjection((*currContourFamily)[contour][first].m_position); A1 = planeProjection( (*currContourFamily)[contour][(first + 1) % n].m_position); B = planeProjection((*currContourFamily)[contour][last].m_position); B1 = planeProjection( (*currContourFamily)[contour][(last + 1) % n].m_position); for (node = first; !(*currContourFamily)[contour][node].hasAttribute( ContourNode::JR_RESERVED); node = (node + 1) % n) { P = planeProjection((*currContourFamily)[contour][node].m_position); P1 = planeProjection( (*currContourFamily)[contour][(node + 1) % n].m_position); // EXPLANATION: // Segment P-P1 must be included in fat lines passing for A-A1 or B-B1 result &= (fabs(cross(P - A, normalize(A1 - A))) < a->m_height * shapeDistMul && fabs(cross(P1 - A, normalize(A1 - A))) < a->m_height * shapeDistMul) || (fabs(cross(P - B, normalize(B1 - B))) < b->m_height * shapeDistMul && fabs(cross(P1 - B, normalize(B1 - B))) < b->m_height * shapeDistMul); } } // Finally, restore nodes attributes for (a = m_enteringSequences.begin(); a != m_enteringSequences.end(); ++a) { node = a->m_graphHolder->getNode(a->m_head) .getLink(a->m_headLink) ->getLeftGenerator(); contour = a->m_graphHolder->getNode(a->m_head) .getLink(a->m_headLink) ->getLeftContour(); (*currContourFamily)[contour][node].clearAttribute( ContourNode::JR_RESERVED); } return result; } //-------------------------------------------------------------------------- //-------------------------------------------- // Solve new junction position problem //-------------------------------------------- inline bool JunctionArea::solveJunctionPosition() { std::vector::iterator a; double Sx2 = 0, Sy2 = 0, Sxy = 0; TPointD P, v, b; double h; // Build preliminary sums for the linear system for (a = m_enteringSequences.begin(); a != m_enteringSequences.end(); ++a) { h = a->m_height; v = a->m_direction; P = planeProjection(*a->m_graphHolder->getNode(a->m_head)); // Height-weighted problem Sx2 += sq(v.x) * h; Sy2 += sq(v.y) * h; Sxy += v.x * v.y * h; b.x += h * (sq(v.y) * P.x - (v.x * v.y * P.y)); b.y += h * (sq(v.x) * P.y - (v.x * v.y * P.x)); } // First check problem determinant double det = Sx2 * Sy2 - sq(Sxy); if (fabs(det) < 0.1) return 0; // Now construct linear system TAffine M(Sx2 / det, Sxy / det, 0, Sxy / det, Sy2 / det, 0); m_newJointPosition = M * b; // Finally, check if J is too far from the line extensions of the entering // sequences for (a = m_enteringSequences.begin(); a != m_enteringSequences.end(); ++a) { P = planeProjection(*a->m_graphHolder->getNode(a->m_head)); if (tdistance(m_newJointPosition, a->m_direction, P) > a->m_height * lineDistMul) return 0; } return 1; } //-------------------------------------------------------------------------- //--------------------------------------- // Calculate optimal joint heights //--------------------------------------- // Globals namespace { const std::vector *currEnterings; const std::vector *heightIndicesPtr; std::vector *optHeights; double optMeanError; double hMax; } //-------------------------------------------------------------------------- inline bool checkCircles(std::vector &heights) { unsigned int i, j; double cos, sin, frac; TPointD vi, vj; // Execute test on angle-consecutive EnteringSequences couples for (j = 0, i = currEnterings->size() - 1; j < currEnterings->size(); i = j, ++j) { vi = (*currEnterings)[i].m_direction; vj = (*currEnterings)[j].m_direction; sin = cross(vi, vj); if (heights[i] == heights[j]) goto test_against_max_height; frac = heights[i] / heights[j]; if (sin < 0) return 0; cos = vi * vj; if (cos < 0 && (frac < -cos || frac > (-1 / cos))) return 0; test_against_max_height: // Reusing cos cos = (sin < 0.1) ? std::max(heights[i], heights[j]) : norm((vi * (heights[j] / sin)) + (vj * (heights[i] / sin))); if (cos < hMax) return 0; } return 1; } //-------------------------------------------------------------------------- inline void tryConfiguration(const std::vector &bounds) { std::vector currHeights(currEnterings->size()); double mean, currMeanError = 0; unsigned int i, j, first, end; for (i = 0, first = 0; i <= bounds.size(); first = end, ++i) { end = i < bounds.size() ? end = bounds[i] + 1 : currEnterings->size(); // Calculate mean from first (included) to end (not included) for (j = first, mean = 0; j < end; ++j) mean += (*currEnterings)[(*heightIndicesPtr)[j]].m_height; mean /= end - first; // Check if the distance from extremities to mean is tolerable if (std::max((*currEnterings)[(*heightIndicesPtr)[end - 1]].m_height - mean, mean - (*currEnterings)[(*heightIndicesPtr)[first]].m_height) > hDiffMul * mean) return; // Calculate squared error to mean for (j = first; j < end; ++j) currMeanError += sq((*currEnterings)[(*heightIndicesPtr)[j]].m_height - mean); // Set calculated currHeights for (j = first; j < end; ++j) currHeights[(*heightIndicesPtr)[j]] = mean; } // Update current maximum height hMax = mean; // Global // If this configuration could be better than current, launch circle test if (optHeights->empty() || currMeanError < optMeanError) { if (checkCircles(currHeights)) { (*optHeights) = currHeights; optMeanError = currMeanError; } } } //-------------------------------------------------------------------------- class hLess { public: std::vector &m_entSequences; hLess(std::vector &v) : m_entSequences(v) {} inline bool operator()(unsigned int a, unsigned int b) { return m_entSequences[a].m_height < m_entSequences[b].m_height; } }; //-------------------------------------------------------------------------- // EXPLANATION: We build intervals on which height means are done. // Their right Interval Bounds are supposed INCLUDED. // Example: heights[]= {1, 2, 3, 4}; rightIntervalBounds[]={0, 2}; // => do height means on: {1}, {2,3}, {4}. (*) // After means are calculated, a test on the obtained configuration is // performed. Among those configurations which pass the test, the one // with rightIntervalBounds.size()->min and, on same sizes, // currMeanError->min is the 'best' configuration possible. // If no height configuration pass the test, reconstruction fails. //(*) NOTE: The right Interval Bounds will never include index n-1, which // interferes with push_backs. inline bool JunctionArea::makeHeights() { std::vector heightOrderedIndices; std::vector rightIntervalBounds; std::vector optimalHeights; unsigned int i, n, m; // Sort entering sequences' indices for increasing height/thickness heightOrderedIndices.resize(m_enteringSequences.size()); for (i = 0; i < m_enteringSequences.size(); ++i) heightOrderedIndices[i] = i; sort(heightOrderedIndices.begin(), heightOrderedIndices.end(), hLess(m_enteringSequences)); // Initialize globals/references currEnterings = &m_enteringSequences; heightIndicesPtr = &heightOrderedIndices; optMeanError = 0; optHeights = &optimalHeights; // Now build height-mean configurations and launch their tests n = m_enteringSequences.size(); // The m=0 case is done first, apart rightIntervalBounds.resize(0); tryConfiguration(rightIntervalBounds); for (m = 1; m < n && optimalHeights.empty(); ++m) { // Initialize bounds rightIntervalBounds.resize(1); rightIntervalBounds[0] = 0; while (!rightIntervalBounds.empty()) { // Fill bounds if necessary while (rightIntervalBounds.size() < m) rightIntervalBounds.push_back(rightIntervalBounds.back() + 1); tryConfiguration(rightIntervalBounds); //'Advance' configuration: increment last index and pop those // exceeding valid size. If bounds gets empty, done all configs // with m+1 intervals. while (( ++rightIntervalBounds.back(), rightIntervalBounds.back() < n - 1 - (m - rightIntervalBounds.size()) ? 0 : (rightIntervalBounds.pop_back(), !rightIntervalBounds.empty()))) ; } } if (!optimalHeights.empty()) { for (i = 0; i < n; ++i) m_enteringSequences[i].m_height = optimalHeights[i]; return 1; } return 0; } //========================================================================== //------------------------------ // Area Calculation Main //------------------------------ class EntSequenceLess { public: EntSequenceLess() {} inline bool operator()(const EnteringSequence &a, const EnteringSequence &b) { // m_direction is normalized, therefore: return a.m_direction.y >= 0 ? b.m_direction.y >= 0 ? a.m_direction.x > b.m_direction.x : 1 : b.m_direction.y < 0 ? a.m_direction.x < b.m_direction.x : 0; } }; //-------------------------------------------------------------------------- bool JunctionArea::calculateReconstruction() { unsigned int i; if (m_enteringSequences.size() == 0) return 0; // Apply preliminary tests for this Junction Area // a) Check if endpoints got absorbed by the expansion process for (i = 0; i < m_jointsAbsorbed.size(); ++i) if (currJSGraph->getNode(m_jointsAbsorbed[i]).degree() == 1) return 0; // b) Check if the contours shape resembles that of a crossing sort(m_enteringSequences.begin(), m_enteringSequences.end(), EntSequenceLess()); if (!checkShape()) return 0; // c) Build the new junction Point plane position if (!solveJunctionPosition()) return 0; // d) Build joint optimal heights (each for entering sequence...) if (!makeHeights()) return 0; return 1; } //========================================================================== //--------------------------- // Sequences Pull Back //--------------------------- // EXPLANATION: We have to insure that connecting entering sequences to the // new junction point happens *smoothly*. In order to do this, wh withdraw // entering sequences along the enterin road, until the angle given by the // connecting line and the entering direction is small. // However, sequence pull back can be done only under some constraints: // * we have to remain inside a road axis // * ........................ a given fat line around the entering direction inline bool JunctionArea::sequencesPullBack() { std::vector::iterator a; double alongLinePosition, distanceFromLine; unsigned int i, iLink, tail; TPointD P; for (a = m_enteringSequences.begin(); a != m_enteringSequences.end(); ++a) { i = a->m_head; iLink = a->m_headLink; // NOTE: updated tails are stored this way, *DONT* look in a->m_tail // because these typically store old infos tail = currJSGraph->getNode(a->m_initialJoint).getLink(a->m_outerLink)->m_tail; P = planeProjection(*a->m_graphHolder->getNode(a->m_head)); while (i != tail) { alongLinePosition = a->m_direction * (m_newJointPosition - P); distanceFromLine = tdistance(m_newJointPosition, a->m_direction, P); if (alongLinePosition >= 0 && (distanceFromLine / alongLinePosition) <= 0.5) goto found_pull_back; // We then take the next arc and check it if (!a->m_graphHolder->getNode(i).getLink(iLink)->hasAttribute( SkeletonArc::ROAD)) return 0; // Pull back failed a->next(i, iLink); P = planeProjection(*a->m_graphHolder->getNode(i)); if (tdistance(P, a->m_direction, m_newJointPosition) > std::max(pullBackMul * a->m_height, 1.0)) return 0; // Pull back failed } // Now checking opposite sequence extremity if (alongLinePosition < 0 || (distanceFromLine / alongLinePosition) > 0.5) return 0; found_pull_back: a->m_head = i; a->m_headLink = iLink; } return 1; } //-------------------------------------------------------------------------- //---------------------------------------------- // Substitute new junction configuration //---------------------------------------------- void JunctionArea::apply() { std::vector::iterator a; unsigned int newJoint, newSkeletonNode; unsigned int head, headLink, tail, tailLink; unsigned int outerJoint, outerLink; unsigned int i, next, nextLink; // First, check if Entering Sequences pullback is possible if (!sequencesPullBack()) return; // Then, we can substitute the old configuration // First, sign as 'ELIMINATED' all absorbed joints for (i = 0; i < m_jointsAbsorbed.size(); ++i) currJSGraph->node(m_jointsAbsorbed[i]) .setAttribute(JointSequenceGraph::ELIMINATED); newJoint = currJSGraph->newNode(); for (a = m_enteringSequences.begin(); a != m_enteringSequences.end(); ++a) { // Initialize infos newSkeletonNode = a->m_graphHolder->newNode(T3DPointD(m_newJointPosition, a->m_height)); // Retrieve sequence infos to substitute // NOTE: We update *tail* infos in currJSGraph sequences after each "apply" const JointSequenceGraph::Link &tempLink = currJSGraph->getNode(a->m_initialJoint).getLink(a->m_outerLink); head = tempLink->m_head; headLink = tempLink->m_headLink; tail = tempLink->m_tail; tailLink = tempLink->m_tailLink; outerJoint = tempLink.getNext(); // Find outerLink - from outerJoint to a->m_initialJoint for (i = 0; (currJSGraph->getNode(outerJoint).getLink(i)->m_tail != head) || (currJSGraph->getNode(outerJoint).getLink(i)->m_tailLink != headLink); ++i) ; outerLink = i; // Now, provide skeleton linkages // NOTE: Discriminate between the following cases // a) m_head->degree == 2 // b) m_head == m_tail // c) m_head == original m_head - or, (m_head!=m_tail && m_head->deg>2) if (a->m_graphHolder->getNode(a->m_head).degree() == 2) { a->m_graphHolder->newLink(newSkeletonNode, a->m_head); a->m_graphHolder->node(a->m_head) .link(!a->m_headLink) .setNext(newSkeletonNode); a->m_graphHolder->node(a->m_head) .link(!a->m_headLink) ->setAttribute(SkeletonArc::ROAD); // Better clear road attribute or set it? } else if (a->m_head == tail) { a->m_graphHolder->newLink(newSkeletonNode, tail); a->m_graphHolder->node(tail).link(tailLink).setNext(newSkeletonNode); a->m_graphHolder->node(tail).link(tailLink)->setAttribute( SkeletonArc::ROAD); } else //(a->m_head == head) { // In this case, better introduce further a new substitute of head unsigned int newHead = a->m_graphHolder->newNode( T3DPointD(*a->m_graphHolder->getNode(a->m_head))); a->m_graphHolder->newLink(newSkeletonNode, newHead); a->m_graphHolder->newLink(newHead, newSkeletonNode); // Link newHead on the other side next = a->m_graphHolder->getNode(head).getLink(headLink).getNext(); nextLink = a->m_graphHolder->getNode(next).linkOfNode(head); a->m_graphHolder->newLink(newHead, next); a->m_graphHolder->node(next).link(nextLink).setNext(newHead); } // Finally, update joint linkings and sequence tails. Sequence newSequence; newSequence.m_graphHolder = a->m_graphHolder; newSequence.m_head = newSkeletonNode; newSequence.m_headLink = 0; newSequence.m_tail = tail; newSequence.m_tailLink = tailLink; currJSGraph->node(outerJoint).link(outerLink).setNext(newJoint); currJSGraph->node(outerJoint).link(outerLink)->m_tail = newSkeletonNode; currJSGraph->node(outerJoint).link(outerLink)->m_tailLink = 0; currJSGraph->newLink(newJoint, outerJoint, newSequence); } } //-------------------------------------------------------------------------- //------------------------------- // Junction Recovery Main //------------------------------- // EXPLANATION: Junction Recovery attempts reconstruction of badly-behaved // crossings in the raw skeleton of the image. // void inline junctionRecovery(Contours* polygons) void junctionRecovery(Contours *polygons, VectorizerCoreGlobals &g) { globals = &g; unsigned int i, j; std::vector junctionAreasList; // For all joints not processed by the Recoverer, launch a new junction // area reconstruction for (i = 0; i < globals->organizedGraphs.size(); ++i) { currJSGraph = &globals->organizedGraphs[i]; currContourFamily = &(*polygons)[contourFamilyOfOrganized[i]]; junctionAreasList.clear(); // First, graph roads are found and signed on the skeleton findRoads(*currJSGraph); // Then, junction areas are identified and reconstructions are calculated for (j = 0; j < currJSGraph->getNodesCount(); ++j) if (!currJSGraph->getNode(j).hasAttribute(JointSequenceGraph::REACHED)) { junctionAreasList.push_back(JunctionArea()); junctionAreasList.back().expandArea(j); if (!junctionAreasList.back().calculateReconstruction()) junctionAreasList.pop_back(); } // Finally, reconstructions are substituted inside the skeleton for (j = 0; j < junctionAreasList.size(); ++j) junctionAreasList[j].apply(); } }