def catMap(delaunayMap, rectified = True, includeTerminalPositions = False): """catMap(delaunayMap, rectified = True, includeTerminalPositions = False) Extract a CAT (chordal axis transform) from a GeoMap object containing a Delaunay Triangulation. Implements the rectified version (from L.Prasad's DGCI'05 article), which also works for GeoMaps with non-triangular Faces. Expects outer faces of the delaunayMap to be marked with the OUTER_FACE flag (in order to skip these faces and work only on the inner parts of the shapes), and edges which belong to the constrained segments of the CDT to be marked with the CONTOUR_SEGMENT flag (i.e. an edge is a chord iff 'not edge.flag(CONTOUR_SEGMENT)') Usually, you will do sth. along: >>> del1 = delaunay.faceCDTMap(map1.face(173), map1.imageSize()) >>> cat1 = delaunay.catMap(del1, rectified = False) The optional parameter `rectified` has been set to False here to use the original definition of junction node positions (using the circumcenter if possible) which works only for triangulations (and is therefore disabled by default). For triangulations, it is also possible to include the opposite vertex of the terminal triangles into the skeleton by setting `includeTerminalPositions` to True. If you want to use the rectified CAT (with weak chords being suppressed), you would use removeWeakChords before calling catMap: >>> del2 = copy.copy(del1) # if you want to keep the original >>> removeWeakChords(del2) >>> rcat2 = delaunay.catMap(del2) The resulting map will have additional attributes that relate the skeleton with the original boundary: subtendedLengths: This is a list mapping edge labels of the skeleton to the accumulated lengths of all contour segments within the contours of the sleeve- and terminal-faces that comprise the sleeve represented by that skeleton edge. shapeWidths: This is a list mapping edge labels of the skeleton to lists of shape widths at each support point of the polygon (may be None at the ends). In theory, this allows to reconstruct the original boundary and thus makes the CAT fully reversible (see Prasad's papers). nodeChordLabels: This is a list mapping node labels of the skeleton to lists of (sleeveLabel, chordLabel) pairs, where sleeveLabel is the label of a skeleton edge and chordLabel is the dart label of the first chord from the `delaunayMap` within that sleeve. (This is needed for later corrections of the junction node positions after pruning.)""" if rectified: junctionPos = rectifiedJunctionNodePosition assert not includeTerminalPositions, \ "includeTerminalPositions is not supported for the rectified CAT!" else: junctionPos = junctionNodePosition result = GeoMap(delaunayMap.imageSize()) result.subtendedLengths = [0] # unused Edge 0 result.shapeWidths = [None] result.nodeChordLabels = [] faceChords = [None] * delaunayMap.maxFaceLabel() boundaryLength = [None] * delaunayMap.maxFaceLabel() faceType = [None] * delaunayMap.maxFaceLabel() nodeLabel = [None] * delaunayMap.maxFaceLabel() for face in delaunayMap.faceIter(skipInfinite = True): if face.flag(OUTER_FACE): continue assert face.holeCount() == 0 chords = [] bl = 0.0 for dart in face.contour().phiOrbit(): if not dart.edge().flag(CONTOUR_SEGMENT): chords.append(dart) else: bl += dart.edge().length() boundaryLength[face.label()] = bl faceChords[face.label()] = chords # classify into terminal, sleeve, and junction triangles: if len(chords) < 2: faceType[face.label()] = "T" if chords: nodePos = middlePoint(chords[0]) # (may be changed later) elif len(chords) == 2: faceType[face.label()] = "S" else: faceType[face.label()] = "J" nodePos = junctionPos(chords) # add nodes for non-sleeve triangles: if faceType[face.label()] != "S" and chords: nodeLabel[face.label()] = result.addNode(nodePos).label() result.nodeChordLabels.append([]) for face in delaunayMap.faceIter(skipInfinite = True): if face.flag(OUTER_FACE): continue if faceType[face.label()] != "S": #print faceType[face.label()] + "-triangle", face.label() for dart in faceChords[face.label()]: #print "following limb starting with", dart startNode = result.node(nodeLabel[face.label()]) startDart = dart.clone() edgePoints = [] subtendedBoundaryLength = 0.0 shapeWidth = [] if faceType[face.label()] == "T": subtendedBoundaryLength += boundaryLength[face.label()] if includeTerminalPositions: # include opposite position startNode.setPosition((dart.clone().nextPhi())[-1]) while True: edgePoints.append(middlePoint(dart)) shapeWidth.append(dart.edge().length()) dart.nextAlpha() nextFace = dart.leftFace() if faceType[nextFace.label()] == "S": subtendedBoundaryLength += boundaryLength[nextFace.label()] # continue with opposite dart: if dart == faceChords[nextFace.label()][0]: dart = faceChords[nextFace.label()][1] else: dart = faceChords[nextFace.label()][0] else: faceChords[nextFace.label()].remove(dart) break endNode = result.node(nodeLabel[nextFace.label()]) if faceType[nextFace.label()] == "T": subtendedBoundaryLength += boundaryLength[nextFace.label()] if includeTerminalPositions: endNode.setPosition((dart.clone().nextPhi())[-1]) flags = 0 if not edgePoints or edgePoints[0] != startNode.position(): edgePoints.insert(0, startNode.position()) shapeWidth.insert(0, None) flags |= START_NODE_ADDED if edgePoints[-1] != endNode.position(): edgePoints.append(endNode.position()) shapeWidth.append(None) flags |= END_NODE_ADDED if len(edgePoints) < 2: # don't add edges with only 1 point (two adjacent # T-triangles) - may fail for J-faces?! # but J-faces should not have nodes on their borders assert endNode.position() == startNode.position() and \ endNode.isIsolated() and startNode.isIsolated() nodeLabel[nextFace.label()] = nodeLabel[face.label()] result.removeIsolatedNode(endNode) continue sleeve = result.addEdge( startNode, endNode, edgePoints) sleeve.setFlag(flags) result.subtendedLengths.append(subtendedBoundaryLength) result.shapeWidths.append(shapeWidth) if faceType[face.label()] == "J": result.nodeChordLabels[startNode.label()].append( (sleeve.label(), startDart.label())) if faceType[nextFace.label()] == "J": result.nodeChordLabels[endNode.label()].append( (sleeve.label(), dart.label())) result.initializeMap(initLabelImage = False) return result
def pyCrackEdgeGraph(labelImage, eightConnectedRegions=True, progressHook=None): result = GeoMap(labelImage.size()) cc = crackConnectionImage(labelImage) if eightConnectedRegions: for y in range(1, cc.height() - 1): for x in range(1, cc.width() - 1): if cc[x, y] == CONN_ALL4: if labelImage[x, y] == labelImage[x - 1, y - 1]: cc[x, y] += CONN_DIAG_UPLEFT if labelImage[x - 1, y] == labelImage[x, y - 1]: cc[x, y] += CONN_DIAG_UPRIGHT # crossing regions? if cc[x, y] == CONN_ALL4 + CONN_DIAG_UPLEFT + CONN_DIAG_UPRIGHT: # preserve connectedness of higher label: if labelImage[x, y - 1] > labelImage[x - 1, y - 1]: cc[x, y] -= CONN_DIAG_UPLEFT else: cc[x, y] -= CONN_DIAG_UPRIGHT for y in range(cc.height()): for x in range(cc.width()): conn = int(cc[x, y]) if degree[conn] > 2: cc[x, y] = conn | CONN_NODE elif conn & CONN_ALL4 == (CONN_RIGHT | CONN_DOWN): cc[x, y] = conn | CONN_MAYBE_NODE if conn & CONN_DIAG: cc[x, y] = conn | CONN_MAYBE_NODE nodeImage = ScalarImage(cc.size()) # nodeImage encoding: each pixel's higher 28 bits encode the # (label + 1) of a node that has been inserted into the resulting # GeoMap at the corresponding position (+1 because zero is a valid # node label), while the lower 4 bits encode the four CONN_ # directions in which a GeoMap edge is already connected to this # node progressHook = progressHook and progressHook.rangeTicker(cc.height()) for startAt in (CONN_NODE, CONN_MAYBE_NODE): for y in range(cc.height()): if progressHook: progressHook() for x in range(cc.width()): nodeConn = int(cc[x, y]) if nodeConn & startAt: startNodeInfo = int(nodeImage[x, y]) if startNodeInfo: startNode = result.node((startNodeInfo >> 4) - 1) else: startNode = result.addNode((x - 0.5, y - 0.5)) nodeImage[x, y] = startNodeInfo = \ (startNode.label() + 1) << 4 for direction, startConn in enumerate(connections): if nodeConn & startConn and not startNodeInfo & startConn: edge, endPos, endConn = followEdge( cc, (x, y), direction) endNodeInfo = int(nodeImage[endPos]) if not endNodeInfo: endNode = result.addNode( (endPos[0] - 0.5, endPos[1] - 0.5)) endNodeInfo = (endNode.label() + 1) << 4 else: assert not endNodeInfo & endConn, "double connection?" endNode = result.node((endNodeInfo >> 4) - 1) edge = result.addEdge(startNode, endNode, edge) startNodeInfo |= startConn if edge.isLoop(): startNodeInfo |= endConn nodeImage[x, y] = startNodeInfo else: nodeImage[x, y] = startNodeInfo nodeImage[endPos] = endNodeInfo | endConn return result
if not len(possible): break operation = random.choice(possible) changed = False if operation == 0: if mec == None: mec = mergeEdgesCandidates(map) if not len(mec): possible.remove(0) else: nodeLabel = random.choice(mec) mec.remove(nodeLabel) dart = map.node(nodeLabel).anchor() print "removing node %d via %s" % (nodeLabel, dart) # mergePos = dart[0] survivor = map.mergeEdges(dart) if survivor: # assert mergePos in [survivor[i] for i in survivor.mergeIndices], \ # "mergeIndices do not point to merge position!" changed = True if operation == 1: if rbc == None: rbc = removeBridgeCandidates(map) if not len(rbc): possible.remove(1) else: dartLabel = random.choice(rbc)
def pyCrackEdgeGraph(labelImage, eightConnectedRegions = True, progressHook = None): result = GeoMap(labelImage.size()) cc = crackConnectionImage(labelImage) if eightConnectedRegions: for y in range(1, cc.height()-1): for x in range(1, cc.width()-1): if cc[x,y] == CONN_ALL4: if labelImage[x,y] == labelImage[x-1,y-1]: cc[x,y] += CONN_DIAG_UPLEFT if labelImage[x-1,y] == labelImage[x,y-1]: cc[x,y] += CONN_DIAG_UPRIGHT # crossing regions? if cc[x,y] == CONN_ALL4 + CONN_DIAG_UPLEFT + CONN_DIAG_UPRIGHT: # preserve connectedness of higher label: if labelImage[x,y-1] > labelImage[x-1,y-1]: cc[x,y] -= CONN_DIAG_UPLEFT else: cc[x,y] -= CONN_DIAG_UPRIGHT for y in range(cc.height()): for x in range(cc.width()): conn = int(cc[x,y]) if degree[conn] > 2: cc[x,y] = conn | CONN_NODE elif conn & CONN_ALL4 == (CONN_RIGHT | CONN_DOWN): cc[x,y] = conn | CONN_MAYBE_NODE if conn & CONN_DIAG: cc[x,y] = conn | CONN_MAYBE_NODE nodeImage = ScalarImage(cc.size()) # nodeImage encoding: each pixel's higher 28 bits encode the # (label + 1) of a node that has been inserted into the resulting # GeoMap at the corresponding position (+1 because zero is a valid # node label), while the lower 4 bits encode the four CONN_ # directions in which a GeoMap edge is already connected to this # node progressHook = progressHook and progressHook.rangeTicker(cc.height()) for startAt in (CONN_NODE, CONN_MAYBE_NODE): for y in range(cc.height()): if progressHook: progressHook() for x in range(cc.width()): nodeConn = int(cc[x, y]) if nodeConn & startAt: startNodeInfo = int(nodeImage[x, y]) if startNodeInfo: startNode = result.node((startNodeInfo >> 4) - 1) else: startNode = result.addNode((x - 0.5, y - 0.5)) nodeImage[x, y] = startNodeInfo = \ (startNode.label() + 1) << 4 for direction, startConn in enumerate(connections): if nodeConn & startConn and not startNodeInfo & startConn: edge, endPos, endConn = followEdge( cc, (x, y), direction) endNodeInfo = int(nodeImage[endPos]) if not endNodeInfo: endNode = result.addNode((endPos[0] - 0.5, endPos[1] - 0.5)) endNodeInfo = (endNode.label() + 1) << 4 else: assert not endNodeInfo & endConn, "double connection?" endNode = result.node((endNodeInfo >> 4) - 1) edge = result.addEdge(startNode, endNode, edge) startNodeInfo |= startConn if edge.isLoop(): startNodeInfo |= endConn nodeImage[x, y] = startNodeInfo else: nodeImage[x, y] = startNodeInfo nodeImage[endPos] = endNodeInfo | endConn return result