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.Circle;
19  import com.irurueta.geometry.ColinearPointsException;
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 circle for provided collection of 2D points using RANSAC
31   * algorithm.
32   */
33  @SuppressWarnings("DuplicatedCode")
34  public class RANSACCircleRobustEstimator extends CircleRobustEstimator {
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 point.
54       */
55      private double threshold;
56  
57      /**
58       * Constructor.
59       */
60      public RANSACCircleRobustEstimator() {
61          super();
62          threshold = DEFAULT_THRESHOLD;
63      }
64  
65      /**
66       * Constructor with points.
67       *
68       * @param points 2D points to estimate a circle.
69       * @throws IllegalArgumentException if provided list of points don't have
70       *                                  a size greater or equal than MINIMUM_SIZE.
71       */
72      public RANSACCircleRobustEstimator(final List<Point2D> points) {
73          super(points);
74          threshold = DEFAULT_THRESHOLD;
75      }
76  
77      /**
78       * Constructor.
79       *
80       * @param listener listener to be notified of events such as when estimation
81       *                 starts, ends or its progress significantly changes.
82       */
83      public RANSACCircleRobustEstimator(final CircleRobustEstimatorListener listener) {
84          super(listener);
85          threshold = DEFAULT_THRESHOLD;
86      }
87  
88  
89      /**
90       * Constructor.
91       *
92       * @param listener listener to be notified of events such as when estimation
93       *                 starts, ends or its progress significantly changes.
94       * @param points   2D points to estimate a circle.
95       * @throws IllegalArgumentException if provided list of points don't have
96       *                                  a size greater or equal than MINIMUM_SIZE.
97       */
98      public RANSACCircleRobustEstimator(final CircleRobustEstimatorListener listener, final List<Point2D> points) {
99          super(listener, points);
100         threshold = DEFAULT_THRESHOLD;
101     }
102 
103     /**
104      * Returns threshold to determine whether points are inliers or not when
105      * testing possible estimation solutions.
106      * The threshold refers to the amount of error a possible solution has on a
107      * given point.
108      *
109      * @return threshold to determine whether points are inliers or not when
110      * testing possible estimation solutions.
111      */
112     public double getThreshold() {
113         return threshold;
114     }
115 
116     /**
117      * Sets threshold to determine whether points are inliers or not when
118      * testing possible estimation solutions.
119      * The threshold refers to the amount of error a possible solution has on
120      * a given point.
121      *
122      * @param threshold threshold to be set.
123      * @throws IllegalArgumentException if provided value is equal or less than
124      *                                  zero.
125      * @throws LockedException          if robust estimator is locked because an
126      *                                  estimation is already in progress.
127      */
128     public void setThreshold(final double threshold) throws LockedException {
129         if (isLocked()) {
130             throw new LockedException();
131         }
132         if (threshold <= MIN_THRESHOLD) {
133             throw new IllegalArgumentException();
134         }
135         this.threshold = threshold;
136     }
137 
138 
139     /**
140      * Estimates a circle using a robust estimator and the best set of 2D points
141      * that fit into the locus of the estimated circle found using the robust
142      * estimator.
143      *
144      * @return a circle.
145      * @throws LockedException          if robust estimator is locked because an
146      *                                  estimation is already in progress.
147      * @throws NotReadyException        if provided input data is not enough to start
148      *                                  the estimation.
149      * @throws RobustEstimatorException if estimation fails for any reason
150      *                                  (i.e. numerical instability, no solution available, etc).
151      */
152     @Override
153     public Circle estimate() throws LockedException, NotReadyException, RobustEstimatorException {
154         if (isLocked()) {
155             throw new LockedException();
156         }
157         if (!isReady()) {
158             throw new NotReadyException();
159         }
160 
161         final var innerEstimator = new RANSACRobustEstimator<>(new RANSACRobustEstimatorListener<Circle>() {
162 
163             @Override
164             public double getThreshold() {
165                 return threshold;
166             }
167 
168             @Override
169             public int getTotalSamples() {
170                 return points.size();
171             }
172 
173             @Override
174             public int getSubsetSize() {
175                 return CircleRobustEstimator.MINIMUM_SIZE;
176             }
177 
178             @Override
179             public void estimatePreliminarSolutions(final int[] samplesIndices, final List<Circle> solutions) {
180                 final var point1 = points.get(samplesIndices[0]);
181                 final var point2 = points.get(samplesIndices[1]);
182                 final var point3 = points.get(samplesIndices[2]);
183 
184                 try {
185                     final var circle = new Circle(point1, point2, point3);
186                     solutions.add(circle);
187                 } catch (final ColinearPointsException e) {
188                     // if points are coincident, no solution is added
189                 }
190             }
191 
192             @Override
193             public double computeResidual(final Circle currentEstimation, final int i) {
194                 return residual(currentEstimation, points.get(i));
195             }
196 
197             @Override
198             public boolean isReady() {
199                 return RANSACCircleRobustEstimator.this.isReady();
200             }
201 
202             @Override
203             public void onEstimateStart(final RobustEstimator<Circle> estimator) {
204                 if (listener != null) {
205                     listener.onEstimateStart(RANSACCircleRobustEstimator.this);
206                 }
207             }
208 
209             @Override
210             public void onEstimateEnd(final RobustEstimator<Circle> estimator) {
211                 if (listener != null) {
212                     listener.onEstimateEnd(RANSACCircleRobustEstimator.this);
213                 }
214             }
215 
216             @Override
217             public void onEstimateNextIteration(final RobustEstimator<Circle> estimator, final int iteration) {
218                 if (listener != null) {
219                     listener.onEstimateNextIteration(RANSACCircleRobustEstimator.this, iteration);
220                 }
221             }
222 
223             @Override
224             public void onEstimateProgressChange(final RobustEstimator<Circle> estimator, final float progress) {
225                 if (listener != null) {
226                     listener.onEstimateProgressChange(RANSACCircleRobustEstimator.this, progress);
227                 }
228             }
229         });
230 
231         try {
232             locked = true;
233             innerEstimator.setConfidence(confidence);
234             innerEstimator.setMaxIterations(maxIterations);
235             innerEstimator.setProgressDelta(progressDelta);
236             return innerEstimator.estimate();
237         } catch (final com.irurueta.numerical.LockedException e) {
238             throw new LockedException(e);
239         } catch (final com.irurueta.numerical.NotReadyException e) {
240             throw new NotReadyException(e);
241         } finally {
242             locked = false;
243         }
244     }
245 
246     /**
247      * Returns method being used for robust estimation.
248      *
249      * @return method being used for robust estimation.
250      */
251     @Override
252     public RobustEstimatorMethod getMethod() {
253         return RobustEstimatorMethod.RANSAC;
254     }
255 }