View Javadoc
1   /*
2    * Copyright (C) 2015 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.CoincidentPointsException;
19  import com.irurueta.geometry.Conic;
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.List;
28  
29  /**
30   * Finds the best conic for provided collection of 2D points using MSAC
31   * algorithm.
32   */
33  @SuppressWarnings("DuplicatedCode")
34  public class MSACConicRobustEstimator extends ConicRobustEstimator {
35  
36      /**
37       * Constant defining default threshold to determine whether points are
38       * inliers or not.
39       * Threshold is defined by the equation abs(trans(x) * C * x) < t, where trans
40       * is the transposition, x i a point and C is a conic and t is a threshold.
41       * This equation determines the points x belonging to the locus of a conic
42       * C up to a certain threshold.
43       */
44      public static final double DEFAULT_THRESHOLD = 1e-6;
45  
46      /**
47       * Minimum value that can be set as threshold.
48       * Threshold must be strictly greater than 0.0.
49       */
50      public static final double MIN_THRESHOLD = 0.0;
51  
52      /**
53       * Threshold to determine whether points are inliers or not when testing
54       * possible estimation solutions.
55       * The threshold refers to the amount of error (i.e. distance) a possible
56       * solution has on a matched pair of points.
57       */
58      private double threshold;
59  
60      /**
61       * Constructor.
62       */
63      public MSACConicRobustEstimator() {
64          super();
65          threshold = DEFAULT_THRESHOLD;
66      }
67  
68      /**
69       * Constructor with points.
70       *
71       * @param points 2D points to estimate a conic.
72       * @throws IllegalArgumentException if provided list of points don't have
73       *                                  a size greater or equal than MINIMUM_SIZE.
74       */
75      public MSACConicRobustEstimator(final List<Point2D> points) {
76          super(points);
77          threshold = DEFAULT_THRESHOLD;
78      }
79  
80      /**
81       * Constructor.
82       *
83       * @param listener listener to be notified of events such as when estimation
84       *                 starts, ends or its progress significantly changes.
85       */
86      public MSACConicRobustEstimator(final ConicRobustEstimatorListener listener) {
87          super(listener);
88          threshold = DEFAULT_THRESHOLD;
89      }
90  
91      /**
92       * Constructor.
93       *
94       * @param listener listener to be notified of events such as when estimation
95       *                 starts, ends or its progress significantly changes.
96       * @param points   2D points to estimate a conic.
97       * @throws IllegalArgumentException if provided list of points don't have a
98       *                                  size greater or equal than MINIMUM_SIZE.
99       */
100     public MSACConicRobustEstimator(final ConicRobustEstimatorListener listener, final List<Point2D> points) {
101         super(listener, points);
102         threshold = DEFAULT_THRESHOLD;
103     }
104 
105     /**
106      * Returns threshold to determine whether points are inliers or not when
107      * testing possible estimation solutions.
108      * The threshold refers to the amount of error a possible solution has on a
109      * given point.
110      *
111      * @return threshold to determine whether points are inliers or not when
112      * testing possible estimation solutions.
113      */
114     public double getThreshold() {
115         return threshold;
116     }
117 
118     /**
119      * Sets threshold to determine whether points are inliers or not when
120      * testing possible estimation solutions.
121      * The threshold refers to the amount of error a possible solution has on
122      * a given point.
123      *
124      * @param threshold threshold to be set.
125      * @throws IllegalArgumentException if provided value is equal or less than
126      *                                  zero.
127      * @throws LockedException          if robust estimator is locked because an
128      *                                  estimation is already in progress.
129      */
130     public void setThreshold(final double threshold) throws LockedException {
131         if (isLocked()) {
132             throw new LockedException();
133         }
134         if (threshold <= MIN_THRESHOLD) {
135             throw new IllegalArgumentException();
136         }
137         this.threshold = threshold;
138     }
139 
140     /**
141      * Estimates a conic using a robust estimator and the best set of 2D points
142      * that fit into the locus of the estimated conic found using the robust
143      * estimator.
144      *
145      * @return a conic.
146      * @throws LockedException          if robust estimator is locked because an
147      *                                  estimation is already in progress.
148      * @throws NotReadyException        if provided input data is not enough to start
149      *                                  the estimation.
150      * @throws RobustEstimatorException if estimation fails for any reason
151      *                                  (i.e. numerical instability, no solution available, etc).
152      */
153     @Override
154     public Conic estimate() throws LockedException, NotReadyException, RobustEstimatorException {
155         if (isLocked()) {
156             throw new LockedException();
157         }
158         if (!isReady()) {
159             throw new NotReadyException();
160         }
161 
162         final var innerEstimator = new MSACRobustEstimator<>(new MSACRobustEstimatorListener<Conic>() {
163 
164             @Override
165             public double getThreshold() {
166                 return threshold;
167             }
168 
169             @Override
170             public int getTotalSamples() {
171                 return points.size();
172             }
173 
174             @Override
175             public int getSubsetSize() {
176                 return ConicRobustEstimator.MINIMUM_SIZE;
177             }
178 
179             @Override
180             public void estimatePreliminarSolutions(final int[] samplesIndices, final List<Conic> solutions) {
181                 final var point1 = points.get(samplesIndices[0]);
182                 final var point2 = points.get(samplesIndices[1]);
183                 final var point3 = points.get(samplesIndices[2]);
184                 final var point4 = points.get(samplesIndices[3]);
185                 final var point5 = points.get(samplesIndices[4]);
186 
187                 try {
188                     final var conic = new Conic(point1, point2, point3, point4, point5);
189                     solutions.add(conic);
190                 } catch (final CoincidentPointsException e) {
191                     // if points are coincident, no solution is added
192                 }
193             }
194 
195             @Override
196             public double computeResidual(final Conic currentEstimation, final int i) {
197                 return residual(currentEstimation, points.get(i));
198             }
199 
200             @Override
201             public boolean isReady() {
202                 return MSACConicRobustEstimator.this.isReady();
203             }
204 
205             @Override
206             public void onEstimateStart(final RobustEstimator<Conic> estimator) {
207                 if (listener != null) {
208                     listener.onEstimateStart(MSACConicRobustEstimator.this);
209                 }
210             }
211 
212             @Override
213             public void onEstimateEnd(final RobustEstimator<Conic> estimator) {
214                 if (listener != null) {
215                     listener.onEstimateEnd(MSACConicRobustEstimator.this);
216                 }
217             }
218 
219             @Override
220             public void onEstimateNextIteration(final RobustEstimator<Conic> estimator, final int iteration) {
221                 if (listener != null) {
222                     listener.onEstimateNextIteration(MSACConicRobustEstimator.this, iteration);
223                 }
224             }
225 
226             @Override
227             public void onEstimateProgressChange(final RobustEstimator<Conic> estimator, final float progress) {
228                 if (listener != null) {
229                     listener.onEstimateProgressChange(MSACConicRobustEstimator.this, progress);
230                 }
231             }
232         });
233 
234         try {
235             locked = true;
236             innerEstimator.setConfidence(confidence);
237             innerEstimator.setMaxIterations(maxIterations);
238             innerEstimator.setProgressDelta(progressDelta);
239             return innerEstimator.estimate();
240         } catch (final com.irurueta.numerical.LockedException e) {
241             throw new LockedException(e);
242         } catch (final com.irurueta.numerical.NotReadyException e) {
243             throw new NotReadyException(e);
244         } finally {
245             locked = false;
246         }
247     }
248 
249     /**
250      * Returns method being used for robust estimation.
251      *
252      * @return method being used for robust estimation.
253      */
254     @Override
255     public RobustEstimatorMethod getMethod() {
256         return RobustEstimatorMethod.MSAC;
257     }
258 }