0.4.1
Loading...
Searching...
No Matches
CsvLogger.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 "CsvLogger.hpp"
10
11#include "NodeData/NodeData.hpp"
12
13#include "util/Logger.hpp"
14
15#include <iomanip> // std::setprecision
16#include <regex>
17
19namespace nm = NAV::NodeManager;
21#include "NodeRegistry.hpp"
23#include "util/StringUtil.hpp"
24
26 : Node(typeStatic())
27{
28 LOG_TRACE("{}: called", name);
29
31
32 _hasConfig = true;
33 _guiConfigDefaultWindowSize = { 380, 70 };
34
35 nm::CreateInputPin(this, "writeObservation", Pin::Type::Flow,
36 { NodeData::type() },
38}
39
41{
42 LOG_TRACE("{}: called", nameId());
43}
44
46{
47 return "CsvLogger";
48}
49
50std::string NAV::CsvLogger::type() const
51{
52 return typeStatic();
53}
54
56{
57 return "Data Logger";
58}
59
61{
62 if (FileWriter::guiConfig(".csv", { ".csv" }, size_t(id), nameId()))
63 {
66 }
67
69 {
71 }
72
73 if (ImGui::Button(fmt::format("Clear header##{}", size_t(id)).c_str()))
74 {
75 _headerLogging.clear();
76 }
77 ImGui::SameLine();
78 if (ImGui::Button(fmt::format("Select all##{}", size_t(id)).c_str()))
79 {
80 for (auto& header : _headerLogging) { header.second = true; }
82 }
83 ImGui::SameLine();
84 if (ImGui::Button(fmt::format("Deselect all##{}", size_t(id)).c_str()))
85 {
86 for (auto& header : _headerLogging) { header.second = false; }
88 }
89 ImGui::SameLine();
90 if (ImGui::Checkbox(fmt::format("Default for new##{}", size_t(id)).c_str(), &_headerLoggingDefault))
91 {
93 }
94 ImGui::SameLine();
95 if (ImGui::Checkbox(fmt::format("Sort headers in GUI##{}", size_t(id)).c_str(), &_headerLoggingSortGui))
96 {
98 }
99
100 if (_headerLoggingRegex.empty()) { ImGui::BeginDisabled(); }
101 std::optional<bool> regexSelect;
102 if (ImGui::Button(fmt::format("Select regex##{}", size_t(id)).c_str()))
103 {
104 regexSelect = true;
105 }
106 ImGui::SameLine();
107 if (ImGui::Button(fmt::format("Deselect regex##{}", size_t(id)).c_str()))
108 {
109 regexSelect = false;
110 }
111 if (regexSelect.has_value())
112 {
113 bool anyChanged = false;
114 try
115 {
116 for (auto& [desc, checked] : _headerLogging)
117 {
118 std::regex self_regex(_headerLoggingRegex,
119 std::regex_constants::ECMAScript | std::regex_constants::icase);
120 if (std::regex_search(desc, self_regex) && checked != *regexSelect)
121 {
122 anyChanged = true;
123 checked = *regexSelect;
124 }
125 }
126 }
127 catch (const std::regex_error& e)
128 {
129 LOG_ERROR("Regex could not be parsed: {}", e.what());
130 }
131 if (anyChanged)
132 {
134 }
135 }
136 if (_headerLoggingRegex.empty()) { ImGui::EndDisabled(); }
137 ImGui::SameLine();
138 ImGui::SetNextItemWidth(300.0F * gui::NodeEditorApplication::windowFontRatio());
139 if (ImGui::InputText(fmt::format("##Select Regex {}", size_t(id)).c_str(), &_headerLoggingRegex))
140 {
142 }
143
144 if (!_headerLogging.empty())
145 {
146 auto* headerLogging = &_headerLogging;
147 decltype(_headerLogging) sortedHeaderLogging;
149 {
150 sortedHeaderLogging = _headerLogging;
151 std::ranges::sort(sortedHeaderLogging);
152 headerLogging = &sortedHeaderLogging;
153 }
154 int nCols = std::min((static_cast<int>(headerLogging->size()) - 1) / 5 + 1, 3);
155 if (ImGui::BeginChild(fmt::format("Headers Scrolling {}", size_t(id)).c_str(), ImGui::GetContentRegionAvail(), false))
156 {
157 if (ImGui::BeginTable(fmt::format("Logging headers##{}", size_t(id)).c_str(), nCols, ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg | ImGuiTableFlags_SizingFixedFit | ImGuiTableFlags_NoHostExtendX, ImVec2(0.0F, 0.0F)))
158 {
159 for (auto& [desc, checked] : *headerLogging)
160 {
161 ImGui::TableNextColumn();
162 if (ImGui::Checkbox(fmt::format("{}##{}", desc, size_t(id)).c_str(), &checked))
163 {
165 {
166 if (auto iter = std::ranges::find_if(_headerLogging, [&](const std::pair<std::string, bool>& header) {
167 return desc == header.first; // NOLINT(clang-analyzer-core.CallAndMessage)
168 });
169 iter != _headerLogging.end())
170 {
171 iter->second = checked;
172 }
173 }
175 }
176 }
177
178 ImGui::EndTable();
179 }
180 }
181 ImGui::EndChild();
182 }
183 else
184 {
185 ImGui::TextUnformatted("Please run the flow to collect information about the available data.");
186 }
187}
188
189[[nodiscard]] json NAV::CsvLogger::save() const
190{
191 LOG_TRACE("{}: called", nameId());
192
193 return {
194 { "FileWriter", FileWriter::save() },
195 { "header", _headerLogging },
196 { "lastConnectedType", _lastConnectedType },
197 { "headerLoggingRegex", _headerLoggingRegex },
198 { "headerLoggingDefault", _headerLoggingDefault },
199 { "headerLoggingSortGui", _headerLoggingSortGui },
200 };
201}
202
204{
205 LOG_TRACE("{}: called", nameId());
206
207 if (j.contains("FileWriter")) { FileWriter::restore(j.at("FileWriter")); }
208 if (j.contains("header")) { j.at("header").get_to(_headerLogging); }
209 if (j.contains("lastConnectedType")) { j.at("lastConnectedType").get_to(_lastConnectedType); }
210 if (j.contains("headerLoggingRegex")) { j.at("headerLoggingRegex").get_to(_headerLoggingRegex); }
211 if (j.contains("headerLoggingDefault")) { j.at("headerLoggingDefault").get_to(_headerLoggingDefault); }
212}
213
215{
216 _filestream.flush();
217}
218
219bool NAV::CsvLogger::onCreateLink([[maybe_unused]] OutputPin& startPin, [[maybe_unused]] InputPin& endPin)
220{
221 LOG_TRACE("{}: called for {} ==> {}", nameId(), size_t(startPin.id), size_t(endPin.id));
222
223 if (_lastConnectedType != startPin.dataIdentifier.front())
224 {
225 LOG_DEBUG("{}: [{} ==> {}] Dropping headers because last type [{}] and new type [{}]", nameId(), size_t(startPin.id), size_t(endPin.id), _lastConnectedType, startPin.dataIdentifier.front());
226 _headerLogging.clear();
227 }
228 _lastConnectedType = startPin.dataIdentifier.front();
229
230 return true;
231}
232
234{
235 LOG_TRACE("{}: called", nameId());
236
238 {
239 return false;
240 }
241
243
244 _headerWritten = false;
245 _dynamicHeader.clear();
246
247 return true;
248}
249
251{
252 LOG_TRACE("{}: called", nameId());
253
255}
256
258{
259 _filestream << "Time [s],GpsCycle,GpsWeek,GpsToW [s]";
260
261#if LOG_LEVEL <= LOG_LEVEL_TRACE
262 std::string headers = "Time [s],GpsCycle,GpsWeek,GpsToW [s]";
263#endif
264
266 for (const auto& [desc, enabled] : _headerLogging)
267 {
268 if (!enabled) { continue; }
270
271 auto header = str::replaceAll_copy(desc, ",", "_");
272#if LOG_LEVEL <= LOG_LEVEL_TRACE
273 headers += "," + header;
274#endif
275 _filestream << "," << header;
276 }
277 _filestream << std::endl; // NOLINT(performance-avoid-endl)
278
279#if LOG_LEVEL <= LOG_LEVEL_TRACE
280 LOG_TRACE("{}: Header written:\n{}", nameId(), headers);
281#endif
282
283 _headerWritten = true;
284}
285
286void NAV::CsvLogger::rewriteData(size_t oldSize, size_t newSize)
287{
288 LOG_TRACE("{}: Rewriting header, because {} new elements", nameId(), newSize - oldSize);
290 auto tmpFilePath = getFilepath().concat("_temp");
291 std::filesystem::rename(getFilepath(), tmpFilePath);
293 writeHeader();
294
295 std::ifstream tmpFilestream(tmpFilePath, std::ios_base::in | std::ios_base::binary);
296 if (tmpFilestream.good())
297 {
298 std::string delimiterEnd(newSize - oldSize, ',');
299 std::string line;
300 std::getline(tmpFilestream, line); // Old header
301 while (std::getline(tmpFilestream, line) && !tmpFilestream.eof())
302 {
303 _filestream << line << delimiterEnd << '\n';
304 }
305 }
306 if (tmpFilestream.is_open()) { tmpFilestream.close(); }
307 tmpFilestream.clear();
308 std::filesystem::remove(tmpFilePath);
309}
310
312{
313 auto obs = queue.extract_front();
314
315 auto oldHeaderLength = static_cast<size_t>(std::ranges::count_if(_headerLogging, [](const auto& header) { return header.second; }));
316 if (!_headerWritten)
317 {
318 for (const auto& desc : obs->staticDataDescriptors())
319 {
320 if (auto iter = std::ranges::find_if(_headerLogging, [&](const std::pair<std::string, bool>& header) {
321 return desc == header.first;
322 });
323 iter == _headerLogging.end())
324 {
325 _headerLogging.emplace_back(desc, _headerLoggingDefault);
327 }
328 }
329 }
330 for (const auto& desc : obs->dynamicDataDescriptors())
331 {
332 if (std::ranges::none_of(_dynamicHeader, [&](const auto& header) { return header == desc; }))
333 {
334 LOG_DATA("{}: Adding new dynamic header: {}", nameId(), desc);
335 _dynamicHeader.push_back(desc);
336 if (auto iter = std::ranges::find_if(_headerLogging, [&](const std::pair<std::string, bool>& header) {
337 return desc == header.first;
338 });
339 iter == _headerLogging.end())
340 {
341 _headerLogging.emplace_back(desc, _headerLoggingDefault);
343 }
344 }
345 }
346
347 if (!_headerWritten) { writeHeader(); }
348 else if (auto newHeaderLength = static_cast<size_t>(std::ranges::count_if(_headerLogging, [](const auto& header) { return header.second; }));
349 oldHeaderLength != newHeaderLength)
350 {
351 rewriteData(oldHeaderLength, newHeaderLength);
352 }
353
354 constexpr int gpsCyclePrecision = 3;
355 constexpr int gpsTimePrecision = 12;
356 constexpr int valuePrecision = 15;
357
358 if (!obs->insTime.empty())
359 {
360 _filestream << std::setprecision(valuePrecision) << std::round(calcTimeIntoRun(obs->insTime) * 1e9) / 1e9;
361 }
362 _filestream << ",";
363 if (!obs->insTime.empty())
364 {
365 _filestream << std::fixed << std::setprecision(gpsCyclePrecision) << obs->insTime.toGPSweekTow().gpsCycle;
366 }
367 _filestream << ",";
368 if (!obs->insTime.empty())
369 {
370 _filestream << std::defaultfloat << std::setprecision(gpsTimePrecision) << obs->insTime.toGPSweekTow().gpsWeek;
371 }
372 _filestream << ",";
373 if (!obs->insTime.empty())
374 {
375 _filestream << std::defaultfloat << std::setprecision(gpsTimePrecision) << obs->insTime.toGPSweekTow().tow;
376 }
377 _filestream << std::setprecision(valuePrecision);
378
379 size_t dataLogged = 0;
380 const auto staticDataDescriptors = obs->staticDataDescriptors();
381 for (size_t i = 0; i < obs->staticDescriptorCount(); ++i)
382 {
383 const auto& desc = staticDataDescriptors.at(i);
384 if (auto iter = std::ranges::find_if(_headerLogging, [&](const std::pair<std::string, bool>& header) {
385 return desc == header.first;
386 });
387 iter != _headerLogging.end() && !iter->second)
388 {
389 continue;
390 }
391 dataLogged++;
392 _filestream << ',';
393 if (auto val = obs->getValueAt(i)) { _filestream << *val; }
394 }
395
396 for (const auto& desc : _dynamicHeader)
397 {
398 if (auto iter = std::ranges::find_if(_headerLogging, [&](const std::pair<std::string, bool>& header) {
399 return desc == header.first;
400 });
401 iter != _headerLogging.end() && !iter->second)
402 {
403 continue;
404 }
405 dataLogged++;
406 _filestream << ',';
407 if (auto val = obs->getDynamicDataAt(desc)) { _filestream << *val; }
408 }
409 for (size_t i = dataLogged; i < _headerLoggingCount; i++)
410 {
411 _filestream << ',';
412 }
413
414 _filestream << '\n';
415}
Data Logger for CSV files.
Save/Load the Nodes.
nlohmann::json json
json namespace
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_DATA
All output which occurs repeatedly every time observations are received.
Definition Logger.hpp:29
#define LOG_ERROR
Error occurred, which stops part of the program to work, but not everything.
Definition Logger.hpp:73
#define LOG_TRACE
Detailled info to trace the execution of the program. Should not be called on functions which receive...
Definition Logger.hpp:65
Abstract NodeData Class.
Manages all Nodes.
Utility class which specifies available nodes.
Utility functions for working with std::strings.
static bool ShowOriginInput(const char *id)
Shows a GUI to input a origin location.
void initialize() const
Initialize the common log variables.
Definition CommonLog.cpp:51
static double calcTimeIntoRun(const InsTime &insTime)
Calculates the relative time into he run.
Definition CommonLog.cpp:70
void guiConfig() override
ImGui config window which is shown on double click.
Definition CsvLogger.cpp:60
bool _headerLoggingSortGui
Sort headers in the GUI.
static std::string typeStatic()
String representation of the Class Type.
Definition CsvLogger.cpp:45
std::vector< std::pair< std::string, bool > > _headerLogging
Header which should be logged.
bool initialize() override
Initialize the node.
void rewriteData(size_t oldSize, size_t newSize)
Rewrites the data file with a new size.
std::vector< std::string > _dynamicHeader
Dynamic Header.
Definition CsvLogger.hpp:97
bool _headerWritten
Flag whether the header was written already.
Definition CsvLogger.hpp:94
void restore(const json &j) override
Restores the node from a json object.
json save() const override
Saves the node into a json object.
void writeObservation(InputPin::NodeDataQueue &queue, size_t pinIdx)
Write Observation to the file.
std::string _lastConnectedType
Type last connected.
Definition CsvLogger.hpp:91
bool onCreateLink(OutputPin &startPin, InputPin &endPin) override
Called when a new link is to be established.
std::string type() const override
String representation of the Class Type.
Definition CsvLogger.cpp:50
void writeHeader()
Writes the header.
bool _headerLoggingDefault
Default for new headers.
void flush() override
Function called by the flow executer after finishing to flush out remaining data.
~CsvLogger() override
Destructor.
Definition CsvLogger.cpp:40
static std::string category()
String representation of the Class Category.
Definition CsvLogger.cpp:55
std::string _headerLoggingRegex
Regex to search for when selecting.
size_t _headerLoggingCount
Amount of headers which are logged.
CsvLogger()
Default constructor.
Definition CsvLogger.cpp:25
void deinitialize() override
Deinitialize the node.
@ ASCII
Ascii text data.
FileType _fileType
File Type.
void deinitialize()
Deinitialize the file reader.
bool guiConfig(const char *vFilters, const std::vector< std::string > &extensions, size_t id, const std::string &nameId)
ImGui config.
std::filesystem::path getFilepath()
Returns the path of the file.
void restore(const json &j)
Restores the node from a json object.
json save() const
Saves the node into a json object.
bool initialize()
Initialize the file reader.
std::ofstream _filestream
File stream to write the file.
Input pins of nodes.
Definition Pin.hpp:491
TsDeque< std::shared_ptr< const NAV::NodeData > > NodeDataQueue
Node data queue type.
Definition Pin.hpp:707
static std::string type()
Returns the type of the data class.
Definition NodeData.hpp:45
bool doDeinitialize(bool wait=false)
Asks the node worker to deinitialize the node.
Definition Node.cpp:395
ImVec2 _guiConfigDefaultWindowSize
Definition Node.hpp:410
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
bool _hasConfig
Flag if the config window should be shown.
Definition Node.hpp:413
Output pins of nodes.
Definition Pin.hpp:338
auto extract_front()
Returns a copy of the first element in the container and removes it from the container.
Definition TsDeque.hpp:494
static float windowFontRatio()
Ratio to multiply for GUI window elements.
InputPin * CreateInputPin(Node *node, const char *name, Pin::Type pinType, const std::vector< std::string > &dataIdentifier={}, InputPin::Callback callback=static_cast< InputPin::FlowFirableCallbackFunc >(nullptr), InputPin::FlowFirableCheckFunc firable=nullptr, int priority=0, int idx=-1)
Create an Input Pin object.
void ApplyChanges()
Signals that there have been changes to the flow.
static std::string replaceAll_copy(std::string str, const std::string &from, const std::string &to, CaseSensitivity cs)
Replaces all occurrence of a search pattern with another sequence.
@ Flow
NodeData Trigger.
Definition Pin.hpp:52