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