SDKPython SDK

Capture

What capture does, the guided interaction, capture progress and control, and starting capture manually.

Current version · 1.4.0

Capture

Read first: Device discovery & connection (capture can start only after the device is connected) · After this page you can: Let the default flow complete the capture, or take over the capture interaction in your program

Capture configuration

Automatic flow

The default automatic flow reads its capture settings from AlgorithmsConfig.capture when the Sdk is created. Choose the setting by intent:

IntentSetting
Let the SDK show motion prompts in an interactive terminalinteraction=CaptureInteractionMode.AUTO_TERMINAL
Let a GUI, ROS2 node, or remote program present prompts and controlsinteraction=CaptureInteractionMode.EXTERNAL
Ignore reusable capture data and collect every required sample againforce=True
Look up and store results separately for each weareroperator_id="operator-a"

For example, use the built-in terminal guidance and force a complete recapture for 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",
        ),
    ),
)

See CaptureConfig for field types, defaults, and constraints.

Manual flow

The manual flow provides the settings for this capture directly in the start_capture() call:

IntentParameter
Select the algorithm packages and capture plan for this runpackage_ids=("<package-id>",)
Look up and store results for a specific weareroperator_id="operator-a"
Ignore reusable capture data and collect every required sample againforce=True

For example, force a complete recapture for operator-a with one algorithm package:

session = device.start_capture(
    package_ids=("<package-id>",),
    operator_id="operator-a",
    force=True,
)

If you only want to recompute calibration from existing capture data, first see the distinction between the two force settings in Overview.

Capture status and control

The default automatic flow checks and advances capture on its own, so it does not need these APIs. Use them only when taking control:

  • Application-controlled start: capture_status() → inspect the state → start_capture().
  • External UI guidance: capture_progress() → show the current action → capture_control().

Decide whether to start manual capture

First check whether the selected algorithm packages and wearer still need capture:

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)

Choose the next step from readiness.state:

StateNext step
NEEDS_CAPTURECall start_capture() to create a capture session
READYReuse the existing capture and continue to calibration
NOT_REQUIREDSkip capture for this selection and continue

Use plan and stages to show this run to the wearer. See CaptureReadiness for complete fields and Starting capture manually for the start code.

Advance capture from an external UI

Subscribe to progress, render the current action, and respond with identifiers from that same progress update:

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)

The application supplies render_prompt() and should_skip(). Use control.retry(stage_id) to repeat a stage and control.cancel(request_id) to cancel the current request; identifiers must come from the latest progress update. See External takeover of guided interaction for the complete flow and CaptureProgress for field definitions.

Capture methods

Guided capture interaction

The default auto_terminal guides capture in an interactive console. Foreground run_forever() can enable terminal interaction; if capture or calibration is still pending when run_background() starts, startup also completes the terminal guidance first. A real TTY is enabled automatically; see Terminal output and interaction for other consoles.

Example terminal output:

[Capture] glove.primary.right
open_hand: Open your hand
Press Enter to begin...
[####################] 2/2

[Calibration] glove.primary.right -> hand.primary.right completed

glove.primary.right identifies the right glove; hand.primary.right is its output target. After Enter, the progress bar updates on the same line; the algorithm package determines the motion prompt and sample count. If a non-interactive environment or manual update needs guided capture, use external mode and let the application handle confirmation.

External takeover of guided interaction

With interaction set to external, a GUI, ROS2 node, or other program displays prompts and calls confirm(), skip(), retry(), or cancel() through the glove control interface. The SDK still handles capture and calibration; see Calibration for each target's status.

The snippet uses the device_config and target_config defined above. Your application supplies the UI functions and event loop. See the complete external_input_run_background.py example:

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()

Starting capture manually

When you need to decide for yourself when to query and start capture, select the target device first and then explicitly call capture_status() and start_capture() on that GloveDevice. This approach directly controls when capture starts, unlike the external mode that only takes over the guided interaction. To start calibration manually on the same device, see Starting calibration manually.

For the start parameters and the minimal forced-recapture call, see Manual flow under Capture configuration.

Capture and calibration use the same device, and the entire capture completes before calibration starts. The loop keeps reading the state of the same session; it does not create the session again. Calibration receives the completed capture session while it is still open; close created sessions on failure, cancellation, or timeout:

Rendering diagram

For the parameters, types and defaults, see Capture/Calibration session in the API reference.

For a runnable example of the complete sequential capture of the left and right hands and then saving the calibration results separately by side, see host_capture_calibration.py.

Algorithm packages should first be installed through the product CLI. Pass the algorithm package IDs needed this time to the SDK, and display the motions according to the returned capture plan.

Query readiness with capture_status() first, then start the capture; capture_status() does not create a Session. Capture and the subsequent calibration must use the same GloveDevice:

from qnbot_sdk import Side
from qnbot_sdk.glove import (
    CaptureSessionState,
    CaptureStageState,
)

device = glove.device(side=Side.LEFT)

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)

left_session = device.start_capture(
    package_ids=("<package-id-1>", "<package-id-2>"),
    operator_id="operator-a",
    force=False,
)

glove.start()  # Create the first start_capture() before start()
while True:
    snapshot = left_session.snapshot()
    render_capture_plan(snapshot.plan, snapshot.stage_progress, snapshot.next_action)
    if snapshot.state is CaptureSessionState.COMPLETED:
        break
    if snapshot.state in (CaptureSessionState.FAILED, CaptureSessionState.CANCELLED):
        raise RuntimeError(snapshot.failure.message if snapshot.failure else "capture stopped")

    current = next(
        (stage for stage in snapshot.stage_progress
         if stage.stage_id == snapshot.current_stage),
        None,
    )
    if current is not None and current.state is CaptureStageState.AWAITING_CONFIRMATION:
        if snapshot.request_id is None:
            raise RuntimeError("capture confirmation has no request_id")
        input("Complete the prompted motion, then press Enter: ")
        left_session.confirm(snapshot.request_id)
        continue
    glove.update().sleep()

capture_result = left_session.result()
if capture_result is None:
    raise RuntimeError("completed capture has no result")

For the complete fields, types, and nullability behavior, see CaptureReadiness, CaptureSessionSnapshot, and CaptureResult.

A CaptureSession executes the complete plan for one hand at a time. Two Sessions must be created sequentially for the left and right hands, and capture is not interleaved. Before the Session is complete, result() returns None; after completion it returns the capture summary. Subsequent calibration directly passes in the completed Session that is still open, with no need to touch raw frame files or SDK internal identifiers; see Starting calibration manually.

capture_status() queries whether the current plan still needs capture and does not create a Session; only start_capture() starts the actual session. For the next step from each state, see Decide whether to start manual capture.

The snippets show the main flow; the application supplies the render_* functions. Set a timeout and close Sessions on cancellation or failure. For an optional stage, pass its current request_id to skip(); use retry(stage_id) to repeat a stage. The linked runnable example includes interaction, timeouts, and resource cleanup.