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