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.CoincidentPointsException;
19  import com.irurueta.geometry.Line2D;
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 line for provided collection of 2D points using PROSAC
31   * algorithm.
32   */
33  @SuppressWarnings("DuplicatedCode")
34  public class PROSACLine2DRobustEstimator extends Line2DRobustEstimator {
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       * Threshold to determine whether points are inliers or not when testing
51       * possible estimation solutions.
52       * The threshold refers to the amount of error (i.e. distance) a possible
53       * solution has on a sampled line.
54       */
55      private double threshold;
56  
57      /**
58       * Quality scores corresponding to each provided point.
59       * The larger the score value the better the quality of the sample.
60       */
61      private double[] qualityScores;
62  
63      /**
64       * Constructor.
65       */
66      public PROSACLine2DRobustEstimator() {
67          super();
68          threshold = DEFAULT_THRESHOLD;
69      }
70  
71      /**
72       * Constructor with points.
73       *
74       * @param points 2D points to estimate a 2D line.
75       * @throws IllegalArgumentException if provided list of points doesn't have
76       *                                  a size greater or equal than MINIMUM_SIZE.
77       */
78      public PROSACLine2DRobustEstimator(final List<Point2D> points) {
79          super(points);
80          threshold = DEFAULT_THRESHOLD;
81      }
82  
83      /**
84       * Constructor.
85       *
86       * @param listener listener to be notified of events such as when estimation
87       *                 starts, ends or its progress significantly changes.
88       */
89      public PROSACLine2DRobustEstimator(final Line2DRobustEstimatorListener listener) {
90          super(listener);
91          threshold = DEFAULT_THRESHOLD;
92      }
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      * @param points   2D points to estimate a 2D line.
101      * @throws IllegalArgumentException if provided list of points doesn't have
102      *                                  a size greater or equal than MINIMUM_SIZE.
103      */
104     public PROSACLine2DRobustEstimator(final Line2DRobustEstimatorListener listener, final List<Point2D> points) {
105         super(listener, points);
106         threshold = DEFAULT_THRESHOLD;
107     }
108 
109     /**
110      * Constructor.
111      *
112      * @param qualityScores quality scores corresponding to each provided point.
113      * @throws IllegalArgumentException if provided quality scores length is
114      *                                  smaller than MINIMUM_SIZE (i.e. 2 points).
115      */
116     public PROSACLine2DRobustEstimator(final double[] qualityScores) {
117         super();
118         threshold = DEFAULT_THRESHOLD;
119         internalSetQualityScores(qualityScores);
120     }
121 
122     /**
123      * Constructor with points.
124      *
125      * @param points        2D points to estimate a 2D line.
126      * @param qualityScores quality scores corresponding to each provided point.
127      * @throws IllegalArgumentException if provided list of points don't have
128      *                                  the same size as the list of provided quality scores, or it their size
129      *                                  is not greater or equal than MINIMUM_SIZE.
130      */
131     public PROSACLine2DRobustEstimator(final List<Point2D> points, final double[] qualityScores) {
132         super(points);
133 
134         if (qualityScores.length != points.size()) {
135             throw new IllegalArgumentException();
136         }
137 
138         threshold = DEFAULT_THRESHOLD;
139         internalSetQualityScores(qualityScores);
140     }
141 
142     /**
143      * Constructor.
144      *
145      * @param listener      listener to be notified of events such as when estimation
146      *                      starts, ends or its progress significantly changes.
147      * @param qualityScores quality scores corresponding to each provided point.
148      * @throws IllegalArgumentException if provided quality scores length is
149      *                                  smaller than MINIMUM_SIZE (i.e. 2 points).
150      */
151     public PROSACLine2DRobustEstimator(final Line2DRobustEstimatorListener listener, final double[] qualityScores) {
152         super(listener);
153         threshold = DEFAULT_THRESHOLD;
154         internalSetQualityScores(qualityScores);
155     }
156 
157 
158     /**
159      * Constructor.
160      *
161      * @param listener      listener to be notified of events such as when estimation
162      *                      starts, ends or its progress significantly changes.
163      * @param points        2D points to estimate a 2D line.
164      * @param qualityScores quality scores corresponding to each provided point.
165      * @throws IllegalArgumentException if provided list of points don't have
166      *                                  the same size as the list of provided quality scores, or it their size
167      *                                  is not greater or equal than MINIMUM_SIZE.
168      */
169     public PROSACLine2DRobustEstimator(
170             final Line2DRobustEstimatorListener listener, final List<Point2D> points, final double[] qualityScores) {
171         super(listener, points);
172 
173         if (qualityScores.length != points.size()) {
174             throw new IllegalArgumentException();
175         }
176 
177         threshold = DEFAULT_THRESHOLD;
178         internalSetQualityScores(qualityScores);
179     }
180 
181     /**
182      * Returns threshold to determine whether points are inliers or not when
183      * testing possible estimation solutions.
184      * The threshold refers to the amount of error a possible solution has on a
185      * given point.
186      *
187      * @return threshold to determine whether points are inliers or not when
188      * testing possible estimation solutions.
189      */
190     public double getThreshold() {
191         return threshold;
192     }
193 
194     /**
195      * Sets threshold to determine whether points are inliers or not when
196      * testing possible estimation solutions.
197      * The threshold refers to the amount of error a possible solution has on
198      * a given point.
199      *
200      * @param threshold threshold to be set.
201      * @throws IllegalArgumentException if provided value is equal or less than
202      *                                  zero.
203      * @throws LockedException          if robust estimator is locked because an
204      *                                  estimation is already in progress.
205      */
206     public void setThreshold(final double threshold) throws LockedException {
207         if (isLocked()) {
208             throw new LockedException();
209         }
210         if (threshold <= MIN_THRESHOLD) {
211             throw new IllegalArgumentException();
212         }
213         this.threshold = threshold;
214     }
215 
216     /**
217      * Returns quality scores corresponding to each provided point.
218      * The larger the score value the better the quality of the sampled point.
219      *
220      * @return quality scores corresponding to each point.
221      */
222     @Override
223     public double[] getQualityScores() {
224         return qualityScores;
225     }
226 
227     /**
228      * Sets quality scores corresponding to each provided point.
229      * The larger the score value the better the quality of the sampled point.
230      *
231      * @param qualityScores quality scores corresponding to each point.
232      * @throws LockedException          if robust estimator is locked because an
233      *                                  estimation is already in progress.
234      * @throws IllegalArgumentException if provided quality scores length is
235      *                                  smaller than MINIMUM_SIZE (i.e. 2 samples).
236      */
237     @Override
238     public void setQualityScores(final double[] qualityScores) throws LockedException {
239         if (isLocked()) {
240             throw new LockedException();
241         }
242         internalSetQualityScores(qualityScores);
243     }
244 
245     /**
246      * Indicates if estimator is ready to start the 2D line estimation.
247      * This is true when input data (i.e. 2D points and quality scores) are
248      * provided and a minimum of MINIMUM_SIZE points are available.
249      *
250      * @return true if estimator is ready, false otherwise.
251      */
252     @Override
253     public boolean isReady() {
254         return super.isReady() && qualityScores != null && qualityScores.length == points.size();
255     }
256 
257     /**
258      * Estimates a 2D line using a robust estimator and the best set of 2D
259      * points that pass through the estimated 2D line (i.e. belong to its locus)
260      *
261      * @return a 2D line.
262      * @throws LockedException          if robust estimator is locked because an
263      *                                  estimation is already in progress.
264      * @throws NotReadyException        if provided input data is not enough to start
265      *                                  the estimation.
266      * @throws RobustEstimatorException if estimation fails for any reason
267      *                                  (i.e. numerical instability, no solution available, etc).
268      */
269     @Override
270     public Line2D estimate() throws LockedException, NotReadyException, RobustEstimatorException {
271         if (isLocked()) {
272             throw new LockedException();
273         }
274         if (!isReady()) {
275             throw new NotReadyException();
276         }
277 
278         final var innerEstimator = new PROSACRobustEstimator<>(new PROSACRobustEstimatorListener<Line2D>() {
279 
280             @Override
281             public double getThreshold() {
282                 return threshold;
283             }
284 
285             @Override
286             public int getTotalSamples() {
287                 return points.size();
288             }
289 
290             @Override
291             public int getSubsetSize() {
292                 return Line2DRobustEstimator.MINIMUM_SIZE;
293             }
294 
295             @Override
296             public void estimatePreliminarSolutions(final int[] samplesIndices, final List<Line2D> solutions) {
297                 final var point1 = points.get(samplesIndices[0]);
298                 final var point2 = points.get(samplesIndices[1]);
299 
300                 try {
301                     final var line = new Line2D(point1, point2, false);
302                     solutions.add(line);
303                 } catch (final CoincidentPointsException e) {
304                     // if points are coincident, no solution is added
305                 }
306             }
307 
308             @Override
309             public double computeResidual(final Line2D currentEstimation, final int i) {
310                 return residual(currentEstimation, points.get(i));
311             }
312 
313             @Override
314             public boolean isReady() {
315                 return PROSACLine2DRobustEstimator.this.isReady();
316             }
317 
318             @Override
319             public void onEstimateStart(final RobustEstimator<Line2D> estimator) {
320                 if (listener != null) {
321                     listener.onEstimateStart(PROSACLine2DRobustEstimator.this);
322                 }
323             }
324 
325             @Override
326             public void onEstimateEnd(final RobustEstimator<Line2D> estimator) {
327                 if (listener != null) {
328                     listener.onEstimateEnd(PROSACLine2DRobustEstimator.this);
329                 }
330             }
331 
332             @Override
333             public void onEstimateNextIteration(final RobustEstimator<Line2D> estimator, final int iteration) {
334                 if (listener != null) {
335                     listener.onEstimateNextIteration(PROSACLine2DRobustEstimator.this, iteration);
336                 }
337             }
338 
339             @Override
340             public void onEstimateProgressChange(final RobustEstimator<Line2D> estimator, final float progress) {
341                 if (listener != null) {
342                     listener.onEstimateProgressChange(PROSACLine2DRobustEstimator.this, progress);
343                 }
344             }
345 
346             @Override
347             public double[] getQualityScores() {
348                 return qualityScores;
349             }
350         });
351 
352         try {
353             locked = true;
354             innerEstimator.setConfidence(confidence);
355             innerEstimator.setMaxIterations(maxIterations);
356             innerEstimator.setProgressDelta(progressDelta);
357             return innerEstimator.estimate();
358         } catch (final com.irurueta.numerical.LockedException e) {
359             throw new LockedException(e);
360         } catch (final com.irurueta.numerical.NotReadyException e) {
361             throw new NotReadyException(e);
362         } finally {
363             locked = false;
364         }
365     }
366 
367     /**
368      * Returns method being used for robust estimation.
369      *
370      * @return method being used for robust estimation.
371      */
372     @Override
373     public RobustEstimatorMethod getMethod() {
374         return RobustEstimatorMethod.PROSAC;
375     }
376 
377     /**
378      * Sets quality scores corresponding to each provided point.
379      * This method is used internally and does not check whether instance is
380      * locked or not.
381      *
382      * @param qualityScores quality scores to be set.
383      * @throws IllegalArgumentException if provided quality scores length is
384      *                                  smaller than MINIMUM_SIZE.
385      */
386     private void internalSetQualityScores(final double[] qualityScores) {
387         if (qualityScores.length < MINIMUM_SIZE) {
388             throw new IllegalArgumentException();
389         }
390 
391         this.qualityScores = qualityScores;
392     }
393 }