Rerun Radius 组件深度解析:场景单位(Scene Units)与 UI Points 双模式尺寸语义
【免费下载链接】rerunVisualize, query, and stream to train on multimodal robotics data.项目地址: https://gitcode.com/GitHub_Trending/re/rerun
Radius 是 Rerun 数据类型体系中负责表达"尺寸"的核心组件,用于描述点、线、箭头、包围盒、圆柱体、胶囊体、椭球体等图元的半径。它最独特的设计在于:同一个Float32标量,通过符号位同时编码"场景单位"与"UI Points"两套度量体系,让开发者既能绘制随视角缩放的真实尺寸物体,也能绘制恒定屏幕大小的标记。本文将以 radius.md 为主线,结合仓库内的类型定义、自动生成的 SDK 绑定与官方示例,完整讲解 Radius 的编码原理、边界值语义、跨语言 API 用法及其在 16 个 Archetype 中的实际应用。
Radius 组件的定位与定义
Radius 的官方定义只有一句话:"The radius of something, e.g. a point."(某物体的半径,例如一个点的半径)。它属于rerun.components.Radius,状态为stable(稳定),意味着其语义与序列化格式已固化,可放心在长期项目中依赖。
该组件的规范源头位于类型定义文件 radius.def.rs:
#[rerun::rerun_type] #[python(aliases = "float")] #[python(array_aliases = "float | npt.ArrayLike")] #[rust(derive(Copy, PartialEq, PartialOrd, bytemuck::Pod, bytemuck::Zeroable))] #[rust(repr = "transparent")] #[rerun(state = "stable")] pub struct Radius { pub value: rerun::encodings::Float32, }这份.def.rs是 Rerun 代码生成管线的"单一事实来源"(由re_types_builder解析),Rust、Python、C++ 三端 SDK 的Radius类型都由它统一生成,从而保证三端语义完全一致。值得注意的是:
#[rust(repr = "transparent")]与bytemuck::Pod / Zeroable意味着 Radius 在内存中与Float32布局完全一致,可零拷贝地与 Arrow 数组互转;#[python(aliases = "float")]允许 Python 侧直接传普通float字面量;#[python(array_aliases = "float | npt.ArrayLike")]允许传 NumPy 数组批量指定半径。
双单位编码:一个Float32表达两种度量体系
Radius 最重要的设计决策是用值的符号区分单位,这是理解该组件的关键:
内部存储中,正值表示场景单位(scene units),负值被解释为 UI Points。
这意味着 Radius 根本不需要额外的判别字段(tag/discriminator),序列化时就是一个裸Float32,紧凑且高效。Rust 侧的手写扩展实现位于 radius_ext.rs,通过两个构造函数和两个访问器完成符号语义的封装:
/// Creates a new radius in scene units. /// Values passed must be finite positive. pub fn new_scene_units(radius_in_scene_units: f32) -> Self { debug_assert!(0.0 <= radius_in_scene_units, "Bad radius: {radius_in_scene_units}"); Self(Float32(radius_in_scene_units)) } /// Creates a new radius in ui points. /// Values passed must be finite positive. pub fn new_ui_points(radius_in_ui_points: f32) -> Self { debug_assert!(0.0 <= radius_in_ui_points, "Bad radius: {radius_in_ui_points}"); Self(Float32(-radius_in_ui_points)) } pub fn scene_units(&self) -> Option<f32> { self.0.is_sign_positive().then_some(*self.0) } pub fn ui_points(&self) -> Option<f32> { self.0.is_sign_negative().then_some(-*self.0) }两种度量的物理含义截然不同,对应两种典型的使用诉求:
| 度量体系 | 存储值 | 行为特征 | 典型用途 |
|---|---|---|---|
| 场景单位(Scene Units) | 正值 | 与视图缩放联动,随相机拉近拉远而等比放大缩小,是真实世界尺寸 | 机器人的点云、路标点、包围盒、圆柱半径等需要表达真实几何尺寸的对象 |
| UI Points | 负值 | 与视图缩放无关,恒定屏幕尺寸;但对应用 UI 缩放敏感 | 交互标记、关键点、类别标签的锚点等需要始终清晰可见的 UI 元素 |
关于 UI Points 的缩放行为,官方文档明确给出了换算基准:
- 100% UI 缩放时,1 UI Point = 1 像素;
- Viewer 的 UI 缩放默认跟随操作系统缩放,例如全高清(Full HD)屏幕通常为 100%,4K 屏幕通常为 200%。
因此同一个半径值,在 4K 高分屏上会被自动放大一倍(以像素计),从而在不同 DPI 的屏幕上保持一致的视觉尺寸——这正是"UI Points"相对裸像素的优势。
边界值与特殊值:符号语义的严谨落地
由于 Radius 用符号位承载语义,零值(0.0)与负零(-0.0)就成了必须明确区分的边界情况。实现中特意使用is_sign_positive()/is_sign_negative()而非>= 0/< 0比较,正是为了把 IEEE 754 中的-0.0正确归类为 UI Points(负零是有效的负号值)。这一细节由 radius_ext.rs 中的单元测试scene_point_distinction完整覆盖:
let radius = Radius(Float32(1.0)); // scene_units() == Some(1.0), ui_points() == None let radius = Radius(Float32(-1.0)); // scene_units() == None, ui_points() == Some(1.0) let radius = Radius(Float32(f32::INFINITY)); // scene_units() == Some(inf) let radius = Radius(Float32(f32::NEG_INFINITY)); // ui_points() == Some(inf) let radius = Radius(Float32(0.0)); // scene_units() == Some(0.0) let radius = Radius(Float32(-0.0)); // scene_units() == None, ui_points() == Some(0.0)可以看到,正负无穷也被符号位正确分流。此外,radius_ext.rs还提供了两个常用常量和默认值:
pub const ZERO: Self = Self(Float32(0.0)); // 零半径(场景单位) pub const ONE_UI_POINTS: Self = Self(Float32(-1.0)); // 1 个 UI Point 的半径 impl Default for Radius { fn default() -> Self { Self::new_ui_points(1.5) } }默认半径为 1.5 个 UI Points:当用户未显式指定半径时,图元会以约 1.5 像素(100% 缩放下)的屏幕尺寸绘制,保证"开了点但没给半径"也能立即看见。两个构造函数内部都用debug_assert!校验入参必须为非负,防止把负值误解为"反向半径"造成歧义。
数据编码与 Arrow 表示
Radius 的序列化链路非常直接:
- Rerun 编码(encoding):
Float32; - Arrow 数据类型(datatype):
Float32即在数据帧(Dataframe)与磁盘存储中,一个 Radius 就是 4 字节单精度浮点数,无任何额外开销。自动生成的 Rust 绑定见 radius.rs,其核心结构为:
#[repr(transparent)] pub struct Radius(pub crate::encodings::Float32); impl ::re_types_core::WrapperComponent for Radius { type Encoding = crate::encodings::Float32; fn name() -> ComponentType { "rerun.components.Radius".into() } }它实现了Deref / DerefMut、From<T: Into<Float32>>、Copy / PartialEq / PartialOrd等 trait,因此在实际使用中 Radius 与普通f32几乎可以互换:任何能转成Float32的类型都能直接Into<Radius>,配合with_radii这类批量写入接口使用非常顺滑。
跨语言 API 使用指南
Radius 在 Rust、Python、C++ 三端均有对称的 API,下面是三端对照表(以官方示例 points3d_ui_radius 为参照):
| 语义 | Rust | Python | C++ |
|---|---|---|---|
| 场景单位 | Radius::new_scene_units(0.3)或直接传0.3f32 | rr.Radius.scene_units(0.3)或直接传0.3 | rerun::Radius::scene_units(0.3f) |
| UI Points | Radius::new_ui_points(40.0) | rr.Radius.ui_points(40.0) | rerun::Radius::ui_points(40.0f) |
| 常量(1 UI Point) | Radius::ONE_UI_POINTS | — | — |
注:Python 与 C++ 的构造器名为
ui_points/scene_units(见 geo_points_simple.py、line_strips3d_ui_radius.rs 等示例),Rust 端则为new_ui_points/new_scene_units,命名略有差异但语义一致。
Rust 示例:同帧混合两种单位
fn main() -> Result<(), Box<dyn std::error::Error>> { let rec = rerun::RecordingStreamBuilder::new("rerun_example_points3d_ui_radius").spawn()?; // 两个蓝色点:场景单位半径 0.1 与 0.3,随视角缩放 rec.log( "scene_units", &rerun::Points3D::new([(0.0, 1.0, 0.0), (1.0, 1.0, 1.0)]) .with_radii([0.1, 0.3]) // 默认即场景单位 .with_colors([rerun::Color::from_rgb(0, 0, 255)]), )?; // 两个红色点:UI Points 半径 40 与 60,恒定屏幕尺寸 rec.log( "ui_points", &rerun::Points3D::new([(0.0, 0.0, 0.0), (1.0, 0.0, 1.0)]) .with_radii([ rerun::Radius::new_ui_points(40.0), rerun::Radius::new_ui_points(60.0), ]) .with_colors([rerun::Color::from_rgb(255, 0, 0)]), )?; Ok(()) }Python 示例:完全等价的写法
import rerun as rr rr.init("rerun_example_points3d_ui_radius", spawn=True) # 场景单位:默认行为 rr.log( "scene_units", rr.Points3D([[0, 1, 0], [1, 1, 1]], radii=[0.1, 0.3], colors=[0, 0, 255]), ) # UI Points:通过 rr.Radius.ui_points 显式声明 rr.log( "ui_points", rr.Points3D( [[0, 0, 0], [1, 0, 1]], radii=rr.Radius.ui_points([40.0, 60.0]), # 批量传入数组 colors=[255, 0, 0], ), )Python 侧由于array_aliases的存在,rr.Radius.ui_points(...)既能接收单个float也能接收npt.ArrayLike数组,与radii=[...]逐点对齐。
底层写入机制
在 Archetype 层面,radii字段通过with_radii统一写入。以 Points3D 为例,其实现位于 points3d.rs:
/// Optional radii for the points, effectively turning them into circles. pub fn with_radii( mut self, radii: impl IntoIterator<Item = impl Into<crate::components::Radius>>, ) -> Self { self.radii = try_serialize_field(Self::descriptor_radii(), radii); self }注意impl Into<Radius>的泛型约束:传入的每个元素都会被自动转换为Radius,因此直接传f32字面量时默认按场景单位解释,只有显式调用new_ui_points/ui_points才会编码为 UI Points。在 Points3D 的字段体系中,radii属于Recommended(推荐)字段,与colors同级——这意味着绘图时给出半径是常见的最佳实践,但并非强制。
使用 Radius 的 Archetype 全景
根据组件参考文档,Radius被 16 个 Archetype 引用,覆盖了 Rerun 几乎所有的几何图元。按用途可归纳为五类:
1. 点与轨迹类
- Points2D、Points3D:半径把点变成可见圆斑;
- LineStrips2D、LineStrips3D:折线的线宽;
- GraphNodes:图结构中的节点大小。
2. 箭头与刚体类
- Arrows2D、Arrows3D:箭头杆身的粗细;
- Capsules3D、Cylinders3D:胶囊体与圆柱的截面半径。
3. 包围盒与曲面类
- Boxes2D、Boxes3D:圆角包围盒的圆角半径;
- Ellipses2D、Ellipsoids3D:椭圆/椭球体的轴半径。
4. 地理空间类
- GeoPoints、GeoLineStrings:地图上的点位与路径宽度(官方示例中即用
Radius::ui_points指定恒定屏幕尺寸,见 geo_points_simple.py)。
5. 相机模型类
- Pinhole:针孔相机模型相关的半径描述。
实践建议与易错点
综合官方文档与源码实现,使用 Radius 时有几个关键经验值得记录:
- 单位混用是最大陷阱:
with_radii([0.1, 0.3])直接传字面量 = 场景单位;要固定屏幕尺寸必须显式用ui_points系构造器。若在一个批次中混入两种语义,务必逐元素确认符号。 - 零半径与负零:
0.0是"场景单位下的零",-0.0则是"0 个 UI Points"。Rust 实现用is_sign_negative()精确区分二者,序列化往返不会丢失该语义。 - 默认值兜底:未指定半径时使用
new_ui_points(1.5)(1.5 个 UI Points),保证图元始终可见,因此"忘设半径"不会导致画面空无一物。 - UI 缩放联动:UI Points 不随视图缩放,但会随应用 UI 缩放等比变化(100% 缩放时 1 UI Point = 1 像素)。若你的场景需要"无论 1080p 还是 4K 屏幕都保持相同屏幕尺寸",UI Points 是正确的选择;反之,需要"真实世界尺寸感"(如机器人导航中的障碍物半径),请使用场景单位。
通过本文对 radius.md、radius.def.rs、radius_ext.rs 及官方示例的交叉解读,可以看出 Radius 是 Rerun 中"小类型、深设计"的典型代表:一个 4 字节浮点数,以符号位承载双单位语义,再经由代码生成管线在 Rust/Python/C++ 三端提供一致的构造器、常量与默认值,最终支撑起从点云到地图、从包围盒到相机模型的整套可视化体系。
【免费下载链接】rerunVisualize, query, and stream to train on multimodal robotics data.项目地址: https://gitcode.com/GitHub_Trending/re/rerun
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考