View Javadoc
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 }