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 }