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.NoIntersectionException;
19 import com.irurueta.geometry.Plane;
20 import com.irurueta.geometry.Point3D;
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 3D point for provided collection of 3D planes using PROSAC
31 * algorithm.
32 */
33 @SuppressWarnings("DuplicatedCode")
34 public class PROSACPoint3DRobustEstimator extends Point3DRobustEstimator {
35
36 /**
37 * Constant defining default threshold to determine whether points are
38 * inliers or not.
39 * Because typical resolution for points is 1 voxel, 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 * Indicates that by default inliers will only be computed but not kept.
52 */
53 public static final boolean DEFAULT_COMPUTE_AND_KEEP_INLIERS = false;
54
55 /**
56 * Indicates that by default residuals will only be computed but not kept.
57 */
58 public static final boolean DEFAULT_COMPUTE_AND_KEEP_RESIDUALS = false;
59
60 /**
61 * Threshold to determine whether planes are inliers or not when testing
62 * possible estimation solutions.
63 * The threshold refers to the amount of error (i.e. distance) a possible
64 * solution has on a sampled plane.
65 */
66 private double threshold;
67
68 /**
69 * Quality scores corresponding to each provided point.
70 * The larger the score value the better the quality of the sample.
71 */
72 private double[] qualityScores;
73
74 /**
75 * Indicates whether inliers must be computed and kept.
76 */
77 private boolean computeAndKeepInliers;
78
79 /**
80 * Indicates whether residuals must be computed and kept.
81 */
82 private boolean computeAndKeepResiduals;
83
84 /**
85 * Constructor.
86 */
87 public PROSACPoint3DRobustEstimator() {
88 super();
89 threshold = DEFAULT_THRESHOLD;
90 computeAndKeepInliers = DEFAULT_COMPUTE_AND_KEEP_INLIERS;
91 computeAndKeepResiduals = DEFAULT_COMPUTE_AND_KEEP_RESIDUALS;
92 }
93
94 /**
95 * Constructor with planes.
96 *
97 * @param planes 3D planes to estimate a 3D point.
98 * @throws IllegalArgumentException if provided list of planes doesn't have
99 * a size greater or equal than MINIMUM_SIZE.
100 */
101 public PROSACPoint3DRobustEstimator(final List<Plane> planes) {
102 super(planes);
103 threshold = DEFAULT_THRESHOLD;
104 computeAndKeepInliers = DEFAULT_COMPUTE_AND_KEEP_INLIERS;
105 computeAndKeepResiduals = DEFAULT_COMPUTE_AND_KEEP_RESIDUALS;
106 }
107
108 /**
109 * Constructor.
110 *
111 * @param listener listener to be notified of events such as when estimation
112 * starts, ends or its progress significantly changes.
113 */
114 public PROSACPoint3DRobustEstimator(final Point3DRobustEstimatorListener listener) {
115 super(listener);
116 threshold = DEFAULT_THRESHOLD;
117 computeAndKeepInliers = DEFAULT_COMPUTE_AND_KEEP_INLIERS;
118 computeAndKeepResiduals = DEFAULT_COMPUTE_AND_KEEP_RESIDUALS;
119 }
120
121
122 /**
123 * Constructor.
124 *
125 * @param listener listener to be notified of events such as when estimation
126 * starts, ends or its progress significantly changes.
127 * @param planes 3D planes to estimate a 3D point.
128 * @throws IllegalArgumentException if provided list of planes doesn't have
129 * a size greater or equal than MINIMUM_SIZE.
130 */
131 public PROSACPoint3DRobustEstimator(final Point3DRobustEstimatorListener listener, final List<Plane> planes) {
132 super(listener, planes);
133 threshold = DEFAULT_THRESHOLD;
134 computeAndKeepInliers = DEFAULT_COMPUTE_AND_KEEP_INLIERS;
135 computeAndKeepResiduals = DEFAULT_COMPUTE_AND_KEEP_RESIDUALS;
136 }
137
138 /**
139 * Constructor.
140 *
141 * @param qualityScores quality scores corresponding to each provided plane.
142 * @throws IllegalArgumentException if provided quality scores length is
143 * smaller than MINIMUM_SIZE (i.e. 2 planes).
144 */
145 public PROSACPoint3DRobustEstimator(final double[] qualityScores) {
146 super();
147 threshold = DEFAULT_THRESHOLD;
148 internalSetQualityScores(qualityScores);
149 computeAndKeepInliers = DEFAULT_COMPUTE_AND_KEEP_INLIERS;
150 computeAndKeepResiduals = DEFAULT_COMPUTE_AND_KEEP_RESIDUALS;
151 }
152
153 /**
154 * Constructor with planes.
155 *
156 * @param planes 3D planes to estimate a 3D point.
157 * @param qualityScores quality scores corresponding to each provided plane.
158 * @throws IllegalArgumentException if provided list of planes doesn't have
159 * the same size as the list of provided quality scores, or it their size
160 * is not greater or equal than MINIMUM_SIZE.
161 */
162 public PROSACPoint3DRobustEstimator(final List<Plane> planes, final double[] qualityScores) {
163 super(planes);
164
165 if (qualityScores.length != planes.size()) {
166 throw new IllegalArgumentException();
167 }
168
169 threshold = DEFAULT_THRESHOLD;
170 internalSetQualityScores(qualityScores);
171 computeAndKeepInliers = DEFAULT_COMPUTE_AND_KEEP_INLIERS;
172 computeAndKeepResiduals = DEFAULT_COMPUTE_AND_KEEP_RESIDUALS;
173 }
174
175 /**
176 * Constructor.
177 *
178 * @param listener listener to be notified of events such as when estimation
179 * starts, ends or its progress significantly changes.
180 * @param qualityScores quality scores corresponding to each provided plane.
181 * @throws IllegalArgumentException if provided quality scores length is
182 * smaller than MINIMUM_SIZE (i.e. 3 planes).
183 */
184 public PROSACPoint3DRobustEstimator(final Point3DRobustEstimatorListener listener, final double[] qualityScores) {
185 super(listener);
186 threshold = DEFAULT_THRESHOLD;
187 internalSetQualityScores(qualityScores);
188 computeAndKeepInliers = DEFAULT_COMPUTE_AND_KEEP_INLIERS;
189 computeAndKeepResiduals = DEFAULT_COMPUTE_AND_KEEP_RESIDUALS;
190 }
191
192
193 /**
194 * Constructor.
195 *
196 * @param listener listener to be notified of events such as when estimation
197 * starts, ends or its progress significantly changes.
198 * @param planes 3D planes to estimate a 3D point.
199 * @param qualityScores quality scores corresponding to each provided plane.
200 * @throws IllegalArgumentException if provided list of planes doesn't have
201 * the same size as the list of provided quality scores, or it their size
202 * is not greater or equal than MINIMUM_SIZE.
203 */
204 public PROSACPoint3DRobustEstimator(
205 final Point3DRobustEstimatorListener listener, final List<Plane> planes, final double[] qualityScores) {
206 super(listener, planes);
207
208 if (qualityScores.length != planes.size()) {
209 throw new IllegalArgumentException();
210 }
211
212 threshold = DEFAULT_THRESHOLD;
213 internalSetQualityScores(qualityScores);
214 computeAndKeepInliers = DEFAULT_COMPUTE_AND_KEEP_INLIERS;
215 computeAndKeepResiduals = DEFAULT_COMPUTE_AND_KEEP_RESIDUALS;
216 }
217
218 /**
219 * Returns threshold to determine whether planes are inliers or not when
220 * testing possible estimation solutions.
221 * The threshold refers to the amount of error a possible solution has on a
222 * given plane.
223 *
224 * @return threshold to determine whether planes are inliers or not when
225 * testing possible estimation solutions.
226 */
227 public double getThreshold() {
228 return threshold;
229 }
230
231 /**
232 * Sets threshold to determine whether planes are inliers or not when
233 * testing possible estimation solutions.
234 * The threshold refers to the amount of error a possible solution has on
235 * a given plane.
236 *
237 * @param threshold threshold to be set.
238 * @throws IllegalArgumentException if provided value is equal or less than
239 * zero.
240 * @throws LockedException if robust estimator is locked because an
241 * estimation is already in progress.
242 */
243 public void setThreshold(final double threshold) throws LockedException {
244 if (isLocked()) {
245 throw new LockedException();
246 }
247 if (threshold <= MIN_THRESHOLD) {
248 throw new IllegalArgumentException();
249 }
250 this.threshold = threshold;
251 }
252
253 /**
254 * Returns quality scores corresponding to each provided plane.
255 * The larger the score value the better the quality of the sampled plane.
256 *
257 * @return quality scores corresponding to each plane.
258 */
259 @Override
260 public double[] getQualityScores() {
261 return qualityScores;
262 }
263
264 /**
265 * Sets quality scores corresponding to each provided plane.
266 * The larger the score value the better the quality of the sampled plane.
267 *
268 * @param qualityScores quality scores corresponding to each plane.
269 * @throws LockedException if robust estimator is locked because an
270 * estimation is already in progress.
271 * @throws IllegalArgumentException if provided quality scores length is
272 * smaller than MINIMUM_SIZE (i.e. 23 samples).
273 */
274 @Override
275 public void setQualityScores(final double[] qualityScores) throws LockedException {
276 if (isLocked()) {
277 throw new LockedException();
278 }
279 internalSetQualityScores(qualityScores);
280 }
281
282 /**
283 * Indicates if estimator is ready to start the 3D point estimation.
284 * This is true when input data (i.e. 3D planes and quality scores) are
285 * provided and a minimum of MINIMUM_SIZE points are available.
286 *
287 * @return true if estimator is ready, false otherwise.
288 */
289 @Override
290 public boolean isReady() {
291 return super.isReady() && qualityScores != null && qualityScores.length == planes.size();
292 }
293
294 /**
295 * Indicates whether inliers must be computed and kept.
296 *
297 * @return true if inliers must be computed and kept, false if inliers
298 * only need to be computed but not kept.
299 */
300 public boolean isComputeAndKeepInliersEnabled() {
301 return computeAndKeepInliers;
302 }
303
304 /**
305 * Specifies whether inliers must be computed and kept.
306 *
307 * @param computeAndKeepInliers true if inliers must be computed and kept,
308 * false if inliers only need to be computed but not kept.
309 * @throws LockedException if estimator is locked.
310 */
311 public void setComputeAndKeepInliersEnabled(final boolean computeAndKeepInliers) throws LockedException {
312 if (isLocked()) {
313 throw new LockedException();
314 }
315 this.computeAndKeepInliers = computeAndKeepInliers;
316 }
317
318 /**
319 * Indicates whether residuals must be computed and kept.
320 *
321 * @return true if residuals must be computed and kept, false if residuals
322 * only need to be computed but not kept.
323 */
324 public boolean isComputeAndKeepResidualsEnabled() {
325 return computeAndKeepResiduals;
326 }
327
328 /**
329 * Specifies whether residuals must be computed and kept.
330 *
331 * @param computeAndKeepResiduals true if residuals must be computed and
332 * kept, false if residuals only need to be computed but not kept.
333 * @throws LockedException if estimator is locked.
334 */
335 public void setComputeAndKeepResidualsEnabled(final boolean computeAndKeepResiduals) throws LockedException {
336 if (isLocked()) {
337 throw new LockedException();
338 }
339 this.computeAndKeepResiduals = computeAndKeepResiduals;
340 }
341
342 /**
343 * Estimates a 3D point using a robust estimator and the best set of 3D
344 * planes that intersect into the estimated 3D point.
345 *
346 * @return a 3D point.
347 * @throws LockedException if robust estimator is locked because an
348 * estimation is already in progress.
349 * @throws NotReadyException if provided input data is not enough to start
350 * the estimation.
351 * @throws RobustEstimatorException if estimation fails for any reason
352 * (i.e. numerical instability, no solution available, etc).
353 */
354 @Override
355 public Point3D estimate() throws LockedException, NotReadyException, RobustEstimatorException {
356 if (isLocked()) {
357 throw new LockedException();
358 }
359 if (!isReady()) {
360 throw new NotReadyException();
361 }
362
363 final var innerEstimator = new PROSACRobustEstimator<>(new PROSACRobustEstimatorListener<Point3D>() {
364
365 @Override
366 public double getThreshold() {
367 return threshold;
368 }
369
370 @Override
371 public int getTotalSamples() {
372 return planes.size();
373 }
374
375 @Override
376 public int getSubsetSize() {
377 return Point3DRobustEstimator.MINIMUM_SIZE;
378 }
379
380 @Override
381 public void estimatePreliminarSolutions(final int[] samplesIndices, final List<Point3D> solutions) {
382 final var plane1 = planes.get(samplesIndices[0]);
383 final var plane2 = planes.get(samplesIndices[1]);
384 final var plane3 = planes.get(samplesIndices[2]);
385
386 try {
387 final var point = plane1.getIntersection(plane2, plane3);
388 solutions.add(point);
389 } catch (final NoIntersectionException e) {
390 // if points are coincident, no solution is added
391 }
392 }
393
394 @Override
395 public double computeResidual(final Point3D currentEstimation, final int i) {
396 return residual(currentEstimation, planes.get(i));
397 }
398
399 @Override
400 public boolean isReady() {
401 return PROSACPoint3DRobustEstimator.this.isReady();
402 }
403
404 @Override
405 public void onEstimateStart(final RobustEstimator<Point3D> estimator) {
406 if (listener != null) {
407 listener.onEstimateStart(PROSACPoint3DRobustEstimator.this);
408 }
409 }
410
411 @Override
412 public void onEstimateEnd(final RobustEstimator<Point3D> estimator) {
413 if (listener != null) {
414 listener.onEstimateEnd(PROSACPoint3DRobustEstimator.this);
415 }
416 }
417
418 @Override
419 public void onEstimateNextIteration(final RobustEstimator<Point3D> estimator, final int iteration) {
420 if (listener != null) {
421 listener.onEstimateNextIteration(PROSACPoint3DRobustEstimator.this, iteration);
422 }
423 }
424
425 @Override
426 public void onEstimateProgressChange(final RobustEstimator<Point3D> estimator, final float progress) {
427 if (listener != null) {
428 listener.onEstimateProgressChange(PROSACPoint3DRobustEstimator.this, progress);
429 }
430 }
431
432 @Override
433 public double[] getQualityScores() {
434 return qualityScores;
435 }
436 });
437
438 try {
439 locked = true;
440 inliersData = null;
441 innerEstimator.setComputeAndKeepInliersEnabled(computeAndKeepInliers || refineResult);
442 innerEstimator.setComputeAndKeepResidualsEnabled(computeAndKeepResiduals || refineResult);
443 innerEstimator.setConfidence(confidence);
444 innerEstimator.setMaxIterations(maxIterations);
445 innerEstimator.setProgressDelta(progressDelta);
446 final var result = innerEstimator.estimate();
447 inliersData = innerEstimator.getInliersData();
448 return attemptRefine(result);
449 } catch (final com.irurueta.numerical.LockedException e) {
450 throw new LockedException(e);
451 } catch (final com.irurueta.numerical.NotReadyException e) {
452 throw new NotReadyException(e);
453 } finally {
454 locked = false;
455 }
456 }
457
458 /**
459 * Returns method being used for robust estimation.
460 *
461 * @return method being used for robust estimation.
462 */
463 @Override
464 public RobustEstimatorMethod getMethod() {
465 return RobustEstimatorMethod.PROSAC;
466 }
467
468 /**
469 * Gets standard deviation used for Levenberg-Marquardt fitting during
470 * refinement.
471 * Returned value gives an indication of how much variance each residual
472 * has.
473 * Typically, this value is related to the threshold used on each robust
474 * estimation, since residuals of found inliers are within the range of
475 * such threshold.
476 *
477 * @return standard deviation used for refinement.
478 */
479 @Override
480 protected double getRefinementStandardDeviation() {
481 return threshold;
482 }
483
484 /**
485 * Sets quality scores corresponding to each provided line.
486 * This method is used internally and does not check whether instance is
487 * locked or not.
488 *
489 * @param qualityScores quality scores to be set.
490 * @throws IllegalArgumentException if provided quality scores length is
491 * smaller than MINIMUM_SIZE.
492 */
493 private void internalSetQualityScores(final double[] qualityScores) {
494 if (qualityScores.length < MINIMUM_SIZE) {
495 throw new IllegalArgumentException();
496 }
497
498 this.qualityScores = qualityScores;
499 }
500 }