Rerun 中 ClassDescriptionMapElem 详解:AnnotationContext 语义类映射的编码原理与实战
【免费下载链接】rerunVisualize, query, and stream to train on multimodal robotics data.项目地址: https://gitcode.com/GitHub_Trending/re/rerun
导读
ClassDescriptionMapElem是 Rerun(用于可视化、查询与流式处理多模态机器人数据的开源项目)中一个编码层(encodings)辅助类型,负责把语义类别 ID(ClassId)映射到对应的类别描述(ClassDescription),是AnnotationContext标注上下文组件的内部键值对元素。本文将围绕该类型,从字段语义、Arrow 内存布局、与周边类型(ClassDescription/AnnotationInfo/ClassId)的嵌套关系,到 Python / Rust / C++ 三种 SDK 的实战用法逐层展开,帮助你理解 Rerun 是如何在分割、关键点与骨架连线场景中完成"类别 ID → 名称与颜色"的解析。
类型定位:AnnotationContext 内部的键值对
在 class_description_map_elem.md 中,ClassDescriptionMapElem被明确定义为:
一个用于将
encodings.ClassId映射到类别描述的辅助类型,内部使用于components.AnnotationContext。
这意味着你通常不会直接以独立组件的形式向数据流中写入它,而是把它作为AnnotationContext的列表元素批量提交。对应的 Rust 组件定义位于 annotation_context.rs,可以看到AnnotationContext的字段就是一个Vec<ClassDescriptionMapElem>:
pub struct AnnotationContext( /// List of class descriptions, mapping class indices to class names, colors etc. pub Vec<crate::encodings::ClassDescriptionMapElem>, );即:一个标注上下文 = 若干条 "类别 ID → 类别描述" 的映射记录,每条记录就是一个ClassDescriptionMapElem。类型层面的约束定义在类型定义源文件 annotation_context.def.rs 中:
pub struct AnnotationContext { pub class_map: Vec<rerun::encodings::ClassDescriptionMapElem>, }字段语义:键与值
ClassDescriptionMapElem只有两个字段,语义上构成典型的"键值对":
| 字段 | 类型 | 含义 |
|---|---|---|
class_id | 非空ClassId | 键:一个 16 位语义类别 ID(即components.ClassId) |
class_description | 非空ClassDescription | 值:该类别的名称、颜色等全部描述信息 |
其中class_id在 class_id.md 中被定义为一个16 位(UInt16)的语义类别 ID。它本身只是一个数字标识;真正承载"这个 ID 显示成什么文字、什么颜色"的是class_description这个值。
从 Rust 的自动生成实现 class_description_map_elem.rs 可以看到完全一致的结构:
pub struct ClassDescriptionMapElem { /// The key: the `components::ClassId`. pub class_id: crate::encodings::ClassId, /// The value: class name, color, etc. pub class_description: crate::encodings::ClassDescription, }Arrow 内存布局:完整嵌套的 Struct
作为 Rerun 数据模型的一等公民,ClassDescriptionMapElem通过 Arrow 格式序列化。其完整布局(来自 class_description_map_elem.md)如下:
Struct( "class_id": non-null UInt16 "class_description": non-null Struct( "info": non-null Struct( "id": non-null UInt16 "label": Utf8 "color": UInt32 ) "keypoint_annotations": non-null List(non-null Struct( "id": non-null UInt16 "label": Utf8 "color": UInt32 )) "keypoint_connections": non-null List(non-null Struct( "keypoint0": non-null UInt16 "keypoint1": non-null UInt16 )) ) )这个布局可以从实现层面得到验证:Rust 代码中的arrow_data_type()与to_arrow_opt会生成一个包含class_id(UInt16)和class_description(嵌套Struct)两个非空字段的StructArray,序列化细节参见 class_description_map_elem.rs;反序列化时同样按字段名从StructArray中取出class_id与class_description并强制非空(class_description_map_elem.rs)。
理解这个布局的关键点:
- 顶层是 Struct:键与值被绑定为一条不可分割的记录;
class_id是非空 UInt16:任何映射都必须有明确键,不允许缺失;class_description是非空 Struct:其内部又分为类别信息(info)、关键点标注列表(keypoint_annotations)与关键点连线列表(keypoint_connections)三个子字段;- 连线是一对 UInt16:
keypoint0/keypoint1各指一个关键点 ID。
值对象拆解:ClassDescription 与 AnnotationInfo
要真正用好ClassDescriptionMapElem,需要理解其"值"一侧的两个下层类型。
ClassDescription:一个语义类的完整描述
class_description.md 将ClassDescription定义为"一个语义类的描述":
info(非空AnnotationInfo):类别自身的标注信息,负责派生标签与颜色;keypoint_annotations(非空AnnotationInfo列表):该类别下所有关键点的标注信息;keypoint_connections(非空KeypointPair列表):关键点之间的连线(骨架边)。
其工作方式是:当一个实体被ClassId标注后,Rerun 使用该类别对应的AnnotationInfo派生标签与颜色;类内的关键点则用KeypointId标注,并优先使用关键点自身关联的AnnotationInfo的标签与颜色。若某条keypoint_connections中定义了两点间的连线,且这两个关键点都存在于该类别的实例中,则两点之间绘制一条边,边的标签与颜色同样取自该类别的AnnotationInfo。
AnnotationInfo:ID、标签与颜色三元组
annotation_info.md 定义了AnnotationInfo的三个字段:
| 字段 | 类型 | 含义 |
|---|---|---|
id | 非空UInt16 | 该标注所属的ClassId或KeypointId |
label | Utf8(可空) | 在 UI 中显示的标签 |
color | Rgba32(UInt32,可空) | 应用到被标注实体上的颜色 |
可见ClassDescriptionMapElem的 Arrow 布局其实就是把这套三元组(id/label/color)在info与keypoint_annotations两处复用。
使用场景:AnnotationContext 的查找语义
ClassDescriptionMapElem并不孤立存在,它服务的是 AnnotationContext 组件 的显示语义:
- 实体通过
ClassId与KeypointId表达"我属于哪个语义类别"; - 标签与颜色在适当的标注上下文中查找;
- 查找时沿着实体的路径层级向上遍历祖先,取遇到的第一个标注上下文。
也就是说,把AnnotationContext挂在某个实体路径(如masks、detections)上,会作用于该路径下所有子孙实体。该行为在 annotation_context.py 与类型定义源 annotation_context.def.rs 中有明确说明。archetype 层面的 annotation_context.md 还指出其可在 Spatial2DView、Spatial3DView 与 DataframeView 中展示。
三语言实战:如何构造 ClassDescriptionMapElem
下面三个示例(来自 docs/snippets/all/tutorials 的同一教程,三种语言实现相同逻辑)完整演示了从AnnotationInfo/ClassDescription构造映射的两种典型路径。
Python:直接把 AnnotationInfo / ClassDescription 交给 AnnotationContext
在 annotation_context.py 中:
import rerun as rr rr.init("rerun_example_annotation_context_connections") # 两个类别:一个纯标签,一个带颜色 rr.log( "masks", # 作用于 "masks" 之下的所有实体 rr.AnnotationContext( [ rr.AnnotationInfo(id=0, label="Background"), rr.AnnotationInfo(id=1, label="Person", color=(255, 0, 0)), ], ), static=True, ) # 带关键点与骨架连线的类别 rr.log( "detections", # 作用于 "detections" 之下的所有实体 rr.ClassDescription( info=rr.AnnotationInfo(0, label="Snake"), keypoint_annotations=[ rr.AnnotationInfo(id=i, color=(0, 28 * i, 0)) for i in range(10) ], keypoint_connections=[(i, i + 1) for i in range(9)], ), static=True, )Python 端之所以能这样直接传AnnotationInfo或ClassDescription,是因为转换器会自动补全ClassDescriptionMapElem:若传入的是AnnotationInfo,则先包装成ClassDescription,再取class_description.info.id作为class_id生成映射元素;具体逻辑见 class_description_map_elem_ext.py。序列化时,转换器把每个元素拆回ClassIdBatch与ClassDescriptionBatch两个 Arrow 数组,再合并成StructArray(class_description_map_elem_ext.py)。
Rust:显式使用 ClassDescriptionMapElem::from
在 annotation_context.rs 中,映射元素通过元组或结构体字面量构造:
use rerun::{ AnnotationContext, AnnotationInfo, ClassDescription, Rgba32, encodings::{ClassDescriptionMapElem, KeypointId}, }; let rec = rerun::RecordingStreamBuilder::new( "rerun_example_annotation_context_connections", ).spawn()?; // 从 (id, label) 与 (id, label, color) 元组构造映射元素 rec.log_static( "masks", // 作用于 "masks" 之下的所有实体 &AnnotationContext::new([ ClassDescriptionMapElem::from((0, "Background")), ClassDescriptionMapElem::from((1, "Person", Rgba32::from_rgb(255, 0, 0))), ]), )?; // 显式构造 ClassDescription 后包装成映射元素 rec.log_static( "detections", // 作用于 "detections" 之下的所有实体 &AnnotationContext::new([ClassDescription { info: (0, "Snake").into(), keypoint_annotations: (0..10) .map(|i| AnnotationInfo { id: i, label: None, color: Some(Rgba32::from_rgb(0, (28 * i) as u8, 0)), }) .collect(), keypoint_connections: (0..9) .map(|i| (KeypointId(i), KeypointId(i + 1))) .map(Into::into) .collect(), }]), )?;这些From转换由 SDK 扩展实现提供:(u16, &str)、(u16, &str, Rgba32)、AnnotationInfo与ClassDescription都可以直接转换成ClassDescriptionMapElem,其中从ClassDescription转换时自动取info.id作为class_id(见 class_description_map_elem_ext.rs)。
C++:通过容器构造
在 annotation_context.cpp 中:
#include <rerun.hpp> int main(int argc, char* argv[]) { const auto rec = rerun::RecordingStream("rerun_example_annotation_context_connections"); rec.spawn().exit_on_failure(); // 两个类别:一个纯标签,一个带颜色 rec.log_static( "masks", // 作用于 "masks" 之下的所有实体 rerun::AnnotationContext({ rerun::AnnotationInfo(0, "Background"), rerun::AnnotationInfo(1, "Person", rerun::Rgba32(255, 0, 0)), }) ); // 关键点 + 骨架连线 std::vector<rerun::AnnotationInfo> keypoint_annotations; for (uint16_t i = 0; i < 10; ++i) { keypoint_annotations.push_back(rerun::AnnotationInfo( i, rerun::Rgba32(0, static_cast<uint8_t>(28 * i), 0) )); } std::vector<rerun::KeypointPair> keypoint_connections; for (uint16_t i = 0; i < 9; ++i) { keypoint_connections.push_back(rerun::KeypointPair(i, i + 1)); } rec.log_static( "detections", // 作用于 "detections" 之下的所有实体 rerun::AnnotationContext({rerun::ClassDescription( rerun::AnnotationInfo(0, "Snake"), keypoint_annotations, keypoint_connections )}) ); }注意三处示例都使用了static=True(Python)或log_static(Rust / C++):标注上下文通常是在数据录制开始时一次性定义的静态元数据,无需随时间变化。
从定义到绑定的生成链
ClassDescriptionMapElem这类类型并非手工维护,而是由 Rerun 的类型构建器自动生成。以 Rust 为例,生成的代码头部标注了来源:
// DO NOT EDIT! This file was auto-generated by crates/build/re_types_builder/src/codegen/rust/api.rs // Based on "crates/build/re_type_definitions/rerun/encodings/class_description_map_elem.def.rs".也就是说,类型语义的"唯一事实来源"是crates/build/re_type_definitions下的定义文件,构建器据此生成 Rust、Python、C++ 三种语言绑定(Rust 绑定在 class_description_map_elem.rs 与其_ext扩展文件,Python 绑定在 rerun_py/rerun_sdk/rerun 下的 encodings 与 components 目录)。这也是"编码层类型 + 组件层类型"这一分层设计的由来:ClassDescriptionMapElem、ClassDescription、AnnotationInfo归属encodings(纯编码结构),而AnnotationContext归属components(可落盘组件),后者通过Vec<ClassDescriptionMapElem>复用前者。
稳定性与使用注意事项
- 标记为 unstable:
ClassDescriptionMapElem在文档与源码中均带有⚠️ This type is _unstable_ and may change significantly in a way that the data won't be backwards compatible.警告,且状态标注为#[rerun(state = "unstable")](见 annotation_context.def.rs)。这意味着该类型在后续版本中可能以不向后兼容的方式变化,涉及数据落盘与长期归档时需关注版本对应关系。 - 不直接独立使用:它是
AnnotationContext的内部实现细节。日常开发中优先通过rr.AnnotationContext(...)/AnnotationContext::new(...)高阶 API 构造,转换器会自动补齐class_id。 - 查找语义依赖路径层级:标注上下文沿实体路径向上查找第一个匹配项,因此把上下文挂在越靠近根的位置,影响范围越大;挂得越深,越能覆盖局部子树的显示规则。
总结
ClassDescriptionMapElem是 Rerun 标注体系中最小的"映射单元":一条记录绑定一个 16 位ClassId与其完整的ClassDescription(类别信息 + 关键点标注 + 骨架连线),多个映射元素聚合成AnnotationContext,再通过路径层级查找为分割掩码、检测框和关键点骨架提供标签与颜色。理解它的字段语义、Arrow 嵌套布局与三语言构造方式,即可在机器人视觉数据可视化中高效地定制语义标注显示规则。
【免费下载链接】rerunVisualize, query, and stream to train on multimodal robotics data.项目地址: https://gitcode.com/GitHub_Trending/re/rerun
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考