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.MetricTransformation3D;
20  import com.irurueta.geometry.Point3D;
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 3D transformation for provided collections of
32   * matched 3D point using PROMedS algorithm.
33   */
34  @SuppressWarnings("DuplicatedCode")
35  public class PROMedSMetricTransformation3DRobustEstimator extends MetricTransformation3DRobustEstimator {
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 PROMedSMetricTransformation3DRobustEstimator() {
88          super();
89          stopThreshold = DEFAULT_STOP_THRESHOLD;
90      }
91  
92      /**
93       * Constructor with lists of points to be used to estimate a metric 3D
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 a
100      *                     metric 3D transformation.
101      * @param outputPoints list of output points to be used to estimate a
102      *                     metric 3D 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 PROMedSMetricTransformation3DRobustEstimator(
107             final List<Point3D> inputPoints, final List<Point3D> 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 PROMedSMetricTransformation3DRobustEstimator(final MetricTransformation3DRobustEstimatorListener listener) {
119         super(listener);
120         stopThreshold = DEFAULT_STOP_THRESHOLD;
121     }
122 
123     /**
124      * Constructor with listener and lists of points to be used to estimate a
125      * metric 3D transformation.
126      * Points in the list located at the same position are considered to be
127      * matched. Hence, both lists must have the same size, and their size must
128      * be greater or equal than MINIMUM_SIZE.
129      *
130      * @param listener     listener to be notified of events such as when estimation
131      *                     stars, ends or its progress significantly changes.
132      * @param inputPoints  list of input points to be used to estimate a
133      *                     metric 3D transformation.
134      * @param outputPoints list of output points to be used to estimate a
135      *                     metric 3D transformation.
136      * @throws IllegalArgumentException if provided lists of points don't have
137      *                                  the same size or their size is smaller than MINIMUM_SIZE.
138      */
139     public PROMedSMetricTransformation3DRobustEstimator(
140             final MetricTransformation3DRobustEstimatorListener listener,
141             final List<Point3D> inputPoints, final List<Point3D> outputPoints) {
142         super(listener, inputPoints, outputPoints);
143         stopThreshold = DEFAULT_STOP_THRESHOLD;
144     }
145 
146     /**
147      * Constructor.
148      *
149      * @param qualityScores quality scores corresponding to each pair of matched
150      *                      points.
151      * @throws IllegalArgumentException if provided quality scores length is
152      *                                  smaller than MINIMUM_SIZE (i.e. 3 samples).
153      */
154     public PROMedSMetricTransformation3DRobustEstimator(final double[] qualityScores) {
155         super();
156         stopThreshold = DEFAULT_STOP_THRESHOLD;
157         internalSetQualityScores(qualityScores);
158     }
159 
160     /**
161      * Constructor with lists of points to be used to estimate a metric 3D
162      * transformation.
163      * Points in the list located at the same position are considered to be
164      * matched. Hence, both lists must have the same size, and their size must
165      * be greater or equal than MINIMUM_SIZE.
166      *
167      * @param inputPoints   list of input points to be used to estimate a
168      *                      metric 3D transformation.
169      * @param outputPoints  list of output points to be used to estimate a
170      *                      metric 3D transformation.
171      * @param qualityScores quality scores corresponding to each pair of matched
172      *                      points.
173      * @throws IllegalArgumentException if provided lists of points and array
174      *                                  of quality scores don't have the same size or their size is smaller than
175      *                                  MINIMUM_SIZE.
176      */
177     public PROMedSMetricTransformation3DRobustEstimator(
178             final List<Point3D> inputPoints, final List<Point3D> outputPoints, final double[] qualityScores) {
179         super(inputPoints, outputPoints);
180 
181         if (qualityScores.length != inputPoints.size()) {
182             throw new IllegalArgumentException();
183         }
184 
185         stopThreshold = DEFAULT_STOP_THRESHOLD;
186         internalSetQualityScores(qualityScores);
187     }
188 
189     /**
190      * Constructor.
191      *
192      * @param listener      listener to be notified of events such as when estimation
193      *                      starts, ends or its progress significantly changes.
194      * @param qualityScores quality scores corresponding to each pair of matched
195      *                      points.
196      * @throws IllegalArgumentException if provided quality scores length is
197      *                                  smaller than MINIMUM_SIZE (i.e. 3 samples).
198      */
199     public PROMedSMetricTransformation3DRobustEstimator(
200             final MetricTransformation3DRobustEstimatorListener listener, final double[] qualityScores) {
201         super(listener);
202         stopThreshold = DEFAULT_STOP_THRESHOLD;
203         internalSetQualityScores(qualityScores);
204     }
205 
206     /**
207      * Constructor with listener and lists of points to be used to estimate a
208      * metric 3D transformation.
209      * Points in the list located at the same position are considered to be
210      * matched. Hence, both lists must have the same size, and their size must
211      * be greater or equal than MINIMUM_SIZE.
212      *
213      * @param listener      listener to be notified of events such as when estimation
214      *                      stars, ends or its progress significantly changes.
215      * @param inputPoints   list of input points to be used to estimate a
216      *                      metric 3D transformation.
217      * @param outputPoints  list of output points to be used to estimate a
218      *                      metric 3D transformation.
219      * @param qualityScores quality scores corresponding to each pair of matched
220      *                      points.
221      * @throws IllegalArgumentException if provided lists of points don't have
222      *                                  the same size or their size is smaller than MINIMUM_SIZE.
223      */
224     public PROMedSMetricTransformation3DRobustEstimator(
225             final MetricTransformation3DRobustEstimatorListener listener,
226             final List<Point3D> inputPoints, final List<Point3D> outputPoints, final double[] qualityScores) {
227         super(listener, inputPoints, outputPoints);
228 
229         if (qualityScores.length != inputPoints.size()) {
230             throw new IllegalArgumentException();
231         }
232 
233         stopThreshold = DEFAULT_STOP_THRESHOLD;
234         internalSetQualityScores(qualityScores);
235     }
236 
237     /**
238      * Constructor.
239      *
240      * @param weakMinimumSizeAllowed true allows 3 points, false requires 4.
241      */
242     public PROMedSMetricTransformation3DRobustEstimator(final boolean weakMinimumSizeAllowed) {
243         super(weakMinimumSizeAllowed);
244         stopThreshold = DEFAULT_STOP_THRESHOLD;
245     }
246 
247     /**
248      * Constructor with lists of points to be used to estimate a metric 3D
249      * transformation.
250      * Points in the list located at the same position are considered to be
251      * matched. Hence, both lists must have the same size, and their size must
252      * be greater or equal than MINIMUM_SIZE.
253      *
254      * @param inputPoints            list of input points to be used to estimate a
255      *                               metric 3D transformation.
256      * @param outputPoints           list of output points to be used to estimate a
257      *                               metric 3D transformation.
258      * @param weakMinimumSizeAllowed true allows 3 points, false requires 4.
259      * @throws IllegalArgumentException if provided lists of points don't have
260      *                                  the same size or their size is smaller than MINIMUM_SIZE.
261      */
262     public PROMedSMetricTransformation3DRobustEstimator(
263             final List<Point3D> inputPoints, final List<Point3D> outputPoints, final boolean weakMinimumSizeAllowed) {
264         super(inputPoints, outputPoints, weakMinimumSizeAllowed);
265         stopThreshold = DEFAULT_STOP_THRESHOLD;
266     }
267 
268     /**
269      * Constructor.
270      *
271      * @param listener               listener to be notified of events such as when estimation
272      *                               starts, ends or its progress significantly changes.
273      * @param weakMinimumSizeAllowed true allows 3 points, false requires 4.
274      */
275     public PROMedSMetricTransformation3DRobustEstimator(
276             final MetricTransformation3DRobustEstimatorListener listener, final boolean weakMinimumSizeAllowed) {
277         super(listener, weakMinimumSizeAllowed);
278         stopThreshold = DEFAULT_STOP_THRESHOLD;
279     }
280 
281     /**
282      * Constructor with listener and lists of points to be used to estimate a
283      * metric 3D transformation.
284      * Points in the list located at the same position are considered to be
285      * matched. Hence, both lists must have the same size, and their size must
286      * be greater or equal than MINIMUM_SIZE.
287      *
288      * @param listener               listener to be notified of events such as when estimation
289      *                               stars, ends or its progress significantly changes.
290      * @param inputPoints            list of input points to be used to estimate a
291      *                               metric 3D transformation.
292      * @param outputPoints           list of output points to be used to estimate a
293      *                               metric 3D transformation.
294      * @param weakMinimumSizeAllowed true allows 3 points, false requires 4.
295      * @throws IllegalArgumentException if provided lists of points don't have
296      *                                  the same size or their size is smaller than MINIMUM_SIZE.
297      */
298     public PROMedSMetricTransformation3DRobustEstimator(
299             final MetricTransformation3DRobustEstimatorListener listener, final List<Point3D> inputPoints,
300             final List<Point3D> outputPoints, final boolean weakMinimumSizeAllowed) {
301         super(listener, inputPoints, outputPoints, weakMinimumSizeAllowed);
302         stopThreshold = DEFAULT_STOP_THRESHOLD;
303     }
304 
305     /**
306      * Constructor.
307      *
308      * @param qualityScores          quality scores corresponding to each pair of matched
309      *                               points.
310      * @param weakMinimumSizeAllowed true allows 3 points, false requires 4.
311      * @throws IllegalArgumentException if provided quality scores length is
312      *                                  smaller than MINIMUM_SIZE (i.e. 3 samples).
313      */
314     public PROMedSMetricTransformation3DRobustEstimator(
315             final double[] qualityScores, final boolean weakMinimumSizeAllowed) {
316         super(weakMinimumSizeAllowed);
317         stopThreshold = DEFAULT_STOP_THRESHOLD;
318         internalSetQualityScores(qualityScores);
319     }
320 
321     /**
322      * Constructor with lists of points to be used to estimate a metric 3D
323      * transformation.
324      * Points in the list located at the same position are considered to be
325      * matched. Hence, both lists must have the same size, and their size must
326      * be greater or equal than MINIMUM_SIZE.
327      *
328      * @param inputPoints            list of input points to be used to estimate a
329      *                               metric 3D transformation.
330      * @param outputPoints           list of output points to be used to estimate a
331      *                               metric 3D transformation.
332      * @param qualityScores          quality scores corresponding to each pair of matched
333      *                               points.
334      * @param weakMinimumSizeAllowed true allows 3 points, false requires 4.
335      * @throws IllegalArgumentException if provided lists of points and array
336      *                                  of quality scores don't have the same size or their size is smaller than
337      *                                  MINIMUM_SIZE.
338      */
339     public PROMedSMetricTransformation3DRobustEstimator(
340             final List<Point3D> inputPoints, final List<Point3D> outputPoints, final double[] qualityScores,
341             final boolean weakMinimumSizeAllowed) {
342         super(inputPoints, outputPoints, weakMinimumSizeAllowed);
343 
344         if (qualityScores.length != inputPoints.size()) {
345             throw new IllegalArgumentException();
346         }
347 
348         stopThreshold = DEFAULT_STOP_THRESHOLD;
349         internalSetQualityScores(qualityScores);
350     }
351 
352     /**
353      * Constructor.
354      *
355      * @param listener               listener to be notified of events such as when estimation
356      *                               starts, ends or its progress significantly changes.
357      * @param qualityScores          quality scores corresponding to each pair of matched
358      *                               points.
359      * @param weakMinimumSizeAllowed true allows 3 points, false requires 4.
360      * @throws IllegalArgumentException if provided quality scores length is
361      *                                  smaller than MINIMUM_SIZE (i.e. 3 samples).
362      */
363     public PROMedSMetricTransformation3DRobustEstimator(
364             final MetricTransformation3DRobustEstimatorListener listener, final double[] qualityScores,
365             final boolean weakMinimumSizeAllowed) {
366         super(listener, weakMinimumSizeAllowed);
367         stopThreshold = DEFAULT_STOP_THRESHOLD;
368         internalSetQualityScores(qualityScores);
369     }
370 
371     /**
372      * Constructor with listener and lists of points to be used to estimate a
373      * metric 3D transformation.
374      * Points in the list located at the same position are considered to be
375      * matched. Hence, both lists must have the same size, and their size must
376      * be greater or equal than MINIMUM_SIZE.
377      *
378      * @param listener               listener to be notified of events such as when estimation
379      *                               stars, ends or its progress significantly changes.
380      * @param inputPoints            list of input points to be used to estimate a
381      *                               metric 3D transformation.
382      * @param outputPoints           list of output points to be used to estimate a
383      *                               metric 3D transformation.
384      * @param qualityScores          quality scores corresponding to each pair of matched
385      *                               points.
386      * @param weakMinimumSizeAllowed true allows 3 points, false requires 4.
387      * @throws IllegalArgumentException if provided lists of points don't have
388      *                                  the same size or their size is smaller than MINIMUM_SIZE.
389      */
390     public PROMedSMetricTransformation3DRobustEstimator(
391             final MetricTransformation3DRobustEstimatorListener listener,
392             final List<Point3D> inputPoints, final List<Point3D> outputPoints, final double[] qualityScores,
393             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 metric 3D 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 a metric 3D transformation using a robust estimator and
507      * the best set of matched 3D point correspondences found using the robust
508      * estimator.
509      *
510      * @return a metric 3D 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 MetricTransformation3D 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<MetricTransformation3D>() {
529 
530                     // point to be reused when computing residuals
531                     private final Point3D testPoint = Point3D.create(CoordinatesType.HOMOGENEOUS_COORDINATES);
532 
533                     private final MetricTransformation3DEstimator nonRobustEstimator =
534                             new MetricTransformation3DEstimator(isWeakMinimumSizeAllowed());
535 
536                     private final List<Point3D> subsetInputPoints = new ArrayList<>();
537                     private final List<Point3D> 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<MetricTransformation3D> 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 MetricTransformation3D 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 PROMedSMetricTransformation3DRobustEstimator.this.isReady();
586                     }
587 
588                     @Override
589                     public void onEstimateStart(final RobustEstimator<MetricTransformation3D> estimator) {
590                         if (listener != null) {
591                             listener.onEstimateStart(PROMedSMetricTransformation3DRobustEstimator.this);
592                         }
593                     }
594 
595                     @Override
596                     public void onEstimateEnd(final RobustEstimator<MetricTransformation3D> estimator) {
597                         if (listener != null) {
598                             listener.onEstimateEnd(PROMedSMetricTransformation3DRobustEstimator.this);
599                         }
600                     }
601 
602                     @Override
603                     public void onEstimateNextIteration(
604                             final RobustEstimator<MetricTransformation3D> estimator, final int iteration) {
605                         if (listener != null) {
606                             listener.onEstimateNextIteration(
607                                     PROMedSMetricTransformation3DRobustEstimator.this, iteration);
608                         }
609                     }
610 
611                     @Override
612                     public void onEstimateProgressChange(
613                             final RobustEstimator<MetricTransformation3D> estimator, final float progress) {
614                         if (listener != null) {
615                             listener.onEstimateProgressChange(
616                                     PROMedSMetricTransformation3DRobustEstimator.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 }