View Javadoc
1   /*
2    * Copyright (C) 2017 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.geometry.estimators;
17  
18  import com.irurueta.geometry.CoordinatesType;
19  import com.irurueta.geometry.MetricTransformation2D;
20  import com.irurueta.geometry.Point2D;
21  import com.irurueta.numerical.robust.PROMedSRobustEstimator;
22  import com.irurueta.numerical.robust.PROMedSRobustEstimatorListener;
23  import com.irurueta.numerical.robust.RobustEstimator;
24  import com.irurueta.numerical.robust.RobustEstimatorException;
25  import com.irurueta.numerical.robust.RobustEstimatorMethod;
26  
27  import java.util.ArrayList;
28  import java.util.List;
29  
30  /**
31   * Finds the best metric 2D transformation for provided collections of
32   * matched 2D point using PROMedS algorithm.
33   */
34  public class PROMedSMetricTransformation2DRobustEstimator extends MetricTransformation2DRobustEstimator {
35  
36      /**
37       * Default value to be used for stop threshold. Stop threshold can be used
38       * to keep the algorithm iterating in case that best estimated threshold
39       * using median of residuals is not small enough. Once a solution is found
40       * that generates a threshold below this value, the algorithm will stop.
41       * The stop threshold can be used to prevent the LMedS algorithm iterating
42       * too many times in cases where samples have a very similar accuracy.
43       * For instance, in cases where proportion of outliers is very small (close
44       * to 0%), and samples are very accurate (i.e. 1e-6), the algorithm would
45       * iterate for a long time trying to find the best solution when indeed
46       * there is no need to do that if a reasonable threshold has already been
47       * reached.
48       * Because of this behaviour the stop threshold can be set to a value much
49       * lower than the one typically used in RANSAC, and yet the algorithm could
50       * still produce even smaller thresholds in estimated results.
51       */
52      public static final double DEFAULT_STOP_THRESHOLD = 1.0;
53  
54      /**
55       * Minimum allowed stop threshold value.
56       */
57      public static final double MIN_STOP_THRESHOLD = 0.0;
58  
59      /**
60       * Threshold to be used to keep the algorithm iterating in case that best
61       * estimated threshold using median of residuals is not small enough. Once
62       * a solution is found that generates a threshold below this value, the
63       * algorithm will stop.
64       * The stop threshold can be used to prevent the LMedS algorithm iterating
65       * too many times in cases where samples have a very similar accuracy.
66       * For instance, in cases where proportion of outliers is very small (close
67       * to 0%), and samples are very accurate (i.e. 1e-6), the algorithm would
68       * iterate for a long time trying to find the best solution when indeed
69       * there is no need to do that if a reasonable threshold has already been
70       * reached.
71       * Because of this behaviour the stop threshold can be set to a value much
72       * lower than the one typically used in RANSAC, and yet the algorithm could
73       * still produce even smaller thresholds in estimated results.
74       */
75      private double stopThreshold;
76  
77      /**
78       * Quality scores corresponding to each pair of matched points.
79       * The larger the score value the better the quality of the matching.
80       */
81      private double[] qualityScores;
82  
83      /**
84       * Constructor.
85       */
86      public PROMedSMetricTransformation2DRobustEstimator() {
87          super();
88          stopThreshold = DEFAULT_STOP_THRESHOLD;
89      }
90  
91      /**
92       * Constructor with lists of points to be used to estimate a metric 2D
93       * transformation.
94       * Points in the list located at the same position are considered to be
95       * matched. Hence, both lists must have the same size, and their size must
96       * be greater or equal than MINIMUM_SIZE.
97       *
98       * @param inputPoints  list of input points to be used to estimate a
99       *                     metric 2D transformation.
100      * @param outputPoints list of output points to be used to estimate a
101      *                     metric 2D transformation.
102      * @throws IllegalArgumentException if provided lists of points don't have
103      *                                  the same size or their size is smaller than MINIMUM_SIZE.
104      */
105     public PROMedSMetricTransformation2DRobustEstimator(
106             final List<Point2D> inputPoints, final List<Point2D> outputPoints) {
107         super(inputPoints, outputPoints);
108         stopThreshold = DEFAULT_STOP_THRESHOLD;
109     }
110 
111     /**
112      * Constructor.
113      *
114      * @param listener listener to be notified of events such as when estimation
115      *                 starts, ends or its progress significantly changes.
116      */
117     public PROMedSMetricTransformation2DRobustEstimator(final MetricTransformation2DRobustEstimatorListener listener) {
118         super(listener);
119         stopThreshold = DEFAULT_STOP_THRESHOLD;
120     }
121 
122     /**
123      * Constructor with listener and lists of points to be used to estimate a
124      * metric 2D transformation.
125      * Points in the list located at the same position are considered to be
126      * matched. Hence, both lists must have the same size, and their size must
127      * be greater or equal than MINIMUM_SIZE.
128      *
129      * @param listener     listener to be notified of events such as when estimation
130      *                     stars, ends or its progress significantly changes.
131      * @param inputPoints  list of input points to be used to estimate a
132      *                     metric 2D transformation.
133      * @param outputPoints list of output points to be used to estimate a
134      *                     metric 2D transformation.
135      * @throws IllegalArgumentException if provided lists of points don't have
136      *                                  the same size or their size is smaller than MINIMUM_SIZE.
137      */
138     public PROMedSMetricTransformation2DRobustEstimator(
139             final MetricTransformation2DRobustEstimatorListener listener,
140             final List<Point2D> inputPoints, final List<Point2D> outputPoints) {
141         super(listener, inputPoints, outputPoints);
142         stopThreshold = DEFAULT_STOP_THRESHOLD;
143     }
144 
145     /**
146      * Constructor.
147      *
148      * @param qualityScores quality scores corresponding to each pair of matched
149      *                      points.
150      * @throws IllegalArgumentException if provided quality scores length is
151      *                                  smaller than MINIMUM_SIZE (i.e. 3 samples).
152      */
153     public PROMedSMetricTransformation2DRobustEstimator(final double[] qualityScores) {
154         super();
155         stopThreshold = DEFAULT_STOP_THRESHOLD;
156         internalSetQualityScores(qualityScores);
157     }
158 
159     /**
160      * Constructor with lists of points to be used to estimate a metric 2D
161      * transformation.
162      * Points in the list located at the same position are considered to be
163      * matched. Hence, both lists must have the same size, and their size must
164      * be greater or equal than MINIMUM_SIZE.
165      *
166      * @param inputPoints   list of input points to be used to estimate a
167      *                      metric 2D transformation.
168      * @param outputPoints  list of output points to be used to estimate a
169      *                      metric 2D transformation.
170      * @param qualityScores quality scores corresponding to each pair of matched
171      *                      points.
172      * @throws IllegalArgumentException if provided lists of points and array
173      *                                  of quality scores don't have the same size or their size is smaller than
174      *                                  MINIMUM_SIZE.
175      */
176     public PROMedSMetricTransformation2DRobustEstimator(
177             final List<Point2D> inputPoints, final List<Point2D> outputPoints, final double[] qualityScores) {
178         super(inputPoints, outputPoints);
179 
180         if (qualityScores.length != inputPoints.size()) {
181             throw new IllegalArgumentException();
182         }
183 
184         stopThreshold = DEFAULT_STOP_THRESHOLD;
185         internalSetQualityScores(qualityScores);
186     }
187 
188     /**
189      * Constructor.
190      *
191      * @param listener      listener to be notified of events such as when estimation
192      *                      starts, ends or its progress significantly changes.
193      * @param qualityScores quality scores corresponding to each pair of matched
194      *                      points.
195      * @throws IllegalArgumentException if provided quality scores length is
196      *                                  smaller than MINIMUM_SIZE (i.e. 3 samples).
197      */
198     public PROMedSMetricTransformation2DRobustEstimator(
199             final MetricTransformation2DRobustEstimatorListener listener, final double[] qualityScores) {
200         super(listener);
201         stopThreshold = DEFAULT_STOP_THRESHOLD;
202         internalSetQualityScores(qualityScores);
203     }
204 
205     /**
206      * Constructor with listener and lists of points to be used to estimate a
207      * metric 2D transformation.
208      * Points in the list located at the same position are considered to be
209      * matched. Hence, both lists must have the same size, and their size must
210      * be greater or equal than MINIMUM_SIZE.
211      *
212      * @param listener      listener to be notified of events such as when estimation
213      *                      stars, ends or its progress significantly changes.
214      * @param inputPoints   list of input points to be used to estimate a
215      *                      metric 2D transformation.
216      * @param outputPoints  list of output points to be used to estimate a
217      *                      metric 2D transformation.
218      * @param qualityScores quality scores corresponding to each pair of matched
219      *                      points.
220      * @throws IllegalArgumentException if provided lists of points don't have
221      *                                  the same size or their size is smaller than MINIMUM_SIZE.
222      */
223     public PROMedSMetricTransformation2DRobustEstimator(
224             final MetricTransformation2DRobustEstimatorListener listener,
225             final List<Point2D> inputPoints, final List<Point2D> outputPoints, final double[] qualityScores) {
226         super(listener, inputPoints, outputPoints);
227 
228         if (qualityScores.length != inputPoints.size()) {
229             throw new IllegalArgumentException();
230         }
231 
232         stopThreshold = DEFAULT_STOP_THRESHOLD;
233         internalSetQualityScores(qualityScores);
234     }
235 
236     /**
237      * Constructor.
238      *
239      * @param weakMinimumSizeAllowed true allows 2 points, false requires 3.
240      */
241     public PROMedSMetricTransformation2DRobustEstimator(final boolean weakMinimumSizeAllowed) {
242         super(weakMinimumSizeAllowed);
243         stopThreshold = DEFAULT_STOP_THRESHOLD;
244     }
245 
246     /**
247      * Constructor with lists of points to be used to estimate a metric 2D
248      * transformation.
249      * Points in the list located at the same position are considered to be
250      * matched. Hence, both lists must have the same size, and their size must
251      * be greater or equal than MINIMUM_SIZE.
252      *
253      * @param inputPoints            list of input points to be used to estimate a
254      *                               metric 2D transformation.
255      * @param outputPoints           list of output points to be used to estimate a
256      *                               metric 2D transformation.
257      * @param weakMinimumSizeAllowed true allows 2 points, false requires 3.
258      * @throws IllegalArgumentException if provided lists of points don't have
259      *                                  the same size or their size is smaller than MINIMUM_SIZE.
260      */
261     public PROMedSMetricTransformation2DRobustEstimator(
262             final List<Point2D> inputPoints, final List<Point2D> outputPoints, final boolean weakMinimumSizeAllowed) {
263         super(inputPoints, outputPoints, weakMinimumSizeAllowed);
264         stopThreshold = DEFAULT_STOP_THRESHOLD;
265     }
266 
267     /**
268      * Constructor.
269      *
270      * @param listener               listener to be notified of events such as when estimation
271      *                               starts, ends or its progress significantly changes.
272      * @param weakMinimumSizeAllowed true allows 2 points, false requires 3.
273      */
274     public PROMedSMetricTransformation2DRobustEstimator(
275             final MetricTransformation2DRobustEstimatorListener listener, final boolean weakMinimumSizeAllowed) {
276         super(listener, weakMinimumSizeAllowed);
277         stopThreshold = DEFAULT_STOP_THRESHOLD;
278     }
279 
280     /**
281      * Constructor with listener and lists of points to be used to estimate a
282      * metric 2D transformation.
283      * Points in the list located at the same position are considered to be
284      * matched. Hence, both lists must have the same size, and their size must
285      * be greater or equal than MINIMUM_SIZE.
286      *
287      * @param listener               listener to be notified of events such as when estimation
288      *                               stars, ends or its progress significantly changes.
289      * @param inputPoints            list of input points to be used to estimate a
290      *                               metric 2D transformation.
291      * @param outputPoints           list of output points to be used to estimate a
292      *                               metric 2D transformation.
293      * @param weakMinimumSizeAllowed true allows 2 points, false requires 3.
294      * @throws IllegalArgumentException if provided lists of points don't have
295      *                                  the same size or their size is smaller than MINIMUM_SIZE.
296      */
297     public PROMedSMetricTransformation2DRobustEstimator(
298             final MetricTransformation2DRobustEstimatorListener listener,
299             final List<Point2D> inputPoints, final List<Point2D> outputPoints, final boolean weakMinimumSizeAllowed) {
300         super(listener, inputPoints, outputPoints, weakMinimumSizeAllowed);
301         stopThreshold = DEFAULT_STOP_THRESHOLD;
302     }
303 
304     /**
305      * Constructor.
306      *
307      * @param qualityScores          quality scores corresponding to each pair of matched
308      *                               points.
309      * @param weakMinimumSizeAllowed true allows 2 points, false requires 3.
310      * @throws IllegalArgumentException if provided quality scores length is
311      *                                  smaller than MINIMUM_SIZE (i.e. 3 samples).
312      */
313     public PROMedSMetricTransformation2DRobustEstimator(
314             final double[] qualityScores, final boolean weakMinimumSizeAllowed) {
315         super(weakMinimumSizeAllowed);
316         stopThreshold = DEFAULT_STOP_THRESHOLD;
317         internalSetQualityScores(qualityScores);
318     }
319 
320     /**
321      * Constructor with lists of points to be used to estimate a metric 2D
322      * transformation.
323      * Points in the list located at the same position are considered to be
324      * matched. Hence, both lists must have the same size, and their size must
325      * be greater or equal than MINIMUM_SIZE.
326      *
327      * @param inputPoints            list of input points to be used to estimate a
328      *                               metric 2D transformation.
329      * @param outputPoints           list of output points to be used to estimate a
330      *                               metric 2D transformation.
331      * @param qualityScores          quality scores corresponding to each pair of matched
332      *                               points.
333      * @param weakMinimumSizeAllowed true allows 2 points, false requires 3.
334      * @throws IllegalArgumentException if provided lists of points and array
335      *                                  of quality scores don't have the same size or their size is smaller than
336      *                                  MINIMUM_SIZE.
337      */
338     public PROMedSMetricTransformation2DRobustEstimator(
339             final List<Point2D> inputPoints, final List<Point2D> outputPoints, final double[] qualityScores,
340             final boolean weakMinimumSizeAllowed) {
341         super(inputPoints, outputPoints, weakMinimumSizeAllowed);
342 
343         if (qualityScores.length != inputPoints.size()) {
344             throw new IllegalArgumentException();
345         }
346 
347         stopThreshold = DEFAULT_STOP_THRESHOLD;
348         internalSetQualityScores(qualityScores);
349     }
350 
351     /**
352      * Constructor.
353      *
354      * @param listener               listener to be notified of events such as when estimation
355      *                               starts, ends or its progress significantly changes.
356      * @param qualityScores          quality scores corresponding to each pair of matched
357      *                               points.
358      * @param weakMinimumSizeAllowed true allows 2 points, false requires 3.
359      * @throws IllegalArgumentException if provided quality scores length is
360      *                                  smaller than MINIMUM_SIZE (i.e. 3 samples).
361      */
362     public PROMedSMetricTransformation2DRobustEstimator(
363             final MetricTransformation2DRobustEstimatorListener listener, final double[] qualityScores,
364             final boolean weakMinimumSizeAllowed) {
365         super(listener, weakMinimumSizeAllowed);
366         stopThreshold = DEFAULT_STOP_THRESHOLD;
367         internalSetQualityScores(qualityScores);
368     }
369 
370     /**
371      * Constructor with listener and lists of points to be used to estimate a
372      * metric 2D transformation.
373      * Points in the list located at the same position are considered to be
374      * matched. Hence, both lists must have the same size, and their size must
375      * be greater or equal than MINIMUM_SIZE.
376      *
377      * @param listener               listener to be notified of events such as when estimation
378      *                               stars, ends or its progress significantly changes.
379      * @param inputPoints            list of input points to be used to estimate a
380      *                               metric 2D transformation.
381      * @param outputPoints           list of output points to be used to estimate a
382      *                               metric 2D transformation.
383      * @param qualityScores          quality scores corresponding to each pair of matched
384      *                               points.
385      * @param weakMinimumSizeAllowed true allows 2 points, false requires 3.
386      * @throws IllegalArgumentException if provided lists of points don't have
387      *                                  the same size or their size is smaller than MINIMUM_SIZE.
388      */
389     public PROMedSMetricTransformation2DRobustEstimator(
390             final MetricTransformation2DRobustEstimatorListener listener, final List<Point2D> inputPoints,
391             final List<Point2D> outputPoints, final double[] qualityScores, final boolean weakMinimumSizeAllowed) {
392         super(listener, inputPoints, outputPoints, weakMinimumSizeAllowed);
393 
394         if (qualityScores.length != inputPoints.size()) {
395             throw new IllegalArgumentException();
396         }
397 
398         stopThreshold = DEFAULT_STOP_THRESHOLD;
399         internalSetQualityScores(qualityScores);
400     }
401 
402     /**
403      * Returns threshold to be used to keep the algorithm iterating in case that
404      * best estimated threshold using median of residuals is not small enough.
405      * Once a solution is found that generates a threshold below this value, the
406      * algorithm will stop.
407      * As in LMedS, the stop threshold can be used to prevent the PROMedS
408      * algorithm iterating too many times in cases where samples have a very
409      * similar accuracy.
410      * For instance, in cases where proportion of outliers is very small (close
411      * to 0%), and samples are very accurate (i.e. 1e-6), the algorithm would
412      * iterate for a long time trying to find the best solution when indeed
413      * there is no need to do that if a reasonable threshold has already been
414      * reached.
415      * Because of this behaviour the stop threshold can be set to a value much
416      * lower than the one typically used in RANSAC, and yet the algorithm could
417      * still produce even smaller thresholds in estimated results.
418      *
419      * @return stop threshold to stop the algorithm prematurely when a certain
420      * accuracy has been reached.
421      */
422     public double getStopThreshold() {
423         return stopThreshold;
424     }
425 
426     /**
427      * Sets threshold to be used to keep the algorithm iterating in case that
428      * best estimated threshold using median of residuals is not small enough.
429      * Once a solution is found that generates a threshold below this value, the
430      * algorithm will stop.
431      * As in LMedS, the stop threshold can be used to prevent the PROMedS
432      * algorithm iterating too many times in cases where samples have a very
433      * similar accuracy.
434      * For instance, in cases where proportion of outliers is very small (close
435      * to 0%), and samples are very accurate (i.e. 1e-6), the algorithm would
436      * iterate for a long time trying to find the best solution when indeed
437      * there is no need to do that if a reasonable threshold has already been
438      * reached.
439      * Because of this behaviour the stop threshold can be set to a value much
440      * lower than the one typically used in RANSAC, and yet the algorithm could
441      * still produce even smaller thresholds in estimated results.
442      *
443      * @param stopThreshold stop threshold to stop the algorithm prematurely
444      *                      when a certain accuracy has been reached.
445      * @throws IllegalArgumentException if provided value is zero or negative
446      * @throws LockedException          if robust estimator is locked because an
447      *                                  estimation is already in progress.
448      */
449     public void setStopThreshold(final double stopThreshold) throws LockedException {
450         if (isLocked()) {
451             throw new LockedException();
452         }
453         if (stopThreshold <= MIN_STOP_THRESHOLD) {
454             throw new IllegalArgumentException();
455         }
456 
457         this.stopThreshold = stopThreshold;
458     }
459 
460     /**
461      * Returns quality scores corresponding to each pair of matched points.
462      * The larger the score value the better the quality of the matching.
463      *
464      * @return quality scores corresponding to each pair of matched points.
465      */
466     @Override
467     public double[] getQualityScores() {
468         return qualityScores;
469     }
470 
471     /**
472      * Sets quality scores corresponding to each pair of matched points.
473      * The larger the score value the better the quality of the matching.
474      *
475      * @param qualityScores quality scores corresponding to each pair of matched
476      *                      points.
477      * @throws LockedException          if robust estimator is locked because an
478      *                                  estimation is already in progress.
479      * @throws IllegalArgumentException if provided quality scores length is
480      *                                  smaller than MINIMUM_SIZE (i.e. 3 samples).
481      */
482     @Override
483     public void setQualityScores(final double[] qualityScores) throws LockedException {
484         if (isLocked()) {
485             throw new LockedException();
486         }
487         internalSetQualityScores(qualityScores);
488     }
489 
490     /**
491      * Indicates if estimator is ready to start the metric 2D transformation
492      * estimation.
493      * This is true when input data (i.e. lists of matched points and quality
494      * scores) are provided and a minimum of MINIMUM_SIZE points are available.
495      *
496      * @return true if estimator is ready, false otherwise.
497      */
498     @Override
499     public boolean isReady() {
500         return super.isReady() && qualityScores != null && qualityScores.length == inputPoints.size();
501     }
502 
503     /**
504      * Estimates a metric 2D transformation using a robust estimator and
505      * the best set of matched 2D point correspondences found using the robust
506      * estimator.
507      *
508      * @return a metric 2D transformation.
509      * @throws LockedException          if robust estimator is locked because an
510      *                                  estimation is already in progress.
511      * @throws NotReadyException        if provided input data is not enough to start
512      *                                  the estimation.
513      * @throws RobustEstimatorException if estimation fails for any reason
514      *                                  (i.e. numerical instability, no solution available, etc).
515      */
516     @SuppressWarnings("DuplicatedCode")
517     @Override
518     public MetricTransformation2D estimate() throws LockedException, NotReadyException, RobustEstimatorException {
519         if (isLocked()) {
520             throw new LockedException();
521         }
522         if (!isReady()) {
523             throw new NotReadyException();
524         }
525 
526         final var innerEstimator = new PROMedSRobustEstimator<>(
527                 new PROMedSRobustEstimatorListener<MetricTransformation2D>() {
528 
529                     // point to be reused when computing residuals
530                     private final Point2D testPoint = Point2D.create(CoordinatesType.HOMOGENEOUS_COORDINATES);
531 
532                     private final MetricTransformation2DEstimator nonRobustEstimator =
533                             new MetricTransformation2DEstimator(isWeakMinimumSizeAllowed());
534 
535                     private final List<Point2D> subsetInputPoints = new ArrayList<>();
536                     private final List<Point2D> subsetOutputPoints = new ArrayList<>();
537 
538                     @Override
539                     public double getThreshold() {
540                         return stopThreshold;
541                     }
542 
543                     @Override
544                     public int getTotalSamples() {
545                         return inputPoints.size();
546                     }
547 
548                     @Override
549                     public int getSubsetSize() {
550                         return MetricTransformation2DRobustEstimator.MINIMUM_SIZE;
551                     }
552 
553                     @SuppressWarnings("DuplicatedCode")
554                     @Override
555                     public void estimatePreliminarSolutions(
556                             final int[] samplesIndices, final List<MetricTransformation2D> solutions) {
557                         subsetInputPoints.clear();
558                         subsetOutputPoints.clear();
559                         for (final var samplesIndex : samplesIndices) {
560                             subsetInputPoints.add(inputPoints.get(samplesIndex));
561                             subsetOutputPoints.add(outputPoints.get(samplesIndex));
562                         }
563 
564                         try {
565                             nonRobustEstimator.setPoints(subsetInputPoints, subsetOutputPoints);
566                             solutions.add(nonRobustEstimator.estimate());
567                         } catch (final Exception e) {
568                             // if points are coincident, no solution is added
569                         }
570                     }
571 
572                     @Override
573                     public double computeResidual(final MetricTransformation2D currentEstimation, final int i) {
574                         final var inputPoint = inputPoints.get(i);
575                         final var outputPoint = outputPoints.get(i);
576 
577                         // transform input point and store result in mTestPoint
578                         currentEstimation.transform(inputPoint, testPoint);
579 
580                         return outputPoint.distanceTo(testPoint);
581                     }
582 
583                     @Override
584                     public boolean isReady() {
585                         return PROMedSMetricTransformation2DRobustEstimator.this.isReady();
586                     }
587 
588                     @Override
589                     public void onEstimateStart(final RobustEstimator<MetricTransformation2D> estimator) {
590                         if (listener != null) {
591                             listener.onEstimateStart(PROMedSMetricTransformation2DRobustEstimator.this);
592                         }
593                     }
594 
595                     @Override
596                     public void onEstimateEnd(final RobustEstimator<MetricTransformation2D> estimator) {
597                         if (listener != null) {
598                             listener.onEstimateEnd(PROMedSMetricTransformation2DRobustEstimator.this);
599                         }
600                     }
601 
602                     @Override
603                     public void onEstimateNextIteration(
604                             final RobustEstimator<MetricTransformation2D> estimator, final int iteration) {
605                         if (listener != null) {
606                             listener.onEstimateNextIteration(
607                                     PROMedSMetricTransformation2DRobustEstimator.this, iteration);
608                         }
609                     }
610 
611                     @Override
612                     public void onEstimateProgressChange(
613                             final RobustEstimator<MetricTransformation2D> estimator, final float progress) {
614                         if (listener != null) {
615                             listener.onEstimateProgressChange(
616                                     PROMedSMetricTransformation2DRobustEstimator.this, progress);
617                         }
618                     }
619 
620                     @Override
621                     public double[] getQualityScores() {
622                         return qualityScores;
623                     }
624                 });
625 
626         try {
627             locked = true;
628             inliersData = null;
629             innerEstimator.setConfidence(confidence);
630             innerEstimator.setMaxIterations(maxIterations);
631             innerEstimator.setProgressDelta(progressDelta);
632             final var transformation = innerEstimator.estimate();
633             inliersData = innerEstimator.getInliersData();
634             return attemptRefine(transformation);
635         } catch (final com.irurueta.numerical.LockedException e) {
636             throw new LockedException(e);
637         } catch (final com.irurueta.numerical.NotReadyException e) {
638             throw new NotReadyException(e);
639         } finally {
640             locked = false;
641         }
642     }
643 
644     /**
645      * Returns method being used for robust estimation.
646      *
647      * @return method being used for robust estimation.
648      */
649     @Override
650     public RobustEstimatorMethod getMethod() {
651         return RobustEstimatorMethod.PROMEDS;
652     }
653 
654     /**
655      * Gets standard deviation used for Levenberg-Marquardt fitting during
656      * refinement.
657      * Returned value gives an indication of how much variance each residual
658      * has.
659      * Typically, this value is related to the threshold used on each robust
660      * estimation, since residuals of found inliers are within the range of such
661      * threshold.
662      *
663      * @return standard deviation used for refinement.
664      */
665     @Override
666     protected double getRefinementStandardDeviation() {
667         final var inliersData = (PROMedSRobustEstimator.PROMedSInliersData) getInliersData();
668         return inliersData.getEstimatedThreshold();
669     }
670 
671     /**
672      * Sets quality scores corresponding to each pair of matched points.
673      * This method is used internally and does not check whether instance is
674      * locked or not.
675      *
676      * @param qualityScores quality scores to be set.
677      * @throws IllegalArgumentException if provided quality scores length is
678      *                                  smaller than MINIMUM_SIZE.
679      */
680     private void internalSetQualityScores(final double[] qualityScores) {
681         if (qualityScores.length < getMinimumPoints()) {
682             throw new IllegalArgumentException();
683         }
684 
685         this.qualityScores = qualityScores;
686     }
687 }