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.NoIntersectionException;
19  import com.irurueta.geometry.Plane;
20  import com.irurueta.geometry.Point3D;
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 3D point for provided collection of 3D planes using RANSAC
31   * algorithms.
32   */
33  public class RANSACPoint3DRobustEstimator extends Point3DRobustEstimator {
34  
35      /**
36       * Constant defining default threshold to determine whether planes 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       * Indicates that by default inliers will only be computed but not kept.
51       */
52      public static final boolean DEFAULT_COMPUTE_AND_KEEP_INLIERS = false;
53  
54      /**
55       * Indicates that by default residuals will only be computed but not kept.
56       */
57      public static final boolean DEFAULT_COMPUTE_AND_KEEP_RESIDUALS = false;
58  
59      /**
60       * Threshold to determine whether planes are inliers or not when testing
61       * possible estimation solutions.
62       * The threshold refers to the amount of error (i.e. distance) a possible
63       * solution has on a sampled line.
64       */
65      private double threshold;
66  
67      /**
68       * Indicates whether inliers must be computed and kept.
69       */
70      private boolean computeAndKeepInliers;
71  
72      /**
73       * Indicates whether residuals must be computed and kept.
74       */
75      private boolean computeAndKeepResiduals;
76  
77      /**
78       * Constructor.
79       */
80      public RANSACPoint3DRobustEstimator() {
81          super();
82          threshold = DEFAULT_THRESHOLD;
83          computeAndKeepInliers = DEFAULT_COMPUTE_AND_KEEP_INLIERS;
84          computeAndKeepResiduals = DEFAULT_COMPUTE_AND_KEEP_RESIDUALS;
85      }
86  
87      /**
88       * Constructor with planes.
89       *
90       * @param planes 3D planes to estimate a 3D point.
91       * @throws IllegalArgumentException if provided list of planes doesn't have
92       *                                  a size greater or equal than MINIMUM_SIZE.
93       */
94      public RANSACPoint3DRobustEstimator(final List<Plane> planes) {
95          super(planes);
96          threshold = DEFAULT_THRESHOLD;
97          computeAndKeepInliers = DEFAULT_COMPUTE_AND_KEEP_INLIERS;
98          computeAndKeepResiduals = DEFAULT_COMPUTE_AND_KEEP_RESIDUALS;
99      }
100 
101     /**
102      * Constructor.
103      *
104      * @param listener listener to be notified of events such as when estimation
105      *                 starts, ends or its progress significantly changes.
106      */
107     public RANSACPoint3DRobustEstimator(final Point3DRobustEstimatorListener listener) {
108         super(listener);
109         threshold = DEFAULT_THRESHOLD;
110         computeAndKeepInliers = DEFAULT_COMPUTE_AND_KEEP_INLIERS;
111         computeAndKeepResiduals = DEFAULT_COMPUTE_AND_KEEP_RESIDUALS;
112     }
113 
114 
115     /**
116      * Constructor.
117      *
118      * @param listener listener to be notified of events such as when estimation
119      *                 starts, ends or its progress significantly changes.
120      * @param planes   3D planes to estimate a 3D point.
121      * @throws IllegalArgumentException if provided list of planes doesn't have
122      *                                  a size greater or equal than MINIMUM_SIZE.
123      */
124     public RANSACPoint3DRobustEstimator(final Point3DRobustEstimatorListener listener, final List<Plane> planes) {
125         super(listener, planes);
126         threshold = DEFAULT_THRESHOLD;
127         computeAndKeepInliers = DEFAULT_COMPUTE_AND_KEEP_INLIERS;
128         computeAndKeepResiduals = DEFAULT_COMPUTE_AND_KEEP_RESIDUALS;
129     }
130 
131     /**
132      * Returns threshold to determine whether planes are inliers or not when
133      * testing possible estimation solutions.
134      * The threshold refers to the amount of error a possible solution has on a
135      * given plane.
136      *
137      * @return threshold to determine whether planes are inliers or not when
138      * testing possible estimation solutions.
139      */
140     public double getThreshold() {
141         return threshold;
142     }
143 
144     /**
145      * Sets threshold to determine whether planes are inliers or not when
146      * testing possible estimation solutions.
147      * The threshold refers to the amount of error a possible solution has on
148      * a given plane.
149      *
150      * @param threshold threshold to be set.
151      * @throws IllegalArgumentException if provided value is equal or less than
152      *                                  zero.
153      * @throws LockedException          if robust estimator is locked because an
154      *                                  estimation is already in progress.
155      */
156     public void setThreshold(final double threshold) throws LockedException {
157         if (isLocked()) {
158             throw new LockedException();
159         }
160         if (threshold <= MIN_THRESHOLD) {
161             throw new IllegalArgumentException();
162         }
163         this.threshold = threshold;
164     }
165 
166     /**
167      * Indicates whether inliers must be computed and kept.
168      *
169      * @return true if inliers must be computed and kept, false if inliers
170      * only need to be computed but not kept.
171      */
172     public boolean isComputeAndKeepInliersEnabled() {
173         return computeAndKeepInliers;
174     }
175 
176     /**
177      * Specifies whether inliers must be computed and kept.
178      *
179      * @param computeAndKeepInliers true if inliers must be computed and kept,
180      *                              false if inliers only need to be computed but not kept.
181      * @throws LockedException if estimator is locked.
182      */
183     public void setComputeAndKeepInliersEnabled(final boolean computeAndKeepInliers) throws LockedException {
184         if (isLocked()) {
185             throw new LockedException();
186         }
187         this.computeAndKeepInliers = computeAndKeepInliers;
188     }
189 
190     /**
191      * Indicates whether residuals must be computed and kept.
192      *
193      * @return true if residuals must be computed and kept, false if residuals
194      * only need to be computed but not kept.
195      */
196     public boolean isComputeAndKeepResidualsEnabled() {
197         return computeAndKeepResiduals;
198     }
199 
200     /**
201      * Specifies whether residuals must be computed and kept.
202      *
203      * @param computeAndKeepResiduals true if residuals must be computed and
204      *                                kept, false if residuals only need to be computed but not kept.
205      * @throws LockedException if estimator is locked.
206      */
207     public void setComputeAndKeepResidualsEnabled(final boolean computeAndKeepResiduals) throws LockedException {
208         if (isLocked()) {
209             throw new LockedException();
210         }
211         this.computeAndKeepResiduals = computeAndKeepResiduals;
212     }
213 
214     /**
215      * Estimates a 3D point using a robust estimator and the best set of 3D
216      * planes that intersect into the estimated 3D point.
217      *
218      * @return a 3D point.
219      * @throws LockedException          if robust estimator is locked because an
220      *                                  estimation is already in progress.
221      * @throws NotReadyException        if provided input data is not enough to start
222      *                                  the estimation.
223      * @throws RobustEstimatorException if estimation fails for any reason
224      *                                  (i.e. numerical instability, no solution available, etc).
225      */
226     @SuppressWarnings("DuplicatedCode")
227     @Override
228     public Point3D estimate() throws LockedException, NotReadyException, RobustEstimatorException {
229         if (isLocked()) {
230             throw new LockedException();
231         }
232         if (!isReady()) {
233             throw new NotReadyException();
234         }
235 
236         final var innerEstimator = new RANSACRobustEstimator<>(new RANSACRobustEstimatorListener<Point3D>() {
237 
238             @Override
239             public double getThreshold() {
240                 return threshold;
241             }
242 
243             @Override
244             public int getTotalSamples() {
245                 return planes.size();
246             }
247 
248             @Override
249             public int getSubsetSize() {
250                 return Point3DRobustEstimator.MINIMUM_SIZE;
251             }
252 
253             @Override
254             public void estimatePreliminarSolutions(final int[] samplesIndices, final List<Point3D> solutions) {
255                 final var plane1 = planes.get(samplesIndices[0]);
256                 final var plane2 = planes.get(samplesIndices[1]);
257                 final var plane3 = planes.get(samplesIndices[2]);
258 
259                 try {
260                     final var point = plane1.getIntersection(plane2, plane3);
261                     solutions.add(point);
262                 } catch (final NoIntersectionException e) {
263                     // if points are coincident, no solution is added
264                 }
265             }
266 
267             @Override
268             public double computeResidual(final Point3D currentEstimation, final int i) {
269                 return residual(currentEstimation, planes.get(i));
270             }
271 
272             @Override
273             public boolean isReady() {
274                 return RANSACPoint3DRobustEstimator.this.isReady();
275             }
276 
277             @Override
278             public void onEstimateStart(final RobustEstimator<Point3D> estimator) {
279                 if (listener != null) {
280                     listener.onEstimateStart(RANSACPoint3DRobustEstimator.this);
281                 }
282             }
283 
284             @Override
285             public void onEstimateEnd(final RobustEstimator<Point3D> estimator) {
286                 if (listener != null) {
287                     listener.onEstimateEnd(RANSACPoint3DRobustEstimator.this);
288                 }
289             }
290 
291             @Override
292             public void onEstimateNextIteration(final RobustEstimator<Point3D> estimator, final int iteration) {
293                 if (listener != null) {
294                     listener.onEstimateNextIteration(RANSACPoint3DRobustEstimator.this, iteration);
295                 }
296             }
297 
298             @Override
299             public void onEstimateProgressChange(final RobustEstimator<Point3D> estimator, final float progress) {
300                 if (listener != null) {
301                     listener.onEstimateProgressChange(RANSACPoint3DRobustEstimator.this, progress);
302                 }
303             }
304         });
305 
306         try {
307             locked = true;
308             inliersData = null;
309             innerEstimator.setComputeAndKeepInliersEnabled(computeAndKeepInliers || refineResult);
310             innerEstimator.setComputeAndKeepResidualsEnabled(computeAndKeepResiduals || refineResult);
311             innerEstimator.setConfidence(confidence);
312             innerEstimator.setMaxIterations(maxIterations);
313             innerEstimator.setProgressDelta(progressDelta);
314             final var result = innerEstimator.estimate();
315             inliersData = innerEstimator.getInliersData();
316             return attemptRefine(result);
317         } catch (final com.irurueta.numerical.LockedException e) {
318             throw new LockedException(e);
319         } catch (final com.irurueta.numerical.NotReadyException e) {
320             throw new NotReadyException(e);
321         } finally {
322             locked = false;
323         }
324     }
325 
326     /**
327      * Returns method being used for robust estimation.
328      *
329      * @return method being used for robust estimation.
330      */
331     @Override
332     public RobustEstimatorMethod getMethod() {
333         return RobustEstimatorMethod.RANSAC;
334     }
335 
336     /**
337      * Gets standard deviation used for Levenberg-Marquardt fitting during
338      * refinement.
339      * Returned value gives an indication of how much variance each residual
340      * has.
341      * Typically, this value is related to the threshold used on each robust
342      * estimation, since residuals of found inliers are within the range of
343      * such threshold.
344      *
345      * @return standard deviation used for refinement.
346      */
347     @Override
348     protected double getRefinementStandardDeviation() {
349         return threshold;
350     }
351 }