/* * 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.EllipseRotated_F64; import org.ddogleg.optimization.DerivativeChecker; 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 TestRefineEllipseEuclideanLeastSquares { Random rand = new Random(234); @Test public void perfectEllipse() { checkPerfect(0,0,2,1,0); checkPerfect(1,-2,2,1,0); checkPerfect(0.5,3,2,1,0.1); } @Test public void perfectCircle() { checkPerfect(0,0,2,2,0); checkPerfect(1,-2,2,2,0); checkPerfect(0.5,3,2,2,0.1); } @Test public void perfectDataBadGuess() { EllipseRotated_F64 trueModel = new EllipseRotated_F64(-1,1.5,3,2,-0.3); checkIncorrect(-1, 1.5, 3, 2, -0.2, trueModel,false); checkIncorrect(-1, 1.5, 3, 2, -0.3, trueModel,false); checkIncorrect(-0.5,1.5,3,2,-0.3,trueModel,false); checkIncorrect(-1,2,3,2,-0.3,trueModel,false); checkIncorrect(-1,1.5,2.5,1.5,-0.3,trueModel,false); // test circle case trueModel = new EllipseRotated_F64(-1,1.5,2,2,-0.3); checkIncorrect(-0.5,2,1.5,2.5,-0.25,trueModel,true); } @Test public void noisyEllipse() { double sigma = 0.05; checkNoisy(0,0,2,1,0 , sigma); checkNoisy(1, -2, 2, 1, 0, sigma); checkNoisy(0.5, 3, 2, 1, 0.1, sigma); } public void checkPerfect( 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>(); for( int i = 0; i < 20; i++ ) { double theta = 2.0*(double)Math.PI*i/20; points.add(UtilEllipse_F64.computePoint(theta, rotated, null)); // System.out.println(points.get(i).x+" "+points.get(i).y); } RefineEllipseEuclideanLeastSquares alg = new RefineEllipseEuclideanLeastSquares(); assertTrue(alg.refine(rotated, points)); EllipseRotated_F64 found = alg.getFound(); assertEquals( rotated.center.x , found.center.x , GrlConstants.DOUBLE_TEST_TOL ); assertEquals( rotated.center.y , found.center.y , GrlConstants.DOUBLE_TEST_TOL ); assertEquals( rotated.a , found.a , GrlConstants.DOUBLE_TEST_TOL ); assertEquals( rotated.b , found.b , GrlConstants.DOUBLE_TEST_TOL ); assertEquals( rotated.phi , found.phi , GrlConstants.DOUBLE_TEST_TOL ); } /** * Perfect observations but crappy initial model */ public void checkIncorrect( double x0 , double y0, double a, double b, double phi , EllipseRotated_F64 trueModel , boolean isCircle ) { EllipseRotated_F64 rotated = new EllipseRotated_F64(x0,y0,a,b,phi); List<Point2D_F64> points = new ArrayList<Point2D_F64>(); for( int i = 0; i < 20; i++ ) { double theta = 2.0*(double)Math.PI*i/20; points.add(UtilEllipse_F64.computePoint(theta, trueModel, null)); // System.out.println(points.get(i).x+" "+points.get(i).y); } RefineEllipseEuclideanLeastSquares alg = new RefineEllipseEuclideanLeastSquares(); assertTrue(alg.refine(rotated, points)); EllipseRotated_F64 found = alg.getFound(); assertEquals( trueModel.center.x , found.center.x , GrlConstants.DOUBLE_TEST_TOL ); assertEquals( trueModel.center.y , found.center.y , GrlConstants.DOUBLE_TEST_TOL ); assertEquals( trueModel.a , found.a , GrlConstants.DOUBLE_TEST_TOL ); assertEquals( trueModel.b , found.b , GrlConstants.DOUBLE_TEST_TOL ); if( !isCircle ) assertEquals( trueModel.phi , found.phi , GrlConstants.DOUBLE_TEST_TOL ); } public void checkNoisy( double x0 , double y0, double a, double b, double phi , double sigma ) { EllipseRotated_F64 rotated = new EllipseRotated_F64(x0,y0,a,b,phi); List<Point2D_F64> points = new ArrayList<Point2D_F64>(); for( int i = 0; i < 20; i++ ) { double theta = 2.0*(double)Math.PI*i/20; Point2D_F64 p = UtilEllipse_F64.computePoint(theta, rotated, null); p.x += rand.nextGaussian()*sigma; p.y += rand.nextGaussian()*sigma; points.add(p); // System.out.println(points.get(i).x+" "+points.get(i).y); } RefineEllipseEuclideanLeastSquares alg = new RefineEllipseEuclideanLeastSquares(); assertTrue(alg.refine(rotated, points)); double after = alg.optimizer.getFunctionValue(); assertTrue(after<alg.initialError); } @Test public void checkJacobian() { EllipseRotated_F64 model = new EllipseRotated_F64(1,2,3,2,0.1); List<Point2D_F64> points = new ArrayList<Point2D_F64>(); for( int i = 0; i < 20; i++ ) { double theta = 2.0*(double)Math.PI*i/20; points.add(UtilEllipse_F64.computePoint(theta, model, null)); } model = new EllipseRotated_F64(0.5,2.1,2.9,1.5,0.15); RefineEllipseEuclideanLeastSquares alg = new RefineEllipseEuclideanLeastSquares(); alg.refine(model,points); RefineEllipseEuclideanLeastSquares.Error error = alg.createError(); RefineEllipseEuclideanLeastSquares.Jacobian jacobian = alg.createJacobian(); DerivativeChecker.jacobian(error,jacobian,alg.initialParam,1e-5); } }