dataload_mcap.cpp
Go to the documentation of this file.
1 #include "dataload_mcap.h"
2 
4 
5 #include "mcap/reader.hpp"
6 #include "dialog_mcap.h"
7 
8 #include <QTextStream>
9 #include <QFile>
10 #include <QMessageBox>
11 #include <QDebug>
12 #include <QSettings>
13 #include <QProgressDialog>
14 #include <QDateTime>
15 #include <QInputDialog>
16 #include <QPushButton>
17 #include <QElapsedTimer>
18 #include <QStandardItemModel>
19 #include <QtConcurrent>
20 
21 #include <set>
22 
24 {
25 }
26 
28 {
29 }
30 
31 bool DataLoadMCAP::xmlSaveState(QDomDocument& doc, QDomElement& parent_element) const
32 {
33  if (!_dialog_parameters)
34  {
35  return false;
36  }
37  QDomElement elem = doc.createElement("parameters");
38  const auto& params = *_dialog_parameters;
39  elem.setAttribute("use_timestamp", int(params.use_timestamp));
40  elem.setAttribute("use_mcap_log_time", int(params.use_mcap_log_time));
41  elem.setAttribute("clamp_large_arrays", int(params.clamp_large_arrays));
42  elem.setAttribute("max_array_size", params.max_array_size);
43  elem.setAttribute("selected_topics", params.selected_topics.join(';'));
44 
45  parent_element.appendChild(elem);
46  return true;
47 }
48 
49 bool DataLoadMCAP::xmlLoadState(const QDomElement& parent_element)
50 {
51  QDomElement elem = parent_element.firstChildElement("parameters");
52  if (elem.isNull())
53  {
54  _dialog_parameters = std::nullopt;
55  return false;
56  }
57  mcap::LoadParams params;
58  params.use_timestamp = bool(elem.attribute("use_timestamp").toInt());
59  params.use_mcap_log_time = bool(elem.attribute("use_mcap_log_time").toInt());
60  params.clamp_large_arrays = bool(elem.attribute("clamp_large_arrays").toInt());
61  params.max_array_size = elem.attribute("max_array_size").toInt();
62  params.selected_topics = elem.attribute("selected_topics").split(';');
63  _dialog_parameters = params;
64  return true;
65 }
66 
67 const std::vector<const char*>& DataLoadMCAP::compatibleFileExtensions() const
68 {
69  static std::vector<const char*> ext = { "mcap", "MCAP" };
70  return ext;
71 }
72 
74 {
75  if (!parserFactories())
76  {
77  throw std::runtime_error("No parsing available");
78  }
79 
80  // open file
81  mcap::McapReader reader;
82  auto status = reader.open(info->filename.toStdString());
83  if (!status.ok())
84  {
85  QMessageBox::warning(nullptr, "Can't open file",
86  tr("Code: %0\n Message: %1")
87  .arg(int(status.code))
88  .arg(QString::fromStdString(status.message)));
89  return false;
90  }
91 
92  status = reader.readSummary(mcap::ReadSummaryMethod::NoFallbackScan);
93  if (!status.ok())
94  {
95  QMessageBox::warning(nullptr, "Can't open summary of the file",
96  tr("Code: %0\n Message: %1")
97  .arg(int(status.code))
98  .arg(QString::fromStdString(status.message)));
99  return false;
100  }
101  plot_data.addUserDefined("plotjuggler::mcap::file_path")
102  ->second.pushBack({ 0, std::any(info->filename.toStdString()) });
103 
104  const std::optional<mcap::Statistics> statistics = reader.statistics();
105 
106  std::unordered_map<int, mcap::SchemaPtr> mcap_schemas; // schema_id
107  std::unordered_map<int, mcap::ChannelPtr> channels; // channel_id
108  std::unordered_map<int, MessageParserPtr> parsers_by_channel; // channel_id
109 
110  int total_dt_schemas = 0;
111 
112  std::unordered_set<mcap::ChannelId> channels_containing_datatamer_schema;
113  std::unordered_set<mcap::ChannelId> channels_containing_datatamer_data;
114 
115  for (const auto& [schema_id, schema_ptr] : reader.schemas())
116  {
117  mcap_schemas.insert({ schema_id, schema_ptr });
118  }
119 
120  if (!info->plugin_config.hasChildNodes())
121  {
122  _dialog_parameters = std::nullopt;
123  }
124 
125  for (const auto& [channel_id, channel_ptr] : reader.channels())
126  {
127  channels.insert({ channel_id, channel_ptr });
128  }
129 
130  // don't show the dialog if we already loaded the parameters with xmlLoadState
131  if (!_dialog_parameters)
132  {
133  std::unordered_map<uint16_t, uint64_t> msg_count;
134  if (statistics)
135  {
136  msg_count = statistics->channelMessageCounts;
137  }
138  DialogMCAP dialog(channels, mcap_schemas, msg_count, _dialog_parameters);
139  auto ret = dialog.exec();
140  if (ret != QDialog::Accepted)
141  {
142  return false;
143  }
144  _dialog_parameters = dialog.getParams();
145  }
146 
147  std::set<QString> notified_encoding_problem;
148 
149  QElapsedTimer timer;
150  timer.start();
151 
152  struct FailedParserInfo
153  {
154  std::set<std::string> topics;
155  std::string error_message;
156  };
157 
158  std::map<std::string, FailedParserInfo> parsers_blacklist;
159 
160  for (const auto& [channel_id, channel_ptr] : channels)
161  {
162  const auto& topic_name = channel_ptr->topic;
163  const QString topic_name_qt = QString::fromStdString(topic_name);
164  // skip topics that haven't been selected
165  if (!_dialog_parameters->selected_topics.contains(topic_name_qt))
166  {
167  continue;
168  }
169  const auto& schema = mcap_schemas.at(channel_ptr->schemaId);
170 
171  // check if this schema is in the blacklist
172  auto blacklist_it = parsers_blacklist.find(schema->name);
173  if (blacklist_it != parsers_blacklist.end())
174  {
175  blacklist_it->second.topics.insert(channel_ptr->topic);
176  continue;
177  }
178 
179  const std::string definition(reinterpret_cast<const char*>(schema->data.data()),
180  schema->data.size());
181 
182  if (schema->name == "data_tamer_msgs/msg/Schemas")
183  {
184  channels_containing_datatamer_schema.insert(channel_id);
185  total_dt_schemas += statistics->channelMessageCounts.at(channel_id);
186  }
187  if (schema->name == "data_tamer_msgs/msg/Snapshot")
188  {
189  channels_containing_datatamer_data.insert(channel_id);
190  }
191 
192  QString channel_encoding = QString::fromStdString(channel_ptr->messageEncoding);
193  QString schema_encoding = QString::fromStdString(schema->encoding);
194 
195  auto it = parserFactories()->find(channel_encoding);
196 
197  if (it == parserFactories()->end())
198  {
199  it = parserFactories()->find(schema_encoding);
200  }
201 
202  if (it == parserFactories()->end())
203  {
204  // show message only once per encoding type
205  if (notified_encoding_problem.count(schema_encoding) == 0)
206  {
207  notified_encoding_problem.insert(schema_encoding);
208  auto msg = QString("No parser available for encoding [%0] nor [%1]")
209  .arg(channel_encoding)
210  .arg(schema_encoding);
211  QMessageBox::warning(nullptr, "Encoding problem", msg);
212  }
213  continue;
214  }
215 
216  try
217  {
218  auto& parser_factory = it->second;
219  auto parser =
220  parser_factory->createParser(topic_name, schema->name, definition, plot_data);
221 
222  parsers_by_channel.insert({ channel_ptr->id, parser });
223  }
224  catch (std::exception& e)
225  {
226  FailedParserInfo failed_parser_info;
227  failed_parser_info.error_message = e.what();
228  failed_parser_info.topics.insert(channel_ptr->topic);
229  parsers_blacklist.insert({ schema->name, failed_parser_info });
230  }
231  };
232 
233  // If any parser failed, show a message box with the error
234  if (!parsers_blacklist.empty())
235  {
236  QString error_message;
237  for (const auto& [schema_name, failed_parser_info] : parsers_blacklist)
238  {
239  error_message += QString("Schema: %1\n").arg(QString::fromStdString(schema_name));
240  error_message += QString("Error: %1\n")
241  .arg(QString::fromStdString(failed_parser_info.error_message));
242  error_message += QString("Topics affected: \n");
243  for (const auto& topic : failed_parser_info.topics)
244  {
245  error_message += QString(" - %1\n").arg(QString::fromStdString(topic));
246  }
247  error_message += "------------------\n";
248  }
249  QMessageBox::warning(nullptr, "Parser Error", error_message);
250  }
251 
252  std::unordered_set<int> enabled_channels;
253  size_t total_msgs = 0;
254 
255  for (const auto& [channel_id, parser] : parsers_by_channel)
256  {
257  parser->setLargeArraysPolicy(_dialog_parameters->clamp_large_arrays,
258  _dialog_parameters->max_array_size);
259  parser->enableEmbeddedTimestamp(_dialog_parameters->use_timestamp);
260 
261  QString topic_name = QString::fromStdString(channels[channel_id]->topic);
262  if (_dialog_parameters->selected_topics.contains(topic_name))
263  {
264  enabled_channels.insert(channel_id);
265  auto mcap_channel = channels[channel_id]->id;
266  if (statistics->channelMessageCounts.count(mcap_channel) != 0)
267  {
268  total_msgs += statistics->channelMessageCounts.at(channel_id);
269  }
270  }
271  }
272 
273  //-------------------------------------------
274  //---------------- Parse messages -----------
275 
276  auto onProblem = [](const mcap::Status& problem) {
277  qDebug() << QString::fromStdString(problem.message);
278  };
279 
280  auto messages = reader.readMessages(onProblem);
281 
282  QProgressDialog progress_dialog("Loading... please wait", "Cancel", 0, 0, nullptr);
283  progress_dialog.setWindowTitle("Loading the MCAP file");
284  progress_dialog.setWindowModality(Qt::ApplicationModal);
285  progress_dialog.setRange(0, std::max<size_t>(total_msgs, 1) - 1);
286  progress_dialog.show();
287  progress_dialog.setValue(0);
288 
289  size_t msg_count = 0;
290 
291  for (const auto& msg_view : messages)
292  {
293  if (enabled_channels.count(msg_view.channel->id) == 0)
294  {
295  continue;
296  }
297 
298  // MCAP always represents publishTime in nanoseconds
299  double timestamp_sec = double(msg_view.message.publishTime) * 1e-9;
300  if (_dialog_parameters->use_mcap_log_time)
301  {
302  timestamp_sec = double(msg_view.message.logTime) * 1e-9;
303  }
304  auto parser_it = parsers_by_channel.find(msg_view.channel->id);
305  if (parser_it == parsers_by_channel.end())
306  {
307  qDebug() << "Skipping channeld id: " << msg_view.channel->id;
308  continue;
309  }
310 
311  auto parser = parser_it->second;
312  MessageRef msg(msg_view.message.data, msg_view.message.dataSize);
313  parser->parseMessage(msg, timestamp_sec);
314 
315  if (msg_count++ % 1000 == 0)
316  {
317  progress_dialog.setValue(msg_count);
318  QApplication::processEvents();
319  if (progress_dialog.wasCanceled())
320  {
321  break;
322  }
323  }
324  }
325 
326  reader.close();
327  qDebug() << "Loaded file in " << timer.elapsed() << "milliseconds";
328  return true;
329 }
DialogMCAP
Definition: dialog_mcap.h:25
PJ::FileLoadInfo
Definition: dataloader_base.h:18
mcap::LoadParams
Definition: dataload_params.h:8
definition
const char * definition()
DataLoadMCAP::~DataLoadMCAP
virtual ~DataLoadMCAP() override
Definition: dataload_mcap.cpp:27
dialog_mcap.h
PJ::FileLoadInfo::plugin_config
QDomDocument plugin_config
Saved configuration from a previous run or a Layout file.
Definition: dataloader_base.h:25
arg
auto arg(const Char *name, const T &arg) -> detail::named_arg< Char, T >
Definition: core.h:1875
mqtt_test_proto.msg
msg
Definition: mqtt_test_proto.py:43
dataload_mcap.h
mcap::LoadParams::max_array_size
unsigned max_array_size
Definition: dataload_params.h:11
mcap::LoadParams::clamp_large_arrays
bool clamp_large_arrays
Definition: dataload_params.h:12
start_test_publisher.topic
topic
Definition: start_test_publisher.py:31
DataLoadMCAP::xmlLoadState
bool xmlLoadState(const QDomElement &parent_element) override
Override this method to load the status of the plugin from XML.
Definition: dataload_mcap.cpp:49
DialogMCAP::getParams
mcap::LoadParams getParams() const
Definition: dialog_mcap.cpp:143
DataLoadMCAP::xmlSaveState
bool xmlSaveState(QDomDocument &doc, QDomElement &parent_element) const override
Override this method to save the status of the plugin to XML.
Definition: dataload_mcap.cpp:31
PJ::PlotDataMapRef::addUserDefined
AnySeriesMap::iterator addUserDefined(const std::string &name, PlotGroup::Ptr group={})
Definition: plotdata.cpp:57
DataLoadMCAP::DataLoadMCAP
DataLoadMCAP()
Definition: dataload_mcap.cpp:23
udp_client.parser
parser
Definition: udp_client.py:10
DataLoadMCAP::compatibleFileExtensions
virtual const std::vector< const char * > & compatibleFileExtensions() const override
Provide a list of file extensions that this plugin can open.
Definition: dataload_mcap.cpp:67
mcap::LoadParams::selected_topics
QStringList selected_topics
Definition: dataload_params.h:10
PJ::MessageRef
Definition: messageparser_base.h:28
mcap::LoadParams::use_timestamp
bool use_timestamp
Definition: dataload_params.h:13
PJ::PlotDataMapRef
Definition: plotdata.h:34
DataLoadMCAP::readDataFromFile
virtual bool readDataFromFile(PJ::FileLoadInfo *fileload_info, PlotDataMapRef &destination) override
Definition: dataload_mcap.cpp:73
messageparser_base.h
mqtt_test.ret
ret
Definition: mqtt_test.py:30
DataLoadMCAP::_dialog_parameters
std::optional< mcap::LoadParams > _dialog_parameters
Definition: dataload_mcap.h:38
PJ::DataLoader::parserFactories
const ParserFactories * parserFactories() const
Definition: dataloader_base.h:52
PJ::FileLoadInfo::filename
QString filename
name of the file to open
Definition: dataloader_base.h:21
mcap::LoadParams::use_mcap_log_time
bool use_mcap_log_time
Definition: dataload_params.h:14


plotjuggler
Author(s): Davide Faconti
autogenerated on Mon May 26 2025 02:22:36