Ejemplo n.º 1
0
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)