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