def fitSingleSegment(a): xmin, ymin, w, h = geometric.computeBox(a) inverse = w < h if inverse: a = numpy.roll(a, 1, axis=1) seg = regLin(a) if inverse: seg.inverse() #a = numpy.roll(a,1,axis=0) return seg
def isCurvedSegment(pathGroup): """Check if the segments in pathGroups can form a rectangle. Returns a Rectangle or None""" #print 'xxxxxxxx isRectangle',pathGroups if isinstance(pathGroup, Circle): return None segmentList = [p for p in pathGroup.listOfPaths if p.isSegment()] #or p.effectiveNPoints >0] if len(segmentList) != 4: debug('rectangle Failed at length ', len(segmentList)) return None a, b, c, d = segmentList if geometric.length(a.point1, d.pointN) > 0.2 * (a.length + d.length) * 0.5: debug('rectangle test failed closing ', geometric.length(a.point1, d.pointN), a.length, d.length) return None Aac, Abd = geometric.closeAngleAbs(a.angle, c.angle), geometric.closeAngleAbs( b.angle, d.angle) if min(Aac, Abd) > 0.07 or max(Aac, Abd) > 0.27: debug('rectangle Failed at angles', Aac, Abd) return None notsimilarL = lambda d1, d2: abs(d1 - d2) > 0.20 * min(d1, d2) pi, twopi = numpy.pi, 2 * numpy.pi angles = numpy.array([p.angle for p in segmentList]) minAngleInd = numpy.argmin( numpy.minimum(abs(angles), abs(abs(angles) - pi), abs(abs(angles) - twopi))) rotAngle = angles[minAngleInd] width = (segmentList[minAngleInd].length + segmentList[(minAngleInd + 2) % 4].length) * 0.5 height = (segmentList[(minAngleInd + 1) % 4].length + segmentList[(minAngleInd + 3) % 4].length) * 0.5 # set rectangle center as the bbox center x, y, w, h = geometric.computeBox( numpy.concatenate([p.points for p in segmentList])) r = Rectangle(numpy.array([x + w / 2, y + h / 2]), (width, height), rotAngle, pathGroup.listOfPaths, pathGroup.refNode) debug(' found a rectangle !! ', a.length, b.length, c.length, d.length) return r
def box(self): return geometric.computeBox(self.points)
def getdiag(points): xmin, ymin, w, h = geometric.computeBox(points) return numpy.sqrt(w**2 + h**2), w, h
def segsFromTangents(svgCommandsList, refNode, options): """Finds segments part in a list of points represented by svgCommandsList. The method is to build the (averaged) tangent vectors to the curve. Aligned points will have tangent with similar angle, so we cluster consecutive angles together to define segments. Then we extend segments to connected points not already part of other segments. Then we merge consecutive segments with similar angles. """ sourcepoints, svgCommandsList = geometric.toArray(svgCommandsList) d = geometric.D(sourcepoints[0], sourcepoints[-1]) x, y, wTot, hTot = geometric.computeBox(sourcepoints) if wTot == 0: wTot = 0.001 if hTot == 0: hTot = 0.001 if d == 0: # then we remove the last point to avoid null distance # in other calculations sourcepoints = sourcepoints[:-1] svgCommandsList = svgCommandsList[:-1] isClosing = FitShapes.isClosing(wTot, hTot, d) if len(sourcepoints) < 4: return groups.PathGroup.toSegments(sourcepoints, svgCommandsList, refNode, isClosing=isClosing) tangents = manipulation.buildTangents(sourcepoints, isClosing=isClosing) aCurvedSegment = FitShapes.curvedFromTangents(svgCommandsList, refNode, x, y, wTot, hTot, d, isClosing, sourcepoints, tangents, options) if not aCurvedSegment == None: return aCurvedSegment # cluster points by angle of their tangents ------------- tgSegs = [ internal.Segment.fromCenterAndDir(p, t) for (p, t) in zip(sourcepoints, tangents) ] clustersInd = sorted( manipulation.clusterAngles([s.angle for s in tgSegs])) debug("build envelop cluster: ", clustersInd) # build Segments from clusters newSegs = [] for imin, imax in clustersInd: if imin + 1 < imax: # consider clusters with more than 3 points seg = manipulation.fitSingleSegment(sourcepoints[imin:imax + 1]) elif imin + 1 == imax: # 2 point path : we build a segment seg = internal.Segment.from2Points(sourcepoints[imin], sourcepoints[imax], sourcepoints[imin:imax + 1]) else: seg = internal.Path(sourcepoints[imin:imax + 1]) seg.sourcepoints = sourcepoints newSegs.append(seg) manipulation.resetPrevNextSegment(newSegs) debug(newSegs) # ----------------------- # ----------------------- # Merge consecutive Path objects updatedSegs = [] def toMerge(p): l = [p] setattr(p, 'merged', True) if hasattr(p, "__next__") and not p.next.isSegment(): l += toMerge(p.next) return l for i, seg in enumerate(newSegs[:-1]): if seg.isSegment(): updatedSegs.append(seg) continue if hasattr(seg, 'merged'): continue mergeList = toMerge(seg) debug('merging ', mergeList) p = internal.Path(numpy.concatenate([p.points for p in mergeList])) debug('merged == ', p.points) updatedSegs.append(p) if not hasattr(newSegs[-1], 'merged'): updatedSegs.append(newSegs[-1]) debug("merged path", updatedSegs) newSegs = manipulation.resetPrevNextSegment(updatedSegs) # Extend segments ----------------------------------- if options.segExtensionEnable: newSegs = extenders.SegmentExtender.extendSegments( newSegs, options.segExtensionDtoSeg, options.segExtensionQual) debug("extended segs", newSegs) newSegs = manipulation.resetPrevNextSegment(newSegs) debug("extended segs", newSegs) # ---------------------------------------- # --------------------------------------- # merge consecutive segments with close angle updatedSegs = [] if options.segAngleMergeEnable: newSegs = miscellaneous.mergeConsecutiveCloseAngles( newSegs, mangle=options.segAngleMergeTol1) newSegs = manipulation.resetPrevNextSegment(newSegs) debug(' __ 2nd angle merge') newSegs = miscellaneous.mergeConsecutiveCloseAngles( newSegs, mangle=options.segAngleMergeTol2) # 2nd pass newSegs = manipulation.resetPrevNextSegment(newSegs) debug('after merge ', len(newSegs), newSegs) # Check if first and last also have close angles. if isClosing and len(newSegs) > 2: first, last = newSegs[0], newSegs[-1] if first.isSegment() and last.isSegment(): if geometric.closeAngleAbs(first.angle, last.angle) < 0.1: # force merge points = numpy.concatenate([last.points, first.points]) newseg = manipulation.fitSingleSegment(points) newseg.next = first.__next__ if hasattr( first, "__next__") else None last.prev.next = None newSegs[0] = newseg newSegs.pop() # ----------------------------------------------------- # remove negligible Path/Segments between 2 large Segments if options.segRemoveSmallEdge: PreProcess.removeSmallEdge(newSegs, wTot, hTot) newSegs = manipulation.resetPrevNextSegment(newSegs) debug('after remove small ', len(newSegs), newSegs) # ----------------------------------------------------- # ----------------------------------------------------- # Extend segments to their intersections for p in newSegs: if p.isSegment() and hasattr(p, "__next__"): p.setIntersectWithNext() # ----------------------------------------------------- return groups.PathGroup(newSegs, svgCommandsList, refNode, isClosing)
def checkForArcs(points, tangents): """Determine if the points and their tangents represent a circle The difficulty is to be able to recognize ellipse while avoiding paths small fluctuations a nd false positive due to badly drawn rectangle or non-convex closed curves. Method : we consider angle of tangent as function of lenght on path. For circles these are : angle = c1 x lenght + c0. (c1 ~1) We calculate dadl = d(angle)/d(length) and compare to c1. We use 3 criteria : * num(dadl > 6) : number of sharp angles * length(dadl<0.3)/totalLength : lengths of straight lines within the path. * totalLength/(2pi x radius) : fraction of lenght vs a plain circle Still failing to recognize elongated ellipses... """ if len(points) < 10: return False, 0 if all(points[0] == points[-1]): # last exactly equals the first. # Ignore last point for this check points = points[:-1] tangents = tangents[:-1] print(('Removed last ', points)) xmin, ymin, w, h = geometric.computeBox(points) diag2 = (w * w + h * h) diag = numpy.sqrt(diag2) * 0.5 norms = numpy.sqrt(numpy.sum(tangents**2, 1)) angles = numpy.arctan2(tangents[:, 1], tangents[:, 0]) #debug( 'angle = ', repr(angles)) N = len(angles) deltas = points[1:] - points[:-1] deltasD = numpy.concatenate( [[geometric.D(points[0], points[-1]) / diag], numpy.sqrt(numpy.sum(deltas**2, 1)) / diag]) # locate and avoid the point when swicthing # from -pi to +pi. The point is around the minimum imin = numpy.argmin(angles) debug(' imin ', imin) angles = numpy.roll(angles, -imin) deltasD = numpy.roll(deltasD, -imin) n = int(N * 0.1) # avoid fluctuations by removing points around the min angles = angles[n:-n] deltasD = deltasD[n:-n] deltasD = deltasD.cumsum() N = len(angles) # smooth angles to avoid artificial bumps angles = manipulation.smoothArray(angles, n=max(int(N * 0.03), 2)) deltaA = angles[1:] - angles[:-1] deltasDD = (deltasD[1:] - deltasD[:-1]) deltasDD[numpy.where(deltasDD == 0.)] = 1e-5 * deltasD[0] dAdD = abs(deltaA / deltasDD) belowT, count = True, 0 self.temp = (deltasD, angles, tangents, dAdD) #TODO: Loop over deltasDD searching for curved segments, no sharp bumps and a curve of at least 1/4 pi curveStart = 0 curveToTest = numpy.array([deltasDD[curveStart]]) dAdDd = numpy.array([dAdD[curveStart]]) v = dAdD[curveStart] belowT = (v < 6) for i in range(1, deltasDD.size): curveToTest = numpy.append(curveToTest, deltasDD[i]) dAdDd = numpy.append(dAdDd, dAdD[i]) fracStraight = numpy.sum(curveToTest[numpy.where( dAdDd < 0.3)]) / (deltasD[i] - deltasD[curveStart]) curveLength = (deltasD[i] - deltasD[curveStart]) / 3.14 v = dAdD[i] if v > 6 and belowT: count += 1 belowT = False belowT = (v < 6) inkex.debug("SSS " + str(count) + ":" + str(fracStraight)) if curveLength > 1.4 or fracStraight > 0.4 or count > 8: inkex.debug("curveLengtha:" + str(curveLength) + "fracStraight:" + str(fracStraight) + "count:" + str(count)) isArc = False curveStart = int(i) curveToTest = numpy.array([deltasDD[curveStart]]) v = dAdD[curveStart] dAdDd = numpy.array([dAdD[curveStart]]) belowT = (v < 6) count = 0 continue else: inkex.debug("curveLengthb:" + str(curveLength) + "fracStraight:" + str(fracStraight) + "count:" + str(count)) isArc= (count < 4 and fracStraight<=0.3) or \ (fracStraight<=0.1 and count<5) if not isArc: return False, 0 # It's a circle ! radius = points - numpy.array([xmin + w * 0.5, ymin + h * 0.5]) radius_n = numpy.sqrt(numpy.sum(radius**2, 1)) # normalize mini = numpy.argmin(radius_n) rmin = radius_n[mini] maxi = numpy.argmax(radius_n) rmax = radius_n[maxi] # void points around maxi and mini to make sure the 2nd max is found # on the "other" side n = len(radius_n) radius_n[maxi] = 0 radius_n[mini] = 0 for i in range(1, int(n / 8 + 1)): radius_n[(maxi + i) % n] = 0 radius_n[(maxi - i) % n] = 0 radius_n[(mini + i) % n] = 0 radius_n[(mini - i) % n] = 0 radius_n_2 = [r for r in radius_n if r > 0] rmax_2 = max(radius_n_2) rmin_2 = min(radius_n_2) # not good !! anglemax = numpy.arccos(radius[maxi][0] / rmax) * numpy.sign( radius[maxi][1]) return True, (xmin + w * 0.5, ymin + h * 0.5, 0.5 * (rmin + rmin_2), 0.5 * (rmax + rmax_2), anglemax)