/* * Copyright (c) 2011-2016, Peter Abeles. All Rights Reserved. * * This file is part of BoofCV (http://boofcv.org). * * 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 boofcv.alg.fiducial.calib.circle; import georegression.misc.GrlConstants; import georegression.struct.shapes.EllipseRotated_F64; import org.junit.Test; import java.util.ArrayList; import java.util.List; import static org.junit.Assert.assertEquals; /** * @author Peter Abeles */ public class TestEllipsesIntoClusters { /** * Just make sure it doesn't blow up with an empty input */ @Test public void emptyInput() { EllipsesIntoClusters alg = new EllipsesIntoClusters(2.0,0.5); List<EllipseRotated_F64> input = new ArrayList<>(); List<List<EllipsesIntoClusters.Node>> output = new ArrayList<>(); alg.process(input,output); assertEquals( 0 , output.size()); } /** * Makes sure the output is cleared */ @Test public void outputCleared() { EllipsesIntoClusters alg = new EllipsesIntoClusters(2.0,0.5); List<EllipseRotated_F64> input = new ArrayList<>(); List<List<EllipsesIntoClusters.Node>> output = new ArrayList<>(); output.add( new ArrayList<EllipsesIntoClusters.Node>()); alg.process(input,output); assertEquals( 0 , output.size()); } /** * Provide it a simple case to cluster and make sure everything is connected properly */ @Test public void checkConnections() { EllipsesIntoClusters alg = new EllipsesIntoClusters(2.1,0.5); List<EllipseRotated_F64> input = new ArrayList<>(); input.add(new EllipseRotated_F64(0 ,0,1,1,0)); input.add(new EllipseRotated_F64(2.0,0,1,1,0)); input.add(new EllipseRotated_F64(4.0,0,1,1,0)); input.add(new EllipseRotated_F64( 0,2,1,1,0)); input.add(new EllipseRotated_F64(2.0,2,1,1,0)); input.add(new EllipseRotated_F64(4.0,2,1,1,0)); List<List<EllipsesIntoClusters.Node>> output = new ArrayList<>(); alg.process(input,output); assertEquals( 1 , output.size()); List<EllipsesIntoClusters.Node> found = output.get(0); assertEquals( 6 , found.size()); int histogram[] = new int[5]; for( EllipsesIntoClusters.Node n : found ) { histogram[n.connections.size]++; } assertEquals(0, histogram[0]); assertEquals(0, histogram[1]); assertEquals(4, histogram[2]); assertEquals(2, histogram[3]); assertEquals(0, histogram[4]); } /** * Points should not be clustered together due to distance apart */ @Test public void noCluster_distance() { EllipsesIntoClusters alg = new EllipsesIntoClusters(2.0,0.5); List<EllipseRotated_F64> input = new ArrayList<>(); input.add(new EllipseRotated_F64(0,0,2,1,0)); input.add(new EllipseRotated_F64(4.1,0,2,1,0)); List<List<EllipsesIntoClusters.Node>> output = new ArrayList<>(); alg.process(input,output); assertEquals( 0 , output.size()); // a positive case for sanity right at the border input.get(1).center.x = 4; alg.process(input,output); assertEquals( 1 , output.size()); } /** * Points should not be clustered together due difference in side */ @Test public void noCluster_size() { EllipsesIntoClusters alg = new EllipsesIntoClusters(2.0,0.5); List<EllipseRotated_F64> input = new ArrayList<>(); input.add(new EllipseRotated_F64(0,0,2,1,0)); input.add(new EllipseRotated_F64(2,0,0.999,1,0)); List<List<EllipsesIntoClusters.Node>> output = new ArrayList<>(); alg.process(input,output); assertEquals( 0 , output.size()); // a positive case for sanity right at the border input.get(1).a = 1.0; alg.process(input,output); assertEquals( 1 , output.size()); } @Test public void multipleClusters() { EllipsesIntoClusters alg = new EllipsesIntoClusters(2.0,0.5); // two clusters differentiated by size List<EllipseRotated_F64> input = new ArrayList<>(); input.add(new EllipseRotated_F64(0,0,2,1,0)); input.add(new EllipseRotated_F64(1,0,2,1,0)); input.add(new EllipseRotated_F64(1,0,8,1,0)); input.add(new EllipseRotated_F64(0,0,8,1,0)); input.add(new EllipseRotated_F64(2.2,0,2,1,0)); input.add(new EllipseRotated_F64(2.2,0,8,1,0)); List<List<EllipsesIntoClusters.Node>> output = new ArrayList<>(); alg.process(input,output); assertEquals( 2 , output.size()); for (int i = 0; i < 2; i++) { assertEquals( 3 , output.get(i).size()); double expected = input.get(output.get(i).get(0).which).a; for (int j = 0; j < 3; j++) { assertEquals(expected, input.get( output.get(i).get(j).which).a, GrlConstants.DOUBLE_TEST_TOL); } } } @Test public void multipleCalls() { EllipsesIntoClusters alg = new EllipsesIntoClusters(2.0,0.5); // two clusters differentiated by size List<EllipseRotated_F64> input = new ArrayList<>(); input.add(new EllipseRotated_F64(0,0,2,1,0)); input.add(new EllipseRotated_F64(1,0,2,1,0)); input.add(new EllipseRotated_F64(1,0,8,1,0)); input.add(new EllipseRotated_F64(0,0,8,1,0)); input.add(new EllipseRotated_F64(2.2,0,2,1,0)); input.add(new EllipseRotated_F64(2.2,0,8,1,0)); List<List<EllipsesIntoClusters.Node>> output = new ArrayList<>(); // call it twice to see if it resets alg.process(input,output); alg.process(input,output); assertEquals( 2 , output.size()); for (int i = 0; i < 2; i++) { assertEquals(3, output.get(i).size()); } } @Test public void joinClusters() { List<EllipsesIntoClusters.Node> mouth = new ArrayList<>(); List<EllipsesIntoClusters.Node> food = new ArrayList<>(); mouth.add( new EllipsesIntoClusters.Node()); mouth.add( new EllipsesIntoClusters.Node()); for (int i = 0; i < 4; i++) { food.add( new EllipsesIntoClusters.Node()); } EllipsesIntoClusters alg = new EllipsesIntoClusters(0.5,0.5); alg.clusters.add(mouth); alg.clusters.add(food); alg.joinClusters(0,1); assertEquals(6,mouth.size()); assertEquals(0,food.size()); } @Test public void axisAdjustedDistance() { EllipseRotated_F64 a = new EllipseRotated_F64(2,3,3,3,0); EllipseRotated_F64 b = new EllipseRotated_F64(6,3,3,3,0); // it's circular so it should be the usual euclidean distance squared assertEquals(4*4, EllipsesIntoClusters.axisAdjustedDistance(a,b), 1e-6); a.phi = Math.PI/2.0; assertEquals(4*4, EllipsesIntoClusters.axisAdjustedDistance(a,b), 1e-6); // not a circle any more. First test it lies along the major axis, should still be eclidean a.a=6;a.phi = 0; assertEquals(4*4, EllipsesIntoClusters.axisAdjustedDistance(a,b), 1e-6); // now rotate it. Distance should double a.phi = Math.PI/2.0; assertEquals(8*8, EllipsesIntoClusters.axisAdjustedDistance(a,b), 1e-6); // Now do a rigorous test across all angles for (int i = 0; i < 60; i++) { a.phi = 2.0*Math.PI*i/60; double dd = Math.pow(4*Math.cos(a.phi),2) + Math.pow(2*4*Math.sin(a.phi),2); assertEquals(dd, EllipsesIntoClusters.axisAdjustedDistance(a,b), 1e-6); } } }