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 }