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.RANSACRobustEstimator;
22 import com.irurueta.numerical.robust.RANSACRobustEstimatorListener;
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 @SuppressWarnings("DuplicatedCode")
34 public class RANSACPoint2DRobustEstimator extends Point2DRobustEstimator {
35
36 /**
37 * Constant defining default threshold to determine whether lines are
38 * inliers or not.
39 * Because typical resolution for points is 1 pixel, then default threshold
40 * is defined as 1.
41 */
42 public static final double DEFAULT_THRESHOLD = 1.0;
43
44 /**
45 * Minimum value that can be set as threshold.
46 * Threshold must be strictly greater than 0.0.
47 */
48 public static final double MIN_THRESHOLD = 0.0;
49
50 /**
51 * Indicates that by default inliers will only be computed but not kept.
52 */
53 public static final boolean DEFAULT_COMPUTE_AND_KEEP_INLIERS = false;
54
55 /**
56 * Indicates that by default residuals will only be computed but not kept.
57 */
58 public static final boolean DEFAULT_COMPUTE_AND_KEEP_RESIDUALS = false;
59
60 /**
61 * Threshold to determine whether lines are inliers or not when testing
62 * possible estimation solutions.
63 * The threshold refers to the amount of error (i.e. distance) a possible
64 * solution has on a sampled line.
65 */
66 private double threshold;
67
68 /**
69 * Indicates whether inliers must be computed and kept.
70 */
71 private boolean computeAndKeepInliers;
72
73 /**
74 * Indicates whether residuals must be computed and kept.
75 */
76 private boolean computeAndKeepResiduals;
77
78 /**
79 * Constructor.
80 */
81 public RANSACPoint2DRobustEstimator() {
82 super();
83 threshold = DEFAULT_THRESHOLD;
84 computeAndKeepInliers = DEFAULT_COMPUTE_AND_KEEP_INLIERS;
85 computeAndKeepResiduals = DEFAULT_COMPUTE_AND_KEEP_RESIDUALS;
86 }
87
88 /**
89 * Constructor with lines.
90 *
91 * @param lines 2D lines to estimate a 2D point.
92 * @throws IllegalArgumentException if provided list of lines don't have
93 * a size greater or equal than MINIMUM_SIZE.
94 */
95 public RANSACPoint2DRobustEstimator(final List<Line2D> lines) {
96 super(lines);
97 threshold = DEFAULT_THRESHOLD;
98 computeAndKeepInliers = DEFAULT_COMPUTE_AND_KEEP_INLIERS;
99 computeAndKeepResiduals = DEFAULT_COMPUTE_AND_KEEP_RESIDUALS;
100 }
101
102 /**
103 * Constructor.
104 *
105 * @param listener listener to be notified of events such as when estimation
106 * starts, ends or its progress significantly changes.
107 */
108 public RANSACPoint2DRobustEstimator(final Point2DRobustEstimatorListener listener) {
109 super(listener);
110 threshold = DEFAULT_THRESHOLD;
111 computeAndKeepInliers = DEFAULT_COMPUTE_AND_KEEP_INLIERS;
112 computeAndKeepResiduals = DEFAULT_COMPUTE_AND_KEEP_RESIDUALS;
113 }
114
115
116 /**
117 * Constructor.
118 *
119 * @param listener listener to be notified of events such as when estimation
120 * starts, ends or its progress significantly changes.
121 * @param lines 2D lines to estimate a 2D point.
122 * @throws IllegalArgumentException if provided list of lines don't have
123 * a size greater or equal than MINIMUM_SIZE.
124 */
125 public RANSACPoint2DRobustEstimator(final Point2DRobustEstimatorListener listener, final List<Line2D> lines) {
126 super(listener, lines);
127 threshold = DEFAULT_THRESHOLD;
128 computeAndKeepInliers = DEFAULT_COMPUTE_AND_KEEP_INLIERS;
129 computeAndKeepResiduals = DEFAULT_COMPUTE_AND_KEEP_RESIDUALS;
130 }
131
132 /**
133 * Returns threshold to determine whether lines are inliers or not when
134 * testing possible estimation solutions.
135 * The threshold refers to the amount of error a possible solution has on a
136 * given line.
137 *
138 * @return threshold to determine whether lines are inliers or not when
139 * testing possible estimation solutions.
140 */
141 public double getThreshold() {
142 return threshold;
143 }
144
145 /**
146 * Sets threshold to determine whether lines are inliers or not when
147 * testing possible estimation solutions.
148 * The threshold refers to the amount of error a possible solution has on
149 * a given line.
150 *
151 * @param threshold threshold to be set.
152 * @throws IllegalArgumentException if provided value is equal or less than
153 * zero.
154 * @throws LockedException if robust estimator is locked because an
155 * estimation is already in progress.
156 */
157 public void setThreshold(final double threshold) throws LockedException {
158 if (isLocked()) {
159 throw new LockedException();
160 }
161 if (threshold <= MIN_THRESHOLD) {
162 throw new IllegalArgumentException();
163 }
164 this.threshold = threshold;
165 }
166
167 /**
168 * Indicates whether inliers must be computed and kept.
169 *
170 * @return true if inliers must be computed and kept, false if inliers
171 * only need to be computed but not kept.
172 */
173 public boolean isComputeAndKeepInliersEnabled() {
174 return computeAndKeepInliers;
175 }
176
177 /**
178 * Specifies whether inliers must be computed and kept.
179 *
180 * @param computeAndKeepInliers true if inliers must be computed and kept,
181 * false if inliers only need to be computed but not kept.
182 * @throws LockedException if estimator is locked.
183 */
184 public void setComputeAndKeepInliersEnabled(final boolean computeAndKeepInliers) throws LockedException {
185 if (isLocked()) {
186 throw new LockedException();
187 }
188 this.computeAndKeepInliers = computeAndKeepInliers;
189 }
190
191 /**
192 * Indicates whether residuals must be computed and kept.
193 *
194 * @return true if residuals must be computed and kept, false if residuals
195 * only need to be computed but not kept.
196 */
197 public boolean isComputeAndKeepResidualsEnabled() {
198 return computeAndKeepResiduals;
199 }
200
201 /**
202 * Specifies whether residuals must be computed and kept.
203 *
204 * @param computeAndKeepResiduals true if residuals must be computed and
205 * kept, false if residuals only need to be computed but not kept.
206 * @throws LockedException if estimator is locked.
207 */
208 public void setComputeAndKeepResidualsEnabled(final boolean computeAndKeepResiduals) throws LockedException {
209 if (isLocked()) {
210 throw new LockedException();
211 }
212 this.computeAndKeepResiduals = computeAndKeepResiduals;
213 }
214
215 /**
216 * Estimates a 2D point using a robust estimator and the best set of 2D
217 * lines that intersect into the estimated 2D point.
218 *
219 * @return a 2D point.
220 * @throws LockedException if robust estimator is locked because an
221 * estimation is already in progress.
222 * @throws NotReadyException if provided input data is not enough to start
223 * the estimation.
224 * @throws RobustEstimatorException if estimation fails for any reason
225 * (i.e. numerical instability, no solution available, etc).
226 */
227 @Override
228 public Point2D estimate() throws LockedException, NotReadyException, RobustEstimatorException {
229 if (isLocked()) {
230 throw new LockedException();
231 }
232 if (!isReady()) {
233 throw new NotReadyException();
234 }
235
236 final var innerEstimator = new RANSACRobustEstimator<>(new RANSACRobustEstimatorListener<Point2D>() {
237
238 @Override
239 public double getThreshold() {
240 return threshold;
241 }
242
243 @Override
244 public int getTotalSamples() {
245 return lines.size();
246 }
247
248 @Override
249 public int getSubsetSize() {
250 return Point2DRobustEstimator.MINIMUM_SIZE;
251 }
252
253 @Override
254 public void estimatePreliminarSolutions(final int[] samplesIndices, final List<Point2D> solutions) {
255 final var line1 = lines.get(samplesIndices[0]);
256 final var line2 = lines.get(samplesIndices[1]);
257
258 try {
259 final var point = line1.getIntersection(line2);
260 solutions.add(point);
261 } catch (final NoIntersectionException e) {
262 // if points are coincident, no solution is added
263 }
264 }
265
266 @Override
267 public double computeResidual(final Point2D currentEstimation, final int i) {
268 return residual(currentEstimation, lines.get(i));
269 }
270
271 @Override
272 public boolean isReady() {
273 return RANSACPoint2DRobustEstimator.this.isReady();
274 }
275
276 @Override
277 public void onEstimateStart(final RobustEstimator<Point2D> estimator) {
278 if (listener != null) {
279 listener.onEstimateStart(RANSACPoint2DRobustEstimator.this);
280 }
281 }
282
283 @Override
284 public void onEstimateEnd(final RobustEstimator<Point2D> estimator) {
285 if (listener != null) {
286 listener.onEstimateEnd(RANSACPoint2DRobustEstimator.this);
287 }
288 }
289
290 @Override
291 public void onEstimateNextIteration(final RobustEstimator<Point2D> estimator, final int iteration) {
292 if (listener != null) {
293 listener.onEstimateNextIteration(RANSACPoint2DRobustEstimator.this, iteration);
294 }
295 }
296
297 @Override
298 public void onEstimateProgressChange(final RobustEstimator<Point2D> estimator, final float progress) {
299 if (listener != null) {
300 listener.onEstimateProgressChange(RANSACPoint2DRobustEstimator.this, progress);
301 }
302 }
303 });
304
305 try {
306 locked = true;
307 inliersData = null;
308 innerEstimator.setComputeAndKeepInliersEnabled(computeAndKeepInliers || refineResult);
309 innerEstimator.setComputeAndKeepResidualsEnabled(computeAndKeepResiduals || refineResult);
310 innerEstimator.setConfidence(confidence);
311 innerEstimator.setMaxIterations(maxIterations);
312 innerEstimator.setProgressDelta(progressDelta);
313 final var result = innerEstimator.estimate();
314 inliersData = innerEstimator.getInliersData();
315 return attemptRefine(result);
316 } catch (final com.irurueta.numerical.LockedException e) {
317 throw new LockedException(e);
318 } catch (final com.irurueta.numerical.NotReadyException e) {
319 throw new NotReadyException(e);
320 } finally {
321 locked = false;
322 }
323 }
324
325 /**
326 * Returns method being used for robust estimation.
327 *
328 * @return method being used for robust estimation.
329 */
330 @Override
331 public RobustEstimatorMethod getMethod() {
332 return RobustEstimatorMethod.RANSAC;
333 }
334
335 /**
336 * Gets standard deviation used for Levenberg-Marquardt fitting during
337 * refinement.
338 * Returned value gives an indication of how much variance each residual
339 * has.
340 * Typically, this value is related to the threshold used on each robust
341 * estimation, since residuals of found inliers are within the range of
342 * such threshold.
343 *
344 * @return standard deviation used for refinement.
345 */
346 @Override
347 protected double getRefinementStandardDeviation() {
348 return threshold;
349 }
350 }