深入解析:从数据模型、实例连接语义到存储与查询)
Rerun 组件批次Component Batches深入解析从数据模型、实例连接语义到存储与查询【免费下载链接】rerunVisualize, query, and stream to train on multimodal robotics data.项目地址: https://gitcode.com/GitHub_Trending/re/rerun导读本文围绕 Rerun 数据模型中最基础也最容易被忽视的概念——**组件批次Component Batches**展开。在 Rerun 中任何组件在任一时刻的值本质上都是一个列表batch理解这一点是掌握点云、检测框、骨架关键点、机器人关节等批量数据高效落盘的前提。读完本文你将掌握批次的不可变语义、实例连接instance joining与 clamping 规则、与 latest-at 查询的组合行为以及批次在 Chunk 中以 Arrow List Array 存储并暴露在 DataFrame 查询中的完整链路。什么是组件批次组件值永远是一个列表在 Rerun 的数据模型中一个实体entity在某时刻的某个组件component的值永远是一个列表——即一个批次batch而不是单个标量。这一设计贯穿整个数据模型从日志 API 到存储层再到查询层都保持一致。考虑下面这个最简单的例子rr.log(/data, rr.Points3D(positions[0.0, 0.0, 0.0]))为了方便使用rr.Points3D原语类型archetype接受单个位置但实际发生的是对应的Position3D组件被记录为长度为 1 的批次。因此以下两种写法完全等价single_point [0.0, 0.0, 0.0] rr.log(/data, rr.Points3D(positionssingle_point)) rr.log(/data, rr.Points3D(positions[single_point]))批次并不只限于单个元素记录更大规模的批次当然也是可能的rr.log(/data, rr.Points3D(positions[[0.0, 0.0, 0.0], [1.0, 1.0, 1.0]]))以批次形式记录数据的场景非常普遍点云如上面的例子目标检测得到的包围盒bounding boxes骨架中跟踪的关键点keypoints机器人手臂的各个关节值joint values。这也是为什么在日志 API 中大多数原语类型都以复数形式命名例如上面的rr.Points3D、rr.Points2D、rr.Arrows3D等——它们天然表达一个批次。这种命名方式背后隐含的语义是一个批次中的每一个独立的值被称为一个实例instance。相关概念梳理在 Entities and Components 中可以看到实体entity是你用rr.log()记录的事物由实体路径entity path标识组件是附着在实体上的数据位置、颜色、像素等而原语类型archetype则是相关组件的语义组合例如Points3D内部就是positions、colors、radii等组件的集合。组件批次是不可变的immutable当数据以批次的形式记录到某个时间点的某个组件上时该批次就是不可变的不能向其中追加新的实例不能修改其中已有的实例。如果需要改变必须整体重新记录整个批次新的批次会替换旧的批次。一个值得注意的细节是当同一个组件在同一时间点被多次记录时最后一次记录的批次会被使用但之前记录的批次仍然保留在存储storage中。也就是说覆盖只是查询语义层面的结果存储层并不会物理删除旧数据。这一点与 Chunk 的存储方式直接相关——多个时间点的数据会累积存放在 Chunk 的组件列中见下文存储一节Rerun 依赖 latest-at 查询语义来决定哪个批次生效。实例连接语义Instance joining semantics组件通常是作为原语类型的一部分被记录的。原语类型是相关组件的语义组合参见 Entities and Components。大多数原语类型具有实例连接语义instance joining semantics即一个组件的第 n 个实例与另一个组件的第 n 个实例相对应。以rr.Points3D为例其colors字段的第 n 个值作用于positions字段的第 n 个值。也就是说颜色与位置按索引一一对应。从源码结构看实例连接并不是在日志时完成的而是在查询/可视化时通过迭代器实现。在crates/store/re_query/src/bin/clamped_zip.rs中可以找到clamped_zip_*系列迭代器组合子的生成逻辑其文档注释明确指出clamped zip 迭代器中元素的个数与 required主迭代器中元素的个数一致——这正是 clamping 语义的代码级体现。实例 clampingInstance clamping具有实例连接语义的原语类型通常会有一个必需的组件作为主组件primary component。主组件决定了该原语类型代表多少个逻辑实例。对于rr.Points3D而言主组件是Position3D其批次的长度决定了 Viewer 中会显示多少个点。对于主组件以外的其他组件规则如下如果某个组件有更多的实例多余的实例会被 Viewer 忽略如果某个组件有更少的实例最后一个实例会按需重复repeat补齐到与主组件相同的长度。后者被称为clamping 语义也可以理解为以主组件为基准的左连接left-join。clamping 让很多自然的日志调用成为可能例如rr.log( /data, rr.Points3D( positions[[0.0, 0.0, 0.0], [1.0, 1.0, 1.0], [2.0, 2.0, 2.0]], radii0.5, ), )这里记录了一个 N3 的位置批次以及一个 N1 的半径批次。这个唯一的半径值会被 clamping 到三个位置上因此在 Viewer 中三个点共用同一个半径显示。从实现角度看查询端通过clamped_zip迭代器把主组件批次与其余组件批次对齐主组件决定迭代次数其余组件不足补最后一个、超出被截断。这对应于文档中所说的clamping 语义可以看作以主组件为基准的左连接。实例连接与 latest-at 语义实例连接作用于 Viewer 中正在显示的各组件的当前值。需要牢记的是latest-at 语义依然生效——也就是说被连接的各个组件并不需要在同一时间点被记录。举例说明你可以在录制的开始阶段记录一个带有位置和颜色的点云之后只记录更新的位置。Viewer 在渲染时会按照 latest-at 语义去查找最近一次记录的颜色并将其用于显示。这正是 latest-at.md 中partial updates部分更新理念的基石由于实例连接发生在查询时你完全可以在不同时间点只更新某个组件而让其他组件沿用历史值。实例连接并非普遍适用需要强调实例连接语义并不是通用的。有些原语类型完全不使用它有些则部分使用。以rr.Mesh3D为例vertex_positions是其必需组件决定了网格中顶点的数量部分组件与vertex_positions具有实例连接语义例如vertex_colors顶点颜色和vertex_texcoords顶点纹理坐标但另一些组件没有实例连接语义例如triangle_indices三角形索引它包含的是指向vertex_positions批次中顶点的三元组索引用来定义要显示的三角形其长度与顶点数没有一一对应关系。因此在使用具体原语类型时应查阅其字段说明参见 Archetypes 参考确认哪些组件与主组件连接、哪些独立定义结构。存储批次如何保存在 Chunk 中在内部组件数据以 **Arrow List 数组Arrow List arrays**的形式存储在 chunk 中List 数组的每一行对应一个时间点每行中的值对应那个时间点的组件批次由于 List 数组的各行可以有不同的长度因此 Rerun 天然支持每个时间点的组件批次长度不同。一个 Chunk 是 Arrow 编码的、面向列的表结构包含控制列如行 ID、时间/索引列如log_tick、log_time、stable_time以及若干组件列如Points3D:colors、Points3D:positions、Points3D:radii。组件列中每个单元格就是一个组件批次——组件批次是 Rerun 中数据的原子单元。两种写入路径行导向的logAPI每次调用生成一个行 ID、为内置时间线赋值并把传入的数据打包成组件批次交给 micro-batcher 累积进当前 chunk达到一定大小或周期性阈值后发送。该路径在 SDK 端以小批量构建 chunk用少量延迟与内存换取更高的传输与摄取效率见 chunks.md 与 micro-batching 参考。列导向的send_columnsAPI一次性为多个索引列与组件列发送数据。它绕过时间上下文与 micro-batcher只包含调用中显式给出的时间线不自动添加log_time、log_tick或任何用户时间线详见 send_columns 指南。组件列中的每一行同样可以是一个批次——例如一次调用记录一个点云随时间演化的全部位置批次。在crates/store/re_chunk/src/中chunk.rs、latest_at.rs、earliest_at.rs、merge.rs等文件共同实现了 chunk 的构建、按时间查询与合并slice.rs中则可以看到对 chunk 进行行/列切片时对 span 做 clamping 的处理逻辑如span.clamped_to(self.num_rows())与批次层面的 clamping 语义相呼应。查询视角DataFrame 中的 ListArray 列批次以 List 数组存储的设计在查询 Rerun 数据时体现得最为直观返回的 DataFrame 中组件列的数据类型永远是ListArray——即使底层列每行只有一个值或者所有行批次长度相同。例如 Dataframe queries 中展示的查询结果其组件列类型标注为List[nullable f64]如Scalars:scalars列每一行的单元格是包含若干数值的列表。这意味着下游分析代码必须意识到组件列是列表套标量的结构在处理时不能假设每行只有一个标量同时也正是这种结构让不同时间点上长度可变的批次可以整齐地放进同一张表。此外DataFrame 查询中行与批次的对应关系也值得注意行由索引时间线的唯一值驱动生成组件列在某一行的值就是该时间点上该组件的批次缺失时为 null。静态数据static data虽然对所有时间有效但它本身不能驱动生成行只会出现在由其他时序数据生成的行中——因此在查询大量静态批次数据时可能需要用filter_contents()将其单独过滤出来查询以避免同一份大静态批次在每个行里被重复产出详见 dataframe-queries.md 的 FAQ 部分。实践建议与进阶阅读善用 clamping 减少数据量当多个组件共享同一属性时如整片点云使用统一半径、统一颜色记录 N1 的批次即可无需为每个实例复制值Viewer 会自动补全。记住批次的不可变性与覆盖规则要修改一个时间点上的批次必须整体重记同一时间点多次记录时以最后一次为准但旧批次仍留在存储中。结合 latest-at 做部分更新实例连接发生在查询时因此可以在不同时间分别更新不同组件如先记颜色、后只更新位置Viewer 会组合出当前状态。更多内容见 Query semantics partial updates。批量高效写入用send_columns当数据本身是列式结构如已经按时间组织的数组时用send_columns一次性发送多个批次比逐行log更高效注意它绕过时间上下文与 micro-batcher时间线需显式给出。继续深入 Chunk 模型批次是 Chunk 组件列中的单元格理解 chunks.md 有助于把握数据如何被记录、注入、存储与查询的完整生命周期源码层面可阅读crates/store/re_chunk/src/chunk.rs与crates/store/re_query/src/bin/clamped_zip.rs中 clamping 迭代器的生成逻辑。【免费下载链接】rerunVisualize, query, and stream to train on multimodal robotics data.项目地址: https://gitcode.com/GitHub_Trending/re/rerun创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考