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.PROMedSRobustEstimator;
22 import com.irurueta.numerical.robust.PROMedSRobustEstimatorListener;
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 PROMedS algorithm.
32 */
33 public class PROMedSPolynomialRobustEstimator 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 solution is found
39 * that generates a threshold below this value, the algorithm will stop.
40 * The stop threshold can be used to prevent the LMedS algorithm iterating
41 * too many times in cases where samples have a very similar accuracy.
42 * For instance, in cases where proportion of outliers is very small (close
43 * to 0%), and samples are very accurate (i.e. 1e-6), the algorithm would
44 * iterate for a long time trying to find the best solution when indeed
45 * there is no need to do that if a reasonable threshold has already been
46 * reached.
47 * Because of this behaviour the stop threshold can be set to a value much
48 * lower than the one typically used in RANSAC, and yet the algorithm could
49 * still produce even smaller thresholds in estimated results
50 */
51 public static final double DEFAULT_STOP_THRESHOLD = 1e-6;
52
53 /**
54 * Minimum allowed stop threshold value
55 */
56 public static final double MIN_STOP_THRESHOLD = 0.0;
57
58 /**
59 * Threshold to be used to keep the algorithm iterating in case that best
60 * estimated threshold using median of residuals is not small enough. Once
61 * a solution is found that generates a threshold below this value, the
62 * algorithm will stop.
63 * The stop threshold can be used to prevent the LMedS algorithm iterating
64 * too many times in cases where samples have a very similar accuracy.
65 * For instance, in cases where proportion of outliers is very small (close
66 * to 0%), and samples are very accurate (i.e. 1e-6), the algorithm would
67 * iterate for a long time trying to find the best solution when indeed
68 * there is no need to do that if a reasonable threshold has already been
69 * reached.
70 * Because of this behaviour the stop threshold can be set to a value much
71 * lower than the one typically used in RANSAC, and yet the algorithm could
72 * still produce even smaller thresholds in estimated results
73 */
74 private double stopThreshold;
75
76 /**
77 * Quality scores corresponding to each provided polynomial evaluation.
78 * The larger the score value the better the quality of the sample.
79 */
80 private double[] qualityScores;
81
82 /**
83 * Constructor.
84 */
85 public PROMedSPolynomialRobustEstimator() {
86 super();
87 stopThreshold = DEFAULT_STOP_THRESHOLD;
88 }
89
90 /**
91 * Constructor.
92 *
93 * @param degree degree of polynomial to be estimated.
94 * @throws IllegalArgumentException if provided degree is less than 1.
95 */
96 public PROMedSPolynomialRobustEstimator(final int degree) {
97 super(degree);
98 stopThreshold = DEFAULT_STOP_THRESHOLD;
99 }
100
101 /**
102 * Constructor.
103 *
104 * @param evaluations collection of polynomial evaluations.
105 * @throws IllegalArgumentException if provided number of evaluations is
106 * less than the required minimum.
107 */
108 public PROMedSPolynomialRobustEstimator(final List<PolynomialEvaluation> evaluations) {
109 super(evaluations);
110 stopThreshold = DEFAULT_STOP_THRESHOLD;
111 }
112
113 /**
114 * Constructor.
115 *
116 * @param listener listener to be notified of events such as when estimation
117 * starts, ends or its progress significantly changes.
118 */
119 public PROMedSPolynomialRobustEstimator(final PolynomialRobustEstimatorListener listener) {
120 super(listener);
121 stopThreshold = DEFAULT_STOP_THRESHOLD;
122 }
123
124 /**
125 * Constructor.
126 *
127 * @param degree degree of polynomial to be estimated.
128 * @param evaluations collection of polynomial evaluations.
129 * @throws IllegalArgumentException if provided degree is less than 1 or if
130 * provided number of evaluations is less than the required minimum for
131 * provided degree.
132 */
133 public PROMedSPolynomialRobustEstimator(final int degree, final List<PolynomialEvaluation> evaluations) {
134 super(degree, evaluations);
135 stopThreshold = DEFAULT_STOP_THRESHOLD;
136 }
137
138 /**
139 * Constructor.
140 *
141 * @param degree degree of polynomial to be estimated.
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 degree is less than 1.
145 */
146 public PROMedSPolynomialRobustEstimator(final int degree, final PolynomialRobustEstimatorListener listener) {
147 super(degree, listener);
148 stopThreshold = DEFAULT_STOP_THRESHOLD;
149 }
150
151 /**
152 * Constructor.
153 *
154 * @param evaluations collection of polynomial evaluations.
155 * @param listener listener to be notified of events such as when estimation
156 * starts, ends or its progress significantly changes.
157 * @throws IllegalArgumentException if provided number of evaluations is
158 * less than the required minimum.
159 */
160 public PROMedSPolynomialRobustEstimator(
161 final List<PolynomialEvaluation> evaluations, final PolynomialRobustEstimatorListener listener) {
162 super(evaluations, listener);
163 stopThreshold = DEFAULT_STOP_THRESHOLD;
164 }
165
166 /**
167 * Constructor.
168 *
169 * @param degree degree of polynomial to be estimated.
170 * @param evaluations collection of polynomial evaluations.
171 * @param listener listener to be notified of events such as when estimation
172 * starts, ends or its progress significantly changes.
173 * @throws IllegalArgumentException if provided degree is less than 1 or if
174 * provided number of evaluations is less than the required minimum for
175 * provided degree.
176 */
177 public PROMedSPolynomialRobustEstimator(
178 final int degree, final List<PolynomialEvaluation> evaluations,
179 final PolynomialRobustEstimatorListener listener) {
180 super(degree, evaluations, listener);
181 stopThreshold = DEFAULT_STOP_THRESHOLD;
182 }
183
184 /**
185 * Returns threshold to be used to keep the algorithm iterating in case that
186 * best estimated threshold using median of residuals is not small enough.
187 * Once a solution is found that generates a threshold below this value, the
188 * algorithm will stop.
189 * The stop threshold can be used to prevent the LMedS algorithm iterating
190 * too many times in cases where samples have a very similar accuracy.
191 * For instance, in cases where proportion of outliers is very small (close
192 * to 0%), and samples are very accurate (i.e. 1e-6), the algorithm would
193 * iterate for a long time trying to find the best solution when indeed
194 * there is no need to do that if a reasonable threshold has already been
195 * reached.
196 * Because of this behaviour the stop threshold can be set to a value much
197 * lower than the one typically used in RANSAC, and yet the algorithm could
198 * still produce even smaller thresholds in estimated results
199 *
200 * @return stop threshold to stop the algorithm prematurely when a certain
201 * accuracy has been reached
202 */
203 public double getStopThreshold() {
204 return stopThreshold;
205 }
206
207 /**
208 * Sets threshold to be used to keep the algorithm iterating in case that
209 * best estimated threshold using median of residuals is not small enough.
210 * Once a solution is found that generates a threshold below this value, the
211 * algorithm will stop.
212 * The stop threshold can be used to prevent the LMedS algorithm iterating
213 * too many times in cases where samples have a very similar accuracy.
214 * For instance, in cases where proportion of outliers is very small (close
215 * to 0%), and samples are very accurate (i.e. 1e-6), the algorithm would
216 * iterate for a long time trying to find the best solution when indeed
217 * there is no need to do that if a reasonable threshold has already been
218 * reached.
219 * Because of this behaviour the stop threshold can be set to a value much
220 * lower than the one typically used in RANSAC, and yet the algorithm could
221 * still produce even smaller thresholds in estimated results
222 *
223 * @param stopThreshold stop threshold to stop the algorithm prematurely
224 * when a certain accuracy has been reached
225 * @throws IllegalArgumentException if provided value is zero or negative
226 * @throws LockedException if robust estimator is locked because an
227 * estimation is already in progress
228 */
229 public void setStopThreshold(final double stopThreshold) throws LockedException {
230 if (isLocked()) {
231 throw new LockedException();
232 }
233 if (stopThreshold <= MIN_STOP_THRESHOLD) {
234 throw new IllegalArgumentException();
235 }
236
237 this.stopThreshold = stopThreshold;
238 }
239
240 /**
241 * Returns quality scores corresponding to each provided point.
242 * The larger the score value the better the quality of the sampled point
243 *
244 * @return quality scores corresponding to each point
245 */
246 @Override
247 public double[] getQualityScores() {
248 return qualityScores;
249 }
250
251 /**
252 * Sets quality scores corresponding to each provided point.
253 * The larger the score value the better the quality of the sampled point.
254 *
255 * @param qualityScores quality scores corresponding to each point.
256 * @throws LockedException if robust estimator is locked because an
257 * estimation is already in progress.
258 * @throws IllegalArgumentException if provided quality scores length is
259 * smaller than required minimum size.
260 */
261 @Override
262 public void setQualityScores(final double[] qualityScores) throws LockedException {
263 if (isLocked()) {
264 throw new LockedException();
265 }
266 internalSetQualityScores(qualityScores);
267 }
268
269 /**
270 * Indicates if estimator is ready to start the polynomial estimation.
271 * This is true when input data (i.e. polynomial evaluations and quality
272 * scores) are provided and enough data is available.
273 *
274 * @return true if estimator is ready, false otherwise
275 */
276 @Override
277 public boolean isReady() {
278 return super.isReady() && qualityScores != null && qualityScores.length == evaluations.size();
279 }
280
281 /**
282 * Estimates polynomial.
283 *
284 * @return estimated polynomial.
285 * @throws LockedException if robust estimator is locked because an
286 * estimation is already in progress.
287 * @throws NotReadyException if provided input data is not enough to start
288 * the estimation.
289 * @throws RobustEstimatorException if estimation fails for any other reason
290 * (i.e. numerical instability, no solution available, etc).
291 */
292 @Override
293 public Polynomial estimate() throws LockedException, NotReadyException, RobustEstimatorException {
294 if (isLocked()) {
295 throw new LockedException();
296 }
297 if (!isReady()) {
298 throw new NotReadyException();
299 }
300
301 final PROMedSRobustEstimator<Polynomial> innerEstimator = new PROMedSRobustEstimator<>(
302 new PROMedSRobustEstimatorListener<>() {
303
304 // subset of evaluations picked on each iteration
305 private final List<PolynomialEvaluation> subsetEvaluations = new ArrayList<>();
306
307 @Override
308 public double getThreshold() {
309 return stopThreshold;
310 }
311
312 @Override
313 public int getTotalSamples() {
314 return evaluations.size();
315 }
316
317 @Override
318 public int getSubsetSize() {
319 return polynomialEstimator.getMinNumberOfEvaluations();
320 }
321
322 @SuppressWarnings("DuplicatedCode")
323 @Override
324 public void estimatePreliminarSolutions(
325 final int[] samplesIndices, final List<Polynomial> solutions) {
326 subsetEvaluations.clear();
327 for (var samplesIndex : samplesIndices) {
328 subsetEvaluations.add(evaluations.get(samplesIndex));
329 }
330
331 try {
332 polynomialEstimator.setLMSESolutionAllowed(false);
333 polynomialEstimator.setEvaluations(subsetEvaluations);
334
335 final var polynomial = polynomialEstimator.estimate();
336 solutions.add(polynomial);
337 } catch (final Exception e) {
338 // if anything fails, no solution is added
339 }
340 }
341
342 @Override
343 public double computeResidual(final Polynomial currentEstimation, int i) {
344 final var eval = evaluations.get(i);
345 return getDistance(eval, currentEstimation);
346 }
347
348 @Override
349 public boolean isReady() {
350 return PROMedSPolynomialRobustEstimator.this.isReady();
351 }
352
353 @Override
354 public void onEstimateStart(final RobustEstimator<Polynomial> estimator) {
355 if (listener != null) {
356 listener.onEstimateStart(PROMedSPolynomialRobustEstimator.this);
357 }
358 }
359
360 @Override
361 public void onEstimateEnd(final RobustEstimator<Polynomial> estimator) {
362 if (listener != null) {
363 listener.onEstimateEnd(PROMedSPolynomialRobustEstimator.this);
364 }
365 }
366
367 @Override
368 public void onEstimateNextIteration(
369 final RobustEstimator<Polynomial> estimator, final int iteration) {
370 if (listener != null) {
371 listener.onEstimateNextIteration(PROMedSPolynomialRobustEstimator.this, iteration);
372 }
373 }
374
375 @Override
376 public void onEstimateProgressChange(
377 final RobustEstimator<Polynomial> estimator, final float progress) {
378 if (listener != null) {
379 listener.onEstimateProgressChange(PROMedSPolynomialRobustEstimator.this, progress);
380 }
381 }
382
383 @Override
384 public double[] getQualityScores() {
385 return qualityScores;
386 }
387 });
388
389 try {
390 locked = true;
391 innerEstimator.setConfidence(confidence);
392 innerEstimator.setMaxIterations(maxIterations);
393 innerEstimator.setProgressDelta(progressDelta);
394 return innerEstimator.estimate();
395 } finally {
396 locked = false;
397 }
398 }
399
400 /**
401 * Returns method being used for robust estimation.
402 *
403 * @return method being used for robust estimation.
404 */
405 @Override
406 public RobustEstimatorMethod getMethod() {
407 return RobustEstimatorMethod.PROMEDS;
408 }
409
410 /**
411 * Sets quality scores corresponding to each provided polynomial evaluation.
412 * This method is used internally and does not check whether instance is
413 * locked or not
414 *
415 * @param qualityScores quality scores to be set
416 * @throws IllegalArgumentException if provided quality scores length is
417 * smaller than required minimum size.
418 */
419 private void internalSetQualityScores(final double[] qualityScores) {
420 if (qualityScores.length < polynomialEstimator.getMinNumberOfEvaluations()) {
421 throw new IllegalArgumentException();
422 }
423
424 this.qualityScores = qualityScores;
425 }
426 }