View Javadoc
1   /*
2    * Copyright (C) 2016 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  
22  import java.util.ArrayList;
23  import java.util.BitSet;
24  
25  /**
26   * This class implements LMedS (Least Median of Squares) algorithm to robustly
27   * estimate a data model.
28   * LMedS is based on the idea that a given proportion of outliers exists in the
29   * total amount of samples provided. This algorithm tries to iteratively find
30   * the beast subset of samples picking the ones with the least median of error.
31   * To determine whether a sample is an outlier or not, and the estimated error
32   * for each sample, provided listener must be used.
33   * Contrary to RANSAC, this algorithm does not require a fixed threshold to be
34   * set to determine whether samples are inliers or not. Instead, threshold is
35   * computed dynamically. Because of that LMedS typically produces results with
36   * larger error than RANSAC having a similar computational cost, because samples
37   * usually contain a large error. Hence, if threshold is known in advance for a
38   * given estimation, RANSAC should be preferred rather than LMedS.
39   * On the contrary, if it can be ensured that samples are very accurate except
40   * for some outliers, then LMedS becomes much more accurate than RANSAC because
41   * it typically converges to a solution with a very small threshold. However,
42   * typically inlier samples tend to have certain error, and in practice LMedS
43   * produces results with a similar accuracy and computational cost than RANSAC.
44   *
45   * @param <T> type of object to be estimated.
46   */
47  @SuppressWarnings("DuplicatedCode")
48  public class LMedSRobustEstimator<T> extends RobustEstimator<T> {
49  
50      /**
51       * Constant defining default confidence of the estimated result, which is
52       * 99%. This means that with a probability of 99% estimation will be
53       * accurate because chosen sub-samples will be inliers.
54       */
55      public static final double DEFAULT_CONFIDENCE = 0.99;
56  
57      /**
58       * Default maximum allowed number of iterations.
59       */
60      public static final int DEFAULT_MAX_ITERATIONS = 5000;
61  
62      /**
63       * Minimum allowed confidence value.
64       */
65      public static final double MIN_CONFIDENCE = 0.0;
66  
67      /**
68       * Maximum allowed confidence value.
69       */
70      public static final double MAX_CONFIDENCE = 1.0;
71  
72      /**
73       * Minimum allowed number of iterations.
74       */
75      public static final int MIN_ITERATIONS = 1;
76  
77      /**
78       * Default value to be used for stop threshold. Stop threshold can be used
79       * to keep the algorithm iterating in case that best threshold is not small
80       * enough. Once a better solution is found yielding a threshold smaller than
81       * this value, the algorithm will stop.
82       */
83      public static final double DEFAULT_STOP_THRESHOLD = 0.0;
84  
85      /**
86       * Minimum allowed stop threshold value.
87       */
88      public static final double MIN_STOP_THRESHOLD = 0.0;
89  
90      /**
91       * Default factor to normalize threshold to determine inliers. This factor
92       * can be used to increase or lower the dynamically computed threshold so
93       * that the algorithm becomes more or less accurate. The stricter the
94       * threshold (lower factor), the more time the algorithm will need to
95       * converge, if it can converge. By default, the factor is 1.0, which makes
96       * the threshold to be computed as the median of residuals.
97       */
98      public static final double DEFAULT_INLIER_FACTOR = 1.0; // 1.5 would also be reasonable
99  
100     /**
101      * Minimum allowed value for inlier factor.
102      */
103     public static final double MIN_INLER_FACTOR = 0.0;
104 
105     /**
106      * Constant to estimate standard deviation of residuals based on their
107      * median.
108      */
109     public static final double STD_CONSTANT = 1.4826;
110 
111     /**
112      * Amount of confidence expressed as a value between 0 and 1.0 (which is
113      * equivalent to 100%). The amount of confidence indicates the probability
114      * that the estimated result is correct. Usually this value will be close
115      * to 1.0, but not exactly 1.0.
116      */
117     private double confidence;
118 
119     /**
120      * Maximum allowed number of iterations. When the maximum number of
121      * iterations is exceeded, result will not be available, however an
122      * approximate result will be available for retrieval.
123      */
124     private int maxIterations;
125 
126     /**
127      * Instance in charge of picking random subsets of samples.
128      */
129     private SubsetSelector subsetSelector;
130 
131     /**
132      * Number of iterations to be done to obtain required confidence.
133      */
134     private int iters;
135 
136     /**
137      * Best solution that has been found so far during an estimation.
138      */
139     private T bestResult;
140 
141     /**
142      * Data related to inliers found for best result.
143      */
144     private LMedSInliersData bestInliersData;
145 
146     /**
147      * Threshold to be used to keep the algorithm iterating in case that
148      * best threshold is not small enough. Once a better solution is found
149      * yielding a threshold smaller than this value, the algorithm will stop.
150      */
151     private double stopThreshold;
152 
153     /**
154      * Factor to normalize threshold to determine inliers. This factor can be
155      * used to increase or lower the dynamically computed threshold so that the
156      * algorithm becomes more or less accurate. The stricter the threshold
157      * (lower factor), the more time the algorithm will need to converge, if
158      * it can converge. By default, the factor is 1.0, which makes the threshold
159      * to be computed as the median of residuals.
160      */
161     private double inlierFactor;
162 
163 
164     /**
165      * Constructor.
166      */
167     public LMedSRobustEstimator() {
168         super();
169         confidence = DEFAULT_CONFIDENCE;
170         maxIterations = DEFAULT_MAX_ITERATIONS;
171         iters = maxIterations;
172         bestResult = null;
173         bestInliersData = null;
174         stopThreshold = DEFAULT_STOP_THRESHOLD;
175         inlierFactor = DEFAULT_INLIER_FACTOR;
176     }
177 
178     /**
179      * Constructor with listener.
180      *
181      * @param listener listener to be notified of events such as when estimation
182      *                 starts, ends or its progress significantly changes, as well as in charge
183      *                 of picking samples and doing per-iteration estimations.
184      */
185     public LMedSRobustEstimator(final LMedSRobustEstimatorListener<T> listener) {
186         super(listener);
187         confidence = DEFAULT_CONFIDENCE;
188         maxIterations = DEFAULT_MAX_ITERATIONS;
189         iters = maxIterations;
190         bestResult = null;
191         bestInliersData = null;
192         stopThreshold = DEFAULT_STOP_THRESHOLD;
193         inlierFactor = DEFAULT_INLIER_FACTOR;
194     }
195 
196     /**
197      * Returns amount of confidence expressed as a value between 0 and 1.0
198      * (which is equivalent to 100%). The amount of confidence indicates the
199      * probability that the estimated result is correct. Usually this value will
200      * be close to 1.0, but not exactly 1.0.
201      *
202      * @return amount of confidence as a value between 0.0 and 1.0.
203      */
204     public double getConfidence() {
205         return confidence;
206     }
207 
208     /**
209      * Sets amount of confidence expressed as a value between 0 and 1.0 (which
210      * is equivalent to 100%). The amount of confidence indicates the
211      * probability that the estimated result is correct. Usually this value will
212      * be close to 1.0, but not exactly 1.0.
213      *
214      * @param confidence confidence to be set as a value between 0.0 and 1.0.
215      * @throws IllegalArgumentException if provided value is not between 0.0 and
216      *                                  1.0.
217      * @throws LockedException          if this estimator is locked because an estimation
218      *                                  is being computed.
219      */
220     public void setConfidence(final double confidence) throws LockedException {
221         if (isLocked()) {
222             throw new LockedException();
223         }
224         if (confidence < MIN_CONFIDENCE || confidence > MAX_CONFIDENCE) {
225             throw new IllegalArgumentException();
226         }
227         this.confidence = confidence;
228     }
229 
230     /**
231      * Maximum allowed number of iterations. When the maximum number of
232      * iterations is exceeded, result will not be available, however an
233      * approximate result will be available for retrieval.
234      *
235      * @return maximum allowed number of iterations.
236      */
237     public int getMaxIterations() {
238         return maxIterations;
239     }
240 
241     /**
242      * Sets maximum allowed number of iterations. When the maximum number of
243      * iterations is exceeded, result will not be available, however an
244      * approximate result will be available for retrieval.
245      *
246      * @param maxIterations maximum allowed number of iterations to be set.
247      * @throws IllegalArgumentException if provided value is less than 1.
248      * @throws LockedException          if this estimator is locked because an estimation
249      *                                  is being computed.
250      */
251     public void setMaxIterations(final int maxIterations) throws LockedException {
252         if (isLocked()) {
253             throw new LockedException();
254         }
255         if (maxIterations < MIN_ITERATIONS) {
256             throw new IllegalArgumentException();
257         }
258         this.maxIterations = maxIterations;
259     }
260 
261     /**
262      * Returns threshold to be used to keep the algorithm iterating in case that
263      * best threshold is not small enough. Once a better solution is found
264      * yielding a threshold smaller than this value, the algorithm will stop.
265      *
266      * @return threshold to be used to keep the algorithm iterating in case that
267      * best threshold is not small enough.
268      */
269     public double getStopThreshold() {
270         return stopThreshold;
271     }
272 
273     /**
274      * Sets threshold to be used to keep the algorithm iterating in case that
275      * best threshold is not small enough. Once a better solution is found
276      * yielding a threshold smaller than this vlaue, the algorithm will stop.
277      *
278      * @param stopThreshold threshold to be used to keep the algorithm iterating
279      *                      in case that best threshold is not small enough.
280      * @throws IllegalArgumentException if provided value is less or equal than
281      *                                  0.0.
282      * @throws LockedException          if this estimator is locked because an estimation
283      *                                  is being computed.
284      */
285     public void setStopThreshold(final double stopThreshold) throws LockedException {
286         if (isLocked()) {
287             throw new LockedException();
288         }
289         if (stopThreshold < MIN_STOP_THRESHOLD) {
290             throw new IllegalArgumentException();
291         }
292 
293         this.stopThreshold = stopThreshold;
294     }
295 
296     /**
297      * Returns factor to normalize or adjust threshold to determine inliers.
298      * This factor can be used to increase or lower the dynamically computed
299      * threshold so that the algorithm becomes more or less accurate. The
300      * stricter the threshold (lower factor), the more time the algorithm will
301      * need to converge, if it can converge. By default, the factor is 1.0, which
302      * makes the threshold to be computed as the median of residuals.
303      *
304      * @return factor to normalize threshold to determine inliers.
305      */
306     public double getInlierFactor() {
307         return inlierFactor;
308     }
309 
310     /**
311      * Sets factor to normalize or adjust threshold to determine inliers.
312      * This factor can be used to increase or lower the dynamically computed
313      * threshold so that the algorithm becomes more or less accurate. The
314      * stricter the threshold (lower factor), the more time the algorithm will
315      * need to converge, if it can converge. By default, the factor is 1.0, which
316      * makes the threshold to be computed as the median of residuals.
317      *
318      * @param inlierFactor inlier factor to be set.
319      * @throws IllegalArgumentException if provided value is less or equal than
320      *                                  0.0.
321      * @throws LockedException          if this estimator is locked because an estimation
322      *                                  is being computed.
323      */
324     public void setInlierFactor(final double inlierFactor) throws LockedException {
325         if (isLocked()) {
326             throw new LockedException();
327         }
328         if (inlierFactor <= MIN_INLER_FACTOR) {
329             throw new IllegalArgumentException();
330         }
331 
332         this.inlierFactor = inlierFactor;
333     }
334 
335     /**
336      * Returns number of iterations to be done to obtain required confidence.
337      *
338      * @return number of iterations to be done to obtain required confidence.
339      */
340     public int getNIters() {
341         return iters;
342     }
343 
344     /**
345      * Returns best solution that has been found so far during an estimation.
346      *
347      * @return best solution that has been found so far during an estimation.
348      */
349     public T getBestResult() {
350         return bestResult;
351     }
352 
353     /**
354      * Returns data related to inliers found for best result.
355      *
356      * @return data related to inliers found for best result.
357      */
358     public LMedSInliersData getBestInliersData() {
359         return bestInliersData;
360     }
361 
362     /**
363      * Indicates if estimator is ready to start the estimation process.
364      *
365      * @return true if ready, false otherwise.
366      */
367     @Override
368     public boolean isReady() {
369         if (!super.isReady()) {
370             return false;
371         }
372         return (listener instanceof LMedSRobustEstimatorListener);
373     }
374 
375     /**
376      * Robustly estimates an instance of T.
377      *
378      * @return estimated object.
379      * @throws LockedException          if robust estimator is locked.
380      * @throws NotReadyException        if provided input data is not enough to start
381      *                                  the estimation.
382      * @throws RobustEstimatorException if estimation fails for any reason
383      *                                  (i.e. numerical instability, no solution available, etc).
384      */
385     @Override
386     public T estimate() throws LockedException, NotReadyException, RobustEstimatorException {
387         if (isLocked()) {
388             throw new LockedException();
389         }
390         if (!isReady()) {
391             throw new NotReadyException();
392         }
393 
394         try {
395             final var listener = (LMedSRobustEstimatorListener<T>) this.listener;
396 
397             locked = true;
398 
399             listener.onEstimateStart(this);
400 
401             final var totalSamples = listener.getTotalSamples();
402             final var subsetSize = listener.getSubsetSize();
403             int bestNumInliers;
404             var threshold = Double.MAX_VALUE;
405             iters = Integer.MAX_VALUE;
406             int newNIters;
407             var currentIter = 0;
408             // reusable list that will contain preliminary solutions on each
409             // iteration
410             final var iterResults = new ArrayList<T>();
411             bestResult = null; // best result found so far
412             // progress and previous progress to determine when progress
413             // notification must occur
414             var previousProgress = 0.0f;
415             float progress;
416             // indices of subset picked in one iteration
417             final var subsetIndices = new int[subsetSize];
418             final var residualsTemp = new double[totalSamples];
419             // indicates if result improved
420             boolean improved;
421             // indicates whether algorithm must continue iterating
422             var continueIteration = true;
423 
424             if (subsetSelector == null) {
425                 // create new subset selector
426                 subsetSelector = SubsetSelector.create(totalSamples);
427             } else {
428                 // set number of samples to current subset selector
429                 subsetSelector.setNumSamples(totalSamples);
430             }
431 
432             // data related to inliers
433             var inliersData = new LMedSInliersData(totalSamples);
434             // sorter to compute medians
435             final var sorter = Sorter.<Double>create();
436 
437             while (continueIteration) {
438                 // generate a random subset of samples
439                 subsetSelector.computeRandomSubsets(subsetSize, subsetIndices);
440 
441                 // clear list of preliminary solutions before calling listener
442                 iterResults.clear();
443                 // compute solution for current iteration
444                 listener.estimatePreliminarSolutions(subsetIndices, iterResults);
445 
446                 // iterate over all solutions that have been found
447                 improved = false;
448                 for (final var iterResult : iterResults) {
449                     // compute inliers
450                     computeInliers(iterResult, subsetSize, inlierFactor, residualsTemp, listener, sorter, inliersData);
451 
452                     // save solution that produces the best residual
453                     if (inliersData.isMedianResidualImproved()) {
454                         improved = true;
455 
456                         // keep current solution
457                         bestResult = iterResult;
458 
459                         // keep the best inliers data corresponding to best solution,
460                         // in case it can be useful along with the result
461                         bestInliersData = inliersData;
462 
463                         // recompute number of times the algorithm needs to be
464                         // executed depending on current number of inliers to
465                         // achieve with probability mConfidence that we have
466                         // inliers and probability 1 - mConfidence that we have
467                         // outliers
468                         bestNumInliers = inliersData.getNumInliers();
469                         final var probInlier = ((double) bestNumInliers) / ((double) totalSamples);
470 
471                         final var probSubsetAllInliers = Math.pow(probInlier, subsetSize);
472 
473                         if (Math.abs(probSubsetAllInliers) < Double.MIN_VALUE || Double.isNaN(probSubsetAllInliers)) {
474                             newNIters = Integer.MAX_VALUE;
475                         } else {
476                             final var logProbSomeOutliers = Math.log(1.0 - probSubsetAllInliers);
477                             if (Math.abs(logProbSomeOutliers) < Double.MIN_VALUE || Double.isNaN(logProbSomeOutliers)) {
478                                 newNIters = Integer.MAX_VALUE;
479                             } else {
480                                 newNIters = (int) Math.ceil(Math.abs(Math.log(1.0 - confidence) / logProbSomeOutliers));
481                             }
482                         }
483 
484                         if (newNIters < iters) {
485                             iters = newNIters;
486                         }
487 
488                         threshold = inliersData.getEstimatedThreshold();
489 
490                         // create new inliers data instance until a new best
491                         // solution is found
492                         final var bestMedianResidual = inliersData.getBestMedianResidual();
493                         inliersData = new LMedSInliersData(totalSamples);
494                         // update the best median residual on new instance so
495                         // that only better solutions that are found later
496                         // can update inliers data
497                         inliersData.update(bestMedianResidual, inliersData.getStandardDeviation(),
498                                 inliersData.getInliers(), inliersData.getResiduals(), inliersData.getNumInliers(),
499                                 inliersData.getEstimatedThreshold(), false);
500                     }
501                 }
502 
503                 if (iters > 0) {
504                     progress = Math.min((float) currentIter / (float) iters, 1.0f);
505                 } else {
506                     progress = 1.0f;
507                 }
508                 if (progress - previousProgress > progressDelta) {
509                     previousProgress = progress;
510                     listener.onEstimateProgressChange(this, progress);
511                 }
512                 currentIter++;
513                 continueIteration = (currentIter < maxIterations) && (threshold > stopThreshold);
514                 if (!improved) {
515                     continueIteration &= (currentIter < iters);
516                 }
517 
518                 listener.onEstimateNextIteration(this, currentIter);
519             }
520 
521             // no solution could be found after completing all iterations
522             if (bestResult == null) {
523                 throw new RobustEstimatorException();
524             }
525 
526             listener.onEstimateEnd(this);
527 
528             return bestResult;
529         } catch (final SubsetSelectorException e) {
530             throw new RobustEstimatorException(e);
531         } finally {
532             locked = false;
533         }
534     }
535 
536     /**
537      * Returns data about inliers once estimation has been done.
538      *
539      * @return data about inliers or null if estimation has not been done.
540      */
541     @Override
542     public InliersData getInliersData() {
543         return getBestInliersData();
544     }
545 
546     /**
547      * Returns method being used for robust estimation.
548      *
549      * @return method being used for robust estimation.
550      */
551     @Override
552     public RobustEstimatorMethod getMethod() {
553         return RobustEstimatorMethod.LMEDS;
554     }
555 
556     /**
557      * Computes inliers data for current iteration.
558      *
559      * @param <T>           type of result to be estimated.
560      * @param iterResult    result to be tested on current iteration.
561      * @param subsetSize    subset sample size to be picked on each iteration.
562      * @param inlierFactor  factor to adjust threshold to determine whether
563      *                      samples are inliers or not.
564      * @param residualsTemp temporal array to store residuals, since median
565      *                      computation requires modifying the original array.
566      * @param listener      listener to obtain residuals for samples.
567      * @param sorter        sorter instance to compute median of residuals.
568      * @param inliersData   inliers data to be reused on each iteration.
569      */
570     private static <T> void computeInliers(
571             final T iterResult, final int subsetSize, final double inlierFactor, final double[] residualsTemp,
572             final LMedSRobustEstimatorListener<T> listener, final Sorter<Double> sorter, LMedSInliersData inliersData) {
573 
574         final var residuals = inliersData.getResiduals();
575         final var inliers = inliersData.getInliers();
576         var bestMedianResidual = inliersData.getBestMedianResidual();
577         var medianResidualImproved = false;
578 
579         final var totalSamples = residuals.length;
580 
581         for (var i = 0; i < totalSamples; i++) {
582             residuals[i] = Math.abs(listener.computeResidual(iterResult, i));
583         }
584         System.arraycopy(residuals, 0, residualsTemp, 0, residuals.length);
585         final var medianResidual = sorter.median(residualsTemp);
586         if (medianResidual < bestMedianResidual) {
587             bestMedianResidual = medianResidual;
588             medianResidualImproved = true;
589         }
590 
591         final var standardDeviation = STD_CONSTANT * (1.0 + 5.0 / (totalSamples - subsetSize))
592                 * Math.sqrt(medianResidual);
593         final var normEstimatedThreshold = inlierFactor * medianResidual;
594 
595         // determine which points are inliers
596         var numInliers = 0;
597         for (var i = 0; i < totalSamples; i++) {
598             if (residuals[i] <= normEstimatedThreshold) {
599                 numInliers++;
600                 inliers.set(i);
601             } else {
602                 inliers.clear(i);
603             }
604         }
605 
606         // store values in inliers data, only if residuals improve
607         if (medianResidualImproved) {
608             inliersData.update(bestMedianResidual, standardDeviation, inliers, residuals, numInliers,
609                     normEstimatedThreshold, true);
610         }
611     }
612 
613     /**
614      * Contains data related to inliers estimated in one iteration.
615      */
616     public static class LMedSInliersData extends InliersData {
617         /**
618          * Best median of error found so far taking into account all provided
619          * samples.
620          */
621         private double bestMedianResidual;
622 
623         /**
624          * Standard deviation of error among all provided samples respect to
625          * currently estimated result.
626          */
627         private double standardDeviation;
628 
629         /**
630          * Efficiently stores which samples are considered inliers and which
631          * ones aren't.
632          */
633         private BitSet inliers;
634 
635         /**
636          * Estimated threshold to determine whether samples are inliers or not.
637          */
638         private double estimatedThreshold;
639 
640         /**
641          * Indicates whether median residual computed in current iteration has
642          * improved respect to previous iterations.
643          */
644         private boolean medianResidualImproved;
645 
646         /**
647          * Constructor.
648          *
649          * @param totalSamples total number of samples.
650          */
651         protected LMedSInliersData(final int totalSamples) {
652             bestMedianResidual = Double.MAX_VALUE;
653             standardDeviation = Double.MAX_VALUE;
654             estimatedThreshold = Double.MAX_VALUE;
655             inliers = new BitSet(totalSamples);
656             residuals = new double[totalSamples];
657             numInliers = 0;
658             medianResidualImproved = false;
659         }
660 
661         /**
662          * Returns best median of error found so far taking into account all
663          * provided samples.
664          *
665          * @return best median of error found so far taking into account all
666          * provided samples.
667          */
668         public double getBestMedianResidual() {
669             return bestMedianResidual;
670         }
671 
672         /**
673          * Returns standard deviation of error among all provided samples
674          * respect to currently estimated result.
675          *
676          * @return standard deviation of error among all provided samples
677          * respect to currently estimated result.
678          */
679         public double getStandardDeviation() {
680             return standardDeviation;
681         }
682 
683         /**
684          * Returns efficient array indicating which samples are considered
685          * inliers and which ones aren't.
686          *
687          * @return array indicating which samples are considered inliers and
688          * which ones aren't.
689          */
690         @Override
691         public BitSet getInliers() {
692             return inliers;
693         }
694 
695         /**
696          * Returns estimated threshold to determine whether samples are inliers
697          * or not.
698          *
699          * @return estimated threshold to determine whether samples are inliers
700          * or not.
701          */
702         public double getEstimatedThreshold() {
703             return estimatedThreshold;
704         }
705 
706         /**
707          * Returns boolean indicating whether median residual computed in
708          * current iteration has improved respect to previous iterations.
709          *
710          * @return true if median residual improved, false otherwise.
711          */
712         public boolean isMedianResidualImproved() {
713             return medianResidualImproved;
714         }
715 
716         /**
717          * Updates data contained in this instance.
718          *
719          * @param bestMedianResidual     best median of error found so far taking
720          *                               into account all provided samples.
721          * @param standardDeviation      standard deviation of error among all
722          *                               provided samples respect to currently estimated result.
723          * @param inliers                efficiently stores which samples are considered
724          *                               inliers and which ones aren't.
725          * @param residuals              residuals obtained for each sample of data.
726          * @param numInliers             number of inliers found on current iteration.
727          * @param estimatedThreshold     estimated threshold to determine whether
728          *                               samples are inliers or not.
729          * @param medianResidualImproved indicates whether median residual
730          *                               computed in current iteration has improved respect to previous
731          *                               iteration.
732          */
733         protected void update(final double bestMedianResidual, final double standardDeviation,
734                               final BitSet inliers, final double[] residuals, final int numInliers,
735                               final double estimatedThreshold, final boolean medianResidualImproved) {
736             this.bestMedianResidual = bestMedianResidual;
737             this.standardDeviation = standardDeviation;
738             this.inliers = inliers;
739             this.residuals = residuals;
740             this.numInliers = numInliers;
741             this.estimatedThreshold = estimatedThreshold;
742             this.medianResidualImproved = medianResidualImproved;
743         }
744     }
745 }