Appendix D: Extension Profiles
These extensions provide domain-specific capabilities beyond the Core profile. The Sensing Common module supplies reusable sensing metadata, ROI negotiation structures, and codec/payload descriptors that the specialized sensor profiles build upon. The VIO profile carries raw and fused IMU/magnetometer samples. The Vision profile shares camera metadata, encoded frames, and optional feature tracks for perception pipelines. The SLAM Frontend profile adds features and keyframes for SLAM and SfM pipelines. The Semantics profile allows 2D and 3D object detections to be exchanged for AR, robotics, and analytics use cases. The Radar profile provides detection-centric radar metadata and per-frame detection sets, plus a tensor transport path for raw or processed radar data cubes used in ISAC and ML workloads. The Lidar profile transports compressed point clouds, associated metadata, and optional detections for mapping and perception workloads. The AR+Geo profile adds GeoPose, frame transforms, and geo-anchoring structures, which allow clients to align local coordinate systems with global reference frames and support persistent AR content. The Mapping profile provides map lifecycle metadata, multi-source pose graph edge types, inter-map alignment primitives, and lifecycle events for multi-agent collaborative mapping. The Spatial Events profile provides typed, spatially-scoped events, zone definitions, and zone state summaries for smart infrastructure alerting, anomaly detection, and capacity management.
Common type aliases and geometry primitives are defined once in Appendix A. Extension modules import those shared definitions and MUST NOT re-declare them.
Sensing Common Extension
Shared base types, enums, and ROI negotiation utilities reused by all sensing profiles (radar, lidar, vision).
// SPDX-License-Identifier: MIT
// SpatialDDS Sensing Common 1.5 (Extension module)
#ifndef SPATIAL_CORE_INCLUDED
#define SPATIAL_CORE_INCLUDED
#include "core.idl"
#endif
module spatial { module sensing { module common {
const string MODULE_ID = "spatial.sensing.common/1.5";
// --- Standard sizing tiers ---
// Use these to bound sequences for detections and other per-frame arrays.
const uint32 SZ_TINY = 64;
const uint32 SZ_SMALL = 256;
const uint32 SZ_MEDIUM = 2048;
const uint32 SZ_LARGE = 8192;
const uint32 SZ_XL = 32768;
// Reuse Core primitives (time, pose, blob references)
typedef builtin::Time Time;
typedef spatial::core::PoseSE3 PoseSE3;
typedef spatial::core::BlobRef BlobRef;
typedef spatial::common::FrameRef FrameRef;
// ---- Axes & Regions (for tensors or scans) ----
enum AxisEncoding {
@value(0) AXIS_CENTERS,
@value(1) AXIS_LINSPACE
};
// Compact parametric axis definition
@extensibility(APPENDABLE) struct Linspace {
double start; // first point
double step; // spacing (may be negative for descending axes)
uint32 count; // number of samples (>=1)
};
// Discriminated union: carries only one encoding on wire
@extensibility(APPENDABLE) union AxisSpec switch (AxisEncoding) {
case AXIS_CENTERS: sequence<double, 65535> centers;
case AXIS_LINSPACE: Linspace lin;
};
@extensibility(APPENDABLE) struct Axis {
string name; // "range","azimuth","elevation","doppler","time","channel"
string unit; // "m","deg","m/s","Hz","s",...
AxisSpec spec; // encoding of the axis samples (centers or linspace)
};
@extensibility(APPENDABLE) struct ROI {
// Range bounds (meters). When has_range == false, consumers MUST ignore range_min/range_max.
boolean has_range;
float range_min;
float range_max;
// Azimuth bounds (degrees). When has_azimuth == false, azimuth bounds are unspecified.
boolean has_azimuth;
float az_min;
float az_max;
// Elevation bounds (degrees). When has_elevation == false, elevation bounds are unspecified.
boolean has_elevation;
float el_min;
float el_max;
// Doppler bounds (m/s). When has_doppler == false, doppler_min/doppler_max are unspecified.
boolean has_doppler;
float dop_min;
float dop_max;
// Image-plane ROI for vision (pixels). When has_image_roi == false, u/v bounds are unspecified.
boolean has_image_roi;
int32 u_min;
int32 v_min;
int32 u_max;
int32 v_max;
// Indicates this ROI covers the entire valid domain of its axes. When true, all numeric bounds may be ignored.
boolean global;
};
// ---- Codecs / Payload kinds (shared enums) ----
enum Codec {
@value(0) CODEC_NONE,
@value(1) LZ4,
@value(2) ZSTD,
@value(3) GZIP,
@value(10) DRACO, // geometry compression
@value(20) JPEG,
@value(21) H264,
@value(22) H265,
@value(23) AV1,
@value(40) FP8Q,
@value(41) FP4Q,
@value(42) AE_V1
};
enum PayloadKind {
@value(0) DENSE_TILES, // tiled dense blocks (e.g., tensor tiles)
@value(1) SPARSE_COO, // sparse indices + values
@value(2) LATENT, // learned latent vectors
@value(10) BLOB_GEOMETRY, // PCC/PLY/glTF+Draco
@value(11) BLOB_RASTER // JPEG/GOP chunk(s)
};
enum SampleType { // post-decode voxel/point sample type
@value(0) U8_MAG,
@value(1) F16_MAG,
@value(2) CF16,
@value(3) CF32,
@value(4) MAGPHASE_S8
};
// ---- Stream identity & calibration header shared by sensors ----
@extensibility(APPENDABLE) struct StreamMeta {
@key string stream_id; // stable id for this sensor stream
FrameRef frame_ref; // mounting frame (Core frame naming)
PoseSE3 T_bus_sensor; // extrinsics (sensor in bus frame)
double nominal_rate_hz; // advertised cadence
string schema_version; // MUST be "spatial.sensing.common/1.5"
};
// ---- Frame index header shared by sensors (small, on-bus) ----
@extensibility(APPENDABLE) struct FrameHeader {
@key string stream_id;
uint64 frame_seq;
Time t_start;
Time t_end;
// Optional sensor pose at acquisition (moving platforms)
boolean has_sensor_pose;
PoseSE3 sensor_pose;
// data pointers: heavy bytes referenced as blobs
sequence<BlobRef, SZ_SMALL> blobs;
};
// ---- Quality & health (uniform across sensors) ----
enum Health {
@value(0) OK,
@value(1) DEGRADED,
@value(2) ERROR
};
@extensibility(APPENDABLE) struct FrameQuality {
boolean has_snr_db;
float snr_db; // valid when has_snr_db == true
float percent_valid; // 0..100
Health health;
string note; // short diagnostic
};
// ---- ROI request/reply (control-plane pattern) ----
@extensibility(APPENDABLE) struct ROIRequest {
@key string stream_id;
uint64 request_id;
Time t_start; Time t_end;
ROI roi;
boolean wants_payload_kind; PayloadKind desired_payload_kind;
boolean wants_codec; Codec desired_codec;
boolean wants_sample_type; SampleType desired_sample_type;
int32 max_bytes; // -1 for unlimited
};
@extensibility(APPENDABLE) struct ROIReply {
@key string stream_id;
uint64 request_id;
// Typically returns new frames whose blobs contain only the ROI
sequence<spatial::sensing::common::FrameHeader, 64> frames;
};
}; }; };
Standard Sequence Bounds (Normative)
| Payload | Recommended Bound | Rationale |
|---|---|---|
| 2D Detections (per frame) | SZ_MEDIUM (2048) |
Typical object detectors |
| 3D Detections (LiDAR) | SZ_SMALL (256) |
Clusters/objects, not raw points |
| Radar Detections (micro-dets) | SZ_XL (32768) |
Numerous sparse returns per frame |
| Keypoints/Tracks (per frame) | SZ_LARGE (8192) |
Feature-rich frames |
Producers SHOULD choose the smallest tier that covers real workloads; exceeding these bounds requires a new profile minor.
Axis Encoding (Normative)
The Axis struct embeds a discriminated union to ensure only one encoding is transmitted on the wire.
enum AxisEncoding { AXIS_CENTERS = 0, AXIS_LINSPACE = 1 };
@extensibility(APPENDABLE) struct Linspace { double start; double step; uint32 count; };
@extensibility(APPENDABLE) union AxisSpec switch (AxisEncoding) {
case AXIS_CENTERS: sequence<double, 65535> centers;
case AXIS_LINSPACE: Linspace lin;
default: ;
};
@extensibility(APPENDABLE) struct Axis { string name; string unit; AxisSpec spec; };
AXIS_CENTERS— Explicit sample positions carried asdoublevalues.AXIS_LINSPACE— Uniform grid defined bystart + i * stepfori ∈ [0, count‑1].- Negative
stepindicates descending axes. countMUST be ≥ 1 andstep * (count – 1) + startyields the last coordinate.
IDL Tooling Notes (Non-Consecutive Enums)
Several enumerations in the SpatialDDS 1.5 profiles use intentionally
sparse or non-consecutive numeric values. These enums are designed for
forward extensibility (e.g., reserving ranges for future codecs, layouts, or
pixel formats). Because of this, certain DDS toolchains (including Cyclone
DDS’s idlc) may emit non-fatal warnings such as:
“enum literal values are not consecutive”
These warnings do not indicate a schema error. All affected enums are valid IDL4.x and interoperable on the wire.
The intentionally sparse enums are:
- CovarianceType (types.idl)
- Codec (common.idl)
- PayloadKind (common.idl)
- RigRole (vision.idl)
- RadSensorType (rad.idl)
- RadTensorLayout (rad.idl)
- CloudEncoding (lidar.idl)
- ColorSpace (vision.idl)
- PixFormat (vision.idl)
No changes are required for implementers. These warnings may be safely ignored.
VIO / Inertial Extension
Raw IMU/mag samples, 9-DoF bundles, and fused state outputs.
// SPDX-License-Identifier: MIT
// SpatialDDS VIO/Inertial 1.5
#ifndef SPATIAL_CORE_INCLUDED
#define SPATIAL_CORE_INCLUDED
#include "core.idl"
#endif
module spatial {
module vio {
const string MODULE_ID = "spatial.vio/1.5";
typedef builtin::Time Time;
typedef spatial::common::FrameRef FrameRef;
// IMU calibration
@extensibility(APPENDABLE) struct ImuInfo {
@key string imu_id;
FrameRef frame_ref;
double accel_noise_density; // (m/s^2)/√Hz
double gyro_noise_density; // (rad/s)/√Hz
double accel_random_walk; // (m/s^3)/√Hz
double gyro_random_walk; // (rad/s^2)/√Hz
Time stamp;
};
// Raw IMU sample
@extensibility(APPENDABLE) struct ImuSample {
@key string imu_id;
spatial::common::Vec3 accel; // m/s^2
spatial::common::Vec3 gyro; // rad/s
Time stamp;
string source_id;
uint64 seq;
};
// Magnetometer
@extensibility(APPENDABLE) struct MagnetometerSample {
@key string mag_id;
spatial::common::Vec3 mag; // microtesla
Time stamp;
FrameRef frame_ref;
string source_id;
uint64 seq;
};
// Convenience raw 9-DoF bundle
@extensibility(APPENDABLE) struct SensorFusionSample {
@key string fusion_id; // e.g., device id
spatial::common::Vec3 accel; // m/s^2
spatial::common::Vec3 gyro; // rad/s
spatial::common::Vec3 mag; // microtesla
Time stamp;
FrameRef frame_ref;
string source_id;
uint64 seq;
};
// Fused state (orientation ± position)
enum FusionMode {
@value(0) ORIENTATION_3DOF,
@value(1) ORIENTATION_6DOF,
@value(2) POSE_6DOF
};
enum FusionSourceKind {
@value(0) EKF,
@value(1) AHRS,
@value(2) VIO_FUSED,
@value(3) IMU_ONLY,
@value(4) MAG_AIDED,
@value(5) AR_PLATFORM
};
@extensibility(APPENDABLE) struct FusedState {
@key string fusion_id;
FusionMode mode;
FusionSourceKind source_kind;
spatial::common::QuaternionXYZW q; // quaternion (x,y,z,w) in GeoPose order
boolean has_position;
spatial::common::Vec3 t; // meters, in frame_ref
boolean has_gravity;
spatial::common::Vec3 gravity; // m/s^2
boolean has_lin_accel;
spatial::common::Vec3 lin_accel; // m/s^2
boolean has_gyro_bias;
spatial::common::Vec3 gyro_bias; // rad/s
boolean has_accel_bias;
spatial::common::Vec3 accel_bias; // m/s^2
boolean has_cov_orient;
spatial::common::Mat3x3 cov_orient; // 3x3 covariance
boolean has_cov_pos;
spatial::common::Mat3x3 cov_pos; // 3x3 covariance
Time stamp;
FrameRef frame_ref;
string source_id;
uint64 seq;
double quality; // 0..1
};
}; // module vio
};
Vision Extension
Camera intrinsics, video frames, and keypoints/tracks for perception and analytics pipelines. ROI semantics follow §2 Conventions for global normative rules; axes use the Sensing Common AXIS_CENTERS/AXIS_LINSPACE union encoding. See §2 Conventions for global normative rules.
Pre-Rectified Images (Normative)
When images have been rectified (undistorted) before publication, producers MUST set dist = NONE, dist_params to an empty sequence, and model = PINHOLE. Consumers receiving dist = NONE MUST NOT apply any distortion correction.
Image Dimensions (Normative)
CamIntrinsics.width and CamIntrinsics.height are REQUIRED and MUST be populated from the actual image dimensions. A VisionMeta sample with width = 0 or height = 0 is malformed and consumers MAY reject it.
Distortion Model Mapping (Informative)
Vision uses CamModel + Distortion, while SLAM Frontend uses DistortionModelKind. Implementers bridging the two SHOULD map as follows:
| Vision | SLAM Frontend | Notes |
|---|---|---|
Distortion.NONE |
DistortionModelKind.NONE |
No distortion |
Distortion.RADTAN |
DistortionModelKind.RADTAN |
Brown-Conrady |
Distortion.KANNALA_BRANDT |
DistortionModelKind.KANNALA_BRANDT |
Fisheye |
CamModel.FISHEYE_EQUIDISTANT |
DistortionModelKind.EQUIDISTANT |
Equivalent naming |
// SPDX-License-Identifier: MIT
// SpatialDDS Vision (sensing.vision) 1.5 — Extension profile
#ifndef SPATIAL_CORE_INCLUDED
#define SPATIAL_CORE_INCLUDED
#include "core.idl"
#endif
#ifndef SPATIAL_SENSING_COMMON_INCLUDED
#define SPATIAL_SENSING_COMMON_INCLUDED
#include "common.idl"
#endif
module spatial { module sensing { module vision {
// Module identifier for discovery and schema registration
const string MODULE_ID = "spatial.sensing.vision/1.5";
// Reuse Core + Sensing Common
typedef builtin::Time Time;
typedef spatial::core::PoseSE3 PoseSE3;
typedef spatial::core::BlobRef BlobRef;
typedef spatial::common::FrameRef FrameRef;
typedef spatial::sensing::common::Codec Codec; // JPEG/H264/H265/AV1, etc.
typedef spatial::sensing::common::PayloadKind PayloadKind; // use BLOB_RASTER for frames/GOPs
typedef spatial::sensing::common::SampleType SampleType;
typedef spatial::sensing::common::Axis Axis;
typedef spatial::sensing::common::ROI ROI;
typedef spatial::sensing::common::StreamMeta StreamMeta;
typedef spatial::sensing::common::FrameHeader FrameHeader;
typedef spatial::sensing::common::FrameQuality FrameQuality;
typedef spatial::sensing::common::ROIRequest ROIRequest;
typedef spatial::sensing::common::ROIReply ROIReply;
// ROI bounds follow Sensing Common presence flags.
// Axis samples are encoded via the Sensing Common union (AXIS_CENTERS or AXIS_LINSPACE).
// Camera / imaging specifics
enum CamModel {
@value(0) PINHOLE,
@value(1) FISHEYE_EQUIDISTANT,
@value(2) KB_4,
@value(3) OMNI
};
enum Distortion {
@value(0) NONE,
@value(1) RADTAN,
@value(2) KANNALA_BRANDT
};
enum PixFormat {
@value(0) UNKNOWN,
@value(1) YUV420,
@value(2) RGB8,
@value(3) BGR8,
@value(4) RGBA8,
@value(5) DEPTH16, // 16-bit unsigned depth image (mm)
@value(10) RAW10,
@value(12) RAW12,
@value(16) RAW16
};
// DEPTH16 frames carry per-pixel unsigned 16-bit depth values. The default
// unit is millimeters (range 0-65535 mm ~= 0-65.5 m), consistent with
// OpenNI, RealSense SDK, and ARKit conventions. A value of 0 denotes no
// measurement (invalid/missing depth). Publishers using a different unit
// (e.g., 100 um for sub-millimeter sensors) MUST include a depth_unit key in
// the corresponding VisionMeta.base.attributes sequence with json value
// {"unit": "100um"} (or "mm", "m"). When depth_unit is absent, consumers
// MUST assume millimeters.
//
// A depth stream is published as a separate VisionMeta + VisionFrame pair
// with pix = DEPTH16 and a stream_id distinct from the co-located color
// stream. The rig_id field links the depth and color streams for spatial
// alignment; CamIntrinsics carries the depth camera's intrinsic calibration.
// For sensors with factory-aligned depth and color (e.g., iPhone LiDAR),
// both streams share the same CamIntrinsics and FrameRef.
enum ColorSpace {
@value(0) SRGB,
@value(1) REC709,
@value(2) REC2020,
@value(10) LINEAR
};
enum RigRole {
@value(0) LEFT,
@value(1) RIGHT,
@value(2) CENTER,
@value(3) FRONT,
@value(4) FRONT_LEFT,
@value(5) FRONT_RIGHT,
@value(6) BACK,
@value(7) BACK_LEFT,
@value(8) BACK_RIGHT,
@value(9) AUX,
@value(10) PANORAMIC, // stitched panoramic view
@value(11) EQUIRECTANGULAR // equirectangular 360° projection
};
@extensibility(APPENDABLE) struct CamIntrinsics {
CamModel model;
uint16 width; uint16 height; // REQUIRED: image dimensions in pixels
float fx; float fy; float cx; float cy;
Distortion dist; // NONE for pre-rectified images
sequence<float,16> dist_params; // empty when dist == NONE
float shutter_us; // exposure time
float readout_us; // rolling-shutter line time (0=global)
PixFormat pix; ColorSpace color;
string calib_version; // hash or tag
};
// Static description — RELIABLE + TRANSIENT_LOCAL (late joiners receive the latest meta)
@extensibility(APPENDABLE) struct VisionMeta {
@key string stream_id;
StreamMeta base; // frame_ref, T_bus_sensor, nominal_rate_hz
CamIntrinsics K; // intrinsics
RigRole role; // for stereo/rigs
string rig_id; // shared id across synchronized cameras
// Default payload (frames ride as blobs)
Codec codec; // JPEG/H264/H265/AV1 or NONE
PixFormat pix; // for RAW payloads
ColorSpace color;
string schema_version; // MUST be "spatial.sensing.vision/1.5"
};
// Per-frame index — BEST_EFFORT + KEEP_LAST=1 (large payloads referenced via blobs)
@extensibility(APPENDABLE) struct VisionFrame {
@key string stream_id;
uint64 frame_seq;
FrameHeader hdr; // t_start/t_end, optional sensor_pose, blobs[]
// May override meta per-frame
Codec codec;
PixFormat pix;
ColorSpace color;
boolean has_line_readout_us;
float line_readout_us; // valid when has_line_readout_us == true
boolean rectified; // true if pre-rectified to pinhole
boolean is_key_frame; // true if this frame is a selected keyframe
FrameQuality quality; // shared health/SNR notes
};
// Optional lightweight derivatives (for VIO/SfM/analytics)
@extensibility(APPENDABLE) struct Keypoint2D { float u; float v; float score; };
@extensibility(APPENDABLE) struct Track2D {
uint64 id;
sequence<Keypoint2D, spatial::sensing::common::SZ_LARGE> trail;
};
// Detections topic — BEST_EFFORT
@extensibility(APPENDABLE) struct VisionDetections {
@key string stream_id;
uint64 frame_seq;
Time stamp;
sequence<Keypoint2D, spatial::sensing::common::SZ_LARGE> keypoints;
sequence<Track2D, spatial::sensing::common::SZ_MEDIUM> tracks;
// Masks/boxes can be added in Semantics profile to keep Vision lean
};
}; }; };
SLAM Frontend Extension
Per-keyframe features, matches, landmarks, tracks, and camera calibration.
// SPDX-License-Identifier: MIT
// SpatialDDS SLAM Frontend 1.5
#ifndef SPATIAL_CORE_INCLUDED
#define SPATIAL_CORE_INCLUDED
#include "core.idl"
#endif
module spatial {
module slam_frontend {
const string MODULE_ID = "spatial.slam_frontend/1.5";
// Reuse core: Time, etc.
typedef builtin::Time Time;
typedef spatial::common::FrameRef FrameRef;
// Camera calibration
enum DistortionModelKind {
@value(0) NONE,
@value(1) RADTAN,
@value(2) EQUIDISTANT,
@value(3) KANNALA_BRANDT
};
@extensibility(APPENDABLE) struct CameraInfo {
@key string camera_id;
uint32 width; uint32 height; // pixels
double fx; double fy; // focal (px)
double cx; double cy; // principal point (px)
DistortionModelKind dist_kind;
sequence<double, 8> dist; // model params (bounded)
FrameRef frame_ref; // camera frame
Time stamp; // calib time (or 0 if static)
};
// 2D features & descriptors per keyframe
@extensibility(APPENDABLE) struct Feature2D {
double u; double v; // pixel coords
float scale; // px
float angle; // rad [0,2π)
float score; // detector response
};
@extensibility(APPENDABLE) struct KeyframeFeatures {
@key string node_id; // keyframe id
string camera_id;
string desc_type; // "ORB32","BRISK64","SPT256Q",...
uint32 desc_len; // bytes per descriptor
boolean row_major; // layout hint
sequence<Feature2D, 4096> keypoints; // ≤4096
sequence<uint8, 1048576> descriptors; // ≤1 MiB packed bytes
Time stamp;
string source_id;
uint64 seq;
};
// Optional cross-frame matches
@extensibility(APPENDABLE) struct FeatureMatch {
string node_id_a; uint32 idx_a;
string node_id_b; uint32 idx_b;
float score; // similarity or distance
};
@extensibility(APPENDABLE) struct MatchSet {
@key string match_id; // e.g., "kf_12<->kf_18"
sequence<FeatureMatch, 8192> matches;
Time stamp;
string source_id;
};
// Sparse 3D landmarks & tracks (optional)
@extensibility(APPENDABLE) struct Landmark {
@key string lm_id;
string map_id;
spatial::common::Vec3 p;
boolean has_cov;
spatial::common::Mat3x3 cov; // 3x3 pos covariance (row-major)
sequence<uint8, 4096> desc; // descriptor bytes
string desc_type;
Time stamp;
string source_id;
uint64 seq;
};
@extensibility(APPENDABLE) struct TrackObs {
string node_id; // observing keyframe
double u; double v; // pixel coords
};
@extensibility(APPENDABLE) struct Tracklet {
@key string track_id;
boolean has_lm_id; // true when lm_id is populated
string lm_id; // link to Landmark when present
sequence<TrackObs, 64> obs; // ≤64 obs
string source_id;
Time stamp;
};
}; // module slam_frontend
};
Semantics / Perception Extension
2D detections tied to keyframes; 3D oriented boxes in world frames (optionally tiled).
Size Convention (Normative)
Detection3D.size is the extent of the oriented bounding box in the object's local frame (center + q):
size[0] = width (local X), size[1] = height (local Z), size[2] = depth (local Y).
All values are in meters and MUST be non-negative. For datasets that use (width, length, height), map as (width, height, length).
// SPDX-License-Identifier: MIT
// SpatialDDS Semantics 1.5
#ifndef SPATIAL_CORE_INCLUDED
#define SPATIAL_CORE_INCLUDED
#include "core.idl"
#endif
#ifndef SPATIAL_SENSING_COMMON_INCLUDED
#define SPATIAL_SENSING_COMMON_INCLUDED
#include "common.idl"
#endif
module spatial {
module semantics {
const string MODULE_ID = "spatial.semantics/1.5";
typedef builtin::Time Time;
typedef spatial::core::TileKey TileKey;
typedef spatial::common::FrameRef FrameRef;
// 2D detections per keyframe (image space)
@extensibility(APPENDABLE) struct Detection2D {
@key string det_id; // unique per publisher
string node_id; // keyframe id
string camera_id; // camera
string class_id; // ontology label
float score; // [0..1]
spatial::common::BBox2D bbox; // [u_min,v_min,u_max,v_max] (px)
boolean has_mask; // if a pixel mask exists
string mask_blob_id; // BlobChunk ref (role="mask")
Time stamp;
string source_id;
};
@extensibility(APPENDABLE) struct Detection2DSet {
@key string set_id; // batch id (e.g., node_id + seq)
string node_id;
string camera_id;
sequence<Detection2D, spatial::sensing::common::SZ_SMALL> dets; // ≤256
Time stamp;
string source_id;
};
// 3D detections in world/local frame (scene space)
@extensibility(APPENDABLE) struct Detection3D {
@key string det_id;
FrameRef frame_ref; // e.g., "map" (pose known elsewhere)
boolean has_tile;
TileKey tile_key; // valid when has_tile = true
string class_id; // semantic label
float score; // [0..1]
// Oriented bounding box in frame_ref
spatial::common::Vec3 center; // m
spatial::common::Vec3 size; // (width, height, depth) in meters; see Size Convention
spatial::common::QuaternionXYZW q; // orientation (x,y,z,w) in GeoPose order
// Uncertainty (optional)
boolean has_covariance;
spatial::common::Mat3x3 cov_pos; // 3x3 position covariance (row-major)
spatial::common::Mat3x3 cov_rot; // 3x3 rotation covariance (row-major)
// Optional instance tracking
boolean has_track_id;
string track_id;
Time stamp;
string source_id;
// Optional attribute key-value pairs
boolean has_attributes;
sequence<spatial::common::MetaKV, 8> attributes; // valid when has_attributes == true
// Occlusion / visibility (0.0 = fully occluded, 1.0 = fully visible)
boolean has_visibility;
float visibility; // valid when has_visibility == true
// Evidence counts
boolean has_num_pts;
uint32 num_lidar_pts; // valid when has_num_pts == true
uint32 num_radar_pts; // valid when has_num_pts == true
};
@extensibility(APPENDABLE) struct Detection3DSet {
@key string set_id; // batch id
FrameRef frame_ref; // common frame for the set
boolean has_tile;
TileKey tile_key; // valid when has_tile = true
sequence<Detection3D, spatial::sensing::common::SZ_SMALL> dets; // ≤256
Time stamp;
string source_id;
};
}; // module semantics
};
Radar Extension
Radar metadata, per-frame detection sets, and raw/processed tensor transport. The detection-centric path (RadSensorMeta / RadDetectionSet) serves automotive-style point detections. The tensor path (RadTensorMeta / RadTensorFrame) serves raw or processed radar data cubes for ISAC and ML workloads. Deployments may use either or both. ROI semantics follow §2 Conventions for global normative rules. See §2 Conventions for global normative rules.
// SPDX-License-Identifier: MIT
// SpatialDDS Radar (sensing.rad) 1.5 - Extension profile
// Detection-centric radar for automotive, industrial, and robotics sensors.
#ifndef SPATIAL_CORE_INCLUDED
#define SPATIAL_CORE_INCLUDED
#include "core.idl"
#endif
#ifndef SPATIAL_SENSING_COMMON_INCLUDED
#define SPATIAL_SENSING_COMMON_INCLUDED
#include "common.idl"
#endif
module spatial { module sensing { module rad {
// Module identifier for discovery and schema registration
const string MODULE_ID = "spatial.sensing.rad/1.5";
// Reuse Core + Sensing Common types
typedef builtin::Time Time;
typedef spatial::core::PoseSE3 PoseSE3;
typedef spatial::core::BlobRef BlobRef;
typedef spatial::common::FrameRef FrameRef;
typedef spatial::sensing::common::Codec Codec;
typedef spatial::sensing::common::StreamMeta StreamMeta;
typedef spatial::sensing::common::FrameHeader FrameHeader;
typedef spatial::sensing::common::FrameQuality FrameQuality;
typedef spatial::sensing::common::Axis Axis;
typedef spatial::sensing::common::SampleType SampleType;
typedef spatial::sensing::common::PayloadKind PayloadKind;
typedef spatial::sensing::common::ROI ROI;
typedef spatial::sensing::common::ROIRequest ROIRequest;
typedef spatial::sensing::common::ROIReply ROIReply;
// ---- Radar sensor type ----
enum RadSensorType {
@value(0) SHORT_RANGE, // e.g., corner/parking radar, ~30m
@value(1) MEDIUM_RANGE, // e.g., blind-spot, ~80m
@value(2) LONG_RANGE, // e.g., forward-facing, ~200m+
@value(3) IMAGING_4D, // 4D imaging radar (range/az/el/doppler)
@value(4) SAR, // synthetic aperture radar
@value(255) OTHER
};
// ---- Dynamic property per detection ----
enum RadDynProp {
@value(0) UNKNOWN,
@value(1) MOVING,
@value(2) STATIONARY,
@value(3) ONCOMING,
@value(4) CROSSING_LEFT,
@value(5) CROSSING_RIGHT,
@value(6) STOPPED // was moving, now stationary
};
// ---- Static sensor description ----
// RELIABLE + TRANSIENT_LOCAL (late joiners receive the latest meta)
@extensibility(APPENDABLE) struct RadSensorMeta {
@key string stream_id; // stable id for this radar stream
StreamMeta base; // frame_ref, T_bus_sensor, nominal_rate_hz
RadSensorType sensor_type; // range class of this radar
// Detection-space limits (from sensor datasheet)
boolean has_range_limits;
float min_range_m; // valid when has_range_limits == true
float max_range_m;
boolean has_azimuth_fov;
float az_fov_min_deg; // valid when has_azimuth_fov == true
float az_fov_max_deg;
boolean has_elevation_fov;
float el_fov_min_deg; // valid when has_elevation_fov == true
float el_fov_max_deg;
boolean has_velocity_limits;
float v_min_mps; // valid when has_velocity_limits == true
float v_max_mps; // max unambiguous radial velocity
// Max detections per frame (informative hint for subscriber allocation)
uint32 max_detections_per_frame;
// Processing chain description (informative)
string proc_chain; // e.g., "CFAR -> clustering -> tracking"
string schema_version; // MUST be "spatial.sensing.rad/1.5"
};
// ---- Per-detection data ----
@extensibility(APPENDABLE) struct RadDetection {
// Position in sensor frame (meters)
spatial::common::Vec3 xyz_m;
// Velocity: Cartesian vector preferred; scalar radial as fallback.
// Producers SHOULD populate velocity_xyz when available.
// When only radial velocity is known, set has_velocity_xyz = false
// and use v_r_mps.
boolean has_velocity_xyz;
spatial::common::Vec3 velocity_xyz; // m/s in frame_ref (valid when has_velocity_xyz == true)
boolean has_v_r_mps;
double v_r_mps; // scalar radial velocity (valid when has_v_r_mps == true)
// Ego-motion compensated velocity (optional)
boolean has_velocity_comp_xyz;
spatial::common::Vec3 velocity_comp_xyz; // ego-compensated, m/s (valid when has_velocity_comp_xyz == true)
// Radar cross-section in physical units
boolean has_rcs_dbm2;
float rcs_dbm2; // dBm^2 (valid when has_rcs_dbm2 == true)
// Generic intensity / magnitude (0..1 normalized, for renderers)
float intensity;
// Per-detection quality / confidence (0..1)
float quality;
// Dynamic property classification
boolean has_dyn_prop;
RadDynProp dyn_prop; // valid when has_dyn_prop == true
// Per-detection position uncertainty (optional)
boolean has_pos_rms;
float x_rms_m; // valid when has_pos_rms == true
float y_rms_m;
float z_rms_m;
// Per-detection velocity uncertainty (optional)
boolean has_vel_rms;
float vx_rms_mps; // valid when has_vel_rms == true
float vy_rms_mps;
float vz_rms_mps;
// Ambiguity state (radar-specific; 0 = unambiguous)
boolean has_ambig_state;
uint8 ambig_state; // valid when has_ambig_state == true
// False alarm probability hint (0 = high confidence, higher = less certain)
boolean has_false_alarm_prob;
float false_alarm_prob; // valid when has_false_alarm_prob == true
// Optional tracking ID assigned by the radar firmware
boolean has_sensor_track_id;
uint32 sensor_track_id; // valid when has_sensor_track_id == true
};
// ---- Detection set (per-frame batch) ----
// BEST_EFFORT + KEEP_LAST=1
@extensibility(APPENDABLE) struct RadDetectionSet {
@key string stream_id;
uint64 frame_seq;
FrameRef frame_ref; // coordinate frame of xyz_m
sequence<RadDetection, spatial::sensing::common::SZ_XL> dets;
Time stamp;
string source_id;
uint64 seq;
// Processing provenance
string proc_chain; // e.g., "ARS408-CFAR" or "OS-CFAR->cluster"
// Frame-level quality
boolean has_quality;
FrameQuality quality; // valid when has_quality == true
};
// ==============================================================
// RAD TENSOR TYPES (for raw/processed radar cubes)
// ==============================================================
// Layout of the RAD tensor - axis ordering convention
enum RadTensorLayout {
@value(0) RA_D, // [range, azimuth, doppler]
@value(1) R_AZ_EL_D, // [range, azimuth, elevation, doppler]
@value(2) CH_FAST_SLOW, // [channel/Rx, fast_time, slow_time] - raw FMCW
@value(3) CH_R_D, // [channel, range, doppler] - post range-doppler FFT
@value(255) CUSTOM // axes[] defines the order explicitly
};
// Static tensor description - RELIABLE + TRANSIENT_LOCAL
// Publishes once; late joiners receive current state.
@extensibility(APPENDABLE) struct RadTensorMeta {
@key string stream_id; // stable id for this radar tensor stream
StreamMeta base; // frame_ref, T_bus_sensor, nominal_rate_hz
RadSensorType sensor_type; // reuse existing enum (SHORT_RANGE, IMAGING_4D, etc.)
// Tensor shape description
RadTensorLayout layout; // axis ordering convention
sequence<Axis, 8> axes; // axis definitions (range/az/el/doppler/channel/time)
SampleType voxel_type; // pre-compression sample type (e.g., CF32)
string physical_meaning; // e.g., "raw FMCW I/Q", "post 3D-FFT complex baseband"
// Antenna configuration (informative, for MIMO systems)
boolean has_antenna_config;
uint16 num_tx; // valid when has_antenna_config == true
uint16 num_rx; // valid when has_antenna_config == true
uint16 num_virtual_channels; // num_tx * num_rx (informative)
// Waveform parameters (informative)
boolean has_waveform_params;
float bandwidth_hz; // valid when has_waveform_params == true
float center_freq_hz; // valid when has_waveform_params == true
float chirp_duration_s; // valid when has_waveform_params == true
uint32 samples_per_chirp; // valid when has_waveform_params == true
uint32 chirps_per_frame; // valid when has_waveform_params == true
// Default payload settings for frames
PayloadKind payload_kind; // DENSE_TILES, SPARSE_COO, or LATENT
Codec codec; // LZ4, ZSTD, CODEC_NONE, etc.
boolean has_quant_scale;
float quant_scale; // valid when has_quant_scale == true
uint32 tile_size[4]; // for DENSE_TILES; unused dims = 1
string schema_version; // MUST be "spatial.sensing.rad/1.5"
};
// Per-frame tensor index - BEST_EFFORT + KEEP_LAST=1
// Heavy payload referenced via FrameHeader.blobs[]
@extensibility(APPENDABLE) struct RadTensorFrame {
@key string stream_id;
uint64 frame_seq;
FrameHeader hdr; // t_start/t_end, optional sensor_pose, blobs[]
// Per-frame overrides (may differ from RadTensorMeta defaults)
PayloadKind payload_kind;
Codec codec;
SampleType voxel_type_after_decode; // post-decode type (e.g., CF32 -> MAG_F16)
boolean has_quant_scale;
float quant_scale; // valid when has_quant_scale == true
FrameQuality quality; // SNR/valid%/health note
string proc_chain; // e.g., "raw", "FFT3D->hann", "FFT3D->OS-CFAR"
};
}; }; };
Lidar Extension
Lidar metadata, compressed point cloud frames, and detections. ROI semantics follow §2 Conventions for global normative rules; axes use the Sensing Common AXIS_CENTERS/AXIS_LINSPACE union encoding. See §2 Conventions for global normative rules.
BIN_INTERLEAVED Encoding (Normative)
BIN_INTERLEAVED indicates raw interleaved binary where each point is a contiguous record of fields defined by the PointLayout enum. There is no header. The ring field is serialized as uint16 per the LidarDetection.ring type.
| Layout | Fields per point | Default byte-width per field |
|---|---|---|
XYZ_I |
x, y, z, intensity | 4 × float32 = 16 bytes |
XYZ_I_R |
x, y, z, intensity, ring | 4 × float32 + 1 × uint16 = 18 bytes |
XYZ_I_R_N |
x, y, z, intensity, ring, nx, ny, nz | 4 × float32 + 1 × uint16 + 3 × float32 = 30 bytes |
XYZ_I_R_T |
x, y, z, intensity, ring, t | 4 × float32 + 1 × uint16 + 1 × float32 = 22 bytes |
XYZ_I_R_T_N |
x, y, z, intensity, ring, t, nx, ny, nz | 4 × float32 + 1 × uint16 + 1 × float32 + 3 × float32 = 34 bytes |
When BIN_INTERLEAVED is used, consumers MUST interpret the blob as N × record_size bytes where N = blob_size / record_size.
Per-Point Timestamps (Normative)
Layouts XYZ_I_R_T and XYZ_I_R_T_N include a per-point relative timestamp field t serialized as float32, representing seconds elapsed since FrameHeader.t_start. Consumers performing motion compensation SHOULD use t_start + point.t as the acquisition time for each point.
Computing t_end for Spinning Lidars (Informative)
When a source provides only t_start, producers SHOULD compute t_end as t_start + (1.0 / nominal_rate_hz) for spinning lidars, or as t_start + max(point.t) when per-point timestamps are available. Producers MUST populate t_end rather than leaving it as zero.
// SPDX-License-Identifier: MIT
// SpatialDDS LiDAR (sensing.lidar) 1.5 — Extension profile
#ifndef SPATIAL_CORE_INCLUDED
#define SPATIAL_CORE_INCLUDED
#include "core.idl"
#endif
#ifndef SPATIAL_SENSING_COMMON_INCLUDED
#define SPATIAL_SENSING_COMMON_INCLUDED
#include "common.idl"
#endif
module spatial { module sensing { module lidar {
// Module identifier for discovery and schema registration
const string MODULE_ID = "spatial.sensing.lidar/1.5";
// Reuse Core + Sensing Common
typedef builtin::Time Time;
typedef spatial::core::PoseSE3 PoseSE3;
typedef spatial::core::BlobRef BlobRef;
typedef spatial::common::FrameRef FrameRef;
typedef spatial::sensing::common::Codec Codec;
typedef spatial::sensing::common::PayloadKind PayloadKind; // use BLOB_GEOMETRY for clouds
typedef spatial::sensing::common::SampleType SampleType; // optional for per-point extras
typedef spatial::sensing::common::Axis Axis;
typedef spatial::sensing::common::ROI ROI;
typedef spatial::sensing::common::StreamMeta StreamMeta;
typedef spatial::sensing::common::FrameHeader FrameHeader;
typedef spatial::sensing::common::FrameQuality FrameQuality;
typedef spatial::sensing::common::ROIRequest ROIRequest;
typedef spatial::sensing::common::ROIReply ROIReply;
// ROI bounds follow Sensing Common presence flags.
// Axis samples are encoded via the Sensing Common union (AXIS_CENTERS or AXIS_LINSPACE).
// Device + data model
enum LidarType {
@value(0) SPINNING_2D,
@value(1) MULTI_BEAM_3D,
@value(2) SOLID_STATE
};
enum CloudEncoding {
@value(0) PCD,
@value(1) PLY,
@value(2) LAS,
@value(3) LAZ,
@value(4) BIN_INTERLEAVED, // raw interleaved binary (fields by PointLayout)
@value(10) GLTF_DRACO,
@value(20) MPEG_PCC,
@value(255) CUSTOM_BIN
};
enum PointLayout { // intensity, ring, normal
@value(0) XYZ_I,
@value(1) XYZ_I_R,
@value(2) XYZ_I_R_N,
@value(3) XYZ_I_R_T, // with per-point relative timestamp
@value(4) XYZ_I_R_T_N // with per-point timestamp + normals
};
// Static description — RELIABLE + TRANSIENT_LOCAL (late joiners receive the latest meta)
@extensibility(APPENDABLE) struct LidarMeta {
@key string stream_id;
StreamMeta base; // frame_ref, T_bus_sensor, nominal_rate_hz
LidarType type;
uint16 n_rings; // 0 if N/A
boolean has_range_limits;
float min_range_m; // valid when has_range_limits == true
float max_range_m;
boolean has_horiz_fov;
float horiz_fov_deg_min; // valid when has_horiz_fov == true
float horiz_fov_deg_max;
boolean has_vert_fov;
float vert_fov_deg_min; // valid when has_vert_fov == true
float vert_fov_deg_max;
// Sensor wavelength (informative; for eye-safety and atmospheric classification)
boolean has_wavelength;
float wavelength_nm; // valid when has_wavelength == true; e.g., 865, 905, 1550
// Default payload for frames (clouds ride as blobs)
CloudEncoding encoding; // PCD/PLY/LAS/LAZ/etc.
Codec codec; // ZSTD/LZ4/DRACO/…
PointLayout layout; // expected fields when decoded
string schema_version; // MUST be "spatial.sensing.lidar/1.5"
};
// Per-frame index — BEST_EFFORT + KEEP_LAST=1 (large payloads referenced via blobs)
@extensibility(APPENDABLE) struct LidarFrame {
@key string stream_id;
uint64 frame_seq;
FrameHeader hdr; // t_start/t_end, optional sensor_pose, blobs[]
CloudEncoding encoding; // may override meta
Codec codec; // may override meta
PointLayout layout; // may override meta
boolean has_per_point_timestamps; // true when blob contains per-point t
// Optional quick hints (for health/telemetry)
boolean has_average_range_m;
float average_range_m; // valid when has_average_range_m == true
boolean has_percent_valid;
float percent_valid; // valid when has_percent_valid == true (0..100)
boolean has_quality;
FrameQuality quality; // valid when has_quality == true
};
// Lightweight derivative for immediate fusion/tracking (optional)
@extensibility(APPENDABLE) struct LidarDetection {
spatial::common::Vec3 xyz_m;
float intensity;
uint16 ring;
float quality; // 0..1
};
// Detections topic — BEST_EFFORT
@extensibility(APPENDABLE) struct LidarDetectionSet {
@key string stream_id;
uint64 frame_seq;
FrameRef frame_ref; // coordinate frame of xyz_m
sequence<LidarDetection, spatial::sensing::common::SZ_SMALL> dets;
Time stamp;
};
}; }; };
AR + Geo Extension
Multi-frame geo-referenced nodes for AR clients, VPS services, and multi-agent alignment.
NodeGeo extends core::Node with an array of metric poses in different coordinate frames and an optional geographic anchor. A VPS service localizing a client against multiple overlapping maps returns one NodeGeo carrying poses in each map's frame. In hierarchical spaces (building → floor → room → table), the same message carries poses at every level of the hierarchy. Consumers select the frame they need; producers include only the frames they can compute.
The poses array uses core::FramedPose — each entry is self-contained with its own frame reference and covariance. This replaces the previous pattern of a single bare PoseSE3 with frame_ref and cov as sibling fields, which could only express one local pose and left the relationship between the top-level cov and the geopose's cov ambiguous.
// SPDX-License-Identifier: MIT
// SpatialDDS AR+Geo 1.5
#ifndef SPATIAL_CORE_INCLUDED
#define SPATIAL_CORE_INCLUDED
#include "core.idl"
#endif
module spatial {
module argeo {
const string MODULE_ID = "spatial.argeo/1.5";
typedef builtin::Time Time;
typedef spatial::core::PoseSE3 PoseSE3;
typedef spatial::core::FramedPose FramedPose;
typedef spatial::core::GeoPose GeoPose;
typedef spatial::core::CovMatrix CovMatrix;
typedef spatial::common::FrameRef FrameRef;
// A pose-graph node with one or more metric-frame poses and an
// optional geographic anchor.
//
// Keyed by node_id (same key as core::Node). Published alongside
// core::Node when geo-referencing is available.
//
// poses[] carries the node's position in one or more local/metric
// coordinate frames. Each FramedPose is self-contained (pose +
// frame_ref + cov + stamp). Typical entries:
// - SLAM map frame (always present)
// - ENU frame anchored to a surveyed point
// - Building / floor / room frames in hierarchical spaces
// - Alternative map frames when multiple maps overlap
//
// geopose provides the WGS84 anchor (lat/lon/alt) when known.
// It remains a separate field because geographic coordinates use
// degrees, not meters — they cannot share the FramedPose type.
@extensibility(APPENDABLE) struct NodeGeo {
string map_id;
@key string node_id; // same id as core::Node
// One or more metric poses in different frames.
// The first entry SHOULD be the primary SLAM map frame.
// Additional entries provide the same physical pose expressed
// in alternative coordinate frames (other maps, hierarchical
// spaces, ENU anchors). Consumers select by frame_ref.
sequence<FramedPose, 8> poses;
// Geographic anchor (optional — absent for indoor-only maps)
boolean has_geopose;
GeoPose geopose;
string source_id;
uint64 seq; // per-source monotonic
uint64 graph_epoch; // increments on major rebases/merges
};
}; // module argeo
};
Usage scenarios (informative):
Multi-map localization: A VPS service localizes a client against three overlapping maps and returns:
{
"node_id": "vps-fix/client-42/0017",
"map_id": "mall-west",
"poses": [
{
"pose": { "t": [12.3, -4.1, 1.5], "q": [0, 0, 0, 1] },
"frame_ref": { "uuid": "aaa-...", "fqn": "mall-west/lidar-map" },
"cov": { "type": "COV_POSE6", "pose": [ ... ] },
"stamp": { "sec": 1714071000, "nanosec": 0 }
},
{
"pose": { "t": [12.1, -4.3, 1.5], "q": [0, 0, 0.01, 1] },
"frame_ref": { "uuid": "bbb-...", "fqn": "mall-west/photo-map" },
"cov": { "type": "COV_POSE6", "pose": [ ... ] },
"stamp": { "sec": 1714071000, "nanosec": 0 }
}
],
"has_geopose": true,
"geopose": {
"lat_deg": 37.7749, "lon_deg": -122.4194, "alt_m": 15.0,
"q": [0, 0, 0, 1], "frame_kind": "ENU",
"frame_ref": { "uuid": "ccc-...", "fqn": "earth-fixed/enu" },
"stamp": { "sec": 1714071000, "nanosec": 0 },
"cov": { "type": "COV_POS3", "pos": [ ... ] }
},
"source_id": "vps/mall-west-service",
"seq": 17,
"graph_epoch": 3
}
Hierarchical spaces: A localization service returns poses in building, floor, and room frames:
{
"node_id": "vps-fix/headset-07/0042",
"map_id": "building-west",
"poses": [
{
"pose": { "t": [45.2, 22.1, 9.3], "q": [0, 0, 0, 1] },
"frame_ref": { "uuid": "bld-...", "fqn": "building-west/enu" },
"cov": { "type": "COV_POS3", "pos": [ ... ] },
"stamp": { "sec": 1714071000, "nanosec": 0 }
},
{
"pose": { "t": [15.2, 8.1, 0.3], "q": [0, 0, 0, 1] },
"frame_ref": { "uuid": "fl3-...", "fqn": "building-west/floor-3" },
"cov": { "type": "COV_POS3", "pos": [ ... ] },
"stamp": { "sec": 1714071000, "nanosec": 0 }
},
{
"pose": { "t": [3.2, 2.1, 0.3], "q": [0, 0, 0, 1] },
"frame_ref": { "uuid": "rmB-...", "fqn": "building-west/floor-3/room-B" },
"cov": { "type": "COV_POS3", "pos": [ ... ] },
"stamp": { "sec": 1714071000, "nanosec": 0 }
}
],
"has_geopose": true,
"geopose": { "lat_deg": 37.7750, "lon_deg": -122.4190, "alt_m": 24.3, "..." : "..." },
"source_id": "vps/building-west-indoor",
"seq": 42,
"graph_epoch": 1
}
Mapping Extension
Map lifecycle metadata, multi-source edge types, inter-map alignment, and lifecycle events for multi-agent collaborative mapping.
The Core profile provides the mechanical primitives for map data: Node/Edge for pose graphs, TileMeta/TilePatch/BlobRef for geometry transport, FrameTransform for frame alignment, and SnapshotRequest/SnapshotResponse for tile catch-up. The SLAM Frontend profile carries feature-level data for re-localization. The Anchors profile handles durable landmarks with incremental sync.
This extension adds the map lifecycle layer — the metadata and coordination types that let multiple independent SLAM agents discover, align, merge, version, and qualify each other's maps without prior arrangement:
MapMeta— top-level map descriptor: what exists, what it covers, its quality and lifecycle state.mapping::Edge— extendscore::Edgewith richer constraint types for multi-source pose graphs (cross-map loop closures, GPS, anchor, IMU, semantic constraints).MapAlignment— the inter-map transform with provenance, uncertainty, and evidence references.MapEvent— lightweight lifecycle notifications so subscribers react to map state changes without polling.
Design note — no new map formats. This profile does not add occupancy grid, TSDF, or voxel map types. Those are representation-specific formats expressed as TileMeta.encoding values (e.g., "occupancy_grid/uint8", "tsdf/f32", "voxel_hash/f32"). The mapping profile addresses coordinating and aligning maps, not inventing new map formats.
Topic Layout
| Type | Topic | QoS | Notes |
|---|---|---|---|
MapMeta |
spatialdds/<scene>/mapping/meta/v1 |
RELIABLE + TRANSIENT_LOCAL, KEEP_LAST(1) per key | One sample per (map_id, source_id). Late joiners get current state. |
mapping::Edge |
spatialdds/<scene>/mapping/edge/v1 |
RELIABLE, KEEP_ALL | Superset of core::Edge for multi-source constraints. |
MapAlignment |
spatialdds/<scene>/mapping/alignment/v1 |
RELIABLE + TRANSIENT_LOCAL, KEEP_LAST(1) per key | Durable inter-map transforms. |
MapEvent |
spatialdds/<scene>/mapping/event/v1 |
RELIABLE, KEEP_LAST(32) | Lightweight lifecycle notifications. |
Core Node and Edge topics remain unchanged. Agents that produce cross-map constraints publish on the mapping/edge topic; agents that only produce intra-map odometry/loop closures continue using core topics. Consumers that need cross-map awareness subscribe to both.
Range-only constraints: When type == RANGE, the edge carries a scalar distance measurement between from_id and to_id in the range_m / range_std_m fields. The T_from_to and information fields SHOULD be set to identity / zero respectively. Pose graph optimizers that encounter a RANGE edge SHOULD treat it as a distance-only factor: ||pos(from_id) - pos(to_id)|| = range_m. Common sources include UWB inter-robot ranging, acoustic ranging (underwater), and BLE RSSI-derived distances. Range edges may reference nodes in different maps (with has_from_map_id / has_to_map_id populated), enabling range-assisted inter-map alignment.
// SPDX-License-Identifier: MIT
// SpatialDDS Mapping Extension 1.5
//
// Map lifecycle metadata, multi-source edge types, and inter-map
// alignment primitives for multi-agent collaborative mapping.
#ifndef SPATIAL_CORE_INCLUDED
#define SPATIAL_CORE_INCLUDED
#include "core.idl"
#endif
module spatial {
module mapping {
const string MODULE_ID = "spatial.mapping/1.5";
typedef builtin::Time Time;
typedef spatial::core::PoseSE3 PoseSE3;
typedef spatial::core::FrameRef FrameRef;
typedef spatial::core::CovMatrix CovMatrix;
typedef spatial::core::BlobRef BlobRef;
typedef spatial::common::MetaKV MetaKV;
typedef spatial::core::GeoPose GeoPose;
// ================================================================
// 1. MAP METADATA
// ================================================================
// Enums are placed in a nested module to avoid literal collisions
// at the module scope in IDL compilers.
module enums {
module map_kind {
// Representation kind — what type of spatial map this describes.
// The enum identifies the high-level representation; the actual
// encoding and codec live in TileMeta.encoding as today.
enum MapKind {
@value(0) POSE_GRAPH, // sparse keyframe graph (Node + Edge)
@value(1) OCCUPANCY_GRID, // 2D or 2.5D grid (nav planning)
@value(2) POINT_CLOUD, // dense 3D point cloud
@value(3) MESH, // triangle mesh / surface
@value(4) TSDF, // truncated signed distance field
@value(5) VOXEL, // volumetric voxel grid
@value(6) NEURAL_FIELD, // NeRF, 3DGS, neural SDF (see neural profile)
@value(7) FEATURE_MAP, // visual place recognition / bag-of-words
@value(8) SEMANTIC, // semantic / panoptic map layer
@value(9) OTHER
};
};
module map_status {
// Map status — lifecycle state.
enum MapStatus {
@value(0) BUILDING, // actively being constructed (SLAM running)
@value(1) OPTIMIZING, // global optimization / bundle adjustment in progress
@value(2) STABLE, // optimized and not actively changing
@value(3) FROZEN, // immutable reference map (no further updates)
@value(4) DEPRECATED // superseded by a newer map; consumers should migrate
};
};
module edge_type {
// Extends core::EdgeTypeCore (ODOM=0, LOOP=1) with constraint types
// needed for multi-agent, multi-sensor pose graph optimization.
//
// Values 0-1 are identical to EdgeTypeCore. Core consumers that
// only understand ODOM/LOOP can safely downcast by treating
// unknown values as LOOP.
enum EdgeType {
@value(0) ODOM, // odometry (sequential)
@value(1) LOOP, // intra-map loop closure
@value(2) INTER_MAP, // cross-map loop closure (between two agents' maps)
@value(3) GPS, // absolute pose from GNSS
@value(4) ANCHOR, // constraint from recognizing a shared anchor
@value(5) IMU_PREINT, // IMU pre-integration factor
@value(6) GRAVITY, // gravity direction prior
@value(7) PLANE, // planar constraint (e.g., ground plane)
@value(8) SEMANTIC, // semantic co-observation ("both see the same door")
@value(9) MANUAL, // human-provided alignment
@value(10) RANGE, // range-only distance constraint (UWB, acoustic, BLE)
@value(11) OTHER
};
};
module alignment_method {
// How an alignment was established.
enum AlignmentMethod {
@value(0) VISUAL_LOOP, // feature-based visual closure
@value(1) LIDAR_ICP, // point cloud registration (ICP / NDT)
@value(2) ANCHOR_MATCH, // shared anchor recognition
@value(3) GPS_COARSE, // GPS-derived coarse alignment
@value(4) SEMANTIC_MATCH, // semantic landmark co-observation
@value(5) MANUAL, // operator-provided ground truth
@value(6) MULTI_METHOD, // combination of methods
@value(7) RANGE_COARSE, // range-only (UWB, acoustic) coarse alignment
@value(8) OTHER
};
};
module map_event_kind {
// Lightweight event published when a map undergoes a significant
// lifecycle transition. Subscribers (fleet managers, UI dashboards,
// data pipelines) can react without polling MapMeta.
enum MapEventKind {
@value(0) CREATED, // new map started
@value(1) EPOCH_ADVANCE, // graph_epoch incremented (rebase / merge)
@value(2) STATUS_CHANGE, // status field changed (e.g., BUILDING → STABLE)
@value(3) ALIGNMENT_NEW, // new MapAlignment published involving this map
@value(4) ALIGNMENT_UPDATE, // existing MapAlignment revised
@value(5) DEPRECATED, // map marked deprecated
@value(6) DELETED // map data removed from bus
};
};
};
typedef enums::map_kind::MapKind MapKind;
typedef enums::map_status::MapStatus MapStatus;
typedef enums::edge_type::EdgeType EdgeType;
typedef enums::alignment_method::AlignmentMethod AlignmentMethod;
typedef enums::map_event_kind::MapEventKind MapEventKind;
// Quality metrics — optional per-map health indicators.
// All fields are optional via has_* flags to avoid mandating
// metrics that not every SLAM system produces.
@extensibility(APPENDABLE) struct MapQuality {
boolean has_loop_closure_count;
uint32 loop_closure_count; // total loop closures accepted
boolean has_mean_residual;
double mean_residual; // mean constraint residual after optimization (meters)
boolean has_max_drift_m;
double max_drift_m; // estimated worst-case drift (meters)
boolean has_coverage_pct;
float coverage_pct; // fraction of declared extent actually mapped [0..1]
boolean has_keyframe_count;
uint32 keyframe_count; // number of keyframes / nodes
boolean has_landmark_count;
uint32 landmark_count; // number of 3D landmarks
};
// Top-level map descriptor. Published with RELIABLE + TRANSIENT_LOCAL
// so late joiners discover all active maps immediately.
//
// One MapMeta per (map_id, source_id) — a single physical map may have
// multiple representations (e.g., pose graph + occupancy grid + mesh),
// each published as a separate MapMeta with the same map_id but
// different kind and source_id.
@extensibility(APPENDABLE) struct MapMeta {
@key string map_id; // unique map identifier
@key string source_id; // producing agent / SLAM system
MapKind kind; // representation type
MapStatus status; // lifecycle state
string algorithm; // e.g., "ORB-SLAM3", "Cartographer", "RTAB-Map", "LIO-SAM"
FrameRef frame_ref; // map's canonical coordinate frame
// Spatial extent (axis-aligned in frame_ref)
boolean has_extent;
spatial::core::Aabb3 extent; // bounding box of mapped region
// Geo-anchor: where this map sits on Earth (when known)
boolean has_geopose;
GeoPose geopose; // map origin in WGS84
// Versioning — aligns with core Node/Edge graph_epoch
uint64 graph_epoch; // increments on major rebases / merges
uint64 revision; // monotonic within an epoch (fine-grained updates)
// Quality
boolean has_quality;
MapQuality quality;
// Timing
Time created; // map creation time
Time stamp; // last update time
// Content references — how to get the map data
// For pose graphs: subscribe to Node/Edge on the standard topic with this map_id
// For dense maps: these blob_ids reference the backing TileMeta/BlobChunk data
sequence<BlobRef, 32> blob_refs; // optional: pre-built map artifacts
// Extensible metadata (encoding details, sensor suite, etc.)
sequence<MetaKV, 32> attributes;
string schema_version; // MUST be "spatial.mapping/1.5"
};
// ================================================================
// 2. EXTENDED EDGE TYPES
// ================================================================
// (EdgeType enum is defined in enums submodule; typedef above)
// Extended edge that carries the richer EdgeType plus provenance.
// Supplements core::Edge — publishers that produce multi-source
// constraints publish mapping::Edge; the fields are a superset
// of core::Edge.
@extensibility(APPENDABLE) struct Edge {
string map_id;
@key string edge_id;
string from_id; // source node (may be in a different map_id)
string to_id; // target node
EdgeType type; // extended type enum
PoseSE3 T_from_to; // relative pose: from_id → to_id
spatial::common::Mat6x6 information; // 6x6 info matrix (row-major)
Time stamp;
string source_id; // who produced this constraint
uint64 seq;
uint64 graph_epoch;
// Cross-map provenance (populated when type == INTER_MAP)
boolean has_from_map_id;
string from_map_id; // map_id of from_id's origin
boolean has_to_map_id;
string to_map_id; // map_id of to_id's origin
// Match quality for loop closures and cross-map edges
boolean has_match_score;
float match_score; // similarity / inlier ratio [0..1]
boolean has_inlier_count;
uint32 inlier_count; // feature inliers supporting this edge
// Range-only constraint (populated when type == RANGE)
// For range-only edges, T_from_to and information are unused (set to
// identity/zero); the scalar range_m is the primary payload.
// The optimizer treats this as a distance-only factor between from_id
// and to_id: ||pos(from_id) - pos(to_id)|| = range_m ± range_std_m.
boolean has_range_m;
float range_m; // measured distance (meters)
boolean has_range_std_m;
float range_std_m; // 1-sigma distance uncertainty (meters)
};
// ================================================================
// 3. MAP ALIGNMENT
// ================================================================
// (AlignmentMethod enum is defined in enums submodule; typedef above)
// Inter-map transform: aligns map_id_from's frame to map_id_to's frame,
// with provenance and quality metadata.
//
// This is the merge primitive. When a multi-robot SLAM system determines
// that two maps overlap, it publishes a MapAlignment. Downstream consumers
// (planning, visualization, fleet coordination) use this to reason across
// maps without waiting for a full graph merge.
@extensibility(APPENDABLE) struct MapAlignment {
@key string alignment_id; // unique alignment identifier
string map_id_from; // source map
string map_id_to; // target map (reference)
PoseSE3 T_from_to; // transform: map_id_from frame → map_id_to frame
CovMatrix cov; // uncertainty of the alignment
AlignmentMethod method; // how the alignment was computed
Time stamp; // when the alignment was computed
string source_id; // who computed it
// Quality evidence
boolean has_match_score;
float match_score; // overall alignment quality [0..1]
boolean has_overlap_pct;
float overlap_pct; // estimated spatial overlap [0..1]
boolean has_supporting_edges;
uint32 supporting_edges; // number of cross-map edges backing this alignment
// Versioning — alignment may be refined as more evidence accumulates
uint64 revision; // monotonic; newer revision supersedes older
// Optional: list of cross-map edge_ids that support this alignment
sequence<string, 64> evidence_edge_ids;
string schema_version; // MUST be "spatial.mapping/1.5"
};
// ================================================================
// 4. MAP LIFECYCLE EVENTS
// ================================================================
// (MapEventKind enum is defined in enums submodule; typedef above)
@extensibility(APPENDABLE) struct MapEvent {
@key string map_id;
MapEventKind event;
string source_id;
Time stamp;
// Context (populated per event kind)
boolean has_new_status;
MapStatus new_status; // for STATUS_CHANGE
boolean has_new_epoch;
uint64 new_epoch; // for EPOCH_ADVANCE
boolean has_alignment_id;
string alignment_id; // for ALIGNMENT_NEW / ALIGNMENT_UPDATE
// Human-readable reason
boolean has_reason;
string reason; // e.g., "merged with map/robot-B after 42 loop closures"
};
}; // module mapping
}; // module spatial
Spatial Events Extension
Typed, spatially-scoped events for zone monitoring, anomaly detection, and smart infrastructure alerting. Bridges perception streams (Detection3DSet) and application logic (fleet management, building automation, safety systems).
The Semantics profile provides spatial facts — "what is where." This extension adds spatial interpretations — "something happened that matters." Events are derived from perception streams plus zone definitions plus temporal rules (dwell thresholds, speed limits, capacity caps). They are typed, severity-graded, tied to triggering detections, and scoped to named spatial zones.
The profile defines three types:
SpatialZone— named spatial regions with rules (restricted, speed-limited, capacity-capped). Published latched so all participants know the zone layout.SpatialEvent— typed event tied to a zone, triggering detection, optional media evidence, and severity.ZoneState— periodic zone occupancy and status snapshot for dashboards and capacity management.
Integration with Discovery: Zone publishers announce via disco::Announce with kind: OTHER (or a future ZONE_MANAGER kind) and coverage matching the zone's spatial extent. Consumers use CoverageQuery filtered by module_id_in: ["spatial.events/1.5"] to discover event sources in a region. SpatialZone geometry reuses the same Aabb3 and FrameRef primitives as CoverageElement, ensuring consistent spatial reasoning.
Topic Layout
| Type | Topic | QoS | Notes |
|---|---|---|---|
SpatialZone |
spatialdds/<scene>/events/zone/v1 |
RELIABLE + TRANSIENT_LOCAL, KEEP_LAST(1) per key | Latched zone definitions. Late joiners get full zone set. |
SpatialEvent |
spatialdds/<scene>/events/event/v1 |
RELIABLE, KEEP_LAST(64) | Event stream. Consumers filter by zone_id, severity, event type. |
ZoneState |
spatialdds/<scene>/events/zone\_state/v1 |
BEST_EFFORT, KEEP_LAST(1) per key | Periodic zone status snapshots. |
// SPDX-License-Identifier: MIT
// SpatialDDS Spatial Events Extension 1.5
//
// Typed, spatially-scoped events for zone monitoring, anomaly detection,
// and smart infrastructure alerting.
#ifndef SPATIAL_CORE_INCLUDED
#define SPATIAL_CORE_INCLUDED
#include "core.idl"
#endif
module spatial {
module events {
const string MODULE_ID = "spatial.events/1.5";
typedef builtin::Time Time;
typedef spatial::core::PoseSE3 PoseSE3;
typedef spatial::core::FrameRef FrameRef;
typedef spatial::core::Aabb3 Aabb3;
typedef spatial::core::BlobRef BlobRef;
typedef spatial::core::GeoPose GeoPose;
typedef spatial::common::MetaKV MetaKV;
// ================================================================
// ENUMS (scoped to avoid literal collisions in IDL compilers)
// ================================================================
module enums {
module zone_kind_enum {
// Zone classification — what kind of spatial region this is.
enum ZoneKind {
@value(0) RESTRICTED, // entry prohibited or requires authorization
@value(1) SPEED_LIMITED, // maximum speed enforced
@value(2) CAPACITY_LIMITED, // maximum occupancy enforced
@value(3) ONE_WAY, // directional traffic constraint
@value(4) LOADING, // loading/unloading area with dwell rules
@value(5) HAZARD, // known hazard zone (chemical, height, machinery)
@value(6) MONITORING, // general observation zone (no specific constraint)
@value(7) GEOFENCE, // boundary-crossing detection only
@value(8) OTHER
};
};
module event_type_enum {
// Event type — what happened.
enum EventType {
@value(0) ZONE_ENTRY, // object entered the zone
@value(1) ZONE_EXIT, // object exited the zone
@value(2) DWELL_TIMEOUT, // object exceeded dwell time limit
@value(3) SPEED_VIOLATION, // object exceeded speed limit
@value(4) CAPACITY_BREACH, // zone occupancy exceeded capacity
@value(5) WRONG_WAY, // object traveling against one-way direction
@value(6) PROXIMITY_ALERT, // two tracked objects closer than safe distance
@value(7) UNATTENDED, // object stationary without associated person
@value(8) ANOMALY, // general anomaly (ML-detected, pattern deviation)
@value(9) LINE_CROSS, // object crossed a defined trip line
@value(10) LOITERING, // person/object lingering beyond threshold
@value(11) TAILGATING, // unauthorized entry following authorized person
@value(12) OTHER
};
};
module severity_enum {
// Severity level.
enum Severity {
@value(0) INFO, // informational (logging, analytics)
@value(1) WARNING, // advisory — may require attention
@value(2) ALERT, // actionable — requires human review
@value(3) CRITICAL // immediate intervention required
};
};
module event_state_enum {
// Event lifecycle state.
enum EventState {
@value(0) ACTIVE, // event is ongoing
@value(1) RESOLVED, // condition cleared (e.g., person left zone)
@value(2) ACKNOWLEDGED, // human acknowledged the event
@value(3) SUPPRESSED // suppressed by rule or operator
};
};
};
typedef enums::zone_kind_enum::ZoneKind ZoneKind;
typedef enums::event_type_enum::EventType EventType;
typedef enums::severity_enum::Severity Severity;
typedef enums::event_state_enum::EventState EventState;
// ================================================================
// 1. SPATIAL ZONES
// ================================================================
// (ZoneKind enum is defined in enums submodule; typedef above)
// Named spatial region with associated rules.
// Published with RELIABLE + TRANSIENT_LOCAL so late joiners
// receive the full zone layout.
@extensibility(APPENDABLE) struct SpatialZone {
@key string zone_id; // unique zone identifier
string name; // human-readable name (e.g., "Loading Bay 3")
ZoneKind kind; // zone classification
FrameRef frame_ref; // coordinate frame for geometry
// Zone geometry (axis-aligned in frame_ref)
boolean has_bounds;
Aabb3 bounds; // 3D bounding box (valid when has_bounds == true)
// Optional geo-anchor for earth-fixed zones
boolean has_geopose;
GeoPose geopose; // zone center in WGS84
// Zone rules (optional per kind)
boolean has_speed_limit_mps;
float speed_limit_mps; // max speed in m/s (for SPEED_LIMITED)
boolean has_capacity;
uint32 capacity; // max occupancy count (for CAPACITY_LIMITED)
boolean has_dwell_limit_sec;
float dwell_limit_sec; // max dwell time in seconds (for LOADING, RESTRICTED)
// Applicable object classes — which detection class_ids trigger events.
// Empty means all classes.
sequence<string, 32> class_filter;
// Schedule (optional) — zone is only active during specified hours.
// Format: ISO 8601 recurring interval or cron-like string in attributes.
boolean has_schedule;
string schedule; // e.g., "R/2024-01-01T06:00:00/PT12H" or deployment-specific
// Owner / authority
string provider_id; // who defines this zone
Time stamp; // last update time
// Extensible metadata
sequence<MetaKV, 16> attributes;
string schema_version; // MUST be "spatial.events/1.5"
};
// ================================================================
// 2. SPATIAL EVENTS
// ================================================================
// (EventType, Severity, EventState enums are defined in enums submodule; typedefs above)
// A spatially-grounded event.
//
// Keyed by event_id. Publishers update the same event_id as state
// changes (ACTIVE → RESOLVED). Subscribers use KEEP_LAST per key
// to see the latest state of each event.
@extensibility(APPENDABLE) struct SpatialEvent {
@key string event_id; // unique event identifier
EventType type; // what happened
Severity severity; // how urgent
EventState state; // lifecycle state
// Where — zone reference (optional; not all events are zone-scoped)
boolean has_zone_id;
string zone_id; // references SpatialZone.zone_id
// Where — 3D position of the event (in the zone's or scene's frame_ref)
boolean has_position;
spatial::common::Vec3 position; // event location (meters)
FrameRef frame_ref; // coordinate frame for position
// What — triggering detection(s)
boolean has_trigger_det_id;
string trigger_det_id; // primary triggering Detection3D.det_id
boolean has_trigger_track_id;
string trigger_track_id; // tracked object ID (from Detection3D.track_id)
string trigger_class_id; // class of triggering object (e.g., "person", "forklift")
// Who — secondary object for relational events (PROXIMITY_ALERT, TAILGATING)
boolean has_secondary_det_id;
string secondary_det_id;
// Measured values (populated per event type)
boolean has_measured_speed_mps;
float measured_speed_mps; // for SPEED_VIOLATION
boolean has_measured_dwell_sec;
float measured_dwell_sec; // for DWELL_TIMEOUT, LOITERING, UNATTENDED
boolean has_measured_distance_m;
float measured_distance_m; // for PROXIMITY_ALERT
boolean has_zone_occupancy;
uint32 zone_occupancy; // current count for CAPACITY_BREACH
// Confidence
float confidence; // [0..1] — event detection confidence
// Evidence — optional media snapshot or clip
boolean has_evidence;
BlobRef evidence; // reference to snapshot image or video clip
// Narrative — optional human-readable description
boolean has_description;
string description; // e.g., "Forklift stopped in pedestrian corridor
// near anchor loading-bay-3 for 4 minutes"
// Timing
Time event_start; // when the event condition began
Time stamp; // latest update time (may differ from event_start)
// Producer
string source_id; // who detected this event
// Extensible metadata
sequence<MetaKV, 8> attributes;
string schema_version; // MUST be "spatial.events/1.5"
};
// ================================================================
// 3. ZONE STATE (periodic summary)
// ================================================================
// Lightweight periodic snapshot of a zone's current status.
// Enables dashboards and capacity management without maintaining
// event counters client-side.
@extensibility(APPENDABLE) struct ZoneState {
@key string zone_id; // references SpatialZone.zone_id
uint32 current_occupancy; // number of tracked objects currently in zone
boolean has_capacity;
uint32 capacity; // echoed from SpatialZone for convenience
// Active alert count by severity
uint32 active_info;
uint32 active_warning;
uint32 active_alert;
uint32 active_critical;
// Class breakdown (optional — top N classes present)
sequence<MetaKV, 8> class_counts; // namespace = class_id, json = {"count": N}
Time stamp;
string source_id;
// schema_version intentionally omitted: ZoneState is a lightweight
// summary; version is inferred from the accompanying SpatialZone.
};
}; // module events
}; // module spatial
Example JSON (Informative)
Zone Definition:
{
"zone_id": "zone/facility-west/pedestrian-corridor-B",
"name": "Pedestrian Corridor B",
"kind": "RESTRICTED",
"frame_ref": { "uuid": "f1a2b3c4-...", "fqn": "facility-west/enu" },
"bounds": { "min_xyz": [10.0, 0.0, 0.0], "max_xyz": [25.0, 5.0, 3.0] },
"has_geopose": false,
"has_speed_limit_mps": false,
"has_capacity": false,
"has_dwell_limit_sec": true,
"dwell_limit_sec": 120.0,
"class_filter": ["forklift", "agv", "pallet_truck"],
"has_schedule": true,
"schedule": "R/2024-01-01T06:00:00/PT14H",
"provider_id": "safety/zone-manager",
"stamp": { "sec": 1714070400, "nanosec": 0 },
"schema_version": "spatial.events/1.5"
}
Event:
{
"event_id": "evt/facility-west/2024-04-26T12:05:00Z/001",
"type": "DWELL_TIMEOUT",
"severity": "ALERT",
"state": "ACTIVE",
"has_zone_id": true,
"zone_id": "zone/facility-west/pedestrian-corridor-B",
"has_position": true,
"position": [17.2, 2.1, 0.5],
"frame_ref": { "uuid": "f1a2b3c4-...", "fqn": "facility-west/enu" },
"has_trigger_det_id": true,
"trigger_det_id": "det/fused/forklift-07",
"has_trigger_track_id": true,
"trigger_track_id": "track/forklift-07",
"trigger_class_id": "forklift",
"has_measured_dwell_sec": true,
"measured_dwell_sec": 247.0,
"confidence": 0.94,
"has_evidence": true,
"evidence": { "blob_id": "snap-20240426-120500-cam3", "role": "evidence/jpeg", "checksum": "sha256:ab12..." },
"has_description": true,
"description": "Forklift stopped in pedestrian corridor B near loading-bay-3 for 4 min 7 sec",
"event_start": { "sec": 1714131653, "nanosec": 0 },
"stamp": { "sec": 1714131900, "nanosec": 0 },
"source_id": "analytics/zone-monitor",
"schema_version": "spatial.events/1.5"
}