feat(zed): improve MCAP export batching and defaults

Default ZED MCAP export to neural_plus depth across the CLI and Python wrappers, and add tail-frame handling plus better corrupted-frame diagnostics in zed_svo_to_mcap.

Add mixed hardware/software worker pools to the batch MCAP wrapper, replace tqdm with progress-table on TTYs, keep text event logging and heartbeats for non-TTY runs, and document the NVENC session-limit rationale for mixed mode in the README.

Also refresh Python dependencies for the batch tooling and move the OpenSSL lookup in CMake so the local workspace build remains compatible with the vendored cnats setup.
This commit is contained in:
2026-03-23 09:07:38 +00:00
parent 2f74a9561d
commit a0b9c95d5b
7 changed files with 909 additions and 69 deletions
+41 -2
View File
@@ -53,7 +53,7 @@ struct CliOptions {
std::string codec{"h265"};
std::string encoder_device{"auto"};
std::string mcap_compression{"zstd"};
std::string depth_mode{"neural"};
std::string depth_mode{"neural_plus"};
std::string depth_size{"optimal"};
bool with_pose{false};
std::uint32_t start_frame{0};
@@ -670,9 +670,25 @@ std::expected<void, std::string> read_frame_data(
if (grab_status == sl::ERROR_CODE::END_OF_SVOFILE_REACHED) {
return std::unexpected("end-of-svo");
}
if (grab_status == sl::ERROR_CODE::CORRUPTED_FRAME &&
stream.last_timestamp_ns != 0 &&
stream.current_timestamp_ns >= stream.last_timestamp_ns) {
spdlog::warn(
"treating unreadable tail frame as end-of-svo for {} current_timestamp_ns={} last_timestamp_ns={}",
stream.source.path.string(),
stream.current_timestamp_ns,
stream.last_timestamp_ns);
return std::unexpected("end-of-svo");
}
if (grab_status != sl::ERROR_CODE::SUCCESS) {
const auto svo_position = stream.camera->getSVOPosition();
return std::unexpected(
"failed to grab frame for " + stream.source.label + ": " + zed_status_string(grab_status));
"failed to grab frame for " + stream.source.label +
": " + zed_status_string(grab_status) +
" svo_position=" + std::to_string(svo_position) +
" total_frames=" + std::to_string(stream.total_frames) +
" current_timestamp_ns=" + std::to_string(stream.current_timestamp_ns) +
" last_timestamp_ns=" + std::to_string(stream.last_timestamp_ns));
}
const auto image_status = stream.camera->retrieveImage(left_frame, sl::VIEW::LEFT_BGR, sl::MEM::CPU);
@@ -1545,6 +1561,14 @@ int run_single_source(
if (grab_status == sl::ERROR_CODE::END_OF_SVOFILE_REACHED) {
break;
}
if (grab_status == sl::ERROR_CODE::CORRUPTED_FRAME &&
camera.getSVOPosition() >= static_cast<int>(last_frame)) {
spdlog::warn(
"treating unreadable tail frame as end-of-svo for {} at frame {}",
input_path.string(),
camera.getSVOPosition());
break;
}
if (grab_status != sl::ERROR_CODE::SUCCESS) {
progress.finish(emitted_frames, false);
sink->close();
@@ -2049,6 +2073,21 @@ int run_multi_source(
stream.source.label);
}
close_camera_streams(streams);
if (!interrupted && emitted_groups == 0) {
std::error_code remove_error{};
std::filesystem::remove(output_path, remove_error);
if (remove_error) {
spdlog::warn(
"failed to remove empty MCAP output '{}': {}",
output_path.string(),
remove_error.message());
}
spdlog::error(
"no synced frame groups were found across {} camera(s) for '{}'",
sources.size(),
output_path.string());
return exit_code(ToolExitCode::RuntimeError);
}
if (interrupted) {
spdlog::warn(