View Javadoc
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 }