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.LMedSRobustEstimator;
24  import com.irurueta.numerical.robust.LMedSRobustEstimatorListener;
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 LMedS
34   * algorithm.
35   *
36   * @param <S> a {@link RadioSource} type.
37   */
38  public class LMedSRobustRangingRadioSourceEstimator3D<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       * Constructor.
85       */
86      public LMedSRobustRangingRadioSourceEstimator3D() {
87          super();
88      }
89  
90      /**
91       * Constructor.
92       * Sets radio signal ranging readings belonging to the same radio source.
93       *
94       * @param readings radio signal ranging readings belonging to the same
95       *                 radio source.
96       * @throws IllegalArgumentException if readings are not valid.
97       */
98      public LMedSRobustRangingRadioSourceEstimator3D(final List<? extends RangingReadingLocated<S, Point3D>> readings) {
99          super(readings);
100     }
101 
102     /**
103      * Constructor.
104      *
105      * @param listener listener in charge of attending events raised by this instance.
106      */
107     public LMedSRobustRangingRadioSourceEstimator3D(
108             final RobustRangingRadioSourceEstimatorListener<S, Point3D> listener) {
109         super(listener);
110     }
111 
112     /**
113      * Constructor.
114      * Sets radio signal readings belonging to the same radio source.
115      *
116      * @param readings radio signal readings belonging to the same radio source.
117      * @param listener listener in charge of attending events raised by this instance.
118      * @throws IllegalArgumentException if readings are not valid.
119      */
120     public LMedSRobustRangingRadioSourceEstimator3D(
121             final List<? extends RangingReadingLocated<S, Point3D>> readings,
122             final RobustRangingRadioSourceEstimatorListener<S, Point3D> listener) {
123         super(readings, listener);
124     }
125 
126     /**
127      * Constructor.
128      *
129      * @param initialPosition initial position to start the estimation or radio
130      *                        source position.
131      */
132     public LMedSRobustRangingRadioSourceEstimator3D(final Point3D initialPosition) {
133         super(initialPosition);
134     }
135 
136     /**
137      * Constructor.
138      * <p>
139      * Sets radio signal readings belonging to the same radio source.
140      *
141      * @param readings        radio signal readings belonging to the same radio source.
142      * @param initialPosition initial position to start the estimation of radio
143      *                        source position.
144      * @throws IllegalArgumentException if readings are not valid.
145      */
146     public LMedSRobustRangingRadioSourceEstimator3D(
147             final List<? extends RangingReadingLocated<S, Point3D>> readings, final Point3D initialPosition) {
148         super(readings, initialPosition);
149     }
150 
151     /**
152      * Constructor.
153      *
154      * @param initialPosition initial position to start the estimation of radio
155      *                        source position.
156      * @param listener        listener in charge of attending events raised by this instance.
157      */
158     public LMedSRobustRangingRadioSourceEstimator3D(
159             final Point3D initialPosition, final RobustRangingRadioSourceEstimatorListener<S, Point3D> listener) {
160         super(initialPosition, listener);
161     }
162 
163     /**
164      * Constructor.
165      * Sets radio signal ranging readings belonging to the same radio source.
166      *
167      * @param readings        radio signal ranging readings belonging to the same radio source.
168      * @param initialPosition initial position to start the estimation of radio source
169      *                        position.
170      * @param listener        listener in charge of attending events raised by this instance.
171      * @throws IllegalArgumentException if readings are not valid.
172      */
173     public LMedSRobustRangingRadioSourceEstimator3D(
174             final List<? extends RangingReadingLocated<S, Point3D>> readings, final Point3D initialPosition,
175             final RobustRangingRadioSourceEstimatorListener<S, Point3D> listener) {
176         super(readings, initialPosition, listener);
177     }
178 
179     /**
180      * Returns threshold to be used to keep the algorithm iterating in case that
181      * best estimated threshold using median of residuals is not small enough.
182      * Once a solution is found that generates a threshold below this value, the
183      * algorithm will stop.
184      * The stop threshold can be used to prevent the LMedS algorithm to iterate
185      * too many times in cases where samples have a very similar accuracy.
186      * For instance, in cases where proportion of outliers is very small (close
187      * to 0%), and samples are very accurate (i.e. 1e-6), the algorithm would
188      * iterate for a long time trying to find the best solution when indeed
189      * there is no need to do that if a reasonable threshold has already been
190      * reached.
191      * Because of this behaviour the stop threshold can be set to a value much
192      * lower than the one typically used in RANSAC, and yet the algorithm could
193      * still produce even smaller thresholds in estimated results.
194      *
195      * @return stop threshold to stop the algorithm prematurely when a certain
196      * accuracy has been reached.
197      */
198     public double getStopThreshold() {
199         return stopThreshold;
200     }
201 
202     /**
203      * Sets threshold to be used to keep the algorithm iterating in case that
204      * best estimated threshold using median of residuals is not small enough.
205      * Once a solution is found that generates a threshold below this value,
206      * the algorithm will stop.
207      * The stop threshold can be used to prevent the LMedS algorithm to iterate
208      * too many times in cases where samples have a very similar accuracy.
209      * For instance, in cases where proportion of outliers is very small (close
210      * to 0%), and samples are very accurate (i.e. 1e-6), the algorithm would
211      * iterate for a long time trying to find the best solution when indeed
212      * there is no need to do that if a reasonable threshold has already been
213      * reached.
214      * Because of this behaviour the stop threshold can be set to a value much
215      * lower than the one typically used in RANSAC, and yet the algorithm could
216      * still produce even smaller thresholds in estimated results.
217      *
218      * @param stopThreshold stop threshold to stop the algorithm prematurely
219      *                      when a certain accuracy has been reached.
220      * @throws IllegalArgumentException if provided value is zero or negative.
221      * @throws LockedException          if this solver is locked.
222      */
223     public void setStopThreshold(final double stopThreshold) throws LockedException {
224         if (isLocked()) {
225             throw new LockedException();
226         }
227         if (stopThreshold <= MIN_STOP_THRESHOLD) {
228             throw new IllegalArgumentException();
229         }
230 
231         this.stopThreshold = stopThreshold;
232     }
233 
234     /**
235      * Robustly estimates position for a radio source.
236      *
237      * @throws LockedException          if instance is busy during estimation.
238      * @throws NotReadyException        if estimator is not ready.
239      * @throws RobustEstimatorException if estimation fails for any reason
240      *                                  (i.e. numerical instability, no solution available, etc).
241      */
242     @SuppressWarnings("DuplicatedCode")
243     @Override
244     public void estimate() throws LockedException, NotReadyException,
245             RobustEstimatorException {
246         if (isLocked()) {
247             throw new LockedException();
248         }
249         if (!isReady()) {
250             throw new NotReadyException();
251         }
252 
253         final var innerEstimator = new LMedSRobustEstimator<>(new LMedSRobustEstimatorListener<Solution<Point3D>>() {
254             @Override
255             public int getTotalSamples() {
256                 return readings.size();
257             }
258 
259             @Override
260             public int getSubsetSize() {
261                 return Math.max(preliminarySubsetSize, getMinReadings());
262             }
263 
264             @Override
265             public void estimatePreliminarSolutions(
266                     final int[] sampleIndices, final List<Solution<Point3D>> solutions) {
267                 solvePreliminarySolutions(sampleIndices, solutions);
268             }
269 
270             @Override
271             public double computeResidual(final Solution<Point3D> currentEstimation, final int i) {
272                 return residual(currentEstimation, i);
273             }
274 
275             @Override
276             public boolean isReady() {
277                 return LMedSRobustRangingRadioSourceEstimator3D.this.isReady();
278             }
279 
280             @Override
281             public void onEstimateStart(final RobustEstimator<Solution<Point3D>> estimator) {
282                 // no action needed
283             }
284 
285             @Override
286             public void onEstimateEnd(final RobustEstimator<Solution<Point3D>> estimator) {
287                 // no action needed
288             }
289 
290             @Override
291             public void onEstimateNextIteration(
292                     final RobustEstimator<Solution<Point3D>> estimator, final int iteration) {
293                 if (listener != null) {
294                     listener.onEstimateNextIteration(
295                             LMedSRobustRangingRadioSourceEstimator3D.this, iteration);
296                 }
297             }
298 
299             @Override
300             public void onEstimateProgressChange(
301                     final RobustEstimator<Solution<Point3D>> estimator, final float progress) {
302                 if (listener != null) {
303                     listener.onEstimateProgressChange(LMedSRobustRangingRadioSourceEstimator3D.this, progress);
304                 }
305             }
306         });
307 
308         try {
309             locked = true;
310 
311             if (listener != null) {
312                 listener.onEstimateStart(this);
313             }
314 
315             inliersData = null;
316             innerEstimator.setConfidence(confidence);
317             innerEstimator.setMaxIterations(maxIterations);
318             innerEstimator.setProgressDelta(progressDelta);
319             final var result = innerEstimator.estimate();
320             inliersData = innerEstimator.getInliersData();
321             attemptRefine(result);
322 
323             if (listener != null) {
324                 listener.onEstimateEnd(this);
325             }
326 
327         } catch (final com.irurueta.numerical.LockedException e) {
328             throw new LockedException(e);
329         } catch (final com.irurueta.numerical.NotReadyException e) {
330             throw new NotReadyException(e);
331         } finally {
332             locked = false;
333         }
334     }
335 
336     /**
337      * Returns method being used for robust estimation.
338      *
339      * @return method being used for robust estimation.
340      */
341     @Override
342     public RobustEstimatorMethod getMethod() {
343         return RobustEstimatorMethod.LMEDS;
344     }
345 }