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