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 }