INSTINCT Code Coverage Report


Directory: src/
File: Nodes/DataProvider/IMU/FileReader/ImuFile.cpp
Date: 2025-11-25 23:34:18
Exec Total Coverage
Lines: 13 192 6.8%
Functions: 4 16 25.0%
Branches: 11 414 2.7%

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 #include "ImuFile.hpp"
10
11 #include "util/Logger.hpp"
12 #include "util/StringUtil.hpp"
13
14 #include "Navigation/Transformations/CoordinateFrames.hpp"
15
16 #include "internal/FlowManager.hpp"
17
18 #include "NodeData/IMU/ImuObs.hpp"
19 #include "NodeData/IMU/ImuObsWDelta.hpp"
20
21 114 NAV::ImuFile::ImuFile()
22
3/6
✓ Branch 1 taken 114 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 114 times.
✗ Branch 5 not taken.
✓ Branch 8 taken 114 times.
✗ Branch 9 not taken.
114 : Imu(typeStatic())
23 {
24 LOG_TRACE("{}: called", name);
25
26 114 _hasConfig = true;
27 114 _guiConfigDefaultWindowSize = { 377, 201 };
28
29
4/8
✓ Branch 1 taken 114 times.
✗ Branch 2 not taken.
✓ Branch 5 taken 114 times.
✗ Branch 6 not taken.
✓ Branch 8 taken 228 times.
✓ Branch 9 taken 114 times.
✗ Branch 12 not taken.
✗ Branch 13 not taken.
456 CreateOutputPin("ImuObs", Pin::Type::Flow, { NAV::ImuObs::type(), NAV::ImuObsWDelta::type() }, &ImuFile::pollData);
30
2/4
✓ Branch 2 taken 114 times.
✗ Branch 3 not taken.
✓ Branch 6 taken 114 times.
✗ Branch 7 not taken.
228 CreateOutputPin("Header Columns", Pin::Type::Object, { "std::vector<std::string>" }, &_headerColumns);
31 228 }
32
33 228 NAV::ImuFile::~ImuFile()
34 {
35 LOG_TRACE("{}: called", nameId());
36 228 }
37
38 228 std::string NAV::ImuFile::typeStatic()
39 {
40
1/2
✓ Branch 1 taken 228 times.
✗ Branch 2 not taken.
456 return "ImuFile";
41 }
42
43 std::string NAV::ImuFile::type() const
44 {
45 return typeStatic();
46 }
47
48 114 std::string NAV::ImuFile::category()
49 {
50
1/2
✓ Branch 1 taken 114 times.
✗ Branch 2 not taken.
228 return "Data Provider";
51 }
52
53 void NAV::ImuFile::guiConfig()
54 {
55 if (auto res = FileReader::guiConfig(".csv,.*", { ".csv" }, size_t(id), nameId()))
56 {
57 LOG_DEBUG("{}: Path changed to {}", nameId(), _path);
58 flow::ApplyChanges();
59 if (res == FileReader::PATH_CHANGED)
60 {
61 doReinitialize();
62 }
63 else
64 {
65 doDeinitialize();
66 }
67 }
68
69 Imu::guiConfig();
70
71 // Header info
72 if (ImGui::BeginTable(fmt::format("##ImuHeaders ({})", id.AsPointer()).c_str(), 3,
73 ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg))
74 {
75 ImGui::TableSetupColumn("Time", ImGuiTableColumnFlags_WidthFixed);
76 ImGui::TableSetupColumn("IMU", ImGuiTableColumnFlags_WidthFixed);
77 ImGui::TableSetupColumn("Delta IMU", ImGuiTableColumnFlags_WidthFixed);
78 ImGui::TableHeadersRow();
79
80 auto TextColoredIfExists = [this](int index, const char* displayText, const char* searchText, bool alwaysNormal = false) {
81 ImGui::TableSetColumnIndex(index);
82 if (alwaysNormal
83 || std::ranges::find_if(_headerColumns, [&searchText](const std::string& header) {
84 return header.starts_with(searchText);
85 }) != _headerColumns.end())
86 {
87 ImGui::TextUnformatted(displayText);
88 }
89 else
90 {
91 ImGui::TextDisabled("%s", displayText);
92 }
93 };
94
95 ImGui::TableNextRow();
96 TextColoredIfExists(0, "GpsCycle", "GpsCycle");
97 TextColoredIfExists(1, "Mag", "MagX");
98 TextColoredIfExists(2, "DeltaTime", "DeltaTime");
99 ImGui::TableNextRow();
100 TextColoredIfExists(0, "GpsWeek", "GpsWeek");
101 TextColoredIfExists(1, "Acc", "AccX");
102 TextColoredIfExists(2, "DeltaTheta", "DeltaThetaX");
103 ImGui::TableNextRow();
104 TextColoredIfExists(0, "GpsToW", "GpsToW");
105 TextColoredIfExists(1, "Gyro", "GyroX");
106 TextColoredIfExists(2, "DeltaVel", "DeltaVelX");
107 ImGui::TableNextRow();
108 TextColoredIfExists(0, "TimeStartup", "TimeStartup");
109 TextColoredIfExists(1, "Temperature", "Temperature");
110
111 ImGui::EndTable();
112 }
113 }
114
115 [[nodiscard]] json NAV::ImuFile::save() const
116 {
117 LOG_TRACE("{}: called", nameId());
118
119 json j;
120
121 j["FileReader"] = FileReader::save();
122 j["Imu"] = Imu::save();
123
124 return j;
125 }
126
127 void NAV::ImuFile::restore(json const& j)
128 {
129 LOG_TRACE("{}: called", nameId());
130
131 if (j.contains("FileReader"))
132 {
133 FileReader::restore(j.at("FileReader"));
134 }
135 if (j.contains("Imu"))
136 {
137 Imu::restore(j.at("Imu"));
138 }
139 }
140
141 bool NAV::ImuFile::initialize()
142 {
143 LOG_TRACE("{}: called", nameId());
144
145 bool success = false;
146 if (FileReader::initialize())
147 {
148 for (auto& col : _headerColumns)
149 {
150 str::replace(col, "GpsTow", "GpsToW");
151 }
152
153 size_t nDelta = 0;
154 for (const auto& col : _headerColumns)
155 {
156 if (col.starts_with("DeltaTime")
157 || col.starts_with("DeltaThetaX") || col.starts_with("DeltaThetaY") || col.starts_with("DeltaThetaZ")
158 || col.starts_with("DeltaVelX") || col.starts_with("DeltaVelY") || col.starts_with("DeltaVelZ"))
159 {
160 nDelta++;
161 }
162 }
163
164 _withDelta = nDelta == 7;
165
166 outputPins[OUTPUT_PORT_INDEX_IMU_OBS].dataIdentifier = _withDelta ? std::vector{ NAV::ImuObsWDelta::type() } : std::vector{ NAV::ImuObs::type() };
167 success = true;
168 }
169 else
170 {
171 outputPins[OUTPUT_PORT_INDEX_IMU_OBS].dataIdentifier = { NAV::ImuObs::type(), NAV::ImuObsWDelta::type() };
172 }
173
174 for (auto& link : outputPins[OUTPUT_PORT_INDEX_IMU_OBS].links)
175 {
176 if (auto* pin = link.getConnectedPin())
177 {
178 outputPins[OUTPUT_PORT_INDEX_IMU_OBS].recreateLink(*pin);
179 }
180 }
181
182 return success;
183 }
184
185 void NAV::ImuFile::deinitialize()
186 {
187 LOG_TRACE("{}: called", nameId());
188
189 FileReader::deinitialize();
190 }
191
192 bool NAV::ImuFile::resetNode()
193 {
194 FileReader::resetReader();
195
196 return true;
197 }
198
199 std::shared_ptr<const NAV::NodeData> NAV::ImuFile::pollData()
200 {
201 std::shared_ptr<ImuObs> obs;
202 if (_withDelta) { obs = std::make_shared<ImuObsWDelta>(_imuPos); }
203 else { obs = std::make_shared<ImuObs>(_imuPos); }
204
205 // Read line
206 std::string line;
207 getline(line);
208 // Remove any starting non text characters
209 line.erase(line.begin(), std::ranges::find_if(line, [](int ch) { return std::isgraph(ch); }));
210
211 if (line.empty())
212 {
213 return nullptr;
214 }
215
216 // Convert line into stream
217 std::stringstream lineStream(line);
218 std::string cell;
219
220 std::optional<uint16_t> gpsCycle = 0;
221 std::optional<uint16_t> gpsWeek;
222 std::optional<long double> gpsToW;
223 std::optional<double> magX;
224 std::optional<double> magY;
225 std::optional<double> magZ;
226 std::optional<double> accelX;
227 std::optional<double> accelY;
228 std::optional<double> accelZ;
229 std::optional<double> gyroX;
230 std::optional<double> gyroY;
231 std::optional<double> gyroZ;
232
233 std::optional<double> deltaTime;
234 std::optional<double> deltaThetaX;
235 std::optional<double> deltaThetaY;
236 std::optional<double> deltaThetaZ;
237 std::optional<double> deltaVelX;
238 std::optional<double> deltaVelY;
239 std::optional<double> deltaVelZ;
240
241 // Split line at comma
242 for (const auto& column : _headerColumns)
243 {
244 if (std::getline(lineStream, cell, ','))
245 {
246 // Remove any trailing non text characters
247 cell.erase(std::ranges::find_if(cell, [](int ch) { return std::iscntrl(ch); }), cell.end());
248
249 if (cell.empty()) { continue; }
250
251 if (column.starts_with("GpsCycle"))
252 {
253 gpsCycle = static_cast<uint16_t>(std::stoul(cell));
254 }
255 else if (column.starts_with("GpsWeek"))
256 {
257 gpsWeek = static_cast<uint16_t>(std::stoul(cell));
258 }
259 else if (column.starts_with("GpsToW"))
260 {
261 gpsToW = std::stold(cell);
262 }
263 else if (column.starts_with("MagX"))
264 {
265 magX = std::stod(cell);
266 }
267 else if (column.starts_with("MagY"))
268 {
269 magY = std::stod(cell);
270 }
271 else if (column.starts_with("MagZ"))
272 {
273 magZ = std::stod(cell);
274 }
275 else if (column.starts_with("AccX"))
276 {
277 accelX = std::stod(cell);
278 }
279 else if (column.starts_with("AccY"))
280 {
281 accelY = std::stod(cell);
282 }
283 else if (column.starts_with("AccZ"))
284 {
285 accelZ = std::stod(cell);
286 }
287 else if (column.starts_with("GyroX"))
288 {
289 gyroX = std::stod(cell);
290 }
291 else if (column.starts_with("GyroY"))
292 {
293 gyroY = std::stod(cell);
294 }
295 else if (column.starts_with("GyroZ"))
296 {
297 gyroZ = std::stod(cell);
298 }
299 else if (column.starts_with("Temperature"))
300 {
301 obs->temperature.emplace(std::stod(cell));
302 }
303 else if (column.starts_with("DeltaTime"))
304 {
305 deltaTime = std::stod(cell);
306 }
307 else if (column.starts_with("DeltaThetaX"))
308 {
309 deltaThetaX = std::stod(cell);
310 }
311 else if (column.starts_with("DeltaThetaY"))
312 {
313 deltaThetaY = std::stod(cell);
314 }
315 else if (column.starts_with("DeltaThetaZ"))
316 {
317 deltaThetaZ = std::stod(cell);
318 }
319 else if (column.starts_with("DeltaVelX"))
320 {
321 deltaVelX = std::stod(cell);
322 }
323 else if (column.starts_with("DeltaVelY"))
324 {
325 deltaVelY = std::stod(cell);
326 }
327 else if (column.starts_with("DeltaVelZ"))
328 {
329 deltaVelZ = std::stod(cell);
330 }
331 }
332 }
333
334 if (_withDelta)
335 {
336 if (deltaTime && deltaThetaX && deltaThetaY && deltaThetaZ && deltaVelX && deltaVelY && deltaVelZ)
337 {
338 if (auto obsWDelta = std::reinterpret_pointer_cast<ImuObsWDelta>(obs))
339 {
340 obsWDelta->dtime = deltaTime.value();
341 obsWDelta->dtheta = { deltaThetaX.value(), deltaThetaY.value(), deltaThetaZ.value() };
342 obsWDelta->dvel = { deltaVelX.value(), deltaVelY.value(), deltaVelZ.value() };
343 }
344 }
345 else
346 {
347 LOG_ERROR("{}: Columns 'DeltaTime', 'DeltaThetaX', 'DeltaThetaY', 'DeltaThetaZ', 'DeltaVelX', 'DeltaVelY', 'DeltaVelZ' are needed.", nameId());
348 return nullptr;
349 }
350 }
351
352 if (!gpsCycle || !gpsWeek || !gpsToW)
353 {
354 LOG_ERROR("{}: Fields 'GpsCycle', 'GpsWeek', 'GpsToW' are needed.", nameId());
355 return nullptr;
356 }
357 if (!accelX || !accelY || !accelZ)
358 {
359 LOG_ERROR("{}: Fields 'AccX', 'AccY', 'AccZ' are needed.", nameId());
360 return nullptr;
361 }
362 if (!gyroX || !gyroY || !gyroZ)
363 {
364 LOG_ERROR("{}: Fields 'GyroX', 'GyroY', 'GyroZ' are needed.", nameId());
365 return nullptr;
366 }
367
368 obs->insTime = InsTime(gpsCycle.value(), gpsWeek.value(), gpsToW.value());
369 obs->p_acceleration = { accelX.value(), accelY.value(), accelZ.value() };
370 obs->p_angularRate = { gyroX.value(), gyroY.value(), gyroZ.value() };
371
372 if (magX && magY && magZ)
373 {
374 obs->p_magneticField.emplace(magX.value(), magY.value(), magZ.value());
375 }
376
377 invokeCallbacks(OUTPUT_PORT_INDEX_IMU_OBS, obs);
378 return obs;
379 }
380