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