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.PROSACRobustEstimator;
22 import com.irurueta.numerical.robust.PROSACRobustEstimatorListener;
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 PROSAC
31 * algorithm.
32 */
33 public class PROSACPoint2DRobustEstimator extends Point2DRobustEstimator {
34
35 /**
36 * Constant defining default threshold to determine whether points are
37 * inliers or not.
38 * Because typical resolution for points is 1 pixel, then default threshold
39 * is defined as 1.
40 */
41 public static final double DEFAULT_THRESHOLD = 1.0;
42
43 /**
44 * Minimum value that can be set as threshold.
45 * Threshold must be strictly greater than 0.0.
46 */
47 public static final double MIN_THRESHOLD = 0.0;
48
49 /**
50 * Indicates that by default inliers will only be computed but not kept.
51 */
52 public static final boolean DEFAULT_COMPUTE_AND_KEEP_INLIERS = false;
53
54 /**
55 * Indicates that by default residuals will only be computed but not kept.
56 */
57 public static final boolean DEFAULT_COMPUTE_AND_KEEP_RESIDUALS = false;
58
59 /**
60 * Threshold to determine whether lines are inliers or not when testing
61 * possible estimation solutions.
62 * The threshold refers to the amount of error (i.e. distance) a possible
63 * solution has on a sampled line.
64 */
65 private double threshold;
66
67 /**
68 * Quality scores corresponding to each provided point.
69 * The larger the score value the better the quality of the sample
70 */
71 private double[] qualityScores;
72
73 /**
74 * Indicates whether inliers must be computed and kept.
75 */
76 private boolean computeAndKeepInliers;
77
78 /**
79 * Indicates whether residuals must be computed and kept.
80 */
81 private boolean computeAndKeepResiduals;
82
83 /**
84 * Constructor.
85 */
86 public PROSACPoint2DRobustEstimator() {
87 super();
88 threshold = DEFAULT_THRESHOLD;
89 computeAndKeepInliers = DEFAULT_COMPUTE_AND_KEEP_INLIERS;
90 computeAndKeepResiduals = DEFAULT_COMPUTE_AND_KEEP_RESIDUALS;
91 }
92
93 /**
94 * Constructor with lines.
95 *
96 * @param lines 2D lines to estimate a 2D point.
97 * @throws IllegalArgumentException if provided list of lines don't have
98 * a size greater or equal than MINIMUM_SIZE.
99 */
100 public PROSACPoint2DRobustEstimator(final List<Line2D> lines) {
101 super(lines);
102 threshold = DEFAULT_THRESHOLD;
103 computeAndKeepInliers = DEFAULT_COMPUTE_AND_KEEP_INLIERS;
104 computeAndKeepResiduals = DEFAULT_COMPUTE_AND_KEEP_RESIDUALS;
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 */
113 public PROSACPoint2DRobustEstimator(final Point2DRobustEstimatorListener listener) {
114 super(listener);
115 threshold = DEFAULT_THRESHOLD;
116 computeAndKeepInliers = DEFAULT_COMPUTE_AND_KEEP_INLIERS;
117 computeAndKeepResiduals = DEFAULT_COMPUTE_AND_KEEP_RESIDUALS;
118 }
119
120
121 /**
122 * Constructor.
123 *
124 * @param listener listener to be notified of events such as when estimation
125 * starts, ends or its progress significantly changes.
126 * @param lines 2D lines to estimate a 2D point.
127 * @throws IllegalArgumentException if provided list of lines don't have
128 * a size greater or equal than MINIMUM_SIZE.
129 */
130 public PROSACPoint2DRobustEstimator(final Point2DRobustEstimatorListener listener, final List<Line2D> lines) {
131 super(listener, lines);
132 threshold = DEFAULT_THRESHOLD;
133 computeAndKeepInliers = DEFAULT_COMPUTE_AND_KEEP_INLIERS;
134 computeAndKeepResiduals = DEFAULT_COMPUTE_AND_KEEP_RESIDUALS;
135 }
136
137 /**
138 * Constructor.
139 *
140 * @param qualityScores quality scores corresponding to each provided line.
141 * @throws IllegalArgumentException if provided quality scores length is
142 * smaller than MINIMUM_SIZE (i.e. 2 lines).
143 */
144 public PROSACPoint2DRobustEstimator(final double[] qualityScores) {
145 super();
146 threshold = DEFAULT_THRESHOLD;
147 internalSetQualityScores(qualityScores);
148 computeAndKeepInliers = DEFAULT_COMPUTE_AND_KEEP_INLIERS;
149 computeAndKeepResiduals = DEFAULT_COMPUTE_AND_KEEP_RESIDUALS;
150 }
151
152 /**
153 * Constructor with lines.
154 *
155 * @param lines 2D lines to estimate a 2D point.
156 * @param qualityScores quality scores corresponding to each provided line.
157 * @throws IllegalArgumentException if provided list of lines don't have
158 * the same size as the list of provided quality scores, or it their size
159 * is not greater or equal than MINIMUM_SIZE.
160 */
161 public PROSACPoint2DRobustEstimator(final List<Line2D> lines, final double[] qualityScores) {
162 super(lines);
163
164 if (qualityScores.length != lines.size()) {
165 throw new IllegalArgumentException();
166 }
167
168 threshold = DEFAULT_THRESHOLD;
169 internalSetQualityScores(qualityScores);
170 computeAndKeepInliers = DEFAULT_COMPUTE_AND_KEEP_INLIERS;
171 computeAndKeepResiduals = DEFAULT_COMPUTE_AND_KEEP_RESIDUALS;
172 }
173
174 /**
175 * Constructor.
176 *
177 * @param listener listener to be notified of events such as when estimation
178 * starts, ends or its progress significantly changes.
179 * @param qualityScores quality scores corresponding to each provided line.
180 * @throws IllegalArgumentException if provided quality scores length is
181 * smaller than MINIMUM_SIZE (i.e. 2 lines).
182 */
183 public PROSACPoint2DRobustEstimator(final Point2DRobustEstimatorListener listener, final double[] qualityScores) {
184 super(listener);
185 threshold = DEFAULT_THRESHOLD;
186 internalSetQualityScores(qualityScores);
187 computeAndKeepInliers = DEFAULT_COMPUTE_AND_KEEP_INLIERS;
188 computeAndKeepResiduals = DEFAULT_COMPUTE_AND_KEEP_RESIDUALS;
189 }
190
191
192 /**
193 * Constructor.
194 *
195 * @param listener listener to be notified of events such as when estimation
196 * starts, ends or its progress significantly changes.
197 * @param lines 2D lines to estimate a 2D point.
198 * @param qualityScores quality scores corresponding to each provided line.
199 * @throws IllegalArgumentException if provided list of lines don't have
200 * the same size as the list of provided quality scores, or it their size
201 * is not greater or equal than MINIMUM_SIZE.
202 */
203 public PROSACPoint2DRobustEstimator(
204 final Point2DRobustEstimatorListener listener, final List<Line2D> lines, final double[] qualityScores) {
205 super(listener, lines);
206
207 if (qualityScores.length != lines.size()) {
208 throw new IllegalArgumentException();
209 }
210
211 threshold = DEFAULT_THRESHOLD;
212 internalSetQualityScores(qualityScores);
213 computeAndKeepInliers = DEFAULT_COMPUTE_AND_KEEP_INLIERS;
214 computeAndKeepResiduals = DEFAULT_COMPUTE_AND_KEEP_RESIDUALS;
215 }
216
217 /**
218 * Returns threshold to determine whether lines are inliers or not when
219 * testing possible estimation solutions.
220 * The threshold refers to the amount of error a possible solution has on a
221 * given line.
222 *
223 * @return threshold to determine whether lines are inliers or not when
224 * testing possible estimation solutions.
225 */
226 public double getThreshold() {
227 return threshold;
228 }
229
230 /**
231 * Sets threshold to determine whether lines are inliers or not when
232 * testing possible estimation solutions.
233 * The threshold refers to the amount of error a possible solution has on
234 * a given line.
235 *
236 * @param threshold threshold to be set.
237 * @throws IllegalArgumentException if provided value is equal or less than
238 * zero.
239 * @throws LockedException if robust estimator is locked because an
240 * estimation is already in progress.
241 */
242 public void setThreshold(final double threshold) throws LockedException {
243 if (isLocked()) {
244 throw new LockedException();
245 }
246 if (threshold <= MIN_THRESHOLD) {
247 throw new IllegalArgumentException();
248 }
249 this.threshold = threshold;
250 }
251
252 /**
253 * Returns quality scores corresponding to each provided line.
254 * The larger the score value the better the quality of the sampled line.
255 *
256 * @return quality scores corresponding to each line.
257 */
258 @Override
259 public double[] getQualityScores() {
260 return qualityScores;
261 }
262
263 /**
264 * Sets quality scores corresponding to each provided line.
265 * The larger the score value the better the quality of the sampled line.
266 *
267 * @param qualityScores quality scores corresponding to each line.
268 * @throws LockedException if robust estimator is locked because an
269 * estimation is already in progress.
270 * @throws IllegalArgumentException if provided quality scores length is
271 * smaller than MINIMUM_SIZE (i.e. 2 samples).
272 */
273 @Override
274 public void setQualityScores(final double[] qualityScores) throws LockedException {
275 if (isLocked()) {
276 throw new LockedException();
277 }
278 internalSetQualityScores(qualityScores);
279 }
280
281 /**
282 * Indicates if estimator is ready to start the 2D point estimation.
283 * This is true when input data (i.e. 2D lines and quality scores) are
284 * provided and a minimum of MINIMUM_SIZE points are available.
285 *
286 * @return true if estimator is ready, false otherwise.
287 */
288 @Override
289 public boolean isReady() {
290 return super.isReady() && qualityScores != null && qualityScores.length == lines.size();
291 }
292
293 /**
294 * Indicates whether inliers must be computed and kept.
295 *
296 * @return true if inliers must be computed and kept, false if inliers
297 * only need to be computed but not kept.
298 */
299 public boolean isComputeAndKeepInliersEnabled() {
300 return computeAndKeepInliers;
301 }
302
303 /**
304 * Specifies whether inliers must be computed and kept.
305 *
306 * @param computeAndKeepInliers true if inliers must be computed and kept,
307 * false if inliers only need to be computed but not kept.
308 * @throws LockedException if estimator is locked.
309 */
310 public void setComputeAndKeepInliersEnabled(final boolean computeAndKeepInliers) throws LockedException {
311 if (isLocked()) {
312 throw new LockedException();
313 }
314 this.computeAndKeepInliers = computeAndKeepInliers;
315 }
316
317 /**
318 * Indicates whether residuals must be computed and kept.
319 *
320 * @return true if residuals must be computed and kept, false if residuals
321 * only need to be computed but not kept.
322 */
323 public boolean isComputeAndKeepResidualsEnabled() {
324 return computeAndKeepResiduals;
325 }
326
327 /**
328 * Specifies whether residuals must be computed and kept.
329 *
330 * @param computeAndKeepResiduals true if residuals must be computed and
331 * kept, false if residuals only need to be computed but not kept.
332 * @throws LockedException if estimator is locked.
333 */
334 public void setComputeAndKeepResidualsEnabled(final boolean computeAndKeepResiduals) throws LockedException {
335 if (isLocked()) {
336 throw new LockedException();
337 }
338 this.computeAndKeepResiduals = computeAndKeepResiduals;
339 }
340
341 /**
342 * Estimates a 2D point using a robust estimator and the best set of 2D
343 * lines that intersect into the estimated 2D point.
344 *
345 * @return a 2D point.
346 * @throws LockedException if robust estimator is locked because an
347 * estimation is already in progress.
348 * @throws NotReadyException if provided input data is not enough to start
349 * the estimation.
350 * @throws RobustEstimatorException if estimation fails for any reason
351 * (i.e. numerical instability, no solution available, etc).
352 */
353 @SuppressWarnings("DuplicatedCode")
354 @Override
355 public Point2D estimate() throws LockedException, NotReadyException, RobustEstimatorException {
356 if (isLocked()) {
357 throw new LockedException();
358 }
359 if (!isReady()) {
360 throw new NotReadyException();
361 }
362
363 final var innerEstimator = new PROSACRobustEstimator<>(new PROSACRobustEstimatorListener<Point2D>() {
364
365 @Override
366 public double getThreshold() {
367 return threshold;
368 }
369
370 @Override
371 public int getTotalSamples() {
372 return lines.size();
373 }
374
375 @Override
376 public int getSubsetSize() {
377 return Point2DRobustEstimator.MINIMUM_SIZE;
378 }
379
380 @Override
381 public void estimatePreliminarSolutions(final int[] samplesIndices, final List<Point2D> solutions) {
382 final var line1 = lines.get(samplesIndices[0]);
383 final var line2 = lines.get(samplesIndices[1]);
384
385 try {
386 final var point = line1.getIntersection(line2);
387 solutions.add(point);
388 } catch (final NoIntersectionException e) {
389 // if points are coincident, no solution is added
390 }
391 }
392
393 @Override
394 public double computeResidual(final Point2D currentEstimation, final int i) {
395 return residual(currentEstimation, lines.get(i));
396 }
397
398 @Override
399 public boolean isReady() {
400 return PROSACPoint2DRobustEstimator.this.isReady();
401 }
402
403 @Override
404 public void onEstimateStart(final RobustEstimator<Point2D> estimator) {
405 if (listener != null) {
406 listener.onEstimateStart(PROSACPoint2DRobustEstimator.this);
407 }
408 }
409
410 @Override
411 public void onEstimateEnd(final RobustEstimator<Point2D> estimator) {
412 if (listener != null) {
413 listener.onEstimateEnd(PROSACPoint2DRobustEstimator.this);
414 }
415 }
416
417 @Override
418 public void onEstimateNextIteration(final RobustEstimator<Point2D> estimator, final int iteration) {
419 if (listener != null) {
420 listener.onEstimateNextIteration(PROSACPoint2DRobustEstimator.this, iteration);
421 }
422 }
423
424 @Override
425 public void onEstimateProgressChange(final RobustEstimator<Point2D> estimator, final float progress) {
426 if (listener != null) {
427 listener.onEstimateProgressChange(PROSACPoint2DRobustEstimator.this, progress);
428 }
429 }
430
431 @Override
432 public double[] getQualityScores() {
433 return qualityScores;
434 }
435 });
436
437 try {
438 locked = true;
439 inliersData = null;
440 innerEstimator.setComputeAndKeepInliersEnabled(computeAndKeepInliers || refineResult);
441 innerEstimator.setComputeAndKeepResidualsEnabled(computeAndKeepResiduals || refineResult);
442 innerEstimator.setConfidence(confidence);
443 innerEstimator.setMaxIterations(maxIterations);
444 innerEstimator.setProgressDelta(progressDelta);
445 final var result = innerEstimator.estimate();
446 inliersData = innerEstimator.getInliersData();
447 return attemptRefine(result);
448 } catch (final com.irurueta.numerical.LockedException e) {
449 throw new LockedException(e);
450 } catch (final com.irurueta.numerical.NotReadyException e) {
451 throw new NotReadyException(e);
452 } finally {
453 locked = false;
454 }
455 }
456
457 /**
458 * Returns method being used for robust estimation.
459 *
460 * @return method being used for robust estimation.
461 */
462 @Override
463 public RobustEstimatorMethod getMethod() {
464 return RobustEstimatorMethod.PROSAC;
465 }
466
467 /**
468 * Gets standard deviation used for Levenberg-Marquardt fitting during
469 * refinement.
470 * Returned value gives an indication of how much variance each residual
471 * has.
472 * Typically, this value is related to the threshold used on each robust
473 * estimation, since residuals of found inliers are within the range of
474 * such threshold.
475 *
476 * @return standard deviation used for refinement.
477 */
478 @Override
479 protected double getRefinementStandardDeviation() {
480 return threshold;
481 }
482
483 /**
484 * Sets quality scores corresponding to each provided line.
485 * This method is used internally and does not check whether instance is
486 * locked or not.
487 *
488 * @param qualityScores quality scores to be set.
489 * @throws IllegalArgumentException if provided quality scores length is
490 * smaller than MINIMUM_SIZE.
491 */
492 private void internalSetQualityScores(final double[] qualityScores) {
493 if (qualityScores.length < MINIMUM_SIZE) {
494 throw new IllegalArgumentException();
495 }
496
497 this.qualityScores = qualityScores;
498 }
499 }