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
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;
|
|
}
|