INSTINCT Code Coverage Report


Directory: src/
File: Nodes/Plotting/Plot.hpp
Date: 2025-02-07 16:54:41
Exec Total Coverage
Lines: 7 22 31.8%
Functions: 6 23 26.1%
Branches: 8 34 23.5%

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 Plot.hpp
10 /// @brief Plots data into ImPlot Windows
11 /// @author T. Topp (topp@ins.uni-stuttgart.de)
12 /// @date 2021-01-09
13
14 #pragma once
15
16 // <boost/asio.hpp> needs to be included before <winsock.h> (even though not used in this file)
17 // https://stackoverflow.com/questions/9750344/boostasio-winsock-and-winsock-2-compatibility-issue
18 #ifdef _WIN32
19 // Set the proper SDK version before including boost/Asio
20 #include <SDKDDKVer.h>
21 // Note boost/ASIO includes Windows.h.
22 #include <boost/asio.hpp>
23 #endif //_WIN32
24
25 #include <array>
26 #include <imgui.h>
27 #include <implot.h>
28
29 #include <map>
30 #include <memory>
31 #include <mutex>
32 #include <unordered_set>
33
34 #include "NodeData/NodeData.hpp"
35 #include "internal/Node/Node.hpp"
36 #include "internal/gui/widgets/DynamicInputPins.hpp"
37 #include "internal/gui/widgets/PositionInput.hpp"
38
39 #include "util/Container/ScrollingBuffer.hpp"
40 #include "util/Container/Vector.hpp"
41 #include "util/Plot/PlotEventTooltip.hpp"
42 #include "util/Plot/PlotItemStyle.hpp"
43 #include "util/Plot/PlotTooltip.hpp"
44 #include "util/Logger/CommonLog.hpp"
45
46 #include "NodeData/General/DynamicData.hpp"
47 #include "NodeData/GNSS/GnssCombination.hpp"
48 #include "NodeData/GNSS/GnssObs.hpp"
49 #include "NodeData/GNSS/RtklibPosObs.hpp"
50 #include "NodeData/GNSS/SppSolution.hpp"
51 #include "NodeData/IMU/ImuObs.hpp"
52 #include "NodeData/IMU/ImuObsSimulated.hpp"
53 #include "NodeData/IMU/ImuObsWDelta.hpp"
54 #include "NodeData/IMU/KvhObs.hpp"
55 #include "NodeData/IMU/VectorNavBinaryOutput.hpp"
56 #include "NodeData/State/InsGnssLCKFSolution.hpp"
57 #include "NodeData/State/PosVelAtt.hpp"
58 #include "NodeData/State/InsGnssTCKFSolution.hpp"
59 #include "NodeData/WiFi/WiFiPositioningSolution.hpp"
60
61 namespace NAV
62 {
63 /// @brief Plot node which plots all kind of observations
64 class Plot : public Node, public CommonLog
65 {
66 public:
67 /// @brief Default constructor
68 Plot();
69 /// @brief Destructor
70 ~Plot() override;
71 /// @brief Copy constructor
72 Plot(const Plot&) = delete;
73 /// @brief Move constructor
74 Plot(Plot&&) = delete;
75 /// @brief Copy assignment operator
76 Plot& operator=(const Plot&) = delete;
77 /// @brief Move assignment operator
78 Plot& operator=(Plot&&) = delete;
79
80 /// @brief String representation of the Class Type
81 [[nodiscard]] static std::string typeStatic();
82
83 /// @brief String representation of the Class Type
84 [[nodiscard]] std::string type() const override;
85
86 /// @brief String representation of the Class Category
87 [[nodiscard]] static std::string category();
88
89 /// @brief ImGui config window which is shown on double click
90 /// @attention Don't forget to set _hasConfig to true in the constructor of the node
91 void guiConfig() override;
92
93 /// @brief Saves the node into a json object
94 [[nodiscard]] json save() const override;
95
96 /// @brief Restores the node from a json object
97 /// @param[in] j Json object with the node state
98 void restore(const json& j) override;
99
100 /// @brief Called when a new link was established
101 /// @param[in] startPin Pin where the link starts
102 /// @param[in] endPin Pin where the link ends
103 void afterCreateLink(OutputPin& startPin, InputPin& endPin) override;
104
105 /// @brief Information needed to plot the data on a certain pin
106 struct PinData
107 {
108 /// @brief Stores the actual data coming from a pin
109 struct PlotData
110 {
111 /// @brief Default constructor (needed to make serialization with json working)
112
1/2
✓ Branch 2 taken 3951 times.
✗ Branch 3 not taken.
3951 PlotData() = default;
113
114 /// @brief Constructor
115 /// @param[in] displayName Display name of the contained data
116 /// @param[in] size Size of the buffer
117 PlotData(std::string displayName, size_t size);
118
119 /// Display name of the contained data
120 std::string displayName;
121 /// Buffer for the data
122 ScrollingBuffer<double> buffer;
123 /// Flag if data was received, as the buffer contains std::nan("") otherwise
124 bool hasData = false;
125
126 /// When connecting a new link. All data is flagged for delete and only those who are also present in the new link are kept
127 bool markedForDelete = false;
128 /// Bool to show if dynamic data
129 bool isDynamic = false;
130 };
131
132 /// @brief Possible Pin types
133 enum class PinType : uint8_t
134 {
135 Flow, ///< NodeData Trigger
136 Bool, ///< Boolean
137 Int, ///< Integer Number
138 Float, ///< Floating Point Number
139 Matrix, ///< Matrix Object
140 };
141
142 /// @brief Constructor
143
1/2
✓ Branch 3 taken 264 times.
✗ Branch 4 not taken.
264 PinData() = default;
144 /// @brief Destructor
145 391 ~PinData() = default;
146 /// @brief Copy constructor
147 /// @param[in] other The other element to copy
148 PinData(const PinData& other);
149
150 /// @brief Move constructor
151 /// @param[in] other The other element to move
152 PinData(PinData&& other) noexcept;
153
154 /// @brief Copy assignment operator
155 /// @param[in] rhs The other element to copy
156 PinData& operator=(const PinData& rhs);
157 /// @brief Move assignment operator
158 /// @param[in] rhs The other element to move
159 PinData& operator=(PinData&& rhs) noexcept;
160
161 /// @brief Adds a plotData Element to the list
162 /// @param[in] dataIndex Index where to add the data to
163 /// @param[in] displayName Display name of the contained data
164 void addPlotDataItem(size_t dataIndex, const std::string& displayName);
165
166 /// Size of all buffers of the plotData elements
167 int size = 0;
168 /// Data Identifier of the connected pin
169 std::string dataIdentifier;
170 /// List with all the data
171 std::vector<PlotData> plotData;
172 /// List with the raw data received
173 ScrollingBuffer<std::shared_ptr<const NodeData>> rawNodeData;
174 /// Pin Type
175 PinType pinType = PinType::Flow;
176 /// Amount of points to skip for plotting
177 int stride = 1;
178 /// Mutex to lock the buffer so that the GUI thread and the calculation threads don't cause a data race
179 std::mutex mutex;
180 /// Dynamic data start index
181 int dynamicDataStartIndex = -1;
182 /// Events with relative time, absolute time, tooltip text and data Index (-1 means all)
183 std::vector<std::tuple<double, InsTime, std::string, int32_t>> events;
184 };
185
186 /// @brief Information specifying the look of each plot
187 struct PlotInfo
188 {
189 /// Info needed to draw a data set
190 struct PlotItem
191 {
192 /// @brief Default constructor (needed to make serialization with json working)
193
4/8
✓ Branch 14 taken 404 times.
✗ Branch 15 not taken.
✓ Branch 17 taken 404 times.
✗ Branch 18 not taken.
✓ Branch 20 taken 404 times.
✗ Branch 21 not taken.
✓ Branch 23 taken 404 times.
✗ Branch 24 not taken.
404 PlotItem() = default;
194
195 /// @brief Constructor
196 /// @param[in] pinIndex Index of the pin where the data came in
197 /// @param[in] dataIndex Index of the data on the pin
198 /// @param[in] displayName Display name of the data
199 PlotItem(size_t pinIndex, size_t dataIndex, std::string displayName)
200 : pinIndex(pinIndex), dataIndex(dataIndex), displayName(std::move(displayName))
201 {
202 style.colormapMaskDataCmpIdx = dataIndex;
203 style.markerColormapMaskDataCmpIdx = dataIndex;
204 }
205
206 /// @brief Constructor
207 /// @param[in] pinIndex Index of the pin where the data came in
208 /// @param[in] dataIndex Index of the data on the pin
209 /// @param[in] displayName Display name of the data
210 /// @param[in] axis Axis to plot the data on (Y1, Y2, Y3)
211 PlotItem(size_t pinIndex, size_t dataIndex, std::string displayName, ImAxis axis)
212 : PlotItem(pinIndex, dataIndex, std::move(displayName))
213 {
214 this->axis = axis; // NOLINT(cppcoreguidelines-prefer-member-initializer)
215 }
216
217 /// @brief Equal comparison operator (needed to search the vector with std::find)
218 /// @param[in] rhs Right-hand-side of the operator
219 /// @return True if the pin and data indices match
220 constexpr bool operator==(const PlotItem& rhs) const
221 {
222 return pinIndex == rhs.pinIndex && dataIndex == rhs.dataIndex && displayName == rhs.displayName;
223 }
224
225 size_t pinIndex{}; ///< Index of the pin where the data came in
226 size_t dataIndex{}; ///< Index of the data on the pin
227 std::string displayName; ///< Display name of the data (not changing and unique)
228 ImAxis axis{ ImAxis_Y1 }; ///< Axis to plot the data on (Y1, Y2, Y3)
229 PlotItemStyle style{}; ///< Defines how the data should be plotted
230 /// Buffer for the colormap mask
231 ScrollingBuffer<ImU32> colormapMaskColors = ScrollingBuffer<ImU32>(0);
232 /// Colormap version (to track updates of the colormap)
233 size_t colormapMaskVersion = 0;
234 /// Buffer for the colormap mask
235 ScrollingBuffer<ImU32> markerColormapMaskColors = ScrollingBuffer<ImU32>(0);
236 /// Colormap version (to track updates of the colormap)
237 size_t markerColormapMaskVersion = 0;
238
239 /// Buffer for event markers
240 ScrollingBuffer<double> eventMarker = ScrollingBuffer<double>(0);
241
242 /// Error bounds lower and upper data
243 std::array<ScrollingBuffer<double>, 2> errorBoundsData;
244
245 /// List of tooltips (x,y, tooltip)
246 std::vector<std::tuple<double, double, PlotEventTooltip>> eventTooltips;
247 };
248
249 /// @brief Default constructor
250 161 PlotInfo() = default;
251
252 /// @brief Constructor
253 /// @param[in] title Title of the ImPlot
254 /// @param[in] nInputPins Amount of inputPins
255 161 PlotInfo(const std::string& title, size_t nInputPins)
256
2/4
✓ Branch 3 taken 161 times.
✗ Branch 4 not taken.
✓ Branch 10 taken 161 times.
✗ Branch 11 not taken.
322 : title(title), headerText(title), selectedXdata(nInputPins, 1) {}
257
258 /// Size of the plot
259 ImVec2 size{ -1, 300 };
260
261 /// Title of the ImPlot
262 std::string title;
263 /// Title of the CollapsingHeader
264 std::string headerText;
265 /// Flag, whether to override the x axis label
266 bool overrideXAxisLabel = false;
267 /// X axis label
268 std::string xAxisLabel;
269 /// Y1 axis label
270 std::string y1AxisLabel;
271 /// Y2 axis label
272 std::string y2AxisLabel;
273 /// Y3 axis label
274 std::string y3AxisLabel;
275 /// Selected pin in the GUI for the Drag & Drop Data
276 size_t selectedPin = 0;
277 /// Flags which are passed to the plot
278 int plotFlags = 0;
279 /// Flags for the x-Axis
280 ImPlotAxisFlags xAxisFlags = ImPlotAxisFlags_AutoFit;
281 /// Flags for the y-Axes
282 ImPlotAxisFlags yAxisFlags = ImPlotAxisFlags_AutoFit;
283 /// Scale for the x-Axis
284 ImPlotScale xAxisScale = ImPlotScale_Linear;
285 /// Scale for the y-Axes
286 std::array<ImPlotScale, 3> yAxesScale = { ImPlotScale_Linear, ImPlotScale_Linear, ImPlotScale_Linear };
287 /// Line Flags for all items (each item can override the selection)
288 ImPlotLineFlags lineFlags = ImPlotLineFlags_NoClip | ImPlotLineFlags_SkipNaN;
289
290 /// @brief Key: PinIndex, Value: plotData to use for x-Axis
291 std::vector<size_t> selectedXdata;
292
293 /// List containing all elements which should be plotted
294 std::vector<PlotItem> plotItems;
295
296 /// Width of plot Data content
297 float leftPaneWidth = 180.0F;
298 /// Width of the plot
299 float rightPaneWidth = 400.0F;
300
301 /// Flag whether the whole plot is visible. If not, then it should be deleted
302 bool visible = true;
303
304 /// List of tooltip windows to show
305 std::vector<PlotTooltip> tooltips;
306 };
307
308 private:
309 /// @brief Initialize the node
310 bool initialize() override;
311
312 /// @brief Deinitialize the node
313 void deinitialize() override;
314
315 /// @brief Adds/Deletes Plots depending on the variable nPlots
316 void updateNumberOfPlots();
317
318 /// @brief Function to call to add a new pin
319 /// @param[in, out] node Pointer to this node
320 static void pinAddCallback(Node* node);
321 /// @brief Function to call to delete a pin
322 /// @param[in, out] node Pointer to this node
323 /// @param[in] pinIdx Input pin index to delete
324 static void pinDeleteCallback(Node* node, size_t pinIdx);
325
326 /// Index of the GPST data (unix timestamp)
327 size_t GPST_PLOT_IDX = 1;
328
329 /// Data storage for each pin
330 std::vector<PinData> _pinData;
331
332 /// Info for each plot window
333 std::vector<PlotInfo> _plots;
334
335 /// Amount of plot windows (should equal _plots.size())
336 size_t _nPlots = 0;
337 /// Possible data identifiers to connect
338 std::vector<std::string> _dataIdentifier = {
339 // General
340 DynamicData::type(),
341 // GNSS
342 GnssCombination::type(),
343 GnssObs::type(),
344 RtklibPosObs::type(),
345 SppSolution::type(),
346 // IMU
347 ImuObs::type(),
348 ImuObsSimulated::type(),
349 ImuObsWDelta::type(),
350 KvhObs::type(),
351 VectorNavBinaryOutput::type(),
352 // State
353 InsGnssLCKFSolution::type(),
354 Pos::type(),
355 PosVel::type(),
356 PosVelAtt::type(),
357 InsGnssTCKFSolution::type(),
358 // WiFi
359 WiFiPositioningSolution::type(),
360 };
361
362 /// Index of the Collapsible Header currently being dragged
363 int _dragAndDropHeaderIndex = -1;
364
365 size_t _screenshotFrameCnt = 0; ///< Frame counter for taking screenshots (> 0 when screenshot in progress)
366 size_t _screenShotPlotIdx = 0; ///< Plot index a screenshot is taken of
367
368 /// Values to force the x axis range to and a set of plotIdx to force
369 std::pair<std::unordered_set<size_t>, ImPlotRange> _forceXaxisRange;
370
371 /// Start position for the calculation of relative North-South and East-West
372 std::optional<gui::widgets::PositionWithFrame> _originPosition;
373
374 /// Flag, whether to override the North/East startValues in the GUI
375 bool _overridePositionStartValues = false;
376
377 /// @brief Dynamic input pins
378 /// @attention This should always be the last variable in the header, because it accesses others through the function callbacks
379 gui::widgets::DynamicInputPins _dynamicInputPins{ 0, this, pinAddCallback, pinDeleteCallback, 1 };
380
381 /// @brief Adds a event to a certain point in time
382 /// @param[in] pinIndex Index of the input pin where the data was received
383 /// @param insTime Absolute time
384 /// @param text Text to display
385 /// @param dataIndex Data Index to add the event for (-1 means all)
386 void addEvent(size_t pinIndex, InsTime insTime, const std::string& text, int32_t dataIndex);
387
388 /// @brief Add Data to the buffer of the pin
389 /// @param[in] pinIndex Index of the input pin where the data was received
390 /// @param[in] dataIndex Index of the data to insert
391 /// @param[in] value The value to insert
392 void addData(size_t pinIndex, size_t dataIndex, double value);
393
394 /// @brief Add Data to the buffer of the pin
395 /// @param[in] pinIndex Index of the input pin where the data was received
396 /// @param[in] displayName Display name of the data
397 /// @param[in] value The value to insert
398 /// @return Data Index where data were inserted
399 size_t addData(size_t pinIndex, std::string displayName, double value);
400
401 /// @brief Calculate the local position offset from the plot origin
402 /// @param[in] lla_position [𝜙, λ, h] Latitude, Longitude, Altitude in [rad, rad, m]
403 /// @return Local positions in north/south and east/west directions in [m]
404 CommonLog::LocalPosition calcLocalPosition(const Eigen::Vector3d& lla_position);
405
406 /// @brief Plots the data on this port
407 /// @param[in] insTime Time the data was received
408 /// @param[in] pinIdx Index of the pin the data is received on
409 void plotBoolean(const InsTime& insTime, size_t pinIdx);
410
411 /// @brief Plots the data on this port
412 /// @param[in] insTime Time the data was received
413 /// @param[in] pinIdx Index of the pin the data is received on
414 void plotInteger(const InsTime& insTime, size_t pinIdx);
415
416 /// @brief Plots the data on this port
417 /// @param[in] insTime Time the data was received
418 /// @param[in] pinIdx Index of the pin the data is received on
419 void plotFloat(const InsTime& insTime, size_t pinIdx);
420
421 /// @brief Plots the data on this port
422 /// @param[in] insTime Time the data was received
423 /// @param[in] pinIdx Index of the pin the data is received on
424 void plotMatrix(const InsTime& insTime, size_t pinIdx);
425
426 /// @brief Plot the data on this port
427 /// @param[in] queue Queue with all the received data messages
428 /// @param[in] pinIdx Index of the pin the data is received on
429 void plotFlowData(InputPin::NodeDataQueue& queue, size_t pinIdx);
430
431 /// @brief Plot the data
432 /// @param[in] obs Observation to plot
433 /// @param[in] pinIndex Index of the input pin where the data was received
434 /// @param[in, out] plotIndex Index for inserting the data into the plot data vector
435 /// @param[in] startIndex Data descriptor start index
436 template<typename T>
437 void plotData(const std::shared_ptr<const T>& obs, size_t pinIndex, size_t& plotIndex, size_t startIndex = 0)
438 {
439 for (size_t i = startIndex; i < T::GetStaticDescriptorCount(); ++i)
440 {
441 addData(pinIndex, plotIndex++, obs->getValueAtOrNaN(i));
442 }
443 }
444 };
445
446 } // namespace NAV
447