Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .clang-format
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,10 @@ AlignAfterOpenBracket: AlwaysBreak
AllowShortBlocksOnASingleLine: Empty
AllowShortFunctionsOnASingleLine: Empty
AllowShortIfStatementsOnASingleLine: Never

---
# Same column limit and indent as C++ for the protobuf schemas under pj_base/proto/.
Language: Proto
BasedOnStyle: Google
ColumnLimit: 120
IndentWidth: 2
3 changes: 2 additions & 1 deletion .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ repos:
- id: check-xml
- id: check-yaml
exclude: .gitlab-ci.yml
args: ['--allow-multiple-documents']
- id: debug-statements
- id: end-of-file-fixer
exclude_types: [svg]
Expand All @@ -30,5 +31,5 @@ repos:
rev: v22.1.0
hooks:
- id: clang-format
types_or: [c++, c]
types_or: [c++, c, proto]
args: ['-fallback-style=none', '-i']
244 changes: 232 additions & 12 deletions docs/builtin_type.md

Large diffs are not rendered by default.

18 changes: 18 additions & 0 deletions pj_base/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,17 @@
# ---------------------------------------------------------------------------

add_library(pj_base STATIC
src/builtin/asset_video_codec.cpp
src/builtin/compressed_point_cloud_codec.cpp
src/builtin/depth_image_codec.cpp
src/builtin/frame_transforms_codec.cpp
src/builtin/image_annotations_codec.cpp
src/builtin/image_codec.cpp
src/builtin/mesh3d_codec.cpp
src/builtin/occupancy_grid_codec.cpp
src/builtin/point_cloud_codec.cpp
src/builtin/scene_entities_codec.cpp
src/builtin/video_frame_codec.cpp
src/type_tree.cpp
)
target_include_directories(pj_base PUBLIC
Expand Down Expand Up @@ -63,6 +72,15 @@ if(PJ_BUILD_TESTS)
tests/image_annotations_decoder_test.cpp
tests/media_metadata_test.cpp
tests/push_message_v2_test.cpp
tests/image_codec_test.cpp
tests/depth_image_codec_test.cpp
tests/point_cloud_codec_test.cpp
tests/compressed_point_cloud_codec_test.cpp
tests/occupancy_grid_codec_test.cpp
tests/mesh3d_codec_test.cpp
tests/video_frame_codec_test.cpp
tests/scene_entities_codec_test.cpp
tests/asset_video_codec_test.cpp
)

foreach(test_src ${PJ_BASE_TESTS})
Expand Down
60 changes: 60 additions & 0 deletions pj_base/include/pj_base/builtin/asset_video.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
/**
* @file asset_video.hpp
* @brief File-backed video reference + typed playback metadata.
*
* AssetVideo is the entry-point handle for video assets ingested by data
* loaders that point at an external media file (LeRobot datasets, MP4
* loaders, etc.). Producers push exactly one AssetVideo per topic; the
* ObjectStore timestamp of that entry equals `time_origin_ns` so timeline
* UIs naturally see the asset's start instant.
*
* Unlike VideoFrame (a single frame of a streamed payload), AssetVideo
* carries no pixel data — it references the file by path and surfaces
* decode-routing metadata (media type, dimensions, frame rate) without
* forcing the consumer to open the file just to size a playback window.
*/
// Copyright 2026 Davide Faconti
// SPDX-License-Identifier: MIT

#pragma once

#include <cstdint>
#include <optional>
#include <string>

#include "pj_base/types.hpp"

namespace PJ {
namespace sdk {

/// File-backed video reference with typed metadata.
///
/// `time_origin_ns` carries the wall-clock instant of the first frame.
/// Consumers subtract this from a tracker time to derive a file-relative
/// seek position. An absent value means the asset is not aligned to wall
/// clock and should not advance with the tracker.
///
/// `duration_ns` is the total playable duration when known; absent means
/// consumers may probe the file.
///
/// `media_type` is a MIME-type hint ("video/mp4", "video/x-matroska",
/// "video/av1"). Empty string means "probe the file".
///
/// `width` / `height` are pixel dimensions; zero in either field means
/// "unknown — probe the file".
///
/// `frame_rate` is nominal frames per second; zero or NaN means "unknown —
/// probe the file". For variable-frame-rate video this is an advisory
/// average; actual per-frame timestamps come from the decoder.
struct AssetVideo {
std::optional<Timestamp> time_origin_ns; ///< Wall-clock instant of the first frame.
std::optional<int64_t> duration_ns; ///< Total playable duration in nanoseconds.
std::string file_path; ///< Absolute path or path relative to a consumer-known root.
std::string media_type; ///< MIME type. Empty string means "probe the file".
uint32_t width = 0; ///< Pixel width. 0 means unknown.
uint32_t height = 0; ///< Pixel height. 0 means unknown.
double frame_rate = 0.0; ///< Nominal frames per second. 0 or NaN means unknown.
};

} // namespace sdk
} // namespace PJ
24 changes: 24 additions & 0 deletions pj_base/include/pj_base/builtin/asset_video_codec.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
#pragma once
// Copyright 2026 Davide Faconti
// SPDX-License-Identifier: MIT

#include <cstddef>
#include <cstdint>
#include <string_view>
#include <vector>

#include "pj_base/builtin/asset_video.hpp"
#include "pj_base/expected.hpp"

namespace PJ {

inline constexpr std::string_view kSchemaAssetVideo = "PJ.AssetVideo";

/// Serializes sdk::AssetVideo to canonical PJ.AssetVideo wire bytes (see
/// pj_base/proto/pj/AssetVideo.proto).
[[nodiscard]] std::vector<uint8_t> serializeAssetVideo(const sdk::AssetVideo& asset);

/// Decodes canonical PJ.AssetVideo wire bytes into sdk::AssetVideo.
[[nodiscard]] Expected<sdk::AssetVideo> deserializeAssetVideo(const uint8_t* data, size_t size);

} // namespace PJ
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/**
* @file BuiltinObject.h
* @file builtin_object.hpp
* @brief Type-erased holder for any builtin object a MessageParser may produce.
*
* BuiltinObject is `std::any`. A producer constructs it by passing a
Expand All @@ -26,24 +26,36 @@
#include <optional>
#include <string_view>

#include "pj_base/builtin/DepthImage.hpp"
#include "pj_base/builtin/FrameTransforms.hpp"
#include "pj_base/builtin/Image.hpp"
#include "pj_base/builtin/ImageAnnotations.hpp"
#include "pj_base/builtin/PointCloud.hpp"
#include "pj_base/builtin/asset_video.hpp"
#include "pj_base/builtin/compressed_point_cloud.hpp"
#include "pj_base/builtin/depth_image.hpp"
#include "pj_base/builtin/frame_transforms.hpp"
#include "pj_base/builtin/image.hpp"
#include "pj_base/builtin/image_annotations.hpp"
#include "pj_base/builtin/mesh3d.hpp"
#include "pj_base/builtin/occupancy_grid.hpp"
#include "pj_base/builtin/point_cloud.hpp"
#include "pj_base/builtin/robot_description.hpp"
#include "pj_base/builtin/scene_entities.hpp"
#include "pj_base/builtin/video_frame.hpp"

namespace PJ {
namespace sdk {

enum class BuiltinObjectType : uint16_t {
kNone = 0,
kImage = 1, ///< sdk::Image — raw or compressed, distinguished by encoding string.
kPointCloud = 3, ///< sdk::PointCloud — packed points + per-channel field layout.
kDepthImage = 4, ///< sdk::DepthImage — depth pixels + camera intrinsics.
kImageAnnotations = 5, ///< sdk::ImageAnnotations — 2D overlays (points, lines, text).
kFrameTransforms = 6, ///< sdk::FrameTransforms — named 3D frame relationships.
// Reserved for future types; keep numeric values stable across releases.
// kOccupancyGrid = 7,
kImage = 1, ///< sdk::Image — raw or compressed, distinguished by encoding string.
kPointCloud = 3, ///< sdk::PointCloud — packed points + per-channel field layout.
kDepthImage = 4, ///< sdk::DepthImage — depth pixels + camera intrinsics.
kImageAnnotations = 5, ///< sdk::ImageAnnotations — 2D overlays (points, lines, text).
kFrameTransforms = 6, ///< sdk::FrameTransforms — named 3D frame relationships.
kOccupancyGrid = 7, ///< sdk::OccupancyGrid — 2D metric grid (maps, costmaps).
kCompressedPointCloud = 8, ///< sdk::CompressedPointCloud — opaque compressed cloud (Draco, ...).
kMesh3D = 9, ///< sdk::Mesh3D — binary mesh asset (GLTF/STL/PLY/OBJ/USD/DAE).
kVideoFrame = 10, ///< sdk::VideoFrame — single frame of h264/h265/vp9/av1 stream.
kSceneEntities = 11, ///< sdk::SceneEntities — procedural 3D scene primitives.
kAssetVideo = 12, ///< sdk::AssetVideo — file-backed video reference + playback metadata.
kRobotDescription = 13, ///< sdk::RobotDescription — raw URDF/SDF/MJCF text + format hint.
};

/// A-priori classification of a schema. Currently carries only the type;
Expand All @@ -68,6 +80,20 @@ struct SchemaClassification {
return "kImageAnnotations";
case BuiltinObjectType::kFrameTransforms:
return "kFrameTransforms";
case BuiltinObjectType::kOccupancyGrid:
return "kOccupancyGrid";
case BuiltinObjectType::kCompressedPointCloud:
return "kCompressedPointCloud";
case BuiltinObjectType::kMesh3D:
return "kMesh3D";
case BuiltinObjectType::kVideoFrame:
return "kVideoFrame";
case BuiltinObjectType::kSceneEntities:
return "kSceneEntities";
case BuiltinObjectType::kAssetVideo:
return "kAssetVideo";
case BuiltinObjectType::kRobotDescription:
return "kRobotDescription";
}
return "kNone";
}
Expand All @@ -93,6 +119,27 @@ struct SchemaClassification {
if (s == "kFrameTransforms") {
return BuiltinObjectType::kFrameTransforms;
}
if (s == "kOccupancyGrid") {
return BuiltinObjectType::kOccupancyGrid;
}
if (s == "kCompressedPointCloud") {
return BuiltinObjectType::kCompressedPointCloud;
}
if (s == "kMesh3D") {
return BuiltinObjectType::kMesh3D;
}
if (s == "kVideoFrame") {
return BuiltinObjectType::kVideoFrame;
}
if (s == "kSceneEntities") {
return BuiltinObjectType::kSceneEntities;
}
if (s == "kAssetVideo") {
return BuiltinObjectType::kAssetVideo;
}
if (s == "kRobotDescription") {
return BuiltinObjectType::kRobotDescription;
}
return std::nullopt;
}

Expand Down Expand Up @@ -121,6 +168,27 @@ using BuiltinObject = std::any;
if (t == typeid(FrameTransforms)) {
return BuiltinObjectType::kFrameTransforms;
}
if (t == typeid(OccupancyGrid)) {
return BuiltinObjectType::kOccupancyGrid;
}
if (t == typeid(CompressedPointCloud)) {
return BuiltinObjectType::kCompressedPointCloud;
}
if (t == typeid(Mesh3D)) {
return BuiltinObjectType::kMesh3D;
}
if (t == typeid(VideoFrame)) {
return BuiltinObjectType::kVideoFrame;
}
if (t == typeid(SceneEntities)) {
return BuiltinObjectType::kSceneEntities;
}
if (t == typeid(AssetVideo)) {
return BuiltinObjectType::kAssetVideo;
}
if (t == typeid(RobotDescription)) {
return BuiltinObjectType::kRobotDescription;
}
return BuiltinObjectType::kNone;
}

Expand Down
41 changes: 41 additions & 0 deletions pj_base/include/pj_base/builtin/compressed_point_cloud.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
/**
* @file compressed_point_cloud.hpp
* @brief Point cloud delivered in a compressed binary format (Draco, ...).
*/
// Copyright 2026 Davide Faconti
// SPDX-License-Identifier: MIT

#pragma once

#include <cstdint>
#include <string>

#include "pj_base/buffer_anchor.hpp"
#include "pj_base/span.hpp"
#include "pj_base/types.hpp"

namespace PJ {
namespace sdk {

/// Point cloud delivered in a compressed binary format. Unlike PointCloud,
/// the wire layout is opaque to PlotJuggler — `data` + `format` must be
/// handed to the matching decoder library (e.g. Draco), which produces a
/// decompressed point set on the host side.
///
/// This type is distinct from PointCloud because per-format decoders carry
/// their own attribute table and layout semantics. Same reasoning that
/// separates VideoFrame from Image.
///
/// `anchor` keeps the underlying buffer alive — the producer may have made
/// `data` a view into the source payload (zero-copy) or into a freshly
/// allocated vector; consumers don't need to know which.
struct CompressedPointCloud {
Timestamp timestamp_ns = 0;
std::string frame_id;
std::string format; ///< Codec identifier, lowercase. Recognized values include "draco".
Span<const uint8_t> data;
BufferAnchor anchor;
};

} // namespace sdk
} // namespace PJ
25 changes: 25 additions & 0 deletions pj_base/include/pj_base/builtin/compressed_point_cloud_codec.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
#pragma once
// Copyright 2026 Davide Faconti
// SPDX-License-Identifier: MIT

#include <cstddef>
#include <cstdint>
#include <string_view>
#include <vector>

#include "pj_base/builtin/compressed_point_cloud.hpp"
#include "pj_base/expected.hpp"

namespace PJ {

inline constexpr std::string_view kSchemaCompressedPointCloud = "PJ.CompressedPointCloud";

/// Serializes sdk::CompressedPointCloud to canonical PJ.CompressedPointCloud
/// wire bytes (see pj_base/proto/pj/CompressedPointCloud.proto).
[[nodiscard]] std::vector<uint8_t> serializeCompressedPointCloud(const sdk::CompressedPointCloud& cloud);

/// Decodes canonical PJ.CompressedPointCloud wire bytes. The returned object
/// owns its bytes via `anchor`.
[[nodiscard]] Expected<sdk::CompressedPointCloud> deserializeCompressedPointCloud(const uint8_t* data, size_t size);

} // namespace PJ
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/**
* @file DepthImage.h
* @file depth_image.hpp
* @brief Depth image with camera intrinsics.
*/
// Copyright 2026 Davide Faconti
Expand Down
25 changes: 25 additions & 0 deletions pj_base/include/pj_base/builtin/depth_image_codec.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
#pragma once
// Copyright 2026 Davide Faconti
// SPDX-License-Identifier: MIT

#include <cstddef>
#include <cstdint>
#include <string_view>
#include <vector>

#include "pj_base/builtin/depth_image.hpp"
#include "pj_base/expected.hpp"

namespace PJ {

inline constexpr std::string_view kSchemaDepthImage = "PJ.DepthImage";

/// Serializes sdk::DepthImage to canonical PJ.DepthImage wire bytes (see
/// pj_base/proto/pj/DepthImage.proto).
[[nodiscard]] std::vector<uint8_t> serializeDepthImage(const sdk::DepthImage& depth);

/// Decodes canonical PJ.DepthImage wire bytes. The returned object owns
/// its depth pixel bytes via `anchor`.
[[nodiscard]] Expected<sdk::DepthImage> deserializeDepthImage(const uint8_t* data, size_t size);

} // namespace PJ
4 changes: 2 additions & 2 deletions pj_base/include/pj_base/builtin/depth_image_utils.hpp
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/**
* @file depth_image_utils.h
* @file depth_image_utils.hpp
* @brief Free-function helpers that derive conventional matrices (R, P)
* from sdk::DepthImage's intrinsics.
*
Expand All @@ -16,7 +16,7 @@

#include <array>

#include "pj_base/builtin/DepthImage.hpp"
#include "pj_base/builtin/depth_image.hpp"

namespace PJ {
namespace sdk {
Expand Down
Loading
Loading