1 /*
2 * Copyright (C) 2018 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.navigation.indoor.radiosource;
17
18 import com.irurueta.geometry.Point3D;
19 import com.irurueta.navigation.LockedException;
20 import com.irurueta.navigation.NotReadyException;
21 import com.irurueta.navigation.indoor.RadioSource;
22 import com.irurueta.navigation.indoor.RangingReadingLocated;
23 import com.irurueta.numerical.robust.RANSACRobustEstimator;
24 import com.irurueta.numerical.robust.RANSACRobustEstimatorListener;
25 import com.irurueta.numerical.robust.RobustEstimator;
26 import com.irurueta.numerical.robust.RobustEstimatorException;
27 import com.irurueta.numerical.robust.RobustEstimatorMethod;
28
29 import java.util.List;
30
31 /**
32 * Robustly estimated 3D position of a radio source (e.g. Wi-Fi
33 * access point or bluetooth beacon), by discarding outliers using RANSAC
34 * algorithm.
35 *
36 * @param <S> a {@link RadioSource} type.
37 */
38 public class RANSACRobustRangingRadioSourceEstimator3D<S extends RadioSource> extends
39 RobustRangingRadioSourceEstimator3D<S> {
40
41 /**
42 * Constant defining default threshold on received power (RSSI) expressed in
43 * dBm's.
44 */
45 public static final double DEFAULT_THRESHOLD = 0.1;
46
47 /**
48 * Minimum value that can be set as threshold.
49 * Threshold must be strictly greater than 0.0.s
50 */
51 public static final double MIN_THRESHOLD = 0.0;
52
53 /**
54 * Indicates that by default inliers will only be computed but not kept.
55 */
56 public static final boolean DEFAULT_COMPUTE_AND_KEEP_INLIERS = false;
57
58 /**
59 * Indicates that by default residuals will only be computed but not kept.
60 */
61 public static final boolean DEFAULT_COMPUTE_AND_KEEP_RESIDUALS = false;
62
63 /**
64 * Threshold to determine whether samples are inliers or not when testing possible solutions.
65 * The threshold refers to the amount of error on received power (RSSI) expressed
66 * in dBm's between received value that should have been received on estimated
67 * iso-tropical model and actual measured value.
68 */
69 private double threshold = DEFAULT_THRESHOLD;
70
71 /**
72 * Indicates whether inliers must be computed and kept.
73 */
74 private boolean computeAndKeepInliers = DEFAULT_COMPUTE_AND_KEEP_INLIERS;
75
76 /**
77 * Indicates whether residuals must be computed and kept.
78 */
79 private boolean computeAndKeepResiduals = DEFAULT_COMPUTE_AND_KEEP_RESIDUALS;
80
81 /**
82 * Constructor.
83 */
84 public RANSACRobustRangingRadioSourceEstimator3D() {
85 super();
86 }
87
88 /**
89 * Constructor.
90 * Sets radio signal ranging readings belonging to the same radio source.
91 *
92 * @param readings radio signal ranging readings belonging to the same
93 * radio source.
94 * @throws IllegalArgumentException if readings are not valid.
95 */
96 public RANSACRobustRangingRadioSourceEstimator3D(final List<? extends RangingReadingLocated<S, Point3D>> readings) {
97 super(readings);
98 }
99
100 /**
101 * Constructor.
102 *
103 * @param listener listener in charge of attending events raised by this instance.
104 */
105 public RANSACRobustRangingRadioSourceEstimator3D(
106 final RobustRangingRadioSourceEstimatorListener<S, Point3D> listener) {
107 super(listener);
108 }
109
110 /**
111 * Constructor.
112 * Sets radio signal readings belonging to the same radio source.
113 *
114 * @param readings radio signal readings belonging to the same radio source.
115 * @param listener listener in charge of attending events raised by this instance.
116 * @throws IllegalArgumentException if readings are not valid.
117 */
118 public RANSACRobustRangingRadioSourceEstimator3D(
119 final List<? extends RangingReadingLocated<S, Point3D>> readings,
120 final RobustRangingRadioSourceEstimatorListener<S, Point3D> listener) {
121 super(readings, listener);
122 }
123
124 /**
125 * Constructor.
126 *
127 * @param initialPosition initial position to start the estimation or radio
128 * source position.
129 */
130 public RANSACRobustRangingRadioSourceEstimator3D(final Point3D initialPosition) {
131 super(initialPosition);
132 }
133
134 /**
135 * Constructor.
136 * Sets radio signal readings belonging to the same radio source.
137 *
138 * @param readings radio signal readings belonging to the same radio source.
139 * @param initialPosition initial position to start the estimation of radio
140 * source position.
141 * @throws IllegalArgumentException if readings are not valid.
142 */
143 public RANSACRobustRangingRadioSourceEstimator3D(
144 final List<? extends RangingReadingLocated<S, Point3D>> readings, final Point3D initialPosition) {
145 super(readings, initialPosition);
146 }
147
148 /**
149 * Constructor.
150 *
151 * @param initialPosition initial position to start the estimation of radio
152 * source position.
153 * @param listener listener in charge of attending events raised by this instance.
154 */
155 public RANSACRobustRangingRadioSourceEstimator3D(
156 final Point3D initialPosition, final RobustRangingRadioSourceEstimatorListener<S, Point3D> listener) {
157 super(initialPosition, listener);
158 }
159
160 /**
161 * Constructor.
162 * Sets radio signal ranging readings belonging to the same radio source.
163 *
164 * @param readings radio signal ranging readings belonging to the same radio source.
165 * @param initialPosition initial position to start the estimation of radio source
166 * position.
167 * @param listener listener in charge of attending events raised by this instance.
168 * @throws IllegalArgumentException if readings are not valid.
169 */
170 public RANSACRobustRangingRadioSourceEstimator3D(
171 final List<? extends RangingReadingLocated<S, Point3D>> readings, final Point3D initialPosition,
172 final RobustRangingRadioSourceEstimatorListener<S, Point3D> listener) {
173 super(readings, initialPosition, listener);
174 }
175
176 /**
177 * Gets threshold to determine whether samples are inliers or not when testing possible solutions.
178 * The threshold refers to the amount of error on ranging distances.
179 *
180 * @return threshold to determine whether samples are inliers or not.
181 */
182 public double getThreshold() {
183 return threshold;
184 }
185
186 /**
187 * Sets threshold to determine whether samples are inliers or not when testing possible solutions.
188 * The threshold refers to the amount of error on ranging distances.
189 *
190 * @param threshold threshold to determine whether samples are inliers or not.
191 * @throws IllegalArgumentException if provided value is equal or less than zero.
192 * @throws LockedException if this estimator is locked.
193 */
194 public void setThreshold(final double threshold) throws LockedException {
195 if (isLocked()) {
196 throw new LockedException();
197 }
198 if (threshold <= MIN_THRESHOLD) {
199 throw new IllegalArgumentException();
200 }
201 this.threshold = threshold;
202 }
203
204
205 /**
206 * Indicates whether inliers must be computed and kept.
207 *
208 * @return true if inliers must be computed and kept, false if inliers
209 * only need to be computed but not kept.
210 */
211 public boolean isComputeAndKeepInliersEnabled() {
212 return computeAndKeepInliers;
213 }
214
215 /**
216 * Specifies whether inliers must be computed and kept.
217 *
218 * @param computeAndKeepInliers true if inliers must be computed and kept,
219 * false if inliers only need to be computed but not kept.
220 * @throws LockedException if this solver is locked.
221 */
222 public void setComputeAndKeepInliersEnabled(final boolean computeAndKeepInliers) throws LockedException {
223 if (isLocked()) {
224 throw new LockedException();
225 }
226 this.computeAndKeepInliers = computeAndKeepInliers;
227 }
228
229 /**
230 * Indicates whether residuals must be computed and kept.
231 *
232 * @return true if residuals must be computed and kept, false if residuals
233 * only need to be computed but not kept.
234 */
235 public boolean isComputeAndKeepResidualsEnabled() {
236 return computeAndKeepResiduals;
237 }
238
239 /**
240 * Specifies whether residuals must be computed and kept.
241 *
242 * @param computeAndKeepResiduals true if residuals must be computed and kept,
243 * false if residuals only need to be computed but not kept.
244 * @throws LockedException if this solver is locked.
245 */
246 public void setComputeAndKeepResidualsEnabled(final boolean computeAndKeepResiduals) throws LockedException {
247 if (isLocked()) {
248 throw new LockedException();
249 }
250 this.computeAndKeepResiduals = computeAndKeepResiduals;
251 }
252
253 /**
254 * Robustly estimates position for a radio source.
255 *
256 * @throws LockedException if instance is busy during estimation.
257 * @throws NotReadyException if estimator is not ready.
258 * @throws RobustEstimatorException if estimation fails for any reason
259 * (i.e. numerical instability, no solution available, etc).
260 */
261 @SuppressWarnings("DuplicatedCode")
262 @Override
263 public void estimate() throws LockedException, NotReadyException, RobustEstimatorException {
264 if (isLocked()) {
265 throw new LockedException();
266 }
267 if (!isReady()) {
268 throw new NotReadyException();
269 }
270
271 final var innerEstimator = new RANSACRobustEstimator<>(new RANSACRobustEstimatorListener<Solution<Point3D>>() {
272 @Override
273 public double getThreshold() {
274 return threshold;
275 }
276
277 @Override
278 public int getTotalSamples() {
279 return readings.size();
280 }
281
282 @Override
283 public int getSubsetSize() {
284 return Math.max(preliminarySubsetSize, getMinReadings());
285 }
286
287 @Override
288 public void estimatePreliminarSolutions(
289 final int[] samplesIndices, final List<Solution<Point3D>> solutions) {
290 solvePreliminarySolutions(samplesIndices, solutions);
291 }
292
293 @Override
294 public double computeResidual(final Solution<Point3D> currentEstimation, final int i) {
295 return residual(currentEstimation, i);
296 }
297
298 @Override
299 public boolean isReady() {
300 return RANSACRobustRangingRadioSourceEstimator3D.this.isReady();
301 }
302
303 @Override
304 public void onEstimateStart(final RobustEstimator<Solution<Point3D>> estimator) {
305 // no action required
306 }
307
308 @Override
309 public void onEstimateEnd(final RobustEstimator<Solution<Point3D>> estimator) {
310 // no action required
311 }
312
313 @Override
314 public void onEstimateNextIteration(
315 final RobustEstimator<Solution<Point3D>> estimator, final int iteration) {
316 if (listener != null) {
317 listener.onEstimateNextIteration(
318 RANSACRobustRangingRadioSourceEstimator3D.this, iteration);
319 }
320 }
321
322 @Override
323 public void onEstimateProgressChange(
324 final RobustEstimator<Solution<Point3D>> estimator, final float progress) {
325 if (listener != null) {
326 listener.onEstimateProgressChange(
327 RANSACRobustRangingRadioSourceEstimator3D.this, progress);
328 }
329 }
330 });
331
332 try {
333 locked = true;
334
335 if (listener != null) {
336 listener.onEstimateStart(this);
337 }
338
339 inliersData = null;
340 innerEstimator.setComputeAndKeepInliersEnabled(computeAndKeepInliers || refineResult);
341 innerEstimator.setComputeAndKeepResidualsEnabled(computeAndKeepResiduals || refineResult);
342 innerEstimator.setConfidence(confidence);
343 innerEstimator.setMaxIterations(maxIterations);
344 innerEstimator.setProgressDelta(progressDelta);
345 final var result = innerEstimator.estimate();
346 inliersData = innerEstimator.getInliersData();
347 attemptRefine(result);
348
349 if (listener != null) {
350 listener.onEstimateEnd(this);
351 }
352
353 } catch (final com.irurueta.numerical.LockedException e) {
354 throw new LockedException(e);
355 } catch (final com.irurueta.numerical.NotReadyException e) {
356 throw new NotReadyException(e);
357 } finally {
358 locked = false;
359 }
360 }
361
362 /**
363 * Returns method being used for robust estimation.
364 *
365 * @return method being used for robust estimation.
366 */
367 @Override
368 public RobustEstimatorMethod getMethod() {
369 return RobustEstimatorMethod.RANSAC;
370 }
371 }