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.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.List;
28
29 /**
30 * Finds the best 2D point for provided collection of 2D lines using RANSAC
31 * algorithm.
32 */
33 public class LMedSPoint2DRobustEstimator extends Point2DRobustEstimator {
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-3;
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 * Constructor.
78 */
79 public LMedSPoint2DRobustEstimator() {
80 super();
81 stopThreshold = DEFAULT_STOP_THRESHOLD;
82 }
83
84 /**
85 * Constructor with lines.
86 *
87 * @param lines 2D lines to estimate a 2D point.
88 * @throws IllegalArgumentException if provided list of lines don't have
89 * a size greater or equal than MINIMUM_SIZE.
90 */
91 public LMedSPoint2DRobustEstimator(final List<Line2D> lines) {
92 super(lines);
93 stopThreshold = DEFAULT_STOP_THRESHOLD;
94 }
95
96 /**
97 * Constructor.
98 *
99 * @param listener listener to be notified of events such as when estimation
100 * starts, ends or its progress significantly changes.
101 */
102 public LMedSPoint2DRobustEstimator(final Point2DRobustEstimatorListener listener) {
103 super(listener);
104 stopThreshold = DEFAULT_STOP_THRESHOLD;
105 }
106
107
108 /**
109 * Constructor.
110 *
111 * @param listener listener to be notified of events such as when estimation
112 * starts, ends or its progress significantly changes.
113 * @param lines 2D lines to estimate a 2D point.
114 * @throws IllegalArgumentException if provided list of lines don't have
115 * a size greater or equal than MINIMUM_SIZE.
116 */
117 public LMedSPoint2DRobustEstimator(final Point2DRobustEstimatorListener listener, final List<Line2D> lines) {
118 super(listener, lines);
119 stopThreshold = DEFAULT_STOP_THRESHOLD;
120 }
121
122 /**
123 * Returns threshold to be used to keep the algorithm iterating in case that
124 * best estimated threshold using median of residuals is not small enough.
125 * Once a solution is found that generates a threshold below this value, the
126 * algorithm will stop.
127 * The stop threshold can be used to prevent the LMedS algorithm iterating
128 * too many times in cases where samples have a very similar accuracy.
129 * For instance, in cases where proportion of outliers is very small (close
130 * to 0%), and samples are very accurate (i.e. 1e-6), the algorithm would
131 * iterate for a long time trying to find the best solution when indeed
132 * there is no need to do that if a reasonable threshold has already been
133 * reached.
134 * Because of this behaviour the stop threshold can be set to a value much
135 * lower than the one typically used in RANSAC, and yet the algorithm could
136 * still produce even smaller thresholds in estimated results.
137 *
138 * @return stop threshold to stop the algorithm prematurely when a certain
139 * accuracy has been reached.
140 */
141 public double getStopThreshold() {
142 return stopThreshold;
143 }
144
145 /**
146 * Sets threshold to be used to keep the algorithm iterating in case that
147 * best estimated threshold using median of residuals is not small enough.
148 * Once a solution is found that generates a threshold below this value, the
149 * algorithm will stop.
150 * The stop threshold can be used to prevent the LMedS algorithm iterating
151 * too many times in cases where samples have a very similar accuracy.
152 * For instance, in cases where proportion of outliers is very small (close
153 * to 0%), and samples are very accurate (i.e. 1e-6), the algorithm would
154 * iterate for a long time trying to find the best solution when indeed
155 * there is no need to do that if a reasonable threshold has already been
156 * reached.
157 * Because of this behaviour the stop threshold can be set to a value much
158 * lower than the one typically used in RANSAC, and yet the algorithm could
159 * still produce even smaller thresholds in estimated results.
160 *
161 * @param stopThreshold stop threshold to stop the algorithm prematurely
162 * when a certain accuracy has been reached.
163 * @throws IllegalArgumentException if provided value is zero or negative.
164 * @throws LockedException if robust estimator is locked because an
165 * estimation is already in progress.
166 */
167 public void setStopThreshold(final double stopThreshold) throws LockedException {
168 if (isLocked()) {
169 throw new LockedException();
170 }
171 if (stopThreshold <= MIN_STOP_THRESHOLD) {
172 throw new IllegalArgumentException();
173 }
174
175 this.stopThreshold = stopThreshold;
176 }
177
178
179 /**
180 * Estimates a 2D point using a robust estimator and the best set of 2D
181 * lines that intersect into the estimated 2D point.
182 *
183 * @return a 2D point.
184 * @throws LockedException if robust estimator is locked because an
185 * estimation is already in progress.
186 * @throws NotReadyException if provided input data is not enough to start
187 * the estimation.
188 * @throws RobustEstimatorException if estimation fails for any reason
189 * (i.e. numerical instability, no solution available, etc).
190 */
191 @Override
192 public Point2D estimate() throws LockedException, NotReadyException, RobustEstimatorException {
193 if (isLocked()) {
194 throw new LockedException();
195 }
196 if (!isReady()) {
197 throw new NotReadyException();
198 }
199
200 final var innerEstimator = new LMedSRobustEstimator<>(new LMedSRobustEstimatorListener<Point2D>() {
201
202 @Override
203 public int getTotalSamples() {
204 return lines.size();
205 }
206
207 @Override
208 public int getSubsetSize() {
209 return Point2DRobustEstimator.MINIMUM_SIZE;
210 }
211
212 @SuppressWarnings("DuplicatedCode")
213 @Override
214 public void estimatePreliminarSolutions(final int[] samplesIndices, final List<Point2D> solutions) {
215 final var line1 = lines.get(samplesIndices[0]);
216 final var line2 = lines.get(samplesIndices[1]);
217
218 try {
219 final var point = line1.getIntersection(line2);
220 solutions.add(point);
221 } catch (final NoIntersectionException e) {
222 // if points are coincident, no solution is added
223 }
224 }
225
226 @Override
227 public double computeResidual(final Point2D currentEstimation, final int i) {
228 return residual(currentEstimation, lines.get(i));
229 }
230
231 @Override
232 public boolean isReady() {
233 return LMedSPoint2DRobustEstimator.this.isReady();
234 }
235
236 @Override
237 public void onEstimateStart(final RobustEstimator<Point2D> estimator) {
238 if (listener != null) {
239 listener.onEstimateStart(LMedSPoint2DRobustEstimator.this);
240 }
241 }
242
243 @Override
244 public void onEstimateEnd(final RobustEstimator<Point2D> estimator) {
245 if (listener != null) {
246 listener.onEstimateEnd(LMedSPoint2DRobustEstimator.this);
247 }
248 }
249
250 @Override
251 public void onEstimateNextIteration(final RobustEstimator<Point2D> estimator, final int iteration) {
252 if (listener != null) {
253 listener.onEstimateNextIteration(LMedSPoint2DRobustEstimator.this, iteration);
254 }
255 }
256
257 @Override
258 public void onEstimateProgressChange(final RobustEstimator<Point2D> estimator, final float progress) {
259 if (listener != null) {
260 listener.onEstimateProgressChange(LMedSPoint2DRobustEstimator.this, progress);
261 }
262 }
263 });
264
265 try {
266 locked = true;
267 inliersData = null;
268 innerEstimator.setConfidence(confidence);
269 innerEstimator.setMaxIterations(maxIterations);
270 innerEstimator.setProgressDelta(progressDelta);
271 innerEstimator.setStopThreshold(stopThreshold);
272 final var result = innerEstimator.estimate();
273 inliersData = innerEstimator.getInliersData();
274 return attemptRefine(result);
275 } catch (final com.irurueta.numerical.LockedException e) {
276 throw new LockedException(e);
277 } catch (final com.irurueta.numerical.NotReadyException e) {
278 throw new NotReadyException(e);
279 } finally {
280 locked = false;
281 }
282 }
283
284 /**
285 * Returns method being used for robust estimation.
286 *
287 * @return method being used for robust estimation.
288 */
289 @Override
290 public RobustEstimatorMethod getMethod() {
291 return RobustEstimatorMethod.LMEDS;
292 }
293
294 /**
295 * Gets standard deviation used for Levenberg-Marquardt fitting during
296 * refinement.
297 * Returned value gives an indication of how much variance each residual
298 * has.
299 * Typically, this value is related to the threshold used on each robust
300 * estimation, since residuals of found inliers are within the range of
301 * such threshold.
302 *
303 * @return standard deviation used for refinement.
304 */
305 @Override
306 protected double getRefinementStandardDeviation() {
307 final var inliersData = (LMedSRobustEstimator.LMedSInliersData) getInliersData();
308 return inliersData.getEstimatedThreshold();
309 }
310 }