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.RssiReadingLocated;
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 estimate 3D position, transmitted power and path-loss exponent of a radio source
33   * (e.g. Wi-Fi access point or bluetooth beacon), by discarding outliers using RANSAC
34   * algorithm and assuming that the access point emits isotropically following the
35   * expression below:
36   * Pr = Pt*Gt*Gr*lambda^2 / (4*pi*d)^2,
37   * where Pr is the received power (expressed in mW),
38   * Gt is the Gain of the transmission antenna
39   * Gr is the Gain of the receiver antenna
40   * d is the distance between emitter and receiver
41   * and lambda is the wavelength and is equal to: lambda = c / f,
42   * where c is the speed of light
43   * and f is the carrier frequency of the signal.
44   * Because usually information about the antenna of the radio source cannot be
45   * retrieved (because many measurements are made on unknown devices where
46   * physical access is not possible), this implementation will estimate the
47   * equivalent transmitted power as: Pte = Pt * Gt * Gr.
48   * If RssiReadings contain RSSI standard deviations, those values will be used,
49   * otherwise it will be assumed an RSSI standard deviation of 1 dB.
50   * Implementations of this class should be able to detect and discard outliers in
51   * order to find the best solution.
52   * <p>
53   * IMPORTANT: When using this class estimation can be done using a
54   * combination of radio source position, transmitted power and path loss
55   * exponent. However enabling all three estimations usually achieves
56   * inaccurate results. When using this class, estimation must be of at least
57   * one parameter (position, transmitted power or path loss exponent) when
58   * initial values are provided for the other two, and at most it should consist
59   * of two parameters (either position and transmitted power, position and
60   * path loss exponent or transmitted power and path loss exponent), providing an
61   * initial value for the remaining parameter.
62   *
63   * @param <S> a {@link RadioSource} type.
64   */
65  @SuppressWarnings("Duplicates")
66  public class RANSACRobustRssiRadioSourceEstimator3D<S extends RadioSource> extends RobustRssiRadioSourceEstimator3D<S> {
67  
68      /**
69       * Constant defining default threshold on received power (RSSI) expressed in
70       * dBm's.
71       */
72      public static final double DEFAULT_THRESHOLD = 0.1;
73  
74      /**
75       * Minimum value that can be set as threshold.
76       * Threshold must be strictly greater than 0.0.s
77       */
78      public static final double MIN_THRESHOLD = 0.0;
79  
80      /**
81       * Indicates that by default inliers will only be computed but not kept.
82       */
83      public static final boolean DEFAULT_COMPUTE_AND_KEEP_INLIERS = false;
84  
85      /**
86       * Indicates that by default residuals will only be computed but not kept.
87       */
88      public static final boolean DEFAULT_COMPUTE_AND_KEEP_RESIDUALS = false;
89  
90      /**
91       * Threshold to determine whether samples are inliers or not when testing possible solutions.
92       * The threshold refers to the amount of error on received power (RSSI) expressed
93       * in dBm's between received value that should have been received on estimated
94       * iso-tropical model and actual measured value.
95       */
96      private double threshold = DEFAULT_THRESHOLD;
97  
98      /**
99       * Indicates whether inliers must be computed and kept.
100      */
101     private boolean computeAndKeepInliers = DEFAULT_COMPUTE_AND_KEEP_INLIERS;
102 
103     /**
104      * Indicates whether residuals must be computed and kept.
105      */
106     private boolean computeAndKeepResiduals = DEFAULT_COMPUTE_AND_KEEP_RESIDUALS;
107 
108     /**
109      * Constructor.
110      */
111     public RANSACRobustRssiRadioSourceEstimator3D() {
112         super();
113     }
114 
115     /**
116      * Constructor.
117      * Sets signal readings belonging to the same radio source.
118      *
119      * @param readings signal readings belonging to the same radio source.
120      * @throws IllegalArgumentException if readings are not valid.
121      */
122     public RANSACRobustRssiRadioSourceEstimator3D(final List<? extends RssiReadingLocated<S, Point3D>> readings) {
123         super(readings);
124     }
125 
126     /**
127      * Constructor.
128      *
129      * @param listener listener in charge of attending events raised by this instance.
130      */
131     public RANSACRobustRssiRadioSourceEstimator3D(final RobustRssiRadioSourceEstimatorListener<S, Point3D> listener) {
132         super(listener);
133     }
134 
135     /**
136      * Constructor.
137      * Sets signal readings belonging to the same radio source.
138      *
139      * @param readings signal readings belonging to the same radio source.
140      * @param listener listener in charge of attending events raised by this instance.
141      * @throws IllegalArgumentException if readings are not valid.
142      */
143     public RANSACRobustRssiRadioSourceEstimator3D(
144             final List<? extends RssiReadingLocated<S, Point3D>> readings,
145             final RobustRssiRadioSourceEstimatorListener<S, Point3D> listener) {
146         super(readings, listener);
147     }
148 
149     /**
150      * Constructor.
151      * Sets signal readings belonging to the same radio source.
152      *
153      * @param readings        signal readings belonging to the same radio source.
154      * @param initialPosition initial position to start the estimation of radio
155      *                        source position.
156      * @throws IllegalArgumentException if readings are not valid.
157      */
158     public RANSACRobustRssiRadioSourceEstimator3D(
159             final List<? extends RssiReadingLocated<S, Point3D>> readings, final Point3D initialPosition) {
160         super(readings, initialPosition);
161     }
162 
163     /**
164      * Constructor.
165      *
166      * @param initialPosition initial position to start the estimation of radio
167      *                        source position.
168      */
169     public RANSACRobustRssiRadioSourceEstimator3D(final Point3D initialPosition) {
170         super(initialPosition);
171     }
172 
173     /**
174      * Constructor.
175      *
176      * @param initialPosition initial position to start the estimation of radio
177      *                        source position.
178      * @param listener        listener in charge of attending events raised by this instance.
179      */
180     public RANSACRobustRssiRadioSourceEstimator3D(
181             final Point3D initialPosition, final RobustRssiRadioSourceEstimatorListener<S, Point3D> listener) {
182         super(initialPosition, listener);
183     }
184 
185     /**
186      * Constructor.
187      * Sets signal readings belonging to the same radio source.
188      *
189      * @param readings        signal readings belonging to the same radio source.
190      * @param initialPosition initial position to start the estimation of radio
191      *                        source position.
192      * @param listener        listener in charge of attending events raised by this instance.
193      * @throws IllegalArgumentException if readings are not valid.
194      */
195     public RANSACRobustRssiRadioSourceEstimator3D(
196             final List<? extends RssiReadingLocated<S, Point3D>> readings, final Point3D initialPosition,
197             final RobustRssiRadioSourceEstimatorListener<S, Point3D> listener) {
198         super(readings, initialPosition, listener);
199     }
200 
201     /**
202      * Constructor.
203      *
204      * @param initialTransmittedPowerdBm initial transmitted power to start the
205      *                                   estimation of radio source transmitted power
206      *                                   (expressed in dBm's)
207      */
208     public RANSACRobustRssiRadioSourceEstimator3D(final Double initialTransmittedPowerdBm) {
209         super(initialTransmittedPowerdBm);
210     }
211 
212     /**
213      * Constructor.
214      * Sets signal readings belonging to the same radio source.
215      *
216      * @param readings                   signal readings belonging to the same radio source.
217      * @param initialTransmittedPowerdBm initial transmitted power to start the
218      *                                   estimation of radio source transmitted power
219      *                                   (expressed in dBm's)
220      * @throws IllegalArgumentException if readings are not valid.
221      */
222     public RANSACRobustRssiRadioSourceEstimator3D(
223             final List<? extends RssiReadingLocated<S, Point3D>> readings, final Double initialTransmittedPowerdBm) {
224         super(readings, initialTransmittedPowerdBm);
225     }
226 
227     /**
228      * Constructor.
229      *
230      * @param initialTransmittedPowerdBm initial transmitted power to start the
231      *                                   estimation of radio source transmitted power
232      *                                   (expressed in dBm's)
233      * @param listener                   listener in charge of attending events raised by this instance.
234      */
235     public RANSACRobustRssiRadioSourceEstimator3D(
236             final Double initialTransmittedPowerdBm,
237             final RobustRssiRadioSourceEstimatorListener<S, Point3D> listener) {
238         super(initialTransmittedPowerdBm, listener);
239     }
240 
241     /**
242      * Constructor.
243      * Sets signal readings belonging to the same radio source.
244      *
245      * @param readings                   signal readings belonging to the same radio source.
246      * @param initialTransmittedPowerdBm initial transmitted power to start the
247      *                                   estimation of radio source transmitted power
248      *                                   (expressed in dBm's)
249      * @param listener                   listener in charge of attending events raised by this instance.
250      * @throws IllegalArgumentException if readings are not valid.
251      */
252     public RANSACRobustRssiRadioSourceEstimator3D(
253             final List<? extends RssiReadingLocated<S, Point3D>> readings, final Double initialTransmittedPowerdBm,
254             final RobustRssiRadioSourceEstimatorListener<S, Point3D> listener) {
255         super(readings, initialTransmittedPowerdBm, listener);
256     }
257 
258     /**
259      * Constructor.
260      * Sets signal readings belonging to the same radio source.
261      *
262      * @param readings                   signal readings belonging to the same radio source.
263      * @param initialPosition            initial position to start the estimation of radio
264      *                                   source position.
265      * @param initialTransmittedPowerdBm initial transmitted power to start the
266      *                                   estimation of radio source transmitted power
267      *                                   (expressed in dBm's).
268      * @throws IllegalArgumentException if readings are not valid.
269      */
270     public RANSACRobustRssiRadioSourceEstimator3D(
271             final List<? extends RssiReadingLocated<S, Point3D>> readings, final Point3D initialPosition,
272             final Double initialTransmittedPowerdBm) {
273         super(readings, initialPosition, initialTransmittedPowerdBm);
274     }
275 
276     /**
277      * Constructor.
278      *
279      * @param initialPosition            initial position to start the estimation of radio
280      *                                   source position.
281      * @param initialTransmittedPowerdBm initial transmitted power to start the
282      *                                   estimation of radio source transmitted power
283      *                                   (expressed in dBm's).
284      */
285     public RANSACRobustRssiRadioSourceEstimator3D(
286             final Point3D initialPosition, final Double initialTransmittedPowerdBm) {
287         super(initialPosition, initialTransmittedPowerdBm);
288     }
289 
290     /**
291      * Constructor.
292      *
293      * @param initialPosition            initial position to start the estimation of radio
294      *                                   source position.
295      * @param initialTransmittedPowerdBm initial transmitted power to start the
296      *                                   estimation of radio source transmitted power
297      *                                   (expressed in dBm's).
298      * @param listener                   in charge of attending events raised by this instance.
299      */
300     public RANSACRobustRssiRadioSourceEstimator3D(
301             final Point3D initialPosition, final Double initialTransmittedPowerdBm,
302             final RobustRssiRadioSourceEstimatorListener<S, Point3D> listener) {
303         super(initialPosition, initialTransmittedPowerdBm, listener);
304     }
305 
306     /**
307      * Constructor.
308      * Sets signal readings belonging to the same radio source.
309      *
310      * @param readings                   signal readings belonging to the same radio source.
311      * @param initialPosition            initial position to start the estimation of radio
312      *                                   source position.
313      * @param initialTransmittedPowerdBm initial transmitted power to start the
314      *                                   estimation of radio source transmitted power
315      *                                   (expressed in dBm's).
316      * @param listener                   listener in charge of attending events raised by this instance.
317      * @throws IllegalArgumentException if readings are not valid.
318      */
319     public RANSACRobustRssiRadioSourceEstimator3D(
320             final List<? extends RssiReadingLocated<S, Point3D>> readings, final Point3D initialPosition,
321             final Double initialTransmittedPowerdBm,
322             final RobustRssiRadioSourceEstimatorListener<S, Point3D> listener) {
323         super(readings, initialPosition, initialTransmittedPowerdBm, listener);
324     }
325 
326     /**
327      * Constructor.
328      * Sets signal readings belonging to the same radio source.
329      *
330      * @param readings                   signal readings belonging to the same radio source.
331      * @param initialPosition            initial position to start the estimation of radio
332      *                                   source position.
333      * @param initialTransmittedPowerdBm initial transmitted power to start the
334      *                                   estimation of radio source transmitted power
335      *                                   (expressed in dBm's).
336      * @param initialPathLossExponent    initial path loss exponent. A typical value is 2.0.
337      * @throws IllegalArgumentException if readings are not valid.
338      */
339     public RANSACRobustRssiRadioSourceEstimator3D(
340             final List<? extends RssiReadingLocated<S, Point3D>> readings, final Point3D initialPosition,
341             final Double initialTransmittedPowerdBm, final double initialPathLossExponent) {
342         super(readings, initialPosition, initialTransmittedPowerdBm, initialPathLossExponent);
343     }
344 
345     /**
346      * Constructor.
347      *
348      * @param initialPosition            initial position to start the estimation of radio
349      *                                   source position.
350      * @param initialTransmittedPowerdBm initial transmitted power to start the
351      *                                   estimation of radio source transmitted power
352      *                                   (expressed in dBm's).
353      * @param initialPathLossExponent    initial path loss exponent. A typical value is 2.0.
354      */
355     public RANSACRobustRssiRadioSourceEstimator3D(
356             final Point3D initialPosition, final Double initialTransmittedPowerdBm,
357             final double initialPathLossExponent) {
358         super(initialPosition, initialTransmittedPowerdBm, initialPathLossExponent);
359     }
360 
361     /**
362      * Constructor.
363      *
364      * @param initialPosition            initial position to start the estimation of radio
365      *                                   source position.
366      * @param initialTransmittedPowerdBm initial transmitted power to start the
367      *                                   estimation of radio source transmitted power
368      *                                   (expressed in dBm's).
369      * @param initialPathLossExponent    initial path loss exponent. A typical value is 2.0.
370      * @param listener                   listener in charge of attending events raised by this instance.
371      */
372     public RANSACRobustRssiRadioSourceEstimator3D(
373             final Point3D initialPosition, final Double initialTransmittedPowerdBm,
374             final double initialPathLossExponent, final RobustRssiRadioSourceEstimatorListener<S, Point3D> listener) {
375         super(initialPosition, initialTransmittedPowerdBm, initialPathLossExponent, listener);
376     }
377 
378     /**
379      * Constructor.
380      * Sets signal readings belonging to the same radio source.
381      *
382      * @param readings                   signal readings belonging to the same radio source.
383      * @param initialPosition            initial position to start the estimation of radio
384      *                                   source position.
385      * @param initialTransmittedPowerdBm initial transmitted power to start the
386      *                                   estimation of radio source transmitted power
387      *                                   (expressed in dBm's).
388      * @param initialPathLossExponent    initial path loss exponent. A typical value is 2.0.
389      * @param listener                   listener in charge of attending events raised by this instance.
390      * @throws IllegalArgumentException if readings are not valid.
391      */
392     public RANSACRobustRssiRadioSourceEstimator3D(
393             final List<? extends RssiReadingLocated<S, Point3D>> readings, final Point3D initialPosition,
394             final Double initialTransmittedPowerdBm, final double initialPathLossExponent,
395             final RobustRssiRadioSourceEstimatorListener<S, Point3D> listener) {
396         super(readings, initialPosition, initialTransmittedPowerdBm, initialPathLossExponent, listener);
397     }
398 
399     /**
400      * Gets threshold to determine whether samples are inliers or not when testing possible solutions.
401      * The threshold refers to the amount of error on received power (RSSI) expressed
402      * in dBm's between received value that should have been received on estimated
403      * iso-tropical model and actual measured value.
404      *
405      * @return threshold to determine whether samples are inliers or not.
406      */
407     public double getThreshold() {
408         return threshold;
409     }
410 
411     /**
412      * Sets threshold to determine whether samples are inliers or not when testing possible solutions.
413      * The threshold refers to the amount of error on received power (RSSI) expressed
414      * in dBm's between received value that should have been received on estimated
415      * iso-tropical model and actual measured value.
416      *
417      * @param threshold threshold to determine whether samples are inliers or not.
418      * @throws IllegalArgumentException if provided value is equal or less than zero.
419      * @throws LockedException          if this estimator is locked.
420      */
421     public void setThreshold(final double threshold) throws LockedException {
422         if (isLocked()) {
423             throw new LockedException();
424         }
425         if (threshold <= MIN_THRESHOLD) {
426             throw new IllegalArgumentException();
427         }
428         this.threshold = threshold;
429     }
430 
431     /**
432      * Indicates whether inliers must be computed and kept.
433      *
434      * @return true if inliers must be computed and kept, false if inliers
435      * only need to be computed but not kept.
436      */
437     public boolean isComputeAndKeepInliersEnabled() {
438         return computeAndKeepInliers;
439     }
440 
441     /**
442      * Specifies whether inliers must be computed and kept.
443      *
444      * @param computeAndKeepInliers true if inliers must be computed and kept,
445      *                              false if inliers only need to be computed but not kept.
446      * @throws LockedException if this solver is locked.
447      */
448     public void setComputeAndKeepInliersEnabled(final boolean computeAndKeepInliers) throws LockedException {
449         if (isLocked()) {
450             throw new LockedException();
451         }
452         this.computeAndKeepInliers = computeAndKeepInliers;
453     }
454 
455     /**
456      * Indicates whether residuals must be computed and kept.
457      *
458      * @return true if residuals must be computed and kept, false if residuals
459      * only need to be computed but not kept.
460      */
461     public boolean isComputeAndKeepResidualsEnabled() {
462         return computeAndKeepResiduals;
463     }
464 
465     /**
466      * Specifies whether residuals must be computed and kept.
467      *
468      * @param computeAndKeepResiduals true if residuals must be computed and kept,
469      *                                false if residuals only need to be computed but not kept.
470      * @throws LockedException if this solver is locked.
471      */
472     public void setComputeAndKeepResidualsEnabled(final boolean computeAndKeepResiduals) throws LockedException {
473         if (isLocked()) {
474             throw new LockedException();
475         }
476         this.computeAndKeepResiduals = computeAndKeepResiduals;
477     }
478 
479     /**
480      * Robustly estimates position, transmitted power and path-loss exponent for a
481      * radio source.
482      *
483      * @throws LockedException          if instance is busy during estimation.
484      * @throws NotReadyException        if estimator is not ready.
485      * @throws RobustEstimatorException if estimation fails for any reason
486      *                                  (i.e. numerical instability, no solution available, etc).
487      */
488     @Override
489     public void estimate() throws LockedException, NotReadyException, RobustEstimatorException {
490         if (isLocked()) {
491             throw new LockedException();
492         }
493         if (!isReady()) {
494             throw new NotReadyException();
495         }
496 
497         final var innerEstimator = new RANSACRobustEstimator<>(new RANSACRobustEstimatorListener<Solution<Point3D>>() {
498             @Override
499             public double getThreshold() {
500                 return threshold;
501             }
502 
503             @Override
504             public int getTotalSamples() {
505                 return readings.size();
506             }
507 
508             @Override
509             public int getSubsetSize() {
510                 return Math.max(preliminarySubsetSize, getMinReadings());
511             }
512 
513             @Override
514             public void estimatePreliminarSolutions(
515                     final int[] samplesIndices, final List<Solution<Point3D>> solutions) {
516                 solvePreliminarySolutions(samplesIndices, solutions);
517             }
518 
519             @Override
520             public double computeResidual(final Solution<Point3D> currentEstimation, final int i) {
521                 return residual(currentEstimation, i);
522             }
523 
524             @Override
525             public boolean isReady() {
526                 return RANSACRobustRssiRadioSourceEstimator3D.this.isReady();
527             }
528 
529             @Override
530             public void onEstimateStart(final RobustEstimator<Solution<Point3D>> estimator) {
531                 // no action needed
532             }
533 
534             @Override
535             public void onEstimateEnd(final RobustEstimator<Solution<Point3D>> estimator) {
536                 // no action needed
537             }
538 
539             @Override
540             public void onEstimateNextIteration(
541                     final RobustEstimator<Solution<Point3D>> estimator, final int iteration) {
542                 if (listener != null) {
543                     listener.onEstimateNextIteration(RANSACRobustRssiRadioSourceEstimator3D.this, iteration);
544                 }
545             }
546 
547             @Override
548             public void onEstimateProgressChange(
549                     final RobustEstimator<Solution<Point3D>> estimator, final float progress) {
550                 if (listener != null) {
551                     listener.onEstimateProgressChange(RANSACRobustRssiRadioSourceEstimator3D.this, progress);
552                 }
553             }
554         });
555 
556         try {
557             locked = true;
558 
559             if (listener != null) {
560                 listener.onEstimateStart(this);
561             }
562 
563             inliersData = null;
564             innerEstimator.setComputeAndKeepInliersEnabled(computeAndKeepInliers || refineResult);
565             innerEstimator.setComputeAndKeepResidualsEnabled(computeAndKeepResiduals || refineResult);
566             innerEstimator.setConfidence(confidence);
567             innerEstimator.setMaxIterations(maxIterations);
568             innerEstimator.setProgressDelta(progressDelta);
569             final var result = innerEstimator.estimate();
570             inliersData = innerEstimator.getInliersData();
571             attemptRefine(result);
572 
573             if (listener != null) {
574                 listener.onEstimateEnd(this);
575             }
576 
577         } catch (final com.irurueta.numerical.LockedException e) {
578             throw new LockedException(e);
579         } catch (final com.irurueta.numerical.NotReadyException e) {
580             throw new NotReadyException(e);
581         } finally {
582             locked = false;
583         }
584     }
585 
586     /**
587      * Returns method being used for robust estimation.
588      *
589      * @return method being used for robust estimation.
590      */
591     @Override
592     public RobustEstimatorMethod getMethod() {
593         return RobustEstimatorMethod.RANSAC;
594     }
595 }