rs-data-collect.cpp
Go to the documentation of this file.
1 // License: Apache 2.0. See LICENSE file in root directory.
2 // Copyright(c) 2018 Intel Corporation. All Rights Reserved.
3 // Minimalistic command-line collect & analyze bandwidth/performance tool for Realsense Cameras.
4 // The data is gathered and serialized in csv-compatible format for offline analysis.
5 // Extract and store frame headers info for video streams; for IMU&Tracking streams also store the actual data
6 // The utility is configured with command-line keys and requires user-provided config file to run
7 // See rs-data-collect.h for config examples
8 
9 #include <librealsense2/rs.hpp>
10 #include "rs-data-collect.h"
11 #include "tclap/CmdLine.h"
12 #include <thread>
13 #include <regex>
14 #include <iostream>
15 #include <algorithm>
16 
17 using namespace std;
18 using namespace TCLAP;
19 using namespace rs_data_collect;
20 
21 
22 data_collector::data_collector(std::shared_ptr<rs2::device> dev,
23  ValueArg<int>& timeout, ValueArg<int>& max_frames) : _dev(dev)
24 {
25  _max_frames = max_frames.isSet() ? max_frames.getValue() :
26  timeout.isSet() ? std::numeric_limits<uint64_t>::max() : DEF_FRAMES_NUMBER;
27  _time_out_sec = timeout.isSet() ? timeout.getValue() : -1;
28 
29  _stop_cond = static_cast<application_stop>((int(max_frames.isSet()) << 1) + int(timeout.isSet()));
30 }
31 
33 {
34  if (!config_file.isSet())
35  throw std::runtime_error(stringify()
36  << "The tool requires a profile configuration file to proceed"
37  << "\nRun rs-data-collect --help for details");
38 
39  const std::string config_filename(config_file.getValue());
40  ifstream file(config_filename);
41 
42  if (!file.is_open())
43  throw runtime_error(stringify() << "Given .csv configure file " << config_filename << " was not found!");
44 
45  // Line starting with non-alpha characters will be treated as comments
46  const static std::regex starts_with("^[a-zA-Z]");
47  string line;
48  rs2_stream stream_type;
50  int width{}, height{}, fps{}, index{};
51 
52  // Parse the config file
53  while (getline(file, line))
54  {
55  auto tokens = tokenize(line, ',');
56 
57  if (std::regex_search(line, starts_with))
58  {
59  if (parse_configuration(line, tokens, stream_type, width, height, format, fps, index))
60  user_requests.push_back({ stream_type, format, width, height, fps, index });
61  }
62  }
63 
64  // Sanity test agains multiple conflicting requests for the same sensor
65  if (user_requests.size())
66  {
67  std::sort(user_requests.begin(), user_requests.end(),
68  [](const stream_request& l, const stream_request& r) { return l._stream_type < r._stream_type; });
69 
70  for (auto i = 0; i < user_requests.size() - 1; i++)
71  {
72  if ((user_requests[i]._stream_type == user_requests[i + 1]._stream_type) && ((user_requests[i]._stream_idx == user_requests[i + 1]._stream_idx)))
73  throw runtime_error(stringify() << "Invalid configuration file - multiple requests for the same sensor:\n"
74  << user_requests[i] << user_requests[+i]);
75  }
76  }
77  else
78  throw std::runtime_error(stringify() << "Invalid configuration file - " << config_filename << " zero requests accepted");
79 
80  // Assign user profile to actual sensors
82 
83  // Report results
84  std::cout << "\nDevice selected: \n\t" << _dev->get_info(RS2_CAMERA_INFO_NAME)
86  std::string((stringify() << ". USB Type: " << _dev->get_info(RS2_CAMERA_INFO_USB_TYPE_DESCRIPTOR))) : "")
87  << "\n\tS.N: " << (_dev->supports(RS2_CAMERA_INFO_SERIAL_NUMBER) ? _dev->get_info(RS2_CAMERA_INFO_SERIAL_NUMBER) : "")
88  << "\n\tFW Ver: " << _dev->get_info(RS2_CAMERA_INFO_FIRMWARE_VERSION)
89  << "\n\nUser streams requested: " << user_requests.size()
90  << ", actual matches: " << selected_stream_profiles.size() << std::endl;
91 
92  if (requests_to_go.size())
93  {
94  std::cout << "\nThe following request(s) are not supported by the device: " << std::endl;
95  for (auto& elem : requests_to_go)
96  std::cout << elem;
97  }
98 }
99 
100 void data_collector::save_data_to_file(const string& out_filename)
101 {
102  if (!data_collection.size())
103  throw runtime_error(stringify() << "No data collected, aborting");
104 
105  // Report amount of frames collected
106  std::vector<uint64_t> frames_per_stream;
107  for (const auto& kv : data_collection)
108  frames_per_stream.emplace_back(kv.second.size());
109 
110  std::sort(frames_per_stream.begin(), frames_per_stream.end());
111  std::cout << "\nData collection accomplished with ["
112  << frames_per_stream.front() << "-" << frames_per_stream.back()
113  << "] frames recorded per stream\nSerializing captured results to "
114  << out_filename << std::endl;
115 
116  // Serialize and store data into csv-like format
117  ofstream csv(out_filename);
118  if (!csv.is_open())
119  throw runtime_error(stringify() << "Cannot open the requested output file " << out_filename << ", please check permissions");
120 
121  csv << "Configuration:\nStream Type,Stream Name,Format,FPS,Width,Height\n";
122  for (const auto& elem : selected_stream_profiles)
123  csv << get_profile_description(elem);
124 
125  for (const auto& elem : data_collection)
126  {
127  csv << "\n\nStream Type,Index,F#,HW Timestamp (ms),Host Timestamp(ms)"
128  << (val_in_range(elem.first.first, { RS2_STREAM_GYRO,RS2_STREAM_ACCEL }) ? ",3DOF_x,3DOF_y,3DOF_z" : "")
129  << (val_in_range(elem.first.first, { RS2_STREAM_POSE }) ? ",t_x,t_y,t_z,r_x,r_y,r_z,r_w" : "")
130  << std::endl;
131 
132  for (auto i = 0; i < elem.second.size(); i++)
133  csv << elem.second[i].to_string();
134  }
135 }
136 
137 void data_collector::collect_frame_attributes(rs2::frame f, std::chrono::time_point<std::chrono::high_resolution_clock> start_time)
138 {
139  auto arrival_time = std::chrono::duration<double, std::milli>(chrono::high_resolution_clock::now() - start_time);
140  auto stream_uid = std::make_pair(f.get_profile().stream_type(), f.get_profile().stream_index());
141 
142  if (data_collection[stream_uid].size() < _max_frames)
143  {
145  f.get_timestamp(),
146  arrival_time.count(),
148  f.get_profile().stream_type(),
149  f.get_profile().stream_index() };
150 
151  // Assume that the frame extensions are unique
152  if (auto motion = f.as<rs2::motion_frame>())
153  {
154  auto axes = motion.get_motion_data();
155  rec._params = { axes.x, axes.y, axes.z };
156  }
157 
158  if (auto pf = f.as<rs2::pose_frame>())
159  {
160  auto pose = pf.get_pose_data();
161  rec._params = { pose.translation.x, pose.translation.y, pose.translation.z,
162  pose.rotation.x,pose.rotation.y,pose.rotation.z,pose.rotation.w };
163  }
164 
165  data_collection[stream_uid].emplace_back(rec);
166  }
167 }
168 
169 bool data_collector::collecting(std::chrono::time_point<std::chrono::high_resolution_clock> start_time)
170 {
171  bool timed_out = false;
172 
173  if (_time_out_sec > 0)
174  {
175  timed_out = (chrono::high_resolution_clock::now() - start_time) > std::chrono::seconds(_time_out_sec);
176  // When the timeout is the only option is specified, disregard frame number
178  return !timed_out;
179  }
180 
181  bool collected_enough_frames = true;
182  for (auto&& profile : selected_stream_profiles)
183  {
184  auto key = std::make_pair(profile.stream_type(), profile.stream_index());
185  if (!data_collection.size() || (data_collection.find(key) != data_collection.end() &&
186  (data_collection[key].size() && data_collection[key].size() < _max_frames)))
187  {
188  collected_enough_frames = false;
189  break;
190  }
191  }
192 
193  return !(timed_out || collected_enough_frames);
194 
195 }
196 
197 bool data_collector::parse_configuration(const std::string& line, const std::vector<std::string>& tokens,
198  rs2_stream& type, int& width, int& height, rs2_format& format, int& fps, int& index)
199 {
200  bool res = false;
201  try
202  {
203  auto tokens = tokenize(line, ',');
204  if (tokens.size() < e_stream_index)
205  return res;
206 
207  // Convert string to uppercase
208  type = parse_stream_type(to_lower(tokens[e_stream_type]));
209  width = parse_number(tokens[e_res_width].c_str());
210  height = parse_number(tokens[e_res_height].c_str());
211  fps = parse_fps(tokens[e_fps]);
212  format = parse_format(to_lower(tokens[e_format]));
213  // Backward compatibility
214  if (tokens.size() > e_stream_index)
215  index = parse_number(tokens[e_stream_index].c_str());
216  res = true;
217  std::cout << "Request added for " << line << std::endl;
218  }
219  catch (...)
220  {
221  std::cout << "Invalid syntax in configuration line " << line << std::endl;
222  }
223 
224  return res;
225 }
226 
227 // Assign the user configuration to the selected device
229 {
230  bool succeed = false;
232  std::vector<rs2::stream_profile> matches;
233 
234  // Configure and starts streaming
235  for (auto&& sensor : _dev->query_sensors())
236  {
237  for (auto& profile : sensor.get_stream_profiles())
238  {
239  // All requests have been resolved
240  if (!requests_to_go.size())
241  break;
242 
243  // Find profile matches
244  auto fulfilled_request = std::find_if(requests_to_go.begin(), requests_to_go.end(), [&matches, profile](const stream_request& req)
245  {
246  bool res = false;
247  if ((profile.stream_type() == req._stream_type) &&
248  (profile.format() == req._stream_format) &&
249  (profile.stream_index() == req._stream_idx) &&
250  (profile.fps() == req._fps))
251  {
252  if (auto vp = profile.as<rs2::video_stream_profile>())
253  {
254  if ((vp.width() != req._width) || (vp.height() != req._height))
255  return false;
256  }
257  res = true;
258  matches.push_back(profile);
259  }
260 
261  return res;
262  });
263 
264  // Remove the request once resolved
265  if (fulfilled_request != requests_to_go.end())
266  requests_to_go.erase(fulfilled_request);
267  }
268 
269  // Aggregate resolved requests
270  if (matches.size())
271  {
272  std::copy(matches.begin(), matches.end(), std::back_inserter(selected_stream_profiles));
273  sensor.open(matches);
274  active_sensors.emplace_back(sensor);
275  matches.clear();
276  }
277 
278  if (selected_stream_profiles.size() == user_requests.size())
279  succeed = true;
280  }
281  return succeed;
282 }
283 
284 int main(int argc, char** argv) try
285 {
286 
287 #ifdef BUILD_EASYLOGGINGPP
289 #endif
290 
291  // Parse command line arguments
292  CmdLine cmd("librealsense rs-data-collect example tool", ' ');
293  ValueArg<int> timeout("t", "Timeout", "Max amount of time to receive frames (in seconds)", false, 10, "");
294  ValueArg<int> max_frames("m", "MaxFrames_Number", "Maximum number of frames-per-stream to receive", false, 100, "");
295  ValueArg<string> out_file("f", "FullFilePath", "the file where the data will be saved to", false, "", "");
296  ValueArg<string> config_file("c", "ConfigurationFile", "Specify file path with the requested configuration", false, "", "");
297 
298  cmd.add(timeout);
299  cmd.add(max_frames);
300  cmd.add(out_file);
301  cmd.add(config_file);
302  cmd.parse(argc, argv);
303 
304  std::cout << "Running rs-data-collect: ";
305  for (auto i=1; i < argc; ++i)
306  std::cout << argv[i] << " ";
307  std::cout << std::endl << std::endl;
308 
309  auto output_file = out_file.isSet() ? out_file.getValue() : DEF_OUTPUT_FILE_NAME;
310 
311  {
312  ofstream csv(output_file);
313  if (!csv.is_open())
314  throw runtime_error(stringify() << "Cannot open the requested output file " << output_file << ", please check permissions");
315  }
316 
317  bool succeed = false;
319  rs2::device_list list;
320 
321  while (!succeed)
322  {
323  list = ctx.query_devices();
324 
325  if (0== list.size())
326  {
327  std::cout << "Connect Realsense Camera to proceed" << std::endl;
328  std::this_thread::sleep_for(std::chrono::seconds(3));
329  continue;
330  }
331 
332  auto dev = std::make_shared<rs2::device>(list.front());
333 
334  data_collector dc(dev,timeout,max_frames); // Parser and the data aggregator
335 
336  dc.parse_and_configure(config_file);
337 
338  //data_collection buffer;
339  auto start_time = chrono::high_resolution_clock::now();
340 
341  // Start streaming
342  for (auto&& sensor : dc.selected_sensors())
343  sensor.start([&dc,&start_time](rs2::frame f)
344  {
345  dc.collect_frame_attributes(f,start_time);
346  });
347 
348  std::cout << "\nData collection started.... \n" << std::endl;
349 
350  while (dc.collecting(start_time))
351  {
352  std::this_thread::sleep_for(std::chrono::seconds(1));
353  std::cout << "Collecting data for "
354  << chrono::duration_cast<chrono::seconds>(chrono::high_resolution_clock::now() - start_time).count()
355  << " sec" << std::endl;
356  }
357 
358  // Stop & flush all active sensors
359  for (auto&& sensor : dc.selected_sensors())
360  {
361  sensor.stop();
362  sensor.close();
363  }
364 
365  dc.save_data_to_file(output_file);
366 
367  succeed = true;
368  }
369 
370  std::cout << "Task completed" << std::endl;
371 
372  return EXIT_SUCCESS;
373 }
374 catch (const rs2::error & e)
375 {
376  cerr << "RealSense error calling " << e.get_failed_function() << "(" << e.get_failed_args() << "):\n " << e.what() << endl;
377  return EXIT_FAILURE;
378 }
379 catch (const exception & e)
380 {
381  cerr << e.what() << endl;
382  return EXIT_FAILURE;
383 }
int parse_number(char const *s, int base=0)
void save_data_to_file(const string &out_filename)
device front() const
Definition: rs_device.hpp:719
std::vector< std::string > tokenize(std::string line, char separator)
std::string to_lower(const std::string &s)
def axes(out, pos, rotation=np.eye(3), size=0.075, thickness=2)
std::vector< rs2::sensor > active_sensors
void collect_frame_attributes(rs2::frame f, std::chrono::time_point< std::chrono::high_resolution_clock > start_time)
stream_profile get_profile() const
Definition: rs_frame.hpp:557
device_list query_devices() const
Definition: rs_context.hpp:112
uint32_t size() const
Definition: rs_device.hpp:711
void parse_and_configure(ValueArg< string > &config_file)
matches
Definition: test-fg.py:19
bool collecting(std::chrono::time_point< std::chrono::high_resolution_clock > start_time)
GLsizei const GLchar *const * string
e
Definition: rmse.py:177
std::string get_profile_description(const rs2::stream_profile &profile)
std::string stringify(const T &e)
Definition: catch.hpp:1648
void sort(sort_type m_sort_type, const std::string &in, const std::string &out)
::realsense_legacy_msgs::pose_< std::allocator< void > > pose
Definition: pose.h:88
GLuint index
GLuint64 key
Definition: glext.h:8966
const std::string & get_failed_args() const
Definition: rs_types.hpp:117
GLdouble f
rs2_stream parse_stream_type(const string str)
GLsizeiptr size
std::ostream & cout()
GLdouble GLdouble r
double get_timestamp() const
Definition: rs_frame.hpp:474
std::vector< rs2::stream_profile > selected_stream_profiles
std::map< std::pair< rs2_stream, int >, std::vector< frame_record > > data_collection
GLint GLsizei GLsizei height
int parse_fps(const string str)
GLint GLint GLsizei GLint GLenum format
bool isSet() const
Definition: Arg.h:575
rs2_format
A stream&#39;s format identifies how binary data is encoded within a frame.
Definition: rs_sensor.h:59
int stream_index() const
Definition: rs_frame.hpp:34
void log_to_file(rs2_log_severity min_severity, const char *file_path=nullptr)
Definition: rs.hpp:26
rs2_format parse_format(const string str)
rs2_stream
Streams are different types of data provided by RealSense devices.
Definition: rs_sensor.h:42
T & getValue()
Definition: ValueArg.h:322
bool parse_configuration(const std::string &line, const std::vector< std::string > &tokens, rs2_stream &type, int &width, int &height, rs2_format &format, int &fps, int &index)
int main(int argc, char **argv)
void parse(int argc, const char *const *argv)
Definition: CmdLine.h:433
GLbitfield GLuint64 timeout
GLenum type
rs2_timestamp_domain get_frame_timestamp_domain() const
Definition: rs_frame.hpp:485
const std::string DEF_OUTPUT_FILE_NAME("frames_data.csv")
bool val_in_range(const T &val, const std::initializer_list< T > &list)
GLint GLsizei count
std::shared_ptr< rs2::device > _dev
std::ostream & cerr()
void add(Arg &a)
Definition: CmdLine.h:413
bool starts_with(const std::string &s, const std::string &prefix)
Definition: os.cpp:343
int i
GLuint res
Definition: glext.h:8856
std::vector< stream_request > user_requests
const uint64_t DEF_FRAMES_NUMBER
rs2_format format
Definition: Arg.h:57
unsigned long long get_frame_number() const
Definition: rs_frame.hpp:521
std::vector< stream_request > requests_to_go
rs2_stream stream_type() const
Definition: rs_frame.hpp:39
const std::string & get_failed_function() const
Definition: rs_types.hpp:112
GLint GLsizei width
void copy(void *dst, void const *src, size_t size)
Definition: types.cpp:836
T as() const
Definition: rs_frame.hpp:580


librealsense2
Author(s): Sergey Dorodnicov , Doron Hirshberg , Mark Horn , Reagan Lopez , Itay Carpis
autogenerated on Mon May 3 2021 02:47:40