0.5.1
Loading...
Searching...
No Matches
PolynomialCycleSlipDetector.hpp
Go to the documentation of this file.
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
25
28
29namespace NAV
30{
31
32/// GnssAnalyzer forward declaration
33class GnssAnalyzer;
34/// CycleSlipDetector forward declaration
36
37/// Cycle-slip detection result type
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
47template<typename Key>
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 explicit PolynomialCycleSlipDetector(size_t windowSize, size_t polyDegree, bool enabled = true)
56 : _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 [[nodiscard]] PolynomialCycleSlipDetectorResult checkForCycleSlip(const Key& key, InsTime insTime, double measurementDifference, double threshold)
65 {
67 if (!_detectors.contains(key))
68 {
69 addMeasurement(key, insTime, measurementDifference);
71 }
72
73 const auto& detector = _detectors.at(key);
74 if (!detector.polyReg.windowSizeReached())
75 {
76 addMeasurement(key, insTime, measurementDifference);
78 }
79
80 auto polynomial = detector.polyReg.calcPolynomial();
81 if (!polynomial)
82 {
83 addMeasurement(key, insTime, measurementDifference);
85 }
86 auto predictedValue = polynomial->f(calcRelativeTime(insTime, detector));
87
88 if (std::abs(measurementDifference - predictedValue) > threshold)
89 {
90 if (_resetAfterCycleSlip) { reset(key); }
91 addMeasurement(key, insTime, measurementDifference);
93 }
94 addMeasurement(key, insTime, measurementDifference);
96 }
97
98 /// Empties the collected polynomials
99 void clear()
100 {
101 _detectors.clear();
102 }
103
104 /// @brief Reset the polynomial for the given combination
105 /// @param[in] key Key of the detector
106 void reset(const Key& key)
107 {
108 if (_detectors.contains(key))
109 {
110 _detectors.erase(key);
111 }
112 }
113
114 /// @brief Is the cycle-slip detector enabled?
115 [[nodiscard]] bool isEnabled() const
116 {
117 return _enabled;
118 }
119 /// @brief Sets the enabled state
120 /// @param[in] enabled Whether to enabled or not
121 void setEnabled(bool enabled)
122 {
123 _enabled = enabled;
124 }
125
126 /// @brief Whether to discard all data after a cycle-slip
127 [[nodiscard]] bool resetAfterCycleSlip() const
128 {
130 }
131 /// @brief Sets whether to discard all data after a cycle-slip
132 /// @param[in] reset Whether to reset or not
137
138 /// @brief Get the window size for the polynomial fit
139 [[nodiscard]] size_t getWindowSize() const { return _windowSize; }
140 /// @brief Sets the amount of points used for the fit (sliding window)
141 /// @param[in] windowSize Amount of points to use for the fit
142 void setWindowSize(size_t windowSize)
143 {
144 _windowSize = windowSize;
145 for (auto& detector : _detectors)
146 {
147 detector.second.polyReg.setWindowSize(windowSize);
148 }
149 }
150
151 /// @brief Get the degree of the polynomial which is used for fitting
152 [[nodiscard]] size_t getPolynomialDegree() const { return _polyDegree; }
153 /// @brief Sets the degree of the polynomial which is used for fitting
154 /// @param[in] polyDegree Polynomial degree to fit
155 void setPolynomialDegree(size_t polyDegree)
156 {
157 _polyDegree = polyDegree;
158 for (auto& detector : _detectors)
159 {
160 detector.second.polyReg.setPolynomialDegree(polyDegree);
161 }
162 }
163
164 /// Strategies for fitting
166
167 /// @brief Get the strategy used for fitting
168 [[nodiscard]] Strategy getFitStrategy() const { return _strategy; }
169 /// @brief Sets the strategy used for fitting
170 /// @param[in] strategy Strategy for fitting
172 {
173 _strategy = strategy;
174 for (auto& detector : _detectors)
175 {
176 detector.second.polyReg.setStrategy(strategy);
177 }
178 }
179
180 /// @brief Get the amount of data collected
181 /// @param[in] key Key of the detector
182 [[nodiscard]] std::optional<size_t> getDataSize(const Key& key) const
183 {
184 if (!_detectors.contains(key)) { return {}; }
185
186 return _detectors.at(key).polyReg.data().size();
187 }
188
189 private:
190 /// @brief Signal Detector struct
192 {
193 /// @brief Constructor
194 /// @param[in] startTime Time when the first message for this detector was received
195 /// @param[in] windowSize Window size for the sliding window
196 /// @param[in] polyDegree Polynomial degree to fit
197 /// @param[in] strategy Strategy for fitting
198 SignalDetector(InsTime startTime, size_t windowSize, size_t polyDegree, Strategy strategy)
199 : startTime(startTime), polyReg(polyDegree, windowSize, strategy) {}
200
201 InsTime startTime; ///< Time when the first message for this detector was received
202 PolynomialRegressor<double> polyReg; ///< Polynomial Regressor
203 };
204
205 bool _enabled = true; ///< Whether the cycle-slip detector is enabled
206 bool _resetAfterCycleSlip = true; ///< Whether to discard all data after a cycle-slip
207 size_t _windowSize; ///< Window size for the sliding window
208 size_t _polyDegree = 2; ///< Polynomial degree to fit
209 Strategy _strategy = Strategy::HouseholderQR; ///< Strategy used for fitting
210 unordered_map<Key, SignalDetector> _detectors; ///< Detectors, one for each key
211
212 /// @brief Calculate the relative time to the start time of the detector
213 /// @param[in] insTime Time of the measurement
214 /// @param[in] detector Detector to use
215 [[nodiscard]] static double calcRelativeTime(const InsTime& insTime, const SignalDetector& detector)
216 {
217 return static_cast<double>((insTime - detector.startTime).count());
218 }
219 /// @brief Calculate the relative time to the start time of the detector
220 /// @param[in] key Key of the detector
221 /// @param[in] insTime Time of the measurement
222 [[nodiscard]] std::optional<double> calcRelativeTime(const Key& key, const InsTime& insTime) const
223 {
224 if (!_detectors.contains(key)) { return {}; }
225
226 return calcRelativeTime(insTime, _detectors.at(key));
227 }
228
229 /// @brief Predicts a value from the collected data and polynomial fit
230 /// @param[in] key Key of the detector
231 /// @param[in] insTime Time of the measurement
232 [[nodiscard]] std::optional<double> predictValue(const Key& key, const InsTime& insTime) const
233 {
234 if (!_detectors.contains(key)) { return {}; }
235
236 const auto& detector = _detectors.at(key);
237
238 auto polynomial = detector.polyReg.calcPolynomial();
239 if (!polynomial) { return {}; }
240 return polynomial->f(calcRelativeTime(insTime, detector));
241 }
242
243 /// @brief Calculates the polynomial from the collected data
244 /// @param[in] key Key of the detector
245 [[nodiscard]] std::optional<Polynomial<double>> calcPolynomial(const Key& key) const
246 {
247 if (!_detectors.contains(key)) { return {}; }
248
249 return _detectors.at(key).polyReg.calcPolynomial();
250 }
251
252 /// @brief Add a measurement to the polynomial fit
253 /// @param[in] key Key of the detector
254 /// @param[in] insTime Time of the measurement
255 /// @param[in] measurementDifference Measurement difference
256 void addMeasurement(const Key& key, InsTime insTime, double measurementDifference)
257 {
258 if (!_enabled) { return; }
259 auto& detector = _detectors.insert({ key, SignalDetector(insTime, _windowSize, _polyDegree, _strategy) }).first->second;
260
261 detector.polyReg.push_back(calcRelativeTime(insTime, detector), measurementDifference);
262 }
263
264 friend class GnssAnalyzer;
265 friend class CycleSlipDetector;
266};
267
268/// @brief Shows a GUI for advanced configuration of the PolynomialCycleSlipDetector
269/// @param[in] label Label to show beside the combo box. This has to be a unique id for ImGui.
270/// @param[in] polynomialCycleSlipDetector Reference to the cycle-slip detector to configure
271/// @param[in] width Width of the widget
272template<typename Key>
273bool PolynomialCycleSlipDetectorGui(const char* label, PolynomialCycleSlipDetector<Key>& polynomialCycleSlipDetector, float width = 0.0F)
274{
275 bool changed = false;
276
277 bool enabled = polynomialCycleSlipDetector.isEnabled();
278 if (ImGui::Checkbox(fmt::format("Enabled##{}", label).c_str(), &enabled))
279 {
280 changed = true;
281 polynomialCycleSlipDetector.setEnabled(enabled);
282 }
283
284 if (!enabled) { ImGui::BeginDisabled(); }
285
286 ImGui::SetNextItemWidth(width);
287 if (int windowSize = static_cast<int>(polynomialCycleSlipDetector.getWindowSize());
288 ImGui::InputIntL(fmt::format("Window size##{}", label).c_str(), &windowSize,
289 std::max(1, static_cast<int>(polynomialCycleSlipDetector.getPolynomialDegree()) + 1)))
290 {
291 changed = true;
292 polynomialCycleSlipDetector.setWindowSize(static_cast<size_t>(windowSize));
293 }
294
295 ImGui::SetNextItemWidth(width);
296 if (int polyDegree = static_cast<int>(polynomialCycleSlipDetector.getPolynomialDegree());
297 ImGui::InputIntL(fmt::format("Polynomial Degree##{}", label).c_str(), &polyDegree,
298 0, std::min(static_cast<int>(polynomialCycleSlipDetector.getWindowSize()) - 1, std::numeric_limits<int>::max())))
299 {
300 changed = true;
301 polynomialCycleSlipDetector.setPolynomialDegree(static_cast<size_t>(polyDegree));
302 }
303
304 ImGui::SetNextItemWidth(width);
305 if (auto strategy = polynomialCycleSlipDetector.getFitStrategy();
306 gui::widgets::EnumCombo(fmt::format("Strategy##{}", label).c_str(), strategy))
307 {
308 changed = true;
309 polynomialCycleSlipDetector.setFitStrategy(strategy);
310 }
311 if (auto resetAfterCycleSlip = polynomialCycleSlipDetector.resetAfterCycleSlip();
312 ImGui::Checkbox(fmt::format("Reset after cycle-slip##{}", label).c_str(), &resetAfterCycleSlip))
313 {
314 changed = true;
315 polynomialCycleSlipDetector.setResetAfterCycleSlip(resetAfterCycleSlip);
316 }
317
318 if (!enabled) { ImGui::EndDisabled(); }
319
320 return changed;
321}
322
323/// @brief Write info to a json object
324/// @param[out] j Json output
325/// @param[in] data Object to read info from
326template<typename Key>
328{
329 j = json{
330 { "enabled", data.isEnabled() },
331 { "windowSize", data.getWindowSize() },
332 { "polynomialDegree", data.getPolynomialDegree() },
333 { "strategy", data.getFitStrategy() },
334 { "resetAfterCycleSlip", data.resetAfterCycleSlip() },
335 };
336}
337/// @brief Read info from a json object
338/// @param[in] j Json variable to read info from
339/// @param[out] data Output object
340template<typename Key>
342{
343 if (j.contains("enabled"))
344 {
345 auto enabled = j.at("enabled").get<bool>();
346 data.setEnabled(enabled);
347 }
348 if (j.contains("windowSize"))
349 {
350 auto windowSize = j.at("windowSize").get<size_t>();
351 data.setWindowSize(windowSize);
352 }
353 if (j.contains("polynomialDegree"))
354 {
355 auto polynomialDegree = j.at("polynomialDegree").get<size_t>();
356 data.setPolynomialDegree(polynomialDegree);
357 }
358 if (j.contains("strategy"))
359 {
360 auto strategy = j.at("strategy").get<size_t>();
361 data.setFitStrategy(static_cast<typename PolynomialCycleSlipDetector<Key>::Strategy>(strategy));
362 }
363 if (j.contains("resetAfterCycleSlip"))
364 {
365 auto reset = j.at("resetAfterCycleSlip").get<bool>();
366 data.setResetAfterCycleSlip(reset);
367 }
368}
369
370} // namespace NAV
Combo representing an enumeration.
nlohmann::json json
json namespace
The class is responsible for all time-related tasks.
Utility functions for std::pair.
Polynomial curve fitting.
Structs identifying a unique satellite.
Unordered map wrapper.
std::unordered_map< Key, T > unordered_map
Unordered map type.
Cycle-slip detector.
Allows creation of GNSS measurement combinations.
The class is responsible for all time-related tasks.
Definition InsTime.hpp:710
size_t getPolynomialDegree() const
Get the degree of the polynomial which is used for fitting.
void setFitStrategy(Strategy strategy)
Sets the strategy used for fitting.
void setWindowSize(size_t windowSize)
Sets the amount of points used for the fit (sliding window)
void addMeasurement(const Key &key, InsTime insTime, double measurementDifference)
Add a measurement to the polynomial fit.
std::optional< double > predictValue(const Key &key, const InsTime &insTime) const
Predicts a value from the collected data and polynomial fit.
void setEnabled(bool enabled)
Sets the enabled state.
bool _resetAfterCycleSlip
Whether to discard all data after a cycle-slip.
PolynomialRegressor<>::Strategy Strategy
Strategies for fitting.
bool isEnabled() const
Is the cycle-slip detector enabled?
std::optional< size_t > getDataSize(const Key &key) const
Get the amount of data collected.
std::optional< double > calcRelativeTime(const Key &key, const InsTime &insTime) const
Calculate the relative time to the start time of the detector.
void setPolynomialDegree(size_t polyDegree)
Sets the degree of the polynomial which is used for fitting.
size_t getWindowSize() const
Get the window size for the polynomial fit.
void setResetAfterCycleSlip(bool reset)
Sets whether to discard all data after a cycle-slip.
static double calcRelativeTime(const InsTime &insTime, const SignalDetector &detector)
Calculate the relative time to the start time of the detector.
Strategy _strategy
Strategy used for fitting.
std::optional< Polynomial< double > > calcPolynomial(const Key &key) const
Calculates the polynomial from the collected data.
bool resetAfterCycleSlip() const
Whether to discard all data after a cycle-slip.
PolynomialCycleSlipDetectorResult checkForCycleSlip(const Key &key, InsTime insTime, double measurementDifference, double threshold)
Checks for a cycle slip.
size_t _windowSize
Window size for the sliding window.
bool _enabled
Whether the cycle-slip detector is enabled.
Strategy getFitStrategy() const
Get the strategy used for fitting.
size_t _polyDegree
Polynomial degree to fit.
void reset(const Key &key)
Reset the polynomial for the given combination.
unordered_map< Key, SignalDetector > _detectors
Detectors, one for each key.
PolynomialCycleSlipDetector(size_t windowSize, size_t polyDegree, bool enabled=true)
Constructor.
void clear()
Empties the collected polynomials.
Polynomial Curve Fitting.
Strategy
Possible Fit strategies.
ImGui extensions.
bool InputIntL(const char *label, int *v, int v_min, int v_max, int step, int step_fast, ImGuiInputTextFlags flags)
Shows a value limited InputText GUI element for 'int'.
Definition imgui_ex.cpp:242
bool EnumCombo(const char *label, T &enumeration, size_t startIdx=0)
Combo representing an enumeration.
Definition EnumCombo.hpp:30
void to_json(json &j, const Node &node)
Converts the provided node into a json object.
Definition Node.cpp:1060
PolynomialCycleSlipDetectorResult
Cycle-slip detection result type.
@ LessDataThanWindowSize
Less data than the specified window size (cannot predict cycle-slip yet)
@ Disabled
The cycle-slip detector is disabled.
void from_json(const json &j, Node &node)
Converts the provided json object into a node object.
Definition Node.cpp:1077
bool PolynomialCycleSlipDetectorGui(const char *label, PolynomialCycleSlipDetector< Key > &polynomialCycleSlipDetector, float width=0.0F)
Shows a GUI for advanced configuration of the PolynomialCycleSlipDetector.
SignalDetector(InsTime startTime, size_t windowSize, size_t polyDegree, Strategy strategy)
Constructor.
PolynomialRegressor< double > polyReg
Polynomial Regressor.
InsTime startTime
Time when the first message for this detector was received.