Capture
What capture does, the guided interaction, capture progress and control, and starting capture manually.
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:
| Intent | Setting |
|---|---|
| Let the SDK show motion prompts in an interactive terminal | interaction=CaptureInteractionMode.AUTO_TERMINAL |
| Let a GUI, ROS2 node, or remote program present prompts and controls | interaction=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:
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:
| Intent | Parameter |
|---|---|
| Select the algorithm packages and capture plan for this run | package_ids=("<package-id>",) |
| Look up and store results for a specific wearer | operator_id="operator-a" |
| 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:
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:
| 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:
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 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
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:
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.