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