def correctorStep(siv, level, pos, epsilon=1e-8): """Perform corrector step, i.e. perform 1D iterative Newton method in direction of gradient in order to return to zero level (with accuracy given by epsilon).""" x, y = pos n = Vector2(siv.dx(x, y), siv.dy(x, y)) n /= numpy.linalg.norm(n) for k in range(100): value = siv(x, y) - level if abs(value) < epsilon: break g = numpy.dot(Vector2(siv.dx(x, y), siv.dy(x, y)), n) if not g: sys.stderr.write("WARNING: correctorStep: zero gradient!\n") break # FIXME: return None instead? correction = -value * n / g # prevent too large steps (i.e. if norm(g) is small): if correction.squaredMagnitude() > 0.25: correction /= 20 * numpy.linalg.norm(correction) x += correction[0] y += correction[1] if not siv.isInside(x, y): return None # out of range return Vector2(x, y)
def addNodeDirectX(x, y, ofs): pos = Vector2(x + ofs, y) # out of three successive pixels, the middle one may be the # threshold, then we would get duplicate points already in the # horizontal pass: node = result.nearestNode(pos, 1e-8) return node or result.addNode(pos)
def followEdge(crackConnectionImage, pos, direction): """Follow edge starting at `pos` in `direction` until crackConnectionImage[pos] has the CONN_NODE bit set, or until we arrive at `pos` again (self-loop). Any CONN_MAYBE_NODE bits along the way are cleared.""" pos = Point2D(pos[0], pos[1]) vPos = Vector2(pos[0] - 0.5, pos[1] - 0.5) result = Polygon([vPos]) startPos = copy.copy(pos) while True: vPos += _dirVector[direction] result.append(vPos) pos += _dirOffset[direction] if pos == startPos: break connection = int(crackConnectionImage[pos]) if connection & CONN_DIAG: if connection & CONN_DIAG_UPLEFT: turnLeft = direction in (DIR_NORTH, DIR_SOUTH) else: turnLeft = direction in (DIR_EAST, DIR_WEST) connection &= ~connections[(direction + 2) % 4] if turnLeft: direction = _turnLeft[direction] else: direction = _turnRight[direction] connection &= ~connections[direction] if not connection & CONN_ALL4: connection &= ~CONN_MAYBE_NODE crackConnectionImage[pos] = connection continue elif connection & CONN_NODE: break if connection & CONN_MAYBE_NODE: # we simply pass over it, but we do not want to start a # new edge here during further down in the process: crackConnectionImage[pos] = connection & ~CONN_MAYBE_NODE direction = _turnRight[direction] while connection & connections[direction] == 0: direction = _turnLeft[direction] return result, pos, connections[(direction + 2) % 4]
def cannyEdgeMap(image, scale, thresh): """cannyEdgeMap(image, scale, thresh) Returns a subpixel-GeoMap object containing thinned canny edges obtained from cannyEdgeImage(image, scale, thresh). (Internally creates a pixel GeoMap first.)""" edgeImage = vigra.analysis.cannyEdgeImageWithThinning( image, scale, thresh, 1) pixelMap = cellimage.GeoMap(edgeImage, 0, cellimage.CellType.Line) spmap = pixelMap2subPixelMap(pixelMap, offset=Vector2(1, 1), imageSize=image.size()) return spmap
def followContour(siv, level, geomap, nodeLabel, h): correctorEpsilon = 1e-6 nodeCrossingDist = 0.01 #global pos, ip, poly, startNode, diff, npos, nip, intersection startNode = geomap.node(nodeLabel) pos = startNode.position() ix = int(pos[0]) iy = int(pos[1]) poly = [pos] while True: npos, nh = predictorCorrectorStep(siv, level, pos, h, correctorEpsilon) h = max(min(h, nh), 1e-5) nix = int(npos[0]) niy = int(npos[1]) if nix != ix or niy != iy: # determine grid intersection: diff = npos - pos if nix != ix: intersectionX = round(npos[0]) intersectionY = pos[1] + (intersectionX - pos[0]) * diff[1] / diff[0] else: intersectionY = round(npos[1]) intersectionX = pos[0] + (intersectionY - pos[1]) * diff[0] / diff[1] intersection = Vector2(intersectionX, intersectionY) # connect to crossed Node: endNode = geomap.nearestNode(intersection, nodeCrossingDist) if not endNode: sys.stderr.write( "WARNING: level contour crossing grid at %s without intersection Node!\n" % repr(intersection)) elif endNode.label() != startNode.label() or len(poly) >= 3: # FIXME: better criterion than len(poly) would be poly.length() poly.append(endNode.position()) geomap.addEdge(startNode, endNode, poly) if not endNode.degree() % 2: return poly = [endNode.position()] startNode = endNode ix = nix iy = niy if nh is None: return # out of image range (/no convergence) poly.append(npos) pos = npos
def findZeroCrossingsOnGrid(siv, level, minDist=0.1): result = [] existing = geomap.PositionedMap() minDist2 = minDist * minDist def addIntersection(p): if not existing(p, minDist2): result.append(p) existing.insert(p, True) for y in range(siv.height() - 1): for x in range(siv.width() - 1): coeff = siv.coefficients(x, y) xPoly = [coeff[k, 0] for k in range(coeff.width())] xPoly[0] -= level try: for k in vigra.polynomialRealRoots(xPoly): if k < 0.0 or k >= 1.0: continue addIntersection(Vector2(x + k, y)) except Exception, e: sys.stderr.write( "WARNING: no convergence in polynomialRealRoots(%s):\n %s\n" % (xPoly, e)) yPoly = [coeff[0, k] for k in range(coeff.height())] yPoly[0] -= level try: for k in vigra.polynomialRealRoots(yPoly): if k < 0.0 or k >= 1.0: continue addIntersection(Vector2(x, y + k)) except Exception, e: sys.stderr.write( "WARNING: no convergence in polynomialRealRoots(%s):\n %s\n" % (yPoly, e))
for y in range(labelImage.height()): result[0, y] += CONN_DOWN result[labelImage.width(), y] += CONN_DOWN result[0, y + 1] += CONN_UP result[labelImage.width(), y + 1] += CONN_UP return result DIR_EAST = 0 DIR_NORTH = 1 DIR_WEST = 2 DIR_SOUTH = 3 _dirOffset = [Size2D(1, 0), Size2D(0, -1), Size2D(-1, 0), Size2D(0, 1)] _dirVector = [Vector2(1, 0), Vector2(0, -1), Vector2(-1, 0), Vector2(0, 1)] _turnRight = [3, 0, 1, 2] _turnLeft = [1, 2, 3, 0] _debugDir = ("east", "north", "west", "south") def followEdge(crackConnectionImage, pos, direction): """Follow edge starting at `pos` in `direction` until crackConnectionImage[pos] has the CONN_NODE bit set, or until we arrive at `pos` again (self-loop). Any CONN_MAYBE_NODE bits along the way are cleared.""" pos = Point2D(pos[0], pos[1]) vPos = Vector2(pos[0] - 0.5, pos[1] - 0.5) result = Polygon([vPos]) startPos = copy.copy(pos)
def pixelMap2subPixelMap(pixelMap, scale=1.0, offset=Vector2(0, 0), imageSize=None, skipEverySecond=False, midCracks=False): """Extract node positions and edge geometry from a cellimage.GeoMap object and returns a new subpixel-GeoMap object initialized with it. For nodes, this function simply uses the center of their bounding box. All positions are shifted by the optional offset and then scaled with the given factor. The imageSize defaults to the (scaled) pixel-based pixelMap's cellImage.size(). Set skipEverySecond to True if the pixelMap contains a crack edge map (otherwise, each resulting edge segment will have an additional mid crack point). If you set midCracks to True, the edge geometry will include the midpoints of each crack instead of the endpoints (skipEverySecond is implied by/ignored if midCracks == True).""" if imageSize == None: imageSize = pixelMap.cellImage.size() * scale result = geomap.GeoMap(imageSize=imageSize) if midCracks: skipEverySecond = False nodes = [None] * (pixelMap.maxNodeLabel() + 1) for node in pixelMap.nodes: ul = node.bounds.upperLeft() center = Vector2(ul[0], ul[1]) + \ Vector2(node.bounds.width() - 1, node.bounds.height() - 1) / 2 nodes[node.label] = result.addNode((center + offset) * scale) # mark as sorted (sigma order will be copied from source map): result.sortEdgesDirectly() undesirable = [] edges = [None] * (pixelMap.maxEdgeLabel() + 1) for edge in pixelMap.edges: it = iter(edge.start) startPos = it.nodePosition() points = list(it) if midCracks: points = points[::2] endPos = it.nodePosition() points.insert(0, startPos) points.append(endPos) if skipEverySecond: points = points[::2] points = [(Vector2(p[0], p[1]) + offset) * scale for p in points] startNeighbor = nodes[edge.start.startNodeLabel()] endNeighbor = nodes[edge.end.startNodeLabel()] if startNeighbor.position() != points[0]: points.insert(0, startNeighbor.position()) if endNeighbor.position() != points[-1]: points.append(endNeighbor.position()) # re-use sigma order from source map: if not startNeighbor.isIsolated(): neighbor = cellimage.GeoMap.DartTraverser(edge.start) while neighbor.nextSigma().edgeLabel() >= edge.label: pass startNeighbor = edges[neighbor.edgeLabel()].dart() if neighbor == neighbor.edge().end: startNeighbor.nextAlpha() if not endNeighbor.isIsolated(): neighbor = cellimage.GeoMap.DartTraverser(edge.end) while neighbor.nextSigma().edgeLabel() >= edge.label: pass endNeighbor = edges[neighbor.edgeLabel()].dart() if neighbor == neighbor.edge().end: endNeighbor.nextAlpha() newEdge = result.addEdge(startNeighbor, endNeighbor, points) edges[edge.label] = newEdge if newEdge.isLoop() and newEdge.partialArea() == 0.0: undesirable.append(newEdge.dart()) for dart in undesirable: result.removeEdge(dart) result.initializeMap() # the border closing was done in C++, so we have to mark the # border edges manually: assert result.face(0).holeCount() == 1 for dart in result.face(0).holeContours().next().phiOrbit(): assert not dart.leftFaceLabel() dart.edge().setFlag(BORDER_PROTECTION) return result
def samplingPoints(img, threshold=128): return [ Vector2(pos[0], pos[1]) for pos in img.size() if img[pos] < threshold ]
def tangentDir(siv, pos): result = Vector2(-siv.dy(pos[0], pos[1]), siv.dx(pos[0], pos[1])) return result / numpy.linalg.norm(result)
def addNodeDirectY(x, y, ofs): pos = Vector2(x, y + ofs) node = result.nearestNode(pos, 1e-8) # already exists? (e.g. hNodes?) return node or result.addNode(pos)