mightyscape-1.2/extensions/fablabchemnitz/shape_recognition/shaperrec/groups.py

279 lines
11 KiB
Python

import numpy
import sys
import inkex
from lxml import etree
from shaperrec import geometric
from shaperrec import miscellaneous
from shaperrec import internal
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
# *************************************************************
# *************************************************************
# Groups of Path
#
class PathGroup(object):
"""A group of Path representing one SVG node.
- a list of Path
- a list of SVG commands describe the full node (=SVG path element)
- a reference to the inkscape node object
"""
listOfPaths = []
refSVGPathList = []
isClosing = False
refNode = None
def __init__(self, listOfPaths, refSVGPathList, refNode=None, isClosing=False):
self.refNode = refNode
self.listOfPaths = listOfPaths
self.refSVGPathList = refSVGPathList
self.isClosing=isClosing
def addToNode(self, node):
newList = miscellaneous.reformatList( self.listOfPaths)
ele = miscellaneous.addPath( newList, node)
debug("PathGroup ", newList)
return ele
def setNodeStyle(self, ele, node):
style = node.get('style')
cssClass = node.get('class')
debug("style ", style)
debug("class ", cssClass)
if style == None and cssClass == None :
style = 'fill:none; stroke:red; stroke-width:1'
if not cssClass == None:
ele.set('class', cssClass)
if not style == None:
ele.set('style', style)
@staticmethod
def toSegments(points, refSVGPathList, refNode, isClosing=False):
"""
"""
segs = [ internal.Segment.from2Points(p, points[i+1], points[i:i+2] ) for (i, p) in enumerate(points[:-1]) ]
manipulation.resetPrevNextSegment(segs)
return PathGroup( segs, refSVGPathList, refNode, isClosing)
class TangentEnvelop(PathGroup):
"""Specialization where the Path objects are all Segments and represent tangents to a curve """
def addToNode(self, node):
newList = [ ]
for s in self.listOfPaths:
newList += s.asSVGCommand(firstP=True)
debug("TangentEnvelop ", newList)
ele = miscellaneous.addPath( newList, node)
return ele
def setNodeStyle(self, ele, node):
style = node.get('style')+';marker-end:url(#Arrow1Lend)'
ele.set('style', style)
class Circle(PathGroup):
"""Specialization where the list of Path objects
is to be replaced by a Circle specified by a center and a radius.
If an other radius 'rmax' is given than the object represents an ellipse.
"""
isClosing= True
def __init__(self, center, rad, refNode=None, rmax=None, angle=0.):
self.listOfPaths = []
self.refNode = refNode
self.center = numpy.array(center)
self.radius = rad
if rmax:
self.type ='ellipse'
else:
self.type = 'circle'
self.rmax = rmax
self.angle = angle
def addToNode(self, refnode):
"""Add a node in the xml structure corresponding to this rect
refnode : xml node used as a reference, new point will be inserted a same level"""
ele = etree.Element('{http://www.w3.org/2000/svg}'+self.type)
ele.set('cx', str(self.center[0]))
ele.set('cy', str(self.center[1]))
if self.rmax:
ele.set('ry', str(self.radius))
ele.set('rx', str(self.rmax))
ele.set('transform', 'rotate(%3.2f,%f,%f)'%(numpy.degrees(self.angle), self.center[0], self.center[1]))
else:
ele.set('r', str(self.radius))
refnode.xpath('..')[0].append(ele)
return ele
class Rectangle(PathGroup):
"""Specialization where the list of Path objects
is to be replaced by a Rectangle specified by a center and size (w,h) and a rotation angle.
"""
def __init__(self, center, size, angle, listOfPaths, refNode=None):
self.listOfPaths = listOfPaths
self.refNode = refNode
self.center = center
self.size = size
self.bbox = size
self.angle = angle
pos = self.center - numpy.array( size )/2
if angle != 0. :
cosa = numpy.cos(angle)
sina = numpy.sin(angle)
self.rotMat = numpy.matrix( [ [ cosa, sina], [-sina, cosa] ] )
self.rotMatstr = 'matrix(%1.7f,%1.7f,%1.7f,%1.7f,0,0)'%(cosa, sina, -sina, cosa)
#debug(' !!!!! Rotated rectangle !!', self.size, self.bbox, ' angles ', a, self.angle ,' center',self.center)
else :
self.rotMatstr = None
self.pos = pos
debug(' !!!!! Rectangle !!', self.size, self.bbox, ' angles ', self.angle, ' center', self.center)
def addToNode(self, refnode):
"""Add a node in the xml structure corresponding to this rect
refnode : xml node used as a reference, new point will be inserted a same level"""
ele = etree.Element('{http://www.w3.org/2000/svg}rect')
self.fill(ele)
refnode.xpath('..')[0].append(ele)
return ele
def fill(self, ele):
w, h = self.size
ele.set('width', str(w))
ele.set('height', str(h))
w, h = self.bbox
ele.set('x', str(self.pos[0]))
ele.set('y', str(self.pos[1]))
if self.rotMatstr:
ele.set('transform', 'rotate(%3.2f,%f,%f)'%(numpy.degrees(self.angle), self.center[0], self.center[1]))
#ele.set('transform', self.rotMatstr)
@staticmethod
def isRectangle( 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
class CurveGroup(PathGroup):
"""Specialization where the list of Path objects
is to be replaced by a Rectangle specified by a center and size (w,h) and a rotation angle.
"""
def __init__(self, center, size, angle, listOfPaths, refNode=None):
self.listOfPaths = listOfPaths
self.refNode = refNode
self.center = center
self.size = size
self.bbox = size
self.angle = angle
pos = self.center - numpy.array( size )/2
if angle != 0. :
cosa = numpy.cos(angle)
sina = numpy.sin(angle)
self.rotMat = numpy.matrix( [ [ cosa, sina], [-sina, cosa] ] )
self.rotMatstr = 'matrix(%1.7f,%1.7f,%1.7f,%1.7f,0,0)'%(cosa, sina, -sina, cosa)
#debug(' !!!!! Rotated rectangle !!', self.size, self.bbox, ' angles ', a, self.angle ,' center',self.center)
else :
self.rotMatstr = None
self.pos = pos
debug(' !!!!! Rectangle !!', self.size, self.bbox, ' angles ', self.angle, ' center', self.center)
def addToNode(self, refnode):
"""Add a node in the xml structure corresponding to this rect
refnode : xml node used as a reference, new point will be inserted a same level"""
ele = etree.Element('{http://www.w3.org/2000/svg}rect')
self.fill(ele)
refnode.xpath('..')[0].append(ele)
return ele
# def fill(self, ele):
# w, h = self.size
# ele.set('width', str(w))
# ele.set('height', str(h))
# w, h = self.bbox
# ele.set('x', str(self.pos[0]))
# ele.set('y', str(self.pos[1]))
# if self.rotMatstr:
# ele.set('transform', 'rotate(%3.2f,%f,%f)'%(numpy.degrees(self.angle), self.center[0], self.center[1]))
# #ele.set('transform', self.rotMatstr)
@staticmethod
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