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