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 _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 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 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 _isArrowHead(stroke, matcher): numPts = 11 sNorm = GeomUtils.strokeNormalizeSpacing(stroke, numpoints = numPts) curvatures = GeomUtils.strokeGetPointsCurvature(sNorm) maxCurv = max(curvatures) maxCurvIdx = curvatures.index(maxCurv) #Make sure the max curvature is roughly in the middle of the stroke before even bothering # with more complicated checks if maxCurvIdx > (numPts / 5.0) and maxCurvIdx < ( 4 * numPts / 5.0): strkLen = GeomUtils.strokeLength(stroke) arrowHeadStroke = GeomUtils.strokeNormalizeSpacing(Stroke([sNorm.Points[0], sNorm.Points[maxCurvIdx], sNorm.Points[-1]]), numpoints = strkLen) #What would the approximated arrowhead look like? origStroke = GeomUtils.strokeNormalizeSpacing(stroke, numpoints = strkLen) approxAcc = GeomUtils.strokeDTWDist(sNorm, arrowHeadStroke) #logger.debug("Stroke approximates arrowhead with %s accuracy" % (approxAcc)) return approxAcc < 500000 #_isArrowHead_Template(stroke, matcher) or _isArrowHead_Template(Stroke(list(reversed(stroke.Points))), matcher) 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 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 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 drawAnno(self, anno): #pdb.set_trace() if self.trackedAnno == None: self.trackedAnno = anno for stk in anno.Strokes: prevPt = None for i, point in enumerate(GeomUtils.strokeNormalizeSpacing(stk, numpoints = len(stk.Points)).Points): if prevPt != None: color = self.colors[anno.pattern[ (i + anno.idx) % len(anno.pattern) ] ] self.getBoard().getGUI().drawLine(prevPt.X, prevPt.Y, point.X, point.Y, color=color, width = 3) prevPt = point if anno == self.trackedAnno: dt = time.time() - self.lastDraw self.lastDraw = time.time() logger.debug("Effective FPS = %s" % (1 / float(dt)))
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 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 strokeCurvatureHistogram(stroke, norm_len=None, gran=10): # points = GeomUtils.strokeNormalizeSpacing( stroke, numpoints=50).Points rad2deg = 57.295 if norm_len == None: norm_len = len(stroke.Points) norm_stroke = GeomUtils.strokeNormalizeSpacing(stroke, numpoints=norm_len) # find the first 90 degree turn in the stroke curvatures = GeomUtils.strokeGetPointsCurvature(norm_stroke) for idx, ori in enumerate(curvatures): print "%s:\t|" % (idx), quantity = ori * rad2deg while quantity > 0: quantity -= gran print "X", print "\t\t%s" % (ori * rad2deg) print "_______________________________" print "Max:%s, Avg%s" % (max(curvatures), sum(curvatures) / float(len(curvatures))) print "_______________________________"
def _isSingleStrokeArrow(stroke): "Input: Single stroke for evaluation. Returns a tuple of points (tip, tail) if the stroke is an arrow, (None, None) otherwise" logger.debug("stroke len %d", stroke.length() ) if len(stroke.Points) < 10: return (None, None)# too small to be arrow norm_len = len(stroke.Points) points = GeomUtils.strokeNormalizeSpacing( stroke, numpoints=norm_len).Points points.reverse() # start from end # find the first 90 degree turn in the stroke orilist = GeomUtils.strokeLineSegOrientations( Stroke(points) ) logger.debug("stroke ori %s", str(orilist) ) prev = None i = 0 for i,ori in enumerate(orilist): if prev is None: prev = ori continue if GeomUtils.angleDiff(ori,prev)>90: break # found the first turn at index i first_corner = i # now we know the scale of the arrow head if there is one # if the first corner is more than 1/4 of the way from the end of the stroke if first_corner > norm_len/5: return (None, None) # scale is wrong for an arrowhead tail = stroke.Points[0] # first of the original points tip = points[i] # reverse point # create a list of the monoticity of all substrokes from the first corner to some dist after m_list = [ GeomUtils.strokeMonotonicity(Stroke(points[first_corner:x])) for x in range(first_corner+2,first_corner*3) ] if len(m_list) == 0: return (None, None) m_min = min(m_list) logger.debug("stroke mon (%f)", m_min ) if m_min>0.65: return (None, None)# too monotonic after the first corner, need to double back return (tip, tail)
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
def generateFeatureVector(stroke): finalVector = {} finalVector['len'] = GeomUtils.strokeLength(stroke) sNorm = GeomUtils.strokeNormalizeSpacing( stroke, max(finalVector['len'] / 4.0, 1) ) finalVector['orientations'] = GeomUtils.pointListOrientationHistogram(sNorm.Points) return finalVector
def _isSingleStrokeArrow(stroke): "Input: Single stroke for evaluation. Returns a tuple of points (tip, tail) if the stroke is an arrow, (None, None) otherwise" if len(stroke.Points) < 10: logger.debug("Not a single-stroke arrow: stroke too short") return (None, None)# too small to be arrow """ #Starting code for line curvature classification #points = GeomUtils.strokeNormalizeSpacing( stroke, numpoints=50).Points for gran in range(1, 10): norm_len = max(len(stroke.Points) / gran, 5) points = GeomUtils.strokeNormalizeSpacing( stroke, numpoints=norm_len).Points points.reverse() # start from end # find the first 90 degree turn in the stroke curvatures = GeomUtils.strokeGetPointsCurvature( Stroke(points) ) gran = 0.1 for idx, ori in enumerate(curvatures): print "%s:\t|" % (idx), quantity = ori while quantity > 0: quantity -= gran print "X", print "\t\t%s" % (ori) print "_______________________________" print "Max:%s, Avg%s" % (max(curvatures), sum(curvatures)/float(len(curvatures))) print "_______________________________" #/EndCurvature classification """ norm_len = max(len(stroke.Points) / 10, 15) points = GeomUtils.strokeNormalizeSpacing( stroke, numpoints=norm_len).Points orilist = GeomUtils.strokeLineSegOrientations( Stroke(points) ) #logger.debug("stroke ori %s", str(orilist) ) prev = None i = 0 for i,ori in enumerate(orilist): if prev is None: prev = ori continue if GeomUtils.angleDiff(ori,prev)>90: break # found the first turn at index i first_corner = i # now we know the scale of the arrow head if there is one # if the first corner is more than 1/4 of the way from the end of the stroke if first_corner > norm_len/5: logger.debug("Not a ss arrow: First right angle too far from endpoint") return (None, None) # scale is wrong for an arrowhead tail = stroke.Points[0] # first of the original points tip = points[i] # reverse point # create a list of the monoticity of all substrokes from the first corner to some dist after m_list = [ GeomUtils.strokeMonotonicity(Stroke(points[first_corner:x])) for x in range(first_corner+2,first_corner*3) ] if len(m_list) == 0: logger.debug("Not a ss arrow: Stroke monotonicity list zero length") return (None, None) m_min = min(m_list) #logger.debug("stroke mon (%f)", m_min ) if m_min>0.65: logger.debug("Not a ss arrow: Stroke too monotonic") return (None, None)# too monotonic after the first corner, need to double back logger.debug("Single Stroke Arrow found!") return (tip, tail)
def traceStroke(stroke): """Take in a true stroke with timing data, bitmap it and then trace the data for it""" #logger.debug("Stripping Timing Information from Stroke") #logger.debug("Stroke in, %s points" % len(stroke.Points)) strokeLen = GeomUtils.strokeLength(stroke) sNorm = GeomUtils.strokeNormalizeSpacing(stroke, int(len(stroke.Points) * 1.5)) #Normalize to ten pixel spacing graph = {} #Graph structure looks like # { <point (x, y)> : {'kids' : <set of Points>, 'thickness' : <number>} } #Find self intersections intersections = {} for i in range(len(sNorm.Points) - 1): seg1 = (sNorm.Points[i], sNorm.Points[i+1]) for j in range(i+1, len(sNorm.Points) - 1 ): seg2 = (sNorm.Points[j], sNorm.Points[j+1]) cross = GeomUtils.getLinesIntersection( seg1, seg2) #Create a new node at the intersection if cross != None \ and cross != seg1[0] \ and cross != seg2[0]: crossPt = (cross.X, cross.Y) intDict = intersections.setdefault(crossPt, {'kids' : set(), 'thickness' : 1}) for pt in seg1 + seg2: #Add the segment endpoints as kids coords = (int(pt.X), int(pt.Y)) if coords != crossPt: intDict['kids'].add(coords) prevPt = None #for i in range(1, len(sNorm.Points)): for pt in sNorm.Points: curPt = (int(pt.X), int(pt.Y)) if prevPt != None: #prevPt = (pt.X, pt.Y) graph[curPt] = {'kids' : set([prevPt]), 'thickness':1} graph[prevPt]['kids'].add(curPt) else: graph[curPt] = {'kids' : set(), 'thickness' :1 } prevPt = curPt for pt, ptDict in intersections.items(): for k in graph.get(pt, {'kids' : []})['kids']: ptDict['kids'].add(k) graph[k]['kids'].add(pt) for k in ptDict['kids']: graph[k]['kids'].add(pt) graph[pt] = ptDict strokeList = ImageStrokeConverter.graphToStrokes(graph) if len(strokeList) > 1: #logger.debug("Stroke tracing split into multiple strokes") strokeList.sort(key=(lambda s: -len(s.points))) retPts = [] if len(strokeList) > 0: for pt in strokeList[0].points: #logger.debug("Adding point %s" % (str(pt))) retPts.append(Point(pt[0], pt[1])) #logger.debug("Stroke out, %s points" % len(retPts)) retStroke = Stroke(retPts) #saver = StrokeStorage.StrokeStorage() #saver.saveStrokes([stroke, retStroke]) return retStroke
def onStrokeAdded( self, stroke ): "Watches for Strokes that look like an arrow to Annotate" smoothedStroke = GeomUtils.strokeSmooth(stroke) ep1 = stroke.Points[0] ep2 = stroke.Points[-1] #ep1 = smoothedStroke.Points[0] #ep2 = smoothedStroke.Points[-1] isArrowHead = False #GeomUtils.ellipseAxisRatio(stroke) #Match single-stroke arrows #DISABLED logger.debug("**Warning: Single-stroke arrows disabled**") tip, tail = None, None tip, tail = _isSingleStrokeArrow(smoothedStroke) #if tip is None or tail is None: #revpts = list(smoothedStroke.Points) #revpts.reverse() #tip, tail = _isSingleStrokeArrow(Stroke(revpts)) if tip is not None and tail is not None: isArrowHead = False anno = ArrowAnnotation( tip, tail, headstroke= stroke, tailstroke = stroke ) self.getBoard().AnnotateStrokes( [stroke], anno) #/DISABLED else: if _isArrowHead(smoothedStroke, self.arrowHeadMatcher): logger.debug(" ARROWHEAD") #head = smoothedStroke head = stroke isArrowHead = True # * (tip-point) # o o # o o # o o # o o #Get the endpoints/tip point as max curvature strokeNorm = GeomUtils.strokeNormalizeSpacing(smoothedStroke, numpoints = 7) curvatures = GeomUtils.strokeGetPointsCurvature(strokeNorm) ptIdx = curvatures.index(max(curvatures)) tip = strokeNorm.Points[ptIdx] #Middle is the point of max curvature #Match it to any tails we have 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] direction = 'head2tail' elif tail.Points[-1] == headpoint: endpoint = tail.Points[0] direction = 'tail2head' logger.debug("Stroke is head of arrow, drawn %s" % (direction)) anno = ArrowAnnotation(tip, endpoint, headstroke = stroke, tailstroke = tail, direction = direction) self.getBoard().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) tail = stroke for tip, head in matchedHeads: logger.debug("Stroke is tail of arrow, drawn head2tail") anno = ArrowAnnotation(tip, ep2, headstroke = head, tailstroke = tail, direction='head2tail') #Arrow is from the back endpoint to the tip of the arrowhead self.getBoard().AnnotateStrokes([head, tail],anno) matchedHeads = self._matchHeadtoTail(tail = stroke, point = ep2) for tip, head in matchedHeads: logger.debug("Stroke is tail of arrow, drawn tail2head") anno = ArrowAnnotation(tip, ep1, headstroke = head, tailstroke =tail, direction='tail2head') self.getBoard().AnnotateStrokes([head, tail],anno) #Add this stroke to the pool for future evaluation sNorm = GeomUtils.strokeNormalizeSpacing(stroke, numpoints = max(GeomUtils.strokeLength(stroke) / 3, 1)) self._endpoints.append( (ep1, stroke, sNorm) ) self._endpoints.append( (ep2, stroke, sNorm) ) if isArrowHead: self._arrowHeads.append( (tip, stroke, sNorm) )