Reading retargeting data
Skeleton, dexterous hand output, and the one-to-one, one-to-many and many-to-many mappings.
Reading retargeting data
Read first: Data channels & read methods · After this page you can: Configure targets and read Skeleton or dexhand output
Retargeting data is produced by SDK scheduling and includes Skeleton and the dexterous hand output you configure. These values are not produced by the device: reading them needs an installed algorithm package and a chosen runtime mode. For the full source description, see Data channels & read methods; for device values, go to Reading raw data.
Configure a target
A target decides which algorithm package receives the glove data and how many results come out. The minimum is an algorithm package ID:
from qnbot_sdk import TargetAlgorithm, TargetConfig
target_config = TargetConfig(
algorithms=(TargetAlgorithm("<package-id>"),),
)type, name, side and source can all be omitted: the SDK fills them in when the result is
unambiguous and fails to create the Sdk when it is not. Fill them in when you need to pin a device
or name an output; the field definitions are in
Configuration reference.
The algorithm package must be installed on this machine with the Qnbot CLI first, then the
package_id shown by qnbot algorithm list goes into TargetAlgorithm. Installing, upgrading and
uninstalling are owned by the CLI guide, see
Package management.
Recreate the Sdk after installing: a running instance does not refresh the package list.
Skeleton
The device's pose() provides reading of the raw pose captured by the glove; skeleton() provides reading of dynamic OpenXR skeleton data. Among them, joint_angles() provides reading of data for 25 joint angles, and pose() provides reading of data for 25 skeleton nodes including the wrist. When you need OpenXR skeleton data, get skeleton() directly from the device and choose the data to read. For the complete joint order and names, see Skeleton fixed joints.
skeleton() is a pre-startup configuration: it must be called after the SDK is created and before start(); repeated calls before startup return the same Skeleton object; calling it after the device has started reports a lifecycle error.
from qnbot_sdk import Sdk
from qnbot_sdk.glove import GloveConfig
def main() -> None:
sdk = Sdk(devices=(GloveConfig(),))
glove = sdk.glove()
try:
skeleton = glove.device().skeleton()
skeleton.joint_angles().subscribe(
lambda sample: print(sample.sequence, sample.value.joints)
)
skeleton.pose().subscribe(
lambda sample: print(
sample.sequence,
sample.value.names,
sample.value.positions_local,
)
)
glove.start()
glove.run_forever()
finally:
glove.close()
if __name__ == "__main__":
main()Choose a skeleton pose path by what you need:
| What you want to do | Read path |
|---|---|
| Confirm which frame the node coordinates are in | coordinate_frame |
| Align node names with the position and rotation arrays | names |
| Drive or visualise from node positions | positions_local |
| Align orientation from node quaternions | orientations_local_xyzw |
For complete fields, types and nullability behavior, see HandSkeletonPose.
How calibration is guided depends on the interaction mode: the default auto_terminal guides it in an interactive terminal and reuses a compatible calibration, while external leaves it to the application; see Guided capture interaction and External takeover of guided interaction. In external mode, select this capability with TargetName.SKELETON. For a complete example, see
skeleton.py.
Skeleton can be used together with dexterous hand output. Whether one-to-one, one-to-many or many-to-many dexterous hand mapping is used, each glove still reads Skeleton data through its own device.skeleton(); these mappings only affect the target of device.output(name=...). For configuration, see dexterous hand output.
Linux x86_64, Linux ARM64, Windows x86_64 and macOS ARM64 all support the above skeleton public API.
Dexterous hand output
First configure the target in Sdk(targets=...). After creating the SDK and selecting the corresponding glove, obtain the output by name and subscribe before startup and run_forever():
from qnbot_sdk import Sdk, TargetAlgorithm, TargetConfig
from qnbot_sdk.glove import GloveConfig
def main() -> None:
sdk = Sdk(
devices=(GloveConfig(),),
targets=(
TargetConfig(
name="dexhand",
algorithms=(TargetAlgorithm(id="<package-id>"),),
),
),
)
glove = sdk.glove()
try:
glove.device().output(name="dexhand").subscribe(
lambda sample: print(sample.value.target, sample.value.joints)
)
glove.start()
glove.run_forever()
finally:
glove.close()
if __name__ == "__main__":
main()The name in output(name=...) corresponds to TargetConfig.name. When there is only one target belonging to this glove in the configuration, the name can be omitted; when multiple targets exist, you must select by name, otherwise AmbiguousSelectionError is thrown.
Choose a field by what you need:
| What you want to do | Read field |
|---|---|
| Tell which target an output came from | target |
| Send joint target positions to hardware | joints |
| Align with your own control cycle or record the time | timestamp |
For complete fields, types and nullability behavior, see HandJointCommand.
Each TargetConfig is bound to only one source; the SDK will not fuse multiple gloves into one target. Below only device, target, output selection and subscription are shown; startup and run modes still follow the previous sections.
One-to-one
When there is only one glove and one target, the SDK can automatically associate the source with the target; when this glove has only one target output, the name parameter can be omitted when calling device.output().
from qnbot_sdk import Sdk, TargetAlgorithm, TargetConfig
from qnbot_sdk.glove import GloveConfig
sdk = Sdk(
devices=(GloveConfig(),),
targets=(
TargetConfig(
algorithms=(TargetAlgorithm(id="<package-id>"),),
),
),
)
sdk.glove().device().output().subscribe(
lambda sample: print(sample.value.joints)
)One-to-many
When one glove drives multiple dexterous hands, configure multiple targets with different name values for the same source, and call device.output(name=...) for each.
from qnbot_sdk import DeviceSelector, Sdk, TargetAlgorithm, TargetConfig
from qnbot_sdk.glove import GloveConfig
glove_source = DeviceSelector(type="glove", name="right-glove")
sdk = Sdk(
devices=(GloveConfig(name="right-glove"),),
targets=(
TargetConfig(
name="primary-hand",
source=glove_source,
algorithms=(TargetAlgorithm(id="<package-id>"),),
),
TargetConfig(
name="backup-hand",
source=glove_source,
algorithms=(TargetAlgorithm(id="<package-id>"),),
),
),
)
device = sdk.glove().device(name="right-glove")
device.output(name="primary-hand").subscribe(
lambda sample: print(sample.value.joints)
)
device.output(name="backup-hand").subscribe(
lambda sample: print(sample.value.joints)
)For a complete configuration, subscription and running example, see multiple_outputs.py.
Many-to-many
When multiple gloves drive multiple dexterous hands, configure multiple mutually independent source-to-target mappings in the same SDK, first use glove.device(name=...) to select the source, then use output(name=...) to select the target of that source.
from qnbot_sdk import DeviceSelector, Side, Sdk, TargetAlgorithm, TargetConfig
from qnbot_sdk.glove import GloveConfig
sdk = Sdk(
devices=(
GloveConfig(name="left-glove", side=Side.LEFT),
GloveConfig(name="right-glove", side=Side.RIGHT),
),
targets=(
TargetConfig(
name="left-hand",
side=Side.LEFT,
source=DeviceSelector(type="glove", name="left-glove"),
algorithms=(TargetAlgorithm(id="<package-id>"),),
),
TargetConfig(
name="right-hand",
side=Side.RIGHT,
source=DeviceSelector(type="glove", name="right-glove"),
algorithms=(TargetAlgorithm(id="<package-id>"),),
),
),
)
glove = sdk.glove()
glove.device(name="left-glove").output(name="left-hand").subscribe(
lambda sample: print(sample.value.joints)
)
glove.device(name="right-glove").output(name="right-hand").subscribe(
lambda sample: print(sample.value.joints)
)