Add ZED SVO to MCAP conversion tool
This commit is contained in:
+243
-26
@@ -3,8 +3,10 @@
|
||||
#include "cvmmap_streamer/record/mcap_record_sink.hpp"
|
||||
|
||||
#include "protobuf_descriptor.hpp"
|
||||
#include "cvmmap_streamer/DepthMap.pb.h"
|
||||
#include "foxglove/CompressedVideo.pb.h"
|
||||
#include "proto/cvmmap_streamer/DepthMap.pb.h"
|
||||
#include "proto/foxglove/CameraCalibration.pb.h"
|
||||
#include "proto/foxglove/CompressedVideo.pb.h"
|
||||
#include "proto/foxglove/PoseInFrame.pb.h"
|
||||
#include <rvl/rvl.hpp>
|
||||
|
||||
#include <google/protobuf/timestamp.pb.h>
|
||||
@@ -293,14 +295,14 @@ std::expected<EncodedDepthPayload, std::string> encode_depth_payload(const RawDe
|
||||
};
|
||||
}
|
||||
|
||||
std::vector<float> depth_m(pixel_count, std::numeric_limits<float>::quiet_NaN());
|
||||
float finite_max_m = 0.0f;
|
||||
for (std::size_t index = 0; index < pixel_count; ++index) {
|
||||
float sample = depth_map.pixels[index];
|
||||
if (source_unit == ipc::DepthUnit::Millimeter && std::isfinite(sample)) {
|
||||
sample *= 0.001f;
|
||||
}
|
||||
if (!std::isfinite(sample) || sample <= 0.0f) {
|
||||
std::vector<float> depth_m(pixel_count, std::numeric_limits<float>::quiet_NaN());
|
||||
float finite_max_m = 0.0f;
|
||||
for (std::size_t index = 0; index < pixel_count; ++index) {
|
||||
float sample = depth_map.pixels[index];
|
||||
if (source_unit == ipc::DepthUnit::Millimeter && std::isfinite(sample)) {
|
||||
sample *= 0.001f;
|
||||
}
|
||||
if (!std::isfinite(sample) || sample <= 0.0f) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -321,18 +323,108 @@ std::expected<EncodedDepthPayload, std::string> encode_depth_payload(const RawDe
|
||||
}
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
std::expected<EncodedDepthPayload, std::string> encode_depth_payload(const RawDepthMapU16View &depth_map) {
|
||||
const auto pixel_count = static_cast<std::size_t>(depth_map.width) * static_cast<std::size_t>(depth_map.height);
|
||||
if (depth_map.width == 0 || depth_map.height == 0) {
|
||||
return std::unexpected("depth map dimensions must be non-zero");
|
||||
}
|
||||
if (pixel_count != depth_map.pixels.size()) {
|
||||
return std::unexpected("depth map dimensions do not match the pixel buffer");
|
||||
}
|
||||
|
||||
try {
|
||||
std::vector<std::uint16_t> pixels(depth_map.pixels.begin(), depth_map.pixels.end());
|
||||
return EncodedDepthPayload{
|
||||
.encoding = DepthEncoding::RvlU16Lossless,
|
||||
.storage_unit = ipc::DepthUnit::Millimeter,
|
||||
.bytes = rvl::compress_image(pixels, depth_map.height, depth_map.width),
|
||||
};
|
||||
} catch (const std::exception &error) {
|
||||
return std::unexpected(std::string("failed to RVL-encode depth map: ") + error.what());
|
||||
}
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
std::expected<void, std::string> append_repeated_double(
|
||||
google::protobuf::RepeatedField<double> *field,
|
||||
std::span<const double> values,
|
||||
std::size_t expected_size,
|
||||
std::string_view field_name) {
|
||||
if (values.size() != expected_size) {
|
||||
return std::unexpected(
|
||||
"expected " + std::to_string(expected_size) + " values for " + std::string(field_name));
|
||||
}
|
||||
field->Reserve(static_cast<int>(values.size()));
|
||||
for (const double value : values) {
|
||||
field->Add(value);
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
std::expected<void, std::string> write_depth_message(
|
||||
mcap::McapWriter &writer,
|
||||
std::uint64_t timestamp_ns,
|
||||
std::string_view frame_id,
|
||||
std::uint32_t width,
|
||||
std::uint32_t height,
|
||||
ipc::DepthUnit source_unit,
|
||||
mcap::ChannelId channel_id,
|
||||
std::uint32_t &sequence,
|
||||
const EncodedDepthPayload &encoded) {
|
||||
cvmmap_streamer::DepthMap message{};
|
||||
*message.mutable_timestamp() = to_proto_timestamp(timestamp_ns);
|
||||
message.set_frame_id(std::string(frame_id));
|
||||
message.set_width(width);
|
||||
message.set_height(height);
|
||||
message.set_source_unit(to_proto_depth_unit(source_unit));
|
||||
message.set_storage_unit(to_proto_storage_unit(encoded.storage_unit));
|
||||
message.set_encoding(to_proto_depth_encoding(encoded.encoding));
|
||||
message.set_data(
|
||||
reinterpret_cast<const char *>(encoded.bytes.data()),
|
||||
static_cast<int>(encoded.bytes.size()));
|
||||
|
||||
std::string serialized{};
|
||||
if (!message.SerializeToString(&serialized)) {
|
||||
return std::unexpected("failed to serialize cvmmap_streamer.DepthMap");
|
||||
}
|
||||
|
||||
mcap::Message record{};
|
||||
record.channelId = channel_id;
|
||||
record.sequence = sequence++;
|
||||
record.logTime = timestamp_ns;
|
||||
record.publishTime = timestamp_ns;
|
||||
record.data = reinterpret_cast<const std::byte *>(serialized.data());
|
||||
record.dataSize = serialized.size();
|
||||
|
||||
const auto write_status = writer.write(record);
|
||||
if (!write_status.ok()) {
|
||||
return std::unexpected("failed to write MCAP depth message: " + write_status.message);
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
struct McapRecordSink::State {
|
||||
mcap::McapWriter writer{};
|
||||
std::string path{};
|
||||
std::string frame_id{};
|
||||
std::string calibration_topic{};
|
||||
std::string pose_topic{};
|
||||
std::string body_topic{};
|
||||
mcap::SchemaId calibration_schema_id{0};
|
||||
mcap::SchemaId pose_schema_id{0};
|
||||
mcap::ChannelId video_channel_id{0};
|
||||
mcap::ChannelId depth_channel_id{0};
|
||||
mcap::ChannelId calibration_channel_id{0};
|
||||
mcap::ChannelId pose_channel_id{0};
|
||||
mcap::ChannelId body_channel_id{0};
|
||||
std::uint32_t video_sequence{0};
|
||||
std::uint32_t depth_sequence{0};
|
||||
std::uint32_t calibration_sequence{0};
|
||||
std::uint32_t pose_sequence{0};
|
||||
std::uint32_t body_sequence{0};
|
||||
CodecType codec{CodecType::H264};
|
||||
std::vector<std::uint8_t> keyframe_preamble{};
|
||||
@@ -363,6 +455,8 @@ std::expected<McapRecordSink, std::string> McapRecordSink::create(
|
||||
auto state = std::make_unique<State>();
|
||||
state->path = config.record.mcap.path;
|
||||
state->frame_id = config.record.mcap.frame_id;
|
||||
state->calibration_topic = config.record.mcap.calibration_topic;
|
||||
state->pose_topic = config.record.mcap.pose_topic;
|
||||
state->body_topic = config.record.mcap.body_topic;
|
||||
|
||||
mcap::McapWriterOptions options("");
|
||||
@@ -398,6 +492,26 @@ std::expected<McapRecordSink, std::string> McapRecordSink::create(
|
||||
state->writer.addChannel(depth_channel);
|
||||
state->depth_channel_id = depth_channel.id;
|
||||
|
||||
const auto calibration_descriptor_set = build_file_descriptor_set(foxglove::CameraCalibration::descriptor());
|
||||
std::string calibration_schema_bytes{};
|
||||
if (!calibration_descriptor_set.SerializeToString(&calibration_schema_bytes)) {
|
||||
return std::unexpected("failed to serialize foxglove.CameraCalibration descriptor set");
|
||||
}
|
||||
|
||||
mcap::Schema calibration_schema("foxglove.CameraCalibration", "protobuf", calibration_schema_bytes);
|
||||
state->writer.addSchema(calibration_schema);
|
||||
state->calibration_schema_id = calibration_schema.id;
|
||||
|
||||
const auto pose_descriptor_set = build_file_descriptor_set(foxglove::PoseInFrame::descriptor());
|
||||
std::string pose_schema_bytes{};
|
||||
if (!pose_descriptor_set.SerializeToString(&pose_schema_bytes)) {
|
||||
return std::unexpected("failed to serialize foxglove.PoseInFrame descriptor set");
|
||||
}
|
||||
|
||||
mcap::Schema pose_schema("foxglove.PoseInFrame", "protobuf", pose_schema_bytes);
|
||||
state->writer.addSchema(pose_schema);
|
||||
state->pose_schema_id = pose_schema.id;
|
||||
|
||||
sink.state_ = state.release();
|
||||
auto update = sink.update_stream_info(stream_info);
|
||||
if (!update) {
|
||||
@@ -470,34 +584,137 @@ std::expected<void, std::string> McapRecordSink::write_depth_map(const RawDepthM
|
||||
return std::unexpected(encoded.error());
|
||||
}
|
||||
|
||||
cvmmap_streamer::DepthMap message{};
|
||||
*message.mutable_timestamp() = to_proto_timestamp(depth_map.timestamp_ns);
|
||||
return write_depth_message(
|
||||
state_->writer,
|
||||
depth_map.timestamp_ns,
|
||||
state_->frame_id,
|
||||
depth_map.width,
|
||||
depth_map.height,
|
||||
source_unit,
|
||||
state_->depth_channel_id,
|
||||
state_->depth_sequence,
|
||||
*encoded);
|
||||
}
|
||||
|
||||
std::expected<void, std::string> McapRecordSink::write_depth_map_u16(const RawDepthMapU16View &depth_map) {
|
||||
if (state_ == nullptr) {
|
||||
return std::unexpected("MCAP sink is not open");
|
||||
}
|
||||
|
||||
auto encoded = encode_depth_payload(depth_map);
|
||||
if (!encoded) {
|
||||
return std::unexpected(encoded.error());
|
||||
}
|
||||
|
||||
return write_depth_message(
|
||||
state_->writer,
|
||||
depth_map.timestamp_ns,
|
||||
state_->frame_id,
|
||||
depth_map.width,
|
||||
depth_map.height,
|
||||
ipc::DepthUnit::Millimeter,
|
||||
state_->depth_channel_id,
|
||||
state_->depth_sequence,
|
||||
*encoded);
|
||||
}
|
||||
|
||||
std::expected<void, std::string> McapRecordSink::write_camera_calibration(const RawCameraCalibrationView &calibration) {
|
||||
if (state_ == nullptr) {
|
||||
return std::unexpected("MCAP sink is not open");
|
||||
}
|
||||
if (state_->calibration_topic.empty()) {
|
||||
return std::unexpected("calibration topic is empty");
|
||||
}
|
||||
|
||||
if (state_->calibration_channel_id == 0) {
|
||||
mcap::Channel calibration_channel(state_->calibration_topic, "protobuf", state_->calibration_schema_id);
|
||||
state_->writer.addChannel(calibration_channel);
|
||||
state_->calibration_channel_id = calibration_channel.id;
|
||||
}
|
||||
|
||||
foxglove::CameraCalibration message{};
|
||||
*message.mutable_timestamp() = to_proto_timestamp(calibration.timestamp_ns);
|
||||
message.set_frame_id(state_->frame_id);
|
||||
message.set_width(depth_map.width);
|
||||
message.set_height(depth_map.height);
|
||||
message.set_source_unit(to_proto_depth_unit(source_unit));
|
||||
message.set_storage_unit(to_proto_storage_unit(encoded->storage_unit));
|
||||
message.set_encoding(to_proto_depth_encoding(encoded->encoding));
|
||||
message.set_data(
|
||||
reinterpret_cast<const char *>(encoded->bytes.data()),
|
||||
static_cast<int>(encoded->bytes.size()));
|
||||
message.set_width(calibration.width);
|
||||
message.set_height(calibration.height);
|
||||
message.set_distortion_model(std::string(calibration.distortion_model));
|
||||
for (const double value : calibration.distortion) {
|
||||
message.add_d(value);
|
||||
}
|
||||
if (auto append = append_repeated_double(message.mutable_k(), calibration.intrinsic_matrix, 9, "K"); !append) {
|
||||
return append;
|
||||
}
|
||||
if (auto append = append_repeated_double(message.mutable_r(), calibration.rectification_matrix, 9, "R"); !append) {
|
||||
return append;
|
||||
}
|
||||
if (auto append = append_repeated_double(message.mutable_p(), calibration.projection_matrix, 12, "P"); !append) {
|
||||
return append;
|
||||
}
|
||||
|
||||
std::string serialized{};
|
||||
if (!message.SerializeToString(&serialized)) {
|
||||
return std::unexpected("failed to serialize cvmmap_streamer.DepthMap");
|
||||
return std::unexpected("failed to serialize foxglove.CameraCalibration");
|
||||
}
|
||||
|
||||
mcap::Message record{};
|
||||
record.channelId = state_->depth_channel_id;
|
||||
record.sequence = state_->depth_sequence++;
|
||||
record.logTime = depth_map.timestamp_ns;
|
||||
record.publishTime = depth_map.timestamp_ns;
|
||||
record.channelId = state_->calibration_channel_id;
|
||||
record.sequence = state_->calibration_sequence++;
|
||||
record.logTime = calibration.timestamp_ns;
|
||||
record.publishTime = calibration.timestamp_ns;
|
||||
record.data = reinterpret_cast<const std::byte *>(serialized.data());
|
||||
record.dataSize = serialized.size();
|
||||
|
||||
const auto write_status = state_->writer.write(record);
|
||||
if (!write_status.ok()) {
|
||||
return std::unexpected("failed to write MCAP depth message: " + write_status.message);
|
||||
return std::unexpected("failed to write MCAP calibration message: " + write_status.message);
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
std::expected<void, std::string> McapRecordSink::write_pose(const RawPoseView &pose) {
|
||||
if (state_ == nullptr) {
|
||||
return std::unexpected("MCAP sink is not open");
|
||||
}
|
||||
if (state_->pose_topic.empty()) {
|
||||
return std::unexpected("pose topic is empty");
|
||||
}
|
||||
|
||||
if (state_->pose_channel_id == 0) {
|
||||
mcap::Channel pose_channel(state_->pose_topic, "protobuf", state_->pose_schema_id);
|
||||
state_->writer.addChannel(pose_channel);
|
||||
state_->pose_channel_id = pose_channel.id;
|
||||
}
|
||||
|
||||
foxglove::PoseInFrame message{};
|
||||
*message.mutable_timestamp() = to_proto_timestamp(pose.timestamp_ns);
|
||||
message.set_frame_id(std::string(pose.reference_frame_id));
|
||||
auto *pose_message = message.mutable_pose();
|
||||
auto *position = pose_message->mutable_position();
|
||||
position->set_x(pose.position[0]);
|
||||
position->set_y(pose.position[1]);
|
||||
position->set_z(pose.position[2]);
|
||||
auto *orientation = pose_message->mutable_orientation();
|
||||
orientation->set_x(pose.orientation[0]);
|
||||
orientation->set_y(pose.orientation[1]);
|
||||
orientation->set_z(pose.orientation[2]);
|
||||
orientation->set_w(pose.orientation[3]);
|
||||
|
||||
std::string serialized{};
|
||||
if (!message.SerializeToString(&serialized)) {
|
||||
return std::unexpected("failed to serialize foxglove.PoseInFrame");
|
||||
}
|
||||
|
||||
mcap::Message record{};
|
||||
record.channelId = state_->pose_channel_id;
|
||||
record.sequence = state_->pose_sequence++;
|
||||
record.logTime = pose.timestamp_ns;
|
||||
record.publishTime = pose.timestamp_ns;
|
||||
record.data = reinterpret_cast<const std::byte *>(serialized.data());
|
||||
record.dataSize = serialized.size();
|
||||
|
||||
const auto write_status = state_->writer.write(record);
|
||||
if (!write_status.ok()) {
|
||||
return std::unexpected("failed to write MCAP pose message: " + write_status.message);
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user