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.CoincidentPointsException;
19 import com.irurueta.geometry.Line2D;
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 line for provided collection of 2D points using LMedS
31 * algorithm.
32 */
33 public class LMedSLine2DRobustEstimator extends Line2DRobustEstimator {
34 /**
35 * Default value to be used for stop threshold. Stop threshold can be used
36 * to keep the algorithm iterating in case that best estimated threshold
37 * using median of residuals is not small enough. Once a solution is found
38 * that generates a threshold below this value, the algorithm will stop.
39 * The stop threshold can be used to prevent the LMedS algorithm iterating
40 * too many times in cases where samples have a very similar accuracy.
41 * For instance, in cases where proportion of outliers is very small (close
42 * to 0%), and samples are very accurate (i.e. 1e-6), the algorithm would
43 * iterate for a long time trying to find the best solution when indeed
44 * there is no need to do that if a reasonable threshold has already been
45 * reached.
46 * Because of this behaviour the stop threshold can be set to a value much
47 * lower than the one typically used in RANSAC, and yet the algorithm could
48 * still produce even smaller thresholds in estimated results.
49 */
50 public static final double DEFAULT_STOP_THRESHOLD = 1e-3;
51
52 /**
53 * Minimum allowed stop threshold value.
54 */
55 public static final double MIN_STOP_THRESHOLD = 0.0;
56
57 /**
58 * Threshold to be used to keep the algorithm iterating in case that best
59 * estimated threshold using median of residuals is not small enough. Once
60 * a solution is found that generates a threshold below this value, the
61 * algorithm will stop.
62 * The stop threshold can be used to prevent the LMedS algorithm iterating
63 * too many times in cases where samples have a very similar accuracy.
64 * For instance, in cases where proportion of outliers is very small (close
65 * to 0%), and samples are very accurate (i.e. 1e-6), the algorithm would
66 * iterate for a long time trying to find the best solution when indeed
67 * there is no need to do that if a reasonable threshold has already been
68 * reached.
69 * Because of this behaviour the stop threshold can be set to a value much
70 * lower than the one typically used in RANSAC, and yet the algorithm could
71 * still produce even smaller thresholds in estimated results.
72 */
73 private double stopThreshold;
74
75 /**
76 * Constructor.
77 */
78 public LMedSLine2DRobustEstimator() {
79 super();
80 stopThreshold = DEFAULT_STOP_THRESHOLD;
81 }
82
83 /**
84 * Constructor with points.
85 *
86 * @param points 2D points to estimate a 2D line.
87 * @throws IllegalArgumentException if provided list of points doesn't have
88 * a size greater or equal than MINIMUM_SIZE.
89 */
90 public LMedSLine2DRobustEstimator(final List<Point2D> points) {
91 super(points);
92 stopThreshold = DEFAULT_STOP_THRESHOLD;
93 }
94
95 /**
96 * Constructor.
97 *
98 * @param listener listener to be notified of events such as when estimation
99 * starts, ends or its progress significantly changes.
100 */
101 public LMedSLine2DRobustEstimator(final Line2DRobustEstimatorListener listener) {
102 super(listener);
103 stopThreshold = DEFAULT_STOP_THRESHOLD;
104 }
105
106
107 /**
108 * Constructor.
109 *
110 * @param listener listener to be notified of events such as when estimation
111 * starts, ends or its progress significantly changes.
112 * @param points 2D points to estimate a 2D line.
113 * @throws IllegalArgumentException if provided list of points doesn't have
114 * a size greater or equal than MINIMUM_SIZE.
115 */
116 public LMedSLine2DRobustEstimator(final Line2DRobustEstimatorListener listener, final List<Point2D> points) {
117 super(listener, points);
118 stopThreshold = DEFAULT_STOP_THRESHOLD;
119 }
120
121 /**
122 * Returns threshold to be used to keep the algorithm iterating in case that
123 * best estimated threshold using median of residuals is not small enough.
124 * Once a solution is found that generates a threshold below this value, the
125 * algorithm will stop.
126 * The stop threshold can be used to prevent the LMedS algorithm iterating
127 * too many times in cases where samples have a very similar accuracy.
128 * For instance, in cases where proportion of outliers is very small (close
129 * to 0%), and samples are very accurate (i.e. 1e-6), the algorithm would
130 * iterate for a long time trying to find the best solution when indeed
131 * there is no need to do that if a reasonable threshold has already been
132 * reached.
133 * Because of this behaviour the stop threshold can be set to a value much
134 * lower than the one typically used in RANSAC, and yet the algorithm could
135 * still produce even smaller thresholds in estimated results.
136 *
137 * @return stop threshold to stop the algorithm prematurely when a certain
138 * accuracy has been reached.
139 */
140 public double getStopThreshold() {
141 return stopThreshold;
142 }
143
144 /**
145 * Sets threshold to be used to keep the algorithm iterating in case that
146 * best estimated threshold using median of residuals is not small enough.
147 * Once a solution is found that generates a threshold below this value, the
148 * algorithm will stop.
149 * The stop threshold can be used to prevent the LMedS algorithm iterating
150 * too many times in cases where samples have a very similar accuracy.
151 * For instance, in cases where proportion of outliers is very small (close
152 * to 0%), and samples are very accurate (i.e. 1e-6), the algorithm would
153 * iterate for a long time trying to find the best solution when indeed
154 * there is no need to do that if a reasonable threshold has already been
155 * reached.
156 * Because of this behaviour the stop threshold can be set to a value much
157 * lower than the one typically used in RANSAC, and yet the algorithm could
158 * still produce even smaller thresholds in estimated results.
159 *
160 * @param stopThreshold stop threshold to stop the algorithm prematurely
161 * when a certain accuracy has been reached.
162 * @throws IllegalArgumentException if provided value is zero or negative.
163 * @throws LockedException if robust estimator is locked because an
164 * estimation is already in progress.
165 */
166 public void setStopThreshold(final double stopThreshold) throws LockedException {
167 if (isLocked()) {
168 throw new LockedException();
169 }
170 if (stopThreshold <= MIN_STOP_THRESHOLD) {
171 throw new IllegalArgumentException();
172 }
173
174 this.stopThreshold = stopThreshold;
175 }
176
177
178 /**
179 * Estimates a 2D line using a robust estimator and the best set of 2D
180 * points that pass through the estimated 2D line (i.e. belong to its locus).
181 *
182 * @return a 2D line.
183 * @throws LockedException if robust estimator is locked because an
184 * estimation is already in progress.
185 * @throws NotReadyException if provided input data is not enough to start
186 * the estimation.
187 * @throws RobustEstimatorException if estimation fails for any reason
188 * (i.e. numerical instability, no solution available, etc).
189 */
190 @SuppressWarnings("DuplicatedCode")
191 @Override
192 public Line2D 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<Line2D>() {
201
202 @Override
203 public int getTotalSamples() {
204 return points.size();
205 }
206
207 @Override
208 public int getSubsetSize() {
209 return Line2DRobustEstimator.MINIMUM_SIZE;
210 }
211
212 @Override
213 public void estimatePreliminarSolutions(final int[] samplesIndices, final List<Line2D> solutions) {
214 final var point1 = points.get(samplesIndices[0]);
215 final var point2 = points.get(samplesIndices[1]);
216
217 try {
218 final var line = new Line2D(point1, point2, false);
219 solutions.add(line);
220 } catch (final CoincidentPointsException e) {
221 // if points are coincident, no solution is added
222 }
223 }
224
225 @Override
226 public double computeResidual(final Line2D currentEstimation, int i) {
227 return residual(currentEstimation, points.get(i));
228 }
229
230 @Override
231 public boolean isReady() {
232 return LMedSLine2DRobustEstimator.this.isReady();
233 }
234
235 @Override
236 public void onEstimateStart(final RobustEstimator<Line2D> estimator) {
237 if (listener != null) {
238 listener.onEstimateStart(LMedSLine2DRobustEstimator.this);
239 }
240 }
241
242 @Override
243 public void onEstimateEnd(final RobustEstimator<Line2D> estimator) {
244 if (listener != null) {
245 listener.onEstimateEnd(LMedSLine2DRobustEstimator.this);
246 }
247 }
248
249 @Override
250 public void onEstimateNextIteration(final RobustEstimator<Line2D> estimator, final int iteration) {
251 if (listener != null) {
252 listener.onEstimateNextIteration(
253 LMedSLine2DRobustEstimator.this, iteration);
254 }
255 }
256
257 @Override
258 public void onEstimateProgressChange(final RobustEstimator<Line2D> estimator, final float progress) {
259 if (listener != null) {
260 listener.onEstimateProgressChange(
261 LMedSLine2DRobustEstimator.this, progress);
262 }
263 }
264 });
265
266 try {
267 locked = true;
268 innerEstimator.setConfidence(confidence);
269 innerEstimator.setMaxIterations(maxIterations);
270 innerEstimator.setProgressDelta(progressDelta);
271 innerEstimator.setStopThreshold(stopThreshold);
272 return innerEstimator.estimate();
273 } catch (final com.irurueta.numerical.LockedException e) {
274 throw new LockedException(e);
275 } catch (final com.irurueta.numerical.NotReadyException e) {
276 throw new NotReadyException(e);
277 } finally {
278 locked = false;
279 }
280 }
281
282 /**
283 * Returns method being used for robust estimation.
284 *
285 * @return method being used for robust estimation.
286 */
287 @Override
288 public RobustEstimatorMethod getMethod() {
289 return RobustEstimatorMethod.LMEDS;
290 }
291 }