/* * 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.shapes.polyline; import georegression.struct.point.Point2D_I32; import org.ddogleg.struct.GrowQueue_I32; import org.junit.Test; import java.util.ArrayList; import java.util.List; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; /** * @author Peter Abeles */ public class TestSplitMergeLineFitLoop { public static final double MINIMUM_SPLIT_FRACTION = 0.01; /** * Tests contours with zero and one points in them */ @Test public void checkZeroOne() { List<Point2D_I32> contour = new ArrayList<>(); SplitMergeLineFitLoop alg = new SplitMergeLineFitLoop(0.15, MINIMUM_SPLIT_FRACTION,100); alg.process(contour); assertEquals(0,alg.getSplits().size); contour.add( new Point2D_I32(2,3)); alg.process(contour); assertEquals(0,alg.getSplits().size); } /** * Sees if it can segment a square. */ @Test public void simpleSquareAll() { List<Point2D_I32> contour = new ArrayList<>(); for( int i = 0; i < 10; i++ ) contour.add( new Point2D_I32(i,0)); for( int i = 1; i < 5; i++ ) contour.add( new Point2D_I32(9,i)); for( int i = 0; i < 10; i++ ) contour.add( new Point2D_I32(9-i,4)); for( int i = 2; i < 5; i++ ) contour.add( new Point2D_I32(0,5-i)); SplitMergeLineFitLoop alg = new SplitMergeLineFitLoop(0.15, MINIMUM_SPLIT_FRACTION,100); alg.process(contour); GrowQueue_I32 splits = alg.getSplits(); matchSplitsToExpected(new int[]{0, 9, 13, 23}, splits); } @Test public void selectFarthest() { SplitMergeLineFitLoop alg = new SplitMergeLineFitLoop(0.15, MINIMUM_SPLIT_FRACTION,100); List<Point2D_I32> contour = new ArrayList<>(); for( int i = 0; i < 10; i++ ) contour.add( new Point2D_I32(i,0)); for( int i = 0; i < 10; i++ ) contour.add( new Point2D_I32(9-i,1)); int found = alg.selectFarthest(contour); assertEquals(0,found); for( int offset = 1; offset < 5; offset++ ) { List<Point2D_I32> adjusted = shiftContour(contour,offset); found = alg.selectFarthest(adjusted); assertEquals(9-offset,found); } } /** * Segment where no splitting is required */ @Test public void splitPixels_nosplit() { SplitMergeLineFitLoop alg = new SplitMergeLineFitLoop(0.15,5* MINIMUM_SPLIT_FRACTION,100); alg.contour = new ArrayList<>(); for( int i = 0; i < 10; i++ ) alg.contour.add( new Point2D_I32(i,0)); alg.N = alg.contour.size(); alg.splitPixels(0,5); assertEquals(0,alg.splits.size); alg.splitPixels(0,9); assertEquals(0,alg.splits.size); alg.splitPixels(5,1); assertEquals(0,alg.splits.size); alg.splitPixels(5,9); assertEquals(0,alg.splits.size); } /** * Basic tests with a single split */ @Test public void splitPixels() { SplitMergeLineFitLoop alg = new SplitMergeLineFitLoop(0.01, MINIMUM_SPLIT_FRACTION,100); alg.contour = new ArrayList<>(); for( int i = 0; i < 10; i++ ) alg.contour.add( new Point2D_I32(i,0)); alg.N = alg.contour.size(); alg.contour.get(4).y = 6;// set it just above the threshold // tests which require splits on recursive calls alg.splitPixels(0,3); assertEquals(0, alg.splits.size); alg.splitPixels(0, 4); assertEquals(1, alg.splits.size); assertEquals(3,alg.splits.data[0]); // will get a hit from its recursive call. // gets split on both sides of the impulse because the impulse is so far from all the other lines alg.splits.reset(); alg.splitPixels(0, 9); assertEquals(3, alg.splits.size); assertEquals(3,alg.splits.data[0]); assertEquals(4,alg.splits.data[1]); assertEquals(5,alg.splits.data[2]); // Test a few edge cases alg.splits.reset(); alg.splitPixels(0,1); assertEquals(0,alg.splits.size); alg.splitPixels(9,1); assertEquals(0,alg.splits.size); } /** * Multiple splits are required */ @Test public void splitPixels_multiple() { SplitMergeLineFitLoop alg = new SplitMergeLineFitLoop(0.001, MINIMUM_SPLIT_FRACTION,100); alg.contour = new ArrayList<>(); alg.contour.add( new Point2D_I32(0,0)); alg.contour.add( new Point2D_I32(10,10)); alg.contour.add( new Point2D_I32(20,20)); alg.contour.add( new Point2D_I32(30,30)); alg.contour.add( new Point2D_I32(50,20)); alg.contour.add( new Point2D_I32(60,10)); alg.contour.add( new Point2D_I32(70,0)); alg.N = alg.contour.size(); alg.splitPixels(0,alg.N-1); assertEquals(2,alg.splits.size); assertEquals(3,alg.splits.data[0]); assertEquals(4,alg.splits.data[1]); alg.contour = shiftContour(alg.contour,2); alg.splits.reset(); alg.splitPixels(alg.N-2,alg.N-1); assertEquals(2,alg.splits.size); assertEquals(1,alg.splits.data[0]); assertEquals(2,alg.splits.data[1]); } @Test public void mergeLines() { SplitMergeLineFitLoop alg = new SplitMergeLineFitLoop(0.05, MINIMUM_SPLIT_FRACTION,100); alg.contour = new ArrayList<>(); for( int i = 0; i < 10; i++ ) alg.contour.add( new Point2D_I32(i,0)); for( int i = 0; i < 10; i++ ) alg.contour.add( new Point2D_I32(9-i,3)); alg.N = alg.contour.size(); alg.splits.add(0); alg.splits.add(5); alg.splits.add(9); alg.splits.add(10); alg.splits.add(15); alg.splits.add(19); assertTrue(alg.mergeSegments()); assertTrue(matchSplitsToExpected(new int[]{0, 9, 10, 19}, alg.splits)); // merge on split 0, special case alg.splits.reset(); alg.splits.add(5); alg.splits.add(9); alg.splits.add(10); alg.splits.add(15); alg.splits.add(19); alg.splits.add(0); assertTrue(alg.mergeSegments()); assertTrue(matchSplitsToExpected(new int[]{9, 10, 19, 0}, alg.splits)); } @Test public void splitSegments() { SplitMergeLineFitLoop alg = new SplitMergeLineFitLoop(0.001, MINIMUM_SPLIT_FRACTION,100); alg.contour = new ArrayList<>(); for( int i = 0; i < 10; i++ ) alg.contour.add( new Point2D_I32(i,0)); alg.contour.get(4).y=5; alg.N = alg.contour.size(); alg.splits.add(0); alg.splits.add(6); assertTrue(alg.splitSegments()); assertTrue(matchSplitsToExpected(new int[]{0, 4, 6}, alg.splits)); } @Test public void circularDistance() { SplitMergeLineFitLoop alg = new SplitMergeLineFitLoop(0.15,5* MINIMUM_SPLIT_FRACTION,100); alg.N = 15; assertEquals(0, alg.circularDistance(0, 0)); assertEquals(1, alg.circularDistance(0, 1)); assertEquals(14,alg.circularDistance(0,14)); assertEquals(0,alg.circularDistance(5,5)); assertEquals(1,alg.circularDistance(5,6)); assertEquals(14,alg.circularDistance(5,4)); } @Test public void selectSplitOffset() { SplitMergeLineFitLoop alg = new SplitMergeLineFitLoop(9.0/9.0, MINIMUM_SPLIT_FRACTION,100); alg.contour = new ArrayList<>(); for( int i = 0; i < 10; i++ ) alg.contour.add( new Point2D_I32(i,0)); alg.contour.get(4).y = 10; alg.N = alg.contour.size(); alg.line.slope.x = 1; // no wrapping around int found = alg.selectSplitOffset(0,9); assertEquals(4,found); found = alg.selectSplitOffset(0,5); assertEquals(4, found); found = alg.selectSplitOffset(0,4); assertEquals(-1, found); found = alg.selectSplitOffset(0,3); assertEquals(-1, found); found = alg.selectSplitOffset(3,5); assertEquals(1, found); // wrapping around found = alg.selectSplitOffset(5,6); assertEquals(-1,found); found = alg.selectSplitOffset(5,8); assertEquals(-1, found); found = alg.selectSplitOffset(9,8); assertEquals(5, found); // test the threshold alg.setSplitFraction(10.1/9.00); found = alg.selectSplitOffset(0,9); assertEquals(-1,found); alg.setSplitFraction(9.9/9.00); found = alg.selectSplitOffset(0,9); assertEquals(4,found); } /** * Makes sure the selectSplitOffset is obeying the minimumSideLengthPixel parameter */ @Test public void selectSplitOffset_minimumSideLengthPixel() { SplitMergeLineFitLoop alg = new SplitMergeLineFitLoop(9.99/9.0, MINIMUM_SPLIT_FRACTION,100); alg.contour = new ArrayList<>(); for( int i = 0; i < 10; i++ ) alg.contour.add( new Point2D_I32(i,0)); alg.contour.get(5).y = 10; alg.N = alg.contour.size(); alg.line.slope.x = 1; // check the default of 1 pixel selectSplitOffset_minimumSideLengthPixel(alg,1); // user specified value of 2 alg.minimumSideLengthPixel = 2; selectSplitOffset_minimumSideLengthPixel(alg,2); } private void selectSplitOffset_minimumSideLengthPixel(SplitMergeLineFitLoop alg, int minimum ) { // test positive cases int found = alg.selectSplitOffset(5-minimum,9); assertEquals(minimum,found); found = alg.selectSplitOffset(0, 5+minimum); assertEquals(5,found); // negative cases that should be within the threshold found = alg.selectSplitOffset(5-minimum+1,9); assertEquals(-1,found); found = alg.selectSplitOffset(0,5+minimum-1); assertEquals(-1,found); } /** * Checks to make sure the minimum side length is correctly set */ @Test public void set_minimumSideLengthPixel() { double minSplitFraction = 0.2; List<Point2D_I32> contour = new ArrayList<>(); for (int i = 0; i < 30; i++) { contour.add(new Point2D_I32(i, 0)); } SplitMergeLineFitLoop alg = new SplitMergeLineFitLoop(0.001,minSplitFraction,100); alg.process(contour); assertEquals(contour.size()/5,alg.minimumSideLengthPixel); } private List<Point2D_I32> shiftContour( List<Point2D_I32> contour , int offset ) { List<Point2D_I32> ret = new ArrayList<>(); for( int i = 0; i < contour.size(); i++ ) { ret.add( contour.get( (i+offset)%contour.size())); } return ret; } private boolean matchSplitsToExpected(int[] expected, GrowQueue_I32 found) { assertEquals(expected.length,found.size()); for (int i = 0; i < expected.length; i++) { boolean match = true; for (int j = 0; j < expected.length; j++) { if( expected[j] != found.get((i+j)%4)) { match = false; break; } } if( match ) return true; } return false; } }