Qualisys PySide6 SDK
PySide6-based SDK for the Qualisys Real-Time (RT) protocol. Stream motion capture data from QTM using Qt's native networking and signals/slots.
Installation
Requires Python 3.10+ and PySide6 6.5+.
We recommend using a virtual environment to avoid conflicts with other packages:
# Create and activate a virtual environment
python -m venv .venv
# Windows
.venv\Scripts\activate
# macOS / Linux
source .venv/bin/activate
Then install the package:
pip install "qtm-rt-pyside6[scene]"
The scene extra adds Qt Quick 3D (PySide6-Addons) for the 3D scene helpers.
For the connection, discovery and parameters alone, pip install qtm-rt-pyside6
is enough: it needs only PySide6-Essentials (QtCore, QtGui, QtNetwork, QtQml,
QtQuick, QtQuickControls2, QtWidgets). Apps that use other Qt modules, such as
WebEngine or Charts, install PySide6 (or PySide6-Addons) themselves.
Quick Start
import sys
from PySide6.QtCore import QCoreApplication
from qtm_rt_pyside6 import QTMConnection, QRTPacket
def on_packet(packet: QRTPacket):
print(f"Frame {packet.framenumber}")
result = packet.get_3d_markers()
if result:
header, markers = result
for marker in markers:
print(f" {marker.x}, {marker.y}, {marker.z}")
app = QCoreApplication(sys.argv)
connection = QTMConnection()
connection.connected.connect(lambda: connection.streamFrames(["3d"]))
connection.packetReceived.connect(on_packet)
connection.error.connect(lambda msg: print(f"Error: {msg}"))
connection.connectToHost("127.0.0.1")
sys.exit(app.exec())
API
QTMConnection Signals
| Signal | Description |
|---|---|
connected() |
Successfully connected and version negotiated |
disconnected() |
Disconnected from QTM |
error(str) |
Connection or protocol error |
packetReceived(QRTPacket) |
Data packet during streaming |
eventReceived(QRTEvent) |
QTM event (capture started, etc.) |
responseReceived(str, object) |
Command response (command_name, data) |
QTMConnection Methods
Core methods to get started:
connectToHost(host, port=22223, version="1.25", timeout_ms=5000)— Connect to QTMdisconnectFromHost()— Close the connectionstreamFrames(components, frames="allframes", udp_port=None, udp_address=None)— Start streaming; passudp_portto receive frames over UDP instead of TCPstreamFramesStop()— Stop streaminggetParameters(parameters)— Request current configuration as XML
See the class docstring or run help(QTMConnection) for the full method
list — control, load/save, trigger, LED, quit, XML settings, etc.
Components
Valid streaming components: 2d, 2dlin, 3d, 3dres, 3dnolabels, 3dnolabelsres, analog, analogsingle, force, forcesingle, 6d, 6dres, 6deuler, 6deulerres, gazevector, eyetracker, image, timecode, skeleton, skeleton:global
3D scene
qtm_rt_pyside6.scene turns a connection into a 3D scene: QTM's labeled and
unlabeled markers, its bones, rigid bodies and solved skeletons. A small app:
import sys
from PySide6.QtGui import QGuiApplication
from PySide6.QtQml import QQmlApplicationEngine
from qtm_rt_pyside6 import QTMConnection
from qtm_rt_pyside6.scene import QtmScene, apply_theme
from qtm_rt_pyside6.scene.quick3d import setup_engine
app = QGuiApplication(sys.argv)
apply_theme(app)
connection = QTMConnection()
scene = QtmScene(connection)
engine = QQmlApplicationEngine()
setup_engine(engine)
engine.rootContext().setContextProperty("qtmScene", scene)
engine.loadData(b"""
import QtQuick
import Qualisys.Scene
Window { width: 1280; height: 800; visible: true; QtmView { anchors.fill: parent; scene: qtmScene } }
""")
connection.connectToHost("127.0.0.1")
sys.exit(app.exec())
QtmScene(connection)asks QTM for its 3D, 6D and skeleton parameters, streams3d,3dnolabels,6dandskeleton:global, and asks again after QTM events (capture or file playback started, calibration) or when the stream no longer matches the parameters. The latest frame wins and is published at 60 Hz (frameChanged);frameReceived(frame)fires for every frame, for app logic that must not miss one.pick(),bounds()andhighlight()serve the views;DemoSource(scene)feeds simulated data when no QTM is at hand.SceneFramedecodes a packet on first use:labeled,unlabeled,rigid_bodies,joints(named"<skeleton>:<segment>"),skeleton_bones,user_bonesandposition(name), all in QTM coordinates (mm).frame.mappingconverts to Qt Quick 3D's Y-up scene, honouring QTM'sAxisUpwards; for Z up that is(x, y, z) -> (x, z, -y).import Qualisys.Scene(QML) givesQtmView, a ready-made view with QTM's mouse (left-drag orbits, right-drag pans, wheel zooms), picking, labels, floor, grid, axes and layer switches, and its parts for custom views:QtmCameraController,MarkerLayer,BoneLayer,SkeletonLayer,RigidBodyLayer,LabelOverlay,FloorandQtmAxes.- Skeletons are drawn with keeled bones by default: octahedral bones with
one sharp, light ridge that marks each segment's forward axis, so a bone's
twist is visible. Left bones are blue, right gold.
QtmView.boneStylealso takes"octahedral","capsule"and"stick";axesModeadds local-axis tripods ("selected"or"all"). - Look: the
QtmThemesingleton holds the colors and fonts (neutral dark grays, Montserrat and Open Sans, one red accent); every token can be overridden.QtmButton,QtmSwitchandQtmSectionmatch it, andapply_theme(app)sets up Fusion with a matching palette. The fonts are used when installed, or from a folder passed toload_fonts().
The data layer (qtm_rt_pyside6.scene) needs only PySide6-Essentials;
qtm_rt_pyside6.scene.quick3d needs the scene extra and says so if it is missing.
Render thread. Qt Quick 3D calls the instance tables' getInstanceBuffer()
from its render thread, which then needs Python's GIL. A Python call that blocks
the GUI thread until the render thread is done deadlocks: QTest.qWait() and
QQuickWindow.grabWindow() do. Use app.exec(), processEvents(), pytest-qt's
qtbot.wait() / waitSignal() and the asynchronous QQuickItem.grabToImage(),
or set QSG_RENDER_LOOP=basic to render on the GUI thread.
PyInstaller picks up the Qualisys.Scene QML files through the package's
hook (pyinstaller40 entry point); no extra --add-data is needed.
Examples
Ready-to-run scripts live in examples/ — see
examples/README.md for what each one demonstrates
and how to run it. basic_example.py is the shortest path to verify
your setup; the GUI examples (gui_example.py and gui_3d_example/)
show how to wire the SDK into a desktop application, and scene_minimal.py
is the 3D scene above as a script (--demo runs it without QTM).
Known Limitations
- No automatic reconnection. If the connection drops, you must call
connectToHost()again. Listen to thedisconnectedsignal to detect this. - Single-threaded.
QTMConnectionandQTMDiscoveryareQObjectsubclasses and must be used from the thread that created them (typically the main thread). Use Qt signals for cross-thread communication. - PySide6 below 6.12 on Python 3.10 and 3.11 (except on Windows). PySide6 6.12.0's Linux wheels are built for Python 3.12 but install on 3.10 and 3.11, where apps abort with
free(): invalid pointer(PYSIDE-3474). The package therefore keeps PySide6 below 6.12 there; macOS is untested, so it is included. Use Python 3.12 or later for the latest PySide6. - Password sent in plain text.
takeControl(password)transmits the password unencrypted over TCP. This is a QTM protocol limitation — use on trusted networks only.
Development
To work on the SDK itself:
git clone https://github.com/qualisys/qualisys_pyside6_sdk.git
cd qualisys_pyside6_sdk
# Create and activate a virtual environment
python -m venv .venv
# Windows
.venv\Scripts\activate
# macOS / Linux
source .venv/bin/activate
# Install dev dependencies (pytest, pytest-qt, ruff, build) and the package
pip install -r requirements-dev.txt
pip install -e ".[scene]"
Tests that need Qt Quick 3D are skipped without the scene extra, and the
render tests are skipped where Qt Quick 3D cannot render (no GPU or software GL).
Run tests and linting:
pytest -v
ruff check .
ruff format --check .
License
MIT — see LICENSE and THIRD_PARTY_LICENSES.
Metadata
Release files for qtm-rt-pyside6 1.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| qtm_rt_pyside6-1.1.0.tar.gz | 100.4 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| qtm_rt_pyside6-1.1.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 166.2 kB
Release files / qtm_rt_pyside6-1.1.0.tar.gz
| Download URL | qtm_rt_pyside6-1.1.0.tar.gz |
|---|---|
| Size | 100.4 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
b44f2792bf6bb0a5243bb4fc4a64cd59656200b5e430da6c68c1f1373559f8d2
|
|
BLAKE2b-256 checksum How to use checksums |
d091c70e2f879881de430fd8f1bb2a2d5d5c5f91786e4456bcca216a84d86f4c
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 14, 2026.
Transparency logRelease files / qtm_rt_pyside6-1.1.0-py3-none-any.whl
| Download URL | qtm_rt_pyside6-1.1.0-py3-none-any.whl |
|---|---|
| Size | 65.8 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
1608b50147a3794e6766891d8fc29d7bd48b4407d253a466e9cf816f8cd48d48
|
|
BLAKE2b-256 checksum How to use checksums |
32aeab796886892866488dd07b0076c683a50e1a54f53a20f3d280f65f1dcd4c
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 14, 2026.
Transparency log