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 }