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.CoplanarPointsException;
19  import com.irurueta.geometry.Point3D;
20  import com.irurueta.geometry.Sphere;
21  import com.irurueta.numerical.robust.LMedSRobustEstimator;
22  import com.irurueta.numerical.robust.LMedSRobustEstimatorListener;
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 sphere for provided collection of 3D points using LMedS
31   * algorithm.
32   */
33  public class LMedSSphereRobustEstimator extends SphereRobustEstimator {
34      /**
35       * Default value to be used for stop threshold. Stop threshold can be used
36       * to keep the algorithm iterating in case that best estimated threshold
37       * using median of residuals is not small enough. Once a solution is found
38       * that generates a threshold below this value, the algorithm will stop.
39       * The stop threshold can be used to prevent the LMedS algorithm iterating
40       * too many times in cases where samples have a very similar accuracy.
41       * For instance, in cases where proportion of outliers is very small (close
42       * to 0%), and samples are very accurate (i.e. 1e-6), the algorithm would
43       * iterate for a long time trying to find the best solution when indeed
44       * there is no need to do that if a reasonable threshold has already been
45       * reached.
46       * Because of this behaviour the stop threshold can be set to a value much
47       * lower than the one typically used in RANSAC, and yet the algorithm could
48       * still produce even smaller thresholds in estimated results.
49       */
50      public static final double DEFAULT_STOP_THRESHOLD = 1e-3;
51  
52      /**
53       * Minimum allowed stop threshold value.
54       */
55      public static final double MIN_STOP_THRESHOLD = 0.0;
56  
57      /**
58       * Threshold to be used to keep the algorithm iterating in case that best
59       * estimated threshold using median of residuals is not small enough. Once
60       * a solution is found that generates a threshold below this value, the
61       * algorithm will stop.
62       * The stop threshold can be used to prevent the LMedS algorithm iterating
63       * too many times in cases where samples have a very similar accuracy.
64       * For instance, in cases where proportion of outliers is very small (close
65       * to 0%), and samples are very accurate (i.e. 1e-6), the algorithm would
66       * iterate for a long time trying to find the best solution when indeed
67       * there is no need to do that if a reasonable threshold has already been
68       * reached.
69       * Because of this behaviour the stop threshold can be set to a value much
70       * lower than the one typically used in RANSAC, and yet the algorithm could
71       * still produce even smaller thresholds in estimated results.
72       */
73      private double mStopThreshold;
74  
75      /**
76       * Constructor.
77       */
78      public LMedSSphereRobustEstimator() {
79          super();
80          mStopThreshold = DEFAULT_STOP_THRESHOLD;
81      }
82  
83      /**
84       * Constructor with points.
85       *
86       * @param points 3D points to estimate a sphere.
87       * @throws IllegalArgumentException if provided list of points don't have
88       *                                  a size greater or equal than MINIMUM_SIZE.
89       */
90      public LMedSSphereRobustEstimator(final List<Point3D> points) {
91          super(points);
92          mStopThreshold = DEFAULT_STOP_THRESHOLD;
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      */
101     public LMedSSphereRobustEstimator(final SphereRobustEstimatorListener listener) {
102         super(listener);
103         mStopThreshold = DEFAULT_STOP_THRESHOLD;
104     }
105 
106 
107     /**
108      * Constructor.
109      *
110      * @param listener listener to be notified of events such as when estimation
111      *                 starts, ends or its progress significantly changes.
112      * @param points   3D points to estimate a sphere.
113      * @throws IllegalArgumentException if provided list of points don't have
114      *                                  a size greater or equal than MINIMUM_SIZE.
115      */
116     public LMedSSphereRobustEstimator(final SphereRobustEstimatorListener listener, final List<Point3D> points) {
117         super(listener, points);
118         mStopThreshold = DEFAULT_STOP_THRESHOLD;
119     }
120 
121     /**
122      * Returns threshold to be used to keep the algorithm iterating in case that
123      * best estimated threshold using median of residuals is not small enough.
124      * Once a solution is found that generates a threshold below this value, the
125      * algorithm will stop.
126      * The stop threshold can be used to prevent the LMedS algorithm iterating
127      * too many times in cases where samples have a very similar accuracy.
128      * For instance, in cases where proportion of outliers is very small (close
129      * to 0%), and samples are very accurate (i.e. 1e-6), the algorithm would
130      * iterate for a long time trying to find the best solution when indeed
131      * there is no need to do that if a reasonable threshold has already been
132      * reached.
133      * Because of this behaviour the stop threshold can be set to a value much
134      * lower than the one typically used in RANSAC, and yet the algorithm could
135      * still produce even smaller thresholds in estimated results.
136      *
137      * @return stop threshold to stop the algorithm prematurely when a certain
138      * accuracy has been reached.
139      */
140     public double getStopThreshold() {
141         return mStopThreshold;
142     }
143 
144     /**
145      * Sets threshold to be used to keep the algorithm iterating in case that
146      * best estimated threshold using median of residuals is not small enough.
147      * Once a solution is found that generates a threshold below this value, the
148      * algorithm will stop.
149      * The stop threshold can be used to prevent the LMedS algorithm iterating
150      * too many times in cases where samples have a very similar accuracy.
151      * For instance, in cases where proportion of outliers is very small (close
152      * to 0%), and samples are very accurate (i.e. 1e-6), the algorithm would
153      * iterate for a long time trying to find the best solution when indeed
154      * there is no need to do that if a reasonable threshold has already been
155      * reached.
156      * Because of this behaviour the stop threshold can be set to a value much
157      * lower than the one typically used in RANSAC, and yet the algorithm could
158      * still produce even smaller thresholds in estimated results.
159      *
160      * @param stopThreshold stop threshold to stop the algorithm prematurely
161      *                      when a certain accuracy has been reached.
162      * @throws IllegalArgumentException if provided value is zero or negative.
163      * @throws LockedException          if robust estimator is locked because an
164      *                                  estimation is already in progress.
165      */
166     public void setStopThreshold(final double stopThreshold) throws LockedException {
167         if (isLocked()) {
168             throw new LockedException();
169         }
170         if (stopThreshold <= MIN_STOP_THRESHOLD) {
171             throw new IllegalArgumentException();
172         }
173 
174         mStopThreshold = stopThreshold;
175     }
176 
177     /**
178      * Estimates a sphere using a robust estimator and the best set of 3D points
179      * that fit into the locus of the estimated sphere found using the robust
180      * estimator.
181      *
182      * @return a sphere.
183      * @throws LockedException          if robust estimator is locked because an
184      *                                  estimation is already in progress.
185      * @throws NotReadyException        if provided input data is not enough to start
186      *                                  the estimation.
187      * @throws RobustEstimatorException if estimation fails for any reason
188      *                                  (i.e. numerical instability, no solution available, etc).
189      */
190     @SuppressWarnings("DuplicatedCode")
191     @Override
192     public Sphere estimate() throws LockedException, NotReadyException, RobustEstimatorException {
193         if (isLocked()) {
194             throw new LockedException();
195         }
196         if (!isReady()) {
197             throw new NotReadyException();
198         }
199 
200         final var innerEstimator = new LMedSRobustEstimator<>(new LMedSRobustEstimatorListener<Sphere>() {
201 
202             @Override
203             public int getTotalSamples() {
204                 return points.size();
205             }
206 
207             @Override
208             public int getSubsetSize() {
209                 return SphereRobustEstimator.MINIMUM_SIZE;
210             }
211 
212             @Override
213             public void estimatePreliminarSolutions(final int[] samplesIndices, final List<Sphere> solutions) {
214                 final var point1 = points.get(samplesIndices[0]);
215                 final var point2 = points.get(samplesIndices[1]);
216                 final var point3 = points.get(samplesIndices[2]);
217                 final var point4 = points.get(samplesIndices[3]);
218 
219                 try {
220                     final var sphere = new Sphere(point1, point2, point3, point4);
221                     solutions.add(sphere);
222                 } catch (final CoplanarPointsException e) {
223                     // if points are coincident, no solution is added
224                 }
225             }
226 
227             @Override
228             public double computeResidual(final Sphere currentEstimation, final int i) {
229                 return residual(currentEstimation, points.get(i));
230             }
231 
232             @Override
233             public boolean isReady() {
234                 return LMedSSphereRobustEstimator.this.isReady();
235             }
236 
237             @Override
238             public void onEstimateStart(final RobustEstimator<Sphere> estimator) {
239                 if (listener != null) {
240                     listener.onEstimateStart(LMedSSphereRobustEstimator.this);
241                 }
242             }
243 
244             @Override
245             public void onEstimateEnd(final RobustEstimator<Sphere> estimator) {
246                 if (listener != null) {
247                     listener.onEstimateEnd(LMedSSphereRobustEstimator.this);
248                 }
249             }
250 
251             @Override
252             public void onEstimateNextIteration(final RobustEstimator<Sphere> estimator, final int iteration) {
253                 if (listener != null) {
254                     listener.onEstimateNextIteration(LMedSSphereRobustEstimator.this, iteration);
255                 }
256             }
257 
258             @Override
259             public void onEstimateProgressChange(final RobustEstimator<Sphere> estimator, final float progress) {
260                 if (listener != null) {
261                     listener.onEstimateProgressChange(LMedSSphereRobustEstimator.this, progress);
262                 }
263             }
264         });
265 
266         try {
267             locked = true;
268             innerEstimator.setConfidence(confidence);
269             innerEstimator.setMaxIterations(maxIterations);
270             innerEstimator.setProgressDelta(progressDelta);
271             innerEstimator.setStopThreshold(mStopThreshold);
272             return innerEstimator.estimate();
273         } catch (final com.irurueta.numerical.LockedException e) {
274             throw new LockedException(e);
275         } catch (final com.irurueta.numerical.NotReadyException e) {
276             throw new NotReadyException(e);
277         } finally {
278             locked = false;
279         }
280     }
281 
282     /**
283      * Returns method being used for robust estimation.
284      *
285      * @return method being used for robust estimation.
286      */
287     @Override
288     public RobustEstimatorMethod getMethod() {
289         return RobustEstimatorMethod.LMEDS;
290     }
291 }