1 /* 2 * Copyright (C) 2016 Alberto Irurueta Carro (alberto@irurueta.com) 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 package com.irurueta.numerical.polynomials.estimators; 17 18 import com.irurueta.numerical.LockedException; 19 import com.irurueta.numerical.NotReadyException; 20 import com.irurueta.numerical.polynomials.Polynomial; 21 import com.irurueta.numerical.robust.LMedSRobustEstimator; 22 import com.irurueta.numerical.robust.LMedSRobustEstimatorListener; 23 import com.irurueta.numerical.robust.RobustEstimator; 24 import com.irurueta.numerical.robust.RobustEstimatorException; 25 import com.irurueta.numerical.robust.RobustEstimatorMethod; 26 27 import java.util.ArrayList; 28 import java.util.List; 29 30 /** 31 * Finds the best polynomial using LMedS algorithm. 32 */ 33 public class LMedSPolynomialRobustEstimator extends PolynomialRobustEstimator { 34 35 /** 36 * Default value to be used for stop threshold. Stop threshold can be used 37 * to keep the algorithm iterating in case that best estimated threshold 38 * using median of residuals is not small enough. Once a solutions is found 39 * that generates a threshold below this value, the algorithm will stop. 40 * Threshold will be used to compare either algebraic or geometric distance 41 * of estimated polynomial respect each provided evaluation. 42 */ 43 public static final double DEFAULT_STOP_THRESHOLD = 1e-6; 44 45 /** 46 * Minimum value that can be set as stop threshold. 47 * Threshold must be strictly greater than 0.0. 48 */ 49 public static final double MIN_STOP_THRESHOLD = 0.0; 50 51 /** 52 * Threshold to be used to keep the algorithm iterating in case that best 53 * estimated threshold using median of residuals is not small enough. Once 54 * a solution is found that generates a threshold below this value, the 55 * algorithm will stop. 56 * The stop threshold can be used to prevent the LMedS algorithm iterating 57 * too many times in case where samples have a very similar accuracy. 58 * For instance, in cases where proportion of outliers is very small (close 59 * to 0%), and samples are very accurate (i.e. 1e-6), the algorithm would 60 * iterate for a long time trying to find the best solution when indeed 61 * there is no need to do that if a reasonable threshold has already been 62 * reached. 63 * Because of this behaviour the sopt threshold can be set to a value much 64 * lower than the one typically used in RANSAC, and yet the algorithm could 65 * still produce even smaller thresholds in estimated results. 66 */ 67 private double stopThreshold; 68 69 /** 70 * Constructor. 71 */ 72 public LMedSPolynomialRobustEstimator() { 73 super(); 74 stopThreshold = DEFAULT_STOP_THRESHOLD; 75 } 76 77 /** 78 * Constructor. 79 * 80 * @param degree degree of polynomial to be estimated. 81 * @throws IllegalArgumentException if provided degree is less than 1. 82 */ 83 public LMedSPolynomialRobustEstimator(final int degree) { 84 super(degree); 85 stopThreshold = DEFAULT_STOP_THRESHOLD; 86 } 87 88 /** 89 * Constructor. 90 * 91 * @param evaluations collections of polynomial evaluations. 92 * @throws IllegalArgumentException if provided number of evaluations is 93 * less than the required minimum. 94 */ 95 public LMedSPolynomialRobustEstimator(final List<PolynomialEvaluation> evaluations) { 96 super(evaluations); 97 stopThreshold = DEFAULT_STOP_THRESHOLD; 98 } 99 100 /** 101 * Constructor. 102 * 103 * @param listener listener to be notified of events such as when estimation 104 * starts, ends or its progress significantly changes. 105 */ 106 public LMedSPolynomialRobustEstimator(final PolynomialRobustEstimatorListener listener) { 107 super(listener); 108 stopThreshold = DEFAULT_STOP_THRESHOLD; 109 } 110 111 /** 112 * Constructor. 113 * 114 * @param degree degree of polynomial to be estimated. 115 * @param evaluations collection of polynomial evaluations. 116 * @throws IllegalArgumentException if provided degree is less than 1 or if 117 * provided number of evaluations is less than the required minimum for 118 * provided degree. 119 */ 120 public LMedSPolynomialRobustEstimator(final int degree, final List<PolynomialEvaluation> evaluations) { 121 super(degree, evaluations); 122 stopThreshold = DEFAULT_STOP_THRESHOLD; 123 } 124 125 /** 126 * Constructor. 127 * 128 * @param degree degree of polynomial to be estimated. 129 * @param listener listener to be notified of events such as when estimation 130 * starts, ends or its progress significantly changes. 131 * @throws IllegalArgumentException if provided degree is less than 1. 132 */ 133 public LMedSPolynomialRobustEstimator(final int degree, final PolynomialRobustEstimatorListener listener) { 134 super(degree, listener); 135 stopThreshold = DEFAULT_STOP_THRESHOLD; 136 } 137 138 /** 139 * Constructor. 140 * 141 * @param evaluations collection of polynomial evaluations. 142 * @param listener listener to be notified of events such as when estimation 143 * starts, ends or its progress significantly changes. 144 * @throws IllegalArgumentException if provided number of evaluations is 145 * less than the required minimum. 146 */ 147 public LMedSPolynomialRobustEstimator( 148 final List<PolynomialEvaluation> evaluations, final PolynomialRobustEstimatorListener listener) { 149 super(evaluations, listener); 150 stopThreshold = DEFAULT_STOP_THRESHOLD; 151 } 152 153 /** 154 * Constructor. 155 * 156 * @param degree degree of polynomial to be estimated. 157 * @param evaluations collection of polynomial evaluations. 158 * @param listener listener to be notified of events such as when estimation 159 * starts, ends or its progress significantly changes. 160 * @throws IllegalArgumentException if provided degree is less than 1 or if 161 * provided number of evaluations is less than the required minimum for 162 * provided degree. 163 */ 164 public LMedSPolynomialRobustEstimator( 165 final int degree, final List<PolynomialEvaluation> evaluations, 166 final PolynomialRobustEstimatorListener listener) { 167 super(degree, evaluations, listener); 168 stopThreshold = DEFAULT_STOP_THRESHOLD; 169 } 170 171 /** 172 * Returns threshold to be used to keep the algorithm iterating in case that 173 * best estimated threshold using median of residuals is not small enough. 174 * Once a solution is found that generates a threshold below this value, the 175 * algorithm will stop. 176 * The stop threshold can be used to prevent the LMedS algorithm iterating 177 * too many times in cases where samples have a very similar accuracy. 178 * For instance, in cases where proportion of outliers is very small (close 179 * to 0%), and samples are very accurate (i.e. 1e-6), the algorithm would 180 * iterate for a long time trying to find the best solution when indeed 181 * there is no need to do that if a reasonable threshold has already been 182 * reached. 183 * Because of this behaviour the stop threshold can be set to a value much 184 * lower than the one typically used in RANSAC, and yet the algorithm could 185 * still produce even smaller thresholds in estimated results. 186 * 187 * @return stop threshold to stop the algorithm prematurely when a certain 188 * accuracy has been reached. 189 */ 190 public double getStopThreshold() { 191 return stopThreshold; 192 } 193 194 /** 195 * Sets threshold to be used to keep the algorithm iterating in case that 196 * best estimated threshold using median of residuals is not small enough. 197 * Once a solution is found that generates a threshold below this value, the 198 * algorithm will stop. 199 * The stop threshold can be used to prevent the LMedS algorithm iterating 200 * too many times in cases where samples have a very similar accuracy. 201 * For instance, in cases where proportion of outliers is very small (close 202 * to 0%), and samples are very accurate (i.e. 1e-6), the algorithm would 203 * iterate for a long time trying to find the best solution when indeed 204 * there is no need to do that if a reasonable threshold has already been 205 * reached. 206 * Because of this behaviour the stop threshold can be set to a value much 207 * lower than the one typically used in RANSAC, and yet the algorithm could 208 * still produce even smaller thresholds in estimated results 209 * 210 * @param stopThreshold stop threshold to stop the algorithm prematurely 211 * when a certain accuracy has been reached 212 * @throws IllegalArgumentException if provided value is zero or negative 213 * @throws LockedException if robust estimator is locked because an 214 * estimation is already in progress 215 */ 216 public void setStopThreshold(final double stopThreshold) throws LockedException { 217 if (isLocked()) { 218 throw new LockedException(); 219 } 220 if (stopThreshold <= MIN_STOP_THRESHOLD) { 221 throw new IllegalArgumentException(); 222 } 223 this.stopThreshold = stopThreshold; 224 } 225 226 /** 227 * Estimates polynomial. 228 * 229 * @return estimated polynomial. 230 * @throws LockedException if robust estimator is locked because an 231 * estimation is already in progress. 232 * @throws NotReadyException if provided input data is not enough to start 233 * the estimation. 234 * @throws RobustEstimatorException if estimation fails for any other reason 235 * (i.e. numerical instability, no solution available, etc). 236 */ 237 @Override 238 public Polynomial estimate() throws LockedException, NotReadyException, RobustEstimatorException { 239 if (isLocked()) { 240 throw new LockedException(); 241 } 242 if (!isReady()) { 243 throw new NotReadyException(); 244 } 245 246 final LMedSRobustEstimator<Polynomial> innerEstimator = 247 new LMedSRobustEstimator<>(new LMedSRobustEstimatorListener<>() { 248 249 // subset of evaluations picked on each iteration 250 private final List<PolynomialEvaluation> subsetEvaluations = new ArrayList<>(); 251 252 @Override 253 public int getTotalSamples() { 254 return evaluations.size(); 255 } 256 257 @Override 258 public int getSubsetSize() { 259 return polynomialEstimator.getMinNumberOfEvaluations(); 260 } 261 262 @Override 263 public void estimatePreliminarSolutions( 264 final int[] samplesIndices, final List<Polynomial> solutions) { 265 subsetEvaluations.clear(); 266 for (final var samplesIndex : samplesIndices) { 267 subsetEvaluations.add(evaluations.get(samplesIndex)); 268 } 269 270 try { 271 polynomialEstimator.setLMSESolutionAllowed(false); 272 polynomialEstimator.setEvaluations(subsetEvaluations); 273 274 final var polynomial = polynomialEstimator.estimate(); 275 solutions.add(polynomial); 276 } catch (final Exception e) { 277 // if anything fails, no solution is added 278 } 279 } 280 281 @Override 282 public double computeResidual(final Polynomial currentEstimation, final int i) { 283 final var eval = evaluations.get(i); 284 return getDistance(eval, currentEstimation); 285 } 286 287 @Override 288 public boolean isReady() { 289 return LMedSPolynomialRobustEstimator.this.isReady(); 290 } 291 292 @Override 293 public void onEstimateStart(final RobustEstimator<Polynomial> estimator) { 294 if (listener != null) { 295 listener.onEstimateStart(LMedSPolynomialRobustEstimator.this); 296 } 297 } 298 299 @Override 300 public void onEstimateEnd(final RobustEstimator<Polynomial> estimator) { 301 if (listener != null) { 302 listener.onEstimateEnd(LMedSPolynomialRobustEstimator.this); 303 } 304 } 305 306 @Override 307 public void onEstimateNextIteration( 308 final RobustEstimator<Polynomial> estimator, final int iteration) { 309 if (listener != null) { 310 listener.onEstimateNextIteration(LMedSPolynomialRobustEstimator.this, iteration); 311 } 312 } 313 314 @Override 315 public void onEstimateProgressChange( 316 final RobustEstimator<Polynomial> estimator, final float progress) { 317 if (listener != null) { 318 listener.onEstimateProgressChange(LMedSPolynomialRobustEstimator.this, progress); 319 } 320 } 321 }); 322 323 try { 324 locked = true; 325 innerEstimator.setConfidence(confidence); 326 innerEstimator.setMaxIterations(maxIterations); 327 innerEstimator.setProgressDelta(progressDelta); 328 innerEstimator.setStopThreshold(stopThreshold); 329 return innerEstimator.estimate(); 330 } finally { 331 locked = false; 332 } 333 } 334 335 /** 336 * Returns method being used for robust estimation. 337 * 338 * @return method being used for robust estimation. 339 */ 340 @Override 341 public RobustEstimatorMethod getMethod() { 342 return RobustEstimatorMethod.LMEDS; 343 } 344 }