采集
采集的作用、引导式交互、采集进度与控制和手动发起采集。
采集
先读:设备发现与连接(设备连接成功后才能开始采集) · 读完你能:要么让默认流程跑完采集,要么在程序里接管采集交互
采集配置
自动流程
默认自动流程在创建 Sdk 时从 AlgorithmsConfig.capture 读取采集配置。配置前先按目的选择:
| 目的 | 配置 |
|---|---|
| 在交互式终端由 SDK 显示动作提示 | interaction=CaptureInteractionMode.AUTO_TERMINAL |
| 由 GUI、ROS2 或远程程序显示提示和控件 | interaction=CaptureInteractionMode.EXTERNAL |
| 忽略可复用的旧采集,完整重新采样 | force=True |
| 按佩戴者分别查找和保存结果 | operator_id="operator-a" |
例如,使用默认终端引导,并强制 operator-a 完整重新采集:
from qnbot_sdk import (
AlgorithmsConfig,
CaptureConfig,
CaptureInteractionMode,
Sdk,
)
sdk = Sdk(
devices=(device_config,),
targets=(target_config,),
algorithms=AlgorithmsConfig(
capture=CaptureConfig(
interaction=CaptureInteractionMode.AUTO_TERMINAL,
force=True,
operator_id="operator-a",
),
),
)字段类型、默认值与约束见 CaptureConfig。
手动流程
手动流程在 start_capture() 调用中直接提供本次采集所需的参数:
| 目的 | 参数 |
|---|---|
| 选择本次使用的算法包和采集计划 | package_ids=("<package-id>",) |
| 按佩戴者查找和保存结果 | operator_id="operator-a" |
| 忽略可复用的旧采集,完整重新采样 | force=True |
例如,强制 operator-a 使用一个算法包完整重新采集:
session = device.start_capture(
package_ids=("<package-id>",),
operator_id="operator-a",
force=True,
)如果你只想使用已有采集重新计算标定,先看概览中两个 force 的区别。
采集状态与控制
默认自动流程会自行检查并推进采集,不需要调用下面的接口。只有两种接管场景需要它们:
- 应用决定何时启动:
capture_status()→ 判断状态 →start_capture()。 - 外部 UI 接管提示:
capture_progress()→ 展示当前动作 →capture_control()。
判断是否启动手动采集
先查询当前算法包和佩戴者是否仍需采集:
readiness = device.capture_status(
package_ids=("<package-id-1>", "<package-id-2>"),
operator_id="operator-a",
)
render_capture_readiness(readiness.state, readiness.plan, readiness.stages)根据 readiness.state 决定下一步:
| 状态 | 下一步 |
|---|---|
NEEDS_CAPTURE | 调用 start_capture() 创建采集会话 |
READY | 复用已有采集,继续标定 |
NOT_REQUIRED | 本次选择无需采集,继续后续流程 |
plan 和 stages 用于向佩戴者展示本次计划。完整字段见 CaptureReadiness,启动代码见手动发起采集。
在外部 UI 中推进采集
订阅进度,展示当前动作,并用同一个进度中的标识响应:
from qnbot_sdk.glove import CaptureStageState
progress = device.capture_progress()
control = device.capture_control()
def on_progress(sample) -> None:
stage = sample.value.stage
render_prompt(stage.prompt, stage.collected_sample_count,
stage.required_sample_count)
if (stage.state is not CaptureStageState.AWAITING_CONFIRMATION
or stage.request_id is None):
return
if stage.optional and should_skip():
control.skip(stage.request_id)
else:
control.confirm(stage.request_id)
progress.subscribe(on_progress)render_prompt() 和 should_skip() 由应用实现。重试阶段时调用 control.retry(stage_id),取消当前请求时调用 control.cancel(request_id);标识必须来自最新进度。完整流程见外部接管引导交互,字段定义见 CaptureProgress。
采集方式
引导式采集交互
默认 auto_terminal 会在交互控制台中引导采集:前台 run_forever() 可以启用终端交互;run_background() 启动时若仍需采集或标定,也会先完成终端引导。真实 TTY 会自动启用,其他控制台的判断见 终端输出与交互。
终端输出示例:
[Capture] glove.primary.right
open_hand: Open your hand
Press Enter to begin...
[####################] 2/2
[Calibration] glove.primary.right -> hand.primary.right completedglove.primary.right 是右手手套,hand.primary.right 是输出目标。按 Enter 后,进度条在同一行刷新;动作提示和采样数由算法包决定。非交互环境或手动更新流程需要引导采集时,应使用 external 模式,由应用接管确认等操作。
外部接管引导交互
将 interaction 设为 external 后,GUI、ROS2 等外部程序负责展示提示,并通过手套控制接口调用 confirm()、skip()、retry() 或 cancel()。SDK 仍负责采集与标定;各 target 的状态见 标定。
下例沿用前文的 device_config、target_config;UI 函数和事件循环由应用实现。完整示例见 external_input_run_background.py:
from qnbot_sdk import (
AlgorithmsConfig,
CalibrationReadinessState,
CaptureConfig,
CaptureInteractionMode,
Sample,
Sdk,
)
from qnbot_sdk.glove import (
CalibrationProgress,
CaptureProgress,
CaptureStageState,
)
sdk = Sdk(
devices=(device_config,),
targets=(target_config,),
algorithms=AlgorithmsConfig(
capture=CaptureConfig(
interaction=CaptureInteractionMode.EXTERNAL,
),
),
)
glove = sdk.glove()
device = glove.device()
readiness = device.calibration_status(name="dexhand")
if readiness.state is CalibrationReadinessState.NEEDS_CALIBRATION:
open_calibration_ui(readiness.operator_id)
capture_progress = device.capture_progress()
calibration_progress = device.calibration_progress(name="dexhand")
control = device.capture_control()
def on_confirm(request_id: str) -> None:
control.confirm(request_id)
def on_skip(request_id: str) -> None:
control.skip(request_id)
def on_cancel(request_id: str) -> None:
control.cancel(request_id)
def render_capture(sample: Sample[CaptureProgress]) -> None:
value = sample.value
stage = value.stage
render_prompt(stage.prompt, stage.collected_sample_count,
stage.required_sample_count)
if stage.request_id is None:
return
if stage.state is CaptureStageState.AWAITING_CONFIRMATION:
bind_waiting_actions(
request_id=stage.request_id,
optional=stage.optional,
on_confirm=on_confirm,
on_skip=on_skip,
on_cancel=on_cancel,
)
else:
bind_cancel_action(stage.request_id, on_cancel)
def render_calibration(sample: Sample[CalibrationProgress]) -> None:
render_calibration_job(sample.value.job)
capture_progress.subscribe(render_capture)
calibration_progress.subscribe(render_calibration)
glove.start()
glove.run_background()
try:
run_application_event_loop()
finally:
glove.request_stop()
glove.join()
glove.close()手动发起采集
需要自行决定何时查询和启动采集时,用本节的两阶段流程:先查询准备情况,再在选定的 GloveDevice 上创建采集会话并推进它。启动时机由应用控制,不同于只接管引导交互的 external 模式。
启动参数和强制重采的最小调用见采集配置中的手动流程。
回环表示继续读取同一个会话的状态,不会再次创建 Session;标定接收仍打开的已完成采集会话:
下面是示例文件里 capture_side() 的主流程摘录:查询准备情况、创建会话、推进会话、取采集结果。
# 摘自 examples/glove/python/src/host_capture_calibration.py 的 capture_side()。
# 代码逐字保留(`# ...` 标记省略),中文注释为文档补充;导入与常量见示例文件。
def capture_side(
device: GloveDevice,
glove: Glove,
package_ids: tuple[str, ...],
force: bool,
start_runtime: bool,
) -> CaptureSession:
# 查询准备情况:capture_status() 只报告计划,不创建 Session。
readiness = device.capture_status(
package_ids=package_ids,
operator_id=OPERATOR_ID,
)
# ... 示例在此打印 readiness.state ...
for stage in readiness.stages:
print(f" {stage.stage_id}: {stage.state.value}")
# 创建会话:一个 Session 执行一只手,采集与标定必须使用同一个 device。
session = device.start_capture(
package_ids=package_ids,
operator_id=OPERATOR_ID,
force=force,
)
if start_runtime:
glove.start()
deadline = time.monotonic() + WAIT_SECONDS
while time.monotonic() < deadline:
snapshot = session.snapshot()
# 完成:取采集结果,并把仍打开的 Session 交给标定(见 calibrate())。
if snapshot.state is CaptureSessionState.COMPLETED:
if session.result() is None:
raise RuntimeError("completed capture session has no result")
print(f"{device.source_id}: capture completed")
return session
# 失败或取消:报错退出,由调用方关闭已创建的会话。
if snapshot.state in (
CaptureSessionState.FAILED,
CaptureSessionState.CANCELLED,
):
raise RuntimeError(
snapshot.failure.message
if snapshot.failure is not None
else f"{device.source_id} capture stopped"
)
current = next(
(
stage
for stage in snapshot.stage_progress
if stage.stage_id == snapshot.current_stage
),
None,
)
# 阶段等待确认:用当前快照的 request_id 确认;可选阶段用 skip(),重采用 retry(stage_id)。
if (
current is not None
and current.state is CaptureStageState.AWAITING_CONFIRMATION
):
if snapshot.request_id is None:
session.cancel()
raise RuntimeError("capture confirmation is missing its request token")
prompt = next(
(
stage.prompt
for stage in snapshot.plan.stages
if stage.stage_id == snapshot.current_stage
),
snapshot.current_stage or "Continue capture",
)
answer = input(f"{device.source_id} {prompt} [Y/n]: ").strip().lower()
if answer in ("", "confirm", "y", "yes"):
session.confirm(snapshot.request_id)
else:
session.cancel()
raise RuntimeError("capture cancelled by operator")
continue
# 仍在运行:让 SDK 推进任务,然后回到读取快照。
update = glove.update()
update.sleep()
session.cancel()
raise TimeoutError(f"{device.source_id} capture timed out")关键点
capture_status()只查询、不创建 Session;状态对应的下一步见判断是否启动手动采集。CaptureSession一次执行一只手:左右手顺序创建两个 Session,不交替采集,采集与标定共用同一个GloveDevice。- 采集完成后把仍打开的 Session 交给
start_calibration(),无需接触原始帧文件或内部标识,见 手动发起标定。 - 左右手顺序采集只启动一次运行:左手
start_runtime=True,右手start_runtime=False;两手都结束后再关闭 Session 与glove。
参考
- 字段与可空行为:Capture 模型
- 参数与默认值:Capture/Calibration 会话
- 算法包安装:算法包管理
- 完整示例:
host_capture_calibration.py