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