boofcv.alg.shapes.polyline.splitmerge.PolylineSplitMerge Maven / Gradle / Ivy
Show all versions of boofcv-feature Show documentation
/*
* Copyright (c) 2011-2020, 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.splitmerge;
import boofcv.misc.CircularIndex;
import boofcv.struct.ConfigLength;
import georegression.geometry.UtilPolygons2D_I32;
import georegression.metric.Distance2D_F64;
import georegression.struct.line.LineParametric2D_F64;
import georegression.struct.line.LineSegment2D_F64;
import georegression.struct.point.Point2D_I32;
import org.ddogleg.struct.FastQueue;
import org.ddogleg.struct.GrowQueue_F64;
import org.ddogleg.struct.GrowQueue_I32;
import org.ddogleg.struct.LinkedList;
import org.ddogleg.struct.LinkedList.Element;
import java.util.List;
/**
*
* Fits a polyline to a contour by fitting the simplest model (3 sides) and adding more sides to it. The number of sides
* is increased until the number of sides reaches maxSides + extraConsider or it already has fit the contour
* to within the specified precision. It then merges lines together until no more can be merged.
*
*
*
* When a side is added to the polygon it selects the side in which the score will be improved the most by splitting.
* The score is computed by computing the euclidean distance a point on the contour is from the line segment it
* belongs to. Note that distance from a line segment and not a line is found, thus if the closest point is past
* an end point the end point is used. The final score is the average distance.
*
*
*
* A set of polylines is found and scored. The best polyline is the one with the best overall score. The overall score is
* found by summing up the average error across all line segments (sum of segment scores dividing by the number of
* segments) [1] and adding a fixed penalty for each line segment.
* Without a penalty the polyline with the largest number of sides will almost always be selected.
*
*
*
* For a complete description of all parameters see the source code.
*
*
* [1] Note that the score is NOT weighted based on the number of points in a line segment. This was done at one
* point at produced much worse results.
*
* The polyline will always be in counter-clockwise ordering.
*
* @author Peter Abeles
*/
public class PolylineSplitMerge {
// Does the polyline form a loop or are the end points disconnected
private boolean loops = true;
// Can it assume the shape is convex? If so it can reject shapes earlier
private boolean convex = false;
// maximum number of sides it will consider
private int maxSides = Integer.MAX_VALUE;
// minimum number of sides that will be considered for the best polyline
private int minSides = 3;
// The minimum length of a side
private int minimumSideLength = 10;
// how many corners past the max it will fit a polygon to
private ConfigLength extraConsider = ConfigLength.relative(1.0,0);
// When selecting the best model how much is a split penalized
private double cornerScorePenalty = 0.25;
// If the score of a side is less than this it is considered a perfect fit and won't be split any more
private double thresholdSideSplitScore = 0;
// maximum number of points along a side it will sample when computing a score
// used to limit computational cost of large contours
int maxNumberOfSideSamples = 50;
// If the contour between two corners is longer than this multiple of the distance
// between the two corners then it will be rejected as not convex
double convexTest = 2.5;
// maximum error along any side
ConfigLength maxSideError = ConfigLength.relative(0.1,3);
// work space for side score calculation
private LineSegment2D_F64 line = new LineSegment2D_F64();
// the corner list that's being built
LinkedList list = new LinkedList<>();
FastQueue corners = new FastQueue<>(Corner::new);
private SplitSelector splitter = new MaximumLineDistance();
private SplitResults resultsA = new SplitResults();
private SplitResults resultsB = new SplitResults();
// List of all the found polylines and their score
private FastQueue polylines = new FastQueue<>(CandidatePolyline::new);
private CandidatePolyline bestPolyline;
// if true that means a fatal error and no polygon can be fit
private boolean fatalError;
// storage for results
ErrorValue sideError = new ErrorValue();
/**
* Process the contour and returns true if a polyline could be found.
* @param contour Contour. Must be a ordered in CW or CCW
* @return true for success or false if one could not be fit
*/
public boolean process(List contour ) {
// Reset internal book keeping variables
reset();
if( loops ) {
// Reject pathological case
if (contour.size() < 3)
return false;
if (!findInitialTriangle(contour))
return false;
} else {
// Reject pathological case
if( contour.size() < 2 )
return false;
// two end points are the seeds. Plus they can't change
addCorner(0);
addCorner(contour.size()-1);
initializeScore(contour,false);
}
savePolyline();
sequentialSideFit(contour,loops);
if( fatalError )
return false;
int MIN_SIZE = loops ? 3 : 2;
double bestScore = Double.MAX_VALUE;
int bestSize = -1;
for (int i = 0; i < Math.min(maxSides-(MIN_SIZE-1),polylines.size); i++) {
if( polylines.get(i).score < bestScore ) {
bestPolyline = polylines.get(i);
bestScore = bestPolyline.score;
bestSize = i + MIN_SIZE;
}
}
// There was no good match within the min/max size requirement
if( bestSize < minSides) {
return false;
}
// make sure all the sides are within error tolerance
for (int i = 0,j=bestSize-1; i < bestSize; j=i,i++) {
Point2D_I32 a = contour.get(bestPolyline.splits.get(i));
Point2D_I32 b = contour.get(bestPolyline.splits.get(j));
double length = a.distance(b);
double thresholdSideError = this.maxSideError.compute(length);
if( bestPolyline.sideErrors.get(i) >= thresholdSideError*thresholdSideError) {
bestPolyline = null;
return false;
}
}
return true;
}
private void sequentialSideFit(List contour, boolean loops ) {
// by finding more corners than necessary it can recover from mistakes previously
int limit = maxSides+extraConsider.computeI(maxSides);
if( limit <= 0 )limit = contour.size(); // handle the situation where it overflows
while( list.size() < limit && !fatalError ) {
if( !increaseNumberOfSidesByOne(contour,loops) ) {
break;
}
}
// remove corners and recompute scores. If the result is better it will be saved
while( !fatalError ) {
Element c = selectCornerToRemove(contour, sideError, loops);
if( c != null ) {
removeCornerAndSavePolyline(c, sideError.value);
} else {
break;
}
}
}
private void reset() {
list.reset();
corners.reset();
polylines.reset();
bestPolyline = null;
fatalError = false;
}
private void printCurrent( List contour ) {
System.out.print(list.size()+" Indexes[");
Element e = list.getHead();
while( e != null ) {
System.out.print(" "+e.object.index);
e = e.next;
}
System.out.println(" ]");
System.out.print(" Errors[");
e = list.getHead();
while( e != null ) {
String split = e.object.splitable ? "T" : "F";
System.out.print(String.format(" %6.1f %1s",e.object.sideError,split));
e = e.next;
}
System.out.println(" ]");
System.out.print(" Pos[");
e = list.getHead();
while( e != null ) {
Point2D_I32 p = contour.get(e.object.index);
System.out.print(String.format(" %3d %3d,",p.x,p.y));
e = e.next;
}
System.out.println(" ]");
}
/**
* Saves the current polyline
*
* @return true if the polyline is better than any previously saved result false if not and it wasn't saved
*/
boolean savePolyline() {
int N = loops ? 3 : 2;
// if a polyline of this size has already been saved then over write it
CandidatePolyline c;
if( list.size() <= polylines.size+N-1 ) {
c = polylines.get( list.size()-N );
// sanity check
if( c.splits.size != list.size() )
throw new RuntimeException("Egads saved polylines aren't in the expected order");
} else {
c = polylines.grow();
c.reset();
c.score = Double.MAX_VALUE;
}
double foundScore = computeScore(list,cornerScorePenalty, loops);
// only save the results if it's an improvement
if( c.score > foundScore ) {
c.score = foundScore;
c.splits.reset();
c.sideErrors.reset();
Element e = list.getHead();
double maxSideError = 0;
while (e != null) {
maxSideError = Math.max(maxSideError,e.object.sideError);
c.splits.add(e.object.index);
c.sideErrors.add(e.object.sideError);
e = e.next;
}
c.maxSideError = maxSideError;
return true;
} else {
return false;
}
}
/**
* Computes the score for a list
*/
static double computeScore( LinkedList list , double cornerPenalty , boolean loops ) {
double sumSides = 0;
Element e = list.getHead();
Element end = loops ? null : list.getTail();
while( e != end ) {
sumSides += e.object.sideError;
e = e.next;
}
int numSides = loops ? list.size() : list.size() - 1;
return sumSides/numSides + cornerPenalty*numSides;
}
/**
* Select an initial triangle. A good initial triangle is needed. By good it
* should minimize the error of the contour from each side
*/
boolean findInitialTriangle(List contour) {
// find the first estimate for a corner
int cornerSeed = findCornerSeed(contour);
// see if it can reject the contour immediately
if( convex ) {
if( !isConvexUsingMaxDistantPoints(contour,0,cornerSeed))
return false;
}
// Select the second corner.
splitter.selectSplitPoint(contour,0,cornerSeed,resultsA);
splitter.selectSplitPoint(contour,cornerSeed,0,resultsB);
if( splitter.compareScore(resultsA.score,resultsB.score) >= 0 ) {
addCorner(resultsA.index);
addCorner(cornerSeed);
} else {
addCorner(cornerSeed);
addCorner(resultsB.index);
}
// Select the third corner. Initial triangle will be complete now
// the third corner will be the one which maximizes the distance from the first two
int index0 = list.getHead().object.index;
int index1 = list.getHead().next.object.index;
int index2 = maximumDistance(contour,index0,index1);
addCorner(index2);
// enforce CCW requirement
ensureTriangleOrder(contour);
return initializeScore(contour, true);
}
/**
* Computes the score and potential split for each side
* @param contour
* @return
*/
private boolean initializeScore(List contour , boolean loops ) {
// Score each side
Element e = list.getHead();
Element end = loops ? null : list.getTail();
while( e != end ) {
if (convex && !isSideConvex(contour, e))
return false;
Element n = e.next;
double error;
if( n == null ) {
error = computeSideError(contour,e.object.index, list.getHead().object.index);
} else {
error = computeSideError(contour,e.object.index, n.object.index);
}
e.object.sideError = error;
e = n;
}
// Compute what would happen if a side was split
e = list.getHead();
while( e != end ) {
computePotentialSplitScore(contour,e,list.size() < minSides);
e = e.next;
}
return true;
}
/**
* Make sure the next corner after the head is the closest one to the head
*/
void ensureTriangleOrder(List contour ) {
Element e = list.getHead();
Corner a = e.object;e=e.next;
Corner b = e.object;e=e.next;
Corner c = e.object;
int distB = CircularIndex.distanceP(a.index,b.index,contour.size());
int distC = CircularIndex.distanceP(a.index,c.index,contour.size());
if( distB > distC ) {
list.reset();
list.pushTail(a);
list.pushTail(c);
list.pushTail(b);
}
}
Element addCorner( int where ) {
Corner c = corners.grow();
c.reset();
c.index = where;
list.pushTail(c);
return list.getTail();
}
/**
* Increase the number of sides in the polyline. This is done greedily selecting the side which would improve the
* score by the most of it was split.
* @param contour Contour
* @return true if a split was selected and false if not
*/
boolean increaseNumberOfSidesByOne(List contour, boolean loops ) {
// System.out.println("increase number of sides by one. list = "+list.size());
Element selected = selectCornerToSplit(loops);
// No side can be split
if( selected == null )
return false;
// Update the corner who's side was just split
selected.object.sideError = selected.object.splitError0;
// split the selected side and add a new corner
Corner c = corners.grow();
c.reset();
c.index = selected.object.splitLocation;
c.sideError = selected.object.splitError1;
Element cornerE = list.insertAfter(selected,c);
// see if the new side could be convex
if (convex && !isSideConvex(contour, selected))
return false;
else {
// compute the score for sides which just changed
computePotentialSplitScore(contour, cornerE, list.size() < minSides);
computePotentialSplitScore(contour, selected, list.size() < minSides);
// Save the results
// printCurrent(contour);
savePolyline();
return true;
}
}
/**
* Checks to see if the side could belong to a convex shape
*/
boolean isSideConvex(List contour, Element e1) {
// a conservative estimate for concavity. Assumes a triangle and that the farthest
// point is equal to the distance between the two corners
Element e2 = next(e1);
int length = CircularIndex.distanceP(e1.object.index,e2.object.index,contour.size());
Point2D_I32 p0 = contour.get(e1.object.index);
Point2D_I32 p1 = contour.get(e2.object.index);
double d = p0.distance(p1);
if (length >= d*convexTest) {
return false;
}
return true;
}
/**
* Selects the best side to split the polyline at.
* @return the selected side or null if the score will not be improved if any of the sides are split
*/
Element selectCornerToSplit( boolean loops ) {
Element selected = null;
double bestChange = convex ? 0 : -Double.MAX_VALUE;
// Pick the side that if split would improve the overall score the most
Element e=list.getHead();
Element end = loops ? null : list.getTail();
while( e != end ) {
Corner c = e.object;
if( !c.splitable) {
e = e.next;
continue;
}
// compute how much better the score will improve because of the split
double change = c.sideError*2 - c.splitError0 - c.splitError1;
// it was found that selecting for the biggest change tends to produce better results
if( change < 0 ) {
change = -change;
}
if( change > bestChange ) {
bestChange = change;
selected = e;
}
e = e.next;
}
return selected;
}
/**
* Selects the best corner to remove. If no corner was found that can be removed then null is returned
* @return The corner to remove. Should only return null if there are 3 sides or less
*/
Element selectCornerToRemove(List contour , ErrorValue sideError , boolean loops ) {
if( list.size() <= 3 )
return null;
// Pick the side that if split would improve the overall score the most
Element target,end;
// if it loops any corner can be split. If it doesn't look the end points can't be removed
if( loops ) {
target = list.getHead();
end = null;
} else {
target = list.getHead().next;
end = list.getTail();
}
Element best = null;
double bestScore = -Double.MAX_VALUE;
while( target != end ) {
Element p = previous(target);
Element n = next(target);
// just contributions of the corners in question
double before = (p.object.sideError + target.object.sideError)/2.0 + cornerScorePenalty;
double after = computeSideError(contour, p.object.index, n.object.index);
if( before-after > bestScore ) {
bestScore = before-after;
best = target;
sideError.value = after;
}
target = target.next;
}
return best;
}
/**
* Remove the corner from the current polyline. If the new polyline has a better score than the currently
* saved one with the same number of corners save it
* @param corner The corner to removed
*/
boolean removeCornerAndSavePolyline( Element corner, double sideErrorAfterRemoved ) {
// System.out.println("removing a corner idx="+target.object.index);
// Note: the corner is "lost" until the next contour is fit. Not worth the effort to recycle
Element p = previous(corner);
// go through the hassle of passing in this value instead of recomputing it
// since recomputing it isn't trivial
p.object.sideError = sideErrorAfterRemoved;
list.remove(corner);
// the line below is commented out because right now the current algorithm will
// never grow after removing a corner. If this changes in the future uncomment it
// computePotentialSplitScore(contour,p);
return savePolyline();
}
/**
* The seed corner is the point farther away from the first point. In a perfect polygon with no noise this should
* be a corner.
*/
static int findCornerSeed(List contour ) {
Point2D_I32 a = contour.get(0);
int best = -1;
double bestDistance = -Double.MAX_VALUE;
for (int i = 1; i < contour.size(); i++) {
Point2D_I32 b = contour.get(i);
double d = distanceSq(a,b);
if( d > bestDistance ) {
bestDistance = d;
best = i;
}
}
return best;
}
/**
* Finds the point in the contour which maximizes the distance between points A
* and B.
*
* @param contour List of all pointsi n the contour
* @param indexA Index of point A
* @param indexB Index of point B
* @return Index of maximal distant point
*/
static int maximumDistance(List contour , int indexA , int indexB ) {
Point2D_I32 a = contour.get(indexA);
Point2D_I32 b = contour.get(indexB);
int best = -1;
double bestDistance = -Double.MAX_VALUE;
for (int i = 0; i < contour.size(); i++) {
Point2D_I32 c = contour.get(i);
// can't sum sq distance because some skinny shapes it maximizes one and not the other
// double d = Math.sqrt(distanceSq(a,c)) + Math.sqrt(distanceSq(b,c));
double d = distanceAbs(a,c) + distanceAbs(b,c);
if( d > bestDistance ) {
bestDistance = d;
best = i;
}
}
return best;
}
/**
* Scores a side based on the sum of Euclidean distance squared of each point along the line. Euclidean squared
* is used because its fast to compute
*
* @param indexA first index. Inclusive
* @param indexB last index. Exclusive
*/
double computeSideError(List contour , int indexA , int indexB ) {
assignLine(contour, indexA, indexB, line);
// don't sample the end points because the error will be zero by definition
int numSamples;
double sumOfDistances = 0;
int length;
if( indexB >= indexA ) {
length = indexB-indexA-1;
numSamples = Math.min(length,maxNumberOfSideSamples);
for (int i = 0; i < numSamples; i++) {
int index = indexA+1+length*i/numSamples;
Point2D_I32 p = contour.get(index);
sumOfDistances += Distance2D_F64.distanceSq(line,p.x,p.y);
}
sumOfDistances /= numSamples;
} else {
length = contour.size()-indexA-1 + indexB;
numSamples = Math.min(length,maxNumberOfSideSamples);
for (int i = 0; i < numSamples; i++) {
int where = length*i/numSamples;
int index = (indexA+1+where)%contour.size();
Point2D_I32 p = contour.get(index);
sumOfDistances += Distance2D_F64.distanceSq(line,p.x,p.y);
}
sumOfDistances /= numSamples;
}
// handle divide by zero error
if( numSamples > 0 )
return sumOfDistances;
else
return 0;
}
/**
* Computes the split location and the score of the two new sides if it's split there
*/
void computePotentialSplitScore( List contour , Element e0 , boolean mustSplit )
{
Element e1 = next(e0);
e0.object.splitable = canBeSplit(contour,e0,mustSplit);
if( e0.object.splitable ) {
setSplitVariables(contour, e0, e1);
}
}
/**
* Selects and splits the side defined by the e0 corner. If convex a check is performed to
* ensure that the polyline will be convex still.
*/
void setSplitVariables(List contour, Element e0, Element e1) {
int distance0 = CircularIndex.distanceP(e0.object.index, e1.object.index, contour.size());
int index0 = CircularIndex.plusPOffset(e0.object.index,minimumSideLength,contour.size());
int index1 = CircularIndex.minusPOffset(e1.object.index,minimumSideLength,contour.size());
splitter.selectSplitPoint(contour, index0, index1, resultsA);
// if convex only perform the split if it would result in a convex polygon
if( convex ) {
Point2D_I32 a = contour.get(e0.object.index);
Point2D_I32 b = contour.get(resultsA.index);
Point2D_I32 c = contour.get(next(e0).object.index);
if (UtilPolygons2D_I32.isPositiveZ(a, b, c)) {
e0.object.splitable = false;
return;
}
}
// see if this would result in a side that's too small
int dist0 = CircularIndex.distanceP(e0.object.index,resultsA.index, contour.size());
if( dist0 < minimumSideLength || (contour.size()-dist0) < minimumSideLength ) {
throw new RuntimeException("Should be impossible");
}
// this function is only called if splitable is set to true so no need to set it again
e0.object.splitLocation = resultsA.index;
e0.object.splitError0 = computeSideError(contour, e0.object.index, resultsA.index);
e0.object.splitError1 = computeSideError(contour, resultsA.index, e1.object.index);
if( e0.object.splitLocation >= contour.size() )
throw new RuntimeException("Egads");
}
/**
* Determines if the side can be split again. A side can always be split as long as
* it's ≥ the minimum length or that the side score is larger the the split threshold
*
* @param e0 The side which is to be tested to see if it can be split
* @param mustSplit if true this will force it to split even if the error would prevent it from splitting
* @return true if it can be split or false if not
*/
boolean canBeSplit( List contour, Element e0 , boolean mustSplit ) {
Element e1 = next(e0);
// NOTE: The contour is passed in but only the size of the contour matters. This was done to prevent
// changing the signature if the algorithm was changed later on.
int length = CircularIndex.distanceP(e0.object.index, e1.object.index, contour.size());
// needs to be <= to prevent it from trying to split a side less than 1
// times two because the two new sides would have to have a length of at least min
if (length <= 2*minimumSideLength) {
return false;
}
// threshold is greater than zero ti prevent it from saying it can split a perfect side
return mustSplit || e0.object.sideError > thresholdSideSplitScore;
}
/**
* Returns the next corner in the list
*/
Element next( Element e ) {
if( e.next == null ) {
return list.getHead();
} else {
return e.next;
}
}
/**
* Returns the previous corner in the list
*/
Element previous( Element e ) {
if( e.previous == null ) {
return list.getTail();
} else {
return e.previous;
}
}
/**
* If point B is the point farthest away from A then by definition this means no other point can be farther
* away.If the shape is convex then the line integration from A to B or B to A cannot be greater than 1/2
* a circle. This test makes sure that the line integral meets the just described constraint and is thus
* convex.
*
* NOTE: indexA is probably the top left point in the contour, since that's how most contour algorithm scan
* but this isn't known for sure. If it was known you could make this requirement tighter.
*
* @param contour Contour points
* @param indexA index of first point
* @param indexB index of second point, which is the farthest away from A.
* @return if it passes the sanity check
*/
static boolean isConvexUsingMaxDistantPoints(List contour , int indexA , int indexB )
{
double d = Math.sqrt(distanceSq(contour.get(indexA),contour.get(indexB)));
// conservative upper bounds would be 1/2 a circle, including interior side.
int maxAllowed = (int)((Math.PI+1)*d+0.5);
int length0 = CircularIndex.distanceP(indexA,indexB,contour.size());
int length1 = CircularIndex.distanceP(indexB,indexA,contour.size());
return length0 <= maxAllowed && length1 <= maxAllowed;
}
/**
* Using double prevision here instead of int due to fear of overflow in very large images
*/
static double distanceSq( Point2D_I32 a , Point2D_I32 b ) {
double dx = b.x-a.x;
double dy = b.y-a.y;
return dx*dx + dy*dy;
}
static double distanceAbs( Point2D_I32 a , Point2D_I32 b ) {
double dx = b.x-a.x;
double dy = b.y-a.y;
return Math.abs(dx) + Math.abs(dy);
}
/**
* Assigns the line so that it passes through points A and B.
*/
public static void assignLine(List contour, int indexA, int indexB, LineParametric2D_F64 line) {
Point2D_I32 endA = contour.get(indexA);
Point2D_I32 endB = contour.get(indexB);
line.p.x = endA.x;
line.p.y = endA.y;
line.slope.x = endB.x-endA.x;
line.slope.y = endB.y-endA.y;
}
public static void assignLine(List contour, int indexA, int indexB, LineSegment2D_F64 line) {
Point2D_I32 endA = contour.get(indexA);
Point2D_I32 endB = contour.get(indexB);
line.a.set(endA.x,endA.y);
line.b.set(endB.x,endB.y);
}
public FastQueue getPolylines() {
return polylines;
}
/**
* Returns the polyline with the best score or null if it failed to fit a polyline
*/
public CandidatePolyline getBestPolyline() {
return bestPolyline;
}
public void setLoops(boolean loops) {
this.loops = loops;
}
public boolean isLoops() {
return loops;
}
/**
* Storage for results from selecting where to split a line
*/
static class SplitResults
{
public int index;
public double score;
}
/**
* Corner in the polyline. The side that this represents is this corner and the next in the list
*/
public static class Corner
{
public int index;
public double sideError;
// if this side was to be split this is where it would be split and what the scores
// for the new sides would be
public int splitLocation;
public double splitError0, splitError1;
// if a side can't be split (e.g. too small or already perfect)
public boolean splitable;
public void reset() {
index = -1;
sideError = -1;
splitLocation = -1;
splitError0 = splitError1 = -1;
splitable = true;
}
}
public static class CandidatePolyline
{
public GrowQueue_I32 splits = new GrowQueue_I32();
public double score;
public double maxSideError;
public GrowQueue_F64 sideErrors = new GrowQueue_F64();
public void reset() {
splits.reset();
sideErrors.reset();
score = Double.NaN;
maxSideError = Double.NaN;
}
}
static class ErrorValue {
public double value;
}
public boolean isConvex() {
return convex;
}
public void setConvex(boolean convex) {
this.convex = convex;
}
public int getMaxSides() {
return maxSides;
}
public void setMaxSides(int maxSides) {
this.maxSides = maxSides;
}
public int getMinimumSideLength() {
return minimumSideLength;
}
public void setMinimumSideLength(int minimumSideLength) {
if( minimumSideLength <= 0 )
throw new IllegalArgumentException("Minimum length must be at least 1");
this.minimumSideLength = minimumSideLength;
}
public double getCornerScorePenalty() {
return cornerScorePenalty;
}
public void setCornerScorePenalty(double cornerScorePenalty) {
this.cornerScorePenalty = cornerScorePenalty;
}
public double getThresholdSideSplitScore() {
return thresholdSideSplitScore;
}
public void setThresholdSideSplitScore(double thresholdSideSplitScore) {
this.thresholdSideSplitScore = thresholdSideSplitScore;
}
public int getMaxNumberOfSideSamples() {
return maxNumberOfSideSamples;
}
public void setMaxNumberOfSideSamples(int maxNumberOfSideSamples) {
this.maxNumberOfSideSamples = maxNumberOfSideSamples;
}
public void setSplitter(SplitSelector splitter) {
this.splitter = splitter;
}
public int getMinSides() {
return minSides;
}
public void setMinSides(int minSides) {
this.minSides = minSides;
}
public ConfigLength getExtraConsider() {
return extraConsider;
}
public void setExtraConsider( ConfigLength extraConsider) {
this.extraConsider = extraConsider;
}
public double getConvexTest() {
return convexTest;
}
public void setConvexTest(double convexTest) {
this.convexTest = convexTest;
}
public ConfigLength getMaxSideError() {
return maxSideError;
}
public void setMaxSideError(ConfigLength maxSideError) {
this.maxSideError = maxSideError;
}
}