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