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.numerical.robust;
17
18 import com.irurueta.numerical.LockedException;
19 import com.irurueta.numerical.NotReadyException;
20 import com.irurueta.sorting.Sorter;
21 import com.irurueta.sorting.SortingException;
22
23 import java.util.ArrayList;
24 import java.util.Arrays;
25 import java.util.BitSet;
26
27 /**
28 * This class implements PROSAC (PROgressive random SAmple Consensus) algorithm
29 * to robustly estimate a data model.
30 * This algorithm is an improvement over RANSAC that can be used whenever a
31 * certain measure of quality is known for each sample of data.
32 * The measure of quality does not need to be precise, only needs to give a
33 * certain idea whether a given sample is likely to be better than another one
34 * (by having a higher quality score).
35 * Whenever RANSAC is being used but quality of measures is known, PROSAC should
36 * be used instead, since this algorithm offers a result with a comparable
37 * precision to that obtained with RANSAC but having a much smaller
38 * computational cost.
39 * The large improvement in computational cost is achieved thanks to the fact
40 * that by taking into account the quality measures, sub-samples can be
41 * prioritized so that more likely to be inliers are picked first.
42 * <p>
43 * This implementation is based on:
44 * <a href="http://devernay.free.fr/vision/src/prosac.c">http://devernay.free.fr/vision/src/prosac.c</a>
45 *
46 * @param <T> type of object to be estimated.
47 */
48 @SuppressWarnings("Duplicates")
49 public class PROSACRobustEstimator<T> extends RobustEstimator<T> {
50
51 /**
52 * Constant defining default confidence of the estimated result, which is
53 * 99%. This means that with a probability of 99% estimation will be
54 * accurate because chosen sub-samples will be inliers.
55 */
56 public static final double DEFAULT_CONFIDENCE = 0.99;
57
58 /**
59 * Default maximum allowed number of iterations.
60 */
61 public static final int DEFAULT_MAX_ITERATIONS = 5000;
62
63 /**
64 * Minimum allowed confidence value.
65 */
66 public static final double MIN_CONFIDENCE = 0.0;
67
68 /**
69 * Maximum allowed confidence value.
70 */
71 public static final double MAX_CONFIDENCE = 1.0;
72
73 /**
74 * Minimum allowed number of iterations.
75 */
76 public static final int MIN_ITERATIONS = 1;
77
78 /**
79 * Minimum allowed threshold to determine inliers.
80 */
81 public static final double MIN_THRESHOLD = 0.0;
82
83 /**
84 * Default maximum allowed outliers proportion in the input data. This is
85 * used do determine the number of required iterations.
86 */
87 public static final double DEFAULT_MAX_OUTLIERS_PROPORTION = 0.8;
88
89 /**
90 * Minimum allowed value for maximum allowed outliers proportion in the
91 * input data.
92 */
93 public static final double MIN_MAX_OUTLIERS_PROPORTION = 0.0;
94
95 /**
96 * Maximum allowed value for maximum allowed outliers proportion in the
97 * input data
98 */
99 public static final double MAX_MAX_OUTLIERS_PROPORTION = 1.0;
100
101 /**
102 * Defines the default value for the maximum probability that a solution
103 * with more than inliersNStar in U_nStar exist and was not found after k
104 * samples.
105 */
106 public static final double DEFAULT_ETA0 = 0.05;
107
108 /**
109 * Minimum allowed value for eta0.
110 */
111 public static final double MIN_ETA0 = 0.0;
112
113 /**
114 * Maximum allowed value for eta0.
115 */
116 public static final double MAX_ETA0 = 1.0;
117
118 /**
119 * Defines the default value for beta, which is the probability that a
120 * match is declared inlier by mistake, i.e. the ratio of the "inlier"
121 * surface by the total surface. The inlier surface is a disc with radius
122 * 1.96s for homography/displacement computation, or a band with width
123 * 1.96*s*2 for epipolar geometry (s is the detection noise), and the total
124 * surface is the surface of the image.
125 */
126 public static final double DEFAULT_BETA = 0.01;
127
128 /**
129 * Minimum allowed value for beta.
130 */
131 public static final double MIN_BETA = 0.0;
132
133 /**
134 * Maximum allowed value for beta.
135 */
136 public static final double MAX_BETA = 1.0;
137
138 /**
139 * Chi squared.
140 */
141 public static final double CHI_SQUARED = 2.706;
142
143 /**
144 * Indicates that by default inliers will only be computed but not kept.
145 */
146 public static final boolean DEFAULT_COMPUTE_AND_KEEP_INLIERS = false;
147
148 /**
149 * Indicates that by default residuals will only be computed but not kept.
150 */
151 public static final boolean DEFAULT_COMPUTE_AND_KEEP_RESIDUALS = false;
152
153 /**
154 * Amount of confidence expressed as a value between 0 and 1.0 (which is
155 * equivalent to 100%). The amount of confidence indicates the probability
156 * that the estimated result is correct. Usually this value will be close
157 * to 1.0, but not exactly 1.0.
158 */
159 private double confidence;
160
161 /**
162 * Maximum allowed number of iterations. When the maximum number of
163 * iterations is exceeded, result will not be available, however an
164 * approximate result will be available for retrieval.
165 */
166 private int maxIterations;
167
168 /**
169 * In this implementation, PROSAC won't stop before having reached the
170 * corresponding inliers rate on the complete data set.
171 * Maximum allowed outliers proportion in the input data: used to compute
172 * nIters (can be as high as 0.95).
173 */
174 private double maxOutliersProportion;
175
176 /**
177 * eta0 is the maximum probability that a solution with more than
178 * inliersNStar inliers in U_nStar exists and was not found after k
179 * samples (typically set to 5%).
180 */
181 private double eta0;
182
183 /**
184 * beta is the probability that a match is declared inlier by mistake,
185 * i.e. the ratio of the "inlier" surface by the total surface. The
186 * inlier surface is a disc with radius 1.96s for homography/displacement
187 * computation, or a band with width 1.96s*2 for epipolar geometry (s is
188 * the detection noise), and the total surface is the surface of the image
189 * YOU MUST ADJUST THIS VALUE, DEPENDING ON YOUR PROBLEM!
190 */
191 private double beta;
192
193 /**
194 * Instance in charge of picking random subsets of samples.
195 */
196 private SubsetSelector subsetSelector;
197
198 /**
199 * Number of iterations to be done to obtain required confidence.
200 */
201 private int nIters;
202
203 /**
204 * Best solution that has been found so far during an estimation.
205 */
206 private T bestResult;
207
208 /**
209 * Data related to inliers found for best result.
210 */
211 private PROSACInliersData bestInliersData;
212
213 /**
214 * Indicates whether inliers must be computed and kept.
215 */
216 private boolean computeAndKeepInliers;
217
218 /**
219 * Indicates whether residuals must be computed and kept.
220 */
221 private boolean computeAndKeepResiduals;
222
223
224 /**
225 * Constructor.
226 */
227 public PROSACRobustEstimator() {
228 super();
229 confidence = DEFAULT_CONFIDENCE;
230 maxIterations = DEFAULT_MAX_ITERATIONS;
231 maxOutliersProportion = DEFAULT_MAX_OUTLIERS_PROPORTION;
232 eta0 = DEFAULT_ETA0;
233 beta = DEFAULT_BETA;
234 nIters = maxIterations;
235 bestResult = null;
236 bestInliersData = null;
237 computeAndKeepInliers = DEFAULT_COMPUTE_AND_KEEP_INLIERS;
238 computeAndKeepResiduals = DEFAULT_COMPUTE_AND_KEEP_RESIDUALS;
239 }
240
241 /**
242 * Constructor with listener.
243 *
244 * @param listener listener to be notified of events such as when estimation
245 * starts, ends or its progress significantly changes, as well as in charge
246 * of picking samples and doing per-iteration estimations.
247 */
248 public PROSACRobustEstimator(final PROSACRobustEstimatorListener<T> listener) {
249 super(listener);
250 confidence = DEFAULT_CONFIDENCE;
251 maxIterations = DEFAULT_MAX_ITERATIONS;
252 maxOutliersProportion = DEFAULT_MAX_OUTLIERS_PROPORTION;
253 eta0 = DEFAULT_ETA0;
254 beta = DEFAULT_BETA;
255 nIters = maxIterations;
256 bestResult = null;
257 bestInliersData = null;
258 computeAndKeepInliers = DEFAULT_COMPUTE_AND_KEEP_INLIERS;
259 computeAndKeepResiduals = DEFAULT_COMPUTE_AND_KEEP_RESIDUALS;
260 }
261
262 /**
263 * Returns amount of confidence expressed as a value between 0 and 1.0
264 * (which is equivalent to 100%). The amount of confidence indicates the
265 * probability that the estimated result is correct. Usually this value will
266 * be close to 1.0, but not exactly 1.0.
267 *
268 * @return amount of confidence as a value between 0.0 and 1.0.
269 */
270 public double getConfidence() {
271 return confidence;
272 }
273
274 /**
275 * Sets amount of confidence expressed as a value between 0 and 1.0 (which
276 * is equivalent to 100%). The amount of confidence indicates the
277 * probability that the estimated result is correct. Usually this value will
278 * be close to 1.0, but not exactly 1.0.
279 *
280 * @param confidence confidence to be set as a value between 0.0 and 1.0.
281 * @throws IllegalArgumentException if provided value is not between 0.0 and
282 * 1.0.
283 * @throws LockedException if this estimator is locked because an estimation
284 * is being computed.
285 */
286 public void setConfidence(final double confidence) throws LockedException {
287 if (isLocked()) {
288 throw new LockedException();
289 }
290 if (confidence < MIN_CONFIDENCE || confidence > MAX_CONFIDENCE) {
291 throw new IllegalArgumentException();
292 }
293 this.confidence = confidence;
294 }
295
296 /**
297 * Maximum allowed number of iterations. When the maximum number of
298 * iterations is exceeded, result will not be available, however an
299 * approximate result will be available for retrieval.
300 *
301 * @return maximum allowed number of iterations.
302 */
303 public int getMaxIterations() {
304 return maxIterations;
305 }
306
307 /**
308 * Sets maximum allowed number of iterations. When the maximum number of
309 * iterations is exceeded, result will not be available, however an
310 * approximate result will be available for retrieval.
311 *
312 * @param maxIterations maximum allowed number of iterations to be set.
313 * @throws IllegalArgumentException if provided value is less than 1.
314 * @throws LockedException if this estimator is locked because an estimation
315 * is being computed.
316 */
317 public void setMaxIterations(final int maxIterations) throws LockedException {
318 if (isLocked()) {
319 throw new LockedException();
320 }
321 if (maxIterations < MIN_ITERATIONS) {
322 throw new IllegalArgumentException();
323 }
324 this.maxIterations = maxIterations;
325 }
326
327 /**
328 * Returns maximum allowed outliers proportion in the input data. This is
329 * used to compute number of iterations to be done (nIters). It typically
330 * can be as high as 0.95. Higher values, up to 1 are possible but not
331 * recommended.
332 * In this implementation, PROSAC won't stop before having reached the
333 * corresponding inliers rate on the complete data set.
334 *
335 * @return maximum allowed outliers proportion in the input data.
336 */
337 public double getMaxOutliersProportion() {
338 return maxOutliersProportion;
339 }
340
341 /**
342 * Sets maximum allowed outliers proportion in the input data. This is used
343 * to compute number of iterations to be done (nIters). It typically can be
344 * as high as 0.95. Higher values, up to 1 are possible but not recommended.
345 * In this implementation, PROSAC won't stop before having reached the
346 * corresponding inliers rate on the complete data set.
347 *
348 * @param maxOutliersProportion maximum allowed outliers proportion in the
349 * input data.
350 * @throws IllegalArgumentException if provided value is less than 0.0 or
351 * greater than 1.0.
352 * @throws LockedException if this estimator is locked because an estimation
353 * is being computed.
354 */
355 public void setMaxOutliersProportion(final double maxOutliersProportion) throws LockedException {
356 if (isLocked()) {
357 throw new LockedException();
358 }
359 if (maxOutliersProportion < MIN_MAX_OUTLIERS_PROPORTION
360 || maxOutliersProportion > MAX_MAX_OUTLIERS_PROPORTION) {
361 throw new IllegalArgumentException();
362 }
363
364 this.maxOutliersProportion = maxOutliersProportion;
365 }
366
367 /**
368 * Return eta0, which is the maximum probability that a solution with more
369 * than inliersNStar inliers in U_nStar exists and was not found after k
370 * samples (typically set to 5%).
371 *
372 * @return eta0 value.
373 */
374 public double getEta0() {
375 return eta0;
376 }
377
378 /**
379 * Sets eta0, which is the maximum probability that a solution with more
380 * than inliersNStar inliers in U_nStar exists and was not found after k
381 * samples (typically set to 5%).
382 *
383 * @param eta0 eta0 value to be set.
384 * @throws IllegalArgumentException if provided value is less than 0.0 or
385 * greater than 1.0.
386 * @throws LockedException if this estimator is locked because an estimation
387 * is being computed.
388 */
389 public void setEta0(final double eta0) throws LockedException {
390 if (isLocked()) {
391 throw new LockedException();
392 }
393 if (eta0 < MIN_ETA0 || eta0 > MAX_ETA0) {
394 throw new IllegalArgumentException();
395 }
396
397 this.eta0 = eta0;
398 }
399
400 /**
401 * Returns beta, which is the probability that a match is declared inlier by
402 * mistake, i.e. the ratio of the "inlier" surface by the total surface. The
403 * inlier surface is a disc with radius 1.96s for homography/displacement
404 * computation, or a band with width 1.96s*2 for epipolar geometry (s is
405 * the detection noise), and the total surface is the surface of the image
406 * YOU MUST ADJUST THIS VALUE, DEPENDING ON YOUR PROBLEM!
407 *
408 * @return beta value.
409 */
410 public double getBeta() {
411 return beta;
412 }
413
414 /**
415 * Sets beta, which is the probability that a match is declared inlier by
416 * mistake, i.e. the ratio of the "inlier" surface by the total surface. The
417 * inlier surface is a disc with radius 1.96s for homography/displacement
418 * computation, or a band with width 1.96s*2 for epipolar geometry (s is
419 * the detection noise), and the total surface is the surface of the image
420 * YOU MUST ADJUST THIS VALUE, DEPENDING ON YOUR PROBLEM!
421 *
422 * @param beta beta value to be set.
423 * @throws IllegalArgumentException if provided value is less than 0.0 or
424 * greater than 1.0.
425 * @throws LockedException if this estimator is locked because an estimation
426 * is being computed.
427 */
428 public void setBeta(final double beta) throws LockedException {
429 if (isLocked()) {
430 throw new LockedException();
431 }
432 if (beta < MIN_BETA || beta > MAX_BETA) {
433 throw new IllegalArgumentException();
434 }
435
436 this.beta = beta;
437 }
438
439 /**
440 * Returns number of iterations to be done to obtain required confidence.
441 * This does not need to be equal to the actual number of iterations the
442 * algorithm finally required to obtain a solution.
443 *
444 * @return number of iterations to be done to obtain required confidence.
445 */
446 public int getNIters() {
447 return nIters;
448 }
449
450 /**
451 * Returns best solution that has been found so far during an estimation.
452 *
453 * @return best solution that has been found so far during an estimation.
454 */
455 public T getBestResult() {
456 return bestResult;
457 }
458
459 /**
460 * Gets data related to inliers found for best result.
461 *
462 * @return data related to inliers found for best result.
463 */
464 public PROSACInliersData getBestInliersData() {
465 return bestInliersData;
466 }
467
468 /**
469 * Indicates whether inliers must be computed and kept.
470 *
471 * @return true if inliers must be computed and kept, false if inliers
472 * only need to be computed but not kept.
473 */
474 public boolean isComputeAndKeepInliersEnabled() {
475 return computeAndKeepInliers;
476 }
477
478 /**
479 * Specifies whether inliers must be computed and kept.
480 *
481 * @param computeAndKeepInliers true if inliers must be computed and kept,
482 * false if inliers only need to be computed but not kept.
483 * @throws LockedException if estimator is locked.
484 */
485 public void setComputeAndKeepInliersEnabled(final boolean computeAndKeepInliers) throws LockedException {
486 if (isLocked()) {
487 throw new LockedException();
488 }
489 this.computeAndKeepInliers = computeAndKeepInliers;
490 }
491
492 /**
493 * Indicates whether residuals must be computed and kept.
494 *
495 * @return true if residuals must be computed and kept, false if residuals
496 * only need to be computed but not kept.
497 */
498 public boolean isComputeAndKeepResidualsEnabled() {
499 return computeAndKeepResiduals;
500 }
501
502 /**
503 * Specifies whether residuals must be computed and kept.
504 *
505 * @param computeAndKeepResiduals true if residuals must be computed and
506 * kept, false if residuals only need to be computed but not kept.
507 * @throws LockedException if estimator is locked.
508 */
509 public void setComputeAndKeepResidualsEnabled(final boolean computeAndKeepResiduals) throws LockedException {
510 if (isLocked()) {
511 throw new LockedException();
512 }
513 this.computeAndKeepResiduals = computeAndKeepResiduals;
514 }
515
516 /**
517 * Indicates if estimator is ready to start the estimation process.
518 *
519 * @return true if ready, false otherwise.
520 */
521 @Override
522 public boolean isReady() {
523 if (!super.isReady()) {
524 return false;
525 }
526 return (listener instanceof PROSACRobustEstimatorListener);
527 }
528
529 /**
530 * Robustly estimates an instance of T.
531 *
532 * @return estimated object.
533 * @throws LockedException if robust estimator is locked.
534 * @throws NotReadyException if provided input data is not enough to start
535 * the estimation.
536 * @throws RobustEstimatorException if estimation fails for any reason
537 * (i.e. numerical instability, no solution available, etc).
538 */
539 @Override
540 public T estimate() throws LockedException, NotReadyException, RobustEstimatorException {
541 if (isLocked()) {
542 throw new LockedException();
543 }
544 if (!isReady()) {
545 throw new NotReadyException();
546 }
547
548 try {
549 final var listener = (PROSACRobustEstimatorListener<T>) this.listener;
550
551 locked = true;
552
553 listener.onEstimateStart(this);
554
555 // N = CORRESPONDENCES
556 final var totalSamples = listener.getTotalSamples();
557 final var subsetSize = listener.getSubsetSize();
558 final var threshold = listener.getThreshold();
559 // only positive thresholds are allowed
560 if (threshold < MIN_THRESHOLD) {
561 throw new RobustEstimatorException();
562 }
563
564 final var qualityScores = listener.getQualityScores();
565 // check for invalid quality scores length
566 if (qualityScores.length != totalSamples) {
567 throw new RobustEstimatorException();
568 }
569 // obtain indices referring to original samples position after sorting
570 // quality scores in descending order
571 final var sortedIndices = computeSortedQualityIndices(listener.getQualityScores());
572
573 // reusable list that will contain preliminary solutions on each
574 // iteration
575 final var iterResults = new ArrayList<T>();
576 bestResult = null;
577 var previousProgress = 0.0f;
578 float progress;
579 // subset indices obtained from a subset selector
580 final var subsetIndices = new int[subsetSize];
581 // subset indices referred to the real samples positions after taking
582 // into account the sorted indices obtained from quality scores
583 final var transformedSubsetIndices = new int[subsetSize];
584 // array containing inliers efficiently
585 final var inliers = new BitSet(totalSamples);
586
587 // T_N
588 nIters = Math.min(computeIterations(1.0 - maxOutliersProportion, subsetSize, confidence),
589 maxIterations);
590
591 // termination length
592 var sampleSizeStar = totalSamples;
593 // number of inliers found within the first
594 // nStar data points
595 var inliersNStar = 0;
596 // best number of inliers found so far
597 // (store the model that goes with it)
598 var inliersBest = 0;
599 final var inliersMin = (int) ((1.0 - maxOutliersProportion) * totalSamples);
600 // iteration number (t)
601 var currentIter = 0;
602 // (n) we draw samples from the set U_n
603 // of the top n (sampleSize) data points
604 var sampleSize = subsetSize;
605 // average number of samples "{M_i}_{i=1}^{tn}"
606 // that contains samples from U_n only
607 double tn = nIters;
608 // integer version of tn
609 var tnPrime = 1;
610 // number of samples to draw to reach the
611 // maximality constraint
612 var kNStar = nIters;
613
614 double[] residuals = null;
615 if (computeAndKeepInliers || computeAndKeepResiduals) {
616 bestInliersData = new PROSACInliersData(totalSamples, computeAndKeepInliers, computeAndKeepResiduals);
617
618 if (computeAndKeepResiduals) {
619 residuals = new double[totalSamples];
620 }
621 }
622
623 // initialize tn
624 for (var i = 0; i < subsetSize; i++) {
625 tn *= (double) (sampleSize - i) / (double) (totalSamples - i);
626 }
627
628 if (subsetSelector == null) {
629 // create new subset selector
630 subsetSelector = SubsetSelector.create(totalSamples);
631 } else {
632 // set number of samples to current subset selector
633 subsetSelector.setNumSamples(totalSamples);
634 }
635
636 while (((inliersBest < inliersMin) || (currentIter < kNStar)) && (nIters > currentIter)
637 && (currentIter < maxIterations)) {
638 if (kNStar > 0) {
639 progress = Math.min((float) currentIter / (float) kNStar, 1.0f);
640 } else {
641 progress = 1.0f;
642 }
643 if (progress - previousProgress > progressDelta) {
644 previousProgress = progress;
645 listener.onEstimateProgressChange(this, progress);
646 }
647 currentIter++;
648
649 // choice of the hypothesis generation set
650
651 // The growth function is defined as
652 // g(t) = min{n : tnPrime > t} where n is sampleSize
653 // Thus sampleSize should be incremented if currentIter > tnPrime
654 if ((currentIter > tnPrime) && (sampleSize < sampleSizeStar)) {
655 var tnPlus1 = (tn * (sampleSize + 1)) / (sampleSize + 1 - subsetSize);
656 sampleSize++;
657 tnPrime += (int) Math.ceil(tnPlus1 - tn);
658 tn = tnPlus1;
659 }
660
661 // Draw semi-random sample
662 if (currentIter > tnPrime) {
663 // during the finishing stage (sampleSize == sampleSizeStar &&
664 // currentIter > tnPrime), draw a standard RANSAC sample
665 // The sample contains subsetSize points selected from U_n at
666 // random
667 subsetSelector.computeRandomSubsets(subsetSize, subsetIndices);
668 } else {
669 // The sample contains subsetSize-1 points selected from
670 // U_sampleSize_1 at random and u_sampleSize
671
672 subsetSelector.computeRandomSubsetsInRange(0, sampleSize, subsetSize, true,
673 subsetIndices);
674 }
675
676 transformIndices(subsetIndices, sortedIndices, transformedSubsetIndices);
677
678 // clear list of preliminary solutions before calling listener
679 iterResults.clear();
680 // compute solution for current iteration
681 listener.estimatePreliminarSolutions(transformedSubsetIndices, iterResults);
682
683 // total number of inliers for a
684 // given result
685 int inliersCurrent;
686 for (final var iterResult : iterResults) {
687 // compute inliers
688 inliersCurrent = computeInliers(iterResult, threshold, inliers, totalSamples, listener, residuals);
689
690 if (inliersCurrent > inliersBest) {
691 // update best number of inliers
692 inliersBest = inliersCurrent;
693 // keep current result
694 bestResult = iterResult;
695
696 // update best inlier data
697 if (bestInliersData != null) {
698 bestInliersData.update(inliers, residuals, inliersBest);
699 }
700
701 // select new termination length sampleSizeStar if possible
702 // only when a new sample is better than the others found
703 // so far
704
705 // best value found so far in terms of inliers ration
706 var sampleSizeBest = totalSamples;
707 var inliersSampleSizeBest = inliersCurrent;
708
709 // test value for the termination length
710 int sampleSizeTest;
711 // number of inliers for that test value
712 int inliersSampleSizeTest;
713 var epsilonSampleSizeBest = (double) inliersSampleSizeBest / (double) sampleSizeBest;
714
715 for (sampleSizeTest = totalSamples, inliersSampleSizeTest = inliersCurrent;
716 sampleSizeTest > subsetSize; sampleSizeTest--) {
717 // Loop invariants:
718 // - inliersSampleSizeTest is the number of inliers
719 // for the sampleSizeTest first correspondences
720 // - sampleSizeBest is the value between
721 // sampleSizeTest+1 and totalSamples that maximizes
722 // the ratio inliersSampleSizeBest/sampleSizeBest
723
724 // - Non-randomness: In >= imin(n*)
725 // - Maximality: the number of samples that were drawn
726 // so far must be enough so that the probability of
727 // having missed a set of inliers is below eta=0.01.
728 // This is the classical RANSAC termination criterion,
729 // except that it takes into account only the
730 // sampleSize first samples (not the total number
731 // of samples)
732 // kNStar = log(eta0) / log(1 - (inliersNStar/
733 // sampleSizeStar)^subsetSize
734 // We have to minimize kNStar, e.g. maximize
735 // inliersNStar/sampleSizeStar, a straightforward
736 // implementation would use the following test:
737 // if(inliersSampleSizeTest > epsilonSampleSizeBest *
738 // sampleSizeTest){ ... blah blah blah
739 // However, since In is binomial, and in the case of
740 // evenly distributed inliers, a better test would be
741 // to reduce sampleSizeStar only if there's a
742 // significant improvement in epsilon. Thus, we use a
743 // Chi-squared test (P=0.10), together with the normal
744 // approximation to the binomial (mu =
745 // epsilonSampleSizeStart * sampleSizeTest, sigma =
746 // sqrt(sampleSizeTest * epsilonSampleSizeStar * (1 -
747 // epsilonSampleSizeStar))).
748 // There is a significant difference between the two
749 // tests (e.g. with the computeInliers function
750 // provided)
751 // We do the cheap test first, and the expensive test
752 // only if the cheap one passes
753 if ((inliersSampleSizeTest * sampleSizeBest > inliersSampleSizeBest * sampleSizeTest)
754 && (inliersSampleSizeTest > epsilonSampleSizeBest * sampleSizeTest
755 + Math.sqrt(sampleSizeTest * epsilonSampleSizeBest * (1.0 - epsilonSampleSizeBest)
756 * CHI_SQUARED))) {
757
758 if (inliersSampleSizeTest < imin(subsetSize, sampleSizeTest, beta)) {
759 // equation not satisfied, no need to test for
760 // smaller sampleSizeTest values anyway
761
762 // jump out of the for sampleSizeTest loop
763 break;
764 }
765 sampleSizeBest = sampleSizeTest;
766 inliersSampleSizeBest = inliersSampleSizeTest;
767 epsilonSampleSizeBest = (double) inliersSampleSizeBest / (double) sampleSizeBest;
768 }
769
770 // prepare for next loop iteration
771 inliersSampleSizeTest -= inliers.get(sortedIndices[sampleSizeTest - 1]) ? 1 : 0;
772 }
773
774 // is the best one we found even better than sampleSizeStar?
775 if (inliersSampleSizeBest * sampleSizeStar > inliersNStar * sampleSizeBest) {
776
777 // update all values
778 sampleSizeStar = sampleSizeBest;
779 inliersNStar = inliersSampleSizeBest;
780 kNStar = computeIterations((double) inliersNStar / (double) sampleSizeStar,
781 subsetSize, 1.0 - eta0);
782 }
783 }
784 }
785
786 listener.onEstimateNextIteration(this, currentIter);
787 }
788
789 // no solution could be found after completing all iterations
790 if (bestResult == null) {
791 throw new RobustEstimatorException();
792 }
793
794 listener.onEstimateEnd(this);
795
796 return bestResult;
797 } catch (final SubsetSelectorException | SortingException e) {
798 throw new RobustEstimatorException(e);
799 } finally {
800 locked = false;
801 }
802
803 }
804
805 /**
806 * Returns data about inliers once estimation has been done.
807 *
808 * @return data about inliers or null if estimation has not been done.
809 */
810 @Override
811 public InliersData getInliersData() {
812 return getBestInliersData();
813 }
814
815 /**
816 * Returns method being used for robust estimation.
817 *
818 * @return method being used for robust estimation.
819 */
820 @Override
821 public RobustEstimatorMethod getMethod() {
822 return RobustEstimatorMethod.PROSAC;
823 }
824
825 /**
826 * Transforms indices picked by the subset selector into the indices where
827 * samples are actually localed by taking into account their original
828 * position before sorting quality scores.
829 *
830 * @param subsetIndices indices picked by the subset selector. These are
831 * positions after sorting. Must have the subset length.
832 * @param sortedIndices indices relating sorted positions to their original
833 * positions. Each position i-th in the array refers to the original
834 * position before sorting. Must have the number of samples length.
835 * @param transformedSubsetIndices array where result is stored. Must have
836 * the subset length.
837 */
838 private static void transformIndices(
839 final int[] subsetIndices, final int[] sortedIndices, final int[] transformedSubsetIndices) {
840 final var length = transformedSubsetIndices.length;
841 for (var i = 0; i < length; i++) {
842 transformedSubsetIndices[i] = sortedIndices[subsetIndices[i]];
843 }
844 }
845
846
847 /**
848 * Computes inliers data for current iteration.
849 *
850 * @param <T> type of result to be estimated.
851 * @param iterResult result to be tested on current iteration.
852 * @param threshold threshold to determine whether samples are inliers or
853 * not.
854 * @param inliers bitset indicating which samples are inliers. This is
855 * indicated in their original position before sorting.
856 * @param totalSamples total number of samples.
857 * @param listener listener to obtain residuals for samples.
858 * @param residuals array where residuals must be stored (if provided).
859 * @return inliers data.
860 */
861 private static <T> int computeInliers(
862 final T iterResult, final double threshold, final BitSet inliers, final int totalSamples,
863 final PROSACRobustEstimatorListener<T> listener, final double[] residuals) {
864
865 var numInliers = 0;
866 double residual;
867 for (var i = 0; i < totalSamples; i++) {
868 residual = Math.abs(listener.computeResidual(iterResult, i));
869 if (residual < threshold) {
870 numInliers++;
871 inliers.set(i);
872 } else {
873 inliers.clear(i);
874 }
875 if (residuals != null) {
876 residuals[i] = residual;
877 }
878 }
879
880 return numInliers;
881 }
882
883
884 /**
885 * Obtains indices of samples corresponding to samples ordered in descending
886 * quality scores.
887 *
888 * @param qualityScores quality scores associated to each sample to be used
889 * to obtain indices to sort samples in descending order of quality values.
890 * @return indices to sort samples in descending order of quality values.
891 * @throws SortingException if sorting fails.
892 */
893 private static int[] computeSortedQualityIndices(final double[] qualityScores) throws SortingException {
894 final var sorter = Sorter.<Double>create();
895 final var qualityScoresCopy = Arrays.copyOf(qualityScores, qualityScores.length);
896 // this method modifies quality scores copy array because it gets sorted
897 // in ascending order. Indices contains indices of samples corresponding
898 // to quality scores ordered in ascending order
899 final var indices = sorter.sortWithIndices(qualityScoresCopy);
900
901 // reverse indices so we have indices of samples ordered in descending
902 // order of quality
903 reverse(indices);
904 return indices;
905 }
906
907 /**
908 * Reverses provided array.
909 *
910 * @param array array to be reversed.
911 */
912 private static void reverse(final int[] array) {
913 final var length = array.length;
914 for (var i = 0; i < length / 2; i++) {
915 var temp = array[i];
916 var pos = length - 1 - i;
917 array[i] = array[pos];
918 array[pos] = temp;
919 }
920 }
921
922 /**
923 * Computes number of required iterations to achieve required confidence
924 * with current probability of inlier and sample subset size.
925 *
926 * @param probInlier probability of inlier.
927 * @param subsetSize sample subset size.
928 * @param confidence required confidence of result.
929 * @return number of required iterations.
930 */
931 private static int computeIterations(final double probInlier, final int subsetSize, final double confidence) {
932
933 // compute number of times the algorithm needs to be executed depending
934 // on number of inliers respect total points to achieve with probability
935 // confidence that we have all inliers and probability 1 - confidence
936 // that we have some outliers
937 final var probSubsetAllInliers = Math.pow(probInlier, subsetSize);
938 if (Math.abs(probSubsetAllInliers) < Double.MIN_VALUE || Double.isNaN(probSubsetAllInliers)) {
939 return Integer.MAX_VALUE;
940 } else {
941 final var logProbSomeOutliers = Math.log(1.0 - probSubsetAllInliers);
942 if (Math.abs(logProbSomeOutliers) < Double.MIN_VALUE || Double.isNaN(logProbSomeOutliers)) {
943 return Integer.MAX_VALUE;
944 } else {
945 return (int) Math.ceil(Math.abs(Math.log(1.0 - confidence) / logProbSomeOutliers));
946 }
947 }
948 }
949
950 /**
951 * Non randomness states that i-m (where i is the cardinal of the set of
952 * inliers for a wrong model) follows the binomial distribution B(n,beta).
953 * For n big enough, B(n,beta) approximates to normal distribution N(mu,
954 * sigma^2) by the central limit theorem, with mu = n*beta and sigma =
955 * sqrt(n*beta*(1 - beta)).
956 * Psi, the probability that In_star out of n_star data points are by chance
957 * inliers to an arbitrary incorrect model, is set to 0.05 (5%, as in the
958 * original paper), and you must change the Chi2 value if you chose a
959 * different value for psi.
960 *
961 * @param subsetSize sample subset size.
962 * @param sampleSize total number of samples.
963 * @param beta beta value.
964 * @return i-m.
965 */
966 private static int imin(final int subsetSize, final int sampleSize, final double beta) {
967 final var mu = sampleSize * beta;
968 final var sigma = Math.sqrt(sampleSize * beta * (1.0 - beta));
969
970 return (int) Math.ceil(subsetSize + mu + sigma * Math.sqrt(CHI_SQUARED));
971 }
972
973 /**
974 * Contains data related to estimated inliers.
975 */
976 public static class PROSACInliersData extends InliersData {
977
978 /**
979 * Efficiently stores which samples are considered inliers and which
980 * ones aren't.
981 */
982 private BitSet inliers;
983
984 public PROSACInliersData(final int totalSamples, final boolean keepInliers, final boolean keepResiduals) {
985 if (keepInliers) {
986 inliers = new BitSet(totalSamples);
987 }
988 if (keepResiduals) {
989 residuals = new double[totalSamples];
990 }
991 numInliers = 0;
992 }
993
994 /**
995 * Returns efficient array indicating which samples are considered
996 * inliers and which ones aren't.
997 *
998 * @return array indicating which samples are considered inliers and
999 * which ones aren't.
1000 */
1001 @Override
1002 public BitSet getInliers() {
1003 return inliers;
1004 }
1005
1006 /**
1007 * Updates data contained in this instance.
1008 *
1009 * @param inliers efficiently stores which samples are considered
1010 * inliers and which ones aren't.
1011 * @param residuals residuals obtained for each sample of data.
1012 * @param numInliers number of inliers found on current iteration.
1013 */
1014 protected void update(final BitSet inliers, final double[] residuals, final int numInliers) {
1015 int totalSamples = 0;
1016 if (inliers != null) {
1017 totalSamples = inliers.length();
1018 }
1019 if (residuals != null) {
1020 totalSamples = residuals.length;
1021 }
1022
1023 if (this.inliers != null && inliers != null && this.residuals != null && residuals != null) {
1024 // update inliers and residuals
1025 for (int i = 0; i < totalSamples; i++) {
1026 this.inliers.set(i, inliers.get(i));
1027 this.residuals[i] = residuals[i];
1028 }
1029 } else if (this.inliers != null && inliers != null) {
1030 // update inliers but not the residuals
1031 for (int i = 0; i < totalSamples; i++) {
1032 this.inliers.set(i, inliers.get(i));
1033 }
1034 } else if (this.residuals != null && residuals != null) {
1035 // update residuals but not inliers
1036 System.arraycopy(residuals, 0, this.residuals, 0, totalSamples);
1037 }
1038 this.numInliers = numInliers;
1039 }
1040 }
1041 }