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.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 estimated 2D position of a radio source (e.g. Wi-Fi
33   * access point or bluetooth beacon), by discarding outliers using RANSAC
34   * algorithm.
35   *
36   * @param <S> a {@link RadioSource} type.
37   */
38  public class RANSACRobustRangingRadioSourceEstimator2D<S extends RadioSource> extends
39          RobustRangingRadioSourceEstimator2D<S> {
40  
41      /**
42       * Constant defining default threshold on received power (RSSI) expressed in
43       * dBm's.
44       */
45      public static final double DEFAULT_THRESHOLD = 0.1;
46  
47      /**
48       * Minimum value that can be set as threshold.
49       * Threshold must be strictly greater than 0.0.
50       */
51      public static final double MIN_THRESHOLD = 0.0;
52  
53      /**
54       * Indicates that by default inliers will only be computed but not kept.
55       */
56      public static final boolean DEFAULT_COMPUTE_AND_KEEP_INLIERS = false;
57  
58      /**
59       * Indicates that by default residuals will only be computed but not kept.
60       */
61      public static final boolean DEFAULT_COMPUTE_AND_KEEP_RESIDUALS = false;
62  
63      /**
64       * Threshold to determine whether samples are inliers or not when testing possible solutions.
65       * The threshold refers to the amount of error on received power (RSSI) expressed
66       * in dBm's between received value that should have been received on estimated
67       * iso-tropical model and actual measured value.
68       */
69      private double threshold = DEFAULT_THRESHOLD;
70  
71      /**
72       * Indicates whether inliers must be computed and kept.
73       */
74      private boolean computeAndKeepInliers = DEFAULT_COMPUTE_AND_KEEP_INLIERS;
75  
76      /**
77       * Indicates whether residuals must be computed and kept.
78       */
79      private boolean computeAndKeepResiduals = DEFAULT_COMPUTE_AND_KEEP_RESIDUALS;
80  
81      /**
82       * Constructor.
83       */
84      public RANSACRobustRangingRadioSourceEstimator2D() {
85          super();
86      }
87  
88      /**
89       * Constructor.
90       * Sets radio signal ranging readings belonging to the same radio source.
91       *
92       * @param readings radio signal ranging readings belonging to the same
93       *                 radio source.
94       * @throws IllegalArgumentException if readings are not valid.
95       */
96      public RANSACRobustRangingRadioSourceEstimator2D(final List<? extends RangingReadingLocated<S, Point2D>> readings) {
97          super(readings);
98      }
99  
100     /**
101      * Constructor.
102      *
103      * @param listener listener in charge of attending events raised by this instance.
104      */
105     public RANSACRobustRangingRadioSourceEstimator2D(
106             final RobustRangingRadioSourceEstimatorListener<S, Point2D> listener) {
107         super(listener);
108     }
109 
110     /**
111      * Constructor.
112      * Sets radio signal readings belonging to the same radio source.
113      *
114      * @param readings radio signal readings belonging to the same radio source.
115      * @param listener listener in charge of attending events raised by this instance.
116      * @throws IllegalArgumentException if readings are not valid.
117      */
118     public RANSACRobustRangingRadioSourceEstimator2D(
119             final List<? extends RangingReadingLocated<S, Point2D>> readings,
120             final RobustRangingRadioSourceEstimatorListener<S, Point2D> listener) {
121         super(readings, listener);
122     }
123 
124     /**
125      * Constructor.
126      *
127      * @param initialPosition initial position to start the estimation or radio
128      *                        source position.
129      */
130     public RANSACRobustRangingRadioSourceEstimator2D(final Point2D initialPosition) {
131         super(initialPosition);
132     }
133 
134     /**
135      * Constructor.
136      * Sets radio signal readings belonging to the same radio source.
137      *
138      * @param readings        radio signal readings belonging to the same radio source.
139      * @param initialPosition initial position to start the estimation of radio
140      *                        source position.
141      * @throws IllegalArgumentException if readings are not valid.
142      */
143     public RANSACRobustRangingRadioSourceEstimator2D(
144             final List<? extends RangingReadingLocated<S, Point2D>> readings, final Point2D initialPosition) {
145         super(readings, initialPosition);
146     }
147 
148     /**
149      * Constructor.
150      *
151      * @param initialPosition initial position to start the estimation of radio
152      *                        source position.
153      * @param listener        listener in charge of attending events raised by this instance.
154      */
155     public RANSACRobustRangingRadioSourceEstimator2D(
156             final Point2D initialPosition, final RobustRangingRadioSourceEstimatorListener<S, Point2D> listener) {
157         super(initialPosition, listener);
158     }
159 
160     /**
161      * Constructor.
162      * Sets radio signal ranging readings belonging to the same radio source.
163      *
164      * @param readings        radio signal ranging readings belonging to the same radio source.
165      * @param initialPosition initial position to start the estimation of radio source
166      *                        position.
167      * @param listener        listener in charge of attending events raised by this instance.
168      * @throws IllegalArgumentException if readings are not valid.
169      */
170     public RANSACRobustRangingRadioSourceEstimator2D(
171             final List<? extends RangingReadingLocated<S, Point2D>> readings, final Point2D initialPosition,
172             final RobustRangingRadioSourceEstimatorListener<S, Point2D> listener) {
173         super(readings, initialPosition, listener);
174     }
175 
176     /**
177      * Gets threshold to determine whether samples are inliers or not when testing possible solutions.
178      * The threshold refers to the amount of error on ranging distances.
179      *
180      * @return threshold to determine whether samples are inliers or not.
181      */
182     public double getThreshold() {
183         return threshold;
184     }
185 
186     /**
187      * Sets threshold to determine whether samples are inliers or not when testing possible solutions.
188      * The threshold refers to the amount of error on ranging distances.
189      *
190      * @param threshold threshold to determine whether samples are inliers or not.
191      * @throws IllegalArgumentException if provided value is equal or less than zero.
192      * @throws LockedException          if this estimator is locked.
193      */
194     public void setThreshold(final double threshold) throws LockedException {
195         if (isLocked()) {
196             throw new LockedException();
197         }
198         if (threshold <= MIN_THRESHOLD) {
199             throw new IllegalArgumentException();
200         }
201         this.threshold = threshold;
202     }
203 
204 
205     /**
206      * Indicates whether inliers must be computed and kept.
207      *
208      * @return true if inliers must be computed and kept, false if inliers
209      * only need to be computed but not kept.
210      */
211     public boolean isComputeAndKeepInliersEnabled() {
212         return computeAndKeepInliers;
213     }
214 
215     /**
216      * Specifies whether inliers must be computed and kept.
217      *
218      * @param computeAndKeepInliers true if inliers must be computed and kept,
219      *                              false if inliers only need to be computed but not kept.
220      * @throws LockedException if this solver is locked.
221      */
222     public void setComputeAndKeepInliersEnabled(final boolean computeAndKeepInliers) throws LockedException {
223         if (isLocked()) {
224             throw new LockedException();
225         }
226         this.computeAndKeepInliers = computeAndKeepInliers;
227     }
228 
229     /**
230      * Indicates whether residuals must be computed and kept.
231      *
232      * @return true if residuals must be computed and kept, false if residuals
233      * only need to be computed but not kept.
234      */
235     public boolean isComputeAndKeepResidualsEnabled() {
236         return computeAndKeepResiduals;
237     }
238 
239     /**
240      * Specifies whether residuals must be computed and kept.
241      *
242      * @param computeAndKeepResiduals true if residuals must be computed and kept,
243      *                                false if residuals only need to be computed but not kept.
244      * @throws LockedException if this solver is locked.
245      */
246     public void setComputeAndKeepResidualsEnabled(final boolean computeAndKeepResiduals) throws LockedException {
247         if (isLocked()) {
248             throw new LockedException();
249         }
250         this.computeAndKeepResiduals = computeAndKeepResiduals;
251     }
252 
253     /**
254      * Robustly estimates position for a radio source.
255      *
256      * @throws LockedException          if instance is busy during estimation.
257      * @throws NotReadyException        if estimator is not ready.
258      * @throws RobustEstimatorException if estimation fails for any reason
259      *                                  (i.e. numerical instability, no solution available, etc).
260      */
261     @SuppressWarnings("DuplicatedCode")
262     @Override
263     public void estimate() throws LockedException, NotReadyException, RobustEstimatorException {
264         if (isLocked()) {
265             throw new LockedException();
266         }
267         if (!isReady()) {
268             throw new NotReadyException();
269         }
270 
271         final var innerEstimator = new RANSACRobustEstimator<>(new RANSACRobustEstimatorListener<Solution<Point2D>>() {
272             @Override
273             public double getThreshold() {
274                 return threshold;
275             }
276 
277             @Override
278             public int getTotalSamples() {
279                 return readings.size();
280             }
281 
282             @Override
283             public int getSubsetSize() {
284                 return Math.max(preliminarySubsetSize, getMinReadings());
285             }
286 
287             @Override
288             public void estimatePreliminarSolutions(
289                     final int[] samplesIndices, final List<Solution<Point2D>> solutions) {
290                 solvePreliminarySolutions(samplesIndices, solutions);
291             }
292 
293             @Override
294             public double computeResidual(final Solution<Point2D> currentEstimation, final int i) {
295                 return residual(currentEstimation, i);
296             }
297 
298             @Override
299             public boolean isReady() {
300                 return RANSACRobustRangingRadioSourceEstimator2D.this.isReady();
301             }
302 
303             @Override
304             public void onEstimateStart(final RobustEstimator<Solution<Point2D>> estimator) {
305                 // no action needed
306             }
307 
308             @Override
309             public void onEstimateEnd(final RobustEstimator<Solution<Point2D>> estimator) {
310                 // no action needed
311             }
312 
313             @Override
314             public void onEstimateNextIteration(
315                     final RobustEstimator<Solution<Point2D>> estimator, final int iteration) {
316                 if (listener != null) {
317                     listener.onEstimateNextIteration(
318                             RANSACRobustRangingRadioSourceEstimator2D.this, iteration);
319                 }
320             }
321 
322             @Override
323             public void onEstimateProgressChange(
324                     final RobustEstimator<Solution<Point2D>> estimator, final float progress) {
325                 if (listener != null) {
326                     listener.onEstimateProgressChange(
327                             RANSACRobustRangingRadioSourceEstimator2D.this, progress);
328                 }
329             }
330         });
331 
332         try {
333             locked = true;
334 
335             if (listener != null) {
336                 listener.onEstimateStart(this);
337             }
338 
339             inliersData = null;
340             innerEstimator.setComputeAndKeepInliersEnabled(computeAndKeepInliers || refineResult);
341             innerEstimator.setComputeAndKeepResidualsEnabled(computeAndKeepResiduals || refineResult);
342             innerEstimator.setConfidence(confidence);
343             innerEstimator.setMaxIterations(maxIterations);
344             innerEstimator.setProgressDelta(progressDelta);
345             final var result = innerEstimator.estimate();
346             inliersData = innerEstimator.getInliersData();
347             attemptRefine(result);
348 
349             if (listener != null) {
350                 listener.onEstimateEnd(this);
351             }
352 
353         } catch (final com.irurueta.numerical.LockedException e) {
354             throw new LockedException(e);
355         } catch (final com.irurueta.numerical.NotReadyException e) {
356             throw new NotReadyException(e);
357         } finally {
358             locked = false;
359         }
360     }
361 
362     /**
363      * Returns method being used for robust estimation.
364      *
365      * @return method being used for robust estimation.
366      */
367     @Override
368     public RobustEstimatorMethod getMethod() {
369         return RobustEstimatorMethod.RANSAC;
370     }
371 }