def merg(self, to_anno, from_anno): bb_from = GeomUtils.strokelistBoundingBox( from_anno.Strokes ) center_from = Point( (bb_from[0].X + bb_from[1].X) / 2.0, (bb_from[0].Y + bb_from[1].Y) / 2.0) tl = Point (center_from.X - from_anno.scale/ 2.0, center_from.Y + (from_anno.scale / 2.0) ) br = Point (center_from.X + from_anno.scale/ 2.0, center_from.Y - (from_anno.scale / 2.0) ) bb_from = (tl, br) bb_to = GeomUtils.strokelistBoundingBox( to_anno.Strokes ) center_to = Point( (bb_to[0].X + bb_to[1].X) / 2.0, (bb_to[0].Y + bb_to[1].Y) / 2.0) tl = Point (center_to.X - to_anno.scale/ 2.0, center_to.Y + (to_anno.scale / 2.0) ) br = Point (center_to.X + to_anno.scale/ 2.0, center_to.Y - (to_anno.scale / 2.0) ) bb_to = (tl, br) if bb_from[0].X - bb_to[0].X > 0 : outText = to_anno.text + from_anno.text else : outText = from_anno.text + to_anno.text #Weight the scale per letter to_anno.scale = ( to_anno.scale * len(to_anno.text) + from_anno.scale * len(from_anno.text) )\ / float(len(to_anno.text) + len(from_anno.text)) tc_logger.debug("MERGED: %s and %s to %s" % (to_anno.text, from_anno.text, outText)) to_anno.text = outText to_anno.alternates = [] return True
def onStrokeAdded( self, stroke ): "Compare this stroke to all templates, and annotate those matching within some threshold." logger.debug("Scoring stroke") #strokeVector = ( len(stroke.Points), GeomUtils.pointListOrientationHistogram(GeomUtils.strokeNormalizeSpacing(stroke, numpoints = len(stroke.Points) / 3.0).Points) ) strokeVector = generateFeatureVector(stroke) logger.debug("Stroke Vector: %s" % (str(strokeVector))) if self._matchVector == None: self._matchVector = strokeVector else: bb1 = GeomUtils.strokelistBoundingBox([stroke]) for prevStk in self._features.keys(): bb2 = GeomUtils.strokelistBoundingBox([prevStk]) if GeomUtils.boundingboxOverlap(bb1, bb2): self.overlaps.setdefault(stroke, set()).add(prevStk) self.overlaps.setdefault(prevStk, set()).add(stroke) self._features[stroke] = strokeVector score = scoreVector(self._matchVector, strokeVector) logger.debug(" Distance %s from baseline" % (score) ) if score < 0.02: self.getBoard().AnnotateStrokes([stroke], MultiStrokeAnnotation("Match")) for stk in self.overlaps.get(stroke, []): multiVect = addVectors( [self._features[stroke], self._features[stk] ] ) logger.debug("Multiple vector: %s" % (str(multiVect))) score = scoreVector(self._matchVector, multiVect) logger.debug(" Distance %s from baseline" % (score) ) if score < 0.02: self.getBoard().AnnotateStrokes([stroke, stk], MultiStrokeAnnotation("Match"))
def drawAnno(self, a): bbox = GeomUtils.strokelistBoundingBox(a.Strokes) gui = self.getBoard().getGUI() drawBox = False if drawBox: # Draw the logical box minScale = 20 heights = [s.BoundTopLeft.Y - s.BoundBottomRight.Y for s in a.Strokes] bb_from = GeomUtils.strokelistBoundingBox(a.Strokes) from_scale = max(minScale, heights[len(heights) / 2]) # from_scale = max(minScale, sum(heights)/float(len(heights))) tl = Point (bb_from[0].X - from_scale, bb_from[0].Y + from_scale / 2) br = Point (bb_from[1].X + from_scale, bb_from[1].Y - from_scale / 2) bb_from = (tl, br) gui.drawBox(tl, br, color="#FFFFFF") visLogger.debug("Drawing Anno: {}".format(a.latex)) if a.latex and len(a.latex) > 0: try: if hasattr(gui, 'drawBitmap'): if a.latex not in self._cachedPixbuf: self._cachedPixbuf[a.latex] = pixbufFromLatex(a.latex) pixbuf = self._cachedPixbuf[a.latex] gui.drawBitmap(bbox[1].X, bbox[1].Y, pixbuf=pixbuf) else: gui.drawText(bbox[1].X, bbox[1].Y, a.latex) except Exception as e: print "Cannot draw equation {}: {}".format(a.latex, e)
def mergeCollections( self, from_anno, to_anno ): "merge from_anno into to_anno if possible" # check that they have compatable scales scale_diff = to_anno.scale / from_anno.scale if scale_diff>2.5 or scale_diff<0.4: return False # check that they are not overlapping bb_from = GeomUtils.strokelistBoundingBox( from_anno.Strokes ) bb_to = GeomUtils.strokelistBoundingBox( to_anno.Strokes ) if GeomUtils.boundingboxOverlap( bb_from, bb_to ): return False # bb[0]-------+ # | | # | | # | (0,0) | # +--------bb[1] # check that they are next to each other if abs( bb_from[1].X - bb_to[0].X ) > to_anno.scale * 0.75 \ and abs( bb_from[0].X - bb_to[1].X ) > to_anno.scale * 0.75 : return False # check y's overlap if bb_from[0].Y - bb_to[1].Y < 0 \ or bb_to[0].Y - bb_from[1].Y < 0 : return False # now we know that we want to merge these text annotations if bb_from[0].X - bb_to[0].X > 0 : to_anno.text = to_anno.text + from_anno.text else : to_anno.text = from_anno.text + to_anno.text to_anno.scale = max( to_anno.scale, from_anno.scale ) return True
def isToRight(self, stroke_to, stroke_from, scale): ''' if from is to the right of to ''' bb_from = GeomUtils.strokelistBoundingBox( [stroke_from] ) center_from = Point( (bb_from[0].X + bb_from[1].X) / 2.0, (bb_from[0].Y + bb_from[1].Y) / 2.0) bb_to = GeomUtils.strokelistBoundingBox( [stroke_to] ) center_to = Point( (bb_to[0].X + bb_to[1].X) / 2.0, (bb_to[0].Y + bb_to[1].Y) / 2.0) if 0 < center_from.X - center_to.X < self.horizDistRatio * scale: return True return False
def onAnnotationAdded( self, strokes, annotation ): "Checks to see if an divide sign has been added" ul,br = GeomUtils.strokelistBoundingBox(strokes) height = ul.Y - br.Y self.getBoard().AnnotateStrokes( strokes, DivideAnnotation(height)) return
def _makeLetterAnnotation(self, strokelist, char, alternates): bb = GeomUtils.strokelistBoundingBox(strokelist) height = bb[0].Y - bb[1].Y width = bb[1].X - bb[0].X scale = max(height, width, 1) retAnnotation = TextAnnotation(char, (alternates,), [strokelist], scale) return retAnnotation
def displayDataManager(self): """Paint whatever the display manager wants on the board""" global HEIGHT, WIDTH, BOARDSCALE self.ResetBoard() print self.dataset.participants[self.participant].diagrams[self.diagram].type xMax = 0 yMax = 0 xMin = sys.maxint yMin = sys.maxint par = self.participant dig = self.diagram # Finds the min and max points so we can scale the data to fit on the screen for stkNum, inkStroke in self.dataset.participants[par].diagrams[dig].InkStrokes.items(): stroke = traceStroke(inkStroke.stroke) ul,br = GeomUtils.strokelistBoundingBox([stroke]) xMax = max(ul.X, br.X, xMax) yMax = max(ul.Y, br.Y, yMax) xMin = min(ul.X, br.X, xMin) yMin = min(ul.Y, br.Y, yMin) # Find the distance that the strokes take up # the "+ 20" is so we can have a 10 pixle buffer around the edges setBoardScale(xMax, yMax) labelStrokeMap = {} #Maps groupLabel : set(strokes) for stkNum, inkStroke in self.dataset.participants[par].diagrams[dig].InkStrokes.items(): #print inkStroke.id stroke = inkStroke.stroke for groupLabel in self.dataset.participants[par].diagrams[dig].groupLabels: if stroke.id in groupLabel.ids: labelStrokeMap.setdefault(groupLabel, set()).add(stroke) points = [] """ # scale each point to it's new position for p in stroke.Points: x = (p.X - xMin) * scaleFactor + 10 # the "+10 is for the 10 pixle boarder # y axis points in the data manager are inverted compaired to our screen # so we invert them y = HEIGHT - ((p.Y - yMin) * scaleFactor + 10) points.append(Point(x,y)) """ # create a new stroke out of the scaled points and add it to the board. #s = Stroke(points) s = inkStroke.stroke self.Board.AddStroke(s) self.StrokeList.append(s) # Annotate the stroke with the type given in the data manager for groupLabel, strokeSet in labelStrokeMap.items(): self.Board.AnnotateStrokes(list(strokeSet), DataManager.DataManagerAnnotation(groupLabel.type))
def mergeCollections(self, from_anno, to_anno): "merge from_anno into to_anno if they are naer enough to each other" minScale = 30 vertOverlapRatio = 0 horizOverlapRatio = 0 groupingDistScale = 0.4 # multiplier for the median scale of how far to check around # The strokes def annoScale(anno): """Helper function to get the scale of this annotation""" heights = [s.BoundTopLeft.Y - s.BoundBottomRight.Y for s in anno.Strokes] # scale = max(minScale, heights[len(heights)/2]) # median scale = sum(heights) / float(max(1, len(heights))) return max(scale, minScale) # bb[0]-------+ # | | # | | # | | # +--------bb[1] # (0,0) from_scale = annoScale(from_anno) bb_from = GeomUtils.strokelistBoundingBox(from_anno.Strokes) tl = Point (bb_from[0].X - from_scale, bb_from[0].Y + from_scale * groupingDistScale) br = Point (bb_from[1].X + from_scale, bb_from[1].Y - from_scale * groupingDistScale) bb_from = (tl, br) to_scale = annoScale(to_anno) bb_to = GeomUtils.strokelistBoundingBox(to_anno.Strokes) tl = Point (bb_to[0].X - to_scale, bb_to[0].Y + to_scale * groupingDistScale) br = Point (bb_to[1].X + to_scale, bb_to[1].Y - to_scale * groupingDistScale) bb_to = (tl, br) # check x's overlap if bb_from[1].X - bb_to[0].X < horizOverlapRatio \ or bb_to[1].X - bb_from[0].X < horizOverlapRatio : logger.debug("Not merging %s and %s: horizontal overlap too small" % (from_anno, to_anno)) return False # check y's overlap if bb_from[0].Y - bb_to[1].Y < vertOverlapRatio \ or bb_to[0].Y - bb_from[1].Y < vertOverlapRatio : logger.debug("Not merging %s and %s: vertical overlap too small" % (from_anno, to_anno)) return False self.annoQueue.put(to_anno) return True
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 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 drawAnno( self, a ): ul,br = GeomUtils.strokelistBoundingBox( a.Strokes ) spaceing = 5 ul.X -= spaceing ul.Y += spaceing br.X += spaceing br.Y -= spaceing self.getBoard().getGUI().drawBox(ul, br, color="#a0a0a0"); self.getBoard().getGUI().drawText( br.X - 15, br.Y, a.text, size=15, color="#a0a0a0" )
def drawAnno( self, a ): ul,br = GeomUtils.strokelistBoundingBox( a.Strokes ) logger.debug(a.Strokes) height = ul.Y - br.Y midpointY = (ul.Y + br.Y) / 2 midpointX = (ul.X + br.X) / 2 left_x = midpointX - a.scale / 2.0 right_x = midpointX + a.scale / 2.0 #self.getBoard().getGUI().drawLine( left_x, midpointY, right_x, midpointY, color="#a0a0a0") y = br.Y self.getBoard().getGUI().drawText( br.X, y, a.text, size=15, color="#a0a0a0" )
def findOnLeft( self, anno, list): bb_e = GeomUtils.strokelistBoundingBox( anno.Strokes ) center_e = Point( (bb_e[0].X + bb_e[1].X) / 2.0, (bb_e[0].Y + bb_e[1].Y) / 2.0) v_offset = anno.scale / 4 h_offset = anno.scale / 2 for a in list: bb_a = GeomUtils.strokelistBoundingBox( a.Strokes ) center_a = Point( (bb_a[0].X + bb_a[1].X) / 2.0, (bb_a[0].Y + bb_a[1].Y) / 2.0) # is it vertically alligned? if not ((center_e.Y < center_a.Y + v_offset) and (center_e.Y > center_a.Y - v_offset)): continue # is it to the Left? if center_e.X <= center_a.X: continue return a return None
def drawAnno( self, a ): if len(a.text) >= 1: ul,br = GeomUtils.strokelistBoundingBox( a.Strokes ) logger.debug(a.Strokes) height = ul.Y - br.Y left_x = ul.X# - height/3 right_x = br.X + height/2 midpoint = (ul.Y + br.Y) / 2 SketchGUI.drawLine( left_x, midpoint, right_x, midpoint, color="#a0a0a0") y = br.Y + 5 for idx, text in enumerate(a.alternates): SketchGUI.drawText( br.X, y, text, size=20, color="#a0a0a0" ) y -= 20
def drawAnno( self, a ): if len(a.text) > 1: ul,br = GeomUtils.strokelistBoundingBox( a.Strokes ) logger.debug(a.Strokes) height = ul.Y - br.Y midpointY = (ul.Y + br.Y) / 2 midpointX = (ul.X + br.X) / 2 left_x = midpointX - a.scale / 2.0 right_x = midpointX + a.scale / 2.0 #SketchGUI.drawLine( left_x, midpointY, right_x, midpointY, color="#a0a0a0") y = br.Y SketchGUI.drawText( br.X, y, a.text, size=15, color="#a0a0a0" ) y -= 15 for idx, text in enumerate(a.alternates): SketchGUI.drawText( br.X, y, text, size=10, color="#a0a0a0" ) y -= 10
def drawAnno( self, a ): ul,br = GeomUtils.strokelistBoundingBox( a.Strokes ) spaceing = 5 ul.X -= spaceing ul.Y += spaceing br.X += spaceing br.Y -= spaceing logger.debug(a.Strokes) height = ul.Y - br.Y midpointY = (ul.Y + br.Y) / 2 midpointX = (ul.X + br.X) / 2 left_x = midpointX - a.scale / 2.0 right_x = midpointX + a.scale / 2.0 self.getBoard().getGUI().drawBox(ul, br, color="#a0a0a0"); self.getBoard().getGUI().drawText( br.X - 15, br.Y, "X", size=15, color="#a0a0a0" )
def drawAnno( self, a ): ul,br = GeomUtils.strokelistBoundingBox( a.Strokes ) spaceing = 5 ul.X -= spaceing ul.Y += spaceing br.X += spaceing br.Y -= spaceing logger.debug(a.Strokes) height = ul.Y - br.Y midpointY = (ul.Y + br.Y) / 2 midpointX = (ul.X + br.X) / 2 left_x = midpointX - a.scale / 2.0 right_x = midpointX + a.scale / 2.0 self.getBoard().getGUI().drawBox(ul, br, color="#a0a0a0"); #print "Drawing " + a.type + " with number " + str(a.number) + " and scale " + str(int(a.scale)) if a.type == "X": self.getBoard().getGUI().drawText( br.X + 10, ul.Y, str(a.number), size= int(a.scale), color="#a0a0a0" )
def drawAnno( self, a ): ul,br = GeomUtils.strokelistBoundingBox( a.Strokes ) spaceing = 5 ul.X -= spaceing ul.Y += spaceing br.X += spaceing br.Y -= spaceing logger.debug(a.Strokes) height = ul.Y - br.Y midpointY = (ul.Y + br.Y) / 2 midpointX = (ul.X + br.X) / 2 #left_x = midpointX - a.scale / 2.0 #right_x = midpointX + a.scale / 2.0 """ self.getBoard().getGUI().drawBox(ul, br, color="#a0a0a0") """ gui = self.getBoard().getGUI() color = "#0C00F0" gui.drawText( br.X - 15, br.Y, a.name , size=15, color=color ) for s in a.Strokes: gui.drawStroke(s, width = 3, color = color) gui.drawStroke(s, width = 1, color = "#000000")
def onAnnotationAdded( self, strokes, annotation ): ''' print "Annotation Added:" print annotation ''' if annotation.isType(BinAnnotation): bb_a = GeomUtils.strokelistBoundingBox( annotation.Strokes ) center_a = Point( (bb_a[0].X + bb_a[1].X) / 2.0, (bb_a[0].Y + bb_a[1].Y) / 2.0) tl = Point (center_a.X - annotation.scale/ 2.0, center_a.Y + (annotation.scale / 2.0) ) br = Point (center_a.X + annotation.scale/ 2.0, center_a.Y - (annotation.scale / 2.0) ) bb_a = (tl, br) for a in self.equalsAnnotations: bb_e = GeomUtils.strokelistBoundingBox( a.Strokes ) center_e = Point( (bb_e[0].X + bb_e[1].X) / 2.0, (bb_e[0].Y + bb_e[1].Y) / 2.0) tl = Point (center_e.X - a.scale/ 2.0, center_e.Y + (a.scale / 2.0) ) br = Point (center_e.X + a.scale/ 2.0, center_e.Y - (a.scale / 2.0) ) bb_e = (tl, br) if bb_e[0].X < bb_a[1].X: continue if bb_e[0].Y - bb_a[1].Y < 0 \ or bb_a[0].Y - bb_e[1].Y < 0 : continue # we have a matching stroke self.equalsAnnotations.remove(a) eAnno = EquationAnnotation(annotation.scale, "X", int(annotation.text,2)) self.equationAnnotations.append(eAnno) self.getBoard().AnnotateStrokes( a.Strokes + strokes, eAnno ) return # No match, so just add it to the list self.binAnnotations.append(annotation) elif annotation.isType(EqualsAnnotation): bb_e = GeomUtils.strokelistBoundingBox( annotation.Strokes ) center_e = Point( (bb_e[0].X + bb_e[1].X) / 2.0, (bb_e[0].Y + bb_e[1].Y) / 2.0) tl = Point (center_e.X - annotation.scale/ 2.0, center_e.Y + (annotation.scale / 2.0) ) br = Point (center_e.X + annotation.scale/ 2.0, center_e.Y - (annotation.scale / 2.0) ) bb_e = (tl, br) for a in self.binAnnotations: bb_a = GeomUtils.strokelistBoundingBox( a.Strokes ) center_a = Point( (bb_a[0].X + bb_a[1].X) / 2.0, (bb_a[0].Y + bb_a[1].Y) / 2.0) tl = Point (center_a.X - a.scale/ 2.0, center_a.Y + (a.scale / 2.0) ) br = Point (center_a.X + a.scale/ 2.0, center_a.Y - (a.scale / 2.0) ) bb_a = (tl, br) if bb_e[0].X < bb_a[1].X: continue if bb_e[0].Y - bb_a[1].Y < 0 \ or bb_a[0].Y - bb_e[1].Y < 0 : continue # we have a matching stroke self.binAnnotations.remove(a) eAnno = EquationAnnotation(a.scale, "X", int(a.text,2)) self.equationAnnotations.append(eAnno) self.getBoard().AnnotateStrokes( a.Strokes + strokes, eAnno ) return # No match, so just add it to the list self.equalsAnnotations.append(annotation)
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
def mergeCollections( self, from_anno, to_anno ): "merge from_anno into to_anno if possible" vertOverlapRatio = 0 horizDistRatio = 2.3 scaleDiffRatio = 2.0 #if from_anno.scale > 0: #scale_diff = to_anno.scale / from_anno.scale #if scale_diff > scaleDiffRatio or scale_diff < 1/ float(scaleDiffRatio) : #tc_logger.debug("Not merging %s and %s: Scale Diff is %s" % (from_anno.text, to_anno.text, scale_diff)) #return False #else: #return False # bb[0]-------+ # | | # | | # | (0,0) | # +--------bb[1] bb_from = GeomUtils.strokelistBoundingBox( from_anno.Strokes ) center_from = Point( (bb_from[0].X + bb_from[1].X) / 2.0, (bb_from[0].Y + bb_from[1].Y) / 2.0) tl = Point (center_from.X - from_anno.scale/ 2.0, center_from.Y + (from_anno.scale / 2.0) ) br = Point (center_from.X + from_anno.scale/ 2.0, center_from.Y - (from_anno.scale / 2.0) ) bb_from = (tl, br) bb_to = GeomUtils.strokelistBoundingBox( to_anno.Strokes ) center_to = Point( (bb_to[0].X + bb_to[1].X) / 2.0, (bb_to[0].Y + bb_to[1].Y) / 2.0) tl = Point (center_to.X - to_anno.scale/ 2.0, center_to.Y + (to_anno.scale / 2.0) ) br = Point (center_to.X + to_anno.scale/ 2.0, center_to.Y - (to_anno.scale / 2.0) ) bb_to = (tl, br) # check that they are next to each other if abs( bb_from[1].X - bb_to[0].X ) > to_anno.scale * horizDistRatio \ and abs( bb_from[0].X - bb_to[1].X ) > to_anno.scale * horizDistRatio \ and abs( bb_from[1].X - bb_to[0].X ) > from_anno.scale * horizDistRatio \ and abs( bb_from[0].X - bb_to[1].X ) > from_anno.scale * horizDistRatio: #tc_logger.debug("Not merging %s and %s: horizontal distance too great" % (from_anno.text, to_anno.text)) return False # check y's overlap if bb_from[0].Y - bb_to[1].Y < vertOverlapRatio \ or bb_to[0].Y - bb_from[1].Y < vertOverlapRatio : #tc_logger.debug("Not merging %s and %s: vertical overlap too small" % (from_anno.text, to_anno.text)) return False # check that they have compatible scales scale_diff1 = to_anno.scale / from_anno.scale scale_diff2 = from_anno.scale / to_anno.scale if scale_diff1 > scaleDiffRatio or scale_diff2 > scaleDiffRatio: tc_logger.debug("Not merging %s and %s: Scale Diff is %s" % (from_anno, to_anno, scale_diff1)) return False # Order the letters properly from left to right out_letter_stroke_map = [] outText = "" from_idx = 0 to_idx = 0 all_alternates = [] while len(outText) < len(from_anno.text) + len(to_anno.text): #Get the BB for the next letter in from_anno if from_idx < len(from_anno.letter2strokesMap) and from_idx < len(from_anno.text): letter_bb_from = GeomUtils.strokelistBoundingBox(from_anno.letter2strokesMap[from_idx]) else: letter_bb_from = None #Get the BB for the next letter in to_anno if to_idx < len(to_anno.letter2strokesMap) and to_idx < len(to_anno.text): letter_bb_to = GeomUtils.strokelistBoundingBox(to_anno.letter2strokesMap[to_idx]) else: letter_bb_to = None if letter_bb_to is None and letter_bb_from is None: logger.warn("Trying to merge beyond available letters") break elif letter_bb_to is None or \ (letter_bb_from is not None and letter_bb_from[0].X < letter_bb_to[0].X ): #The next letter belongs to from_anno. Merge it outText += from_anno.alternates[from_idx][0] all_alternates.append(from_anno.alternates[from_idx]) #Merge the alternates for this letter, too out_letter_stroke_map.append(from_anno.letter2strokesMap[from_idx]) from_idx += 1 elif letter_bb_from is None or \ (letter_bb_to is not None and letter_bb_to[0].X <= letter_bb_from[0].X): #The next letter belongs to to_anno. Merge it outText += to_anno.alternates[to_idx][0] all_alternates.append(to_anno.alternates[to_idx]) #Merge the alternates for this letter, too out_letter_stroke_map.append(to_anno.letter2strokesMap[to_idx]) to_idx += 1 #Weight the scale per letter to_anno.scale = ( to_anno.scale * len(to_anno.text) + from_anno.scale * len(from_anno.text) )\ / float(len(to_anno.text) + len(from_anno.text)) tc_logger.debug("MERGED: %s and %s to %s" % (to_anno.text, from_anno.text, outText)) to_anno.text = outText to_anno.letter2strokesMap = out_letter_stroke_map to_anno.alternates = tuple(all_alternates) return True
def onAnnotationAdded( self, strokes, annotation ): ''' print "Annotation Added:" print annotation ''' if annotation.isType(ExpressionAnnotation): # check to see if there is an operation to the right of the num # and then an expression to the right of that operation left = None right = None op = self.findOnRight(annotation, self.opAnnotations) if op != None: right = self.findOnRight(op, self.expressionAnnotations) if right != None: left = annotation self.expressionAnnotations.remove(right) # if no expression was found to the right, search to the left if left == None: op = self.findOnLeft(annotation, self.opAnnotations) if op != None: left = self.findOnLeft(op, self.expressionAnnotations) if left != None: right = annotation self.expressionAnnotations.remove(left) if left != None: #print "Num Found" #print num s = left.Strokes + op.Strokes + right.Strokes ul, br = GeomUtils.strokelistBoundingBox(s) e = ExpressionAnnotation(ul.Y - br.Y) left.parent = e right.parent = e e.left = left e.right = right e.op = op self.opAnnotations.remove(op) self.getBoard().AnnotateStrokes(s, e) #annotation.scale = ul.Y - br.Y #annotation.number = annotation.number + num.number #self.getBoard().UpdateAnnotation(annotation, s) #self.getBoard().RemoveAnnotation(num) return # This expression can't be added to any of the other parts # so we will properly order it then add it to the list of annotations top = self.order(annotation) print top self.expressionAnnotations.append(top) elif annotation.isType(NumAnnotation): e = ExpressionAnnotation(annotation.scale) e.number = annotation self.getBoard().AnnotateStrokes(strokes, e) elif annotation.isType(PlusAnnotation): self.opAnnotations.append(annotation) elif annotation.isType(MinusAnnotation): self.opAnnotations.append(annotation) elif annotation.isType(DivideAnnotation): self.opAnnotations.append(annotation) elif annotation.isType(MultAnnotation): self.opAnnotations.append(annotation)
def mergeCollections( self, from_anno, to_anno ): "merge from_anno into to_anno if possible" #FIXME: New annotation assumed to be to the right. (Does not handle inserting text in the middle) # check that they have compatable scales vertOverlapRatio = 0 scaleDiffRatio = 1.5 scale_diff = to_anno.scale / from_anno.scale if scale_diff> scaleDiffRatio or 1/float( scale_diff ) > scaleDiffRatio : tc_logger.debug("Not merging %s and %s: Scale Diff is %s" % (to_anno.text, from_anno.text, scale_diff)) return False # check that they are not overlapping bb_from = GeomUtils.strokelistBoundingBox( from_anno.Strokes ) center_from = Point( (bb_from[0].X + bb_from[1].X) / 2.0, (bb_from[0].Y + bb_from[1].Y) / 2.0) tl = Point (center_from.X - from_anno.scale/ 2.0, center_from.Y + (from_anno.scale / 2.0) ) br = Point (center_from.X + from_anno.scale/ 2.0, center_from.Y - (from_anno.scale / 2.0) ) bb_from = (tl, br) bb_to = GeomUtils.strokelistBoundingBox( to_anno.Strokes ) center_to = Point( (bb_to[0].X + bb_to[1].X) / 2.0, (bb_to[0].Y + bb_to[1].Y) / 2.0) tl = Point (center_to.X - to_anno.scale/ 2.0, center_to.Y + (to_anno.scale / 2.0) ) br = Point (center_to.X + to_anno.scale/ 2.0, center_to.Y - (to_anno.scale / 2.0) ) bb_to = (tl, br) """ if not GeomUtils.boundingboxOverlap( bb_from, bb_to ): tc_logger.debug("Not merging Bounding boxes don't overlap") return False """ # bb[0]-------+ # | | # | | # | (0,0) | # +--------bb[1] # check y's overlap if bb_from[0].Y - bb_to[1].Y < vertOverlapRatio \ or bb_to[0].Y - bb_from[1].Y < vertOverlapRatio : tc_logger.debug("Not merging: vertical overlap too small") return False # sort the strokes so they are in order from left to right to_anno.Strokes.sort(key = self.getSortItem); from_anno.Strokes.sort(key = self.getSortItem); first = from_anno.Strokes[0] # left most last = from_anno.Strokes[len(from_anno.Strokes)-1] # right most # check if from to the left of to? if self.isToLeft(to_anno.Strokes[0], last, to_anno.scale): return self.merg(to_anno, from_anno) # check if from is to the right of to? if self.isToRight(to_anno.Strokes[len(to_anno.Strokes)-1], first, to_anno.scale): return self.merg(to_anno, from_anno) # check if from is inserted somewhere inside to for i in range(len(to_anno.Strokes)): if self.isToRight(to_anno.Strokes[i], first, to_anno.scale)\ and self.isToLeft(to_anno.Strokes[i+1], last, to_anno.scale): return self.merg(to_anno, from_anno) return False
def getSortItem(self, stroke): return GeomUtils.strokelistBoundingBox( [stroke] )[0].X
def drawAnno( self, a ): edge_label_size = 15 tape_label_size = 20 active_color = "#BF5252" active_width = 7.0 state_graph = a.state_graph_anno for from_node, connection_list in state_graph.connectMap.items(): if from_node is not None: nodeColor = "#FFFFFF" if from_node == a.active_state: nodeColor = active_color x, y = ( from_node.center.X, from_node.center.Y ) self.getBoard().getGUI().drawCircle (x, y, radius=(from_node.radius / 2.0), color=nodeColor, width=3.0) #GeomUtils.strokeSmooth(edge.tailstroke, width = len(edge.tailstroke.Points) / 3).drawMyself() for edge, to_node in connection_list: if edge == a.leading_edge['edge']: edgeColor = active_color else: edgeColor = "#FFFFFF" if to_node is not None: nodeColor = "#FFFFFF" nodeWidth = 3.0 if to_node == a.active_state: nodeColor = active_color nodeWidth = active_width x, y = ( to_node.center.X, to_node.center.Y ) self.getBoard().getGUI().drawCircle (x, y, radius=(to_node.radius / 2.0), color=nodeColor, fill="", width=nodeWidth) #Draw the smoothed tail if from_node is not None: if edge.direction == "tail2head": #Connect the tail more closely to the edge smooth_tail = Stroke([from_node.center] + edge.tailstroke.Points + [edge.tip]) else: smooth_tail = Stroke([edge.tip] + edge.tailstroke.Points + [from_node.center]) else: smooth_tail = edge.tailstroke smooth_tail = GeomUtils.strokeSmooth(smooth_tail, width = len(edge.tailstroke.Points) / 3, preserveEnds = True) smooth_tail.drawMyself(color=edgeColor) #Draw the smoothed head ep1, ep2 = ( edge.headstroke.Points[0], edge.headstroke.Points[-1] ) smooth_head = Stroke([ep1, edge.tip, ep2]) smooth_head.drawMyself(color = edgeColor) if edge in a.edge2labels_map: #Determine label offset for label in a.edge2labels_map[edge]: textColor = "#FFFFFF" if label == a.leading_edge['label']: tm_logger.debug("Drawing leading label: %s" % (label.text)) textColor = active_color tl, br = GeomUtils.strokelistBoundingBox(label.Strokes) label_point = Point ((tl.X + br.X) / 2.0, br.Y) label_point.X -= edge_label_size label_point.Y -= edge_label_size #label_point = smooth_tail.Points[len(smooth_tail.Points)/2] self.getBoard().getGUI().drawText (label_point.X, label_point.Y, InText=label.text, size=edge_label_size, color=textColor) #endfor #endif #end for edge #end for from_node #Draw the tape string tl, br = GeomUtils.strokelistBoundingBox(a.Strokes) tape_label_pt = Point( \ ((tl.X + br.X) / 2.0) - (len(a.tape_string) + 2) * tape_label_size / 2.0 , \ br.Y - tape_label_size) for curIdx, tapeChar in enumerate(['-'] + a.tape_string + ['-']): curPt = Point(tape_label_pt.X + curIdx * tape_label_size, tape_label_pt.Y) charColor = "#FFFFFF" if curIdx - 1== a.tape_idx: charColor = active_color self.getBoard().getGUI().drawText (curPt.X, curPt.Y, InText=tapeChar, size=tape_label_size, color=charColor)
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 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 mergeCollections( self, from_anno, to_anno ): "merge from_anno into to_anno if possible" #FIXME: New annotation assumed to be to the right. (Does not handle inserting text in the middle) # check that they have compatable scales vertOverlapRatio = 0 horizDistRatio = 2.0 scaleDiffRatio = 1.5 scale_diff = to_anno.scale / from_anno.scale if scale_diff> scaleDiffRatio or 1/float( scale_diff ) > scaleDiffRatio : tc_logger.debug("Not merging %s and %s: Scale Diff is %s" % (to_anno.text, from_anno.text, scale_diff)) return False # check that they are not overlapping bb_from = GeomUtils.strokelistBoundingBox( from_anno.Strokes ) center_from = Point( (bb_from[0].X + bb_from[1].X) / 2.0, (bb_from[0].Y + bb_from[1].Y) / 2.0) tl = Point (center_from.X - from_anno.scale/ 2.0, center_from.Y + (from_anno.scale / 2.0) ) br = Point (center_from.X + from_anno.scale/ 2.0, center_from.Y - (from_anno.scale / 2.0) ) bb_from = (tl, br) bb_to = GeomUtils.strokelistBoundingBox( to_anno.Strokes ) center_to = Point( (bb_to[0].X + bb_to[1].X) / 2.0, (bb_to[0].Y + bb_to[1].Y) / 2.0) tl = Point (center_to.X - to_anno.scale/ 2.0, center_to.Y + (to_anno.scale / 2.0) ) br = Point (center_to.X + to_anno.scale/ 2.0, center_to.Y - (to_anno.scale / 2.0) ) bb_to = (tl, br) """ if not GeomUtils.boundingboxOverlap( bb_from, bb_to ): tc_logger.debug("Not merging Bounding boxes don't overlap") return False """ # bb[0]-------+ # | | # | | # | (0,0) | # +--------bb[1] # check that they are next to each other if abs( bb_from[1].X - bb_to[0].X ) > to_anno.scale * horizDistRatio \ and abs( bb_from[0].X - bb_to[1].X ) > to_anno.scale * horizDistRatio \ and abs( bb_from[1].X - bb_to[0].X ) > from_anno.scale * horizDistRatio \ and abs( bb_from[0].X - bb_to[1].X ) > from_anno.scale * horizDistRatio: tc_logger.debug("Not merging: horizontal distance too great") return False # check y's overlap if bb_from[0].Y - bb_to[1].Y < vertOverlapRatio \ or bb_to[0].Y - bb_from[1].Y < vertOverlapRatio : tc_logger.debug("Not merging: vertical overlap too small") return False # now we know that we want to merge these text annotations if bb_from[0].X - bb_to[0].X > 0 : outText = to_anno.text + from_anno.text else : outText = from_anno.text + to_anno.text #Weight the scale per letter to_anno.scale = ( to_anno.scale * len(to_anno.text) + from_anno.scale * len(from_anno.text) )\ / float(len(to_anno.text) + len(from_anno.text)) tc_logger.debug("MERGED: %s and %s to %s" % (to_anno.text, from_anno.text, outText)) to_anno.text = outText to_anno.alternates = [] return True