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.MSACRobustEstimator;
22  import com.irurueta.numerical.robust.MSACRobustEstimatorListener;
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 points using MSAC algorithm.
33   */
34  @SuppressWarnings("DuplicatedCode")
35  public class MSACEuclideanTransformation2DRobustEstimator extends EuclideanTransformation2DRobustEstimator {
36  
37      /**
38       * Constant defining default threshold to determine whether points are
39       * inliers or not.
40       * By default, 1.0 is considered a good value for cases where measures are
41       * done on pixels, since typically the minimum resolution is 1 pixel.
42       */
43      public static final double DEFAULT_THRESHOLD = 1.0;
44  
45      /**
46       * Minimum value that can be set as threshold.
47       * Threshold must be strictly greater than 0.0.
48       */
49      public static final double MIN_THRESHOLD = 0.0;
50  
51      /**
52       * Threshold to determine whether points are inliers or not when testing
53       * possible estimation solutions.
54       * The threshold refers to the amount of error (i.e. distance) a possible
55       * solution has on a matched pair of points.
56       */
57      private double threshold;
58  
59      /**
60       * Constructor.
61       */
62      public MSACEuclideanTransformation2DRobustEstimator() {
63          super();
64          threshold = DEFAULT_THRESHOLD;
65      }
66  
67      /**
68       * Constructor with lists of points to be used to estimate an Euclidean 2D
69       * transformation.
70       * Points in the list located at the same position are considered to be
71       * matched. Hence, both lists must have the same size, and their size must
72       * be greater or equal than MINIMUM_SIZE.
73       *
74       * @param inputPoints  list of input points ot be used to estimate an
75       *                     Euclidean 2D transformation.
76       * @param outputPoints list of output points to be used to estimate an
77       *                     Euclidean 2D transformation.
78       * @throws IllegalArgumentException if provided lists of points don't have
79       *                                  the same size or their size is smaller than MINIMUM_SIZE.
80       */
81      public MSACEuclideanTransformation2DRobustEstimator(
82              final List<Point2D> inputPoints, final List<Point2D> outputPoints) {
83          super(inputPoints, outputPoints);
84          threshold = DEFAULT_THRESHOLD;
85      }
86  
87      /**
88       * Constructor.
89       *
90       * @param listener listener to be notified of events such as when estimation
91       *                 starts, ends or its progress significantly changes.
92       */
93      public MSACEuclideanTransformation2DRobustEstimator(
94              final EuclideanTransformation2DRobustEstimatorListener listener) {
95          super(listener);
96          threshold = DEFAULT_THRESHOLD;
97      }
98  
99      /**
100      * Constructor with listener and lists of points to be used to estimate an
101      * Euclidean 2D transformation.
102      * Points in the list located at the same position are considered to be
103      * matched. Hence, both lists must have the same size, and their size must
104      * be greater or equal than MINIMUM_SIZE.
105      *
106      * @param listener     listener to be notified of events such as when estimation
107      *                     starts, ends or its progress significantly changes.
108      * @param inputPoints  list of input points to be used to estimate an
109      *                     Euclidean 2D transformation.
110      * @param outputPoints list of output points to be used to estimate an
111      *                     Euclidean 2D transformation.
112      * @throws IllegalArgumentException if provided lists of points don't have
113      *                                  the same size or their size is smaller than MINIMUM_SIZE.
114      */
115     public MSACEuclideanTransformation2DRobustEstimator(
116             final EuclideanTransformation2DRobustEstimatorListener listener,
117             final List<Point2D> inputPoints, final List<Point2D> outputPoints) {
118         super(listener, inputPoints, outputPoints);
119         threshold = DEFAULT_THRESHOLD;
120     }
121 
122     /**
123      * Constructor.
124      *
125      * @param weakMinimumSizeAllowed true allows 3 points, false requires 4.
126      */
127     public MSACEuclideanTransformation2DRobustEstimator(final boolean weakMinimumSizeAllowed) {
128         super(weakMinimumSizeAllowed);
129         threshold = DEFAULT_THRESHOLD;
130     }
131 
132     /**
133      * Constructor with lists of points to be used to estimate an Euclidean 2D
134      * transformation.
135      * Points in the list located at the same position are considered to be
136      * matched. Hence, both lists must have the same size, and their size must
137      * be greater or equal than MINIMUM_SIZE.
138      *
139      * @param inputPoints            list of input points ot be used to estimate an
140      *                               Euclidean 2D transformation.
141      * @param outputPoints           list of output points to be used to estimate an
142      *                               Euclidean 2D transformation.
143      * @param weakMinimumSizeAllowed true allows 3 points, false requires 4.
144      * @throws IllegalArgumentException if provided lists of points don't have
145      *                                  the same size or their size is smaller than MINIMUM_SIZE.
146      */
147     public MSACEuclideanTransformation2DRobustEstimator(
148             final List<Point2D> inputPoints, final List<Point2D> outputPoints, final boolean weakMinimumSizeAllowed) {
149         super(inputPoints, outputPoints, weakMinimumSizeAllowed);
150         threshold = DEFAULT_THRESHOLD;
151     }
152 
153     /**
154      * Constructor.
155      *
156      * @param listener               listener to be notified of events such as when estimation
157      *                               starts, ends or its progress significantly changes.
158      * @param weakMinimumSizeAllowed true allows 3 points, false requires 4.
159      */
160     public MSACEuclideanTransformation2DRobustEstimator(
161             final EuclideanTransformation2DRobustEstimatorListener listener, final boolean weakMinimumSizeAllowed) {
162         super(listener, weakMinimumSizeAllowed);
163         threshold = DEFAULT_THRESHOLD;
164     }
165 
166     /**
167      * Constructor with listener and lists of points to be used to estimate an
168      * Euclidean 2D transformation.
169      * Points in the list located at the same position are considered to be
170      * matched. Hence, both lists must have the same size, and their size must
171      * be greater or equal than MINIMUM_SIZE.
172      *
173      * @param listener               listener to be notified of events such as when estimation
174      *                               starts, ends or its progress significantly changes.
175      * @param inputPoints            list of input points to be used to estimate an
176      *                               Euclidean 2D transformation.
177      * @param outputPoints           list of output points to be used to estimate an
178      *                               Euclidean 2D transformation.
179      * @param weakMinimumSizeAllowed true allows 3 points, false requires 4.
180      * @throws IllegalArgumentException if provided lists of points don't have
181      *                                  the same size or their size is smaller than MINIMUM_SIZE.
182      */
183     public MSACEuclideanTransformation2DRobustEstimator(
184             final EuclideanTransformation2DRobustEstimatorListener listener,
185             final List<Point2D> inputPoints, final List<Point2D> outputPoints, final boolean weakMinimumSizeAllowed) {
186         super(listener, inputPoints, outputPoints, weakMinimumSizeAllowed);
187         threshold = DEFAULT_THRESHOLD;
188     }
189 
190     /**
191      * Returns threshold to determine whether points are inliers or not when
192      * testing possible estimation solutions.
193      * The threshold refers to the amount of error (i.e. Euclidean distance) a
194      * possible solution has on a matched pair of points.
195      *
196      * @return threshold to determine whether points are inliers or not when
197      * testing possible estimation solutions.
198      */
199     public double getThreshold() {
200         return threshold;
201     }
202 
203     /**
204      * Sets threshold to determine whether points are inliers or not when
205      * testing possible estimation solutions.
206      * The threshold refers to the amount of error (i.e. Euclidean distance) a
207      * possible solution has on a matched pair of points.
208      *
209      * @param threshold threshold to determine whether points are inliers or
210      *                  not.
211      * @throws IllegalArgumentException if provided value is equal or less than
212      *                                  zero.
213      * @throws LockedException          if robust estimator is locked because an
214      *                                  estimation is already in progress.
215      */
216     public void setThreshold(final double threshold) throws LockedException {
217         if (isLocked()) {
218             throw new LockedException();
219         }
220         if (threshold <= MIN_THRESHOLD) {
221             throw new IllegalArgumentException();
222         }
223         this.threshold = threshold;
224     }
225 
226     /**
227      * Estimates an Euclidean 2D transformation using a robust estimator and
228      * the best set of matched 2D point correspondences found using the robust
229      * estimator.
230      *
231      * @return an Euclidean 2D transformation.
232      * @throws LockedException          if robust estimator is locked because an
233      *                                  estimation is already in progress.
234      * @throws NotReadyException        if provided input data is not enough to start
235      *                                  the estimation.
236      * @throws RobustEstimatorException if estimation fails for any reason
237      *                                  (i.e. numerical instability, no solution available, etc).
238      */
239     @Override
240     public EuclideanTransformation2D estimate() throws LockedException, NotReadyException, RobustEstimatorException {
241         if (isLocked()) {
242             throw new LockedException();
243         }
244         if (!isReady()) {
245             throw new NotReadyException();
246         }
247 
248         final var innerEstimator = new MSACRobustEstimator<>(
249                 new MSACRobustEstimatorListener<EuclideanTransformation2D>() {
250 
251                     // point to be reused when computing residuals
252                     private final Point2D testPoint = Point2D.create(CoordinatesType.HOMOGENEOUS_COORDINATES);
253 
254                     private final EuclideanTransformation2DEstimator nonRobustEstimator =
255                             new EuclideanTransformation2DEstimator(isWeakMinimumSizeAllowed());
256 
257                     private final List<Point2D> subsetInputPoints = new ArrayList<>();
258                     private final List<Point2D> subsetOutputPoints = new ArrayList<>();
259 
260                     @Override
261                     public double getThreshold() {
262                         return threshold;
263                     }
264 
265                     @Override
266                     public int getTotalSamples() {
267                         return inputPoints.size();
268                     }
269 
270                     @Override
271                     public int getSubsetSize() {
272                         return nonRobustEstimator.getMinimumPoints();
273                     }
274 
275                     @Override
276                     public void estimatePreliminarSolutions(
277                             final int[] samplesIndices, final List<EuclideanTransformation2D> solutions) {
278                         subsetInputPoints.clear();
279                         subsetOutputPoints.clear();
280                         for (final var samplesIndex : samplesIndices) {
281                             subsetInputPoints.add(inputPoints.get(samplesIndex));
282                             subsetOutputPoints.add(outputPoints.get(samplesIndex));
283                         }
284 
285                         try {
286                             nonRobustEstimator.setPoints(subsetInputPoints, subsetOutputPoints);
287                             solutions.add(nonRobustEstimator.estimate());
288                         } catch (final Exception e) {
289                             // if points are coincident, no solution is added
290                         }
291                     }
292 
293                     @Override
294                     public double computeResidual(final EuclideanTransformation2D currentEstimation, final int i) {
295                         final var inputPoint = inputPoints.get(i);
296                         final var outputPoint = outputPoints.get(i);
297 
298                         // transform input point and store result in mTestPoint
299                         currentEstimation.transform(inputPoint, testPoint);
300 
301                         return outputPoint.distanceTo(testPoint);
302                     }
303 
304                     @Override
305                     public boolean isReady() {
306                         return MSACEuclideanTransformation2DRobustEstimator.this.isReady();
307                     }
308 
309                     @Override
310                     public void onEstimateStart(final RobustEstimator<EuclideanTransformation2D> estimator) {
311                         if (listener != null) {
312                             listener.onEstimateStart(MSACEuclideanTransformation2DRobustEstimator.this);
313                         }
314                     }
315 
316                     @Override
317                     public void onEstimateEnd(final RobustEstimator<EuclideanTransformation2D> estimator) {
318                         if (listener != null) {
319                             listener.onEstimateEnd(MSACEuclideanTransformation2DRobustEstimator.this);
320                         }
321                     }
322 
323                     @Override
324                     public void onEstimateNextIteration(
325                             final RobustEstimator<EuclideanTransformation2D> estimator, final int iteration) {
326                         if (listener != null) {
327                             listener.onEstimateNextIteration(
328                                     MSACEuclideanTransformation2DRobustEstimator.this, iteration);
329                         }
330                     }
331 
332                     @Override
333                     public void onEstimateProgressChange(
334                             final RobustEstimator<EuclideanTransformation2D> estimator, final float progress) {
335                         if (listener != null) {
336                             listener.onEstimateProgressChange(
337                                     MSACEuclideanTransformation2DRobustEstimator.this, progress);
338                         }
339                     }
340                 });
341 
342         try {
343             locked = true;
344             inliersData = null;
345             innerEstimator.setConfidence(confidence);
346             innerEstimator.setMaxIterations(maxIterations);
347             innerEstimator.setProgressDelta(progressDelta);
348             final var transformation = innerEstimator.estimate();
349             inliersData = innerEstimator.getInliersData();
350             return attemptRefine(transformation);
351         } catch (final com.irurueta.numerical.LockedException e) {
352             throw new LockedException(e);
353         } catch (final com.irurueta.numerical.NotReadyException e) {
354             throw new NotReadyException(e);
355         } finally {
356             locked = false;
357         }
358     }
359 
360     /**
361      * Returns method being used for robust estimation.
362      *
363      * @return method being used for robust estimation.
364      */
365     @Override
366     public RobustEstimatorMethod getMethod() {
367         return RobustEstimatorMethod.MSAC;
368     }
369 
370     /**
371      * Gets standard deviation used for Levenberg-Marquardt fitting during
372      * refinement.
373      * Returned value gives an indication of how much variance each residual
374      * has.
375      * Typically, this value is related to the threshold used on each robust
376      * estimation, since residuals of found inliers are within the range of
377      * such threshold.
378      *
379      * @return standard deviation used for refinement.
380      */
381     @Override
382     protected double getRefinementStandardDeviation() {
383         return threshold;
384     }
385 }