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 PROMedS (PROgressive least Median Sample) algorithm
29   * to robustly estimate a data model.
30   * This algorithm is a mixture between LMedS and PROSAC, taking the best of
31   * both.
32   * Firstly, it has the advantage that no threshold is required to be set
33   * beforehand, the same as LMedS. Threshold to determine inliers is computed
34   * dynamically, which helps for an easier setup that is problem independent and
35   * depending on the accuracy of the inliers, results will be more accurate than
36   * RANSAC or PROSAC, just the same as LMedS.
37   * On the other hand, if certain information about the quality of the samples
38   * is available, as in PROSAC, the algorithm takes advantage of this additional
39   * information to prioritize the samples with higher quality in order to find
40   * a solution much faster than RANSAC or LMedS.
41   * Finally, if by any chance a threshold to determine inliers is also used, the
42   * algorithm will try to get the solution that better fits in a pure median of
43   * residuals model or in a threshold based one to determine inliers.
44   * Hence, PROMedS can be as fast as PROSAC (which is typically about 100x faster
45   * than RANSAC or LMedS), can obtain the same accuracy as LMedS (which can be
46   * much better than RANSAC or PROSAC in certain scenarios), and has an easier
47   * setup, which is problem independent because no threshold is required to be
48   * known beforehand although one can be provided as well.
49   *
50   * @param <T> type of object to be estimated.
51   */
52  @SuppressWarnings("Duplicates")
53  public class PROMedSRobustEstimator<T> extends RobustEstimator<T> {
54      /**
55       * Constant defining default confidence of the estimated result, which is
56       * 99%. This means that with a probability of 99% estimation will be
57       * accurate because chosen sub-samples will be inliers.
58       */
59      public static final double DEFAULT_CONFIDENCE = 0.99;
60  
61      /**
62       * Default maximum allowed number of iterations.
63       */
64      public static final int DEFAULT_MAX_ITERATIONS = 5000;
65  
66      /**
67       * Minimum allowed confidence value.
68       */
69      public static final double MIN_CONFIDENCE = 0.0;
70  
71      /**
72       * Maximum allowed confidence value.
73       */
74      public static final double MAX_CONFIDENCE = 1.0;
75  
76      /**
77       * Minimum allowed number of iterations.
78       */
79      public static final int MIN_ITERATIONS = 1;
80  
81      /**
82       * Indicates whether the algorithm must stop prematurely when dynamically
83       * computed threshold using median of residuals has a value lower than
84       * provided threshold in listener.
85       * When this flag is enabled accuracy of PROMedS worsens to a lever similar
86       * to PROSAC but the number of iterations is reduced (i.e. less
87       * computational cost). If more accuracy is desired at the expense of some
88       * additional computation cost, then disable this flag.
89       * By default, stop threshold is enabled, so that computational cost is
90       * similar to RANSAC and only accuracy gets better if inliers are more
91       * accurate.
92       */
93      public static final boolean DEFAULT_STOP_THRESHOLD_ENABLED = true;
94  
95      /**
96       * Default maximum allowed outliers proportion in the input data. This is
97       * used do determine the number of required iterations.
98       */
99      public static final double DEFAULT_MAX_OUTLIERS_PROPORTION = 0.8;
100 
101     /**
102      * Minimum allowed value for maximum allowed outliers proportion in the
103      * input data.
104      */
105     public static final double MIN_MAX_OUTLIERS_PROPORTION = 0.0;
106 
107     /**
108      * Maximum allowed value for maximum allowed outliers proportion in the
109      * input data.
110      */
111     public static final double MAX_MAX_OUTLIERS_PROPORTION = 1.0;
112 
113     /**
114      * Defines the default value for the maximum probability that a solution
115      * with more than inliersNStar in U_nStar exist and was not found after k
116      * samples.
117      */
118     public static final double DEFAULT_ETA0 = 0.05;
119 
120     /**
121      * Minimum allowed value for eta0.
122      */
123     public static final double MIN_ETA0 = 0.0;
124 
125     /**
126      * Maximum allowed value for eta0.
127      */
128     public static final double MAX_ETA0 = 1.0;
129 
130     /**
131      * Defines the default value for beta, which is the probability that a
132      * match is declared inlier by mistake, i.e. the ratio of the "inlier"
133      * surface by the total surface. The inlier surface is a disc with radius
134      * 1.96s for homography/displacement computation, or a band with width
135      * 1.96*s*2 for epipolar geometry (s is the detection noise), and the total
136      * surface is the surface of the image.
137      */
138     public static final double DEFAULT_BETA = 0.01;
139 
140     /**
141      * Minimum allowed value for beta.
142      */
143     public static final double MIN_BETA = 0.0;
144 
145     /**
146      * Maximum allowed value for beta.
147      */
148     public static final double MAX_BETA = 1.0;
149 
150     /**
151      * Default factor to normalize threshold to determine inliers. This factor
152      * can be used to increase or lower the dynamically computed threshold so
153      * that the algorithm becomes more or less accurate. The stricter the
154      * threshold (lower factor), the more time the algorithm will need to
155      * converge, if it can converge. By default, the factor is 1.0, which makes
156      * the threshold to be computed as the median of residuals.
157      */
158     public static final double DEFAULT_INLIER_FACTOR = 1.0; //1.5 would also be reasonable
159 
160     /**
161      * Minimum allowed value for inlier factor.
162      */
163     public static final double MIN_INLER_FACTOR = 0.0;
164 
165     /**
166      * Indicates whether the inlier threshold will be used to find inliers along
167      * with their median of residuals.
168      */
169     public static final boolean DEFAULT_USE_INLIER_THRESHOLD = true;
170 
171     /**
172      * Constant to estimate standard deviation of residuals based on their
173      * median.
174      */
175     public static final double STD_CONSTANT = 1.4826;
176 
177     /**
178      * Chi squared.
179      */
180     public static final double CHI_SQUARED = 2.706;
181 
182     /**
183      * Amount of confidence expressed as a value between 0 and 1.0 (which is
184      * equivalent to 100%). The amount of confidence indicates the probability
185      * that the estimated result is correct. Usually this value will be close
186      * to 1.0, but not exactly 1.0.
187      */
188     private double confidence;
189 
190     /**
191      * Maximum allowed number of iterations. When the maximum number of
192      * iterations is exceeded, result will not be available, however an
193      * approximate result will be available for retrieval.
194      */
195     private int maxIterations;
196 
197     /**
198      * In this implementation, PROSAC won't stop before having reached the
199      * corresponding inliers rate on the complete data set.
200      * Maximum allowed outliers proportion in the input data: used to compute
201      * nIters (can be as high as 0.95).
202      */
203     private double maxOutliersProportion;
204 
205     /**
206      * eta0 is the maximum probability that a solution with more than
207      * inliersNStar inliers in U_nStar exists and was not found after k
208      * samples (typically set to 5%).
209      */
210     private double eta0;
211 
212     /**
213      * beta is the probability that a match is declared inlier by mistake,
214      * i.e. the ratio of the "inlier" surface by the total surface. The
215      * inlier surface is a disc with radius 1.96s for homography/displacement
216      * computation, or a band with width 1.96s*2 for epipolar geometry (s is
217      * the detection noise), and the total surface is the surface of the image
218      * YOU MUST ADJUST THIS VALUE, DEPENDING ON YOUR PROBLEM!.
219      */
220     private double beta;
221 
222     /**
223      * Instance in charge of picking random subsets of samples.
224      */
225     private SubsetSelector subsetSelector;
226 
227     /**
228      * Number of iterations to be done to obtain required confidence.
229      */
230     private int iters;
231 
232     /**
233      * Best solution that has been found so far during an estimation.
234      */
235     private T bestResult;
236 
237     /**
238      * Data related to inliers found for best result.
239      */
240     private PROMedSInliersData bestInliersData;
241 
242     /**
243      * Indicates whether the algorithm must stop prematurely when dynamically
244      * computed threshold using median of residuals has a value lower than
245      * provided threshold in listener.
246      * When this flag is enabled accuracy of PROMedS worsens to a lever similar
247      * to PROSAC but the number of iterations is reduced (i.e. less
248      * computational cost). If more accuracy is desired at the expense of some
249      * additional computation cost, then disable this flag.
250      */
251     private boolean stopThresholdEnabled;
252 
253     /**
254      * Factor to normalize threshold to determine inliers. This factor can be
255      * used to increase or lower the dynamically computed threshold so that the
256      * algorithm becomes more or less accurate. The stricter the threshold
257      * (lower factor), the more time the algorithm will need to converge, if
258      * it can converge. By default, the factor is 1.0, which makes the threshold
259      * to be computed as the median of residuals.
260      */
261     private double inlierFactor;
262 
263     /**
264      * Flag indicating whether thresholds to determine inliers are used, or if
265      * only median of residuals is used. When true, the algorithm will try
266      * to fit the best model, otherwise only median of residuals will be used.
267      */
268     private boolean useInlierThresholds;
269 
270     /**
271      * Constructor.
272      */
273     public PROMedSRobustEstimator() {
274         super();
275         confidence = DEFAULT_CONFIDENCE;
276         maxIterations = DEFAULT_MAX_ITERATIONS;
277         maxOutliersProportion = DEFAULT_MAX_OUTLIERS_PROPORTION;
278         eta0 = DEFAULT_ETA0;
279         beta = DEFAULT_BETA;
280         iters = maxIterations;
281         bestResult = null;
282         bestInliersData = null;
283         stopThresholdEnabled = DEFAULT_STOP_THRESHOLD_ENABLED;
284         inlierFactor = DEFAULT_INLIER_FACTOR;
285         useInlierThresholds = DEFAULT_USE_INLIER_THRESHOLD;
286     }
287 
288     /**
289      * Constructor with listener.
290      *
291      * @param listener listener to be notified of events such as when estimation
292      *                 starts, ends or its progress significantly changes, as well as in charge
293      *                 of picking samples and doing per-iteration estimations.
294      */
295     public PROMedSRobustEstimator(final PROMedSRobustEstimatorListener<T> listener) {
296         super(listener);
297         confidence = DEFAULT_CONFIDENCE;
298         maxIterations = DEFAULT_MAX_ITERATIONS;
299         maxOutliersProportion = DEFAULT_MAX_OUTLIERS_PROPORTION;
300         eta0 = DEFAULT_ETA0;
301         beta = DEFAULT_BETA;
302         iters = maxIterations;
303         bestResult = null;
304         bestInliersData = null;
305         stopThresholdEnabled = DEFAULT_STOP_THRESHOLD_ENABLED;
306         inlierFactor = DEFAULT_INLIER_FACTOR;
307         useInlierThresholds = DEFAULT_USE_INLIER_THRESHOLD;
308     }
309 
310     /**
311      * Returns amount of confidence expressed as a value between 0 and 1.0
312      * (which is equivalent to 100%). The amount of confidence indicates the
313      * probability that the estimated result is correct. Usually this value will
314      * be close to 1.0, but not exactly 1.0.
315      *
316      * @return amount of confidence as a value between 0.0 and 1.0.
317      */
318     public double getConfidence() {
319         return confidence;
320     }
321 
322     /**
323      * Sets amount of confidence expressed as a value between 0 and 1.0 (which
324      * is equivalent to 100%). The amount of confidence indicates the
325      * probability that the estimated result is correct. Usually this value will
326      * be close to 1.0, but not exactly 1.0.
327      *
328      * @param confidence confidence to be set as a value between 0.0 and 1.0.
329      * @throws IllegalArgumentException if provided value is not between 0.0 and
330      *                                  1.0.
331      * @throws LockedException          if this estimator is locked because an estimation
332      *                                  is being computed.
333      */
334     public void setConfidence(final double confidence) throws LockedException {
335         if (isLocked()) {
336             throw new LockedException();
337         }
338         if (confidence < MIN_CONFIDENCE || confidence > MAX_CONFIDENCE) {
339             throw new IllegalArgumentException();
340         }
341         this.confidence = confidence;
342     }
343 
344     /**
345      * Maximum allowed number of iterations. When the maximum number of
346      * iterations is exceeded, result will not be available, however an
347      * approximate result will be available for retrieval.
348      *
349      * @return maximum allowed number of iterations.
350      */
351     public int getMaxIterations() {
352         return maxIterations;
353     }
354 
355     /**
356      * Sets maximum allowed number of iterations. When the maximum number of
357      * iterations is exceeded, result will not be available, however an
358      * approximate result will be available for retrieval.
359      *
360      * @param maxIterations maximum allowed number of iterations to be set.
361      * @throws IllegalArgumentException if provided value is less than 1.
362      * @throws LockedException          if this estimator is locked because an estimation
363      *                                  is being computed.
364      */
365     public void setMaxIterations(final int maxIterations) throws LockedException {
366         if (isLocked()) {
367             throw new LockedException();
368         }
369         if (maxIterations < MIN_ITERATIONS) {
370             throw new IllegalArgumentException();
371         }
372         this.maxIterations = maxIterations;
373     }
374 
375     /**
376      * Returns boolean indicating whether the algorithm must stop prematurely
377      * when dynamically computed threshold using median of residuals has a value
378      * lower than provided threshold in listener.
379      * When this flag is enabled accuracy of PROMedS worsens to a lever similar
380      * to PROSAC but the number of iterations is reduced (i.e. less
381      * computational cost). If more accuracy is desired at the expense of some
382      * additional computation cost, then disable this flag.
383      *
384      * @return true if stop threshold is enabled, false otherwise.
385      */
386     public boolean isStopThresholdEnabled() {
387         return stopThresholdEnabled;
388     }
389 
390     /**
391      * Sets boolean indicating whether the algorithm must stop prematurely when
392      * dynamically computed threshold using median of residuals has a value
393      * lower than provided threshold in listener.
394      * When this flag is enabled accuracy of PROMedS worsens to a lever similar
395      * to PROSAC but the number of iterations is reduced (i.e. less
396      * computational cost). If more accuracy is desired at the expense of some
397      * additional computation cost, then disable this flag.
398      *
399      * @param stopThresholdEnabled true if stop threshold is enabled, false
400      *                             otherwise.
401      * @throws LockedException if this estimator is locked because an estimation
402      *                         is being computed.
403      */
404     public void setStopThresholdEnabled(final boolean stopThresholdEnabled) throws LockedException {
405         if (isLocked()) {
406             throw new LockedException();
407         }
408         this.stopThresholdEnabled = stopThresholdEnabled;
409     }
410 
411     /**
412      * Returns factor to normalize or adjust threshold to determine inliers.
413      * This factor can be used to increase or lower the dynamically computed
414      * threshold so that the algorithm becomes more or less accurate. The
415      * stricter the threshold (lower factor), the more time the algorithm will
416      * need to converge, if it can converge. By default, the factor is 1.0, which
417      * makes the threshold to be computed as the median of residuals.
418      *
419      * @return factor to normalize threshold to determine inliers.
420      */
421     public double getInlierFactor() {
422         return inlierFactor;
423     }
424 
425     /**
426      * Sets factor to normalize or adjust threshold to determine inliers.
427      * This factor can be used to increase or lower the dynamically computed
428      * threshold so that the algorithm becomes more or less accurate. The
429      * stricter the threshold (lower factor), the more time the algorithm will
430      * need to converge, if it can converge. By default, the factor is 1.0, which
431      * makes the threshold to be computed as the median of residuals.
432      *
433      * @param inlierFactor inlier factor to be set.
434      * @throws IllegalArgumentException if provided value is less or equal than
435      *                                  0.0.
436      * @throws LockedException          if this estimator is locked because an estimation
437      *                                  is being computed.
438      */
439     public void setInlierFactor(final double inlierFactor) throws LockedException {
440         if (isLocked()) {
441             throw new LockedException();
442         }
443         if (inlierFactor <= MIN_INLER_FACTOR) {
444             throw new IllegalArgumentException();
445         }
446         this.inlierFactor = inlierFactor;
447     }
448 
449     /**
450      * Returns flag indicating whether thresholds to determine inliers are used,
451      * or if only median of residuals is used. When true, the algorithm will try
452      * to fit the best model, otherwise only median of residuals will be used.
453      *
454      * @return true if best model is used (threshold or median), otherwise only
455      * median of residuals will be used.
456      */
457     public boolean isUseInlierThresholds() {
458         return useInlierThresholds;
459     }
460 
461     /**
462      * Sets flag indicating whether thresholds to determine inliers are used, or
463      * if only median of residuals is used. When true, the algorithm will try to
464      * fit the best model, otherwise only median of residuals will be used.
465      *
466      * @param useInlierThresholds true if best model is used (threshold or
467      *                            median), oitherwise only median of residuals will be used.
468      * @throws LockedException if this estimator is locked because an estimation
469      *                         is being computed.
470      */
471     public void setUseInlierThresholds(final boolean useInlierThresholds) throws LockedException {
472         if (isLocked()) {
473             throw new LockedException();
474         }
475 
476         this.useInlierThresholds = useInlierThresholds;
477     }
478 
479     /**
480      * Returns maximum allowed outliers proportion in the input data. This is
481      * used to compute number of iterations to be done (nIters). It typically
482      * can be as high as 0.95. Higher values, up to 1 are possible but not
483      * recommended.
484      * In this implementation, PROSAC won't stop before having reached the
485      * corresponding inliers rate on the complete data set.
486      *
487      * @return maximum allowed outliers proportion in the input data.
488      */
489     public double getMaxOutliersProportion() {
490         return maxOutliersProportion;
491     }
492 
493     /**
494      * Sets maximum allowed outliers proportion in the input data. This is used
495      * to compute number of iterations to be done (nIters). It typically can be
496      * as high as 0.95. Higher values, up to 1 are possible but not recommended.
497      * In this implementation, PROSAC won't stop before having reached the
498      * corresponding inliers rate on the complete data set.
499      *
500      * @param maxOutliersProportion maximum allowed outliers proportion in the
501      *                              input data.
502      * @throws IllegalArgumentException if provided value is less than 0.0 or
503      *                                  greater than 1.0.
504      * @throws LockedException          if this estimator is locked because an estimation
505      *                                  is being computed.
506      */
507     public void setMaxOutliersProportion(final double maxOutliersProportion) throws LockedException {
508         if (isLocked()) {
509             throw new LockedException();
510         }
511         if (maxOutliersProportion < MIN_MAX_OUTLIERS_PROPORTION
512                 || maxOutliersProportion > MAX_MAX_OUTLIERS_PROPORTION) {
513             throw new IllegalArgumentException();
514         }
515 
516         this.maxOutliersProportion = maxOutliersProportion;
517     }
518 
519     /**
520      * Return eta0, which is the maximum probability that a solution with more
521      * than inliersNStar inliers in U_nStar exists and was not found after k
522      * samples (typically set to 5%).
523      *
524      * @return eta0 value.
525      */
526     public double getEta0() {
527         return eta0;
528     }
529 
530     /**
531      * Sets eta0, which is the maximum probability that a solution with more
532      * than inliersNStar inliers in U_nStar exists and was not found after k
533      * samples (typically set to 5%).
534      *
535      * @param eta0 eta0 value to be set.
536      * @throws IllegalArgumentException if provided value is less than 0.0 or
537      *                                  greater than 1.0.
538      * @throws LockedException          if this estimator is locked because an estimation
539      *                                  is being computed.
540      */
541     public void setEta0(final double eta0) throws LockedException {
542         if (isLocked()) {
543             throw new LockedException();
544         }
545         if (eta0 < MIN_ETA0 || eta0 > MAX_ETA0) {
546             throw new IllegalArgumentException();
547         }
548 
549         this.eta0 = eta0;
550     }
551 
552     /**
553      * Returns beta, which is the probability that a match is declared inlier by
554      * mistake, i.e. the ratio of the "inlier" surface by the total surface. The
555      * inlier surface is a disc with radius 1.96s for homography/displacement
556      * computation, or a band with width 1.96s*2 for epipolar geometry (s is
557      * the detection noise), and the total surface is the surface of the image
558      * YOU MUST ADJUST THIS VALUE, DEPENDING ON YOUR PROBLEM!
559      *
560      * @return beta value.
561      */
562     public double getBeta() {
563         return beta;
564     }
565 
566     /**
567      * Sets beta, which is the probability that a match is declared inlier by
568      * mistake, i.e. the ratio of the "inlier" surface by the total surface. The
569      * inlier surface is a disc with radius 1.96s for homography/displacement
570      * computation, or a band with width 1.96s*2 for epipolar geometry (s is
571      * the detection noise), and the total surface is the surface of the image
572      * YOU MUST ADJUST THIS VALUE, DEPENDING ON YOUR PROBLEM!
573      *
574      * @param beta beta value to be set.
575      * @throws IllegalArgumentException if provided value is less than 0.0 or
576      *                                  greater than 1.0.
577      * @throws LockedException          if this estimator is locked because an estimation
578      *                                  is being computed.
579      */
580     public void setBeta(final double beta) throws LockedException {
581         if (isLocked()) {
582             throw new LockedException();
583         }
584         if (beta < MIN_BETA || beta > MAX_BETA) {
585             throw new IllegalArgumentException();
586         }
587 
588         this.beta = beta;
589     }
590 
591     /**
592      * Returns number of iterations to be done to obtain required confidence.
593      * This does not need to be equal to the actual number of iterations the
594      * algorithm finally required to obtain a solution.
595      *
596      * @return number of iterations to be done to obtain required confidence.
597      */
598     public int getNIters() {
599         return iters;
600     }
601 
602     /**
603      * Returns best solution that has been found so far during an estimation.
604      *
605      * @return best solution that has been found so far during an estimation.
606      */
607     public T getBestResult() {
608         return bestResult;
609     }
610 
611     /**
612      * Returns data related to inliers found for best result.
613      *
614      * @return data related to inliers found for best result.
615      */
616     protected PROMedSInliersData getBestInliersData() {
617         return bestInliersData;
618     }
619 
620     /**
621      * Indicates if estimator is ready to start the estimation process.
622      *
623      * @return true if ready, false otherwise.
624      */
625     @Override
626     public boolean isReady() {
627         if (!super.isReady()) {
628             return false;
629         }
630         return (listener instanceof PROMedSRobustEstimatorListener);
631     }
632 
633     /**
634      * Robustly estimates an instance of T.
635      *
636      * @return estimated object.
637      * @throws LockedException          if robust estimator is locked.
638      * @throws NotReadyException        if provided input data is not enough to start
639      *                                  the estimation.
640      * @throws RobustEstimatorException if estimation fails for any reason
641      *                                  (i.e. numerical instability, no solution available, etc).
642      */
643     @Override
644     public T estimate() throws LockedException, NotReadyException, RobustEstimatorException {
645         if (isLocked()) {
646             throw new LockedException();
647         }
648         if (!isReady()) {
649             throw new NotReadyException();
650         }
651 
652         try {
653             var listener = (PROMedSRobustEstimatorListener<T>) this.listener;
654 
655             locked = true;
656 
657             listener.onEstimateStart(this);
658 
659             // N = CORRESPONDENCES
660             final var totalSamples = listener.getTotalSamples();
661             final var subsetSize = listener.getSubsetSize();
662 
663             final var qualityScores = listener.getQualityScores();
664             // check for invalid quality scores length
665             if (qualityScores.length != totalSamples) {
666                 throw new RobustEstimatorException();
667             }
668 
669             var inlierThreshold = 0.0;
670             if (useInlierThresholds) {
671                 inlierThreshold = listener.getThreshold();
672             }
673             // obtain indices referring to original samples position after sorting
674             // quality scores in descending order
675             final var sortedIndices = computeSortedQualityIndices(listener.getQualityScores());
676 
677             // reusable list that will contain preliminary solutions on each
678             // iteration
679             final var iterResults = new ArrayList<T>();
680             bestResult = null;
681             var previousProgress = 0.0f;
682             float progress;
683             // subset indices obtained from a subset selector
684             final var subsetIndices = new int[subsetSize];
685             final var residualsTemp = new double[totalSamples];
686             // subset indices referred to the real samples positions after taking
687             // into account the sorted indices obtained from quality scores
688             final var transformedSubsetIndices = new int[subsetSize];
689             // array containing inliers efficiently
690             final var inliers = new BitSet(totalSamples);
691 
692             // T_N
693             iters = Math.min(computeIterations(1.0 - maxOutliersProportion, subsetSize, confidence),
694                     maxIterations);
695 
696             // termination length
697             var sampleSizeStar = totalSamples;
698             // number of inliers found within the first
699             // nStar data points
700             var inliersNStar = 0;
701             // best number of inliers found so far
702             // (store the model that goes with it)
703             var inliersBest = -1;
704             // threshold to stop algorithm
705             var threshold = Double.MAX_VALUE;
706             final var inliersMin = (int) ((1.0 - maxOutliersProportion) * totalSamples);
707             // iteration number (t)
708             var currentIter = 0;
709             // (n) we draw samples from the set U_n
710             // of the top n (sampleSize) data points
711             var sampleSize = subsetSize;
712             // average number of samples "{M_i}_{i=1}^{Tn}"
713             // that contains samples from U_n only
714             double tn = iters;
715             // integer version of Tn
716             var tnPrime = 1;
717             // number of samples to draw to reach the
718             // maximality constraint
719             var kNStar = iters;
720 
721             // initialize Tn
722             for (var i = 0; i < subsetSize; i++) {
723                 tn *= (double) (sampleSize - i) / (double) (totalSamples - i);
724             }
725 
726             if (subsetSelector == null) {
727                 // create new subset selector
728                 subsetSelector = SubsetSelector.create(totalSamples);
729             } else {
730                 // set number of samples to current subset selector
731                 subsetSelector.setNumSamples(totalSamples);
732             }
733 
734             // data related to inliers
735             var inliersData = new PROMedSInliersData(totalSamples);
736             // sorter to compute medians
737             final var sorter = Sorter.<Double>create();
738 
739             // indicates if result improved
740             boolean improved;
741             var continueIteration = true;
742 
743             // iterate until the expected number of inliers or the estimated
744             // number of iterations is reached
745             while (continueIteration) {
746                 if (kNStar > 0) {
747                     progress = Math.min((float) currentIter / (float) kNStar, 1.0f);
748                 } else {
749                     progress = 1.0f;
750                 }
751                 if (progress - previousProgress > progressDelta) {
752                     previousProgress = progress;
753                     listener.onEstimateProgressChange(this, progress);
754                 }
755                 currentIter++;
756 
757                 // choice of the hypothesis generation set
758 
759                 // The growth function is defined as
760                 // g(t) = min{n : TnPrime > t} where n is sampleSize
761                 // Thus sampleSize should be incremented if currentIter > TnPrime
762                 if ((currentIter > tnPrime) && (sampleSize < sampleSizeStar)) {
763                     final var TnPlus1 = (tn * (sampleSize + 1)) / (sampleSize + 1 - subsetSize);
764                     sampleSize++;
765                     tnPrime += (int) Math.ceil(TnPlus1 - tn);
766                     tn = TnPlus1;
767                 }
768 
769                 // Draw semi-random sample
770                 if (currentIter > tnPrime) {
771                     // during the finishing stage (sampleSize == sampleSizeStar &&
772                     // currentIter > TnPrime), draw a standard RANSAC sample
773                     // The sample contains subsetSize points selected from U_n at
774                     // random
775                     subsetSelector.computeRandomSubsets(subsetSize, subsetIndices);
776                 } else {
777                     // The sample contains subsetSize-1 points selected from
778                     // U_sampleSize_1 at random and u_sampleSize
779 
780                     subsetSelector.computeRandomSubsetsInRange(0, sampleSize, subsetSize, true,
781                             subsetIndices);
782                 }
783 
784                 transformIndices(subsetIndices, sortedIndices, transformedSubsetIndices);
785 
786                 // clear list of preliminary solutions before calling listener
787                 iterResults.clear();
788                 // compute solution for current iteration
789                 listener.estimatePreliminarSolutions(transformedSubsetIndices, iterResults);
790 
791                 // total number of inliers for a
792                 // given result
793                 int inliersCurrent;
794 
795                 // iterate over all solutions that have been found
796                 improved = false;
797                 for (final var iterResult : iterResults) {
798                     // compute inliers
799                     computeInliers(iterResult, subsetSize, inlierFactor, useInlierThresholds, inlierThreshold,
800                             residualsTemp, listener, sorter, inliersData);
801                     inliersCurrent = inliersData.getNumInliers();
802 
803                     if (inliersData.isMedianResidualImproved()) {
804                         improved = true;
805 
806                         // keep current solution
807                         bestResult = iterResult;
808 
809                         // update estimated thresholds to be used as stop
810                         // criterion
811                         threshold = inliersData.getEstimatedThreshold();
812                     }
813 
814                     if (inliersCurrent > inliersBest) {
815                         // update best number of inliers
816                         inliersBest = inliersCurrent;
817                         // keep current solution
818                         bestResult = iterResult;
819 
820                         keepInliersData(inliersData, totalSamples);
821                         // create new instance after keeping inlier data
822                         inliersData = inliersData.createCopy();
823 
824                         // select new termination length sampleSizeStar if possible
825                         // only when a new sample is better than the others found
826                         // so far
827 
828                         // best value found so far in terms of inliers ratio
829                         var sampleSizeBest = totalSamples;
830                         var inliersSampleSizeBest = inliersCurrent;
831                         // test value for the termination length
832                         int sampleSizeTest;
833                         // number of inliers for that test value
834                         int inliersSampleSizeTest;
835                         var epsilonSampleSizeBest = (double) inliersSampleSizeBest / (double) sampleSizeBest;
836 
837                         for (sampleSizeTest = totalSamples, inliersSampleSizeTest = inliersCurrent;
838                              sampleSizeTest > subsetSize; sampleSizeTest--) {
839                             // Loop invariants:
840                             // - inliersSampleSizeTest is the number of inliers
841                             //   for the sampleSizeTest first correspondences
842                             // - sampleSizeBest is the value between
843                             //   sampleSizeTest+1 and totalSamples that maximizes
844                             //   the ratio inliersSampleSizeBest/sampleSizeBest
845 
846                             // - Non-randomness: In >= imin(n*)
847                             // - Maximality: the number of samples that were drawn
848                             //   so far must be enough so that the probability of
849                             //   having missed a set of inliers is below eta=0.01.
850                             //   This is the classical RANSAC termination criterion,
851                             //   except that it takes into account only the
852                             //   sampleSize first samples (not the total number
853                             //   of samples)
854                             //   kNStar = log(eta0) / log(1 - (inliersNStar/
855                             //   sampleSizeStar)^subsetSize
856                             //   We have to minimize kNStar, e.g. maximize
857                             //   inliersNStar/sampleSizeStar, a straightforward
858                             //   implementation would use the following test:
859                             //   if(inliersSampleSizeTest > epsilonSampleSizeBest *
860                             //   sampleSizeTest){ ... blah blah blah
861                             //   However, since In is binomial, and in the case of
862                             //   evenly distributed inliers, a better test would be
863                             //   to reduce sampleSizeStar only if there's a
864                             //   significant improvement in epsilon. Thus, we use a
865                             //   Chi-squared test (P=0.10), together with the normal
866                             //   approximation to the binomial (mu =
867                             //   epsilonSampleSizeStart * sampleSizeTest, sigma =
868                             //   sqrt(sampleSizeTest * epsilonSampleSizeStar * (1 -
869                             //   epsilonSampleSizeStar))).
870                             //   There is a significant difference between the two
871                             //   tests (e.g. with the computeInliers function
872                             //   provided)
873                             //   We do the cheap test first, and the expensive test
874                             //   only if the cheap one passes
875                             if ((inliersSampleSizeTest * sampleSizeBest > inliersSampleSizeBest * sampleSizeTest)
876                                     && (inliersSampleSizeTest > epsilonSampleSizeBest * sampleSizeTest
877                                     + Math.sqrt(sampleSizeTest * epsilonSampleSizeBest
878                                     * (1.0 - epsilonSampleSizeBest) * CHI_SQUARED))) {
879 
880                                 if (inliersSampleSizeTest < imin(subsetSize, sampleSizeTest, beta)) {
881                                     // equation not satisfied, no need to test for
882                                     // smaller sampleSizeTest values anyway
883 
884                                     // jump out of the for sampleSizeTest loop
885                                     break;
886                                 }
887                                 sampleSizeBest = sampleSizeTest;
888                                 inliersSampleSizeBest = inliersSampleSizeTest;
889                                 epsilonSampleSizeBest = (double) inliersSampleSizeBest / (double) sampleSizeBest;
890                             }
891 
892                             // prepare for next loop iteration
893                             inliersSampleSizeTest -= inliers.get(sortedIndices[sampleSizeTest - 1]) ? 1 : 0;
894                         }
895 
896                         // is the best one we found even better than sampleSizeStar?
897                         if (inliersSampleSizeBest * sampleSizeStar > inliersNStar * sampleSizeBest) {
898 
899                             // update all values
900                             sampleSizeStar = sampleSizeBest;
901                             inliersNStar = inliersSampleSizeBest;
902                             kNStar = computeIterations((double) inliersNStar
903                                             / (double) sampleSizeStar, subsetSize, 1.0 - eta0);
904                         }
905                     }
906                 }
907 
908                 continueIteration = (currentIter < maxIterations);
909                 if (useInlierThresholds && stopThresholdEnabled) {
910                     // if inlier threshold is being used, and stop threshold is
911                     // enabled, then stop the algorithm if threshold determined
912                     // by median of residuals has a value lower than inlier
913                     // threshold
914                     continueIteration &= (threshold > inlierThreshold);
915                 }
916 
917                 if (!improved) {
918                     continueIteration &= ((inliersBest < inliersMin) || (currentIter < kNStar))
919                             && (currentIter < iters);
920                 }
921 
922                 listener.onEstimateNextIteration(this, currentIter);
923             }
924 
925             // no solution could be found after completing all iterations
926             if (bestResult == null) {
927                 throw new RobustEstimatorException();
928             }
929 
930             listener.onEstimateEnd(this);
931 
932             return bestResult;
933         } catch (final SubsetSelectorException | SortingException e) {
934             throw new RobustEstimatorException(e);
935         } finally {
936             locked = false;
937         }
938     }
939 
940     /**
941      * Returns data about inliers once estimation has been done.
942      *
943      * @return data about inliers or null if estimation has not been done.
944      */
945     @Override
946     public InliersData getInliersData() {
947         return getBestInliersData();
948     }
949 
950     /**
951      * Returns method being used for robust estimation.
952      *
953      * @return method being used for robust estimation.
954      */
955     @Override
956     public RobustEstimatorMethod getMethod() {
957         return RobustEstimatorMethod.PROMEDS;
958     }
959 
960     /**
961      * Transforms indices picked by the subset selector into the indices where
962      * samples are actually localed by taking into account their original
963      * position before sorting quality scores.
964      *
965      * @param subsetIndices            indices picked by the subset selector. These are
966      *                                 positions after sorting. Must have the subset length.
967      * @param sortedIndices            indices relating sorted positions to their original
968      *                                 positions. Each position i-th in the array refers to the original
969      *                                 position before sorting. Must have the number of samples length.
970      * @param transformedSubsetIndices array where result is stored. Must have
971      *                                 the subset length.
972      */
973     private static void transformIndices(
974             final int[] subsetIndices, final int[] sortedIndices, final int[] transformedSubsetIndices) {
975         final var length = transformedSubsetIndices.length;
976         for (var i = 0; i < length; i++) {
977             transformedSubsetIndices[i] = sortedIndices[subsetIndices[i]];
978         }
979     }
980 
981     /**
982      * Computes inliers data for current iteration.
983      *
984      * @param <T>                 type of result to be estimated.
985      * @param iterResult          result to be tested on current iteration.
986      * @param subsetSize          subset sample size to be picked on each iteration.
987      * @param inlierFactor        factor to adjust threshold to determine whether
988      *                            samples are inliers or not.
989      * @param useInlierThresholds true to use thresholds to determine inliers,
990      *                            false otherwise.
991      * @param inlierThreshold     threshold to determine which samples are inliers.
992      * @param residualsTemp       temporal array to store residuals, since median
993      *                            computation requires modifying the original array.
994      * @param listener            listener to obtain residuals for samples.
995      * @param sorter              sorter instance to compute median of residuals.
996      * @param inliersData         inliers data to be reused on each iteration
997      */
998     private static <T> void computeInliers(
999             final T iterResult, final int subsetSize, final double inlierFactor, final boolean useInlierThresholds,
1000             final double inlierThreshold, final double[] residualsTemp, final LMedSRobustEstimatorListener<T> listener,
1001             final Sorter<Double> sorter, final PROMedSInliersData inliersData) {
1002 
1003         final var residuals = inliersData.getResiduals();
1004         final var lmedsInliers = inliersData.getInliersLMedS();
1005         final var msacInliers = inliersData.getInliersMSAC();
1006         var bestMedianResidual = inliersData.getBestMedianResidual();
1007         var medianResidualImproved = false;
1008 
1009         final var totalSamples = residuals.length;
1010 
1011         for (var i = 0; i < totalSamples; i++) {
1012             residuals[i] = Math.abs(listener.computeResidual(iterResult, i));
1013         }
1014 
1015         System.arraycopy(residuals, 0, residualsTemp, 0, residuals.length);
1016         final var medianResidual = sorter.median(residualsTemp);
1017         if (medianResidual < bestMedianResidual) {
1018             bestMedianResidual = medianResidual;
1019             medianResidualImproved = true;
1020         }
1021 
1022         final var standardDeviation = STD_CONSTANT * (1.0 + 5.0 / (totalSamples - subsetSize))
1023                 * Math.sqrt(medianResidual);
1024         final var normEstimatedThreshold = inlierFactor * medianResidual;
1025 
1026         // determine which points are inliers
1027         var numInliers = 0;
1028         // by default if thresholds are not used
1029         var lmedsInlierModelEnabled = true;
1030         if (useInlierThresholds) {
1031             var numInliersMsac = 0;
1032             var numInliersLmedS = 0;
1033             for (var i = 0; i < totalSamples; i++) {
1034                 if (residuals[i] <= normEstimatedThreshold) {
1035                     numInliersLmedS++;
1036                     lmedsInliers.set(i);
1037                 } else {
1038                     lmedsInliers.clear(i);
1039                 }
1040                 if (residuals[i] <= inlierThreshold) {
1041                     numInliersMsac++;
1042                     msacInliers.set(i);
1043                 } else {
1044                     msacInliers.clear(i);
1045                 }
1046             }
1047 
1048             // keep model with smaller number of inliers (to be more restrictive)
1049             lmedsInlierModelEnabled = numInliersLmedS < numInliersMsac;
1050             numInliers = lmedsInlierModelEnabled ? numInliersLmedS : numInliersMsac;
1051         } else {
1052 
1053             for (var i = 0; i < totalSamples; i++) {
1054                 if (residuals[i] <= normEstimatedThreshold) {
1055                     numInliers++;
1056                     lmedsInliers.set(i);
1057                 } else {
1058                     lmedsInliers.clear(i);
1059                 }
1060             }
1061         }
1062 
1063         // store values in inliers data, only if residuals improve
1064         if (medianResidualImproved) {
1065             inliersData.update(bestMedianResidual, standardDeviation, lmedsInlierModelEnabled, residuals, numInliers,
1066                     medianResidual, normEstimatedThreshold, true);
1067         } else {
1068             inliersData.medianResidualImproved = false;
1069         }
1070     }
1071 
1072     /**
1073      * Obtains indices of samples corresponding to samples ordered in descending
1074      * quality scores.
1075      *
1076      * @param qualityScores quality scores associated to each sample to be used
1077      *                      to obtain indices to sort samples in descending order of quality values.
1078      * @return indices to sort samples in descending order of quality values.
1079      * @throws SortingException if sorting fails.
1080      */
1081     private static int[] computeSortedQualityIndices(double[] qualityScores) throws SortingException {
1082         final var sorter = Sorter.<Double>create();
1083         final var qualityScoresCopy = Arrays.copyOf(qualityScores, qualityScores.length);
1084         // this method modifies quality scores copy array because it gets sorted
1085         // in ascending order. Indices contains indices of samples corresponding
1086         // to quality scores ordered in ascending order
1087         final var indices = sorter.sortWithIndices(qualityScoresCopy);
1088 
1089         // reverse indices so we have indices of samples ordered in descending
1090         // order of quality
1091         reverse(indices);
1092 
1093         return indices;
1094     }
1095 
1096     /**
1097      * Reverses provided array.
1098      *
1099      * @param array array to be reversed.
1100      */
1101     private static void reverse(final int[] array) {
1102         var length = array.length;
1103         for (var i = 0; i < length / 2; i++) {
1104             var temp = array[i];
1105             var pos = length - 1 - i;
1106             array[i] = array[pos];
1107             array[pos] = temp;
1108         }
1109     }
1110 
1111     /**
1112      * Computes number of required iterations to achieve required confidence
1113      * with current probability of inlier and sample subset size.
1114      *
1115      * @param probInlier probability of inlier.
1116      * @param subsetSize sample subset size.
1117      * @param confidence required confidence of result.
1118      * @return number of required iterations.
1119      */
1120     private static int computeIterations(final double probInlier, final int subsetSize, final double confidence) {
1121 
1122         // compute number of times the algorithm needs to be executed depending
1123         // on number of inliers respect total points to achieve with probability
1124         // confidence that we have all inliers and probability 1 - confidence
1125         // that we have some outliers
1126         final var probSubsetAllInliers = Math.pow(probInlier, subsetSize);
1127         if (Math.abs(probSubsetAllInliers) < Double.MIN_VALUE || Double.isNaN(probSubsetAllInliers)) {
1128             return Integer.MAX_VALUE;
1129         } else {
1130             final var logProbSomeOutliers = Math.log(1.0 - probSubsetAllInliers);
1131             if (Math.abs(logProbSomeOutliers) < Double.MIN_VALUE || Double.isNaN(logProbSomeOutliers)) {
1132                 return Integer.MAX_VALUE;
1133             } else {
1134                 return (int) Math.ceil(Math.abs(Math.log(1.0 - confidence) / logProbSomeOutliers));
1135             }
1136         }
1137     }
1138 
1139     /**
1140      * Non randomness states that i-m (where i is the cardinal of the set of
1141      * inliers for a wrong model) follows the binomial distribution B(n,beta).
1142      * For n big enough, B(n,beta) approximates to normal distribution N(mu,
1143      * sigma^2) by the central limit theorem, with mu = n*beta and sigma =
1144      * sqrt(n*beta*(1 - beta)).
1145      * Psi, the probability that In_star out of n_star data points are by chance
1146      * inliers to an arbitrary incorrect model, is set to 0.05 (5%, as in the
1147      * original paper), and you must change the Chi2 value if you chose a
1148      * different value for psi.
1149      *
1150      * @param subsetSize sample subset size.
1151      * @param sampleSize total number of samples.
1152      * @param beta       beta value.
1153      * @return i-m.
1154      */
1155     private static int imin(final int subsetSize, final int sampleSize, final double beta) {
1156         final var mu = sampleSize * beta;
1157         final var sigma = Math.sqrt(sampleSize * beta * (1.0 - beta));
1158 
1159         return (int) Math.ceil(subsetSize + mu + sigma * Math.sqrt(CHI_SQUARED));
1160     }
1161 
1162     /**
1163      * Keeps inliers data stored and initializes a new one with proper
1164      * initial values.
1165      *
1166      * @param inliersData  inliers data to be stored.
1167      * @param totalSamples total number of samples.
1168      */
1169     private void keepInliersData(PROMedSInliersData inliersData, final int totalSamples) {
1170         // keep the best inliers data corresponding to best solution,
1171         // in case it can be useful along with the result
1172         bestInliersData = inliersData;
1173 
1174         // create new inliers data instance until a new best solution
1175         // is found
1176         final var bestMedianResidual = inliersData.getBestMedianResidual();
1177         inliersData = new PROMedSInliersData(totalSamples);
1178         // update the best median residual on new instance so that
1179         // only better solutions that are found later can update
1180         // inliers data
1181         inliersData.update(bestMedianResidual, inliersData.getStandardDeviation(),
1182                 inliersData.isLMedSInlierModelEnabled(), inliersData.getResiduals(), inliersData.getNumInliers(),
1183                 bestMedianResidual, inliersData.getEstimatedThreshold(), false);
1184     }
1185 
1186     /**
1187      * Contains data related to inliers estimated in one iteration.
1188      */
1189     public static class PROMedSInliersData extends InliersData {
1190         /**
1191          * Best median of error found so far taking into account all provided
1192          * samples.
1193          */
1194         private double bestMedianResidual;
1195 
1196         /**
1197          * Standard deviation of error among all provided samples respect to
1198          * currently estimated result.
1199          */
1200         private double standardDeviation;
1201 
1202         /**
1203          * Inliers considering LMedS model.
1204          */
1205         private BitSet inliersLmeds;
1206 
1207         /**
1208          * Inliers considering MSAC model.
1209          */
1210         private BitSet inliersMsac;
1211 
1212         /**
1213          * Indicates whether LMedS or MSAC inlier model is enabled.
1214          */
1215         private boolean lmedsInlierModelEnabled;
1216 
1217         /**
1218          * Median of error found on current iteration among all provided
1219          * samples.
1220          */
1221         private double medianResidual;
1222 
1223         /**
1224          * Estimated threshold to determine whether samples are inliers or not.
1225          */
1226         private double estimatedThreshold;
1227 
1228         /**
1229          * Indicates whether median residual computed in current iteration has
1230          * improved respect to previous iterations.
1231          */
1232         private boolean medianResidualImproved;
1233 
1234         /**
1235          * Constructor.
1236          *
1237          * @param totalSamples total number of samples.
1238          */
1239         PROMedSInliersData(final int totalSamples) {
1240             bestMedianResidual = standardDeviation = medianResidual = estimatedThreshold = Double.MAX_VALUE;
1241             inliersLmeds = new BitSet(totalSamples);
1242             inliersMsac = new BitSet(totalSamples);
1243             lmedsInlierModelEnabled = true;
1244             residuals = new double[totalSamples];
1245             numInliers = 0;
1246             medianResidualImproved = false;
1247         }
1248 
1249         /**
1250          * Returns efficient array indicating which samples are considered
1251          * inliers and which ones aren't.
1252          *
1253          * @return array indicating which samples are considered inliers and
1254          * which ones aren't.
1255          */
1256         @Override
1257         public BitSet getInliers() {
1258             return lmedsInlierModelEnabled ? inliersLmeds : inliersMsac;
1259         }
1260 
1261         /**
1262          * Creates a copy of inlier data.
1263          *
1264          * @return copy of inlier data.
1265          */
1266         PROMedSInliersData createCopy() {
1267             final var result = new PROMedSInliersData(residuals.length);
1268             result.bestMedianResidual = bestMedianResidual;
1269             result.standardDeviation = standardDeviation;
1270             result.medianResidual = medianResidual;
1271             result.estimatedThreshold = estimatedThreshold;
1272             result.inliersLmeds = (BitSet) inliersLmeds.clone();
1273             result.inliersMsac = (BitSet) inliersMsac.clone();
1274             result.lmedsInlierModelEnabled = lmedsInlierModelEnabled;
1275             result.residuals = Arrays.copyOf(residuals, residuals.length);
1276             result.numInliers = numInliers;
1277             result.medianResidualImproved = medianResidualImproved;
1278 
1279             return result;
1280         }
1281 
1282         /**
1283          * Returns best median of error found so far taking into account all
1284          * provided samples.
1285          *
1286          * @return best median of error found so far taking into account all
1287          * provided samples.
1288          */
1289         double getBestMedianResidual() {
1290             return bestMedianResidual;
1291         }
1292 
1293         /**
1294          * Returns standard deviation of error among all provided samples
1295          * respect to currently estimated result.
1296          *
1297          * @return standard deviation of error among all provided samples
1298          * respect to currently estimated result.
1299          */
1300         double getStandardDeviation() {
1301             return standardDeviation;
1302         }
1303 
1304         /**
1305          * Returns inliers considering LMedS model.
1306          *
1307          * @return inliers considering LMedS model.
1308          */
1309         BitSet getInliersLMedS() {
1310             return inliersLmeds;
1311         }
1312 
1313         /**
1314          * Returns inliers considering MSAC model.
1315          *
1316          * @return inliers considering MSAC model.
1317          */
1318         BitSet getInliersMSAC() {
1319             return inliersMsac;
1320         }
1321 
1322         /**
1323          * Returns boolean indicating whether LMedS or MSAC inlier model is
1324          * enabled. If true, estimated threshold was used to determine inliers,
1325          * if false only median of residuals was used.
1326          *
1327          * @return true if LMedS model is used, false if MSAC model is used.
1328          */
1329         boolean isLMedSInlierModelEnabled() {
1330             return lmedsInlierModelEnabled;
1331         }
1332 
1333         /**
1334          * Returns estimated threshold to determine whether samples are inliers
1335          * or not.
1336          *
1337          * @return estimated threshold to determine whether samples are inliers
1338          * or not.
1339          */
1340         public double getEstimatedThreshold() {
1341             return estimatedThreshold;
1342         }
1343 
1344         /**
1345          * Returns boolean indicating whether median residual computed in
1346          * current iteration has improved respect to previous iterations.
1347          *
1348          * @return true if median residual improved, false otherwise.
1349          */
1350         boolean isMedianResidualImproved() {
1351             return medianResidualImproved;
1352         }
1353 
1354         /**
1355          * Updates data contained in this instance.
1356          *
1357          * @param bestMedianResidual      best median of error found so far taking
1358          *                                into account all provided samples.
1359          * @param standardDeviation       standard deviation of error among all
1360          *                                provided samples respect to currently estimated result.
1361          * @param lmedsInlierModelEnabled indicates whether the LMedS or MSAC
1362          *                                inlier model is used.
1363          * @param residuals               residuals obtained for each sample of data.
1364          * @param numInliers              number of inliers found on current iteration.
1365          * @param medianResidual          median of error found on current iteration
1366          *                                among all provided samples.
1367          * @param estimatedThreshold      estimated threshold to determine whether
1368          *                                samples are inliers or not.
1369          * @param medianResidualImproved  indicates whether median residual
1370          *                                computed in current iteration has improved respect to previous
1371          *                                iteration.
1372          */
1373         protected void update(
1374                 final double bestMedianResidual, final double standardDeviation, final boolean lmedsInlierModelEnabled,
1375                 final double[] residuals, final int numInliers, final double medianResidual,
1376                 final double estimatedThreshold, final boolean medianResidualImproved) {
1377             this.bestMedianResidual = bestMedianResidual;
1378             this.standardDeviation = standardDeviation;
1379             this.lmedsInlierModelEnabled = lmedsInlierModelEnabled;
1380             this.residuals = residuals;
1381             this.numInliers = numInliers;
1382             this.medianResidual = medianResidual;
1383             this.estimatedThreshold = estimatedThreshold;
1384             this.medianResidualImproved = medianResidualImproved;
1385         }
1386     }
1387 
1388 }