Not logged in.  Login/Logout/Register | List snippets | | Create snippet | Upload image | Upload data

241
LINES

< > BotCompany Repo | #1035010 // G22SkeletonToMesh [old, superceded by G22SkeletonToMesh_v2]

JavaX fragment (include) [tags: use-pretranspiled]

Transpiled version (8195L) is out of date.

1  
// G22SkeletonToMesh converts a skeleton (thinned image region)
2  
// to a mesh.
3  
4  
// A "mesh" is a set of points ("anchors") connected through curves.
5  
// More docs at G22Mesh
6  
7  
srecord noeq G22SkeletonToMesh<Img extends WidthAndHeight>(IImageRegion<Img> region) extends G22Mesh is Steppable {
8  
  settable bool debug;
9  
  settable bool debugDroppingUnnecessaryAnchors;
10  
  settable bool autoSimplify = true;
11  
  
12  
  // TEMPORARY DATA
13  
  
14  
  ItIt<Pt> regionIterator;
15  
  new L<WorkItem> queue;
16  
  new PtSet pointsSeen;
17  
  
18  
  // p is a point in 3x3 neighborhood of anchor
19  
  srecord noeq WorkItem(Anchor anchor, Pt p) {}
20  
21  
  public bool step() {
22  
    // first, initialize iterator
23  
    if (regionIterator == null)
24  
      regionIterator = region.pixelIterator();
25  
    
26  
    // then check queue for work
27  
    if (nempty(queue)) {
28  
      WorkItem work = popLast(queue);
29  
      
30  
      Pt p = work.p;
31  
      
32  
      new PtBuffer curvePoints;
33  
      curvePoints.add(work.anchor.pt); // add anchor to path
34  
      Anchor endAnchor = null;
35  
36  
      if (debug) print("Exploring from " + work.anchor.pt + " / " + p);
37  
      
38  
      while ping (true) {
39  
        if (debug) print("Curve point " + p);
40  
        curvePoints.add(p);
41  
        
42  
        // did we hit another anchor?
43  
        endAnchor = anchorMap.get(p);
44  
        if (endAnchor != null) {
45  
          if (debug) print("Hit anchor " + endAnchor);
46  
          break;
47  
        }
48  
49  
        // check if we visited this point already
50  
        if (!pointsSeen.add(p)) {
51  
          if (l(curvePoints) == 2)
52  
            true; // It's a 1-pixel curve anyway, just forget it
53  
          else
54  
            // If this is an actual thing, we should make a proper anchor here (splitting existing curves).
55  
            // I don't think it's actually a thing though
56  
            warn("Surprising seen point (" + curvePoints + "). Adding as anchor");
57  
          break;
58  
        }
59  
        
60  
        PtBuffer branches = unexploredPointsAround(p);
61  
        
62  
        // line ends or branches - same thing for us, it means our curve ends and we create a new anchor here
63  
        if (l(branches) != 1) {
64  
          if (debug) print("Branch count: " + l(branches));
65  
          break;
66  
        } else
67  
          p = first(branches); // curve continues
68  
      }
69  
70  
      if (endAnchor == null)
71  
        endAnchor = newAnchor(p);
72  
        
73  
      addCurve(new Curve(work.anchor, endAnchor, curvePoints));
74  
      true;
75  
    }
76  
    
77  
    // finally look for next point in region
78  
    if (regionIterator.hasNext()) {
79  
      Pt p = regionIterator.next();
80  
      
81  
      // check if point already seen, otherwise mark as seen
82  
      if (!pointsSeen.add(p)) true;
83  
      
84  
      // new point - save as anchor
85  
      newAnchor(p);
86  
      true;
87  
    }
88  
    
89  
    // last step, simplify
90  
      
91  
    if (autoSimplify) simplify();
92  
    false;
93  
  }
94  
95  
  Anchor newAnchor(Pt p) {  
96  
    Anchor anchor = new(l(anchorMap)+1, p);
97  
    anchorMap.put(p, anchor);
98  
    //anchors.add(anchor);
99  
      
100  
    // explore in all directions
101  
    for (Pt p2 : unexploredPointsAround(p))
102  
      queue.add(new WorkItem(anchor, p2));
103  
      
104  
    ret anchor;
105  
  }
106  
  
107  
  PtBuffer unexploredPointsAround(Pt p) {
108  
    new PtBuffer out;
109  
    for (int dir = 1; dir <= 8; dir++) {
110  
      Pt p2 = onePathDirection(p, dir);
111  
      if (region.contains(p2) && !pointsSeen.contains(p2))
112  
        out.add(p2);
113  
    }
114  
    ret out;
115  
  }
116  
  
117  
  bool simplify() {
118  
    ret 0 != stepAll(combineSteppables_dontDropEnded(
119  
      l0 dropLengthOneCurves,
120  
      l0 dropShortLoops,
121  
      l0 dropUnnecessaryAnchors));
122  
  }
123  
  
124  
  // drops very short curves connecting a node to itself
125  
  // returns true if anything changed
126  
  bool dropShortLoops() {
127  
    int n = l(curves);
128  
    for (Curve curve : cloneList(curves)) {
129  
      if (curve.start != curve.end) continue;
130  
      if (curve.length() > 3) continue;
131  
      
132  
      if (debug) print("Removing short loop: " + curve);
133  
      removeCurve(curve);
134  
    }
135  
    
136  
    ret l(curves) < n;
137  
  }
138  
      
139  
  // returns true if anything changed
140  
  bool dropLengthOneCurves() {
141  
    int n = l(anchorMap);
142  
    for (Curve curve : cloneList(curves)) {
143  
      if (curve.length() != 1) continue;
144  
      
145  
      // Decide which anchor to keep
146  
      bool keepStartAnchor = curve.start.arity() >= curve.end.arity();
147  
      
148  
      // Forget curve
149  
      removeCurve(curve);
150  
      
151  
      // Merge the less important (defined as: less connected) anchor into the other one
152  
      if (keepStartAnchor)
153  
        mergeAnchor(curve.end, curve.start);
154  
      else
155  
        mergeAnchor(curve.start, curve.end);
156  
    }
157  
    ret l(anchorMap) < n;
158  
  }
159  
  
160  
  void removeCurve(Curve curve) {
161  
    curves.remove(curve);
162  
    curve.start.outgoingCurves.remove(curve);
163  
    curve.end.incomingCurves.remove(curve);
164  
  }
165  
  
166  
  // moves a1's connections to a2, deletes a1
167  
  void mergeAnchor(Anchor a1, Anchor a2) {
168  
    if (debug) printVarsInMultipleLines("mergeAnchor", +a1, +a2);
169  
    
170  
    for (Curve curve : a1.outgoingCurves) {
171  
      // first step is now going from a2 to a1
172  
      curve.path.insertStep(0, ptMinus(a1.pt, a2.pt));
173  
      curve.path.origin(a2.pt);
174  
      curve.start = a2;
175  
      a2.outgoingCurves.add(curve);
176  
    }
177  
    
178  
    for (Curve curve : a1.incomingCurves) {
179  
      // last step is now going from a1 to a2
180  
      curve.path.addStep(ptMinus(a2.pt, a1.pt));
181  
      curve.end = a2;
182  
      a2.incomingCurves.add(curve);
183  
    }
184  
    
185  
    anchorMap.remove(a1.pt);
186  
  }
187  
  
188  
  // returns true if anything changed
189  
  bool dropUnnecessaryAnchors() {
190  
    int n = l(anchorMap);
191  
    for (Anchor anchor : cloneValues(anchorMap)) {
192  
      if (anchor.arity() != 2) continue;
193  
      if (anchor.isRingAnchor()) continue;
194  
      
195  
      // It's a non-branching anchor sitting on a curve - we can drop it.
196  
      if (debugDroppingUnnecessaryAnchors) print("Dropping " + anchor);
197  
      
198  
      // This list will have length 2
199  
      L<Curve> curves = concatLists(anchor.incomingCurves, anchor.outgoingCurves);
200  
      
201  
      // Find the endpoints of the new curve (either 1 or 2 anchors)
202  
      Set<Anchor> anchors = concatMapToSet(curves, c -> c.anchors());
203  
      anchors.remove(this);
204  
      
205  
      if (debugDroppingUnnecessaryAnchors) printVarsInMultipleLines(+curves, +anchors);
206  
      
207  
      // Collect points for merged curve
208  
      
209  
      // Process curve 1, optionally reverse it so it does NOT start at the middle anchor 
210  
      Curve curve1 = first(curves);
211  
      removeCurve(curve1);
212  
      L<Pt> points1 = curve1.path.pointList();
213  
      if (eq(first(points1), anchor.pt)) reverseInPlace(points1);
214  
      
215  
      // Process curve 2, optionally reverse it so it DOES start at the middle anchor 
216  
      Curve curve2 = second(curves);
217  
      removeCurve(curve2);
218  
      L<Pt> points2 = curve2.path.pointList();
219  
      if (!eq(first(points2), anchor.pt)) reverseInPlace(points2);
220  
      
221  
      // Drop overlapping point, append points2 to points1
222  
      assertEquals(anchor.pt, popLast(points1));
223  
      L<Pt> points = points1;
224  
      addAll(points, points2);
225  
      
226  
      Anchor anchor1 = assertNotNull(anchorMap.get(first(points)));
227  
      Anchor anchor2 = assertNotNull(anchorMap.get(last(points)));
228  
      
229  
      if (debugDroppingUnnecessaryAnchors) printVarsInMultipleLines(+anchor, +anchor1, +anchor2);
230  
      
231  
      anchorMap.remove(anchor.pt);
232  
      addCurve(new Curve(anchor1, anchor2, points));
233  
    }
234  
    
235  
    ret l(anchorMap) < n;
236  
  }
237  
  
238  
  void addCurve(Curve curve) {
239  
    curves.add(curve);
240  
  }
241  
}

Author comment

Began life as a copy of #1034998

download  show line numbers  debug dex  old transpilations   

Travelled to 4 computer(s): bhatertpkbcr, ekrmjmnbrukm, mowyntqkapby, mqqgnosmbjvj

No comments. add comment

Snippet ID: #1035010
Snippet name: G22SkeletonToMesh [old, superceded by G22SkeletonToMesh_v2]
Eternal ID of this version: #1035010/63
Text MD5: e2d240043f868cc84b1c0f11d4dcafed
Author: stefan
Category: javax
Type: JavaX fragment (include)
Public (visible to everyone): Yes
Archived (hidden from active list): No
Created/modified: 2022-05-07 02:49:08
Source code size: 7547 bytes / 241 lines
Pitched / IR pitched: No / No
Views / Downloads: 186 / 528
Version history: 62 change(s)
Referenced in: [show references]