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.PROMedSRobustEstimator;
24 import com.irurueta.numerical.robust.PROMedSRobustEstimatorListener;
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 PROMedS
34 * algorithm.
35 *
36 * @param <S> a {@link RadioSource} type.
37 */
38 public class PROMedSRobustRangingRadioSourceEstimator3D<S extends RadioSource> extends
39 RobustRangingRadioSourceEstimator3D<S> {
40
41 /**
42 * Default value to be used for stop threshold. Stop threshold can be used to
43 * avoid keeping the algorithm unnecessarily iterating in case that best
44 * estimated threshold using median of residuals is not small enough. Once a
45 * solution is found that generates a threshold below this value, the
46 * algorithm will stop.
47 * The stop threshold can be used to prevent the LMedS algorithm iterating
48 * too many times in cases where samples have a very similar accuracy.
49 * For instance, in cases where proportion of outliers is very small (close
50 * to 0%), and samples are very accurate (i.e. 1e-6), the algorithm would
51 * iterate for a long time trying to find the best solution when indeed
52 * there is no need to do that if a reasonable threshold has already been
53 * reached.
54 * Because of this behaviour the stop threshold can be set to a value much
55 * lower than the one typically used in RANSAC, and yet the algorithm could
56 * still produce even smaller thresholds in estimated results.
57 */
58 public static final double DEFAULT_STOP_THRESHOLD = 1e-4;
59
60 /**
61 * Minimum allowed stop threshold value.
62 */
63 public static final double MIN_STOP_THRESHOLD = 0.0;
64
65 /**
66 * Threshold to be used to keep the algorithm iterating in case that best
67 * estimated threshold using median of residuals is not small enough. Once
68 * a solution is found that generates a threshold below this value, the
69 * algorithm will stop.
70 * The stop threshold can be used to prevent the LMedS algorithm iterating
71 * too many times in cases where samples have a very similar accuracy.
72 * For instance, in cases where proportion of outliers is very small (close
73 * to 0%), and samples are very accurate (i.e. 1e-6), the algorithm would
74 * iterate for a long time trying to find the best solution when indeed
75 * there is no need to do that if a reasonable threshold has already been
76 * reached.
77 * Because of this behaviour the stop threshold can be set to a value much
78 * lower than the one typically used in RANSAC, and yet the algorithm could
79 * still produce even smaller thresholds in estimated results.
80 */
81 private double stopThreshold = DEFAULT_STOP_THRESHOLD;
82
83 /**
84 * Quality scores corresponding to each provided sample.
85 * The larger the score value the better the quality of the sample.
86 */
87 private double[] qualityScores;
88
89 /**
90 * Constructor.
91 */
92 public PROMedSRobustRangingRadioSourceEstimator3D() {
93 super();
94 }
95
96 /**
97 * Constructor.
98 * Sets radio signal ranging readings belonging to the same radio source.
99 *
100 * @param readings radio signal ranging readings belonging to the same
101 * radio source.
102 * @throws IllegalArgumentException if readings are not valid.
103 */
104 public PROMedSRobustRangingRadioSourceEstimator3D(
105 final List<? extends RangingReadingLocated<S, Point3D>> readings) {
106 super(readings);
107 }
108
109 /**
110 * Constructor.
111 *
112 * @param listener listener in charge of attending events raised by this instance.
113 */
114 public PROMedSRobustRangingRadioSourceEstimator3D(
115 final RobustRangingRadioSourceEstimatorListener<S, Point3D> listener) {
116 super(listener);
117 }
118
119 /**
120 * Constructor.
121 * Sets radio signal readings belonging to the same radio source.
122 *
123 * @param readings radio signal readings belonging to the same radio source.
124 * @param listener listener in charge of attending events raised by this instance.
125 * @throws IllegalArgumentException if readings are not valid.
126 */
127 public PROMedSRobustRangingRadioSourceEstimator3D(
128 final List<? extends RangingReadingLocated<S, Point3D>> readings,
129 final RobustRangingRadioSourceEstimatorListener<S, Point3D> listener) {
130 super(readings, listener);
131 }
132
133 /**
134 * Constructor.
135 *
136 * @param initialPosition initial position to start the estimation or radio
137 * source position.
138 */
139 public PROMedSRobustRangingRadioSourceEstimator3D(final Point3D initialPosition) {
140 super(initialPosition);
141 }
142
143 /**
144 * Constructor.
145 * Sets radio signal readings belonging to the same radio source.
146 *
147 * @param readings radio signal readings belonging to the same radio source.
148 * @param initialPosition initial position to start the estimation of radio
149 * source position.
150 * @throws IllegalArgumentException if readings are not valid.
151 */
152 public PROMedSRobustRangingRadioSourceEstimator3D(
153 final List<? extends RangingReadingLocated<S, Point3D>> readings, final Point3D initialPosition) {
154 super(readings, initialPosition);
155 }
156
157 /**
158 * Constructor.
159 *
160 * @param initialPosition initial position to start the estimation of radio
161 * source position.
162 * @param listener listener in charge of attending events raised by this instance.
163 */
164 public PROMedSRobustRangingRadioSourceEstimator3D(
165 final Point3D initialPosition, final RobustRangingRadioSourceEstimatorListener<S, Point3D> listener) {
166 super(initialPosition, listener);
167 }
168
169 /**
170 * Constructor.
171 * Sets radio signal ranging readings belonging to the same radio source.
172 *
173 * @param readings radio signal ranging readings belonging to the same radio source.
174 * @param initialPosition initial position to start the estimation of radio source
175 * position.
176 * @param listener listener in charge of attending events raised by this instance.
177 * @throws IllegalArgumentException if readings are not valid.
178 */
179 public PROMedSRobustRangingRadioSourceEstimator3D(
180 final List<? extends RangingReadingLocated<S, Point3D>> readings, final Point3D initialPosition,
181 final RobustRangingRadioSourceEstimatorListener<S, Point3D> listener) {
182 super(readings, initialPosition, listener);
183 }
184
185 /**
186 * Constructor.
187 *
188 * @param qualityScores quality scores corresponding to each provided
189 * sample. The larger the score value the better
190 * the quality of the sample.
191 * @throws IllegalArgumentException if quality scores is null, or length
192 * of quality scores is less than required minimum.
193 */
194 public PROMedSRobustRangingRadioSourceEstimator3D(final double[] qualityScores) {
195 super();
196 internalSetQualityScores(qualityScores);
197 }
198
199 /**
200 * Constructor.
201 * Sets radio signal ranging readings belonging to the same radio source.
202 *
203 * @param qualityScores quality scores corresponding to each provided
204 * sample. The larger the score value the better
205 * the quality of the sample.
206 * @param readings radio signal ranging readings belonging to the same
207 * radio source.
208 * @throws IllegalArgumentException if readings are not valid, quality scores
209 * is null, or length of quality scores is less than required minimum.
210 */
211 public PROMedSRobustRangingRadioSourceEstimator3D(
212 final double[] qualityScores, final List<? extends RangingReadingLocated<S, Point3D>> readings) {
213 super(readings);
214 internalSetQualityScores(qualityScores);
215 }
216
217 /**
218 * Constructor.
219 *
220 * @param qualityScores quality scores corresponding to each provided
221 * sample. The larger the score value the better
222 * the quality of the sample.
223 * @param listener listener in charge of attending events raised by this instance.
224 * @throws IllegalArgumentException if quality scores is null, or length
225 * of quality scores is less than required minimum.
226 */
227 public PROMedSRobustRangingRadioSourceEstimator3D(
228 final double[] qualityScores, final RobustRangingRadioSourceEstimatorListener<S, Point3D> listener) {
229 super(listener);
230 internalSetQualityScores(qualityScores);
231 }
232
233 /**
234 * Constructor.
235 * Sets radio signal readings belonging to the same radio source.
236 *
237 * @param qualityScores quality scores corresponding to each provided
238 * sample. The larger the score value the better
239 * the quality of the sample.
240 * @param readings radio signal readings belonging to the same radio source.
241 * @param listener listener in charge of attending events raised by this instance.
242 * @throws IllegalArgumentException if readings are not valid, quality scores
243 * is null, or length of quality scores is less than required minimum.
244 */
245 public PROMedSRobustRangingRadioSourceEstimator3D(
246 final double[] qualityScores, final List<? extends RangingReadingLocated<S, Point3D>> readings,
247 final RobustRangingRadioSourceEstimatorListener<S, Point3D> listener) {
248 super(readings, listener);
249 internalSetQualityScores(qualityScores);
250 }
251
252 /**
253 * Constructor.
254 *
255 * @param qualityScores quality scores corresponding to each provided
256 * sample. The larger the score value the better
257 * the quality of the sample.
258 * @param initialPosition initial position to start the estimation or radio
259 * source position.
260 * @throws IllegalArgumentException if quality scores is null, or length
261 * of quality scores is less than required minimum.
262 */
263 public PROMedSRobustRangingRadioSourceEstimator3D(final double[] qualityScores, final Point3D initialPosition) {
264 super(initialPosition);
265 internalSetQualityScores(qualityScores);
266 }
267
268 /**
269 * Constructor.
270 * Sets radio signal readings belonging to the same radio source.
271 *
272 * @param qualityScores quality scores corresponding to each provided
273 * sample. The larger the score value the better
274 * the quality of the sample.
275 * @param readings radio signal readings belonging to the same radio source.
276 * @param initialPosition initial position to start the estimation of radio
277 * source position.
278 * @throws IllegalArgumentException if readings are not valid, quality scores
279 * is null, or length of quality scores is less than required minimum.
280 */
281 public PROMedSRobustRangingRadioSourceEstimator3D(
282 final double[] qualityScores, final List<? extends RangingReadingLocated<S, Point3D>> readings,
283 final Point3D initialPosition) {
284 super(readings, initialPosition);
285 internalSetQualityScores(qualityScores);
286 }
287
288 /**
289 * Constructor.
290 *
291 * @param qualityScores quality scores corresponding to each provided
292 * sample. The larger the score value the better
293 * the quality of the sample.
294 * @param initialPosition initial position to start the estimation of radio
295 * source position.
296 * @param listener listener in charge of attending events raised by this instance.
297 * @throws IllegalArgumentException if readings are not valid, quality scores
298 * is null, or length of quality scores is less than required minimum.
299 */
300 public PROMedSRobustRangingRadioSourceEstimator3D(
301 final double[] qualityScores, final Point3D initialPosition,
302 final RobustRangingRadioSourceEstimatorListener<S, Point3D> listener) {
303 super(initialPosition, listener);
304 internalSetQualityScores(qualityScores);
305 }
306
307 /**
308 * Constructor.
309 * Sets radio signal ranging readings belonging to the same radio source.
310 *
311 * @param qualityScores quality scores corresponding to each provided
312 * sample. The larger the score value the better
313 * the quality of the sample.
314 * @param readings radio signal ranging readings belonging to the same radio source.
315 * @param initialPosition initial position to start the estimation of radio source
316 * position.
317 * @param listener listener in charge of attending events raised by this instance.
318 * @throws IllegalArgumentException if readings are not valid, quality scores
319 * is null, or length of quality scores is less than required minimum.
320 */
321 public PROMedSRobustRangingRadioSourceEstimator3D(
322 final double[] qualityScores, final List<? extends RangingReadingLocated<S, Point3D>> readings,
323 final Point3D initialPosition, final RobustRangingRadioSourceEstimatorListener<S, Point3D> listener) {
324 super(readings, initialPosition, listener);
325 internalSetQualityScores(qualityScores);
326 }
327
328 /**
329 * Returns threshold to be used to keep the algorithm iterating in case that
330 * best estimated threshold using median of residuals is not small enough.
331 * Once a solution is found that generates a threshold below this value, the
332 * algorithm will stop.
333 * The stop threshold can be used to prevent the LMedS algorithm to iterate
334 * too many times in cases where samples have a very similar accuracy.
335 * For instance, in cases where proportion of outliers is very small (close
336 * to 0%), and samples are very accurate (i.e. 1e-6), the algorithm would
337 * iterate for a long time trying to find the best solution when indeed
338 * there is no need to do that if a reasonable threshold has already been
339 * reached.
340 * Because of this behaviour the stop threshold can be set to a value much
341 * lower than the one typically used in RANSAC, and yet the algorithm could
342 * still produce even smaller thresholds in estimated results.
343 *
344 * @return stop threshold to stop the algorithm prematurely when a certain
345 * accuracy has been reached.
346 */
347 public double getStopThreshold() {
348 return stopThreshold;
349 }
350
351 /**
352 * Sets threshold to be used to keep the algorithm iterating in case that
353 * best estimated threshold using median of residuals is not small enough.
354 * Once a solution is found that generates a threshold below this value,
355 * the algorithm will stop.
356 * The stop threshold can be used to prevent the LMedS algorithm to iterate
357 * too many times in cases where samples have a very similar accuracy.
358 * For instance, in cases where proportion of outliers is very small (close
359 * to 0%), and samples are very accurate (i.e. 1e-6), the algorithm would
360 * iterate for a long time trying to find the best solution when indeed
361 * there is no need to do that if a reasonable threshold has already been
362 * reached.
363 * Because of this behaviour the stop threshold can be set to a value much
364 * lower than the one typically used in RANSAC, and yet the algorithm could
365 * still produce even smaller thresholds in estimated results.
366 *
367 * @param stopThreshold stop threshold to stop the algorithm prematurely
368 * when a certain accuracy has been reached.
369 * @throws IllegalArgumentException if provided value is zero or negative.
370 * @throws LockedException if this solver is locked.
371 */
372 public void setStopThreshold(final double stopThreshold) throws LockedException {
373 if (isLocked()) {
374 throw new LockedException();
375 }
376 if (stopThreshold <= MIN_STOP_THRESHOLD) {
377 throw new IllegalArgumentException();
378 }
379
380 this.stopThreshold = stopThreshold;
381 }
382
383 /**
384 * Returns quality scores corresponding to each pair of
385 * positions and distances (i.e. sample).
386 * The larger the score value the better the quality of the sample.
387 * This implementation always returns null.
388 * Subclasses using quality scores must implement proper behavior.
389 *
390 * @return quality scores corresponding to each sample.
391 */
392 @Override
393 public double[] getQualityScores() {
394 return qualityScores;
395 }
396
397 /**
398 * Sets quality scores corresponding to each pair of positions and
399 * distances (i.e. sample).
400 * The larger the score value the better the quality of the sample.
401 * This implementation makes no action.
402 * Subclasses using quality scores must implement proper behaviour.
403 *
404 * @param qualityScores quality scores corresponding to each pair of
405 * matched points.
406 * @throws IllegalArgumentException if provided quality scores length
407 * is smaller than minimum required samples.
408 * @throws LockedException if robust solver is locked because an
409 * estimation is already in progress.
410 */
411 @Override
412 public void setQualityScores(final double[] qualityScores) throws LockedException {
413 if (isLocked()) {
414 throw new LockedException();
415 }
416 internalSetQualityScores(qualityScores);
417 }
418
419 /**
420 * Indicates whether solver is ready to find a solution.
421 *
422 * @return true if solver is ready, false otherwise.
423 */
424 @Override
425 public boolean isReady() {
426 return super.isReady() && qualityScores != null && qualityScores.length == readings.size();
427 }
428
429 /**
430 * Robustly estimates position for a radio source.
431 *
432 * @throws LockedException if instance is busy during estimation.
433 * @throws NotReadyException if estimator is not ready.
434 * @throws RobustEstimatorException if estimation fails for any reason
435 * (i.e. numerical instability, no solution available, etc).
436 */
437 @SuppressWarnings("DuplicatedCode")
438 @Override
439 public void estimate() throws LockedException, NotReadyException, RobustEstimatorException {
440 if (isLocked()) {
441 throw new LockedException();
442 }
443 if (!isReady()) {
444 throw new NotReadyException();
445 }
446
447 final var innerEstimator = new PROMedSRobustEstimator<>(
448 new PROMedSRobustEstimatorListener<Solution<Point3D>>() {
449
450 @Override
451 public double[] getQualityScores() {
452 return qualityScores;
453 }
454
455 @Override
456 public double getThreshold() {
457 return stopThreshold;
458 }
459
460 @Override
461 public int getTotalSamples() {
462 return readings.size();
463 }
464
465 @Override
466 public int getSubsetSize() {
467 return Math.max(preliminarySubsetSize, getMinReadings());
468 }
469
470 @Override
471 public void estimatePreliminarSolutions(
472 final int[] samplesIndices, final List<Solution<Point3D>> solutions) {
473 solvePreliminarySolutions(samplesIndices, solutions);
474 }
475
476 @Override
477 public double computeResidual(final Solution<Point3D> currentEstimation, final int i) {
478 return residual(currentEstimation, i);
479 }
480
481 @Override
482 public boolean isReady() {
483 return PROMedSRobustRangingRadioSourceEstimator3D.this.isReady();
484 }
485
486 @Override
487 public void onEstimateStart(final RobustEstimator<Solution<Point3D>> estimator) {
488 // no action needed
489 }
490
491 @Override
492 public void onEstimateEnd(final RobustEstimator<Solution<Point3D>> estimator) {
493 // no action needed
494 }
495
496 @Override
497 public void onEstimateNextIteration(
498 final RobustEstimator<Solution<Point3D>> estimator, final int iteration) {
499 if (listener != null) {
500 listener.onEstimateNextIteration(
501 PROMedSRobustRangingRadioSourceEstimator3D.this, iteration);
502 }
503 }
504
505 @Override
506 public void onEstimateProgressChange(
507 final RobustEstimator<Solution<Point3D>> estimator, final float progress) {
508 if (listener != null) {
509 listener.onEstimateProgressChange(
510 PROMedSRobustRangingRadioSourceEstimator3D.this, progress);
511 }
512 }
513 });
514
515 try {
516 locked = true;
517
518 if (listener != null) {
519 listener.onEstimateStart(this);
520 }
521
522 inliersData = null;
523
524 // inlier thresholds are disable to obtain a less restrictive amount of inliers
525 innerEstimator.setUseInlierThresholds(false);
526
527 innerEstimator.setConfidence(confidence);
528 innerEstimator.setMaxIterations(maxIterations);
529 innerEstimator.setProgressDelta(progressDelta);
530 final var result = innerEstimator.estimate();
531 inliersData = innerEstimator.getInliersData();
532 attemptRefine(result);
533
534 if (listener != null) {
535 listener.onEstimateEnd(this);
536 }
537
538 } catch (final com.irurueta.numerical.LockedException e) {
539 throw new LockedException(e);
540 } catch (final com.irurueta.numerical.NotReadyException e) {
541 throw new NotReadyException(e);
542 } finally {
543 locked = false;
544 }
545 }
546
547 /**
548 * Returns method being used for robust estimation.
549 *
550 * @return method being used for robust estimation.
551 */
552 @Override
553 public RobustEstimatorMethod getMethod() {
554 return RobustEstimatorMethod.PROMEDS;
555 }
556
557 /**
558 * Sets quality scores corresponding to each provided sample.
559 * This method is used internally and does not check whether instance is
560 * locked or not.
561 *
562 * @param qualityScores quality scores to be set.
563 * @throws IllegalArgumentException if provided quality scores length
564 * is smaller than 3 samples.
565 */
566 private void internalSetQualityScores(final double[] qualityScores) {
567 if (qualityScores == null || qualityScores.length < getMinReadings()) {
568 throw new IllegalArgumentException();
569 }
570
571 this.qualityScores = qualityScores;
572 }
573 }