0.5.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
19#include "NodeRegistry.hpp"
21#include "util/StringUtil.hpp"
22
24 : Node(typeStatic())
25{
26 LOG_TRACE("{}: called", name);
27
29
30 _hasConfig = true;
31 _guiConfigDefaultWindowSize = { 380, 70 };
32
33 CreateInputPin("writeObservation", Pin::Type::Flow,
34 { NodeData::type() },
36}
37
39{
40 LOG_TRACE("{}: called", nameId());
41}
42
44{
45 return "CsvLogger";
46}
47
48std::string NAV::CsvLogger::type() const
49{
50 return typeStatic();
51}
52
54{
55 return "Data Logger";
56}
57
59{
60 if (FileWriter::guiConfig(".csv", { ".csv" }, size_t(id), nameId()))
61 {
64 }
65
67 {
69 }
70
71 if (ImGui::Button(fmt::format("Clear header##{}", size_t(id)).c_str()))
72 {
73 _headerLogging.clear();
74 }
75 ImGui::SameLine();
76 if (ImGui::Button(fmt::format("Select all##{}", size_t(id)).c_str()))
77 {
78 for (auto& header : _headerLogging) { header.second = true; }
80 }
81 ImGui::SameLine();
82 if (ImGui::Button(fmt::format("Deselect all##{}", size_t(id)).c_str()))
83 {
84 for (auto& header : _headerLogging) { header.second = false; }
86 }
87 ImGui::SameLine();
88 if (ImGui::Checkbox(fmt::format("Default for new##{}", size_t(id)).c_str(), &_headerLoggingDefault))
89 {
91 }
92 ImGui::SameLine();
93 if (ImGui::Checkbox(fmt::format("Sort headers in GUI##{}", size_t(id)).c_str(), &_headerLoggingSortGui))
94 {
96 }
97
98 if (_headerLoggingRegex.empty()) { ImGui::BeginDisabled(); }
99 std::optional<bool> regexSelect;
100 if (ImGui::Button(fmt::format("Select regex##{}", size_t(id)).c_str()))
101 {
102 regexSelect = true;
103 }
104 ImGui::SameLine();
105 if (ImGui::Button(fmt::format("Deselect regex##{}", size_t(id)).c_str()))
106 {
107 regexSelect = false;
108 }
109 if (regexSelect.has_value())
110 {
111 bool anyChanged = false;
112 try
113 {
114 for (auto& [desc, checked] : _headerLogging)
115 {
116 std::regex self_regex(_headerLoggingRegex,
117 std::regex_constants::ECMAScript | std::regex_constants::icase);
118 if (std::regex_search(desc, self_regex) && checked != *regexSelect)
119 {
120 anyChanged = true;
121 checked = *regexSelect;
122 }
123 }
124 }
125 catch (const std::regex_error& e)
126 {
127 LOG_ERROR("Regex could not be parsed: {}", e.what());
128 }
129 if (anyChanged)
130 {
132 }
133 }
134 if (_headerLoggingRegex.empty()) { ImGui::EndDisabled(); }
135 ImGui::SameLine();
136 ImGui::SetNextItemWidth(300.0F * gui::NodeEditorApplication::windowFontRatio());
137 if (ImGui::InputText(fmt::format("##Select Regex {}", size_t(id)).c_str(), &_headerLoggingRegex))
138 {
140 }
141
142 if (!_headerLogging.empty())
143 {
144 auto* headerLogging = &_headerLogging;
145 decltype(_headerLogging) sortedHeaderLogging;
147 {
148 sortedHeaderLogging = _headerLogging;
149 std::ranges::sort(sortedHeaderLogging);
150 headerLogging = &sortedHeaderLogging;
151 }
152 int nCols = std::min((static_cast<int>(headerLogging->size()) - 1) / 5 + 1, 3);
153 if (ImGui::BeginChild(fmt::format("Headers Scrolling {}", size_t(id)).c_str(), ImGui::GetContentRegionAvail(), false))
154 {
155 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)))
156 {
157 for (auto& [desc, checked] : *headerLogging)
158 {
159 ImGui::TableNextColumn();
160 if (ImGui::Checkbox(fmt::format("{}##{}", desc, size_t(id)).c_str(), &checked))
161 {
163 {
164 if (auto iter = std::ranges::find_if(_headerLogging, [&](const std::pair<std::string, bool>& header) {
165 return desc == header.first; // NOLINT(clang-analyzer-core.CallAndMessage)
166 });
167 iter != _headerLogging.end())
168 {
169 iter->second = checked;
170 }
171 }
173 }
174 }
175
176 ImGui::EndTable();
177 }
178 }
179 ImGui::EndChild();
180 }
181 else
182 {
183 ImGui::TextUnformatted("Please run the flow to collect information about the available data.");
184 }
185}
186
187[[nodiscard]] json NAV::CsvLogger::save() const
188{
189 LOG_TRACE("{}: called", nameId());
190
191 return {
192 { "FileWriter", FileWriter::save() },
193 { "header", _headerLogging },
194 { "lastConnectedType", _lastConnectedType },
195 { "headerLoggingRegex", _headerLoggingRegex },
196 { "headerLoggingDefault", _headerLoggingDefault },
197 { "headerLoggingSortGui", _headerLoggingSortGui },
198 };
199}
200
202{
203 LOG_TRACE("{}: called", nameId());
204
205 if (j.contains("FileWriter")) { FileWriter::restore(j.at("FileWriter")); }
206 if (j.contains("header")) { j.at("header").get_to(_headerLogging); }
207 if (j.contains("lastConnectedType")) { j.at("lastConnectedType").get_to(_lastConnectedType); }
208 if (j.contains("headerLoggingRegex")) { j.at("headerLoggingRegex").get_to(_headerLoggingRegex); }
209 if (j.contains("headerLoggingDefault")) { j.at("headerLoggingDefault").get_to(_headerLoggingDefault); }
210}
211
213{
214 _filestream.flush();
215}
216
217bool NAV::CsvLogger::onCreateLink([[maybe_unused]] OutputPin& startPin, [[maybe_unused]] InputPin& endPin)
218{
219 LOG_TRACE("{}: called for {} ==> {}", nameId(), size_t(startPin.id), size_t(endPin.id));
220
221 if (_lastConnectedType != startPin.dataIdentifier.front())
222 {
223 LOG_DEBUG("{}: [{} ==> {}] Dropping headers because last type [{}] and new type [{}]", nameId(), size_t(startPin.id), size_t(endPin.id), _lastConnectedType, startPin.dataIdentifier.front());
224 _headerLogging.clear();
225 }
226 _lastConnectedType = startPin.dataIdentifier.front();
227
228 return true;
229}
230
232{
233 LOG_TRACE("{}: called", nameId());
234
236 {
237 return false;
238 }
239
241
242 _headerWritten = false;
243 _dynamicHeader.clear();
244
245 return true;
246}
247
249{
250 LOG_TRACE("{}: called", nameId());
251
253}
254
256{
257 _filestream << "Time [s],GpsCycle,GpsWeek,GpsToW [s]";
258
259#if LOG_LEVEL <= LOG_LEVEL_TRACE
260 std::string headers = "Time [s],GpsCycle,GpsWeek,GpsToW [s]";
261#endif
262
264 for (const auto& [desc, enabled] : _headerLogging)
265 {
266 if (!enabled) { continue; }
268
269 auto header = str::replaceAll_copy(desc, ",", "_");
270#if LOG_LEVEL <= LOG_LEVEL_TRACE
271 headers += "," + header;
272#endif
273 _filestream << "," << header;
274 }
275 _filestream << std::endl; // NOLINT(performance-avoid-endl)
276
277#if LOG_LEVEL <= LOG_LEVEL_TRACE
278 LOG_TRACE("{}: Header written:\n{}", nameId(), headers);
279#endif
280
281 _headerWritten = true;
282}
283
284void NAV::CsvLogger::rewriteData(size_t oldSize, size_t newSize)
285{
286 LOG_TRACE("{}: Rewriting header, because {} new elements", nameId(), newSize - oldSize);
288 auto tmpFilePath = getFilepath().concat("_temp");
289 std::filesystem::rename(getFilepath(), tmpFilePath);
291 writeHeader();
292
293 std::ifstream tmpFilestream(tmpFilePath, std::ios_base::in | std::ios_base::binary);
294 if (tmpFilestream.good())
295 {
296 std::string delimiterEnd(newSize - oldSize, ',');
297 std::string line;
298 std::getline(tmpFilestream, line); // Old header
299 while (std::getline(tmpFilestream, line) && !tmpFilestream.eof())
300 {
301 _filestream << line << delimiterEnd << '\n';
302 }
303 }
304 if (tmpFilestream.is_open()) { tmpFilestream.close(); }
305 tmpFilestream.clear();
306 std::filesystem::remove(tmpFilePath);
307}
308
310{
311 auto obs = queue.extract_front();
312
313 auto oldHeaderLength = static_cast<size_t>(std::ranges::count_if(_headerLogging, [](const auto& header) { return header.second; }));
314 if (!_headerWritten)
315 {
316 for (const auto& desc : obs->staticDataDescriptors())
317 {
318 if (auto iter = std::ranges::find_if(_headerLogging, [&](const std::pair<std::string, bool>& header) {
319 return desc == header.first;
320 });
321 iter == _headerLogging.end())
322 {
323 _headerLogging.emplace_back(desc, _headerLoggingDefault);
325 }
326 }
327 }
328 for (const auto& desc : obs->dynamicDataDescriptors())
329 {
330 if (std::ranges::none_of(_dynamicHeader, [&](const auto& header) { return header == desc; }))
331 {
332 LOG_DATA("{}: Adding new dynamic header: {}", nameId(), desc);
333 _dynamicHeader.push_back(desc);
334 if (auto iter = std::ranges::find_if(_headerLogging, [&](const std::pair<std::string, bool>& header) {
335 return desc == header.first;
336 });
337 iter == _headerLogging.end())
338 {
339 _headerLogging.emplace_back(desc, _headerLoggingDefault);
341 }
342 }
343 }
344
345 if (!_headerWritten) { writeHeader(); }
346 else if (auto newHeaderLength = static_cast<size_t>(std::ranges::count_if(_headerLogging, [](const auto& header) { return header.second; }));
347 oldHeaderLength != newHeaderLength)
348 {
349 rewriteData(oldHeaderLength, newHeaderLength);
350 }
351
352 constexpr int gpsCyclePrecision = 3;
353 constexpr int gpsTimePrecision = 12;
354 constexpr int valuePrecision = 15;
355
356 if (!obs->insTime.empty())
357 {
358 _filestream << std::setprecision(valuePrecision) << std::round(calcTimeIntoRun(obs->insTime) * 1e9) / 1e9;
359 }
360 _filestream << ",";
361 if (!obs->insTime.empty())
362 {
363 _filestream << std::fixed << std::setprecision(gpsCyclePrecision) << obs->insTime.toGPSweekTow().gpsCycle;
364 }
365 _filestream << ",";
366 if (!obs->insTime.empty())
367 {
368 _filestream << std::defaultfloat << std::setprecision(gpsTimePrecision) << obs->insTime.toGPSweekTow().gpsWeek;
369 }
370 _filestream << ",";
371 if (!obs->insTime.empty())
372 {
373 _filestream << std::defaultfloat << std::setprecision(gpsTimePrecision) << obs->insTime.toGPSweekTow().tow;
374 }
375 _filestream << std::setprecision(valuePrecision);
376
377 size_t dataLogged = 0;
378 const auto staticDataDescriptors = obs->staticDataDescriptors();
379 for (size_t i = 0; i < obs->staticDescriptorCount(); ++i)
380 {
381 const auto& desc = staticDataDescriptors.at(i);
382 if (auto iter = std::ranges::find_if(_headerLogging, [&](const std::pair<std::string, bool>& header) {
383 return desc == header.first;
384 });
385 iter != _headerLogging.end() && !iter->second)
386 {
387 continue;
388 }
389 dataLogged++;
390 _filestream << ',';
391 if (auto val = obs->getValueAt(i)) { _filestream << *val; }
392 }
393
394 for (const auto& desc : _dynamicHeader)
395 {
396 if (auto iter = std::ranges::find_if(_headerLogging, [&](const std::pair<std::string, bool>& header) {
397 return desc == header.first;
398 });
399 iter != _headerLogging.end() && !iter->second)
400 {
401 continue;
402 }
403 dataLogged++;
404 _filestream << ',';
405 if (auto val = obs->getDynamicDataAt(desc)) { _filestream << *val; }
406 }
407 for (size_t i = dataLogged; i < _headerLoggingCount; i++)
408 {
409 _filestream << ',';
410 }
411
412 _filestream << '\n';
413}
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.
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:58
bool _headerLoggingSortGui
Sort headers in the GUI.
static std::string typeStatic()
String representation of the Class Type.
Definition CsvLogger.cpp:43
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:48
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:38
static std::string category()
String representation of the Class Category.
Definition CsvLogger.cpp:53
std::string _headerLoggingRegex
Regex to search for when selecting.
size_t _headerLoggingCount
Amount of headers which are logged.
CsvLogger()
Default constructor.
Definition CsvLogger.cpp:23
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:465
ImVec2 _guiConfigDefaultWindowSize
Definition Node.hpp:522
Node(std::string name)
Constructor.
Definition Node.cpp:29
std::string nameId() const
Node name and id.
Definition Node.cpp:323
std::string name
Name of the Node.
Definition Node.hpp:507
InputPin * CreateInputPin(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.
Definition Node.cpp:252
bool _hasConfig
Flag if the config window should be shown.
Definition Node.hpp:525
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.
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