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.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 }