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.Line2D;
20  import com.irurueta.geometry.Point2D;
21  import com.irurueta.numerical.robust.RANSACRobustEstimator;
22  import com.irurueta.numerical.robust.RANSACRobustEstimatorListener;
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 line for provided collection of 2D points using RANSAC
31   * algorithm.
32   */
33  @SuppressWarnings("DuplicatedCode")
34  public class RANSACLine2DRobustEstimator extends Line2DRobustEstimator {
35      /**
36       * Constant defining default threshold to determine whether points are
37       * inliers or not.
38       * Because typical resolution for points is 1 pixel, then default threshold
39       * is defined as 1.
40       */
41      public static final double DEFAULT_THRESHOLD = 1.0;
42  
43      /**
44       * Minimum value that can be set as threshold.
45       * Threshold must be strictly greater than 0.0.
46       */
47      public static final double MIN_THRESHOLD = 0.0;
48  
49      /**
50       * Threshold to determine whether points are inliers or not when testing
51       * possible estimation solutions.
52       * The threshold refers to the amount of error (i.e. distance) a possible
53       * solution has on a sampled line.
54       */
55      private double threshold;
56  
57      /**
58       * Constructor.
59       */
60      public RANSACLine2DRobustEstimator() {
61          super();
62          threshold = DEFAULT_THRESHOLD;
63      }
64  
65      /**
66       * Constructor with points.
67       *
68       * @param points 2D points to estimate a 2D line.
69       * @throws IllegalArgumentException if provided list of points doesn't have
70       *                                  a size greater or equal than MINIMUM_SIZE.
71       */
72      public RANSACLine2DRobustEstimator(List<Point2D> points) {
73          super(points);
74          threshold = DEFAULT_THRESHOLD;
75      }
76  
77      /**
78       * Constructor.
79       *
80       * @param listener listener to be notified of events such as when estimation
81       *                 starts, ends or its progress significantly changes.
82       */
83      public RANSACLine2DRobustEstimator(final Line2DRobustEstimatorListener listener) {
84          super(listener);
85          threshold = DEFAULT_THRESHOLD;
86      }
87  
88  
89      /**
90       * Constructor.
91       *
92       * @param listener listener to be notified of events such as when estimation
93       *                 starts, ends or its progress significantly changes.
94       * @param points   2D points to estimate a 2D line.
95       * @throws IllegalArgumentException if provided list of points doesn't have
96       *                                  a size greater or equal than MINIMUM_SIZE.
97       */
98      public RANSACLine2DRobustEstimator(final Line2DRobustEstimatorListener listener,
99                                         final List<Point2D> points) {
100         super(listener, points);
101         threshold = DEFAULT_THRESHOLD;
102     }
103 
104     /**
105      * Returns threshold to determine whether points 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 point.
109      *
110      * @return threshold to determine whether points 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 points 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 point.
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 line using a robust estimator and the best set of 2D
142      * points that pass through the estimated 2D line (i.e. belong to its locus).
143      *
144      * @return a 2D line.
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 Line2D 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 RANSACRobustEstimator<>(new RANSACRobustEstimatorListener<Line2D>() {
162 
163             @Override
164             public double getThreshold() {
165                 return threshold;
166             }
167 
168             @Override
169             public int getTotalSamples() {
170                 return points.size();
171             }
172 
173             @Override
174             public int getSubsetSize() {
175                 return Line2DRobustEstimator.MINIMUM_SIZE;
176             }
177 
178             @Override
179             public void estimatePreliminarSolutions(final int[] samplesIndices, final List<Line2D> solutions) {
180                 final var point1 = points.get(samplesIndices[0]);
181                 final var point2 = points.get(samplesIndices[1]);
182 
183                 try {
184                     final var line = new Line2D(point1, point2, false);
185                     solutions.add(line);
186                 } catch (final CoincidentPointsException e) {
187                     // if points are coincident, no solution is added
188                 }
189             }
190 
191             @Override
192             public double computeResidual(final Line2D currentEstimation, final int i) {
193                 return residual(currentEstimation, points.get(i));
194             }
195 
196             @Override
197             public boolean isReady() {
198                 return RANSACLine2DRobustEstimator.this.isReady();
199             }
200 
201             @Override
202             public void onEstimateStart(final RobustEstimator<Line2D> estimator) {
203                 if (listener != null) {
204                     listener.onEstimateStart(RANSACLine2DRobustEstimator.this);
205                 }
206             }
207 
208             @Override
209             public void onEstimateEnd(final RobustEstimator<Line2D> estimator) {
210                 if (listener != null) {
211                     listener.onEstimateEnd(RANSACLine2DRobustEstimator.this);
212                 }
213             }
214 
215             @Override
216             public void onEstimateNextIteration(final RobustEstimator<Line2D> estimator, final int iteration) {
217                 if (listener != null) {
218                     listener.onEstimateNextIteration(RANSACLine2DRobustEstimator.this, iteration);
219                 }
220             }
221 
222             @Override
223             public void onEstimateProgressChange(final RobustEstimator<Line2D> estimator, final float progress) {
224                 if (listener != null) {
225                     listener.onEstimateProgressChange(RANSACLine2DRobustEstimator.this, progress);
226                 }
227             }
228         });
229 
230         try {
231             locked = true;
232             innerEstimator.setConfidence(confidence);
233             innerEstimator.setMaxIterations(maxIterations);
234             innerEstimator.setProgressDelta(progressDelta);
235             return innerEstimator.estimate();
236         } catch (final com.irurueta.numerical.LockedException e) {
237             throw new LockedException(e);
238         } catch (final com.irurueta.numerical.NotReadyException e) {
239             throw new NotReadyException(e);
240         } finally {
241             locked = false;
242         }
243     }
244 
245     /**
246      * Returns method being used for robust estimation.
247      *
248      * @return method being used for robust estimation.
249      */
250     @Override
251     public RobustEstimatorMethod getMethod() {
252         return RobustEstimatorMethod.RANSAC;
253     }
254 }