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.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 quadric for provided collection of 3D points using RANSAC
31   * algorithm.
32   */
33  public class RANSACQuadricRobustEstimator extends QuadricRobustEstimator {
34      /**
35       * Constant defining default threshold to determine whether points are
36       * inliers or not.
37       * Threshold is defined by the equation abs(trans(X)) * Q * X) < t, where
38       * trans is the transposition, X is a point, Q is a quadric and t is a
39       * threshold.
40       * This equation determines the points X belonging to the locus of a quadric
41       * Q up to a certain threshold.
42       */
43      public static final double DEFAULT_THRESHOLD = 1e-6;
44  
45      /**
46       * Minimum value that can be set as threshold.
47       * Threshold must be strictly greater than 0.0.
48       */
49      public static final double MIN_THRESHOLD = 0.0;
50  
51      /**
52       * Threshold to determine whether points are inliers or not when testing
53       * possible estimation solutions.
54       * The threshold refers to the amount of error (i.e. distance) a possible
55       * solution has on a matched pair of points.
56       */
57      private double threshold;
58  
59      /**
60       * Constructor.
61       */
62      public RANSACQuadricRobustEstimator() {
63          super();
64          threshold = DEFAULT_THRESHOLD;
65      }
66  
67      /**
68       * Constructor with points.
69       *
70       * @param points 3D points to estimate a quadric.
71       * @throws IllegalArgumentException if provided list of points don't have
72       *                                  a size greater or equal than MINIMUM_SIZE.
73       */
74      public RANSACQuadricRobustEstimator(final List<Point3D> points) {
75          super(points);
76          threshold = DEFAULT_THRESHOLD;
77      }
78  
79      /**
80       * Constructor.
81       *
82       * @param listener listener to be notified of events such as when estimation
83       *                 starts, ends or its progress significantly changes.
84       */
85      public RANSACQuadricRobustEstimator(final QuadricRobustEstimatorListener listener) {
86          super(listener);
87          threshold = DEFAULT_THRESHOLD;
88      }
89  
90  
91      /**
92       * Constructor.
93       *
94       * @param listener listener to be notified of events such as when estimation
95       *                 starts, ends or its progress significantly changes.
96       * @param points   3D points to estimate a quadric.
97       * @throws IllegalArgumentException if provided list of points don't have
98       *                                  a size greater or equal than MINIMUM_SIZE.
99       */
100     public RANSACQuadricRobustEstimator(final QuadricRobustEstimatorListener listener, final List<Point3D> points) {
101         super(listener, points);
102         threshold = DEFAULT_THRESHOLD;
103     }
104 
105     /**
106      * Returns threshold to determine whether points are inliers or not when
107      * testing possible estimation solutions.
108      * The threshold refers to the amount of error a possible solution has on a
109      * given point.
110      *
111      * @return threshold to determine whether points are inliers or not when
112      * testing possible estimation solutions.
113      */
114     public double getThreshold() {
115         return threshold;
116     }
117 
118     /**
119      * Sets threshold to determine whether points are inliers or not when
120      * testing possible estimation solutions.
121      * The threshold refers to the amount of error a possible solution has on
122      * a given point.
123      *
124      * @param threshold threshold to be set.
125      * @throws IllegalArgumentException if provided value is equal or less than
126      *                                  zero.
127      * @throws LockedException          if robust estimator is locked because an
128      *                                  estimation is already in progress.
129      */
130     public void setThreshold(final double threshold) throws LockedException {
131         if (isLocked()) {
132             throw new LockedException();
133         }
134         if (threshold <= MIN_THRESHOLD) {
135             throw new IllegalArgumentException();
136         }
137         this.threshold = threshold;
138     }
139 
140 
141     /**
142      * Estimates a quadric using a robust estimator and the best set of 3D
143      * points that fit into the locus of the estimated quadric found using the
144      * robust estimator.
145      *
146      * @return a quadric.
147      * @throws LockedException          if robust estimator is locked because an
148      *                                  estimation is already in progress.
149      * @throws NotReadyException        if provided input data is not enough to start
150      *                                  the estimation.
151      * @throws RobustEstimatorException if estimation fails for any reason
152      *                                  (i.e. numerical instability, no solution available, etc).
153      */
154     @SuppressWarnings("DuplicatedCode")
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 RANSACRobustEstimator<>(new RANSACRobustEstimatorListener<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 RANSACQuadricRobustEstimator.this.isReady();
210             }
211 
212             @Override
213             public void onEstimateStart(final RobustEstimator<Quadric> estimator) {
214                 if (listener != null) {
215                     listener.onEstimateStart(RANSACQuadricRobustEstimator.this);
216                 }
217             }
218 
219             @Override
220             public void onEstimateEnd(final RobustEstimator<Quadric> estimator) {
221                 if (listener != null) {
222                     listener.onEstimateEnd(RANSACQuadricRobustEstimator.this);
223                 }
224             }
225 
226             @Override
227             public void onEstimateNextIteration(final RobustEstimator<Quadric> estimator, final int iteration) {
228                 if (listener != null) {
229                     listener.onEstimateNextIteration(RANSACQuadricRobustEstimator.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(RANSACQuadricRobustEstimator.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.RANSAC;
264     }
265 }