Capture
What capture does, the guided interaction, capture progress and control, and starting capture manually.
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:
| Intent | Setting |
|---|---|
| Let the SDK show motion prompts in an interactive terminal | interaction = qnbot::CaptureInteractionMode::auto_terminal |
| Let a GUI, ROS2 node, or remote program present prompts and controls | interaction = qnbot::CaptureInteractionMode::external |
| Ignore reusable capture data and collect every required sample again | force = true |
| Look up and store results separately for each wearer | operator_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:
| Intent | Parameter |
|---|---|
| Select the algorithm packages and capture plan for this run | package_ids |
| Look up and store results for a specific wearer | operator_id |
| Ignore reusable capture data and collect every required sample again | force = 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:
| State | Next step |
|---|---|
needs_capture | Call start_capture() to create a capture session |
ready | Reuse the existing capture and continue to calibration |
not_required | Skip 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 completedglove.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:
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.