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 _matchHeadtoTail(self, head = None, tail = None, point = None): """Input head stroke or tail stroke. If head is specified, match it to a tail. If tail is specified, match it to a head. Parameter 'point' should be the tip if head is specified, the end-point if tail is specified. Returns a list of tuples: (tip, head_stroke) if tail is specified, (endpoint, tail_stroke) if head is specified.""" retlist = [] if point is None: return retlist if head is not None and tail is None: #Head is specified, find the tail tip = point ep1, ep2 = head.Points[0], head.Points[-1] headBreadth = GeomUtils.pointDistance(ep1.X, ep1.Y, ep2.X, ep2.Y) for endpoint, tailStroke in self._endpoints: if GeomUtils.strokeLength(head) < GeomUtils.strokeLength(tailStroke) \ and _isPointWithHead(endpoint, head, tip): #Make sure the proportions aren't totally off logger.debug("Head stroke has a tail close and within cone") pointingLength = len(tailStroke.Points) / 5 #headToTail if endpoint == tailStroke.Points[0]: linept1, linept2 = tailStroke.Points[pointingLength], endpoint elif endpoint== tailStroke.Points[-1]: linept1, linept2 = tailStroke.Points[-pointingLength], endpoint pointsTo = GeomUtils.linePointsTowards(linept1, linept2, tip, headBreadth) if pointsTo: retlist.append( (endpoint, tailStroke) ) elif tail is not None and head is None: #Find the head endpoint = point pointingLength = len(tail.Points) / 5 #headToTail if endpoint == tail.Points[0]: linept1, linept2 = tail.Points[pointingLength], endpoint elif endpoint== tail.Points[-1]: linept1, linept2 = tail.Points[-pointingLength], endpoint for tip, headStroke in self._arrowHeads: ep1, ep2 = headStroke.Points[0], headStroke.Points[-1] headBreadth = GeomUtils.pointDistance(ep1.X, ep1.Y, ep2.X, ep2.Y) if GeomUtils.strokeLength(headStroke) < GeomUtils.strokeLength(tail) \ and _isPointWithHead(endpoint, headStroke, tip): logger.debug("Tail stroke is close and within cone of an arrowhead") pointsTo = GeomUtils.linePointsTowards(linept1, linept2, tip, headBreadth) if pointsTo: retlist.append( (tip, headStroke) ) return retlist
def onStrokeAdded(self, stroke): "Tags 1's and 0's as letters (TextAnnotation)" closedDistRatio = 0.22 circularityThresh_0 = 0.80 circularityThresh_1 = 0.20 strokeLen = max(GeomUtils.strokeLength(stroke), 1) normDist = max(3, strokeLen / 5) head, tail = stroke.Points[0], stroke.Points[-1] endDist = GeomUtils.pointDistance(head.X, head.Y, tail.X, tail.Y) #If the endpoints are 1/thresh apart, actually close the thing isClosedShape = GeomUtils.pointDistanceSquared(head.X, head.Y, tail.X, tail.Y) \ < (strokeLen * closedDistRatio) ** 2 if isClosedShape: #Close the shape back up s_norm = GeomUtils.strokeNormalizeSpacing( Stroke(stroke.Points + [stroke.Points[0]]) , normDist ) else: s_norm = GeomUtils.strokeNormalizeSpacing( stroke , normDist ) curvatures = GeomUtils.strokeGetPointsCurvature(s_norm) circularity = GeomUtils.strokeCircularity( s_norm ) if isClosedShape and circularity > circularityThresh_0: height = stroke.BoundTopLeft.Y - stroke.BoundBottomRight.Y oAnnotation = TextAnnotation("0", height) l_logger.debug("Annotating %s with %s" % ( stroke, oAnnotation)) BoardSingleton().AnnotateStrokes( [stroke], oAnnotation) l_logger.debug(" Afterward: %s.annotations is %s" % ( stroke, stroke.Annotations)) elif len(stroke.Points) >= 2 \ and max(curvatures) < 0.5 \ and circularity < circularityThresh_1: if stroke.Points[0].X < stroke.Points[-1].X + strokeLen / 2.0 \ and stroke.Points[0].X > stroke.Points[-1].X - strokeLen / 2.0: height = stroke.BoundTopLeft.Y - stroke.BoundBottomRight.Y oneAnnotation = TextAnnotation("1", height) l_logger.debug("Annotating %s with %s" % ( stroke, oneAnnotation.text)) BoardSingleton().AnnotateStrokes( [stroke], oneAnnotation) l_logger.debug(" Afterward: %s.annotations is %s" % ( stroke, stroke.Annotations)) elif stroke.Points[0].Y < stroke.Points[-1].Y + strokeLen / 2.0 \ and stroke.Points[0].Y > stroke.Points[-1].Y - strokeLen / 2.0: width = stroke.BoundBottomRight.X - stroke.BoundTopLeft.X dashAnnotation = TextAnnotation("-", width * 1.5) #Treat the dash's (boosted) width as its scale l_logger.debug("Annotating %s with %s" % ( stroke, dashAnnotation.text)) BoardSingleton().AnnotateStrokes( [stroke], dashAnnotation) else: if not isClosedShape: l_logger.debug("0: Not a closed shape") if not (circularity > circularityThresh_0): l_logger.debug("0: Not circular enough: %s" % (circularity)) if not len(stroke.Points) >= 2: l_logger.debug("1: Not enough points") if not (circularity < circularityThresh_1): l_logger.debug("1: Too circular") if not (max(curvatures) < 0.5): l_logger.debug("1: Max curvature too big %s" % max(curvatures)) if not ( stroke.Points[0].X < stroke.Points[-1].X + strokeLen / 3 \ and stroke.Points[0].X > stroke.Points[-1].X - strokeLen / 3): l_logger.debug("1: Not vertical enough: \nX1 %s, \nX2 %s, \nLen %s" % (stroke.Points[0].X, stroke.Points[-1].X, strokeLen))
def onAnnotationAdded( self, strokes, annotation ): "Checks to see if an equals sign has been added" # Find the midpoints ul,br = GeomUtils.strokelistBoundingBox( strokes ) midpointY = (ul.Y + br.Y) / 2 midpointX = (ul.X + br.X) / 2 strokeLen = GeomUtils.strokeLength(strokes[0]) for a in self.possibleAnnotations: s = a.Strokes[0] prevStrokeLen = GeomUtils.strokeLength(s) # test the the two segments are of similar length lengthRange = 0.4 if prevStrokeLen * (1-lengthRange) < strokeLen < prevStrokeLen * (1+lengthRange): pass # were the right length else: # not the right length, so lets start again continue ul,br = GeomUtils.strokelistBoundingBox( [s] ) prevMidpointY = (ul.Y + br.Y) / 2 prevMidpointX = (ul.X + br.X) / 2 # Test that the two segments are close enough horizontally if GeomUtils.pointDistance(midpointX, 0, prevMidpointX, 0) < prevStrokeLen * 0.4: pass # there are close enough horizontally else: # we start again continue # Test that the two segments are close enough vertically if GeomUtils.pointDistance(0,midpointY, 0, prevMidpointY) < prevStrokeLen * 0.5: pass # there are close enough vertically else: # we start again continue # we found a match self.possibleAnnotations.remove(a) self.getBoard().AnnotateStrokes( strokes + [s], EqualsAnnotation(1)) return # no match was found, add to the list of possible self.possibleAnnotations.append(annotation) return
def onStrokeAdded( self, stroke ): "Watches for Strokes with Circularity > threshold to Annotate" # need at least 6 points to be a circle if GeomUtils.strokeLength(stroke) < 6: logger.debug("Too small, not a circle (size %s)" % GeomUtils.strokeLength(stroke) ) 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 ) logger.debug("Circle found") anno = CircleAnnotation( circ_norm, cen, avgDist ) BoardSingleton().AnnotateStrokes( [stroke], anno)
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 _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 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 _matchHeadtoTail(self, head = None, tail = None, point = None): """Input head stroke or tail stroke. If head is specified, match it to a tail. If tail is specified, match it to a head. Parameter 'point' should be the tip if head is specified, the end-point if tail is specified. Returns a list of tuples: (tip, head_stroke) if tail is specified, (endpoint, tail_stroke) if head is specified.""" retlist = [] if point is None: return retlist headStroke = head tailStroke = tail if headStroke is not None and tail is None: #Head is specified, find the tail tip = point ep1, ep2 = headStroke.Points[0], headStroke.Points[-1] headBreadth = GeomUtils.pointDistance(ep1.X, ep1.Y, ep2.X, ep2.Y) for endpoint, origStk, tailStroke in self._endpoints: pointingLength = len(tailStroke.Points) / 10 if endpoint == tailStroke.Points[0]: #Treat as drawn head2tail tailpoints = tailStroke.Points linept1, linept2 = tailStroke.Points[pointingLength], endpoint elif endpoint== tailStroke.Points[-1]: #Treat as drawn tail2head tailpoints = list(reversed(tailStroke.Points)) linept1, linept2 = tailStroke.Points[-(pointingLength+1)], endpoint headLen = GeomUtils.strokeLength(headStroke) tailLen = GeomUtils.strokeLength(tailStroke) pointWithHead = _isPointWithHead(tailpoints, headStroke, tip) if headLen < tailLen * 2 \ and pointWithHead: logger.debug("Head stroke has a tail close and within cone") #headToTail pointsTo = GeomUtils.linePointsTowards(linept1, linept2, tip, headBreadth) if pointsTo: logger.debug(" Tail points to head") retlist.append( (endpoint, origStk) ) else: logger.debug(" Tail does NOT point to head") else: if headLen < tailLen * 2: logger.debug(" Head stroke scale is okay for this arrowhead") else: logger.debug(" Head stroke scale is BAD for this arrowhead") logger.debug(" Head Len: %s, tail Len: %s" % (headLen, tailLen)) if not pointWithHead: logger.debug(" Head stroke is NOT close or within cone of an arrowhead\n") else: logger.debug(" Head stroke is close and within cone of an arrowhead\n") elif tailStroke is not None and headStroke is None: #Find the head endpoint = point pointingLength = len(tailStroke.Points) / 10 if endpoint == tailStroke.Points[0]: #Treat as drawn head2tail tailpoints = tailStroke.Points linept1, linept2 = tailStroke.Points[pointingLength], endpoint elif endpoint== tailStroke.Points[-1]: #Treat as drawn tail2head tailpoints = list(reversed(tailStroke.Points)) linept1, linept2 = tailStroke.Points[-pointingLength], endpoint for tip, origStk, headStroke in self._arrowHeads: ep1, ep2 = headStroke.Points[0], headStroke.Points[-1] headBreadth = GeomUtils.pointDistance(ep1.X, ep1.Y, ep2.X, ep2.Y) headLen = GeomUtils.strokeLength(headStroke) tailLen = GeomUtils.strokeLength(tailStroke) pointWithHead = _isPointWithHead(tailpoints, headStroke, tip) if headLen < tailLen * 2\ and pointWithHead: logger.debug("Tail stroke is close and within cone of an arrowhead") pointsTo = GeomUtils.linePointsTowards(linept1, linept2, tip, headBreadth) if pointsTo: logger.debug(" Tail points to head") retlist.append( (tip, origStk) ) else: logger.debug(" Tail does NOT point to head") else: if headLen < tailLen * 2: logger.debug(" Tail stroke scale is okay for this arrowhead") else: logger.debug(" Tail stroke scale is BAD for this arrowhead") logger.debug(" Head Len: %s, tail Len: %s" % (headLen, tailLen)) if not pointWithHead: logger.debug(" Tail stroke is NOT close or within cone of an arrowhead\n") else: logger.debug(" Tail stroke is close and within cone of an arrowhead\n") return retlist
def 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) )
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 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 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 _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