Skip to content

Commit 62a3b12

Browse files
feat(pj_base): add PJ.PosesInFrame builtin schema (#121)
Canonical array-of-poses type (geometry_msgs/PoseArray, foxglove.PosesInFrame): timestamp, frame_id, poses[]. Deliberately carries no styling - arrow/triad shape and scale are viewer-side decisions. Wire format mirrors foxglove.PosesInFrame field-for-field. - kPosesInFrame = 17 in BuiltinObjectType and the C ABI enum, sentinel-pinned; PJ_ABI_VERSION unchanged (additive change) - hand-rolled wire codec via the builtin_wire Pose/Timestamp helpers - version 0.7.0 -> 0.8.0; ABI baseline untouched (additions only) Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
1 parent 9003e55 commit 62a3b12

16 files changed

Lines changed: 274 additions & 8 deletions

CLAUDE.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,9 @@ two modules below have no own CLAUDE.md.
1515
### Modules
1616

1717
- **pj_base** — vocabulary types (`Timestamp`, `DatasetId`, `Expected<T>`, `Span<T>`, type trees),
18-
the canonical builtin object vocabulary (`pj_base/builtin/`: 15 struct headers — Image, DepthImage,
18+
the canonical builtin object vocabulary (`pj_base/builtin/`: 16 struct headers — Image, DepthImage,
1919
PointCloud, CompressedPointCloud, OccupancyGrid(+Update), Mesh3D, VideoFrame, AssetVideo,
20-
SceneEntities, RobotDescription, CameraInfo, Log, ImageAnnotations, FrameTransforms) and their 14
20+
SceneEntities, RobotDescription, CameraInfo, Log, ImageAnnotations, FrameTransforms, PosesInFrame) and their 15
2121
wire codecs (RobotDescription carries source text as-is — no codec), the C-ABI protocol headers for
2222
DataSource/MessageParser/Toolbox + the C++ SDK base classes / host-view helpers built on them.
2323
- **pj_plugins** — host-side loaders + RAII handles + plugin discovery/catalog for four plugin

CMakeLists.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,7 @@ endif()
111111
if(PJ_INSTALL_SDK)
112112
include(CMakePackageConfigHelpers)
113113

114-
set(PJ_PACKAGE_VERSION "0.7.0")
114+
set(PJ_PACKAGE_VERSION "0.8.0")
115115
set(PJ_PACKAGE_CMAKE_DIR ${CMAKE_INSTALL_LIBDIR}/cmake/plotjuggler_sdk)
116116

117117
install(EXPORT plotjuggler_sdkTargets

conanfile.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
plugin_sdk — umbrella for plugin authors (base + dialog SDK + parser SDK)
77
plugin_host — umbrella for host loaders (data_source/parser/toolbox/dialog)
88
9-
A consuming Conan recipe declares e.g. `plotjuggler_sdk/0.7.0` and then:
9+
A consuming Conan recipe declares e.g. `plotjuggler_sdk/0.8.0` and then:
1010
1111
find_package(plotjuggler_sdk REQUIRED COMPONENTS plugin_sdk)
1212
target_link_libraries(my_plugin PRIVATE plotjuggler_sdk::plugin_sdk)
@@ -30,7 +30,7 @@
3030

3131
class PlotjugglerSdkConan(ConanFile):
3232
name = "plotjuggler_sdk"
33-
version = "0.7.0"
33+
version = "0.8.0"
3434
# Apache-2.0 covers the whole SDK (pj_base + pj_plugins). See LICENSE.
3535
license = "Apache-2.0"
3636
url = "https://github.com/PlotJuggler/plotjuggler_sdk"

docs/builtin_type.md

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ The public headers live under:
2727
#include <pj_base/builtin/robot_description.hpp>
2828
#include <pj_base/builtin/image_annotations.hpp>
2929
#include <pj_base/builtin/frame_transforms.hpp>
30+
#include <pj_base/builtin/poses_in_frame.hpp>
3031
// Codecs — one per type, all share the canonical PJ.<Type> wire format under pj_base/proto/pj/.
3132
#include <pj_base/builtin/image_codec.hpp>
3233
#include <pj_base/builtin/depth_image_codec.hpp>
@@ -39,6 +40,7 @@ The public headers live under:
3940
#include <pj_base/builtin/scene_entities_codec.hpp>
4041
#include <pj_base/builtin/image_annotations_codec.hpp>
4142
#include <pj_base/builtin/frame_transforms_codec.hpp>
43+
#include <pj_base/builtin/poses_in_frame_codec.hpp>
4244
```
4345

4446
## Design Principles
@@ -84,7 +86,7 @@ Builtin objects fall into two serialization families:
8486
| Family | Current types | Storage model | Codec policy |
8587
|--------|---------------|---------------|--------------|
8688
| Byte-backed views | `Image`, `DepthImage`, `PointCloud`, `CompressedPointCloud`, `OccupancyGrid`, `OccupancyGridUpdate`, `Mesh3D`, `VideoFrame` | Header fields live in the SDK struct; payload bytes live behind `Span<const uint8_t>` plus `BufferAnchor`. | No mandatory canonical codec; preserve zero-copy views over ROS, MCAP, compressed image, point-cloud, or plugin-owned payloads. If conversion is unavoidable, allocate a new payload and anchor it. |
87-
| Owned values | `ImageAnnotations`, `FrameTransforms`, `SceneEntities`, `AssetVideo`, `RobotDescription`, `CameraInfo`; future marker types | SDK structs own their vectors/strings/scalars directly. | Add explicit codecs when canonical bytes are needed. Codecs serialize the owned value to the protobuf-wire payload described by the `.proto` contract, using shared private wire primitives. `RobotDescription` carries source-format text as-is (no canonical codec) — the format hint distinguishes URDF / SDF / MJCF. |
89+
| Owned values | `ImageAnnotations`, `FrameTransforms`, `SceneEntities`, `AssetVideo`, `RobotDescription`, `CameraInfo`, `PosesInFrame`; future marker types | SDK structs own their vectors/strings/scalars directly. | Add explicit codecs when canonical bytes are needed. Codecs serialize the owned value to the protobuf-wire payload described by the `.proto` contract, using shared private wire primitives. `RobotDescription` carries source-format text as-is (no canonical codec) — the format hint distinguishes URDF / SDF / MJCF. |
8890

8991
Canonical `.proto` files live under `pj_base/proto/pj` and act as the wire
9092
format contract. One file per top-level message, each named after its message
@@ -123,6 +125,7 @@ annotations, frame transforms, or no builtin object.
123125
| `kCameraInfo` | `PJ::sdk::CameraInfo` | Pinhole camera calibration (intrinsics K, distortion D, rectification R, projection P). |
124126
| `kOccupancyGridUpdate` | `PJ::sdk::OccupancyGridUpdate` | Incremental sub-rectangle patch for a previously-published `OccupancyGrid`. |
125127
| `kLog` | `PJ::sdk::Log` | Textual log message (severity level + text + originating name). |
128+
| `kPosesInFrame` | `PJ::sdk::PosesInFrame` | Array of poses in one frame (PoseArray / particle clouds); styling is viewer-side. |
126129

127130
`BuiltinObject` is `std::any`. Producers store a concrete builtin value in it;
128131
consumers recover the concrete type with `std::any_cast<T>(&object)` or ask
@@ -569,6 +572,27 @@ Foxglove's source-location fields (`file`, `line`) are intentionally omitted.
569572
`pj_base/builtin/log_codec.hpp` serializes and deserializes this type using the
570573
canonical `PJ.Log` protobuf wire format.
571574

575+
## PosesInFrame
576+
577+
`PosesInFrame` is an array of poses expressed in a single reference frame at one
578+
instant — ROS `geometry_msgs/PoseArray`, `foxglove.PosesInFrame` (which it
579+
mirrors field-for-field), AMCL particle clouds, candidate grasp sets, sampled
580+
trajectories.
581+
582+
It deliberately carries **no styling**: how the poses are drawn (arrows vs
583+
triads, scale, color) is a viewer-side decision, exactly as the source messages
584+
carry none. It is an owned value (strings and scalar geometry, no byte blob),
585+
so no `BufferAnchor` is needed.
586+
587+
| Field | Type | Notes |
588+
|-------|------|-------|
589+
| `timestamp_ns` | `Timestamp` | Time of the poses. `0` when the source had no timestamp. |
590+
| `frame_id` | `std::string` | Frame all poses are expressed in. |
591+
| `poses` | `std::vector<Pose>` | The poses. Order is meaningful only to the producer. |
592+
593+
`pj_base/builtin/poses_in_frame_codec.hpp` serializes and deserializes this
594+
type using the canonical `PJ.PosesInFrame` protobuf wire format.
595+
572596
## Conversion Examples
573597

574598
| Source type | Canonical builtin type | Conversion intent |
@@ -584,6 +608,7 @@ canonical `PJ.Log` protobuf wire format.
584608
| MP4 / MKV / AV1 dataset file | `AssetVideo` | Push once per topic with the file path and metadata; consumers seek into the file by tracker time. |
585609
| Detection or tracking message | `ImageAnnotations` | Convert boxes, points, circles, and labels into pixel-space primitives. |
586610
| ROS `tf2_msgs/TFMessage` | `FrameTransforms` | Convert transform batches into named parent/child frame relationships. |
611+
| ROS `geometry_msgs/PoseArray` | `PosesInFrame` | Forward timestamp, frame, and the pose array; the viewer chooses how to draw them. |
587612
| ROS `std_msgs/String` on `/robot_description` (or matching name) carrying URDF XML | `RobotDescription` | Validate root element matches `format`, then carry the raw text + format hint. No mesh resolution at parse time. |
588613
| ROS `sensor_msgs/CameraInfo` | `CameraInfo` | Map K / D / R / P plus dimensions; correlate to the image topic by name. Sub-window (binning / ROI) is dropped. |
589614
| ROS `map_msgs/OccupancyGridUpdate` | `OccupancyGridUpdate` | Forward the cell-space patch (`x`/`y`/`width`/`height` + bytes); the consumer pairs it with the base grid and supplies origin/resolution. |

pj_base/CLAUDE.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
# pj_base — SDK vocabulary, builtin object schemas, and the C plugin ABI
22

3-
pj_base is the **Level 0** foundation and the **SDK boundary** for plugin authors. It owns: the zero-dependency vocabulary types (`Timestamp`, `DatasetId`, `Range`, `Expected<T>`, `Span`, `TypeTree`); the canonical *builtin object* schemas (`sdk::Image`, `PointCloud`, `DepthImage`, `OccupancyGrid`, `FrameTransforms`, … — 15 types) **and 14 of their wire codecs** (RobotDescription has none); and the **C ABI** primitives every plugin family speaks (`plugin_data_api.h` + the service registry) plus the C-ABI protocol headers for **three** families — `data_source_protocol.h`, `message_parser_protocol.h`, `toolbox_protocol.h`. The **Dialog** protocol header is the exception: it lives in `pj_plugins/dialog_protocol/`, not here. It also ships the C++ SDK base classes for DataSource and Toolbox; the MessageParser and Dialog base classes live in `pj_plugins`. Builds as a STATIC lib with **zero public deps** — `fast_float` is a `BUILD_INTERFACE` private impl detail of `parseNumber`. Must NOT depend on `pj_datastore`, `pj_plugins`, Qt, or any Conan runtime lib. This is a read-only submodule subtree: change it only when explicitly working in `plotjuggler_sdk`.
3+
pj_base is the **Level 0** foundation and the **SDK boundary** for plugin authors. It owns: the zero-dependency vocabulary types (`Timestamp`, `DatasetId`, `Range`, `Expected<T>`, `Span`, `TypeTree`); the canonical *builtin object* schemas (`sdk::Image`, `PointCloud`, `DepthImage`, `OccupancyGrid`, `FrameTransforms`, … — 16 types) **and 15 of their wire codecs** (RobotDescription has none); and the **C ABI** primitives every plugin family speaks (`plugin_data_api.h` + the service registry) plus the C-ABI protocol headers for **three** families — `data_source_protocol.h`, `message_parser_protocol.h`, `toolbox_protocol.h`. The **Dialog** protocol header is the exception: it lives in `pj_plugins/dialog_protocol/`, not here. It also ships the C++ SDK base classes for DataSource and Toolbox; the MessageParser and Dialog base classes live in `pj_plugins`. Builds as a STATIC lib with **zero public deps** — `fast_float` is a `BUILD_INTERFACE` private impl detail of `parseNumber`. Must NOT depend on `pj_datastore`, `pj_plugins`, Qt, or any Conan runtime lib. This is a read-only submodule subtree: change it only when explicitly working in `plotjuggler_sdk`.
44

55
## Layout
66
- `include/pj_base/` — vocabulary primitives: `types.hpp`, `type_tree.hpp`, `dataset.hpp`, `expected.hpp`, `span.hpp`, `number_parse.hpp`, `assert.hpp`, `diagnostic_sink.hpp`, `buffer_anchor.hpp`.
7-
- `include/pj_base/builtin/` — the 15 builtin object struct headers (`*.hpp`; 16 enum values in `BuiltinObjectType`, value 2 reserved) + their 14 wire codecs (`*_codec.hpp`; RobotDescription has none) + the `BuiltinObject` (`std::any`) type-erased holder.
7+
- `include/pj_base/builtin/` — the 16 builtin object struct headers (`*.hpp`; 17 enum values in `BuiltinObjectType`, value 2 reserved) + their 15 wire codecs (`*_codec.hpp`; RobotDescription has none) + the `BuiltinObject` (`std::any`) type-erased holder.
88
- `include/pj_base/sdk/` — C++ SDK over the ABI: DataSource + Toolbox `*_plugin_base.hpp`, `service_registry.hpp`/`service_traits.hpp`, host views, Arrow RAII holders, `testing/`.
99
- `include/pj_base/*_protocol.h`, `plugin_data_api.h`, `builtin_object_abi.h`, `plugin_abi_export.hpp` — the stable C-ABI surface for DataSource/MessageParser/Toolbox (the Dialog protocol header lives in `pj_plugins/dialog_protocol/`).
1010
- `proto/pj/` — canonical `.proto` wire contracts for the builtin types (see its README).

pj_base/CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ add_library(pj_base STATIC
1515
src/builtin/occupancy_grid_codec.cpp
1616
src/builtin/occupancy_grid_update_codec.cpp
1717
src/builtin/point_cloud_codec.cpp
18+
src/builtin/poses_in_frame_codec.cpp
1819
src/builtin/scene_entities_codec.cpp
1920
src/builtin/video_frame_codec.cpp
2021
src/number_parse.cpp
@@ -101,6 +102,7 @@ if(PJ_BUILD_TESTS)
101102
tests/video_frame_codec_test.cpp
102103
tests/scene_entities_codec_test.cpp
103104
tests/asset_video_codec_test.cpp
105+
tests/poses_in_frame_codec_test.cpp
104106
)
105107

106108
foreach(test_src ${PJ_BASE_TESTS})

pj_base/include/pj_base/builtin/builtin_object.hpp

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
#include "pj_base/builtin/occupancy_grid.hpp"
3939
#include "pj_base/builtin/occupancy_grid_update.hpp"
4040
#include "pj_base/builtin/point_cloud.hpp"
41+
#include "pj_base/builtin/poses_in_frame.hpp"
4142
#include "pj_base/builtin/robot_description.hpp"
4243
#include "pj_base/builtin/scene_entities.hpp"
4344
#include "pj_base/builtin/video_frame.hpp"
@@ -62,6 +63,7 @@ enum class BuiltinObjectType : uint16_t {
6263
kCameraInfo = 14, ///< sdk::CameraInfo — pinhole camera calibration (K/D/R/P).
6364
kOccupancyGridUpdate = 15, ///< sdk::OccupancyGridUpdate — incremental sub-rectangle patch for an OccupancyGrid.
6465
kLog = 16, ///< sdk::Log — textual log message (level + text + name).
66+
kPosesInFrame = 17, ///< sdk::PosesInFrame — array of poses in one reference frame.
6567
};
6668

6769
/// A-priori classification of a schema. Currently carries only the type;
@@ -106,6 +108,8 @@ struct SchemaClassification {
106108
return "kOccupancyGridUpdate";
107109
case BuiltinObjectType::kLog:
108110
return "kLog";
111+
case BuiltinObjectType::kPosesInFrame:
112+
return "kPosesInFrame";
109113
}
110114
return "kNone";
111115
}
@@ -161,6 +165,9 @@ struct SchemaClassification {
161165
if (s == "kLog") {
162166
return BuiltinObjectType::kLog;
163167
}
168+
if (s == "kPosesInFrame") {
169+
return BuiltinObjectType::kPosesInFrame;
170+
}
164171
return std::nullopt;
165172
}
166173

@@ -219,6 +226,9 @@ using BuiltinObject = std::any;
219226
if (t == typeid(Log)) {
220227
return BuiltinObjectType::kLog;
221228
}
229+
if (t == typeid(PosesInFrame)) {
230+
return BuiltinObjectType::kPosesInFrame;
231+
}
222232
return BuiltinObjectType::kNone;
223233
}
224234

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
/**
2+
* @file poses_in_frame.hpp
3+
* @brief Array of poses in a single reference frame at one instant.
4+
*/
5+
// Copyright 2026 Davide Faconti
6+
// SPDX-License-Identifier: Apache-2.0
7+
8+
#pragma once
9+
10+
#include <string>
11+
#include <vector>
12+
13+
#include "pj_base/builtin/frame_transforms.hpp" // Pose / Vector3 / Quaternion
14+
#include "pj_base/types.hpp"
15+
16+
namespace PJ {
17+
namespace sdk {
18+
19+
/// An array of poses in a single reference frame at one instant
20+
/// (geometry_msgs/PoseArray, foxglove.PosesInFrame). Pure data: rendering
21+
/// style (arrow vs triad, size, color) is chosen by the viewer at draw time.
22+
struct PosesInFrame {
23+
/// Acquisition time in nanoseconds (0 when the source had no timestamp).
24+
Timestamp timestamp_ns = 0;
25+
/// Frame all poses are expressed in.
26+
std::string frame_id;
27+
/// The poses. Order is meaningful only to the producer.
28+
std::vector<Pose> poses;
29+
};
30+
31+
} // namespace sdk
32+
} // namespace PJ
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
#pragma once
2+
// Copyright 2026 Davide Faconti
3+
// SPDX-License-Identifier: Apache-2.0
4+
5+
#include <cstddef>
6+
#include <cstdint>
7+
#include <string_view>
8+
#include <vector>
9+
10+
#include "pj_base/builtin/poses_in_frame.hpp"
11+
#include "pj_base/expected.hpp"
12+
13+
namespace PJ {
14+
15+
inline constexpr std::string_view kSchemaPosesInFrame = "PJ.PosesInFrame";
16+
17+
/// Serializes sdk::PosesInFrame to canonical PJ.PosesInFrame wire bytes
18+
/// (see pj_base/proto/pj/PosesInFrame.proto).
19+
[[nodiscard]] std::vector<uint8_t> serializePosesInFrame(const sdk::PosesInFrame& poses);
20+
21+
/// Decodes canonical PJ.PosesInFrame wire bytes into sdk::PosesInFrame.
22+
[[nodiscard]] Expected<sdk::PosesInFrame> deserializePosesInFrame(const uint8_t* data, size_t size);
23+
24+
} // namespace PJ

pj_base/include/pj_base/builtin_object_abi.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ typedef enum PJ_builtin_object_type_t {
5656
PJ_BUILTIN_OBJECT_TYPE_CAMERA_INFO = 14,
5757
PJ_BUILTIN_OBJECT_TYPE_OCCUPANCY_GRID_UPDATE = 15,
5858
PJ_BUILTIN_OBJECT_TYPE_LOG = 16,
59+
PJ_BUILTIN_OBJECT_TYPE_POSES_IN_FRAME = 17,
5960
/* Reserve future types; appended at the tail. Numeric values are stable
6061
* across releases — never renumber. Each new value here must match the
6162
* matching kFoo entry in BuiltinObjectType (builtin_object.hpp). */

0 commit comments

Comments
 (0)