# HG changeset patch # User Laman # Date 2019-02-14 17:18:55 # Node ID 7c268e382b96e468bfbf9c390a2e7e92975c4187 # Parent 9433c7ab298947cb4050e1d7e4bb79cc207c0f36 geometry: rewritten Line diff --git a/exp/board_detect.py b/exp/board_detect.py --- a/exp/board_detect.py +++ b/exp/board_detect.py @@ -13,7 +13,7 @@ import scipy.cluster import scipy.ndimage import scipy.signal -from geometry import Line, point2lineDistance +from geometry import Line from polar_hough import PolarHough from annotations import DataFile,computeBoundingBox from hough import show,prepareEdgeImg,HoughTransform diff --git a/exp/geometry.py b/exp/geometry.py --- a/exp/geometry.py +++ b/exp/geometry.py @@ -1,59 +1,47 @@ import math -import numpy as np - -from analyzer.epoint import EPoint, homogenize -from analyzer.grid import transformPoint +from analyzer.epoint import EPoint -class Line(): - def __init__(self,a,b): - self.a=a - self.b=b - self.points={a,b} +class Line: + def __init__(self,alpha,d): + self._alpha=alpha + self._d=d + self._sin=math.sin(alpha) + self._cos=math.cos(alpha) - def getSortedPoints(self): - return tuple(sorted(self.points)) + @staticmethod + def fromNormal(a,b,c): + """ax + by + c = 0""" + norm=-c/abs(c)*math.sqrt(a**2+b**2) + (a_,b_,c_)=(a/norm,b/norm,c/norm) + alpha=math.acos(a_) if b_>=0 else 2*math.pi-math.acos(a_) + return Line(alpha,-c_) - def computeAngle(self,line): - ab=self.a-self.b - cd=line.a-line.b - alpha=math.atan(ab.y/ab.x) - gamma=math.atan(cd.y/cd.x) - fi=max(alpha,gamma)-min(alpha,gamma) - return min(fi,math.pi-fi) + @staticmethod + def fromPoints(a,b): + return Line.fromNormal(a.y-b.y, b.x-a.x, (b.y-a.y)*a.x+(a.x-b.x)*a.y) + + def toNormal(self): + # https://en.wikipedia.org/wiki/Line_(mathematics)#In_normal_form + """ax + by + c = 0""" + return (self._cos, self._sin, -self._d) def intersect(self,line): - p=self.toProjective() - q=line.toProjective() - return EPoint.fromProjective(np.cross(p,q)) - - def toProjective(self): - return homogenize(np.cross(self.a.toProjective(),self.b.toProjective())) - - def transform(self,matrix): - a=EPoint.fromProjective(transformPoint(self.a.toProjective(),matrix)) - b=EPoint.fromProjective(transformPoint(self.b.toProjective(),matrix)) - if a is None or b is None: return None - return Line(a,b) + if self._alpha==line._alpha: return None + (a,b,c)=self.toNormal() + (d,e,f)=line.toNormal() + x=(b*f-c*e)/(a*e-b*d) + y=(c*d-a*f)/(a*e-b*d) + return EPoint(x,y) - def score(self,points): - score=len(self.points) - for a in self.points: - closest=sorted(points,key=lambda b: a.dist(b))[:4] - score+=sum(0.01 for b in closest if b in self.points) - return score + def distanceTo(self,point): + # https://en.wikipedia.org/wiki/Point-line_distance#Line_defined_by_an_equation + (a,b,c)=self.toNormal() + return abs(a*point.x+b*point.y+c) # a**2 + b**2 == 1 for Hesse normal form - def __str__(self): return "({0},{1})".format(self.a,self.b) - def __repr__(self): return "Line({0},{1})".format(repr(self.a),repr(self.b)) - - -def point2lineDistance(a,b,p): - # https://en.wikipedia.org/wiki/Point-line_distance#Line_defined_by_two_points - ab=b-a - num=abs(ab.y*p.x - ab.x*p.y + b.x*a.y - a.x*b.y) - denum=math.sqrt(ab.y**2+ab.x**2) - return num/denum # double_area / side_length == height + def __str__(self): return "({0},{1})".format(self._alpha,self._d) + def __repr__(self): return "Line({0},{1})".format(repr(self._alpha),repr(self._d)) def angleDiff(alpha,beta): diff --git a/exp/tests/__init__.py b/exp/tests/__init__.py new file mode 100644 diff --git a/exp/tests/test_geometry.py b/exp/tests/test_geometry.py new file mode 100644 --- /dev/null +++ b/exp/tests/test_geometry.py @@ -0,0 +1,67 @@ +import math +import random +from unittest import TestCase + +from geometry import Line,EPoint + +random.seed(361) + + +class TestLine(TestCase): + def testFromNormal(self): + p=Line.fromNormal(1,0,-1) # x-1=0 + self.assertEqual(p._alpha,0) + self.assertEqual(p._d,1) + + q=Line.fromNormal(1,1,-2) # x+y-2=0 + self.assertAlmostEqual(q._alpha,math.pi/4) + self.assertAlmostEqual(q._d,math.sqrt(2)) + + r=Line.fromNormal(0,1,1) # y+1=0 + self.assertAlmostEqual(r._alpha,math.pi*3/2) + self.assertEqual(r._d,1) + + def testFromPoints(self): + ab=Line.fromPoints(EPoint(1,3),EPoint(1,-1)) + self.assertEqual(ab._alpha,0) + self.assertEqual(ab._d,1) + + cd=Line.fromPoints(EPoint(0,2),EPoint(-1,3)) + self.assertAlmostEqual(cd._alpha,math.pi/4) + self.assertAlmostEqual(cd._d,math.sqrt(2)) + + ef=Line.fromPoints(EPoint(-2,-1),EPoint(-4,-1)) + self.assertAlmostEqual(ef._alpha,math.pi*3/2) + self.assertEqual(ef._d,1) + + def testIntersect(self): + for i in range(10): + a=EPoint(random.randint(-100,100),random.randint(-100,100)) + b=EPoint(random.randint(-100,100),random.randint(-100,100)) + c=EPoint(random.randint(-100,100),random.randint(-100,100)) + ab=Line.fromPoints(a,b) + ac=Line.fromPoints(a,c) + a_=ab.intersect(ac) + self.assertAlmostEqual(a.x,a_.x) + self.assertAlmostEqual(a.y,a_.y) + + def testDistanceTo(self): + p=Line(0,1) + q=Line(math.pi/4,math.sqrt(2)) + r=Line(math.pi*3/2,1) + + a=EPoint(0,0) + b=EPoint(1,0) + c=EPoint(-1,-1) + + self.assertAlmostEqual(p.distanceTo(a),1) + self.assertAlmostEqual(p.distanceTo(b),0) + self.assertAlmostEqual(p.distanceTo(c),2) + + self.assertAlmostEqual(q.distanceTo(a),math.sqrt(2)) + self.assertAlmostEqual(q.distanceTo(b),math.sqrt(2)/2) + self.assertAlmostEqual(q.distanceTo(c),2*math.sqrt(2)) + + self.assertAlmostEqual(r.distanceTo(a),1) + self.assertAlmostEqual(r.distanceTo(b),1) + self.assertAlmostEqual(r.distanceTo(c),0)