0.5.1
Loading...
Searching...
No Matches
AllanDeviation.cpp
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#include "AllanDeviation.hpp"
10#include <algorithm>
11#include <cstddef>
12#include <cstdint>
13#include <imgui.h>
14#include <mutex>
15
16#include "util/Logger.hpp"
17
18#include <Eigen/src/Core/util/Meta.h>
19#include <fmt/core.h>
21
24#include "implot.h"
25
27
28#include <chrono>
29
30#include <unsupported/Eigen/MatrixFunctions>
31
43
48
50{
51 return "AllanDeviation";
52}
53
54std::string NAV::AllanDeviation::type() const
55{
56 return typeStatic();
57}
58
60{
61 return "Data Processor";
62}
63
65{
66 if (ImGui::Checkbox("Compute Allan Deviation last", &_updateLast)) { flow::ApplyChanges(); }
67 if (ImGui::Checkbox("Display Confidence Intervals", &_displayConfidence)) { flow::ApplyChanges(); }
69 {
70 ImGui::SameLine();
71 ImGui::SetNextItemWidth(200.0F);
72 if (ImGui::SliderFloat("Confidence Alpha Channel", &_confidenceFillAlpha, 0.0F, 1.0F, "%.2f"))
73 {
75 }
76 }
77
78 const std::array<std::string, 3> legendEntries{ "x", "y", "z" };
79 constexpr std::array<double, 5> slopeTicks = { -2, -1, 0, 1, 2 };
80
81 if (ImGui::BeginTabBar("AllanDeviationTabBar", ImGuiTabBarFlags_None))
82 {
83 for (size_t sensorIter = 0; sensorIter < SensorType_COUNT; sensorIter++)
84 {
85 auto sensorType = static_cast<SensorType>(sensorIter);
86 const char* sensorName = to_string(sensorType);
87 auto& sensor = _sensors.at(sensorIter);
88 std::scoped_lock<std::mutex> lg(sensor.mutex);
89
90 if (ImGui::BeginTabItem(fmt::format("{}##TabItem {}", sensorName, size_t(id)).c_str()))
91 {
92 if (ImPlot::BeginPlot(fmt::format("Allan Deviation of {}##Plot {}", sensorName, size_t(id)).c_str()))
93 {
94 ImPlot::SetupLegend(ImPlotLocation_SouthWest, ImPlotLegendFlags_None);
95 ImPlot::SetupAxes("τ [s]", fmt::format("σ(τ) [{}]", unitString(sensorType)).c_str(),
96 ImPlotAxisFlags_AutoFit, ImPlotAxisFlags_AutoFit);
97 ImPlot::SetupAxisScale(ImAxis_X1, ImPlotScale_Log10);
98 ImPlot::SetupAxisScale(ImAxis_Y1, ImPlotScale_Log10);
99 ImPlot::PushStyleVar(ImPlotStyleVar_FillAlpha, _confidenceFillAlpha);
100 for (size_t d = 0; d < 3; d++)
101 {
102 if (_displayConfidence & !_averagingTimes.empty())
103 {
104 ImPlot::PlotShaded(legendEntries.at(d).data(),
105 _averagingTimes.data(),
106 sensor.allanDeviationConfidenceIntervals.at(d).at(0).data(),
107 sensor.allanDeviationConfidenceIntervals.at(d).at(1).data(),
108 static_cast<int>(_averagingTimes.size()));
109 }
110 ImPlot::PlotLine(legendEntries.at(d).data(),
111 _averagingTimes.data(),
112 sensor.allanDeviation.at(d).data(),
113 static_cast<int>(_averagingTimes.size()));
114 }
115 ImPlot::EndPlot();
116 }
117 if (ImGui::TreeNode("Slopes"))
118 {
119 if (ImPlot::BeginPlot("Slopes of Allan Variance"))
120 {
121 ImPlot::SetupLegend(ImPlotLocation_SouthWest, ImPlotLegendFlags_None);
122 ImPlot::SetupAxis(ImAxis_X1, "τ [s]", ImPlotAxisFlags_AutoFit);
123 ImPlot::SetupAxis(ImAxis_Y1, "µ [ ]", ImPlotAxisFlags_Lock);
124 ImPlot::SetupAxisScale(ImAxis_X1, ImPlotScale_Log10);
125 ImPlot::SetupAxisLimits(ImAxis_Y1, -2.5, 2.5);
126 ImPlot::SetupAxisTicks(ImAxis_Y1, slopeTicks.data(), 5, nullptr, false);
127 for (size_t d = 0; d < 3; d++)
128 {
129 ImPlot::PlotLine(legendEntries.at(d).data(),
130 _averagingTimes.data(),
131 sensor.slope.at(d).data(),
132 static_cast<int>(_averagingTimes.size()));
133 }
134 ImPlot::EndPlot();
135 }
136
137 ImGui::TreePop();
138 }
139 ImGui::EndTabItem();
140 }
141 }
142 ImGui::EndTabBar();
143 }
144}
145
146[[nodiscard]] json NAV::AllanDeviation::save() const
147{
148 LOG_TRACE("{}: called", nameId());
149
150 return {
151 { "displayConfidence", _displayConfidence },
152 { "confidenceFillAlpha", _confidenceFillAlpha },
153 { "updateLast", _updateLast },
154 };
155}
156
158{
159 LOG_TRACE("{}: called", nameId());
160
161 if (j.contains("displayConfidence")) { j.at("displayConfidence").get_to(_displayConfidence); }
162 if (j.contains("confidenceFillAlpha")) { j.at("confidenceFillAlpha").get_to(_confidenceFillAlpha); }
163 if (j.contains("updateLast")) { j.at("updateLast").get_to(_updateLast); }
164}
165
167{
168 LOG_TRACE("{}: called", nameId());
169
170 _startingInsTime.reset();
171
172 for (auto& sensor : _sensors)
173 {
174 std::scoped_lock<std::mutex> lg(sensor.mutex);
175 sensor.cumSum = { Eigen::Vector3d::Zero() };
176 sensor.allanSum = {};
177 sensor.allanVariance = {};
178 sensor.allanDeviation = {};
179 sensor.slope = {};
180 sensor.allanDeviationConfidenceIntervals = {};
181 }
182
183 _averagingFactors.clear();
184 _averagingTimes.clear();
185 _observationCount.clear();
186
187 _imuObsCount = 0;
188
191 _samplingInterval = 0.0;
192
193 return true;
194}
195
197{
198 LOG_TRACE("{}: called", nameId());
199}
200
202{
203 auto obs = std::static_pointer_cast<const ImuObs>(queue.extract_front());
204
205 bool lastMessage = false;
206 if (queue.empty()
207 && inputPins[INPUT_PORT_INDEX_IMU_OBS].isPinLinked()
208 && inputPins[INPUT_PORT_INDEX_IMU_OBS].link.getConnectedPin()->noMoreDataAvailable)
209 {
210 lastMessage = true;
211 }
212
213 // save InsTime of first imuObs for sampling interval computation
214 if (_startingInsTime.empty()) { _startingInsTime = obs->insTime; }
215
216 _imuObsCount++;
217 {
218 std::scoped_lock<std::mutex> lg(_sensors.at(Accel).mutex);
219 _sensors.at(Accel).cumSum.emplace_back(_sensors.at(Accel).cumSum.back() + obs->p_acceleration);
220 }
221 {
222 std::scoped_lock<std::mutex> lg(_sensors.at(Gyro).mutex);
223 _sensors.at(Gyro).cumSum.emplace_back(_sensors.at(Gyro).cumSum.back() + obs->p_angularRate);
224 }
225
226 // extending _averagingFactors if necessary
228 {
230 _observationCount.push_back(0);
231
232 for (auto& sensor : _sensors)
233 {
234 for (size_t d = 0; d < 3; d++)
235 {
236 sensor.allanSum.at(d).push_back(0);
237 }
238 }
239
240 // computation of next averaging factor
241 while (static_cast<unsigned int>(round(pow(10., static_cast<double>(_nextAveragingFactorExponent) / _averagingFactorsPerDecade))) == _nextAveragingFactor)
242 {
244 }
245
246 _nextAveragingFactor = static_cast<unsigned int>(round(pow(10., static_cast<double>(_nextAveragingFactorExponent) / _averagingFactorsPerDecade)));
247 }
248
249 // computation Allan sum
250 for (size_t k = 0; k < _averagingFactors.size(); k++)
251 {
252 for (auto& sensor : _sensors)
253 {
254 std::scoped_lock<std::mutex> lg(sensor.mutex);
255 Eigen::Vector3d tempSum = sensor.cumSum.at(_imuObsCount)
256 - 2 * sensor.cumSum.at(_imuObsCount - static_cast<unsigned int>(_averagingFactors.at(k)))
257 + sensor.cumSum.at(_imuObsCount - 2 * static_cast<unsigned int>(_averagingFactors.at(k)));
258
259 for (size_t d = 0; d < 3; d++)
260 {
261 sensor.allanSum.at(d).at(k) += pow(tempSum(static_cast<Eigen::Index>(d)), 2);
262 }
263 }
264 _observationCount.at(k)++;
265 }
266
267 // computation of Allan Variance and Deviation
268 if (!_updateLast || lastMessage)
269 {
270 _samplingInterval = static_cast<double>((obs->insTime - _startingInsTime).count()) / _imuObsCount;
271 _averagingTimes.resize(_averagingFactors.size(), 0.);
273 for (size_t k = 0; k < _averagingFactors.size(); k++)
274 {
276 _confidenceMultiplicationFactor.at(k) = sqrt(0.5 / (_imuObsCount / _averagingFactors.at(k) - 1));
277 }
278
279 for (auto& sensor : _sensors)
280 {
281 std::scoped_lock<std::mutex> lg(sensor.mutex);
282 for (size_t d = 0; d < 3; d++)
283 {
284 sensor.allanVariance.at(d).resize(_averagingFactors.size(), 0.);
285 sensor.allanDeviation.at(d).resize(_averagingFactors.size(), 0.);
286
287 for (size_t j = 0; j < 2; j++)
288 {
289 sensor.allanDeviationConfidenceIntervals.at(d).at(j).resize(_averagingFactors.size(), 0.);
290 }
291
292 for (size_t k = 0; k < _averagingFactors.size(); k++)
293 {
294 sensor.allanVariance.at(d).at(k) = sensor.allanSum.at(d).at(k) / (2 * pow(_averagingFactors.at(k), 2) * _observationCount.at(k));
295
296 sensor.allanDeviation.at(d).at(k) = sqrt(sensor.allanVariance.at(d).at(k));
297
298 sensor.allanDeviationConfidenceIntervals.at(d).at(0).at(k) = sensor.allanDeviation.at(d).at(k) * (1 - _confidenceMultiplicationFactor.at(k));
299 sensor.allanDeviationConfidenceIntervals.at(d).at(1).at(k) = sensor.allanDeviation.at(d).at(k) * (1 + _confidenceMultiplicationFactor.at(k));
300 }
301 }
302
303 // Compute Slopes
304 for (size_t d = 0; d < 3; d++) { sensor.slope.at(d).resize(_averagingFactors.size(), 0.); }
305
306 for (size_t k = 0; k < _averagingFactors.size(); k++)
307 {
308 size_t lo = (k == 0 ? k : k - 1);
309 size_t hi = (k == _averagingFactors.size() - 1 ? k : k + 1);
310
311 double divisor = log(_averagingFactors.at(hi) / _averagingFactors.at(lo));
312
313 for (size_t d = 0; d < 3; d++)
314 {
315 sensor.slope.at(d).at(k) = log(sensor.allanVariance.at(d).at(hi) / sensor.allanVariance.at(d).at(lo)) / divisor;
316 }
317 }
318 }
319 }
320
321 // empty _cumSum for performance's sake
322 if (lastMessage)
323 {
324 for (auto& sensor : _sensors) { sensor.cumSum.clear(); }
325 }
326}
327
329{
330 switch (sensorType)
331 {
333 return "Accelerometer";
334 case SensorType::Gyro:
335 return "Gyroscope";
337 return "Error: Count";
338 }
339 return "";
340}
341
343{
344 switch (sensorType)
345 {
347 return "m/s²";
348 case SensorType::Gyro:
349 return "rad/s";
351 return "Error: Count";
352 }
353 return "";
354}
Computes Allan Deviation.
Save/Load the Nodes.
nlohmann::json json
json namespace
Text Help Marker (?) with Tooltip.
Parent Class for all IMU Observations.
Utility class for logging to console and file.
#define LOG_TRACE
Detailled info to trace the execution of the program. Should not be called on functions which receive...
Definition Logger.hpp:65
std::vector< double > _averagingFactors
averaging factors (n) used for Allan Variance computation
unsigned int _imuObsCount
number of IMU observations / length of cumulative sums
~AllanDeviation() override
Destructor.
static const char * unitString(SensorType sensorType)
Returns a string for the unit of the type.
static std::string category()
String representation of the Class Category.
std::vector< double > _averagingTimes
averaging times (τ)
static constexpr size_t INPUT_PORT_INDEX_IMU_OBS
Flow (ImuObs)
static std::string typeStatic()
String representation of the Class Type.
InsTime _startingInsTime
Time of first epoch received.
json save() const override
Saves the node into a json object.
std::array< Sensor, SensorType_COUNT > _sensors
Sensor data.
float _confidenceFillAlpha
The alpha value for the shaded plot of the confidence intervals.
double _samplingInterval
sampling interval
void deinitialize() override
Deinitialize the node.
unsigned int _nextAveragingFactor
next averaging factor to be appended to _averagingFactors
AllanDeviation()
Default constructor.
std::vector< double > _confidenceMultiplicationFactor
multiplication factor for simple confidence
bool _updateLast
Flag wether to update the plot for each message or once at the end.
bool _displayConfidence
Flag wether to display confidence intervals.
@ Accel
Accelerometer.
@ SensorType_COUNT
Amount of sensors to use.
static const char * to_string(SensorType sensorType)
Returns a string representation of the type.
double _averagingFactorsPerDecade
number of averaging factors per decade
void restore(const json &j) override
Restores the node from a json object.
unsigned int _nextAveragingFactorExponent
exponent of next averaging factor
void receiveImuObs(InputPin::NodeDataQueue &queue, size_t pinIdx)
Receive Sensor Data.
bool initialize() override
Initialize the node.
std::string type() const override
String representation of the Class Type.
void guiConfig() override
ImGui config window which is shown on double click.
std::vector< double > _observationCount
number of observations for each τ
static std::string type()
Returns the type of the data class.
Definition ImuObs.hpp:33
TsDeque< std::shared_ptr< const NAV::NodeData > > NodeDataQueue
Node data queue type.
Definition Pin.hpp:707
ImVec2 _guiConfigDefaultWindowSize
Definition Node.hpp:522
Node(std::string name)
Constructor.
Definition Node.cpp:29
std::vector< InputPin > inputPins
List of input pins.
Definition Node.hpp:509
std::string nameId() const
Node name and id.
Definition Node.cpp:323
bool _lockConfigDuringRun
Lock the config when executing post-processing.
Definition Node.hpp:528
std::string name
Name of the Node.
Definition Node.hpp:507
InputPin * CreateInputPin(const char *name, Pin::Type pinType, const std::vector< std::string > &dataIdentifier={}, InputPin::Callback callback=static_cast< InputPin::FlowFirableCallbackFunc >(nullptr), InputPin::FlowFirableCheckFunc firable=nullptr, int priority=0, int idx=-1)
Create an Input Pin object.
Definition Node.cpp:252
bool _hasConfig
Flag if the config window should be shown.
Definition Node.hpp:525
auto extract_front()
Returns a copy of the first element in the container and removes it from the container.
Definition TsDeque.hpp:494
bool empty() const noexcept
Checks if the container has no elements, i.e. whether 'begin() == end()'.
Definition TsDeque.hpp:275
ImGui extensions.
void ApplyChanges()
Signals that there have been changes to the flow.
const char * to_string(gui::widgets::PositionWithFrame::ReferenceFrame refFrame)
Converts the enum to a string.
@ Flow
NodeData Trigger.
Definition Pin.hpp:52