0.4.1
Loading...
Searching...
No Matches
MultiImuFile.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 "MultiImuFile.hpp"
10
11#include "util/Logger.hpp"
12
15
17namespace nm = NAV::NodeManager;
22
24 : Node(typeStatic())
25{
26 LOG_TRACE("{}: called", name);
27
28 _hasConfig = true;
29 _guiConfigDefaultWindowSize = { 528, 379 };
30
32}
33
35{
36 LOG_TRACE("{}: called", nameId());
37}
38
40{
41 return "MultiImuFile";
42}
43
44std::string NAV::MultiImuFile::type() const
45{
46 return typeStatic();
47}
48
50{
51 return "Data Provider";
52}
53
55{
56 float columnWidth = 130.0F * gui::NodeEditorApplication::windowFontRatio();
57
58 if (FileReader::guiConfig(".txt", { ".txt" }, size_t(id), nameId()))
59 {
62 }
63
64 ImGui::SetNextItemWidth(columnWidth);
65
66 if (gui::widgets::EnumCombo(fmt::format("NMEA message type##{}", size_t(id)).c_str(), _nmeaType))
67 {
68 LOG_DEBUG("{}: nmeaType changed to {}", nameId(), fmt::underlying(_nmeaType));
69
72 }
73 ImGui::SameLine();
74 gui::widgets::HelpMarker("Until June 2023, NMEA messages in the Multi-IMU file's header were of the 'GPGGA' type. Since this type does not provide an absolute time reference, it was changed to 'GPZDA'.\n\n");
75
77 {
78 if (gui::widgets::TimeEdit(fmt::format("{}", size_t(id)).c_str(), _startTime, _startTimeEditFormat))
79 {
80 LOG_DEBUG("{}: startTime changed to {}", nameId(), _startTime);
82 }
83 }
84
85 ImGui::Separator();
86 // Set Imu Position and Rotation (from 'Imu::guiConfig();')
87 bool showRotation = true;
88 for (size_t i = 0; i < _nSensors; ++i)
89 {
90 ImGui::SetNextItemOpen(showRotation, ImGuiCond_FirstUseEver);
91 if (i == 0) { showRotation = false; }
92 if (ImGui::TreeNode(fmt::format("Imu #{} Position & Rotation##{}", i + 1, size_t(id)).c_str()))
93 {
94 ImGui::BeginDisabled(); // FIXME Not properly simulated and accounted for in the algorithms
95 std::array<float, 3> imuPos = { static_cast<float>(_imuPosAll[i].b_positionIMU_p().x()), static_cast<float>(_imuPosAll[i].b_positionIMU_p().y()), static_cast<float>(_imuPosAll[i].b_positionIMU_p().z()) };
96 if (ImGui::InputFloat3(fmt::format("Position [m]##{}", size_t(id)).c_str(), imuPos.data()))
97 {
99 _imuPosAll[i]._b_positionIMU_p = Eigen::Vector3d(imuPos.at(0), imuPos.at(1), imuPos.at(2));
100 }
101 ImGui::EndDisabled();
102 ImGui::SameLine();
103 gui::widgets::HelpMarker("Position of the IMU sensor relative to the vehicle center of mass in the body coordinate frame.");
104
105 Eigen::Vector3d eulerAnglesIMU = rad2deg(trafo::quat2eulerZYX(_imuPosAll[i].p_quat_b()));
106 std::array<float, 3> imuRot = { static_cast<float>(eulerAnglesIMU.x()), static_cast<float>(eulerAnglesIMU.y()), static_cast<float>(eulerAnglesIMU.z()) };
107 if (ImGui::InputFloat3(fmt::format("Rotation [deg]##{}", size_t(id)).c_str(), imuRot.data()))
108 {
109 // (-180:180] x (-90:90] x (-180:180]
110 imuRot.at(0) = std::max(imuRot.at(0), -179.9999F);
111 imuRot.at(0) = std::min(imuRot.at(0), 180.0F);
112 imuRot.at(1) = std::max(imuRot.at(1), -89.9999F);
113 imuRot.at(1) = std::min(imuRot.at(1), 90.0F);
114 imuRot.at(2) = std::max(imuRot.at(2), -179.9999F);
115 imuRot.at(2) = std::min(imuRot.at(2), 180.0F);
116
118 _imuPosAll[i]._b_quat_p = trafo::b_Quat_p(deg2rad(imuRot.at(0)), deg2rad(imuRot.at(1)), deg2rad(imuRot.at(2)));
119 }
120
121 ImGui::TreePop();
122 }
123 }
124}
125
126[[nodiscard]] json NAV::MultiImuFile::save() const
127{
128 LOG_TRACE("{}: called", nameId());
129
130 json j;
131
132 j["FileReader"] = FileReader::save();
133 j["imuPos"] = _imuPosAll;
134 j["nmeaType"] = _nmeaType;
135 j["startTime"] = _startTime;
136 j["delim"] = _delim;
137
138 return j;
139}
140
142{
143 LOG_TRACE("{}: called", nameId());
144
145 if (j.contains("FileReader"))
146 {
147 FileReader::restore(j.at("FileReader"));
148 }
149 if (j.contains("imuPos"))
150 {
151 j.at("imuPos").get_to(_imuPosAll);
152 }
153 if (j.contains("nmeaType"))
154 {
155 j.at("nmeaType").get_to(_nmeaType);
156 }
157 if (j.contains("startTime"))
158 {
159 j.at("startTime").get_to(_startTime);
160 }
161 if (j.contains("delim"))
162 {
163 j.at("delim").get_to(_delim);
164 }
165}
166
168{
169 LOG_TRACE("{}: called", nameId());
170
171 return FileReader::initialize();
172}
173
175{
176 LOG_TRACE("{}: called", nameId());
177
179}
180
182{
183 _lastFiltObs.reset();
184 _lineCounter = 0;
185
187
188 for (auto& sensor : _messages)
189 {
190 sensor.clear();
191 }
192 for (auto& cnt : _messageCnt)
193 {
194 cnt = 0;
195 }
196
197 return true;
198}
199
201{
202 while (outputPins.size() < _nSensors)
203 {
204 nm::CreateOutputPin(this, fmt::format("ImuObs {}", outputPins.size() + 1).c_str(), Pin::Type::Flow, { NAV::ImuObs::type() }, &MultiImuFile::pollData);
205 _imuPosAll.resize(_nSensors);
206
207 _messages.resize(_nSensors);
208 _messageCnt.resize(_nSensors);
209 }
210}
211
213{
214 LOG_TRACE("called");
215
216 if (good())
217 {
218 return FileType::ASCII;
219 }
220
221 LOG_ERROR("Could not open file {}", getFilepath().string());
222 return FileType::NONE;
223}
224
226{
227 LOG_TRACE("called");
228
229 _gpzdaFound = false;
230 _gpggaFound = false;
231 const char* gpzda = "GPZDA";
232 const char* gpgga = "GPGGA";
233
234 std::string line;
235 auto len = tellg();
236
237 // Find first line of data
238 while (getline(line))
239 {
240 _lineCounter++;
241
242 // Remove any trailing non text characters
243 line.erase(std::ranges::find_if(line, [](int ch) { return std::iscntrl(ch); }), line.end());
244
245 if ((line.find(gpzda)) != std::string::npos)
246 {
247 _gpzdaFound = true;
248
249 int32_t year{};
250 int32_t month{};
251 int32_t day{};
252 int32_t hour{};
253 int32_t min{};
254 long double sec{};
255
256 // Convert line into stream
257 std::stringstream lineStream(line);
258 std::string cell;
259
260 // Split line at comma
261 for (const auto& col : _headerColumns)
262 {
263 if (std::getline(lineStream, cell, ','))
264 {
265 // Remove any trailing non text characters
266 cell.erase(std::ranges::find_if(cell, [](int ch) { return std::iscntrl(ch); }), cell.end());
267 while (cell.empty())
268 {
269 std::getline(lineStream, cell, ',');
270 }
271
272 if (col == "nmeaMsgType")
273 {
274 LOG_DEBUG("{}: nmeaMsgType read.", nameId());
275 continue;
276 }
277 if (col == "UTC_HMS")
278 {
279 hour = std::stoi(cell.substr(0, 2));
280 min = std::stoi(cell.substr(2, 2));
281 sec = std::stold(cell.substr(4, 5));
282 continue;
283 }
284 if (col == "day")
285 {
286 day = std::stoi(cell);
287 continue;
288 }
289 if (col == "month")
290 {
291 month = std::stoi(cell);
292 continue;
293 }
294 if (col == "year")
295 {
296 year = std::stoi(cell);
297 }
298
299 _startTime = InsTime(year, month, day, hour, min, sec + 1. / _gpsRate, UTC); // GPS has a rate of 1 Hz, actual starttime is one message after the last GPZDA msg in the header
300
301 len = tellg();
302 continue;
303 }
304 }
305 }
306 if (line.find(gpgga) != std::string::npos)
307 {
308 _gpggaFound = true;
309 }
310 else if ((std::find_if(line.begin(), line.begin() + 1, [](int ch) { return std::isdigit(ch); }) != (std::begin(line) + 1)) && (_gpggaFound || _gpzdaFound))
311 {
312 LOG_DEBUG("{}: Found first line of data: {}", nameId(), line);
313 seekg(len, std::ios_base::beg); // Reset the read cursor, otherwise we skip the first message
314 break;
315 }
316 len = tellg();
317 }
319 {
320 LOG_WARN("{}: NMEA message type was set to 'GPZDA', but the file contains only 'GPGGA'. Make sure that the absolute time reference is set correctly.", nameId());
322 }
324 {
325 LOG_WARN("{}: NMEA message type was set to 'GPGGA', but the file contains 'GPZDA'. The absolute time reference was read from the header, not the GUI input.", nameId());
327 }
328 _delim = _gpzdaFound ? ',' : ' ';
329}
330
331std::shared_ptr<const NAV::NodeData> NAV::MultiImuFile::pollData(size_t pinIdx, bool peek)
332{
333 std::shared_ptr<ImuObs> obs = nullptr;
334
335 if (!_messages.at(pinIdx).empty()) // Another pin was reading a message for this pin
336 {
337 obs = _messages.at(pinIdx).begin()->second;
338 if (!peek) // When peeking, we leave the message in the buffer, so we can remove it when polling
339 {
340 _messages.at(pinIdx).erase(_messages.at(pinIdx).begin());
341 }
342 }
343 else
344 {
345 // Read line
346 std::string line;
347 while (getline(line))
348 {
349 _lineCounter++;
350
351 // Remove any starting non text characters
352 line.erase(line.begin(), std::ranges::find_if(line, [](int ch) { return std::isgraph(ch); }));
353
354 if (line.empty())
355 {
356 continue;
357 }
358
359 // Convert line into stream
360 std::stringstream lineStream(line);
361 std::string cell;
362
363 size_t sensorId{};
364 double gpsSecond{};
365 double timeNumerator{};
366 double timeDenominator{};
367 std::optional<double> accelX;
368 std::optional<double> accelY;
369 std::optional<double> accelZ;
370 std::optional<double> gyroX;
371 std::optional<double> gyroY;
372 std::optional<double> gyroZ;
373
374 // Split line at comma
375 for (const auto& col : _columns)
376 {
377 if (std::getline(lineStream, cell, _delim))
378 {
379 // Remove any trailing non text characters
380 cell.erase(std::ranges::find_if(cell, [](int ch) { return std::iscntrl(ch); }), cell.end());
381 while (cell.empty())
382 {
383 std::getline(lineStream, cell, ' ');
384 }
385
386 if (col == "sensorId")
387 {
388 sensorId = std::stoul(cell); // NOLINT(clang-diagnostic-implicit-int-conversion)
389 }
390 else if (col == "gpsSecond")
391 {
392 gpsSecond = std::stod(cell); // [s]
393 if (_startupGpsSecond == 0)
394 {
395 _startupGpsSecond = gpsSecond;
396 }
397 }
398 else if (col == "timeNumerator")
399 {
400 timeNumerator = std::stod(cell);
401 }
402 else if (col == "timeDenominator")
403 {
404 timeDenominator = std::stod(cell);
405 }
406 else if (col == "accelX")
407 {
408 accelX = 0.001 * std::stod(cell); // [m/s²]
409 }
410 else if (col == "accelY")
411 {
412 accelY = 0.001 * std::stod(cell); // [m/s²]
413 }
414 else if (col == "accelZ")
415 {
416 accelZ = 0.001 * std::stod(cell); // [m/s²]
417 }
418 else if (col == "gyroX")
419 {
420 gyroX = deg2rad(std::stod(cell) / 131); // [deg/s]
421 }
422 else if (col == "gyroY")
423 {
424 gyroY = deg2rad(std::stod(cell)) / 131; // [deg/s]
425 }
426 else if (col == "gyroZ")
427 {
428 gyroZ = deg2rad(std::stod(cell)) / 131; // [deg/s]
429 }
430 }
431 }
432
433 auto timeStamp = gpsSecond + timeNumerator / timeDenominator - _startupGpsSecond;
434 if (!peek)
435 {
436 LOG_DEBUG("line: {}", line);
437 LOG_DEBUG("timeStamp: {}", timeStamp);
438 }
439
440 obs = std::make_shared<ImuObs>(_imuPosAll[sensorId - 1]);
441
442 obs->insTime = _startTime + std::chrono::duration<double>(timeStamp);
443
444 if (!accelX || !accelY || !accelZ)
445 {
446 LOG_ERROR("{}: Fields 'accelX', 'accelY', 'accelZ' are needed.", nameId());
447 return nullptr;
448 }
449 if (!gyroX || !gyroY || !gyroZ)
450 {
451 LOG_ERROR("{}: Fields 'gyroX', 'gyroY', 'gyroZ' are needed.", nameId());
452 return nullptr;
453 }
454
455 obs->p_acceleration = { accelX.value(), accelY.value(), accelZ.value() };
456 obs->p_angularRate = { gyroX.value(), gyroY.value(), gyroZ.value() };
457
458 if (sensorId - 1 != pinIdx)
459 {
460 // Write message into buffer to process later on correct pin
461 _messages.at(sensorId - 1).insert(std::make_pair(obs->insTime, obs));
462
463 continue; // Read next line in file and search for correct sensor
464 }
465 if (peek)
466 {
467 // Write message into buffer to process later in poll step
468 _messages.at(pinIdx).insert(std::make_pair(obs->insTime, obs));
469 }
470 break;
471 }
472 }
473
474 // Calls all the callbacks
475 if (obs && !peek)
476 {
477 _messageCnt.at(pinIdx)++;
478
479 // Detect jumps back in time
480 if (obs->insTime < _lastFiltObs)
481 {
482 LOG_ERROR("{}: Jumped back in time on line {} (at {}), by {} s", nameId(), _lineCounter, obs->insTime.toYMDHMS(), static_cast<double>((obs->insTime - _lastFiltObs).count()));
483 return obs;
484 }
485 _lastFiltObs = obs->insTime;
486
487 invokeCallbacks(pinIdx, obs);
488 }
489 if (obs == nullptr)
490 {
491 LOG_DEBUG("{}: Finished reading on pinIdx {}. Read a total of {} messages.", nameId(), pinIdx, _messageCnt.at(pinIdx));
492 }
493 return obs;
494}
Transformation collection.
Combo representing an enumeration.
Save/Load the Nodes.
nlohmann::json json
json namespace
Text Help Marker (?) with Tooltip.
Utility class for logging to console and file.
#define LOG_DEBUG
Debug information. Should not be called on functions which receive observations (spamming)
Definition Logger.hpp:67
#define LOG_ERROR
Error occurred, which stops part of the program to work, but not everything.
Definition Logger.hpp:73
#define LOG_WARN
Error occurred, but a fallback option exists and program continues to work normally.
Definition Logger.hpp:71
#define LOG_TRACE
Detailled info to trace the execution of the program. Should not be called on functions which receive...
Definition Logger.hpp:65
File reader for Multi-IMU data log files.
Manages all Nodes.
bool initialize()
Initialize the file reader.
void restore(const json &j)
Restores the node from a json object.
auto peek()
Looking ahead in the stream.
bool good() const
Fast error checking.
FileType
File Type Enumeration.
@ ASCII
Ascii text data.
@ NONE
Not specified.
std::filesystem::path getFilepath()
Returns the path of the file.
std::streampos tellg()
Getting the current read position.
GuiResult guiConfig(const char *vFilters, const std::vector< std::string > &extensions, size_t id, const std::string &nameId)
ImGui config.
void resetReader()
Moves the read cursor to the start.
auto & getline(std::string &str)
Reads a line from the filestream.
json save() const
Saves the node into a json object.
auto & seekg(std::streamoff pos, std::ios_base::seekdir dir)
Changing the current read position.
void deinitialize()
Deinitialize the file reader.
The class is responsible for all time-related tasks.
Definition InsTime.hpp:710
char _delim
Delimiter: ',' for GPZDA and ' ' for GPGGA messages (NMEA)
std::vector< std::string > _headerColumns
Container of header column names.
NmeaType _nmeaType
Selected NMEA type in the GUI.
std::vector< std::string > _columns
Container of column names.
MultiImuFile()
Default constructor.
bool resetNode() override
Resets the node. Moves the read cursor to the start.
size_t _lineCounter
Counter for lines.
std::vector< std::map< InsTime, std::shared_ptr< ImuObs > > > _messages
Read messages. Vector idx: Sensor Id,.
static std::string category()
String representation of the Class Category.
void updateNumberOfOutputPins()
Adds/Deletes Output Pins depending on the variable _nOutputPins.
InsTime _startTime
Absolute start time.
bool _gpggaFound
Flag that indicates whether a 'GPGGA' message was found in the Multi-IMU-Logs header.
std::shared_ptr< const NodeData > pollData(size_t pinIdx, bool peek)
Polls data from the file.
static std::string typeStatic()
String representation of the Class Type.
std::vector< size_t > _messageCnt
Counter for messages.
double _gpsRate
GPS data rate [Hz].
json save() const override
Saves the node into a json object.
void restore(const json &j) override
Restores the node from a json object.
std::vector< ImuPos > _imuPosAll
Container for individual sensor orientations of a Multi-IMU.
gui::widgets::TimeEditFormat _startTimeEditFormat
Time Format to input the init time with.
bool initialize() override
Initialize the node.
size_t _nSensors
Number of connected sensors.
~MultiImuFile() override
Destructor.
InsTime _lastFiltObs
Previous observation (for timestamp)
void guiConfig() override
ImGui config window which is shown on double click.
@ GPGGA
NMEA message type.
@ GPZDA
NMEA message type.
std::string type() const override
String representation of the Class Type.
bool _gpzdaFound
Flag that indicates whether a 'GPZDA' message was found in the Multi-IMU-Logs header.
NAV::FileReader::FileType determineFileType() override
Function to determine the File Type.
void deinitialize() override
Deinitialize the node.
double _startupGpsSecond
First 'gpsSecond', s.t. measurements start at time = 0.
void readHeader() override
Function to read the Header of a file.
bool doDeinitialize(bool wait=false)
Asks the node worker to deinitialize the node.
Definition Node.cpp:395
ImVec2 _guiConfigDefaultWindowSize
Definition Node.hpp:410
std::vector< OutputPin > outputPins
List of output pins.
Definition Node.hpp:399
Node(std::string name)
Constructor.
Definition Node.cpp:30
std::string nameId() const
Node name and id.
Definition Node.cpp:253
std::string name
Name of the Node.
Definition Node.hpp:395
void invokeCallbacks(size_t portIndex, const std::shared_ptr< const NodeData > &data)
Calls all registered callbacks on the specified output port.
Definition Node.cpp:180
bool _hasConfig
Flag if the config window should be shown.
Definition Node.hpp:413
static float windowFontRatio()
Ratio to multiply for GUI window elements.
OutputPin * CreateOutputPin(Node *node, const char *name, Pin::Type pinType, const std::vector< std::string > &dataIdentifier, OutputPin::PinData data=static_cast< void * >(nullptr), int idx=-1)
Create an Output Pin object.
void ApplyChanges()
Signals that there have been changes to the flow.
bool TimeEdit(const char *str_id, InsTime &insTime, TimeEditFormat &timeEditFormat, float itemWidth=170.0F)
Inputs to edit an InsTime object.
Definition TimeEdit.cpp:52
bool EnumCombo(const char *label, T &enumeration, size_t startIdx=0)
Combo representing an enumeration.
Definition EnumCombo.hpp:30
void HelpMarker(const char *desc, const char *symbol="(?)")
Text Help Marker, e.g. '(?)', with Tooltip.
Eigen::Quaternion< Scalar > b_Quat_p(Scalar mountingAngleX, Scalar mountingAngleY, Scalar mountingAngleZ)
Quaternion for rotations from platform to body frame.
Eigen::Vector3< typename Derived::Scalar > quat2eulerZYX(const Eigen::QuaternionBase< Derived > &q)
Converts the quaternion to Euler rotation angles with rotation sequence ZYX.
@ UTC
Coordinated Universal Time.
constexpr auto deg2rad(const T &deg)
Convert Degree to Radians.
Definition Units.hpp:21
constexpr auto rad2deg(const T &rad)
Convert Radians to Degree.
Definition Units.hpp:39
@ Flow
NodeData Trigger.
Definition Pin.hpp:52