INSTINCT Code Coverage Report


Directory: src/
File: Navigation/GNSS/Ambiguity/CycleSlipDetector/PolynomialCycleSlipDetector.hpp
Date: 2025-02-07 16:54:41
Exec Total Coverage
Lines: 32 117 27.4%
Functions: 7 61 11.5%
Branches: 25 131 19.1%

Line Branch Exec Source
1 // This file is part of INSTINCT, the INS Toolkit for Integrated
2 // Navigation Concepts and Training by the Institute of Navigation of
3 // the University of Stuttgart, Germany.
4 //
5 // This Source Code Form is subject to the terms of the Mozilla Public
6 // License, v. 2.0. If a copy of the MPL was not distributed with this
7 // file, You can obtain one at https://mozilla.org/MPL/2.0/.
8
9 /// @file PolynomialCycleSlipDetector.hpp
10 /// @brief Polynomial Cycle-slip detection algorithm
11 /// @author T. Topp (topp@ins.uni-stuttgart.de)
12 /// @date 2023-10-30
13
14 #pragma once
15
16 #include <vector>
17 #include <utility>
18 #include <optional>
19
20 #include "Navigation/GNSS/Core/SatelliteIdentifier.hpp"
21 #include "Navigation/Math/PolynomialRegressor.hpp"
22 #include "Navigation/Time/InsTime.hpp"
23 #include "util/Container/Unordered_map.hpp"
24 #include "util/Container/Pair.hpp"
25
26 #include "internal/gui/widgets/imgui_ex.hpp"
27 #include "internal/gui/widgets/EnumCombo.hpp"
28
29 namespace NAV
30 {
31
32 /// GnssAnalyzer forward declaration
33 class GnssAnalyzer;
34 /// CycleSlipDetector forward declaration
35 class CycleSlipDetector;
36
37 /// Cycle-slip detection result type
38 enum class PolynomialCycleSlipDetectorResult : uint8_t
39 {
40 Disabled, ///< The cycle-slip detector is disabled
41 LessDataThanWindowSize, ///< Less data than the specified window size (cannot predict cycle-slip yet)
42 NoCycleSlip, ///< No cycle-slip found
43 CycleSlip, ///< Cycle-slip found
44 };
45
46 /// @brief Cycle-slip detection
47 template<typename Key>
48 class PolynomialCycleSlipDetector
49 {
50 public:
51 /// @brief Constructor
52 /// @param[in] windowSize Amount of points to use for the fit (sliding window)
53 /// @param[in] polyDegree Polynomial degree to fit
54 /// @param[in] enabled Whether the detector is enabled
55 113 explicit PolynomialCycleSlipDetector(size_t windowSize, size_t polyDegree, bool enabled = true)
56 113 : _enabled(enabled), _windowSize(windowSize), _polyDegree(polyDegree) {}
57
58 /// @brief Checks for a cycle slip
59 /// @param[in] key Key of the detector
60 /// @param[in] insTime Time of the measurement
61 /// @param[in] measurementDifference Measurement difference
62 /// @param[in] threshold Threshold to categorize a measurement as cycle slip
63 /// @return Cycle-slip result
64 7 [[nodiscard]] PolynomialCycleSlipDetectorResult checkForCycleSlip(const Key& key, InsTime insTime, double measurementDifference, double threshold)
65 {
66
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 7 times.
7 if (!_enabled) { return PolynomialCycleSlipDetectorResult::Disabled; }
67
3/4
✓ Branch 1 taken 7 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 1 times.
✓ Branch 4 taken 6 times.
7 if (!_detectors.contains(key))
68 {
69
1/2
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
1 addMeasurement(key, insTime, measurementDifference);
70 1 return PolynomialCycleSlipDetectorResult::LessDataThanWindowSize;
71 }
72
73
1/2
✓ Branch 1 taken 6 times.
✗ Branch 2 not taken.
6 const auto& detector = _detectors.at(key);
74
3/4
✓ Branch 1 taken 6 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 4 times.
✓ Branch 4 taken 2 times.
6 if (!detector.polyReg.windowSizeReached())
75 {
76
1/2
✓ Branch 1 taken 4 times.
✗ Branch 2 not taken.
4 addMeasurement(key, insTime, measurementDifference);
77 4 return PolynomialCycleSlipDetectorResult::LessDataThanWindowSize;
78 }
79
80
1/2
✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
2 auto polynomial = detector.polyReg.calcPolynomial();
81
2/4
✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 2 times.
✗ Branch 5 not taken.
2 auto predictedValue = polynomial.f(calcRelativeTime(insTime, detector));
82
83
2/2
✓ Branch 1 taken 1 times.
✓ Branch 2 taken 1 times.
2 if (std::abs(measurementDifference - predictedValue) > threshold)
84 {
85
1/2
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
1 reset(key);
86
1/2
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
1 addMeasurement(key, insTime, measurementDifference);
87 1 return PolynomialCycleSlipDetectorResult::CycleSlip;
88 }
89
1/2
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
1 addMeasurement(key, insTime, measurementDifference);
90 1 return PolynomialCycleSlipDetectorResult::NoCycleSlip;
91 2 }
92
93 /// Empties the collected polynomials
94 void clear()
95 {
96 _detectors.clear();
97 }
98
99 /// @brief Reset the polynomial for the given combination
100 /// @param[in] key Key of the detector
101 1 void reset(const Key& key)
102 {
103
1/2
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
1 if (_detectors.contains(key))
104 {
105 1 _detectors.erase(key);
106 }
107 1 }
108
109 /// @brief Is the cycle-slip detector enabled?
110 [[nodiscard]] bool isEnabled() const
111 {
112 return _enabled;
113 }
114 /// @brief Sets the enabled state
115 /// @param[in] enabled Whether to enabled or not
116 void setEnabled(bool enabled)
117 {
118 _enabled = enabled;
119 }
120
121 /// @brief Get the window size for the polynomial fit
122 [[nodiscard]] size_t getWindowSize() const { return _windowSize; }
123 /// @brief Sets the amount of points used for the fit (sliding window)
124 /// @param[in] windowSize Amount of points to use for the fit
125 void setWindowSize(size_t windowSize)
126 {
127 _windowSize = windowSize;
128 for (auto& detector : _detectors)
129 {
130 detector.second.polyReg.setWindowSize(windowSize);
131 }
132 }
133
134 /// @brief Get the degree of the polynomial which is used for fitting
135 [[nodiscard]] size_t getPolynomialDegree() const { return _polyDegree; }
136 /// @brief Sets the degree of the polynomial which is used for fitting
137 /// @param[in] polyDegree Polynomial degree to fit
138 void setPolynomialDegree(size_t polyDegree)
139 {
140 _polyDegree = polyDegree;
141 for (auto& detector : _detectors)
142 {
143 detector.second.polyReg.setPolynomialDegree(polyDegree);
144 }
145 }
146
147 /// Strategies for fitting
148 using Strategy = PolynomialRegressor<>::Strategy;
149
150 /// @brief Get the strategy used for fitting
151 [[nodiscard]] Strategy getFitStrategy() const { return _strategy; }
152 /// @brief Sets the strategy used for fitting
153 /// @param[in] strategy Strategy for fitting
154 void setFitStrategy(Strategy strategy)
155 {
156 _strategy = strategy;
157 for (auto& detector : _detectors)
158 {
159 detector.second.polyReg.setStrategy(strategy);
160 }
161 }
162
163 private:
164 /// @brief Signal Detector struct
165 struct SignalDetector
166 {
167 /// @brief Constructor
168 /// @param[in] startTime Time when the first message for this detector was received
169 /// @param[in] windowSize Window size for the sliding window
170 /// @param[in] polyDegree Polynomial degree to fit
171 /// @param[in] strategy Strategy for fitting
172 7 SignalDetector(InsTime startTime, size_t windowSize, size_t polyDegree, Strategy strategy)
173 7 : startTime(startTime), polyReg(polyDegree, windowSize, strategy) {}
174
175 InsTime startTime; ///< Time when the first message for this detector was received
176 PolynomialRegressor<double> polyReg; ///< Polynomial Regressor
177 };
178
179 bool _enabled = true; ///< Whether the cycle-slip detector is enabled
180 size_t _windowSize; ///< Window size for the sliding window
181 size_t _polyDegree = 2; ///< Polynomial degree to fit
182 Strategy _strategy = Strategy::HouseholderQR; ///< Strategy used for fitting
183 unordered_map<Key, SignalDetector> _detectors; ///< Detectors, one for each key
184
185 /// @brief Calculate the relative time to the start time of the detector
186 /// @param[in] insTime Time of the measurement
187 /// @param[in] detector Detector to use
188 9 [[nodiscard]] static double calcRelativeTime(const InsTime& insTime, const SignalDetector& detector)
189 {
190
1/2
✓ Branch 1 taken 9 times.
✗ Branch 2 not taken.
9 return static_cast<double>((insTime - detector.startTime).count());
191 }
192 /// @brief Calculate the relative time to the start time of the detector
193 /// @param[in] key Key of the detector
194 /// @param[in] insTime Time of the measurement
195 [[nodiscard]] std::optional<double> calcRelativeTime(const Key& key, const InsTime& insTime) const
196 {
197 if (!_detectors.contains(key)) { return {}; }
198
199 return calcRelativeTime(insTime, _detectors.at(key));
200 }
201
202 /// @brief Predicts a value from the collected data and polynomial fit
203 /// @param[in] key Key of the detector
204 /// @param[in] insTime Time of the measurement
205 [[nodiscard]] std::optional<double> predictValue(const Key& key, const InsTime& insTime) const
206 {
207 if (!_detectors.contains(key)) { return {}; }
208
209 const auto& detector = _detectors.at(key);
210
211 auto polynomial = detector.polyReg.calcPolynomial();
212 return polynomial.f(calcRelativeTime(insTime, detector));
213 }
214
215 /// @brief Calculates the polynomial from the collected data
216 /// @param[in] key Key of the detector
217 [[nodiscard]] std::optional<Polynomial<double>> calcPolynomial(const Key& key) const
218 {
219 if (!_detectors.contains(key)) { return {}; }
220
221 return _detectors.at(key).polyReg.calcPolynomial();
222 }
223
224 /// @brief Add a measurement to the polynomial fit
225 /// @param[in] key Key of the detector
226 /// @param[in] insTime Time of the measurement
227 /// @param[in] measurementDifference Measurement difference
228 7 void addMeasurement(const Key& key, InsTime insTime, double measurementDifference)
229 {
230
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 7 times.
7 if (!_enabled) { return; }
231
2/7
✓ Branch 1 taken 7 times.
✗ Branch 2 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 7 times.
✗ Branch 6 not taken.
✗ Branch 7 not taken.
✗ Branch 8 not taken.
7 auto& detector = _detectors.insert({ key, SignalDetector(insTime, _windowSize, _polyDegree, _strategy) }).first->second;
232
233
2/4
✓ Branch 1 taken 7 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 7 times.
✗ Branch 5 not taken.
7 detector.polyReg.push_back(calcRelativeTime(insTime, detector), measurementDifference);
234 }
235
236 friend class GnssAnalyzer;
237 friend class CycleSlipDetector;
238 };
239
240 /// @brief Shows a GUI for advanced configuration of the PolynomialCycleSlipDetector
241 /// @param[in] label Label to show beside the combo box. This has to be a unique id for ImGui.
242 /// @param[in] polynomialCycleSlipDetector Reference to the cycle-slip detector to configure
243 /// @param[in] width Width of the widget
244 template<typename Key>
245 bool PolynomialCycleSlipDetectorGui(const char* label, PolynomialCycleSlipDetector<Key>& polynomialCycleSlipDetector, float width = 0.0F)
246 {
247 bool changed = false;
248
249 bool enabled = polynomialCycleSlipDetector.isEnabled();
250 if (ImGui::Checkbox(fmt::format("Enabled##{}", label).c_str(), &enabled))
251 {
252 changed = true;
253 polynomialCycleSlipDetector.setEnabled(enabled);
254 }
255
256 if (!enabled) { ImGui::BeginDisabled(); }
257
258 ImGui::SetNextItemWidth(width);
259 if (int windowSize = static_cast<int>(polynomialCycleSlipDetector.getWindowSize());
260 ImGui::InputIntL(fmt::format("Window size##{}", label).c_str(), &windowSize,
261 std::max(1, static_cast<int>(polynomialCycleSlipDetector.getPolynomialDegree()) + 1)))
262 {
263 changed = true;
264 polynomialCycleSlipDetector.setWindowSize(static_cast<size_t>(windowSize));
265 }
266
267 ImGui::SetNextItemWidth(width);
268 if (int polyDegree = static_cast<int>(polynomialCycleSlipDetector.getPolynomialDegree());
269 ImGui::InputIntL(fmt::format("Polynomial Degree##{}", label).c_str(), &polyDegree,
270 0, std::min(static_cast<int>(polynomialCycleSlipDetector.getWindowSize()) - 1, std::numeric_limits<int>::max())))
271 {
272 changed = true;
273 polynomialCycleSlipDetector.setPolynomialDegree(static_cast<size_t>(polyDegree));
274 }
275
276 ImGui::SetNextItemWidth(width);
277 if (auto strategy = polynomialCycleSlipDetector.getFitStrategy();
278 gui::widgets::EnumCombo(fmt::format("Strategy##{}", label).c_str(), strategy))
279 {
280 changed = true;
281 polynomialCycleSlipDetector.setFitStrategy(strategy);
282 }
283
284 if (!enabled) { ImGui::EndDisabled(); }
285
286 return changed;
287 }
288
289 /// @brief Write info to a json object
290 /// @param[out] j Json output
291 /// @param[in] data Object to read info from
292 template<typename Key>
293 void to_json(json& j, const PolynomialCycleSlipDetector<Key>& data)
294 {
295 j = json{
296 { "enabled", data.isEnabled() },
297 { "windowSize", data.getWindowSize() },
298 { "polynomialDegree", data.getPolynomialDegree() },
299 { "strategy", data.getFitStrategy() },
300 };
301 }
302 /// @brief Read info from a json object
303 /// @param[in] j Json variable to read info from
304 /// @param[out] data Output object
305 template<typename Key>
306 void from_json(const json& j, PolynomialCycleSlipDetector<Key>& data)
307 {
308 if (j.contains("enabled"))
309 {
310 auto enabled = j.at("enabled").get<bool>();
311 data.setEnabled(enabled);
312 }
313 if (j.contains("windowSize"))
314 {
315 auto windowSize = j.at("windowSize").get<size_t>();
316 data.setWindowSize(windowSize);
317 }
318 if (j.contains("polynomialDegree"))
319 {
320 auto polynomialDegree = j.at("polynomialDegree").get<size_t>();
321 data.setPolynomialDegree(polynomialDegree);
322 }
323 if (j.contains("strategy"))
324 {
325 auto strategy = j.at("strategy").get<size_t>();
326 data.setFitStrategy(static_cast<typename PolynomialCycleSlipDetector<Key>::Strategy>(strategy));
327 }
328 }
329
330 } // namespace NAV
331