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.PROSACRobustEstimator;
22  import com.irurueta.numerical.robust.PROSACRobustEstimatorListener;
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 PROSAC
31   * algorithm.
32   */
33  @SuppressWarnings("DuplicatedCode")
34  public class PROSACDualConicRobustEstimator 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       * Quality scores corresponding to each line.
63       * The larger the score value the better the quality of the sample.
64       */
65      private double[] qualityScores;
66  
67      /**
68       * Constructor.
69       */
70      public PROSACDualConicRobustEstimator() {
71          super();
72          threshold = DEFAULT_THRESHOLD;
73      }
74  
75      /**
76       * Constructor with points.
77       *
78       * @param lines 2D lines to estimate a dual conic.
79       * @throws IllegalArgumentException if provided list of lines don't have
80       *                                  a size greater or equal than MINIMUM_SIZE.
81       */
82      public PROSACDualConicRobustEstimator(final List<Line2D> lines) {
83          super(lines);
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 PROSACDualConicRobustEstimator(final DualConicRobustEstimatorListener listener) {
94          super(listener);
95          threshold = DEFAULT_THRESHOLD;
96      }
97  
98  
99      /**
100      * Constructor.
101      *
102      * @param listener listener to be notified of events such as when estimation
103      *                 starts, ends or its progress significantly changes.
104      * @param lines    2D lines to estimate a dual conic.
105      * @throws IllegalArgumentException if provided list of lines don't have
106      *                                  a size greater or equal than MINIMUM_SIZE.
107      */
108     public PROSACDualConicRobustEstimator(final DualConicRobustEstimatorListener listener, final List<Line2D> lines) {
109         super(listener, lines);
110         threshold = DEFAULT_THRESHOLD;
111     }
112 
113     /**
114      * Constructor with quality scores.
115      *
116      * @param qualityScores quality scores corresponding to each provided line.
117      * @throws IllegalArgumentException if provided quality scores length is
118      *                                  smaller than MINIMUM_SIZE (i.e. 5 lines).
119      */
120     public PROSACDualConicRobustEstimator(final double[] qualityScores) {
121         super();
122         threshold = DEFAULT_THRESHOLD;
123         internalSetQualityScores(qualityScores);
124     }
125 
126     /**
127      * Constructor with lines and quality scores.
128      *
129      * @param lines         2D lines to estimate a dual conic.
130      * @param qualityScores quality scores corresponding to each provided line.
131      * @throws IllegalArgumentException if provided list of lines don't have
132      *                                  the same size as the list of provided quality scores, or it their size
133      *                                  is not greater or equal than MINIMUM_SIZE.
134      */
135     public PROSACDualConicRobustEstimator(final List<Line2D> lines, final double[] qualityScores) {
136         super(lines);
137 
138         if (qualityScores.length != lines.size()) {
139             throw new IllegalArgumentException();
140         }
141 
142         threshold = DEFAULT_THRESHOLD;
143         internalSetQualityScores(qualityScores);
144     }
145 
146     /**
147      * Constructor.
148      *
149      * @param listener      listener to be notified of events such as when estimation
150      *                      starts, ends or its progress significantly changes.
151      * @param qualityScores quality scores corresponding to each provided line.
152      * @throws IllegalArgumentException if provided quality scores length is
153      *                                  smaller than MINIMUM_SIZE (i.e. 5 lines).
154      */
155     public PROSACDualConicRobustEstimator(
156             final DualConicRobustEstimatorListener listener, final double[] qualityScores) {
157         super(listener);
158         threshold = DEFAULT_THRESHOLD;
159         internalSetQualityScores(qualityScores);
160     }
161 
162 
163     /**
164      * Constructor.
165      *
166      * @param listener      listener to be notified of events such as when estimation
167      *                      starts, ends or its progress significantly changes.
168      * @param lines         2D lines to estimate a dual conic.
169      * @param qualityScores quality scores corresponding to each provided line.
170      * @throws IllegalArgumentException if provided list of points don't have
171      *                                  the same size as the list of provided quality scores, or it their size
172      *                                  is not greater or equal than MINIMUM_SIZE.
173      */
174     public PROSACDualConicRobustEstimator(
175             final DualConicRobustEstimatorListener listener, final List<Line2D> lines, final double[] qualityScores) {
176         super(listener, lines);
177 
178         if (qualityScores.length != lines.size()) {
179             throw new IllegalArgumentException();
180         }
181 
182         threshold = DEFAULT_THRESHOLD;
183         internalSetQualityScores(qualityScores);
184     }
185 
186     /**
187      * Returns threshold to determine whether lines are inliers or not when
188      * testing possible estimation solutions.
189      * The threshold refers to the amount of error a possible solution has on a
190      * given line.
191      *
192      * @return threshold to determine whether lines are inliers or not when
193      * testing possible estimation solutions.
194      */
195     public double getThreshold() {
196         return threshold;
197     }
198 
199     /**
200      * Sets threshold to determine whether lines are inliers or not when
201      * testing possible estimation solutions.
202      * The threshold refers to the amount of algebraic error a possible
203      * solution has on a given line.
204      *
205      * @param threshold threshold to be set.
206      * @throws IllegalArgumentException if provided value is equal or less than
207      *                                  zero.
208      * @throws LockedException          if robust estimator is locked because an
209      *                                  estimation is already in progress.
210      */
211     public void setThreshold(final double threshold) throws LockedException {
212         if (isLocked()) {
213             throw new LockedException();
214         }
215         if (threshold <= MIN_THRESHOLD) {
216             throw new IllegalArgumentException();
217         }
218         this.threshold = threshold;
219     }
220 
221     /**
222      * Returns quality scores corresponding to each provided line.
223      * The larger the score value the better the quality of the sampled line.
224      *
225      * @return quality scores corresponding to each point.
226      */
227     @Override
228     public double[] getQualityScores() {
229         return qualityScores;
230     }
231 
232     /**
233      * Sets quality scores corresponding to each provided line.
234      * The larger the score value the better the quality of the sampled line.
235      *
236      * @param qualityScores quality scores corresponding to each line.
237      * @throws LockedException          if robust estimator is locked because an
238      *                                  estimation is already in progress.
239      * @throws IllegalArgumentException if provided quality scores length is
240      *                                  smaller than MINIMUM_SIZE (i.e. 5 samples).
241      */
242     @Override
243     public void setQualityScores(final double[] qualityScores) throws LockedException {
244         if (isLocked()) {
245             throw new LockedException();
246         }
247         internalSetQualityScores(qualityScores);
248     }
249 
250     /**
251      * Indicates if estimator is ready to start the conic estimation.
252      * This is true when input data (i.e. 2D lines and quality scores) are
253      * provided and a minimum of MINIMUM_SIZE lines are available.
254      *
255      * @return true if estimator is ready, false otherwise.
256      */
257     @Override
258     public boolean isReady() {
259         return super.isReady() && qualityScores != null && qualityScores.length == lines.size();
260     }
261 
262     /**
263      * Estimates a dual conic using a robust estimator and the best set of 2D
264      * lines that fit into the locus of the estimated dual conic found using the
265      * robust estimator.
266      *
267      * @return a dual conic.
268      * @throws LockedException          if robust estimator is locked because an
269      *                                  estimation is already in progress.
270      * @throws NotReadyException        if provided input data is not enough to start
271      *                                  the estimation.
272      * @throws RobustEstimatorException if estimation fails for any reason
273      *                                  (i.e. numerical instability, no solution available, etc).
274      */
275     @Override
276     public DualConic estimate() throws LockedException, NotReadyException, RobustEstimatorException {
277         if (isLocked()) {
278             throw new LockedException();
279         }
280         if (!isReady()) {
281             throw new NotReadyException();
282         }
283 
284         final var innerEstimator = new PROSACRobustEstimator<>(new PROSACRobustEstimatorListener<DualConic>() {
285 
286             @Override
287             public double getThreshold() {
288                 return threshold;
289             }
290 
291             @Override
292             public int getTotalSamples() {
293                 return lines.size();
294             }
295 
296             @Override
297             public int getSubsetSize() {
298                 return DualConicRobustEstimator.MINIMUM_SIZE;
299             }
300 
301             @Override
302             public void estimatePreliminarSolutions(final int[] samplesIndices, final List<DualConic> solutions) {
303                 final var line1 = lines.get(samplesIndices[0]);
304                 final var line2 = lines.get(samplesIndices[1]);
305                 final var line3 = lines.get(samplesIndices[2]);
306                 final var line4 = lines.get(samplesIndices[3]);
307                 final var line5 = lines.get(samplesIndices[4]);
308 
309                 try {
310                     final var dualConic = new DualConic(line1, line2, line3, line4, line5);
311                     solutions.add(dualConic);
312                 } catch (final CoincidentLinesException e) {
313                     // if points are coincident, no solution is added
314                 }
315             }
316 
317             @Override
318             public double computeResidual(final DualConic currentEstimation, int i) {
319                 return residual(currentEstimation, lines.get(i));
320             }
321 
322             @Override
323             public boolean isReady() {
324                 return PROSACDualConicRobustEstimator.this.isReady();
325             }
326 
327             @Override
328             public void onEstimateStart(final RobustEstimator<DualConic> estimator) {
329                 if (listener != null) {
330                     listener.onEstimateStart(PROSACDualConicRobustEstimator.this);
331                 }
332             }
333 
334             @Override
335             public void onEstimateEnd(final RobustEstimator<DualConic> estimator) {
336                 if (listener != null) {
337                     listener.onEstimateEnd(PROSACDualConicRobustEstimator.this);
338                 }
339             }
340 
341             @Override
342             public void onEstimateNextIteration(final RobustEstimator<DualConic> estimator, final int iteration) {
343                 if (listener != null) {
344                     listener.onEstimateNextIteration(PROSACDualConicRobustEstimator.this, iteration);
345                 }
346             }
347 
348             @Override
349             public void onEstimateProgressChange(final RobustEstimator<DualConic> estimator, final float progress) {
350                 if (listener != null) {
351                     listener.onEstimateProgressChange(PROSACDualConicRobustEstimator.this, progress);
352                 }
353             }
354 
355             @Override
356             public double[] getQualityScores() {
357                 return qualityScores;
358             }
359         });
360 
361         try {
362             locked = true;
363             innerEstimator.setConfidence(confidence);
364             innerEstimator.setMaxIterations(maxIterations);
365             innerEstimator.setProgressDelta(progressDelta);
366             return innerEstimator.estimate();
367         } catch (final com.irurueta.numerical.LockedException e) {
368             throw new LockedException(e);
369         } catch (final com.irurueta.numerical.NotReadyException e) {
370             throw new NotReadyException(e);
371         } finally {
372             locked = false;
373         }
374     }
375 
376     /**
377      * Returns method being used for robust estimation.
378      *
379      * @return method being used for robust estimation.
380      */
381     @Override
382     public RobustEstimatorMethod getMethod() {
383         return RobustEstimatorMethod.PROSAC;
384     }
385 
386     /**
387      * Sets quality scores corresponding to each provided line.
388      * This method is used internally and does not check whether instance is
389      * locked or not.
390      *
391      * @param qualityScores quality scores to be set.
392      * @throws IllegalArgumentException if provided quality scores length is
393      *                                  smaller than MINIMUM_SIZE.
394      */
395     private void internalSetQualityScores(final double[] qualityScores) {
396         if (qualityScores.length < MINIMUM_SIZE) {
397             throw new IllegalArgumentException();
398         }
399 
400         this.qualityScores = qualityScores;
401     }
402 }