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