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 }