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

218
LINES

< > BotCompany Repo | #1035029 // G22SkeletonToMesh_v2 (now a separate object from the mesh itself)

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

Libraryless. Click here for Pure Java version (12673L/73K).

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

Author comment

Began life as a copy of #1035010

download  show line numbers  debug dex  old transpilations   

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

No comments. add comment

Snippet ID: #1035029
Snippet name: G22SkeletonToMesh_v2 (now a separate object from the mesh itself)
Eternal ID of this version: #1035029/15
Text MD5: 8f0f3c7d31d8b307335a28660b4600a7
Transpilation MD5: 9420f6b76e1061eb2ed66041adb2a575
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:55:26
Source code size: 6751 bytes / 218 lines
Pitched / IR pitched: No / No
Views / Downloads: 112 / 233
Version history: 14 change(s)
Referenced in: [show references]