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