增强大概是你最常用的类别。它给录制增加信息:手部关键点、头部位姿、场景标签、嵌入向量、每个任务片段是否成功。录制本身完全不变,平台会把你的特征合并进保存的结果,查看器、整理和导出都能用上它们。
增强算法默认每条录制运行一次,写 level="episode" 后每个任务片段运行一次。它读取 recording(任务片段级还会读取 episodes)以及你声明的额外 features 输入,并通过 out(一个 FeatureWriter)写出 features。
声明特征
在装饰器里列出你要写的每个特征:Feature(name, type, per, dtype=…, shape=…, unit=…, frame=…, names=…, source=…, describe=…)。type 是接口参考注册表里的语义类型,工作流编辑器就按它检查连接。per 决定了写法:
per | 方法 | 保存为 |
|---|---|---|
frame | out.per_frame(name, values) | 每帧一行的一列。行数不对会报 FEATURE_ROW_COUNT_MISMATCH。 |
recording | out.per_recording(name, value) | 一个小的 JSON 值。 |
episode | out.per_episode(name, value) | 每个任务片段一个值。 |
file | out.file(name, path) | 一个文件,例如深度视频。 |
示例:逐帧的运动强度
就是第一个算法里的那个。文件是 packages/sdk-python/examples/v1/enrich-motion-energy/main.py。
"""Enrich: the mean absolute difference between consecutive frames of the first camera, per frame.
In: the canonical recording.
Out: one new per-frame feature "motion_energy"; the recording itself is not changed.
Why: idle stretches (the arm waits, the person stands still) show as low motion, so a later step can
segment or drop them. A feature is how an algorithm adds knowledge without rewriting the recording.
"""
from dataclasses import dataclass
import numpy as np
from embodiflow import Context, Feature, FeatureWriter, Recording, algorithm, param
@dataclass
class Params:
step: int = param(1, minimum=1, title="Frame step")
@algorithm.enrich(
name="motion-energy",
title="Motion energy",
description="How much the first camera's picture changes from one frame to the next.",
features=[Feature("motion_energy", type="scalar", per="frame", dtype="float32", unit="none")],
params=Params,
)
def run(recording: Recording, out: FeatureWriter, ctx: Context[Params]) -> None:
cam = recording.cameras[0]
energy = np.zeros((recording.num_frames, 1), np.float32)
prev = None
for frame in cam.frames(step=ctx.params.step, gray=True):
current = frame.image.astype(np.float32)
if prev is not None:
energy[frame.index, 0] = np.abs(current - prev).mean()
prev = current
out.per_frame("motion_energy", energy)它的测试是 packages/sdk-python/examples/v1/enrich-motion-energy/test_main.py:用一份很小的测试数据,通过真实的运行器运行这段代码,并检查写出的值。想从它开始,运行 embodiflow algorithm init my-algorithm --category enrich,它会写出 main.py 和 test_main.py;再运行 embodiflow algorithm test my-algorithm(需要装有 pytest 和 SDK 的 Python)。
示例:每个任务片段一个值
写 level="episode" 后,函数每个任务片段运行一次。它的 recording 参数是该任务片段的时间窗口,所以帧数和时间对每个任务片段都从 0 开始。文件是 packages/sdk-python/examples/v1/enrich-episode-length/main.py。
"""Enrich (episode level): the length of each episode and whether it is long enough to train on.
In: the canonical recording and one episode of it, the step runs once per episode.
Out: two per-episode features: "length_s" and "is_long".
Why: shows the episode level: the step gets the episode window, not the whole recording.
"""
from dataclasses import dataclass
from embodiflow import Context, Episode, Feature, FeatureWriter, Recording, algorithm, param
@dataclass
class Params:
long_s: float = param(1.0, minimum=0.0, title="Seconds an episode needs to count as long")
@algorithm.enrich(
name="episode-length",
title="Episode length",
level="episode",
features=[
Feature("length_s", type="scalar", per="episode", dtype="float64", unit="s"),
Feature("is_long", type="label.bool", per="episode"),
],
params=Params,
)
def run(recording: Recording, episode: Episode, out: FeatureWriter, ctx: Context[Params]) -> None:
length = episode.duration_s # seconds, on the recording's timeline
out.per_episode("length_s", length)
out.per_episode("is_long", length >= ctx.params.long_s)它的测试是 packages/sdk-python/examples/v1/enrich-episode-length/test_main.py:用一份很小的测试数据,通过真实的运行器运行这段代码,并检查写出的值。想从它开始,运行 embodiflow algorithm init my-algorithm --category enrich --level episode,它会写出 main.py 和 test_main.py;再运行 embodiflow algorithm test my-algorithm(需要装有 pytest 和 SDK 的 Python)。
值得知道
名称。 你写 motion_energy 这样的短名称,平台会以团队专属的名称保存。和内置特征重名会被 FEATURE_NAMESPACE_VIOLATION 拒绝。
几何类型。 几何类型(pose.se3、keypoints3d.hand21、keypoints2d、深度)需要 unit 和 frame,按 file 保存的除外。逐帧估计值还应该写一列置信度。缺失值用 NaN 或 -1 标记,不要悄悄插值。
额外输入。 用 inputs={"hands": Input(requires=["keypoints3d.hand21"])} 声明。工作流编辑器只会连接声明了所需类型的输出。输入可能缺席时加 optional=True,此时参数为 None。
重定向动作。 类型为 action.joint 或 action.ee_pose、名为 action 的特征,可以用 source="retargeted" 和 describe={"targetRig": …} 写出。人类录制只能通过这种方式带上动作,控制台会把它标为重定向。
模型。 定义 setup(ctx) 来只加载一次模型,在 run 里用 state 接收它的返回值。不要在全局变量里保存随条目变化的状态。
任务片段级步骤。 任务片段级步骤对 episodes 输入里的每个任务片段运行一次,最多同时运行 4 个,最多处理 100 个。一个任务片段失败不会连累其他任务片段:只要至少有一个成功,步骤就算成功,运行会标为部分成功,并列出失败的任务片段。没有任务片段时步骤被跳过,按「不适用」处理。
合并。 你不需要自己合并特征。特征连到输出节点后,工作流会自动加一个步骤,把它们写进保存的录制,运行视图里显示为「合并特征」。