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.MSACRobustEstimator;
22  import com.irurueta.numerical.robust.MSACRobustEstimatorListener;
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 MSAC
31   * algorithm.
32   */
33  @SuppressWarnings("DuplicatedCode")
34  public class MSACSphereRobustEstimator extends SphereRobustEstimator {
35      /**
36       * Constant defining default threshold to determine whether points are
37       * inliers or not.
38       * Because typical resolution for points is 1 voxel, 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 matched pair of points.
54       */
55      private double threshold;
56  
57      /**
58       * Constructor.
59       */
60      public MSACSphereRobustEstimator() {
61          super();
62          threshold = DEFAULT_THRESHOLD;
63      }
64  
65      /**
66       * Constructor with points.
67       *
68       * @param points 3D points to estimate a sphere.
69       * @throws IllegalArgumentException if provided list of points don't have
70       *                                  a size greater or equal than MINIMUM_SIZE.
71       */
72      public MSACSphereRobustEstimator(final List<Point3D> 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 MSACSphereRobustEstimator(final SphereRobustEstimatorListener 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   3D points to estimate a sphere.
95       * @throws IllegalArgumentException if provided list of points don't have
96       *                                  a size greater or equal than MINIMUM_SIZE.
97       */
98      public MSACSphereRobustEstimator(final SphereRobustEstimatorListener listener, final List<Point3D> 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 sphere using a robust estimator and the best set of 3D points
141      * that fit into the locus of the estimated sphere found using the robust
142      * estimator.
143      *
144      * @return a sphere.
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 Sphere 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 MSACRobustEstimator<>(new MSACRobustEstimatorListener<Sphere>() {
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 SphereRobustEstimator.MINIMUM_SIZE;
176             }
177 
178             @Override
179             public void estimatePreliminarSolutions(final int[] samplesIndices, final List<Sphere> 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                 final var point4 = points.get(samplesIndices[3]);
184 
185                 try {
186                     final var sphere = new Sphere(point1, point2, point3, point4);
187                     solutions.add(sphere);
188                 } catch (final CoplanarPointsException e) {
189                     // if points are coincident, no solution is added
190                 }
191             }
192 
193             @Override
194             public double computeResidual(final Sphere currentEstimation, final int i) {
195                 return residual(currentEstimation, points.get(i));
196             }
197 
198             @Override
199             public boolean isReady() {
200                 return MSACSphereRobustEstimator.this.isReady();
201             }
202 
203             @Override
204             public void onEstimateStart(final RobustEstimator<Sphere> estimator) {
205                 if (listener != null) {
206                     listener.onEstimateStart(MSACSphereRobustEstimator.this);
207                 }
208             }
209 
210             @Override
211             public void onEstimateEnd(final RobustEstimator<Sphere> estimator) {
212                 if (listener != null) {
213                     listener.onEstimateEnd(MSACSphereRobustEstimator.this);
214                 }
215             }
216 
217             @Override
218             public void onEstimateNextIteration(final RobustEstimator<Sphere> estimator, final int iteration) {
219                 if (listener != null) {
220                     listener.onEstimateNextIteration(MSACSphereRobustEstimator.this, iteration);
221                 }
222             }
223 
224             @Override
225             public void onEstimateProgressChange(final RobustEstimator<Sphere> estimator, final float progress) {
226                 if (listener != null) {
227                     listener.onEstimateProgressChange(MSACSphereRobustEstimator.this, progress);
228                 }
229             }
230         });
231 
232         try {
233             locked = true;
234             innerEstimator.setConfidence(confidence);
235             innerEstimator.setMaxIterations(maxIterations);
236             innerEstimator.setProgressDelta(progressDelta);
237             return innerEstimator.estimate();
238         } catch (final com.irurueta.numerical.LockedException e) {
239             throw new LockedException(e);
240         } catch (final com.irurueta.numerical.NotReadyException e) {
241             throw new NotReadyException(e);
242         } finally {
243             locked = false;
244         }
245     }
246 
247     /**
248      * Returns method being used for robust estimation.
249      *
250      * @return method being used for robust estimation.
251      */
252     @Override
253     public RobustEstimatorMethod getMethod() {
254         return RobustEstimatorMethod.MSAC;
255     }
256 }