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 }