SDKC++ 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 (the device has to be connected before capture can start) · After this page you can: let the default flow finish the capture, or take over the capture interaction

Capture configuration

Automatic flow

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

IntentSetting
Let the SDK show motion prompts in an interactive terminalinteraction = qnbot::CaptureInteractionMode::auto_terminal
Let a GUI, ROS2 node, or remote program present prompts and controlsinteraction = qnbot::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:

qnbot::SdkConfig config;
config.devices = {device_config};
config.targets = {target_config};
config.algorithms.capture.interaction =
    qnbot::CaptureInteractionMode::auto_terminal;
config.algorithms.capture.force = true;
config.algorithms.capture.operator_id = "operator-a";

qnbot::Sdk sdk(std::move(config));

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
Look up and store results for a specific weareroperator_id
Ignore reusable capture data and collect every required sample againforce = true

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

const bool force_recapture = true;
auto session = device.start_capture(
    {"<package-id>"}, "operator-a", force_recapture);

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:

const auto readiness = device.capture_status(package_ids, operator_id);
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:

auto progress = device.capture_progress();
auto control = device.capture_control();
auto subscription = progress.subscribe(
    [&](const qnbot::Sample<qnbot::CaptureProgress>& sample) {
        const auto& stage = sample.value.stage;
        render_capture(stage);
        if (stage.state !=
                qnbot::CaptureStageState::awaiting_confirmation ||
            !stage.request_id) return;
        if (stage.optional && should_skip())
            control.skip(*stage.request_id);
        else
            control.confirm(*stage.request_id);
    });

The application supplies render_capture() 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 Capture models 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 background execution 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

A GUI, ROS2, or remote program should choose external, subscribe to the source-scoped capture_progress(), and hand the current capture request unchanged to capture_control(). After Capture completes, the calibration status of each target is described in Calibration.

The following snippet continues from the device_config and target_config already created above; open_calibration_ui(), render_capture(), render_calibration_job(), bind_waiting_actions(), and bind_cancel_action() are provided by the application. For a complete example with the same external interaction and background cleanup flow, see external_input_run_background.cpp:

qnbot::SdkConfig config;
config.devices = {device_config};
config.targets = {target_config};
config.algorithms.capture.interaction =
    qnbot::CaptureInteractionMode::external;
config.algorithms.capture.operator_id = "operator-42";

qnbot::Sdk sdk(std::move(config));
auto glove = sdk.glove();
auto device = glove.device();
auto readiness = device.calibration_status("dexhand");
if (readiness.state ==
    qnbot::CalibrationReadinessState::needs_calibration) {
    open_calibration_ui(readiness.operator_id);
}
auto capture_progress = device.capture_progress();
auto calibration_progress = device.calibration_progress("dexhand");
auto control = device.capture_control();
auto on_confirm = [&control](const std::string& request_id) {
    control.confirm(request_id);
};
auto on_skip = [&control](const std::string& request_id) {
    control.skip(request_id);
};
auto on_cancel = [&control](const std::string& request_id) {
    control.cancel(request_id);
};
auto capture_subscription = capture_progress.subscribe(
    [&](const qnbot::Sample<qnbot::CaptureProgress>& sample) {
        render_capture(sample.value.stage);
        if (!sample.value.stage.request_id) return;
        if (sample.value.stage.state ==
            qnbot::CaptureStageState::awaiting_confirmation) {
            bind_waiting_actions(*sample.value.stage.request_id,
                                 sample.value.stage.optional,
                                 on_confirm, on_skip, on_cancel);
        } else {
            bind_cancel_action(*sample.value.stage.request_id, on_cancel);
        }
    });
auto calibration_subscription = calibration_progress.subscribe(
    [](const qnbot::Sample<qnbot::CalibrationProgress>& sample) {
        render_calibration_job(sample.value.job);
    });

For the definitions and constraints of CaptureInteractionMode and operator_id, see CaptureConfig.

Starting capture manually

Applications that need to split capture into its own business stage should use the separate capture flow and explicitly call capture_status() and start_capture() on the selected GloveDevice; for the calibration side after capture completes, 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 sessions.

For a complete runnable example that captures the left and right hands in sequence and then saves the calibration result per side, see host_capture_calibration.cpp.

Algorithm packages should first be installed through the product CLI. The application passes the algorithm package IDs needed this time to the SDK and displays the actions according to the returned capture plan.

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

auto device = glove.device(qnbot::Side::left);

auto readiness = device.capture_status(
    {"<package-id-1>", "<package-id-2>"}, "operator-a");
render_capture_readiness(readiness.state, readiness.plan, readiness.stages);

const bool force_recapture = true;
auto left_session = device.start_capture(
    {"<package-id-1>", "<package-id-2>"}, "operator-a", force_recapture);
glove.start(); // Create the first start_capture() before start()
while (true) {
    const auto snapshot = left_session.snapshot();
    render_capture_plan(snapshot.plan, snapshot.stage_progress, snapshot.next_action);
    if (snapshot.state == qnbot::CaptureSessionState::completed) break;
    if (snapshot.state == qnbot::CaptureSessionState::failed ||
        snapshot.state == qnbot::CaptureSessionState::cancelled)
        throw std::runtime_error("capture stopped");

    const auto current = std::find_if(
        snapshot.stage_progress.begin(), snapshot.stage_progress.end(),
        [&](const auto& stage) {
            return snapshot.current_stage &&
                   stage.stage_id == *snapshot.current_stage;
        });
    if (current != snapshot.stage_progress.end() &&
        current->state == qnbot::CaptureStageState::awaiting_confirmation) {
        if (!snapshot.request_id)
            throw std::runtime_error("capture request_id is missing");
        wait_for_operator_confirmation();
        left_session.confirm(*snapshot.request_id);
        continue;
    }
    static_cast<void>(glove.update().sleep());
}
auto capture_result = left_session.result();
if (!capture_result)
    throw std::runtime_error("completed capture has no result");

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

A CaptureSession executes the complete plan for one hand at a time. The left and right hands must be created as two Sessions in sequence, with no interleaved capture. Before completion, result() returns std::nullopt; after completion, it returns the capture summary. Subsequent calibration passes the completed Session, which is still kept open, directly, so the application does not 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 render_* and wait_for_operator_confirmation(). 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.