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
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))
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
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