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 |