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
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ object store ABI in `V4_STORE.md`.
custom payload as a builtin. `BuiltinObjectType::kNone` means
"scalar/unknown classification", not "storage forbidden".

The full list (17 concrete types) and their exact fields are in
The full list (18 concrete types) and their exact fields are in
`docs/builtin_type.md` and one header each under
`pj_base/include/pj_base/builtin/` (the `builtin_object.hpp` enum is the
authoritative roster — trust it over prose that may lag behind newer additions).
Expand Down Expand Up @@ -80,7 +80,7 @@ into the returned object so large payloads stay zero-copy. See
## Two storage families → two byte strategies

- **Byte-backed** (Image, DepthImage, PointCloud, CompressedPointCloud,
OccupancyGrid(+Update), VoxelGrid, Mesh3D, VideoFrame): potentially megabytes.
OccupancyGrid(+Update), VoxelGrid, GridMap, Mesh3D, VideoFrame): potentially megabytes.
Keep the payload a zero-copy `Span<const uint8_t>` anchored by a `BufferAnchor`
(typically a `shared_ptr<vector<uint8_t>>`); only allocate new bytes if a
conversion is unavoidable.
Expand Down
19 changes: 19 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,25 @@
All notable changes to `plotjuggler_sdk` are recorded here. Versioning policy is in
[`CLAUDE.md`](./CLAUDE.md) → "Release Versioning".

## [0.26.0]

### Feature: `GridMap` canonical builtin object (MINOR)

`sdk::GridMap` (`pj_base/builtin/grid_map.hpp`, `BuiltinObjectType::kGridMap` = 20,
`PJ_BUILTIN_OBJECT_TYPE_GRID_MAP`) is a 2D grid whose cells carry named channels — the
layered, generic-valued sibling of `OccupancyGrid` for elevation maps and multi-layer
costmaps (`grid_map_msgs/GridMap`, `foxglove.Grid`). Row-major fixed-size cell records
described by the shared `PointField` channel model, the packed layout `foxglove.Grid` uses,
so producers with that layout hand `data` over zero-copy; a NaN in a float channel means
"no data". `PJ.GridMap` wire format + `grid_map_codec.hpp` (`serializeGridMap`,
`deserializeGridMap`, and `validateGridMap` for the full layout check once spliced bytes are
attached), an entry in the type-erased dispatcher, in the frozen splice table (`data` =
field 10) and in both host splice-attachment paths, a `GridMapBuilder` (`gridMap()`) in the
parser-module `ObjectWriter`, and `sdk::Vector2` in the geometry vocabulary (already present
as `PJ.Vector2` on the wire). The `PointField` wire helpers shared by the PointCloud,
VoxelGrid and GridMap codecs now live in one private header. Additive: no existing struct,
slot, or wire format changes.

## [0.25.0]

### Feature: plugin-authoring CMake helpers ship with the SDK (MINOR)
Expand Down
4 changes: 2 additions & 2 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,10 @@ not in the PJ4 superproject. This file is the root navigation node for the whole
### Modules

- **pj_base** — vocabulary types (`Timestamp`, `DatasetId`, `Expected<T>`, `Span<T>`, type trees),
the canonical builtin object vocabulary (`pj_base/builtin/`: 17 struct headers — Image, DepthImage,
the canonical builtin object vocabulary (`pj_base/builtin/`: 18 struct headers — Image, DepthImage,
PointCloud, CompressedPointCloud, OccupancyGrid(+Update), Mesh3D, VideoFrame,
SceneEntities, RobotDescription, CameraInfo, Log, ImageAnnotations, FrameTransforms, PosesInFrame,
VoxelGrid, PlotMarkers) and their canonical wire codecs, the C-ABI protocol headers for
VoxelGrid, PlotMarkers, GridMap) and their canonical wire codecs, the C-ABI protocol headers for
DataSource/MessageParser/Toolbox + the C++ SDK base classes / host-view helpers built on them, the
standalone C++17 functional parser-module authoring kit (`pj_base/parser_module/`), the host-side
wasm parser-module manifest custom-section codec, and the test-only static WASI ABI auditor. The
Expand Down
2 changes: 1 addition & 1 deletion VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
0.25.0
0.26.0
56 changes: 55 additions & 1 deletion docs/builtin_type.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ The public headers live under:
#include <pj_base/builtin/camera_info.hpp>
#include <pj_base/builtin/log.hpp>
#include <pj_base/builtin/voxel_grid.hpp>
#include <pj_base/builtin/grid_map.hpp>
#include <pj_base/builtin/plot_markers.hpp>
// Codecs — one per type, all share the canonical PJ.<Type> wire format under pj_base/proto/pj/.
#include <pj_base/builtin/builtin_object_codec.hpp>
Expand All @@ -50,6 +51,7 @@ The public headers live under:
#include <pj_base/builtin/camera_info_codec.hpp>
#include <pj_base/builtin/log_codec.hpp>
#include <pj_base/builtin/voxel_grid_codec.hpp>
#include <pj_base/builtin/grid_map_codec.hpp>
#include <pj_base/builtin/plot_markers_codec.hpp>
```

Expand Down Expand Up @@ -97,7 +99,7 @@ Builtin objects fall into two serialization families:

| Family | Current types | Storage model | Codec policy |
|--------|---------------|---------------|--------------|
| Byte-backed views | `Image`, `DepthImage`, `PointCloud`, `CompressedPointCloud`, `OccupancyGrid`, `OccupancyGridUpdate`, `VoxelGrid`, `Mesh3D`, `VideoFrame` | Header fields live in the SDK struct; payload bytes live behind `Span<const uint8_t>` plus `BufferAnchor`. | Preserve zero-copy views while in-process; use the canonical codec at storage or C ABI boundaries. If no source anchor exists, materialize before returning a long-lived view. |
| Byte-backed views | `Image`, `DepthImage`, `PointCloud`, `CompressedPointCloud`, `OccupancyGrid`, `OccupancyGridUpdate`, `VoxelGrid`, `GridMap`, `Mesh3D`, `VideoFrame` | Header fields live in the SDK struct; payload bytes live behind `Span<const uint8_t>` plus `BufferAnchor`. | Preserve zero-copy views while in-process; use the canonical codec at storage or C ABI boundaries. If no source anchor exists, materialize before returning a long-lived view. |
| Owned values | `ImageAnnotations`, `FrameTransforms`, `SceneEntities`, `RobotDescription`, `CameraInfo`, `Log`, `PosesInFrame`, `PlotMarkers` | SDK structs own their vectors/strings/scalars directly. | Canonical codecs serialize the owned value to the protobuf-wire payload described by the `.proto` contract, using shared private wire primitives. `RobotDescription.text` remains raw URDF/SDF/MJCF source inside its small canonical envelope. |

Canonical `.proto` files live under `pj_base/proto/pj` and act as the wire
Expand Down Expand Up @@ -138,6 +140,7 @@ annotations, frame transforms, or no builtin object.
| `kLog` | `PJ::sdk::Log` | Textual log message (severity level + text + originating name). |
| `kPosesInFrame` | `PJ::sdk::PosesInFrame` | Array of poses in one frame (PoseArray / particle clouds); styling is viewer-side. |
| `kVoxelGrid` | `PJ::sdk::VoxelGrid` | Dense 3D voxel grid (occupancy/cost/ESDF/semantic); the volumetric sibling of `OccupancyGrid`. |
| `kGridMap` | `PJ::sdk::GridMap` | 2D grid of per-cell channels (elevation maps, layered costmaps) in world coordinates. |
| `kPlotMarkers` | `PJ::sdk::PlotMarkers` | Findings on a time-series plot (regions, events, bands, and labels). |

### Object-topic renderer metadata
Expand Down Expand Up @@ -618,6 +621,55 @@ viewer-side, so one type serves occupancy/cost/ESDF/semantic grids.
`pj_base/builtin/voxel_grid_codec.hpp` serializes and deserializes this type
using the canonical `PJ.VoxelGrid` protobuf wire format.

## GridMap

`GridMap` is a 2D grid whose cells carry named channels — the layered,
generic-valued sibling of `OccupancyGrid` — for elevation maps, multi-layer
costmaps, and terrain classification (e.g. `grid_map_msgs/GridMap`,
`foxglove.Grid`).

It is a byte-backed view: `column_count * row_count` fixed-size cell records
live in `data` (a `Span<const uint8_t>` + `BufferAnchor`) in row-major order
(x / column fastest), `row_stride` bytes per row, and the channels inside one
record are described by `fields` — the same `PointField` model `PointCloud`
and `VoxelGrid` use. The packed cell layout is the one `foxglove.Grid` uses,
so a parser can hand that message's `data` over zero-copy; the header and
field descriptors are converted (own wire field numbers, an explicit
`row_count`, and a `PointField` datatype numbering that differs from
`PackedElementField`'s). A producer with another layout (grid_map's
column-major ring buffer) transcodes once at the boundary.

Unlike `OccupancyGrid` (one fixed `-1`/`0..100` channel), the per-cell **value
is generic** and there can be many channels per cell. A NaN in a float channel
means "no data" for that cell; integer channels have no empty sentinel. Which
channel is height, which one drives color, and the colormap are viewer-side
choices. Channel-name conventions consumers may rely on: `elevation` is the
conventional height channel; `red`, `green`, `blue`, `alpha` are the RGBA color
channels, as in `foxglove.Grid`.

| Field | Type | Notes |
|-------|------|-------|
| `timestamp_ns` | `Timestamp` | Time of the grid. `0` when the source had none. |
| `frame_id` | `std::string` | Source coordinate frame; 3D consumers TF-transform from it. |
| `origin` | `Pose` | Corner of cell `(0,0)` in `frame_id`; the grid lies in the pose's local xy-plane. |
| `cell_size` | `Vector2` | Metric cell size along local x (columns) and y (rows), meters. |
| `column_count` / `row_count` | `uint32_t` | Cells along x / y. |
| `cell_stride` / `row_stride` | `uint32_t` | Byte spacing of a cell record / a row (padding allowed). |
| `fields` | `std::vector<PointField>` | Per-cell channel layout. |
| `data` | `Span<const uint8_t>` + `BufferAnchor` | Packed cell records in row-major order. |

Cell `(c, r)` has its center at `origin ∘ ((c + .5)*cell_size.x, (r + .5)*cell_size.y, 0)`.

`pj_base/builtin/grid_map_codec.hpp` serializes and deserializes this type
using the canonical `PJ.GridMap` protobuf wire format. Decoding rejects a
layout the cell math could not index safely (a field with an unknown datatype
or zero count, a field reaching past `cell_stride`, a zero stride with cells
declared, a row shorter than its columns, `data` shorter than
`row_count * row_stride`); a wire that carries no `data` is the functional-v2
splice form and decodes with an empty span, so hosts that attach spliced bytes
and consumers that index cells run `validateGridMap()` once the bytes are in
place.

## Conversion Examples

| Source type | Canonical builtin type | Conversion intent |
Expand All @@ -637,6 +689,8 @@ using the canonical `PJ.VoxelGrid` protobuf wire format.
| 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. |
| 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. |
| `foxglove.VoxelGrid` / `costmap_2d/VoxelGrid` | `VoxelGrid` | Map counts/strides/`cell_size`/`origin` into the struct; keep voxel bytes zero-copy in Z-Y-X order. The draw predicate is viewer-side. |
| `foxglove.Grid` / `foxglove_msgs/Grid` | `GridMap` | Map `pose`/`cell_size`/strides into the struct and keep cell bytes zero-copy; `row_count` is `data.size / row_stride`; each `PackedElementField` becomes a `PointField` with the datatype renumbered (Foxglove: UINT8=1, INT8=2, UINT16=3, INT16=4, UINT32=5, INT32=6, FLOAT32=7, FLOAT64=8) and `count = 1`. |
| ROS `grid_map_msgs/GridMap` | `GridMap` | Transcode once: flip both axes (grid_map puts index (0,0) at the +x/+y corner), unwrap the ring-buffer start indices, interleave the column-major per-layer arrays into row-major cell records (one float32 channel per layer), and move the center pose to the corner of cell (0,0). |

The builtin type is the boundary object. After conversion, consumers should not
need to know which third-party schema produced it.
4 changes: 2 additions & 2 deletions pj_base/CLAUDE.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
# pj_base — SDK vocabulary, builtin object schemas, and the C plugin ABI

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 17 canonical *builtin object* schemas (`sdk::Image`, `PointCloud`, `DepthImage`, `OccupancyGrid`, `VoxelGrid`, `FrameTransforms`, …) and all 17 wire codecs; 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` — and additive extension headers such as `parser_functional_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`.
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 18 canonical *builtin object* schemas (`sdk::Image`, `PointCloud`, `DepthImage`, `OccupancyGrid`, `VoxelGrid`, `FrameTransforms`, …) and all 18 wire codecs; 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` — and additive extension headers such as `parser_functional_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`.

## Layout
- `include/pj_base/` — vocabulary primitives: `types.hpp`, `time.hpp` (absolute time spine: `Timepoint`/`Duration` + `fromRaw`/`toRaw`), `type_tree.hpp`, `dataset.hpp`, `expected.hpp`, `span.hpp`, `number_parse.hpp`, `assert.hpp`, `diagnostic_sink.hpp`, `buffer_anchor.hpp`.
- `include/pj_base/builtin/` — 17 builtin object struct headers (`*.hpp`; stable numeric tags with values 2 and 12 permanently reserved) + all 17 wire codecs (`*_codec.hpp`) + the `BuiltinObject` (`std::any`) type-erased holder and type-erased codec dispatcher.
- `include/pj_base/builtin/` — 18 builtin object struct headers (`*.hpp`; stable numeric tags with values 2 and 12 permanently reserved) + all 18 wire codecs (`*_codec.hpp`) + the `BuiltinObject` (`std::any`) type-erased holder and type-erased codec dispatcher.
- `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/`.
- `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/`).
- `proto/pj/` — canonical `.proto` wire contracts for the builtin types (see its README).
Expand Down
2 changes: 2 additions & 0 deletions pj_base/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ add_library(pj_base STATIC
src/builtin/scene_entities_codec.cpp
src/builtin/video_frame_codec.cpp
src/builtin/voxel_grid_codec.cpp
src/builtin/grid_map_codec.cpp
src/number_parse.cpp
src/parser_module_abi.cpp
src/parser_module_manifest.cpp
Expand Down Expand Up @@ -190,6 +191,7 @@ if(PJ_BUILD_TESTS)
tests/time_spine_test.cpp
tests/poses_in_frame_codec_test.cpp
tests/voxel_grid_codec_test.cpp
tests/grid_map_codec_test.cpp
tests/plot_markers_codec_test.cpp
)

Expand Down
10 changes: 10 additions & 0 deletions pj_base/include/pj_base/builtin/builtin_object.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
#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/grid_map.hpp"
#include "pj_base/builtin/image.hpp"
#include "pj_base/builtin/image_annotations.hpp"
#include "pj_base/builtin/log.hpp"
Expand Down Expand Up @@ -67,6 +68,7 @@ enum class BuiltinObjectType : uint16_t {
kPosesInFrame = 17, ///< sdk::PosesInFrame — array of poses in one reference frame.
kVoxelGrid = 18, ///< sdk::VoxelGrid — dense 3D voxel grid (occupancy/cost/ESDF/semantic).
kPlotMarkers = 19, ///< sdk::PlotMarkers — findings on a time-series plot (regions, events, bands, labels).
kGridMap = 20, ///< sdk::GridMap — 2D grid of per-cell channels (elevation maps, layered costmaps).
};

/// A-priori classification of a schema. Currently carries only the type;
Expand Down Expand Up @@ -115,6 +117,8 @@ struct SchemaClassification {
return "kVoxelGrid";
case BuiltinObjectType::kPlotMarkers:
return "kPlotMarkers";
case BuiltinObjectType::kGridMap:
return "kGridMap";
}
return "kNone";
}
Expand Down Expand Up @@ -176,6 +180,9 @@ struct SchemaClassification {
if (s == "kPlotMarkers") {
return BuiltinObjectType::kPlotMarkers;
}
if (s == "kGridMap") {
return BuiltinObjectType::kGridMap;
}
return std::nullopt;
}

Expand Down Expand Up @@ -240,6 +247,9 @@ using BuiltinObject = std::any;
if (t == typeid(PlotMarkers)) {
return BuiltinObjectType::kPlotMarkers;
}
if (t == typeid(GridMap)) {
return BuiltinObjectType::kGridMap;
}
return BuiltinObjectType::kNone;
}

Expand Down
7 changes: 7 additions & 0 deletions pj_base/include/pj_base/builtin/frame_transforms.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,13 @@
namespace PJ {
namespace sdk {

/// Vector in 2D space (a direction or an extent, e.g. a grid's cell size).
struct Vector2 {
double x = 0.0;
double y = 0.0;
bool operator==(const Vector2&) const = default;
};

/// Translation vector in 3D space.
struct Vector3 {
double x = 0.0;
Expand Down
Loading
Loading