You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

384 lines
14 KiB

// License: Apache 2.0. See LICENSE file in root directory.
// Copyright(c) 2018 Intel Corporation. All Rights Reserved.
// Minimalistic command-line collect & analyze bandwidth/performance tool for Realsense Cameras.
// The data is gathered and serialized in csv-compatible format for offline analysis.
// Extract and store frame headers info for video streams; for IMU&Tracking streams also store the actual data
// The utility is configured with command-line keys and requires user-provided config file to run
// See rs-data-collect.h for config examples
#include <librealsense2/rs.hpp>
#include "rs-data-collect.h"
#include "tclap/CmdLine.h"
#include <thread>
#include <regex>
#include <iostream>
#include <algorithm>
using namespace std;
using namespace TCLAP;
using namespace rs_data_collect;
data_collector::data_collector(std::shared_ptr<rs2::device> dev,
ValueArg<int>& timeout, ValueArg<int>& max_frames) : _dev(dev)
{
_max_frames = max_frames.isSet() ? max_frames.getValue() :
timeout.isSet() ? std::numeric_limits<uint64_t>::max() : DEF_FRAMES_NUMBER;
_time_out_sec = timeout.isSet() ? timeout.getValue() : -1;
_stop_cond = static_cast<application_stop>((int(max_frames.isSet()) << 1) + int(timeout.isSet()));
}
void data_collector::parse_and_configure(ValueArg<string>& config_file)
{
if (!config_file.isSet())
throw std::runtime_error(stringify()
<< "The tool requires a profile configuration file to proceed"
<< "\nRun rs-data-collect --help for details");
const std::string config_filename(config_file.getValue());
ifstream file(config_filename);
if (!file.is_open())
throw runtime_error(stringify() << "Given .csv configure file " << config_filename << " was not found!");
// Line starting with non-alpha characters will be treated as comments
const static std::regex starts_with("^[a-zA-Z]");
string line;
rs2_stream stream_type;
rs2_format format;
int width{}, height{}, fps{}, index{};
// Parse the config file
while (getline(file, line))
{
auto tokens = tokenize(line, ',');
if (std::regex_search(line, starts_with))
{
if (parse_configuration(line, tokens, stream_type, width, height, format, fps, index))
user_requests.push_back({ stream_type, format, width, height, fps, index });
}
}
// Sanity test agains multiple conflicting requests for the same sensor
if (user_requests.size())
{
std::sort(user_requests.begin(), user_requests.end(),
[](const stream_request& l, const stream_request& r) { return l._stream_type < r._stream_type; });
for (auto i = 0; i < user_requests.size() - 1; i++)
{
if ((user_requests[i]._stream_type == user_requests[i + 1]._stream_type) && ((user_requests[i]._stream_idx == user_requests[i + 1]._stream_idx)))
throw runtime_error(stringify() << "Invalid configuration file - multiple requests for the same sensor:\n"
<< user_requests[i] << user_requests[+i]);
}
}
else
throw std::runtime_error(stringify() << "Invalid configuration file - " << config_filename << " zero requests accepted");
// Assign user profile to actual sensors
configure_sensors();
// Report results
std::cout << "\nDevice selected: \n\t" << _dev->get_info(RS2_CAMERA_INFO_NAME)
<< (_dev->supports(RS2_CAMERA_INFO_USB_TYPE_DESCRIPTOR) ?
std::string((stringify() << ". USB Type: " << _dev->get_info(RS2_CAMERA_INFO_USB_TYPE_DESCRIPTOR))) : "")
<< "\n\tS.N: " << (_dev->supports(RS2_CAMERA_INFO_SERIAL_NUMBER) ? _dev->get_info(RS2_CAMERA_INFO_SERIAL_NUMBER) : "")
<< "\n\tFW Ver: " << _dev->get_info(RS2_CAMERA_INFO_FIRMWARE_VERSION)
<< "\n\nUser streams requested: " << user_requests.size()
<< ", actual matches: " << selected_stream_profiles.size() << std::endl;
if (requests_to_go.size())
{
std::cout << "\nThe following request(s) are not supported by the device: " << std::endl;
for (auto& elem : requests_to_go)
std::cout << elem;
}
}
void data_collector::save_data_to_file(const string& out_filename)
{
if (!data_collection.size())
throw runtime_error(stringify() << "No data collected, aborting");
// Report amount of frames collected
std::vector<uint64_t> frames_per_stream;
for (const auto& kv : data_collection)
frames_per_stream.emplace_back(kv.second.size());
std::sort(frames_per_stream.begin(), frames_per_stream.end());
std::cout << "\nData collection accomplished with ["
<< frames_per_stream.front() << "-" << frames_per_stream.back()
<< "] frames recorded per stream\nSerializing captured results to "
<< out_filename << std::endl;
// Serialize and store data into csv-like format
ofstream csv(out_filename);
if (!csv.is_open())
throw runtime_error(stringify() << "Cannot open the requested output file " << out_filename << ", please check permissions");
csv << "Configuration:\nStream Type,Stream Name,Format,FPS,Width,Height\n";
for (const auto& elem : selected_stream_profiles)
csv << get_profile_description(elem);
for (const auto& elem : data_collection)
{
csv << "\n\nStream Type,Index,F#,HW Timestamp (ms),Host Timestamp(ms)"
<< (val_in_range(elem.first.first, { RS2_STREAM_GYRO,RS2_STREAM_ACCEL }) ? ",3DOF_x,3DOF_y,3DOF_z" : "")
<< (val_in_range(elem.first.first, { RS2_STREAM_POSE }) ? ",t_x,t_y,t_z,r_x,r_y,r_z,r_w" : "")
<< std::endl;
for (auto i = 0; i < elem.second.size(); i++)
csv << elem.second[i].to_string();
}
}
void data_collector::collect_frame_attributes(rs2::frame f, std::chrono::time_point<std::chrono::high_resolution_clock> start_time)
{
auto arrival_time = std::chrono::duration<double, std::milli>(chrono::high_resolution_clock::now() - start_time);
auto stream_uid = std::make_pair(f.get_profile().stream_type(), f.get_profile().stream_index());
if (data_collection[stream_uid].size() < _max_frames)
{
frame_record rec{ f.get_frame_number(),
f.get_timestamp(),
arrival_time.count(),
f.get_frame_timestamp_domain(),
f.get_profile().stream_type(),
f.get_profile().stream_index() };
// Assume that the frame extensions are unique
if (auto motion = f.as<rs2::motion_frame>())
{
auto axes = motion.get_motion_data();
rec._params = { axes.x, axes.y, axes.z };
}
if (auto pf = f.as<rs2::pose_frame>())
{
auto pose = pf.get_pose_data();
rec._params = { pose.translation.x, pose.translation.y, pose.translation.z,
pose.rotation.x,pose.rotation.y,pose.rotation.z,pose.rotation.w };
}
data_collection[stream_uid].emplace_back(rec);
}
}
bool data_collector::collecting(std::chrono::time_point<std::chrono::high_resolution_clock> start_time)
{
bool timed_out = false;
if (_time_out_sec > 0)
{
timed_out = (chrono::high_resolution_clock::now() - start_time) > std::chrono::seconds(_time_out_sec);
// When the timeout is the only option is specified, disregard frame number
if (stop_on_timeout == _stop_cond)
return !timed_out;
}
bool collected_enough_frames = true;
for (auto&& profile : selected_stream_profiles)
{
auto key = std::make_pair(profile.stream_type(), profile.stream_index());
if (!data_collection.size() || (data_collection.find(key) != data_collection.end() &&
(data_collection[key].size() && data_collection[key].size() < _max_frames)))
{
collected_enough_frames = false;
break;
}
}
return !(timed_out || collected_enough_frames);
}
bool data_collector::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)
{
bool res = false;
try
{
auto tokens = tokenize(line, ',');
if (tokens.size() < e_stream_index)
return res;
// Convert string to uppercase
type = parse_stream_type(to_lower(tokens[e_stream_type]));
width = parse_number(tokens[e_res_width].c_str());
height = parse_number(tokens[e_res_height].c_str());
fps = parse_fps(tokens[e_fps]);
format = parse_format(to_lower(tokens[e_format]));
// Backward compatibility
if (tokens.size() > e_stream_index)
index = parse_number(tokens[e_stream_index].c_str());
res = true;
std::cout << "Request added for " << line << std::endl;
}
catch (...)
{
std::cout << "Invalid syntax in configuration line " << line << std::endl;
}
return res;
}
// Assign the user configuration to the selected device
bool data_collector::configure_sensors()
{
bool succeed = false;
requests_to_go = user_requests;
std::vector<rs2::stream_profile> matches;
// Configure and starts streaming
for (auto&& sensor : _dev->query_sensors())
{
for (auto& profile : sensor.get_stream_profiles())
{
// All requests have been resolved
if (!requests_to_go.size())
break;
// Find profile matches
auto fulfilled_request = std::find_if(requests_to_go.begin(), requests_to_go.end(), [&matches, profile](const stream_request& req)
{
bool res = false;
if ((profile.stream_type() == req._stream_type) &&
(profile.format() == req._stream_format) &&
(profile.stream_index() == req._stream_idx) &&
(profile.fps() == req._fps))
{
if (auto vp = profile.as<rs2::video_stream_profile>())
{
if ((vp.width() != req._width) || (vp.height() != req._height))
return false;
}
res = true;
matches.push_back(profile);
}
return res;
});
// Remove the request once resolved
if (fulfilled_request != requests_to_go.end())
requests_to_go.erase(fulfilled_request);
}
// Aggregate resolved requests
if (matches.size())
{
std::copy(matches.begin(), matches.end(), std::back_inserter(selected_stream_profiles));
sensor.open(matches);
active_sensors.emplace_back(sensor);
matches.clear();
}
if (selected_stream_profiles.size() == user_requests.size())
succeed = true;
}
return succeed;
}
int main(int argc, char** argv) try
{
#ifdef BUILD_EASYLOGGINGPP
rs2::log_to_file(RS2_LOG_SEVERITY_WARN);
#endif
// Parse command line arguments
CmdLine cmd("librealsense rs-data-collect example tool", ' ');
ValueArg<int> timeout("t", "Timeout", "Max amount of time to receive frames (in seconds)", false, 10, "");
ValueArg<int> max_frames("m", "MaxFrames_Number", "Maximum number of frames-per-stream to receive", false, 100, "");
ValueArg<string> out_file("f", "FullFilePath", "the file where the data will be saved to", false, "", "");
ValueArg<string> config_file("c", "ConfigurationFile", "Specify file path with the requested configuration", false, "", "");
cmd.add(timeout);
cmd.add(max_frames);
cmd.add(out_file);
cmd.add(config_file);
cmd.parse(argc, argv);
std::cout << "Running rs-data-collect: ";
for (auto i=1; i < argc; ++i)
std::cout << argv[i] << " ";
std::cout << std::endl << std::endl;
auto output_file = out_file.isSet() ? out_file.getValue() : DEF_OUTPUT_FILE_NAME;
{
ofstream csv(output_file);
if (!csv.is_open())
throw runtime_error(stringify() << "Cannot open the requested output file " << output_file << ", please check permissions");
}
bool succeed = false;
rs2::context ctx;
rs2::device_list list;
while (!succeed)
{
list = ctx.query_devices();
if (0== list.size())
{
std::cout << "Connect Realsense Camera to proceed" << std::endl;
std::this_thread::sleep_for(std::chrono::seconds(3));
continue;
}
auto dev = std::make_shared<rs2::device>(list.front());
data_collector dc(dev,timeout,max_frames); // Parser and the data aggregator
dc.parse_and_configure(config_file);
//data_collection buffer;
auto start_time = chrono::high_resolution_clock::now();
// Start streaming
for (auto&& sensor : dc.selected_sensors())
sensor.start([&dc,&start_time](rs2::frame f)
{
dc.collect_frame_attributes(f,start_time);
});
std::cout << "\nData collection started.... \n" << std::endl;
while (dc.collecting(start_time))
{
std::this_thread::sleep_for(std::chrono::seconds(1));
std::cout << "Collecting data for "
<< chrono::duration_cast<chrono::seconds>(chrono::high_resolution_clock::now() - start_time).count()
<< " sec" << std::endl;
}
// Stop & flush all active sensors
for (auto&& sensor : dc.selected_sensors())
{
sensor.stop();
sensor.close();
}
dc.save_data_to_file(output_file);
succeed = true;
}
std::cout << "Task completed" << std::endl;
return EXIT_SUCCESS;
}
catch (const rs2::error & e)
{
cerr << "RealSense error calling " << e.get_failed_function() << "(" << e.get_failed_args() << "):\n " << e.what() << endl;
return EXIT_FAILURE;
}
catch (const exception & e)
{
cerr << e.what() << endl;
return EXIT_FAILURE;
}