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 |