View Javadoc
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 }