/* * Copyright (C) 2011-2015, Peter Abeles. All Rights Reserved. * * This file is part of Geometric Regression Library (GeoRegression). * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package georegression.fitting.ellipse; import georegression.geometry.UtilEllipse_F64; import georegression.misc.GrlConstants; import georegression.struct.point.Point2D_F64; import georegression.struct.shapes.EllipseQuadratic_F64; import georegression.struct.shapes.EllipseRotated_F64; import org.junit.Test; import java.util.ArrayList; import java.util.List; import java.util.Random; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; /** * @author Peter Abeles */ public class TestFitEllipseWeightedAlgebraic { Random rand = new Random(234); @Test public void checkCircle() { checkEllipse(0, 0, 3, 3, 0); } /** * Give it points which perfectly describe an ellipse. This isn't actually an easy case. See comments * in random section. */ @Test public void checkEllipse() { checkEllipse(0,0,3,1.5,0); checkEllipse(1,2,3,1.5,0); checkEllipse(1,2,3,1.5,0.25); } public void checkEllipse( double x0 , double y0, double a, double b, double phi ) { EllipseRotated_F64 rotated = new EllipseRotated_F64(x0,y0,a,b,phi); List<Point2D_F64> points = new ArrayList<Point2D_F64>(); double weights[] = new double[21]; for( int i = 0; i < 20; i++ ) { double theta = 2.0*(double)Math.PI*i/20; points.add(UtilEllipse_F64.computePoint(theta, rotated, null)); weights[i] = 0.3; // System.out.println(points.get(i).x+" "+points.get(i).y); } // throw in a point that's way off but has no weight and should be ignored points.add( new Point2D_F64(20,34)); weights[20] = 0; EllipseQuadratic_F64 expected = UtilEllipse_F64.convert(rotated,null); FitEllipseWeightedAlgebraic alg = new FitEllipseWeightedAlgebraic(); assertTrue(alg.process(points,weights)); EllipseQuadratic_F64 found = alg.getEllipse(); normalize(expected); normalize(found); assertEquals(expected.a,found.a, GrlConstants.DOUBLE_TEST_TOL); assertEquals(expected.b,found.b, GrlConstants.DOUBLE_TEST_TOL); assertEquals(expected.c,found.c, GrlConstants.DOUBLE_TEST_TOL); assertEquals(expected.d,found.d, GrlConstants.DOUBLE_TEST_TOL); assertEquals(expected.e,found.e, GrlConstants.DOUBLE_TEST_TOL); assertEquals(expected.f,found.f, GrlConstants.DOUBLE_TEST_TOL); } /** * A bad point is given and see if the estimated error gets worse as it's weight is increased */ @Test public void checkErrorIncreasedWithWeight() { checkEllipse(0,0,3,1.5,0); checkEllipse(1,2,3,1.5,0); checkEllipse(1,2,3,1.5,0.25); } public void checkErrorIncreasedWithWeight( double x0 , double y0, double a, double b, double phi ) { EllipseRotated_F64 rotated = new EllipseRotated_F64(x0,y0,a,b,phi); double errorWeight[] = new double[]{0,0.1,0.5,2.0}; double error[] = new double[4]; for (int i = 0; i < error.length; i++) { List<Point2D_F64> points = new ArrayList<Point2D_F64>(); double weights[] = new double[21]; for( int j = 0; j < 20; j++ ) { double theta = 2.0*(double)Math.PI*j/20; points.add(UtilEllipse_F64.computePoint(theta, rotated, null)); weights[j] = 1.0; } // throw in a point that's way off but has no weight and should be ignored points.add( new Point2D_F64(20,34)); weights[20] = errorWeight[i]; EllipseQuadratic_F64 expected = UtilEllipse_F64.convert(rotated,null); FitEllipseWeightedAlgebraic alg = new FitEllipseWeightedAlgebraic(); assertTrue(alg.process(points,weights)); EllipseQuadratic_F64 found = alg.getEllipse(); normalize(expected); normalize(found); error[i] = Math.abs(expected.a - found.a); error[i] += Math.abs(expected.b - found.b); error[i] += Math.abs(expected.c - found.c); error[i] += Math.abs(expected.d - found.d); error[i] += Math.abs(expected.e - found.e); error[i] += Math.abs(expected.f - found.f); } assertTrue( error[1] > error[0]); assertTrue( error[2] > error[1]); assertTrue( error[3] > error[2]); } /** * Randomly generate points and see if it produces a valid ellipse * * The paper mentions that the case of perfect data is actually numerically unstable. The random test * below has been commented out since even the original algorithm run in octave can't pass that test. */ @Test public void checkRandom() { // for( int i = 0; i < 100; i++ ) { // System.out.println("i = "+i); // double x0 = (rand.nextDouble()-0.5)*2; // double y0 = (rand.nextDouble()-0.5)*2; // double b = rand.nextDouble(); // double a = b+rand.nextDouble()*2+0.1; // double theta = (rand.nextDouble()-0.5)*Math.PI; // // checkEllipse(x0,y0,a,b,theta); // } } private void normalize( EllipseQuadratic_F64 ellipse ) { ellipse.a /= ellipse.f; ellipse.b /= ellipse.f; ellipse.c /= ellipse.f; ellipse.d /= ellipse.f; ellipse.e /= ellipse.f; ellipse.f /= ellipse.f; } }