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