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.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 3D point for provided collection of 3D planes using MSAC
31   * algorithm.
32   */
33  public class MSACPoint3DRobustEstimator extends Point3DRobustEstimator {
34  
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 lines 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 line.
54       */
55      private double threshold;
56  
57      /**
58       * Constructor.
59       */
60      public MSACPoint3DRobustEstimator() {
61          super();
62          threshold = DEFAULT_THRESHOLD;
63      }
64  
65      /**
66       * Constructor with planes.
67       *
68       * @param planes 3D planes to estimate a 3D point.
69       * @throws IllegalArgumentException if provided list of planes doesn't have
70       *                                  a size greater or equal than MINIMUM_SIZE.
71       */
72      public MSACPoint3DRobustEstimator(final List<Plane> planes) {
73          super(planes);
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 MSACPoint3DRobustEstimator(final Point3DRobustEstimatorListener 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 planes   3D planes to estimate a 3D point.
95       * @throws IllegalArgumentException if provided list of planes doesn't have
96       *                                  a size greater or equal than MINIMUM_SIZE.
97       */
98      public MSACPoint3DRobustEstimator(final Point3DRobustEstimatorListener listener, final List<Plane> planes) {
99          super(listener, planes);
100         threshold = DEFAULT_THRESHOLD;
101     }
102 
103     /**
104      * Returns threshold to determine whether planes 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 plane.
108      *
109      * @return threshold to determine whether planes 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 planes 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 plane.
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 3D point using a robust estimator and the best set of 3D
141      * planes that intersect into the estimated 3D point.
142      *
143      * @return a 3D point.
144      * @throws LockedException          if robust estimator is locked because an
145      *                                  estimation is already in progress.
146      * @throws NotReadyException        if provided input data is not enough to start
147      *                                  the estimation.
148      * @throws RobustEstimatorException if estimation fails for any reason
149      *                                  (i.e. numerical instability, no solution available, etc).
150      */
151     @SuppressWarnings("DuplicatedCode")
152     @Override
153     public Point3D 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<Point3D>() {
162 
163             @Override
164             public double getThreshold() {
165                 return threshold;
166             }
167 
168             @Override
169             public int getTotalSamples() {
170                 return planes.size();
171             }
172 
173             @Override
174             public int getSubsetSize() {
175                 return Point3DRobustEstimator.MINIMUM_SIZE;
176             }
177 
178             @SuppressWarnings("DuplicatedCode")
179             @Override
180             public void estimatePreliminarSolutions(final int[] samplesIndices, final List<Point3D> solutions) {
181                 final var plane1 = planes.get(samplesIndices[0]);
182                 final var plane2 = planes.get(samplesIndices[1]);
183                 final var plane3 = planes.get(samplesIndices[2]);
184 
185                 try {
186                     final var point = plane1.getIntersection(plane2, plane3);
187                     solutions.add(point);
188                 } catch (final NoIntersectionException e) {
189                     // if points are coincident, no solution is added
190                 }
191             }
192 
193             @Override
194             public double computeResidual(final Point3D currentEstimation, int i) {
195                 return residual(currentEstimation, planes.get(i));
196             }
197 
198             @Override
199             public boolean isReady() {
200                 return MSACPoint3DRobustEstimator.this.isReady();
201             }
202 
203             @Override
204             public void onEstimateStart(final RobustEstimator<Point3D> estimator) {
205                 if (listener != null) {
206                     listener.onEstimateStart(MSACPoint3DRobustEstimator.this);
207                 }
208             }
209 
210             @Override
211             public void onEstimateEnd(final RobustEstimator<Point3D> estimator) {
212                 if (listener != null) {
213                     listener.onEstimateEnd(MSACPoint3DRobustEstimator.this);
214                 }
215             }
216 
217             @Override
218             public void onEstimateNextIteration(final RobustEstimator<Point3D> estimator, final int iteration) {
219                 if (listener != null) {
220                     listener.onEstimateNextIteration(MSACPoint3DRobustEstimator.this, iteration);
221                 }
222             }
223 
224             @Override
225             public void onEstimateProgressChange(final RobustEstimator<Point3D> estimator, final float progress) {
226                 if (listener != null) {
227                     listener.onEstimateProgressChange(MSACPoint3DRobustEstimator.this, progress);
228                 }
229             }
230         });
231 
232         try {
233             locked = true;
234             inliersData = null;
235             innerEstimator.setConfidence(confidence);
236             innerEstimator.setMaxIterations(maxIterations);
237             innerEstimator.setProgressDelta(progressDelta);
238             final var result = innerEstimator.estimate();
239             inliersData = innerEstimator.getInliersData();
240             return attemptRefine(result);
241         } catch (final com.irurueta.numerical.LockedException e) {
242             throw new LockedException(e);
243         } catch (final com.irurueta.numerical.NotReadyException e) {
244             throw new NotReadyException(e);
245         } finally {
246             locked = false;
247         }
248     }
249 
250     /**
251      * Returns method being used for robust estimation.
252      *
253      * @return method being used for robust estimation.
254      */
255     @Override
256     public RobustEstimatorMethod getMethod() {
257         return RobustEstimatorMethod.MSAC;
258     }
259 
260     /**
261      * Gets standard deviation used for Levenberg-Marquardt fitting during
262      * refinement.
263      * Returned value gives an indication of how much variance each residual
264      * has.
265      * Typically, this value is related to the threshold used on each robust
266      * estimation, since residuals of found inliers are within the range of
267      * such threshold.
268      *
269      * @return standard deviation used for refinement.
270      */
271     @Override
272     protected double getRefinementStandardDeviation() {
273         return threshold;
274     }
275 }