131 lines
5.0 KiB
Python
131 lines
5.0 KiB
Python
|
import numpy
|
||
|
import sys
|
||
|
from shaperrec import manipulation
|
||
|
|
||
|
# *************************************************************
|
||
|
# debugging
|
||
|
def void(*l):
|
||
|
pass
|
||
|
def debug_on(*l):
|
||
|
sys.stderr.write(' '.join(str(i) for i in l) +'\n')
|
||
|
debug = void
|
||
|
#debug = debug_on
|
||
|
|
||
|
##**************************************
|
||
|
##
|
||
|
class SegmentExtender:
|
||
|
"""Extend Segments part of a list of Path by aggregating points from neighbouring Path objects.
|
||
|
|
||
|
There are 2 concrete subclasses for extending forward and backward (due to technical reasons).
|
||
|
"""
|
||
|
|
||
|
def __init__(self, relD, fitQ):
|
||
|
self.relD = relD
|
||
|
self.fitQ = fitQ
|
||
|
|
||
|
def nextPaths(self, seg):
|
||
|
pL = []
|
||
|
p = self.getNext(seg) # prev or next
|
||
|
while p :
|
||
|
if p.isSegment(): break
|
||
|
if p.mergedObj is None: break
|
||
|
pL.append(p)
|
||
|
p = self.getNext(p)
|
||
|
if pL==[]:
|
||
|
return []
|
||
|
return pL
|
||
|
|
||
|
def extend(self, seg):
|
||
|
nextPathL = self.nextPaths(seg)
|
||
|
debug('extend ', self.extDir, seg, nextPathL, seg.length, len(nextPathL))
|
||
|
if nextPathL==[]: return seg
|
||
|
pointsToTest = numpy.concatenate( [p.points for p in nextPathL] )
|
||
|
mergeD = seg.length*self.relD
|
||
|
#print seg.point1 , seg.pointN, pointsToTest
|
||
|
pointsToFit, addedPoints = self.pointsToFit(seg, pointsToTest, mergeD)
|
||
|
if len(pointsToFit)==0:
|
||
|
return seg
|
||
|
newseg = manipulation.fitSingleSegment(pointsToFit)
|
||
|
if newseg.quality()>self.fitQ: # fit failed
|
||
|
return seg
|
||
|
debug( ' EXTENDING ! ', len(seg.points), len(addedPoints) )
|
||
|
self.removePath(seg, newseg, nextPathL, addedPoints )
|
||
|
newseg.points = pointsToFit
|
||
|
seg.mergedObj= newseg
|
||
|
newseg.sourcepoints = seg.sourcepoints
|
||
|
|
||
|
return newseg
|
||
|
|
||
|
@staticmethod
|
||
|
def extendSegments(segmentList, relD=0.03, qual=0.5):
|
||
|
"""Perform Segment extension from list of Path segmentList
|
||
|
returns the updated list of Path objects"""
|
||
|
fwdExt = FwdExtender(relD, qual)
|
||
|
bwdExt = BwdExtender(relD, qual)
|
||
|
# tag all objects with an attribute pointing to the extended object
|
||
|
for seg in segmentList:
|
||
|
seg.mergedObj = seg # by default the extended object is self
|
||
|
# extend each segments, starting by the longest
|
||
|
for seg in sorted(segmentList, key = lambda s : s.length, reverse=True):
|
||
|
if seg.isSegment():
|
||
|
newseg=fwdExt.extend(seg)
|
||
|
seg.mergedObj = bwdExt.extend(newseg)
|
||
|
# the extension procedure has marked as None the mergedObj
|
||
|
# which have been swallowed by an extension.
|
||
|
# filter them out :
|
||
|
updatedSegs=[seg.mergedObj for seg in segmentList if seg.mergedObj]
|
||
|
return updatedSegs
|
||
|
|
||
|
|
||
|
class FwdExtender(SegmentExtender):
|
||
|
extDir='Fwd'
|
||
|
def getNext(self, seg):
|
||
|
return seg.__next__ if hasattr(seg, "__next__") else None
|
||
|
def pointsToFit(self, seg, pointsToTest, mergeD):
|
||
|
distancesToLine =abs(seg.a*pointsToTest[:, 0]+seg.b*pointsToTest[:, 1]+seg.c)
|
||
|
goodInd=len(pointsToTest)
|
||
|
for i, d in reversed(list(enumerate(distancesToLine))):
|
||
|
if d<mergeD: goodInd=i;break
|
||
|
addedPoints = pointsToTest[:len(pointsToTest-goodInd)]
|
||
|
#debug( ' ++ pointsToFit ' , mergeD, i ,len(pointsToTest), addedPoints , seg.points )
|
||
|
return numpy.concatenate([seg.points, addedPoints]), addedPoints
|
||
|
def removePath(self, seg, newseg, nextPathL, addedPoints):
|
||
|
npoints = len(addedPoints)
|
||
|
acc=0
|
||
|
newseg.prev = seg.prev
|
||
|
for p in nextPathL:
|
||
|
if (acc+len(p.points))<=npoints:
|
||
|
p.mergedObj = None
|
||
|
acc += len(p.points)
|
||
|
else:
|
||
|
newseg.next = p
|
||
|
p.points = p.points[:(npoints-acc-len(p.points))]
|
||
|
break
|
||
|
|
||
|
class BwdExtender(SegmentExtender):
|
||
|
extDir='Bwd'
|
||
|
def getNext(self, seg):
|
||
|
return seg.prev
|
||
|
def pointsToFit(self, seg, pointsToTest, mergeD):
|
||
|
# TODO: shouldn't the distances be sorted cclosest to furthest
|
||
|
distancesToLine =abs(seg.a*pointsToTest[:, 0]+seg.b*pointsToTest[:, 1]+seg.c)
|
||
|
goodInd=len(pointsToTest)
|
||
|
for i, d in enumerate(distancesToLine):
|
||
|
if d<mergeD: goodInd=i; break
|
||
|
addedPoints = pointsToTest[goodInd:]
|
||
|
#debug( ' ++ pointsToFit ' , mergeD, i ,len(pointsToTest), addedPoints , seg.points )
|
||
|
return numpy.concatenate([addedPoints, seg.points]), addedPoints
|
||
|
def removePath(self, seg, newseg, nextPathL, addedPoints):
|
||
|
npoints = len(addedPoints)
|
||
|
acc=0
|
||
|
newseg.next = seg.__next__ if hasattr(seg, "__next__") else None
|
||
|
for p in reversed(nextPathL):
|
||
|
if (acc+len(p.points))<=npoints:
|
||
|
p.mergedObj = None
|
||
|
acc += len(p.points)
|
||
|
else:
|
||
|
newseg.prev = p
|
||
|
p.points = p.points[(npoints-acc-len(p.points)):]
|
||
|
break
|
||
|
|