def _scoreStroke(stroke, template, sample_size = None): if sample_size is None: sample_size = len(template) sNorm = GeomUtils.strokeNormalizeSpacing(stroke, len(template)) centr = GeomUtils.centroid(sNorm.Points) numPoints = len(sNorm.Points) point_vect = [] templ_vect = [] numPoints = len(template) if len(template) == len(sNorm.Points): for idx in range(0, numPoints, numPoints/sample_size ): templ_vect.append(template[idx].X) templ_vect.append(template[idx].Y) p = sNorm.Points[idx] point_vect.append(p.X - centr.X) point_vect.append(p.Y - centr.Y) angularDist = GeomUtils.vectorDistance(point_vect, templ_vect) else: angularDist = math.pi return angularDist
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 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 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 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 _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 SaveTemplate(self, numSamples = TEMPLATE_SAMPLE): if len(self.StrokeList) > 0: last_stroke = self.StrokeList[-1] template_name = str(len(self.StrokeList)) sNorm = GeomUtils.strokeNormalizeSpacing(last_stroke, numSamples) centroid = GeomUtils.centroid(sNorm.Points) sNorm = sNorm.translate(-1*centroid.X, -1 * centroid.Y) storeTemplate(sNorm, tag=template_name)
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 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 onStrokeAdded( self, stroke ): "Watches for Strokes that look like an arrow to Annotate" ep1 = stroke.Points[0] ep2 = stroke.Points[-1] isArrowHead = False GeomUtils.ellipseAxisRatio(stroke) #Match single-stroke arrows tip, tail = _isSingleStrokeArrow(stroke) if tip is None or tail is None: revpts = list(stroke.Points) revpts.reverse() tip, tail = _isSingleStrokeArrow(Stroke(revpts)) if tip is not None and tail is not None: isArrowHead = False anno = ArrowAnnotation( tip, tail ) BoardSingleton().AnnotateStrokes( [stroke], anno) else: return if _isArrowHead(stroke, self.arrowHeadMatcher): #We've matched an arrowhead head = stroke isArrowHead = True strokeNorm = GeomUtils.strokeNormalizeSpacing(stroke, numpoints = 5) tip = strokeNorm.Points[2] #Middle normalized point is the tip #Match it to any tails we have if isArrowHead: matchedTails = self._matchHeadtoTail(head = stroke, point = tip) for headpoint, tail in matchedTails: #Orient the tail correctly if tail.Points[0] == headpoint: endpoint = tail.Points[-1] else: endpoint = tail.Points[0] anno = ArrowAnnotation(tip, endpoint) BoardSingleton().AnnotateStrokes([head, tail],anno) #Match it like a tail even if we think it's an arrowhead. Oh ambiguity! matchedHeads = self._matchHeadtoTail(tail = stroke, point = ep1) for tip, head in matchedHeads: anno = ArrowAnnotation(tip, ep2) BoardSingleton().AnnotateStrokes([head, stroke],anno) matchedHeads = self._matchHeadtoTail(tail = stroke, point = ep2) for tip, head in matchedHeads: anno = ArrowAnnotation(tip, ep1) BoardSingleton().AnnotateStrokes([head, stroke],anno) #Add this stroke to the pool for future evaluation self._endpoints.append( (ep1, stroke) ) self._endpoints.append( (ep2, stroke) ) if isArrowHead: self._arrowHeads.append( (tip, stroke) )
def collectionFromItem( self, strokes, annotation ): text_anno = None # text_anno will be the return value if annotation.isType( CircleObserver.CircleAnnotation ): circle = annotation text_anno = TextAnnotation("0",circle.radius*2) if annotation.isType( LineObserver.LineAnnotation ): line = annotation # if the line is up/down then it is a one if GeomUtils.angleParallel( line.angle, 90 ) > 0.6: line_length = GeomUtils.pointDist( line.start_point, line.end_point ) text_anno = TextAnnotation("1",line_length) return text_anno
def onStrokeAdded( self, stroke ): "Watches for Strokes with Circularity > threshold to Annotate" # need at least 6 points to be a line if stroke.length()<6: return linearity = GeomUtils.strokeLinearity( stroke ) angle = GeomUtils.strokeOrientation( stroke ) if( linearity > self.threshold ): lanno = LineAnnotation( linearity, angle, stroke.Points[0], stroke.Points[-1] ) self.getBoard().AnnotateStrokes( [stroke], lanno )
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 tailToNode( self, arrow_anno, circle_anno ): "return true if the tail of the arrow comes from the circle" lineDist = max(len(arrow_anno.tailstroke.Points) / 10, 1) #Check the last 10th of the stroke points the right way if arrow_anno.direction == "tail2head": lineSeg = ( arrow_anno.tailstroke.Points[lineDist], arrow_anno.tailstroke.Points[0] ) else: #direction == 'head2tail' lineSeg = ( arrow_anno.tailstroke.Points[-lineDist], arrow_anno.tailstroke.Points[-1] ) if GeomUtils.pointDist( arrow_anno.tail, circle_anno.center ) < circle_anno.radius* DiGraphAnnotation.MATCHING_DISTANCE: if GeomUtils.linePointsTowards( lineSeg[0], lineSeg[1], circle_anno.center, circle_anno.radius * DiGraphAnnotation.POINTSTO_DISTANCE): return True return False
def scoreStroke(stroke, template): sNorm = GeomUtils.strokeNormalizeSpacing(stroke, TEMPLATE_SAMPLE) centr = GeomUtils.centroid(sNorm.Points) point_vect = [] templ_vect = [] for q in template: templ_vect.append(q.X) templ_vect.append(q.Y) for p in sNorm.Points: point_vect.append(p.X - centr.X) point_vect.append(p.Y - centr.Y) angularDist = GeomUtils.vectorDistance(point_vect, templ_vect) return angularDist
def SaveTemplate(self, sender, e, numSamples = TEMPLATE_SAMPLE): if len(self.StrokeList) > 0: #last_stroke = self.StrokeList[-1] pts = [] for stk in self.StrokeList: pts.extend(stk.Points) templ_stroke = Stroke.Stroke(points= pts) template_name = TEMPLATE_TEXT.Text if template_name.strip() == '': template_name = 'Blob' sNorm = GeomUtils.strokeNormalizeSpacing(templ_stroke, numSamples * len(self.StrokeList)) centroid = GeomUtils.centroid(sNorm.Points) sNorm = sNorm.translate(-1*centroid.X, -1 * centroid.Y) storeTemplate(sNorm, len(self.StrokeList), tag=template_name) self.Clear()
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 Score( self, strokelist, max_return = 1, interest = 0.2): "Compare these strokes to all templates, and return the best templates with their scores. " ROTATIONS = 16 best_templ = None for stroke_order in itertools.permutations(strokelist): pointlist = [] for s in stroke_order: pointlist.extend(s.Points) new_stroke = Stroke.Stroke(points=pointlist) for name, template_set in self._templates.items(): for template in template_set: for angle in [2 * math.pi / ROTATIONS * i for i in range(ROTATIONS)]: end_template = GeomUtils.rotateStroke(Stroke.Stroke(points=template), angle).Points firstpass_score = _scoreStroke(new_stroke, end_template, 10) if best_templ is not None and firstpass_score - 0.1 > best_templ['score']: continue score = _scoreStroke(new_stroke, end_template, len(end_template)) logger.debug(" '%s' ... %s" % (name, score)) if best_templ is None: best_templ = {'score': score + 1} if score < best_templ['score']: best_templ['name'] = name best_templ['score'] = score best_templ['template'] = end_template return best_templ
def onAnnotationSuggested(self, anno_type, strokelist): """Called when the a list of strokes are suggested to yield an annotation of type anno_type.""" #First pass, assume we were just too strict with the distance/size thresholds knownHeads = [] knownTails = [] for tip, stk in self._arrowHeads: if stk in strokelist: knownHeads.append( (tip, stk) ) for ep, tail_stk in self._endpoints: if tail_stk in strokelist: for tip, head_stk in knownHeads: if head_stk == tail_stk: continue headEnds = ( head_stk.Points[0], head_stk.Points[-1] ) if GeomUtils.pointInAngleCone(ep, headEnds[0], tip, headEnds[1]): anno = ArrowAnnotation( tip, ep, headstroke= head_stk, tailstroke = tail_stk ) self.getBoard().AnnotateStrokes([head_stk, tail_stk],anno) logger.debug("Suggestion Response: Matched arrow with looser constraints") return #Second pass, we missed the arrowhead to begin with logger.warn("Not able to generate Arrow!") 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 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 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 drawMyself( self ): for a in self.annotation_list: center = GeomUtils.centroid(a.Strokes[0].Points) #Ugly hack to get the center of the annotation! templ_stroke = Stroke.Stroke(a.template) templ_stroke = templ_stroke.translate(center.X, center.Y) SketchGUI.drawText(center.X, center.Y, InText=a.name) SketchGUI.drawStroke(templ_stroke, color="#F050F0")
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 classifyArrowhead(board, stroke): """Following the class 1 semantics, this classifies arrowheads""" if _isArrowHead(stroke, arrowHeadMatcher): # * (tip-point) # o o # o o # o o # o o #Get the endpoints/tip point as max curvature strokeNorm = GeomUtils.strokeNormalizeSpacing(stroke, numpoints = 7) curvatures = GeomUtils.strokeGetPointsCurvature(strokeNorm) ptIdx = curvatures.index(max(curvatures)) tip = strokeNorm.Points[ptIdx] #Middle is the point of max curvature annotation = ArrowHeadAnnotation(stroke.Points[0], tip, stroke.Points[-1]) return annotation return None
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 tipToNode( self, arrow_anno, circle_anno ): "return true if the tip of the arrow points to the circle" lineDist = min(10, max(len(arrow_anno.tailstroke.Points) / 10, 1)) #Check the last 10th of the stroke points the right way #lineseg: two points from arrow "neck" to arrowhead tip #lineseg2: two points from arrow "neck" to last point in tail stroke if arrow_anno.direction == "tail2head": lineSeg = ( arrow_anno.tailstroke.Points[-lineDist], arrow_anno.tip ) lineSeg2 = ( arrow_anno.tailstroke.Points[-lineDist], arrow_anno.tailstroke.Points[-1] ) else: #direction == 'head2tail' lineSeg = ( arrow_anno.tailstroke.Points[lineDist], arrow_anno.tip ) lineSeg2 = ( arrow_anno.tailstroke.Points[lineDist], arrow_anno.tailstroke.Points[0] ) if GeomUtils.pointDist( arrow_anno.tip, circle_anno.center ) < circle_anno.radius* DiGraphAnnotation.MATCHING_DISTANCE: if GeomUtils.linePointsTowards( lineSeg[0], lineSeg[1], circle_anno.center, circle_anno.radius * DiGraphAnnotation.POINTSTO_DISTANCE): return True if GeomUtils.linePointsTowards( lineSeg2[0], lineSeg2[1], circle_anno.center, circle_anno.radius * DiGraphAnnotation.POINTSTO_DISTANCE): 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 s_norm = GeomUtils.strokeNormalizeSpacing( stroke, 20 ) s_chop = GeomUtils.strokeChopEnds( s_norm, 0.20 ) circ_norm = GeomUtils.strokeCircularity( s_norm ) circ_chop = GeomUtils.strokeCircularity( s_chop ) logger.debug( "stroke: %s", [str(p) for p in s_norm.Points] ) logger.debug( "potential circles (%f,%f) <> %f", circ_norm, circ_chop, self.threshold ) if( circ_norm>self.threshold or circ_chop>self.threshold): cen = stroke.Center avgDist = GeomUtils.averageDistance( cen, stroke.Points ) anno = CircleAnnotation( circ_norm, cen, avgDist ) BoardSingleton().AnnotateStrokes( [stroke], anno)
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" )