INSTINCT Code Coverage Report


Directory: src/
File: Nodes/DataProvider/IMU/FileReader/ImuFile.cpp
Date: 2025-02-07 16:54:41
Exec Total Coverage
Lines: 13 193 6.7%
Functions: 4 16 25.0%
Branches: 11 418 2.6%

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