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.Conic;
20 import com.irurueta.geometry.Point2D;
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 conic for provided collection of 2D points using MSAC
31 * algorithm.
32 */
33 @SuppressWarnings("DuplicatedCode")
34 public class MSACConicRobustEstimator extends ConicRobustEstimator {
35
36 /**
37 * Constant defining default threshold to determine whether points are
38 * inliers or not.
39 * Threshold is defined by the equation abs(trans(x) * C * x) < t, where trans
40 * is the transposition, x i a point and C is a conic and t is a threshold.
41 * This equation determines the points x belonging to the locus of a conic
42 * C 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 MSACConicRobustEstimator() {
64 super();
65 threshold = DEFAULT_THRESHOLD;
66 }
67
68 /**
69 * Constructor with points.
70 *
71 * @param points 2D points to estimate a conic.
72 * @throws IllegalArgumentException if provided list of points don't have
73 * a size greater or equal than MINIMUM_SIZE.
74 */
75 public MSACConicRobustEstimator(final List<Point2D> 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 MSACConicRobustEstimator(final ConicRobustEstimatorListener listener) {
87 super(listener);
88 threshold = DEFAULT_THRESHOLD;
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 2D points to estimate a conic.
97 * @throws IllegalArgumentException if provided list of points don't have a
98 * size greater or equal than MINIMUM_SIZE.
99 */
100 public MSACConicRobustEstimator(final ConicRobustEstimatorListener listener, final List<Point2D> 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 * Estimates a conic using a robust estimator and the best set of 2D points
142 * that fit into the locus of the estimated conic found using the robust
143 * estimator.
144 *
145 * @return a conic.
146 * @throws LockedException if robust estimator is locked because an
147 * estimation is already in progress.
148 * @throws NotReadyException if provided input data is not enough to start
149 * the estimation.
150 * @throws RobustEstimatorException if estimation fails for any reason
151 * (i.e. numerical instability, no solution available, etc).
152 */
153 @Override
154 public Conic estimate() throws LockedException, NotReadyException, RobustEstimatorException {
155 if (isLocked()) {
156 throw new LockedException();
157 }
158 if (!isReady()) {
159 throw new NotReadyException();
160 }
161
162 final var innerEstimator = new MSACRobustEstimator<>(new MSACRobustEstimatorListener<Conic>() {
163
164 @Override
165 public double getThreshold() {
166 return threshold;
167 }
168
169 @Override
170 public int getTotalSamples() {
171 return points.size();
172 }
173
174 @Override
175 public int getSubsetSize() {
176 return ConicRobustEstimator.MINIMUM_SIZE;
177 }
178
179 @Override
180 public void estimatePreliminarSolutions(final int[] samplesIndices, final List<Conic> solutions) {
181 final var point1 = points.get(samplesIndices[0]);
182 final var point2 = points.get(samplesIndices[1]);
183 final var point3 = points.get(samplesIndices[2]);
184 final var point4 = points.get(samplesIndices[3]);
185 final var point5 = points.get(samplesIndices[4]);
186
187 try {
188 final var conic = new Conic(point1, point2, point3, point4, point5);
189 solutions.add(conic);
190 } catch (final CoincidentPointsException e) {
191 // if points are coincident, no solution is added
192 }
193 }
194
195 @Override
196 public double computeResidual(final Conic currentEstimation, final int i) {
197 return residual(currentEstimation, points.get(i));
198 }
199
200 @Override
201 public boolean isReady() {
202 return MSACConicRobustEstimator.this.isReady();
203 }
204
205 @Override
206 public void onEstimateStart(final RobustEstimator<Conic> estimator) {
207 if (listener != null) {
208 listener.onEstimateStart(MSACConicRobustEstimator.this);
209 }
210 }
211
212 @Override
213 public void onEstimateEnd(final RobustEstimator<Conic> estimator) {
214 if (listener != null) {
215 listener.onEstimateEnd(MSACConicRobustEstimator.this);
216 }
217 }
218
219 @Override
220 public void onEstimateNextIteration(final RobustEstimator<Conic> estimator, final int iteration) {
221 if (listener != null) {
222 listener.onEstimateNextIteration(MSACConicRobustEstimator.this, iteration);
223 }
224 }
225
226 @Override
227 public void onEstimateProgressChange(final RobustEstimator<Conic> estimator, final float progress) {
228 if (listener != null) {
229 listener.onEstimateProgressChange(MSACConicRobustEstimator.this, progress);
230 }
231 }
232 });
233
234 try {
235 locked = true;
236 innerEstimator.setConfidence(confidence);
237 innerEstimator.setMaxIterations(maxIterations);
238 innerEstimator.setProgressDelta(progressDelta);
239 return innerEstimator.estimate();
240 } catch (final com.irurueta.numerical.LockedException e) {
241 throw new LockedException(e);
242 } catch (final com.irurueta.numerical.NotReadyException e) {
243 throw new NotReadyException(e);
244 } finally {
245 locked = false;
246 }
247 }
248
249 /**
250 * Returns method being used for robust estimation.
251 *
252 * @return method being used for robust estimation.
253 */
254 @Override
255 public RobustEstimatorMethod getMethod() {
256 return RobustEstimatorMethod.MSAC;
257 }
258 }