def _isPointWithHead(point, head, tip):
    "Returns true if point is close enough and within the cone of the head stroke"
    distanceThresh = 1 
    distanceThresh *= distanceThresh #Keep up with squared distances

    ep1 = head.Points[0]
    ep2 = head.Points[-1]
    #              *  tip
    #           o     o
    #          o        o
    #        o            o
    #      o      (x)      o
    #          midpoint
    #
    #             * endpoint
    #            o
    #            o
    #           o
    #         (etc)
    midpoint = Point((ep1.X + ep2.X)/2, (ep1.Y + ep2.Y)/2) #Mid-way between the two endpoints of the arrowhead
    tip_to_endpoint = GeomUtils.pointDistanceSquared(point.X, point.Y, tip.X, tip.Y)
    tip_to_backofarrowhead =  GeomUtils.pointDistanceSquared(tip.X, tip.Y, midpoint.X, midpoint.Y)
    endpoint_to_backofarrowhead = GeomUtils.pointDistanceSquared(point.X, point.Y, midpoint.X, midpoint.Y)
    
    #logger.debug("tip_to_endpoint: %s\n, tip_to_backofarrowhead: %s,\n endpoint_to_backofarrowhead: %s" % (tip_to_endpoint, tip_to_backofarrowhead, endpoint_to_backofarrowhead))
    #Tail's endpoint is close to the end of the arrowhead, or even closer to the tip of the arrowhead
    if tip_to_backofarrowhead >= endpoint_to_backofarrowhead or tip_to_backofarrowhead >= tip_to_endpoint:
        if GeomUtils.pointInAngleCone(point, ep1, tip, ep2):
            return True
    return False
Exemple #2
0
def _isPointWithHead(point, head, tip):
    "Returns true if point is close enough and within the cone of the head stroke"
    ep1 = head.Points[0]
    ep2 = head.Points[-1]
    midpoint = Point((ep1.X + ep2.X)/2, (ep1.Y + ep2.Y)/2)
    tip_to_endpoint = GeomUtils.pointDistanceSquared(point.X, point.Y, tip.X, tip.Y)
    tip_to_backofarrowhead =  GeomUtils.pointDistanceSquared(tip.X, tip.Y, midpoint.X, midpoint.Y)
    
    if tip_to_endpoint < tip_to_backofarrowhead:
        if GeomUtils.pointInAngleCone(point, ep1, tip, ep2):
            return True
    return False
    def onStrokeAdded( self, stroke ):
        #If it's a closed figure, it is its own wall
        rtm_logger.debug("Stroke Added")
        newWallDict = {'closed': False, 'matches': {}}
        ep1 = stroke.Points[0]
        ep2 = stroke.Points[-1]
        strokeLen = GeomUtils.strokeLength(stroke)

        addToWalls = True
        if GeomUtils.pointDistanceSquared(ep1.X, ep1.Y, ep2.X, ep2.Y) < (strokeLen  * 0.05) ** 2:
            rtm_logger.debug("Closed stroke")
            newWallDict['closed'] = True

        rtm_logger.debug("Adding stroke as possible future wall")
        self.wallInfo[stroke] = newWallDict
        #self.linkStrokesTogether()

        for testStroke, wallDict in self.wallInfo.items():
            gran = min(len(stroke.Points), len(testStroke.Points))
            if wallDict['closed'] and GeomUtils.strokeContainsStroke(testStroke, stroke, granularity = gran):
                outStk = testStroke
                inStk = stroke
            elif newWallDict['closed'] and GeomUtils.strokeContainsStroke(stroke, testStroke, granularity = gran):
                outStk = stroke
                inStk = testStroke
            else:
                continue

            rtm_logger.debug("Found containment with another stroke")
            rtAnno = RaceTrackAnnotation(rightwalls = [outStk], leftwalls = [inStk]) 
            BoardSingleton().AnnotateStrokes([stroke, testStroke], rtAnno)
            del(self.wallInfo[testStroke])
            addToWalls = False
            break
    def refreshTuringMachines(self):
        labelEdgeMatchingThresh = 2000 # how many times greater than the median we'll match edges

        labelEdgeMatches = {} # { label : {edge, distance} }

        for tmAnno in set(self.tmMap.keys()):
            BoardSingleton().RemoveAnnotation(tmAnno)
            del(self.tmMap[tmAnno])

        for textAnno in self.labelMap.keys():
            labelTL, labelBR = GeomUtils.strokelistBoundingBox(textAnno.Strokes)
            #Midpoint of the labe's bounding box
            labelCenterPt = Point ( (labelTL.X + labelBR.X) / 2.0, (labelTL.Y + labelBR.Y) / 2.0) 

            labelMatchDict = labelEdgeMatches.setdefault(textAnno, {}) 

            for graphAnno in self.graphMap:
                #Match edges to labels
                for edgeAnno in graphAnno.edge_set:
                    edgeLabelPoints = GeomUtils.strokeNormalizeSpacing(edgeAnno.tailstroke, 19).Points #Midpoint in the arrow-stroke
                    for elp in edgeLabelPoints:
                        dist = GeomUtils.pointDistanceSquared(elp.X, elp.Y, labelCenterPt.X, labelCenterPt.Y)
                        #labelMatchDict['allmatches'].append({'anno': edgeAnno, 'dist': dist})
                        if 'bestmatch' not in labelMatchDict or dist < labelMatchDict['bestmatch'][1]:
                            labelMatchDict['bestmatch'] = (edgeAnno, dist)

        #labelEdgeMatches contains each label paired with its best edge
        
        #Have each edge claim a label
        edge2LabelMatching = {}
        for textAnno, matchDict in labelEdgeMatches.items():
            if 'bestmatch' in matchDict: # and matchDict['bestmatch'][1] < labelEdgeMatchingThresh:
                edgeLabelList = edge2LabelMatching.setdefault(matchDict['bestmatch'][0], [])
                edgeLabelList.append(textAnno)
            else:
                tm_logger.debug("TextAnno %s not matched to an edge" % (textAnno.text))

        #Make the associations and add the turing machine annotation
        for graphAnno, tmAnno in self.graphMap.items():
            assocSet = set([graphAnno])
            shouldAddAnno = False
            if tmAnno == None:
                shouldAddAnno = True
                tmAnno = TuringMachineAnnotation(state_graph_anno = graphAnno)

            for edgeAnno in graphAnno.edge_set:
                if edge2LabelMatching.get(edgeAnno, None) is not None:
                    assocLabelsList = edge2LabelMatching[edgeAnno]
                    for label in assocLabelsList:
                        assocSet.add(label)
                        tmAnno.assocLabel2Edge(label, edgeAnno)

            if shouldAddAnno:
                BoardSingleton().AnnotateStrokes(tmAnno.getAssociatedStrokes(), tmAnno)
                self.tmMap[tmAnno] = assocSet
            else:
                BoardSingleton().UpdateAnnotation(tmAnno, new_strokes = tmAnno.getAssociatedStrokes())
                self.tmMap[tmAnno] = assocSet
    def onStrokeAdded(self, stroke):
        "Tags 1's and 0's as letters (TextAnnotation)"
        closedDistRatio = 0.22
        circularityThresh_0 = 0.80
        circularityThresh_1 = 0.20
        strokeLen = max(GeomUtils.strokeLength(stroke), 1)
        normDist = max(3, strokeLen / 5)
        head, tail = stroke.Points[0], stroke.Points[-1]

        endDist = GeomUtils.pointDistance(head.X, head.Y, tail.X, tail.Y)
        #If the endpoints are 1/thresh apart, actually close the thing
        isClosedShape = GeomUtils.pointDistanceSquared(head.X, head.Y, tail.X, tail.Y) \
                        < (strokeLen * closedDistRatio) ** 2

        if isClosedShape: #Close the shape back up
            s_norm = GeomUtils.strokeNormalizeSpacing( Stroke(stroke.Points + [stroke.Points[0]]) , normDist ) 
        else:
            s_norm = GeomUtils.strokeNormalizeSpacing( stroke , normDist ) 
        curvatures = GeomUtils.strokeGetPointsCurvature(s_norm)
        circularity = GeomUtils.strokeCircularity( s_norm ) 

        if isClosedShape and circularity > circularityThresh_0:
            height = stroke.BoundTopLeft.Y - stroke.BoundBottomRight.Y
            oAnnotation = TextAnnotation("0", height)
            l_logger.debug("Annotating %s with %s" % ( stroke, oAnnotation))
            BoardSingleton().AnnotateStrokes( [stroke],  oAnnotation)
            l_logger.debug(" Afterward: %s.annotations is %s" % ( stroke, stroke.Annotations))

        elif len(stroke.Points) >= 2 \
            and max(curvatures) < 0.5 \
            and circularity < circularityThresh_1:
                if stroke.Points[0].X < stroke.Points[-1].X + strokeLen / 2.0 \
                and stroke.Points[0].X > stroke.Points[-1].X - strokeLen / 2.0:
                    height = stroke.BoundTopLeft.Y - stroke.BoundBottomRight.Y
                    oneAnnotation = TextAnnotation("1", height)
                    l_logger.debug("Annotating %s with %s" % ( stroke, oneAnnotation.text))
                    BoardSingleton().AnnotateStrokes( [stroke],  oneAnnotation)
                    l_logger.debug(" Afterward: %s.annotations is %s" % ( stroke, stroke.Annotations))
                elif stroke.Points[0].Y < stroke.Points[-1].Y + strokeLen / 2.0 \
                and stroke.Points[0].Y > stroke.Points[-1].Y - strokeLen / 2.0:
                    width = stroke.BoundBottomRight.X - stroke.BoundTopLeft.X 
                    dashAnnotation = TextAnnotation("-", width * 1.5) #Treat the dash's (boosted) width as its scale 
                    l_logger.debug("Annotating %s with %s" % ( stroke, dashAnnotation.text))
                    BoardSingleton().AnnotateStrokes( [stroke],  dashAnnotation)
        else:
            if not isClosedShape:
                l_logger.debug("0: Not a closed shape")
            if not (circularity > circularityThresh_0):
                l_logger.debug("0: Not circular enough: %s" % (circularity))
            if not len(stroke.Points) >= 2:
                l_logger.debug("1: Not enough points")
            if not (circularity < circularityThresh_1):
                l_logger.debug("1: Too circular")
            if not (max(curvatures) < 0.5):
                l_logger.debug("1: Max curvature too big %s" % max(curvatures))
            if not ( stroke.Points[0].X < stroke.Points[-1].X + strokeLen / 3 \
               and   stroke.Points[0].X > stroke.Points[-1].X - strokeLen / 3):
                l_logger.debug("1: Not vertical enough: \nX1 %s, \nX2 %s, \nLen %s" % (stroke.Points[0].X, stroke.Points[-1].X, strokeLen))
Exemple #6
0
def _isPointWithHead(tailpoints, head, tip):
    "Returns true if point is close enough and within the cone of the head stroke"

    distanceThresh = 1 
    distanceThresh *= distanceThresh #Keep up with squared distances

    point = tailpoints[0]
    ep1 = head.Points[0]
    ep2 = head.Points[-1]
    #              *  tip
    #           o     o
    #          o        o
    #        o            o
    #      o      (x)      o
    #          midpoint
    #
    #             * endpoint
    #            o
    #            o
    #           o
    #         (etc)
    midpoint = Point((ep1.X + ep2.X)/2, (ep1.Y + ep2.Y)/2) #Mid-way between the two endpoints of the arrowhead
    tip_to_endpoint = GeomUtils.pointDistanceSquared(point.X, point.Y, tip.X, tip.Y)
    tip_to_backofarrowhead =  GeomUtils.pointDistanceSquared(tip.X, tip.Y, midpoint.X, midpoint.Y)
    endpoint_to_backofarrowhead = GeomUtils.pointDistanceSquared(point.X, point.Y, midpoint.X, midpoint.Y)

    #fuzz = math.sqrt(tip_to_backofarrowhead) #Number of pixels to fuzz the "in-angle-cone" test
    
    #logger.debug("tip_to_endpoint: %s\n, tip_to_backofarrowhead: %s,\n endpoint_to_backofarrowhead: %s" % (tip_to_endpoint, tip_to_backofarrowhead, endpoint_to_backofarrowhead))
    #Tail's endpoint is close to the end of the arrowhead, or even closer to the tip of the arrowhead
    if tip_to_backofarrowhead >= endpoint_to_backofarrowhead or tip_to_backofarrowhead >= tip_to_endpoint:
        logger.debug("Distance from head-tip to tail-endpoint is good!")
        #Check the in-angle cone progressively down the tail
        epList = tailpoints[: len(tailpoints) / 3]

        for pt in epList:
            if GeomUtils.pointInAngleCone(pt, ep1, tip, ep2):
                logger.debug("Endpoint inside angle cone")
                return True
    return False
    def onStrokeAdded( self, stroke ):
        "Watches for Strokes with Circularity > threshold to Annotate"
        # need at least 6 points to be a circle
	if stroke.length()<6:
            return
        strokeLen = GeomUtils.strokeLength(stroke)
        distCutoff = NodeMarker.CLOSED_DIST_THRESH * (strokeLen ** 2)
	#s_norm = GeomUtils.strokeNormalizeSpacing( stroke, 20 ) 
        ep1 = stroke.Points[0]
        ep2 = stroke.Points[-1]

        epDist = GeomUtils.pointDistanceSquared(ep1.X, ep1.Y, ep2.X, ep2.Y)
        if epDist <= distCutoff:
            avgDist = GeomUtils.averageDistance( stroke.Center, stroke.Points )
            self.getBoard().AnnotateStrokes([stroke], DiGraphNodeAnnotation(0, stroke.Center, avgDist))
        else:
            node_log.debug("Not a node: endpoint distance %s > %s" % (epDist, distCutoff))
    def tagBox(self, stroke):

        endPointDistPct = 0.10 #How close (as % of length) the points have to be to each other
        boxApproxThresh = 50000 #The DTW distance between the stroke and how it best fits a box
        stkLen = GeomUtils.strokeLength(stroke)
        ep1, ep2 = stroke.Points[0], stroke.Points[-1]
        epDistSqr = GeomUtils.pointDistanceSquared(ep1.X, ep1.Y, ep2.X, ep2.Y)
        if  epDistSqr > (endPointDistPct * stkLen) ** 2:
            print "Endpoints aren't close enough to be a box"
            return
        overshoot = max(1, len(stroke.Points)/10)
        norm_stroke = GeomUtils.strokeSmooth(GeomUtils.strokeNormalizeSpacing(Stroke(stroke.Points + stroke.Points[0:overshoot]), numpoints = 70))
        #D.strokeCurvatureHistogram(norm_stroke)
        curvatures = GeomUtils.strokeGetPointsCurvature(norm_stroke)
        corners = set([])
        curvatures_cpy = list(curvatures)
        while len(corners) < 4:
            crnr_idx = curvatures_cpy.index(max(curvatures_cpy))
            crnr = curvatures_cpy[crnr_idx] * 57.295
            for nBor in range(crnr_idx -2, crnr_idx + 3):
                if nBor < len(curvatures_cpy) and nBor > 0:
                    curvatures_cpy[nBor] = 0
            if crnr > 0: #30 and crnr < 150:
                #Have a curvature, and we haven't already classified its immed neighbors as a corner
                corners.add(crnr_idx)
            else:
                break
        if len(corners) != 4:
            return
        else:
            c_list = [norm_stroke.Points[c_idx] for c_idx in sorted(list(corners))]
            cornerStroke = Stroke(c_list + c_list[:2])
            boxStroke = GeomUtils.strokeNormalizeSpacing(Stroke(c_list + [c_list[0]]))
            origStroke = GeomUtils.strokeNormalizeSpacing(Stroke(stroke.Points + [stroke.Points[0]]))
            approxAcc = GeomUtils.strokeDTWDist(boxStroke, origStroke)
            print "Box approximates original with %s accuracy" % (approxAcc)
            if approxAcc < boxApproxThresh:
                self.getBoard().AnnotateStrokes([stroke], BoxAnnotation(c_list))
    def refreshTuringMachines(self):

        #Remove all of the current Turing Machines
        for tmAnno in set(self.tmMap.keys()):
            self.getBoard().RemoveAnnotation(tmAnno)
            del(self.tmMap[tmAnno])
        
        #Match all of the labels we've seen with their closest edges
        #    in any graph
        labelEdgeMatches = {} # { label : {edge, distance} }
        for textAnno in self.labelMap.keys():
            labelTL, labelBR = GeomUtils.strokelistBoundingBox(textAnno.Strokes)
            #Midpoint of the label's bounding box
            labelCenterPt = Point ( (labelTL.X + labelBR.X) / 2.0, (labelTL.Y + labelBR.Y) / 2.0) 
            labelMatchDict = labelEdgeMatches.setdefault(textAnno, {}) 

            for graphAnno in self.graphMap:
                #Check to make sure the label doesn't share strokes with the graph structure
                skipThisGraph = False
                for textStroke in textAnno.Strokes:
                    if textStroke in graphAnno.Strokes:
                        skipThisGraph = True
                        break
                if skipThisGraph:
                    continue
                
                #Match edges to labels
                for edgeAnno in graphAnno.edge_set:
                    #Find the best choice among 19 evenly spaced points along the edge tailstroke
                    edgeLabelPoints = GeomUtils.strokeNormalizeSpacing(edgeAnno.tailstroke, 19).Points
                    for elp in edgeLabelPoints:
                        dist = GeomUtils.pointDistanceSquared(elp.X, elp.Y, labelCenterPt.X, labelCenterPt.Y)
                        if 'bestmatch' not in labelMatchDict or dist < labelMatchDict['bestmatch']['dist']:
                            labelMatchDict['bestmatch'] = {'edge': edgeAnno, 'dist': dist}

        #labelEdgeMatches contains each label paired with its best edge
        
        #Get the median size
        sizes = sorted([anno.scale for anno in self.labelMap.keys()])

        if len(sizes) > 0:
            medianSize = sizes[len(sizes) / 2]
        else:
            medianSize = 0

        #Have each edge claim a label
        edge2LabelMatching = {}
        for textAnno, matchDict in labelEdgeMatches.items():
            if 'bestmatch' in matchDict \
                and textAnno.scale < medianSize * TuringMachineCollector.LABELMATCH_DISTANCE[1] \
                and textAnno.scale > medianSize * TuringMachineCollector.LABELMATCH_DISTANCE[0]: 
                edgeLabelList = edge2LabelMatching.setdefault(matchDict['bestmatch']['edge'], [])
                edgeLabelList.append(textAnno)
            else:
                tm_logger.debug("TextAnno %s not matched to an edge" % (textAnno.text))

        #Make the associations and add the turing machine annotation
        for graphAnno, tmAnno in self.graphMap.items():
            assocSet = set([graphAnno])
            shouldAddAnno = False
            if tmAnno == None:
                shouldAddAnno = True
                tmAnno = TuringMachineAnnotation(state_graph_anno = graphAnno)

            for edgeAnno in graphAnno.edge_set:
                if edgeAnno in edge2LabelMatching:
                    assocLabelsList = edge2LabelMatching[edgeAnno]
                    for label in assocLabelsList:
                        assocSet.add(label)
                        tmAnno.assocLabel2Edge(label, edgeAnno)

            if shouldAddAnno:
                self.getBoard().AnnotateStrokes(tmAnno.getAssociatedStrokes(), tmAnno)
                self.tmMap[tmAnno] = assocSet
            else:
                self.getBoard().UpdateAnnotation(tmAnno, new_strokes = tmAnno.getAssociatedStrokes())
                self.tmMap[tmAnno] = assocSet
Exemple #10
0
def _scoreStrokesForLetter(strokelist, letter):
    """Get the confidence score for a group of strokes matching a letter, normalized [0.0-1.0]"""
    retConfidence = 0.0
    #Recognition thresholds
    closedDistRatio = 0.22
    circularityThresh_0 = 0.80
    circularityThresh_1 = 0.20
    maxStraightCurvature = 0.6
    strokesBB = GeomUtils.strokelistBoundingBox(strokelist)

    if len(strokelist) == 0:
        return 0.0

    #The case of a single point
    if strokesBB[0].X == strokesBB[1].X and strokesBB[0].Y == strokesBB[1].Y:
        return 0.0

    #Recognize a zero
    if letter.upper() == "0":
        stroke = strokelist[0]
        strokeLen = max(GeomUtils.strokeLength(stroke), 1)
        normDist = max(3, strokeLen / 5) #granularity of point spacing -- at least 3
        head, tail = stroke.Points[0], stroke.Points[-1]

        endDist = GeomUtils.pointDistance(head.X, head.Y, tail.X, tail.Y)
        #If the endpoints are 1/thresh apart, actually close the thing
        isClosedShape = GeomUtils.pointDistanceSquared(head.X, head.Y, tail.X, tail.Y) \
                        < ( (strokeLen * closedDistRatio) ** 2 )

        if isClosedShape: #Close the shape back up
            s_norm = GeomUtils.strokeNormalizeSpacing( Stroke(stroke.Points + [stroke.Points[0]]) , normDist ) 
        else:
            s_norm = GeomUtils.strokeNormalizeSpacing( stroke , normDist ) 
        #curvatures = GeomUtils.strokeGetPointsCurvature(s_norm)
        circularity = GeomUtils.strokeCircularity( s_norm ) 

        if isClosedShape:
            retConfidence += 0.5
        if circularity > circularityThresh_0:
            retConfidence += 0.5
        return retConfidence
    #Recognize a one
    elif letter.upper() == "1":
        stroke = strokelist[0]
        strokeLen = max(GeomUtils.strokeLength(stroke), 1)
        normDist = max(3, strokeLen / 5) #granularity of point spacing -- at least 3
        s_norm = GeomUtils.strokeNormalizeSpacing( stroke , normDist ) 

        circularity = GeomUtils.strokeCircularity( s_norm ) 
        curvatures = GeomUtils.strokeGetPointsCurvature(s_norm)
        if max(curvatures) < maxStraightCurvature:
            retConfidence += 0.30
        if circularity < circularityThresh_1:
            retConfidence += 0.5
            if stroke.Points[0].X < stroke.Points[-1].X + strokeLen / 2.0 \
            and stroke.Points[0].X > stroke.Points[-1].X - strokeLen / 2.0:
                retConfidence += 0.2
        return retConfidence
    #Recognize a dash
    elif letter.upper() == "-":
        stroke = strokelist[0]
        strokeLen = max(GeomUtils.strokeLength(stroke), 1)
        normDist = max(3, strokeLen / 5) #granularity of point spacing -- at least 3
        s_norm = GeomUtils.strokeNormalizeSpacing( stroke , normDist ) 

        circularity = GeomUtils.strokeCircularity( s_norm ) 
        curvatures = GeomUtils.strokeGetPointsCurvature(s_norm)
        if max(curvatures) < maxStraightCurvature:
            retConfidence += 0.30
        if circularity < circularityThresh_1:
            retConfidence += 0.5
            if stroke.Points[0].Y < stroke.Points[-1].Y + strokeLen / 2.0 \
            and stroke.Points[0].Y > stroke.Points[-1].Y - strokeLen / 2.0:
                retConfidence += 0.2
        return retConfidence
    else:
        return 0.0