def _matchHeadtoTail(self, head = None, tail = None, point = None): """Input head stroke or tail stroke. If head is specified, match it to a tail. If tail is specified, match it to a head. Parameter 'point' should be the tip if head is specified, the end-point if tail is specified. Returns a list of tuples: (tip, head_stroke) if tail is specified, (endpoint, tail_stroke) if head is specified.""" retlist = [] if point is None: return retlist if head is not None and tail is None: #Head is specified, find the tail tip = point ep1, ep2 = head.Points[0], head.Points[-1] headBreadth = GeomUtils.pointDistance(ep1.X, ep1.Y, ep2.X, ep2.Y) for endpoint, tailStroke in self._endpoints: if GeomUtils.strokeLength(head) < GeomUtils.strokeLength(tailStroke) \ and _isPointWithHead(endpoint, head, tip): #Make sure the proportions aren't totally off logger.debug("Head stroke has a tail close and within cone") pointingLength = len(tailStroke.Points) / 5 #headToTail if endpoint == tailStroke.Points[0]: linept1, linept2 = tailStroke.Points[pointingLength], endpoint elif endpoint== tailStroke.Points[-1]: linept1, linept2 = tailStroke.Points[-pointingLength], endpoint pointsTo = GeomUtils.linePointsTowards(linept1, linept2, tip, headBreadth) if pointsTo: retlist.append( (endpoint, tailStroke) ) elif tail is not None and head is None: #Find the head endpoint = point pointingLength = len(tail.Points) / 5 #headToTail if endpoint == tail.Points[0]: linept1, linept2 = tail.Points[pointingLength], endpoint elif endpoint== tail.Points[-1]: linept1, linept2 = tail.Points[-pointingLength], endpoint for tip, headStroke in self._arrowHeads: ep1, ep2 = headStroke.Points[0], headStroke.Points[-1] headBreadth = GeomUtils.pointDistance(ep1.X, ep1.Y, ep2.X, ep2.Y) if GeomUtils.strokeLength(headStroke) < GeomUtils.strokeLength(tail) \ and _isPointWithHead(endpoint, headStroke, tip): logger.debug("Tail stroke is close and within cone of an arrowhead") pointsTo = GeomUtils.linePointsTowards(linept1, linept2, tip, headBreadth) if pointsTo: retlist.append( (tip, headStroke) ) return retlist
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 onAnnotationAdded( self, strokes, annotation ): "Checks to see if an equals sign has been added" # Find the midpoints ul,br = GeomUtils.strokelistBoundingBox( strokes ) midpointY = (ul.Y + br.Y) / 2 midpointX = (ul.X + br.X) / 2 strokeLen = GeomUtils.strokeLength(strokes[0]) for a in self.possibleAnnotations: s = a.Strokes[0] prevStrokeLen = GeomUtils.strokeLength(s) # test the the two segments are of similar length lengthRange = 0.4 if prevStrokeLen * (1-lengthRange) < strokeLen < prevStrokeLen * (1+lengthRange): pass # were the right length else: # not the right length, so lets start again continue ul,br = GeomUtils.strokelistBoundingBox( [s] ) prevMidpointY = (ul.Y + br.Y) / 2 prevMidpointX = (ul.X + br.X) / 2 # Test that the two segments are close enough horizontally if GeomUtils.pointDistance(midpointX, 0, prevMidpointX, 0) < prevStrokeLen * 0.4: pass # there are close enough horizontally else: # we start again continue # Test that the two segments are close enough vertically if GeomUtils.pointDistance(0,midpointY, 0, prevMidpointY) < prevStrokeLen * 0.5: pass # there are close enough vertically else: # we start again continue # we found a match self.possibleAnnotations.remove(a) self.getBoard().AnnotateStrokes( strokes + [s], EqualsAnnotation(1)) return # no match was found, add to the list of possible self.possibleAnnotations.append(annotation) return
def linesPointAtEachother(linepair1, linepair2): ep1 = linepair1[1] ep2 = linepair2[1] pointsToRadius = max(15, 0.26 * GeomUtils.pointDistance(ep1.X, ep1.Y, ep2.X, ep2.Y) ) #Span out the valid radius at about 30 degrees l1_to_l2 = GeomUtils.linePointsTowards(linepair1[0], linepair1[1], linepair2[1], pointsToRadius) l2_to_l1 = GeomUtils.linePointsTowards(linepair2[0], linepair2[1], linepair1[1], pointsToRadius) ss_logger.debug("l1 points to l2: %s" % (l1_to_l2)) ss_logger.debug("l2 points to l1: %s" % (l2_to_l1)) return (l1_to_l2 and l2_to_l1)
def linkStrokesTogether(self): def allPairs(xlist, ylist): for x in xlist: for y in ylist: yield ( x, y ) for w1, w1Dict in self.wallInfo.items(): ep11, ep12 = w1.Points[0], w1.Points[-1] self.wallInfo[w1]['matches'] = {} for w2 , w2Dict in self.wallInfo.items(): if w2 == w1 or w2 in self.wallInfo[w1]['matches']: continue ep21, ep22 = w2.Points[0], w2.Points[-1] bestMatch = {'stroke': None, 'dist': None, 'from_pt': None, 'to_pt': None} for pair in allPairs([ep11, ep12], [ep21, ep22]): dist = GeomUtils.pointDistance(pair[0].X, pair[0].Y, pair[1].X, pair[1].Y) if bestMatch['dist'] is None or bestMatch['dist'] > dist: bestMatch = {'dist': dist, 'from_pt': pair[0], 'to_pt': pair[1]} self.wallInfo[w1]['matches'][w2] = bestMatch self.wallInfo[w2]['matches'][w1] = dict(bestMatch) #endfor w2, w2Dict #endfor w1, w1Dict #Link together partial walls wallStack = list(self.wallInfo.keys()) wallStrokes = {} curWall = None start = None while len(wallStack) > 0: if curWall == None: curWall = wallStack.pop() bestMatch = None bestDist = None for matchStk, matchDict in self.wallInfo[curWall]['matches'].items(): if matchStk in wallStack and (bestMatch == None or matchDict['dist'] < bestDist): bestDist = matchDict['dist'] bestMatch = matchStk if bestMatch != None: curWall = bestMatch wallStack.remove(bestMatch) for stk, stkDict in self.wallInfo.items(): rtm_logger.debug( "%s: " % (stk)) for match, mdict in stkDict['matches'].items(): rtm_logger.debug( " %s: %s " % (match, mdict['dist']))
def _matchHeadtoTail(self, head = None, tail = None, point = None): """Input head stroke or tail stroke. If head is specified, match it to a tail. If tail is specified, match it to a head. Parameter 'point' should be the tip if head is specified, the end-point if tail is specified. Returns a list of tuples: (tip, head_stroke) if tail is specified, (endpoint, tail_stroke) if head is specified.""" retlist = [] if point is None: return retlist headStroke = head tailStroke = tail if headStroke is not None and tail is None: #Head is specified, find the tail tip = point ep1, ep2 = headStroke.Points[0], headStroke.Points[-1] headBreadth = GeomUtils.pointDistance(ep1.X, ep1.Y, ep2.X, ep2.Y) for endpoint, origStk, tailStroke in self._endpoints: pointingLength = len(tailStroke.Points) / 10 if endpoint == tailStroke.Points[0]: #Treat as drawn head2tail tailpoints = tailStroke.Points linept1, linept2 = tailStroke.Points[pointingLength], endpoint elif endpoint== tailStroke.Points[-1]: #Treat as drawn tail2head tailpoints = list(reversed(tailStroke.Points)) linept1, linept2 = tailStroke.Points[-(pointingLength+1)], endpoint headLen = GeomUtils.strokeLength(headStroke) tailLen = GeomUtils.strokeLength(tailStroke) pointWithHead = _isPointWithHead(tailpoints, headStroke, tip) if headLen < tailLen * 2 \ and pointWithHead: logger.debug("Head stroke has a tail close and within cone") #headToTail pointsTo = GeomUtils.linePointsTowards(linept1, linept2, tip, headBreadth) if pointsTo: logger.debug(" Tail points to head") retlist.append( (endpoint, origStk) ) else: logger.debug(" Tail does NOT point to head") else: if headLen < tailLen * 2: logger.debug(" Head stroke scale is okay for this arrowhead") else: logger.debug(" Head stroke scale is BAD for this arrowhead") logger.debug(" Head Len: %s, tail Len: %s" % (headLen, tailLen)) if not pointWithHead: logger.debug(" Head stroke is NOT close or within cone of an arrowhead\n") else: logger.debug(" Head stroke is close and within cone of an arrowhead\n") elif tailStroke is not None and headStroke is None: #Find the head endpoint = point pointingLength = len(tailStroke.Points) / 10 if endpoint == tailStroke.Points[0]: #Treat as drawn head2tail tailpoints = tailStroke.Points linept1, linept2 = tailStroke.Points[pointingLength], endpoint elif endpoint== tailStroke.Points[-1]: #Treat as drawn tail2head tailpoints = list(reversed(tailStroke.Points)) linept1, linept2 = tailStroke.Points[-pointingLength], endpoint for tip, origStk, headStroke in self._arrowHeads: ep1, ep2 = headStroke.Points[0], headStroke.Points[-1] headBreadth = GeomUtils.pointDistance(ep1.X, ep1.Y, ep2.X, ep2.Y) headLen = GeomUtils.strokeLength(headStroke) tailLen = GeomUtils.strokeLength(tailStroke) pointWithHead = _isPointWithHead(tailpoints, headStroke, tip) if headLen < tailLen * 2\ and pointWithHead: logger.debug("Tail stroke is close and within cone of an arrowhead") pointsTo = GeomUtils.linePointsTowards(linept1, linept2, tip, headBreadth) if pointsTo: logger.debug(" Tail points to head") retlist.append( (tip, origStk) ) else: logger.debug(" Tail does NOT point to head") else: if headLen < tailLen * 2: logger.debug(" Tail stroke scale is okay for this arrowhead") else: logger.debug(" Tail stroke scale is BAD for this arrowhead") logger.debug(" Head Len: %s, tail Len: %s" % (headLen, tailLen)) if not pointWithHead: logger.debug(" Tail stroke is NOT close or within cone of an arrowhead\n") else: logger.debug(" Tail stroke is close and within cone of an arrowhead\n") return retlist
def onAnnotationAdded( self, strokes, annotation ): "Checks to see if an multiply sign has been added" # Find the midpoints ul,br = GeomUtils.strokelistBoundingBox( strokes ) midpointY = (ul.Y + br.Y) / 2 midpointX = (ul.X + br.X) / 2 strokeLen = GeomUtils.strokeLength(strokes[0]) if annotation.isType(DirectedLine.BT_LineAnnotation): possibleAnnotations = self.possibleAnnotations_TB otherPossibleAnnotations = self.possibleAnnotations_BT elif annotation.isType(DirectedLine.TB_LineAnnotation): possibleAnnotations = self.possibleAnnotations_BT otherPossibleAnnotations = self.possibleAnnotations_TB print len(possibleAnnotations) for a in possibleAnnotations: s = a.Strokes[0] prevStrokeLen = GeomUtils.strokeLength(s) # test the the two segments are of similar length lengthRange = 0.4 if prevStrokeLen * (1-lengthRange) < strokeLen < prevStrokeLen * (1+lengthRange): pass # were the right length else: # not the right length, so lets start again continue ul,br = GeomUtils.strokelistBoundingBox( [s] ) prevMidpointY = (ul.Y + br.Y) / 2 prevMidpointX = (ul.X + br.X) / 2 # Test that the two segments are close enough horizontally if GeomUtils.pointDistance(midpointX, 0, prevMidpointX, 0) < prevStrokeLen * 0.25: pass # there are close enough horizontally else: # we start again continue # Test that the two segments are close enough vertically if GeomUtils.pointDistance(0,midpointY, 0, prevMidpointY) < prevStrokeLen * 0.25: pass # there are close enough vertically else: # we start again continue # we found a match possibleAnnotations.remove(a) annos = s.findAnnotations() annos += self.getBoard().FindAnnotations(strokelist = strokes) for i in annos: self.getBoard().RemoveAnnotation(i) ul,br = GeomUtils.strokelistBoundingBox( strokes + [s] ) height = ul.Y - br.Y self.getBoard().AnnotateStrokes( strokes + [s], MultAnnotation(height)) return # no match was found, add to the list of possible otherPossibleAnnotations.append(annotation) return
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