qtm-rt-pyside6 0.3.4__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- qtm_rt_pyside6-0.3.4/.github/workflows/ci.yml +37 -0
- qtm_rt_pyside6-0.3.4/.github/workflows/publish.yml +52 -0
- qtm_rt_pyside6-0.3.4/.gitignore +31 -0
- qtm_rt_pyside6-0.3.4/CLAUDE.md +99 -0
- qtm_rt_pyside6-0.3.4/LICENSE +21 -0
- qtm_rt_pyside6-0.3.4/PKG-INFO +155 -0
- qtm_rt_pyside6-0.3.4/README.md +131 -0
- qtm_rt_pyside6-0.3.4/THIRD_PARTY_LICENSES +46 -0
- qtm_rt_pyside6-0.3.4/examples/README.md +71 -0
- qtm_rt_pyside6-0.3.4/examples/basic_example.py +71 -0
- qtm_rt_pyside6-0.3.4/examples/discovery_example.py +43 -0
- qtm_rt_pyside6-0.3.4/examples/gui_3d_example/main.py +657 -0
- qtm_rt_pyside6-0.3.4/examples/gui_3d_example/main.qml +570 -0
- qtm_rt_pyside6-0.3.4/examples/gui_example.py +310 -0
- qtm_rt_pyside6-0.3.4/examples/parameters_example.py +94 -0
- qtm_rt_pyside6-0.3.4/pyproject.toml +54 -0
- qtm_rt_pyside6-0.3.4/qtm_rt_pyside6/__init__.py +23 -0
- qtm_rt_pyside6-0.3.4/qtm_rt_pyside6/connection.py +750 -0
- qtm_rt_pyside6-0.3.4/qtm_rt_pyside6/discovery.py +181 -0
- qtm_rt_pyside6-0.3.4/qtm_rt_pyside6.egg-info/PKG-INFO +155 -0
- qtm_rt_pyside6-0.3.4/qtm_rt_pyside6.egg-info/SOURCES.txt +27 -0
- qtm_rt_pyside6-0.3.4/qtm_rt_pyside6.egg-info/dependency_links.txt +1 -0
- qtm_rt_pyside6-0.3.4/qtm_rt_pyside6.egg-info/requires.txt +2 -0
- qtm_rt_pyside6-0.3.4/qtm_rt_pyside6.egg-info/top_level.txt +1 -0
- qtm_rt_pyside6-0.3.4/requirements-dev.txt +5 -0
- qtm_rt_pyside6-0.3.4/setup.cfg +4 -0
- qtm_rt_pyside6-0.3.4/tests/__init__.py +0 -0
- qtm_rt_pyside6-0.3.4/tests/test_connection.py +679 -0
- qtm_rt_pyside6-0.3.4/tests/test_discovery.py +156 -0
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
pull_request:
|
|
7
|
+
branches: [main]
|
|
8
|
+
|
|
9
|
+
jobs:
|
|
10
|
+
lint:
|
|
11
|
+
runs-on: ubuntu-latest
|
|
12
|
+
steps:
|
|
13
|
+
- uses: actions/checkout@v6
|
|
14
|
+
- uses: actions/setup-python@v6
|
|
15
|
+
with:
|
|
16
|
+
python-version: "3.x"
|
|
17
|
+
- run: pip install ruff
|
|
18
|
+
- run: ruff check .
|
|
19
|
+
- run: ruff format --check .
|
|
20
|
+
|
|
21
|
+
test:
|
|
22
|
+
runs-on: ubuntu-latest
|
|
23
|
+
strategy:
|
|
24
|
+
matrix:
|
|
25
|
+
python-version: ["3.10", "3.11", "3.12", "3.13", "3.x"]
|
|
26
|
+
env:
|
|
27
|
+
QT_QPA_PLATFORM: offscreen
|
|
28
|
+
steps:
|
|
29
|
+
- uses: actions/checkout@v6
|
|
30
|
+
- uses: actions/setup-python@v6
|
|
31
|
+
with:
|
|
32
|
+
python-version: ${{ matrix.python-version }}
|
|
33
|
+
- run: sudo apt-get update && sudo apt-get install -y libegl1
|
|
34
|
+
- run: |
|
|
35
|
+
pip install -r requirements-dev.txt
|
|
36
|
+
pip install -e .
|
|
37
|
+
- run: pytest -v
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
name: Publish
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
release:
|
|
5
|
+
types: [published]
|
|
6
|
+
|
|
7
|
+
permissions:
|
|
8
|
+
contents: write
|
|
9
|
+
id-token: write
|
|
10
|
+
|
|
11
|
+
jobs:
|
|
12
|
+
build:
|
|
13
|
+
runs-on: ubuntu-latest
|
|
14
|
+
steps:
|
|
15
|
+
- uses: actions/checkout@v6
|
|
16
|
+
with:
|
|
17
|
+
fetch-depth: 0
|
|
18
|
+
- uses: actions/setup-python@v6
|
|
19
|
+
with:
|
|
20
|
+
python-version: "3.x"
|
|
21
|
+
- run: pip install build
|
|
22
|
+
- run: python -m build
|
|
23
|
+
- uses: actions/upload-artifact@v7
|
|
24
|
+
with:
|
|
25
|
+
name: dist
|
|
26
|
+
path: dist/
|
|
27
|
+
|
|
28
|
+
publish-pypi:
|
|
29
|
+
needs: build
|
|
30
|
+
runs-on: ubuntu-latest
|
|
31
|
+
environment: pypi
|
|
32
|
+
permissions:
|
|
33
|
+
id-token: write
|
|
34
|
+
steps:
|
|
35
|
+
- uses: actions/download-artifact@v8
|
|
36
|
+
with:
|
|
37
|
+
name: dist
|
|
38
|
+
path: dist/
|
|
39
|
+
- uses: pypa/gh-action-pypi-publish@release/v1
|
|
40
|
+
|
|
41
|
+
upload-release-assets:
|
|
42
|
+
needs: build
|
|
43
|
+
runs-on: ubuntu-latest
|
|
44
|
+
steps:
|
|
45
|
+
- uses: actions/download-artifact@v8
|
|
46
|
+
with:
|
|
47
|
+
name: dist
|
|
48
|
+
path: dist/
|
|
49
|
+
- run: gh release upload "${{ github.event.release.tag_name }}" dist/*
|
|
50
|
+
env:
|
|
51
|
+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
52
|
+
GH_REPO: ${{ github.repository }}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# Byte-compiled / optimized / DLL files
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*$py.class
|
|
5
|
+
|
|
6
|
+
# Distribution / packaging
|
|
7
|
+
build/
|
|
8
|
+
dist/
|
|
9
|
+
*.egg-info/
|
|
10
|
+
*.egg
|
|
11
|
+
|
|
12
|
+
# Virtual environments
|
|
13
|
+
.venv/
|
|
14
|
+
venv/
|
|
15
|
+
ENV/
|
|
16
|
+
|
|
17
|
+
# IDE / AI tools
|
|
18
|
+
.idea/
|
|
19
|
+
.vscode/
|
|
20
|
+
.claude/
|
|
21
|
+
*.swp
|
|
22
|
+
*.swo
|
|
23
|
+
|
|
24
|
+
# Testing
|
|
25
|
+
.pytest_cache/
|
|
26
|
+
.coverage
|
|
27
|
+
htmlcov/
|
|
28
|
+
|
|
29
|
+
# OS
|
|
30
|
+
.DS_Store
|
|
31
|
+
Thumbs.db
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
# CLAUDE.md
|
|
2
|
+
|
|
3
|
+
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
|
4
|
+
|
|
5
|
+
## Project Overview
|
|
6
|
+
|
|
7
|
+
PySide6-based SDK for QTM Real-Time Protocol. Uses Qt's network stack (`QTcpSocket`, `QUdpSocket`) and signals/slots pattern instead of asyncio. Reuses packet parsing code from the original `qtm-rt` SDK, pulled in as a pip dependency (`qtm-rt>=3.0.0`).
|
|
8
|
+
|
|
9
|
+
## Development Commands
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
# Setup environment (use Python 3.10+)
|
|
13
|
+
py -3.10 -m venv .venv
|
|
14
|
+
.venv/Scripts/activate # Windows
|
|
15
|
+
pip install -r requirements-dev.txt
|
|
16
|
+
pip install -e .
|
|
17
|
+
|
|
18
|
+
# Run all tests
|
|
19
|
+
pytest -v
|
|
20
|
+
|
|
21
|
+
# Run a single test file or test
|
|
22
|
+
pytest tests/test_connection.py -v
|
|
23
|
+
pytest tests/test_connection.py::TestQTMConnectionInit::test_creates_socket -v
|
|
24
|
+
|
|
25
|
+
# Run examples (requires QTM running)
|
|
26
|
+
python examples/basic_example.py
|
|
27
|
+
python examples/discovery_example.py
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
# Lint and format
|
|
32
|
+
ruff check .
|
|
33
|
+
ruff format --check .
|
|
34
|
+
ruff format . # auto-fix formatting
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
Tests use `pytest-qt` (configured in `pyproject.toml` with `qt_api = pyside6`). Use `qtbot.waitSignal()` for signal assertions.
|
|
38
|
+
|
|
39
|
+
Version is derived from git tags via `setuptools-scm`. Tag a release with `v0.1.0` format.
|
|
40
|
+
|
|
41
|
+
## Architecture
|
|
42
|
+
|
|
43
|
+
### Module Structure
|
|
44
|
+
|
|
45
|
+
- **`qtm_rt`** - Original SDK installed from PyPI (`qtm-rt>=3.0.0`). We import `packet` and `receiver` for binary parsing
|
|
46
|
+
- **`qtm_rt_pyside6/connection.py`** - `QTMConnection(QObject)` main class using `QTcpSocket`
|
|
47
|
+
- **`qtm_rt_pyside6/discovery.py`** - `QTMDiscovery(QObject)` for UDP broadcast discovery
|
|
48
|
+
|
|
49
|
+
### Signal Pattern
|
|
50
|
+
|
|
51
|
+
The SDK uses Qt signals instead of async/await:
|
|
52
|
+
|
|
53
|
+
```python
|
|
54
|
+
# Connection state
|
|
55
|
+
connected = Signal()
|
|
56
|
+
disconnected = Signal()
|
|
57
|
+
error = Signal(str)
|
|
58
|
+
|
|
59
|
+
# Streaming (high-frequency)
|
|
60
|
+
packetReceived = Signal(object) # QRTPacket
|
|
61
|
+
eventReceived = Signal(object) # QRTEvent
|
|
62
|
+
|
|
63
|
+
# Request/response (generic)
|
|
64
|
+
responseReceived = Signal(str, object) # (command_name, data)
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
### Response Handling
|
|
68
|
+
|
|
69
|
+
Commands that expect responses emit via `responseReceived(command, data)`. Use the helper for cleaner code:
|
|
70
|
+
|
|
71
|
+
```python
|
|
72
|
+
connection.onResponse("getparameters", self.handle_parameters)
|
|
73
|
+
connection.getParameters(["3d"])
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
### Reused Components from `qtm-rt`
|
|
77
|
+
|
|
78
|
+
- `qtm_rt.packet` - `QRTPacket`, `QRTEvent`, all struct definitions and namedtuples
|
|
79
|
+
- `qtm_rt.receiver` - `Receiver` class for packet reassembly (called from `_onReadyRead`)
|
|
80
|
+
|
|
81
|
+
### Data Flow
|
|
82
|
+
|
|
83
|
+
1. `QTMConnection.connectToHost(host)` -> `QTcpSocket.connectToHost()`
|
|
84
|
+
2. Socket `connected` signal -> send version command -> emit `connected` signal
|
|
85
|
+
3. `streamFrames(components)` -> sends command to QTM
|
|
86
|
+
4. Socket `readyRead` -> `Receiver.data_received()` -> packet handlers -> emit `packetReceived`
|
|
87
|
+
|
|
88
|
+
### Protocol Defaults
|
|
89
|
+
|
|
90
|
+
- TCP port: 22223 (little-endian)
|
|
91
|
+
- UDP discovery port: 22226
|
|
92
|
+
- Protocol version: "1.25"
|
|
93
|
+
- Logger name: `"qtm_rt_pyside6"`
|
|
94
|
+
|
|
95
|
+
### Valid Identifiers
|
|
96
|
+
|
|
97
|
+
**Streaming components:** `2d`, `2dlin`, `3d`, `3dres`, `3dnolabels`, `3dnolabelsres`, `analog`, `analogsingle`, `force`, `forcesingle`, `6d`, `6dres`, `6deuler`, `6deulerres`, `gazevector`, `eyetracker`, `image`, `timecode`, `skeleton`, `skeleton:global`
|
|
98
|
+
|
|
99
|
+
**Parameter names for `getParameters()`:** `all`, `general`, `3d`, `6d`, `analog`, `force`, `gazevector`, `eyetracker`, `image`, `skeleton`, `skeleton:global`, `calibration`
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Qualisys AB
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: qtm-rt-pyside6
|
|
3
|
+
Version: 0.3.4
|
|
4
|
+
Summary: PySide6 SDK for QTM Real-Time Protocol
|
|
5
|
+
Author-email: Qualisys AB <support@qualisys.com>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/qualisys/qualisys_pyside6_sdk
|
|
8
|
+
Classifier: Operating System :: OS Independent
|
|
9
|
+
Classifier: Intended Audience :: Developers
|
|
10
|
+
Classifier: Programming Language :: Python :: 3
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
15
|
+
Classifier: Topic :: Scientific/Engineering
|
|
16
|
+
Classifier: Topic :: Utilities
|
|
17
|
+
Requires-Python: >=3.10
|
|
18
|
+
Description-Content-Type: text/markdown
|
|
19
|
+
License-File: LICENSE
|
|
20
|
+
License-File: THIRD_PARTY_LICENSES
|
|
21
|
+
Requires-Dist: PySide6>=6.0.0
|
|
22
|
+
Requires-Dist: qtm-rt>=3.0.0
|
|
23
|
+
Dynamic: license-file
|
|
24
|
+
|
|
25
|
+
# Qualisys PySide6 SDK
|
|
26
|
+
|
|
27
|
+
[](https://pypi.org/project/qtm-rt-pyside6/)
|
|
28
|
+
|
|
29
|
+
PySide6-based SDK for the Qualisys Real-Time (RT) protocol. Stream motion capture data from QTM using Qt's native networking and signals/slots.
|
|
30
|
+
|
|
31
|
+
## Installation
|
|
32
|
+
|
|
33
|
+
Requires Python 3.10+ and PySide6 6.0+.
|
|
34
|
+
|
|
35
|
+
We recommend using a virtual environment to avoid conflicts with other packages:
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
# Create and activate a virtual environment
|
|
39
|
+
python -m venv .venv
|
|
40
|
+
|
|
41
|
+
# Windows
|
|
42
|
+
.venv\Scripts\activate
|
|
43
|
+
|
|
44
|
+
# macOS / Linux
|
|
45
|
+
source .venv/bin/activate
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
Then install the package:
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
pip install qtm-rt-pyside6
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## Quick Start
|
|
55
|
+
|
|
56
|
+
```python
|
|
57
|
+
import sys
|
|
58
|
+
from PySide6.QtCore import QCoreApplication
|
|
59
|
+
from qtm_rt_pyside6 import QTMConnection, QRTPacket
|
|
60
|
+
|
|
61
|
+
def on_packet(packet: QRTPacket):
|
|
62
|
+
print(f"Frame {packet.framenumber}")
|
|
63
|
+
result = packet.get_3d_markers()
|
|
64
|
+
if result:
|
|
65
|
+
header, markers = result
|
|
66
|
+
for marker in markers:
|
|
67
|
+
print(f" {marker.x}, {marker.y}, {marker.z}")
|
|
68
|
+
|
|
69
|
+
app = QCoreApplication(sys.argv)
|
|
70
|
+
|
|
71
|
+
connection = QTMConnection()
|
|
72
|
+
connection.connected.connect(lambda: connection.streamFrames(["3d"]))
|
|
73
|
+
connection.packetReceived.connect(on_packet)
|
|
74
|
+
connection.error.connect(lambda msg: print(f"Error: {msg}"))
|
|
75
|
+
connection.connectToHost("127.0.0.1")
|
|
76
|
+
|
|
77
|
+
sys.exit(app.exec())
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
## API
|
|
81
|
+
|
|
82
|
+
### QTMConnection Signals
|
|
83
|
+
|
|
84
|
+
| Signal | Description |
|
|
85
|
+
|--------|-------------|
|
|
86
|
+
| `connected()` | Successfully connected and version negotiated |
|
|
87
|
+
| `disconnected()` | Disconnected from QTM |
|
|
88
|
+
| `error(str)` | Connection or protocol error |
|
|
89
|
+
| `packetReceived(QRTPacket)` | Data packet during streaming |
|
|
90
|
+
| `eventReceived(QRTEvent)` | QTM event (capture started, etc.) |
|
|
91
|
+
| `responseReceived(str, object)` | Command response (command_name, data) |
|
|
92
|
+
|
|
93
|
+
### QTMConnection Methods
|
|
94
|
+
|
|
95
|
+
Core methods to get started:
|
|
96
|
+
|
|
97
|
+
- `connectToHost(host, port=22223, version="1.25", timeout_ms=5000)` — Connect to QTM
|
|
98
|
+
- `disconnectFromHost()` — Close the connection
|
|
99
|
+
- `streamFrames(components, frames="allframes", udp_port=None, udp_address=None)` — Start streaming; pass `udp_port` to receive frames over UDP instead of TCP
|
|
100
|
+
- `streamFramesStop()` — Stop streaming
|
|
101
|
+
- `getParameters(parameters)` — Request current configuration as XML
|
|
102
|
+
|
|
103
|
+
See the class docstring or run `help(QTMConnection)` for the full method
|
|
104
|
+
list — control, load/save, trigger, LED, quit, XML settings, etc.
|
|
105
|
+
|
|
106
|
+
### Components
|
|
107
|
+
|
|
108
|
+
Valid streaming components: `2d`, `2dlin`, `3d`, `3dres`, `3dnolabels`, `3dnolabelsres`, `analog`, `analogsingle`, `force`, `forcesingle`, `6d`, `6dres`, `6deuler`, `6deulerres`, `gazevector`, `eyetracker`, `image`, `timecode`, `skeleton`, `skeleton:global`
|
|
109
|
+
|
|
110
|
+
## Examples
|
|
111
|
+
|
|
112
|
+
Ready-to-run scripts live in [examples/](examples/) — see
|
|
113
|
+
[examples/README.md](examples/README.md) for what each one demonstrates
|
|
114
|
+
and how to run it. `basic_example.py` is the shortest path to verify
|
|
115
|
+
your setup; the GUI examples (`gui_example.py` and `gui_3d_example/`)
|
|
116
|
+
show how to wire the SDK into a desktop application.
|
|
117
|
+
|
|
118
|
+
## Known Limitations
|
|
119
|
+
|
|
120
|
+
- **No automatic reconnection.** If the connection drops, you must call `connectToHost()` again. Listen to the `disconnected` signal to detect this.
|
|
121
|
+
- **Single-threaded.** `QTMConnection` and `QTMDiscovery` are `QObject` subclasses and must be used from the thread that created them (typically the main thread). Use Qt signals for cross-thread communication.
|
|
122
|
+
- **Password sent in plain text.** `takeControl(password)` transmits the password unencrypted over TCP. This is a QTM protocol limitation — use on trusted networks only.
|
|
123
|
+
|
|
124
|
+
## Development
|
|
125
|
+
|
|
126
|
+
To work on the SDK itself:
|
|
127
|
+
|
|
128
|
+
```bash
|
|
129
|
+
git clone https://github.com/qualisys/qualisys_pyside6_sdk.git
|
|
130
|
+
cd qualisys_pyside6_sdk
|
|
131
|
+
|
|
132
|
+
# Create and activate a virtual environment
|
|
133
|
+
python -m venv .venv
|
|
134
|
+
|
|
135
|
+
# Windows
|
|
136
|
+
.venv\Scripts\activate
|
|
137
|
+
|
|
138
|
+
# macOS / Linux
|
|
139
|
+
source .venv/bin/activate
|
|
140
|
+
|
|
141
|
+
# Install dev dependencies (pytest, pytest-qt, ruff, build) and the package
|
|
142
|
+
pip install -r requirements-dev.txt
|
|
143
|
+
pip install -e .
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
Run tests and linting:
|
|
147
|
+
```bash
|
|
148
|
+
pytest -v
|
|
149
|
+
ruff check .
|
|
150
|
+
ruff format --check .
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
## License
|
|
154
|
+
|
|
155
|
+
MIT — see [LICENSE](LICENSE) and [THIRD_PARTY_LICENSES](THIRD_PARTY_LICENSES).
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
# Qualisys PySide6 SDK
|
|
2
|
+
|
|
3
|
+
[](https://pypi.org/project/qtm-rt-pyside6/)
|
|
4
|
+
|
|
5
|
+
PySide6-based SDK for the Qualisys Real-Time (RT) protocol. Stream motion capture data from QTM using Qt's native networking and signals/slots.
|
|
6
|
+
|
|
7
|
+
## Installation
|
|
8
|
+
|
|
9
|
+
Requires Python 3.10+ and PySide6 6.0+.
|
|
10
|
+
|
|
11
|
+
We recommend using a virtual environment to avoid conflicts with other packages:
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
# Create and activate a virtual environment
|
|
15
|
+
python -m venv .venv
|
|
16
|
+
|
|
17
|
+
# Windows
|
|
18
|
+
.venv\Scripts\activate
|
|
19
|
+
|
|
20
|
+
# macOS / Linux
|
|
21
|
+
source .venv/bin/activate
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
Then install the package:
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
pip install qtm-rt-pyside6
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Quick Start
|
|
31
|
+
|
|
32
|
+
```python
|
|
33
|
+
import sys
|
|
34
|
+
from PySide6.QtCore import QCoreApplication
|
|
35
|
+
from qtm_rt_pyside6 import QTMConnection, QRTPacket
|
|
36
|
+
|
|
37
|
+
def on_packet(packet: QRTPacket):
|
|
38
|
+
print(f"Frame {packet.framenumber}")
|
|
39
|
+
result = packet.get_3d_markers()
|
|
40
|
+
if result:
|
|
41
|
+
header, markers = result
|
|
42
|
+
for marker in markers:
|
|
43
|
+
print(f" {marker.x}, {marker.y}, {marker.z}")
|
|
44
|
+
|
|
45
|
+
app = QCoreApplication(sys.argv)
|
|
46
|
+
|
|
47
|
+
connection = QTMConnection()
|
|
48
|
+
connection.connected.connect(lambda: connection.streamFrames(["3d"]))
|
|
49
|
+
connection.packetReceived.connect(on_packet)
|
|
50
|
+
connection.error.connect(lambda msg: print(f"Error: {msg}"))
|
|
51
|
+
connection.connectToHost("127.0.0.1")
|
|
52
|
+
|
|
53
|
+
sys.exit(app.exec())
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
## API
|
|
57
|
+
|
|
58
|
+
### QTMConnection Signals
|
|
59
|
+
|
|
60
|
+
| Signal | Description |
|
|
61
|
+
|--------|-------------|
|
|
62
|
+
| `connected()` | Successfully connected and version negotiated |
|
|
63
|
+
| `disconnected()` | Disconnected from QTM |
|
|
64
|
+
| `error(str)` | Connection or protocol error |
|
|
65
|
+
| `packetReceived(QRTPacket)` | Data packet during streaming |
|
|
66
|
+
| `eventReceived(QRTEvent)` | QTM event (capture started, etc.) |
|
|
67
|
+
| `responseReceived(str, object)` | Command response (command_name, data) |
|
|
68
|
+
|
|
69
|
+
### QTMConnection Methods
|
|
70
|
+
|
|
71
|
+
Core methods to get started:
|
|
72
|
+
|
|
73
|
+
- `connectToHost(host, port=22223, version="1.25", timeout_ms=5000)` — Connect to QTM
|
|
74
|
+
- `disconnectFromHost()` — Close the connection
|
|
75
|
+
- `streamFrames(components, frames="allframes", udp_port=None, udp_address=None)` — Start streaming; pass `udp_port` to receive frames over UDP instead of TCP
|
|
76
|
+
- `streamFramesStop()` — Stop streaming
|
|
77
|
+
- `getParameters(parameters)` — Request current configuration as XML
|
|
78
|
+
|
|
79
|
+
See the class docstring or run `help(QTMConnection)` for the full method
|
|
80
|
+
list — control, load/save, trigger, LED, quit, XML settings, etc.
|
|
81
|
+
|
|
82
|
+
### Components
|
|
83
|
+
|
|
84
|
+
Valid streaming components: `2d`, `2dlin`, `3d`, `3dres`, `3dnolabels`, `3dnolabelsres`, `analog`, `analogsingle`, `force`, `forcesingle`, `6d`, `6dres`, `6deuler`, `6deulerres`, `gazevector`, `eyetracker`, `image`, `timecode`, `skeleton`, `skeleton:global`
|
|
85
|
+
|
|
86
|
+
## Examples
|
|
87
|
+
|
|
88
|
+
Ready-to-run scripts live in [examples/](examples/) — see
|
|
89
|
+
[examples/README.md](examples/README.md) for what each one demonstrates
|
|
90
|
+
and how to run it. `basic_example.py` is the shortest path to verify
|
|
91
|
+
your setup; the GUI examples (`gui_example.py` and `gui_3d_example/`)
|
|
92
|
+
show how to wire the SDK into a desktop application.
|
|
93
|
+
|
|
94
|
+
## Known Limitations
|
|
95
|
+
|
|
96
|
+
- **No automatic reconnection.** If the connection drops, you must call `connectToHost()` again. Listen to the `disconnected` signal to detect this.
|
|
97
|
+
- **Single-threaded.** `QTMConnection` and `QTMDiscovery` are `QObject` subclasses and must be used from the thread that created them (typically the main thread). Use Qt signals for cross-thread communication.
|
|
98
|
+
- **Password sent in plain text.** `takeControl(password)` transmits the password unencrypted over TCP. This is a QTM protocol limitation — use on trusted networks only.
|
|
99
|
+
|
|
100
|
+
## Development
|
|
101
|
+
|
|
102
|
+
To work on the SDK itself:
|
|
103
|
+
|
|
104
|
+
```bash
|
|
105
|
+
git clone https://github.com/qualisys/qualisys_pyside6_sdk.git
|
|
106
|
+
cd qualisys_pyside6_sdk
|
|
107
|
+
|
|
108
|
+
# Create and activate a virtual environment
|
|
109
|
+
python -m venv .venv
|
|
110
|
+
|
|
111
|
+
# Windows
|
|
112
|
+
.venv\Scripts\activate
|
|
113
|
+
|
|
114
|
+
# macOS / Linux
|
|
115
|
+
source .venv/bin/activate
|
|
116
|
+
|
|
117
|
+
# Install dev dependencies (pytest, pytest-qt, ruff, build) and the package
|
|
118
|
+
pip install -r requirements-dev.txt
|
|
119
|
+
pip install -e .
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
Run tests and linting:
|
|
123
|
+
```bash
|
|
124
|
+
pytest -v
|
|
125
|
+
ruff check .
|
|
126
|
+
ruff format --check .
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
## License
|
|
130
|
+
|
|
131
|
+
MIT — see [LICENSE](LICENSE) and [THIRD_PARTY_LICENSES](THIRD_PARTY_LICENSES).
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
This project uses the following third-party libraries:
|
|
2
|
+
|
|
3
|
+
================================================================================
|
|
4
|
+
PySide6 — The Qt Company
|
|
5
|
+
================================================================================
|
|
6
|
+
|
|
7
|
+
License: GNU Lesser General Public License v3.0 (LGPL-3.0)
|
|
8
|
+
Homepage: https://www.qt.io/qt-for-python
|
|
9
|
+
Source: https://code.qt.io/cgit/pyside/pyside-setup.git
|
|
10
|
+
|
|
11
|
+
PySide6 provides Python bindings for the Qt framework. It is used as a runtime
|
|
12
|
+
dependency and is dynamically linked. Users may replace PySide6 with any
|
|
13
|
+
compatible version under the terms of the LGPL v3.
|
|
14
|
+
|
|
15
|
+
The full text of the LGPL v3 license is available at:
|
|
16
|
+
https://www.gnu.org/licenses/lgpl-3.0.html
|
|
17
|
+
|
|
18
|
+
================================================================================
|
|
19
|
+
Qualisys Python SDK (qtm_rt) — Qualisys AB
|
|
20
|
+
================================================================================
|
|
21
|
+
|
|
22
|
+
License: MIT
|
|
23
|
+
Homepage: https://github.com/qualisys/qualisys_python_sdk
|
|
24
|
+
Included as: Git submodule (qtm_rt/)
|
|
25
|
+
|
|
26
|
+
The MIT License (MIT)
|
|
27
|
+
|
|
28
|
+
Copyright (c) 2015-2016 Qualisys
|
|
29
|
+
|
|
30
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
31
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
32
|
+
in the Software without restriction, including without limitation the rights
|
|
33
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
34
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
35
|
+
furnished to do so, subject to the following conditions:
|
|
36
|
+
|
|
37
|
+
The above copyright notice and this permission notice shall be included in all
|
|
38
|
+
copies or substantial portions of the Software.
|
|
39
|
+
|
|
40
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
41
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
42
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
43
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
44
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
45
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
46
|
+
SOFTWARE.
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# Examples
|
|
2
|
+
|
|
3
|
+
Ready-to-run scripts demonstrating `qtm-rt-pyside6`. Each example is
|
|
4
|
+
self-contained — install the package, start QTM, and run.
|
|
5
|
+
|
|
6
|
+
## Prerequisites
|
|
7
|
+
|
|
8
|
+
- A QTM installation reachable on the network (or `127.0.0.1` if local).
|
|
9
|
+
- `qtm-rt-pyside6` installed (`pip install qtm-rt-pyside6`, or
|
|
10
|
+
`pip install -e .` from a checkout).
|
|
11
|
+
- For the streaming examples, QTM should be playing back data — load a
|
|
12
|
+
file in QTM and choose **Play → Play with Real-Time output**, or run
|
|
13
|
+
a live capture.
|
|
14
|
+
- The 3D and Widgets GUI examples need a display; the rest run in a
|
|
15
|
+
plain console.
|
|
16
|
+
|
|
17
|
+
## Quick reference
|
|
18
|
+
|
|
19
|
+
| Script | What it shows | Run |
|
|
20
|
+
|---|---|---|
|
|
21
|
+
| [`basic_example.py`](basic_example.py) | Stream 3D markers to the console, exit cleanly after 100 frames | `python basic_example.py` |
|
|
22
|
+
| [`discovery_example.py`](discovery_example.py) | UDP-broadcast discovery of QTM instances on the network | `python discovery_example.py [interface_ip]` |
|
|
23
|
+
| [`parameters_example.py`](parameters_example.py) | Request QTM configuration as XML and parse 3D markers, 6DOF bodies, and skeletons out of it | `python parameters_example.py` |
|
|
24
|
+
| [`gui_example.py`](gui_example.py) | Qt Widgets app combining discovery, connect/disconnect, and a live data table with frame rate | `python gui_example.py` |
|
|
25
|
+
| [`gui_3d_example/main.py`](gui_3d_example/main.py) | Qt Quick 3D viewer showing markers as spheres and skeletons as bones, with an orbit camera | `python gui_3d_example/main.py` |
|
|
26
|
+
|
|
27
|
+
If you're new to the SDK, start with `basic_example.py` to confirm the
|
|
28
|
+
connection works, then look at `discovery_example.py` or one of the GUI
|
|
29
|
+
examples depending on what you're building.
|
|
30
|
+
|
|
31
|
+
## Notes on individual examples
|
|
32
|
+
|
|
33
|
+
### `basic_example.py`
|
|
34
|
+
Minimal connect-and-stream loop. Exits automatically after 100 frames
|
|
35
|
+
(`FRAME_LIMIT`) so it's safe to run unattended.
|
|
36
|
+
|
|
37
|
+
### `discovery_example.py`
|
|
38
|
+
Sends a UDP broadcast on port 22226 and prints every QTM instance that
|
|
39
|
+
replies within the 2-second timeout.
|
|
40
|
+
|
|
41
|
+
On machines with multiple network interfaces, the broadcast may not
|
|
42
|
+
reach the QTM subnet when bound to "any" interface. Pass the local
|
|
43
|
+
interface IP as the first argument to bind explicitly:
|
|
44
|
+
|
|
45
|
+
```
|
|
46
|
+
python discovery_example.py 192.168.10.100
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
### `parameters_example.py`
|
|
50
|
+
Connects, requests `3d`, `6d`, and `skeleton` parameters, parses the
|
|
51
|
+
returned XML, and prints marker / body / skeleton names. Useful as a
|
|
52
|
+
template for any "fetch and inspect QTM configuration" task.
|
|
53
|
+
|
|
54
|
+
### `gui_example.py`
|
|
55
|
+
PySide6 / QtWidgets app with discovery, connection management, and a
|
|
56
|
+
live data table. A good starting point for desktop tools built on top
|
|
57
|
+
of the SDK.
|
|
58
|
+
|
|
59
|
+
### `gui_3d_example/`
|
|
60
|
+
PySide6 / Qt Quick 3D scene that streams 3D markers, 6DOF bodies, and
|
|
61
|
+
skeletons from QTM and renders them as primitives with an orbit
|
|
62
|
+
camera. The Python `main.py` provides the data backend; `main.qml`
|
|
63
|
+
defines the scene.
|
|
64
|
+
|
|
65
|
+
## Known limitation: TCP streaming only in these examples
|
|
66
|
+
|
|
67
|
+
All streaming examples use TCP (the default). The SDK also supports
|
|
68
|
+
UDP streaming via `streamFrames(..., udp_port=...)` — see the
|
|
69
|
+
`QTMConnection` docstring or `help(QTMConnection)` for details. None
|
|
70
|
+
of the bundled examples currently demonstrate UDP; contributions
|
|
71
|
+
welcome.
|