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
17 package com.irurueta.geometry.estimators;
18
19 import com.irurueta.geometry.Circle;
20 import com.irurueta.geometry.ColinearPointsException;
21 import com.irurueta.geometry.Point2D;
22 import com.irurueta.numerical.robust.PROSACRobustEstimator;
23 import com.irurueta.numerical.robust.PROSACRobustEstimatorListener;
24 import com.irurueta.numerical.robust.RobustEstimator;
25 import com.irurueta.numerical.robust.RobustEstimatorException;
26 import com.irurueta.numerical.robust.RobustEstimatorMethod;
27
28 import java.util.List;
29
30 /**
31 * Finds the best circle for provided collection of 2D points using PROSAC
32 * algorithm.
33 */
34 @SuppressWarnings("DuplicatedCode")
35 public class PROSACCircleRobustEstimator extends CircleRobustEstimator {
36 /**
37 * Constant defining default threshold to determine whether points are
38 * inliers or not.
39 * Because typical resolution for points is 1 pixel, then default threshold
40 * is defined as 1.
41 */
42 public static final double DEFAULT_THRESHOLD = 1.0;
43
44 /**
45 * Minimum value that can be set as threshold.
46 * Threshold must be strictly greater than 0.0.
47 */
48 public static final double MIN_THRESHOLD = 0.0;
49
50 /**
51 * Threshold to determine whether points are inliers or not when testing
52 * possible estimation solutions.
53 * The threshold refers to the amount of error (i.e. distance) a possible
54 * solution has on a matched pair of points.
55 */
56 private double threshold;
57
58 /**
59 * Quality scores corresponding to each provided point.
60 * The larger the score value the better the quality of the sample.
61 */
62 private double[] qualityScores;
63
64 /**
65 * Constructor.
66 */
67 public PROSACCircleRobustEstimator() {
68 super();
69 threshold = DEFAULT_THRESHOLD;
70 }
71
72 /**
73 * Constructor with points.
74 *
75 * @param points 2D points to estimate a circle.
76 * @throws IllegalArgumentException if provided list of points don't have
77 * a size greater or equal than MINIMUM_SIZE.
78 */
79 public PROSACCircleRobustEstimator(final List<Point2D> points) {
80 super(points);
81 threshold = DEFAULT_THRESHOLD;
82 }
83
84 /**
85 * Constructor.
86 *
87 * @param listener listener to be notified of events such as when estimation
88 * starts, ends or its progress significantly changes.
89 */
90 public PROSACCircleRobustEstimator(final CircleRobustEstimatorListener listener) {
91 super(listener);
92 threshold = DEFAULT_THRESHOLD;
93 }
94
95
96 /**
97 * Constructor.
98 *
99 * @param listener listener to be notified of events such as when estimation
100 * starts, ends or its progress significantly changes.
101 * @param points 2D points to estimate a circle.
102 * @throws IllegalArgumentException if provided list of points don't have
103 * a size greater or equal than MINIMUM_SIZE.
104 */
105 public PROSACCircleRobustEstimator(final CircleRobustEstimatorListener listener, final List<Point2D> points) {
106 super(listener, points);
107 threshold = DEFAULT_THRESHOLD;
108 }
109
110 /**
111 * Constructor.
112 *
113 * @param qualityScores quality scores corresponding to each provided point.
114 * @throws IllegalArgumentException if provided quality scores length is
115 * smaller than MINIMUM_SIZE (i.e. 3 points).
116 */
117 public PROSACCircleRobustEstimator(final double[] qualityScores) {
118 super();
119 threshold = DEFAULT_THRESHOLD;
120 internalSetQualityScores(qualityScores);
121 }
122
123 /**
124 * Constructor with points.
125 *
126 * @param points 2D points to estimate a circle.
127 * @param qualityScores quality scores corresponding to each provided point.
128 * @throws IllegalArgumentException if provided list of points don't have
129 * the same size as the list of provided quality scores, or it their size
130 * is not greater or equal than MINIMUM_SIZE.
131 */
132 public PROSACCircleRobustEstimator(final List<Point2D> points, final double[] qualityScores) {
133 super(points);
134
135 if (qualityScores.length != points.size()) {
136 throw new IllegalArgumentException();
137 }
138
139 threshold = DEFAULT_THRESHOLD;
140 internalSetQualityScores(qualityScores);
141 }
142
143 /**
144 * Constructor.
145 *
146 * @param listener listener to be notified of events such as when estimation
147 * starts, ends or its progress significantly changes.
148 * @param qualityScores quality scores corresponding to each provided point.
149 * @throws IllegalArgumentException if provided quality scores length is
150 * smaller than MINIMUM_SIZE (i.e. 3 points).
151 */
152 public PROSACCircleRobustEstimator(final CircleRobustEstimatorListener listener, final double[] qualityScores) {
153 super(listener);
154 threshold = DEFAULT_THRESHOLD;
155 internalSetQualityScores(qualityScores);
156 }
157
158
159 /**
160 * Constructor.
161 *
162 * @param listener listener to be notified of events such as when estimation
163 * starts, ends or its progress significantly changes.
164 * @param points 2D points to estimate a circle.
165 * @param qualityScores quality scores corresponding to each provided point.
166 * @throws IllegalArgumentException if provided list of points don't have
167 * the same size as the list of provided quality scores, or it their size
168 * is not greater or equal than MINIMUM_SIZE.
169 */
170 public PROSACCircleRobustEstimator(
171 final CircleRobustEstimatorListener listener, final List<Point2D> points, final double[] qualityScores) {
172 super(listener, points);
173
174 if (qualityScores.length != points.size()) {
175 throw new IllegalArgumentException();
176 }
177
178 threshold = DEFAULT_THRESHOLD;
179 internalSetQualityScores(qualityScores);
180 }
181
182 /**
183 * Returns threshold to determine whether points are inliers or not when
184 * testing possible estimation solutions.
185 * The threshold refers to the amount of error a possible solution has on a
186 * given point.
187 *
188 * @return threshold to determine whether points are inliers or not when
189 * testing possible estimation solutions.
190 */
191 public double getThreshold() {
192 return threshold;
193 }
194
195 /**
196 * Sets threshold to determine whether points are inliers or not when
197 * testing possible estimation solutions.
198 * The threshold refers to the amount of error a possible solution has on
199 * a given point.
200 *
201 * @param threshold threshold to be set.
202 * @throws IllegalArgumentException if provided value is equal or less than
203 * zero.
204 * @throws LockedException if robust estimator is locked because an
205 * estimation is already in progress.
206 */
207 public void setThreshold(final double threshold) throws LockedException {
208 if (isLocked()) {
209 throw new LockedException();
210 }
211 if (threshold <= MIN_THRESHOLD) {
212 throw new IllegalArgumentException();
213 }
214 this.threshold = threshold;
215 }
216
217 /**
218 * Returns quality scores corresponding to each provided point.
219 * The larger the score value the better the quality of the sampled point.
220 *
221 * @return quality scores corresponding to each point.
222 */
223 @Override
224 public double[] getQualityScores() {
225 return qualityScores;
226 }
227
228 /**
229 * Sets quality scores corresponding to each provided point.
230 * The larger the score value the better the quality of the sampled point.
231 *
232 * @param qualityScores quality scores corresponding to each point.
233 * @throws LockedException if robust estimator is locked because an
234 * estimation is already in progress.
235 * @throws IllegalArgumentException if provided quality scores length is
236 * smaller than MINIMUM_SIZE (i.e. 3 samples).
237 */
238 @Override
239 public void setQualityScores(final double[] qualityScores) throws LockedException {
240 if (isLocked()) {
241 throw new LockedException();
242 }
243 internalSetQualityScores(qualityScores);
244 }
245
246 /**
247 * Indicates if estimator is ready to start the conic estimation.
248 * This is true when input data (i.e. 2D points and quality scores) are
249 * provided and a minimum of MINIMUM_SIZE points are available.
250 *
251 * @return true if estimator is ready, false otherwise.
252 */
253 @Override
254 public boolean isReady() {
255 return super.isReady() && qualityScores != null && qualityScores.length == points.size();
256 }
257
258 /**
259 * Estimates a circle using a robust estimator and the best set of 2D points
260 * that fit into the locus of the estimated circle found using the robust
261 * estimator.
262 *
263 * @return a circle.
264 * @throws LockedException if robust estimator is locked because an
265 * estimation is already in progress.
266 * @throws NotReadyException if provided input data is not enough to start
267 * the estimation.
268 * @throws RobustEstimatorException if estimation fails for any reason
269 * (i.e. numerical instability, no solution available, etc).
270 */
271 @Override
272 public Circle estimate() throws LockedException, NotReadyException, RobustEstimatorException {
273 if (isLocked()) {
274 throw new LockedException();
275 }
276 if (!isReady()) {
277 throw new NotReadyException();
278 }
279
280 final var innerEstimator = new PROSACRobustEstimator<>(new PROSACRobustEstimatorListener<Circle>() {
281
282 @Override
283 public double getThreshold() {
284 return threshold;
285 }
286
287 @Override
288 public int getTotalSamples() {
289 return points.size();
290 }
291
292 @Override
293 public int getSubsetSize() {
294 return CircleRobustEstimator.MINIMUM_SIZE;
295 }
296
297 @Override
298 public void estimatePreliminarSolutions(final int[] samplesIndices, final List<Circle> solutions) {
299 final var point1 = points.get(samplesIndices[0]);
300 final var point2 = points.get(samplesIndices[1]);
301 final var point3 = points.get(samplesIndices[2]);
302
303 try {
304 final var circle = new Circle(point1, point2, point3);
305 solutions.add(circle);
306 } catch (final ColinearPointsException e) {
307 // if points are coincident, no solution is added
308 }
309 }
310
311 @Override
312 public double computeResidual(final Circle currentEstimation, final int i) {
313 return residual(currentEstimation, points.get(i));
314 }
315
316 @Override
317 public boolean isReady() {
318 return PROSACCircleRobustEstimator.this.isReady();
319 }
320
321 @Override
322 public void onEstimateStart(final RobustEstimator<Circle> estimator) {
323 if (listener != null) {
324 listener.onEstimateStart(PROSACCircleRobustEstimator.this);
325 }
326 }
327
328 @Override
329 public void onEstimateEnd(final RobustEstimator<Circle> estimator) {
330 if (listener != null) {
331 listener.onEstimateEnd(PROSACCircleRobustEstimator.this);
332 }
333 }
334
335 @Override
336 public void onEstimateNextIteration(final RobustEstimator<Circle> estimator, final int iteration) {
337 if (listener != null) {
338 listener.onEstimateNextIteration(PROSACCircleRobustEstimator.this, iteration);
339 }
340 }
341
342 @Override
343 public void onEstimateProgressChange(final RobustEstimator<Circle> estimator, final float progress) {
344 if (listener != null) {
345 listener.onEstimateProgressChange(PROSACCircleRobustEstimator.this, progress);
346 }
347 }
348
349 @Override
350 public double[] getQualityScores() {
351 return qualityScores;
352 }
353 });
354
355 try {
356 locked = true;
357 innerEstimator.setConfidence(confidence);
358 innerEstimator.setMaxIterations(maxIterations);
359 innerEstimator.setProgressDelta(progressDelta);
360 return innerEstimator.estimate();
361 } catch (final com.irurueta.numerical.LockedException e) {
362 throw new LockedException(e);
363 } catch (final com.irurueta.numerical.NotReadyException e) {
364 throw new NotReadyException(e);
365 } finally {
366 locked = false;
367 }
368 }
369
370 /**
371 * Returns method being used for robust estimation.
372 *
373 * @return method being used for robust estimation.
374 */
375 @Override
376 public RobustEstimatorMethod getMethod() {
377 return RobustEstimatorMethod.PROSAC;
378 }
379
380 /**
381 * Sets quality scores corresponding to each provided point.
382 * This method is used internally and does not check whether instance is
383 * locked or not.
384 *
385 * @param qualityScores quality scores to be set.
386 * @throws IllegalArgumentException if provided quality scores length is
387 * smaller than MINIMUM_SIZE.
388 */
389 private void internalSetQualityScores(final double[] qualityScores) {
390 if (qualityScores.length < MINIMUM_SIZE) {
391 throw new IllegalArgumentException();
392 }
393
394 this.qualityScores = qualityScores;
395 }
396 }