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

279 lines
11 KiB
Python
Raw Normal View History

2022-10-13 00:05:56 +02:00
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