Architecture
mHumanTrackAI is built as two processes, designed to be independently testable:
- Python engine (
engine/) — owns cameras, ML inference, occlusion prediction, multi-camera fusion, gesture evaluation, and recording. Knows nothing about Win32/WPF. - C# / WPF app (
app/) — UI, visualization, OS-level input drivers, and process lifecycle. Knows nothing about ML.
C# / WPF App
mHumanTrackAI.App.exe
- EngineHost — spawns the Python engine in a Win32 Job Object
- EngineService — NetMQ client; marshals packets to the UI
- InputRouter → SendInput (accessibility cursor + clicks)
- GamingRouter → ViGEm Xbox 360 (gaming controller)
- HotkeyService → Ctrl+Alt+P global panic
- ForegroundWatcher → per-app profile / mode auto-switch
data
PUB → SUB
:5555 · landmarks JSON
control
REQ ↔ REP
:5556 · command JSON
preview
PUB → SUB
:5557 · meta JSON + JPEG
Python Engine
python -m engine.main
- camera.capture — OpenCV MultiCamera (threaded grab)
- tracking — RTMSource (RTMW) / MediaPipeSource / DummySource
- fusion.fuse — per-joint best-view selector + src tag
- predictor.process — One-Euro + Kalman + IK occlusion predict
- gestures — GestureEngine / GamingEngine → input / controller
- server.publish_landmarks() + publish_preview()
Two-process design
The C# app owns the lifecycle of the Python engine. EngineHost spawns the engine as a hidden child process inside a Win32 Job Object (KILL_ON_JOB_CLOSE), which guarantees the Python child dies with the app even on a hard crash. If an engine is already listening on the control port (for example, a developer started one manually), EngineHost attaches to it instead of spawning a new one.
The engine runs a main publish loop on the main thread and a control loop on a daemon thread, so commands never block the tracking pipeline.
IPC channels
The two processes communicate over ZeroMQ (NetMQ on C#, pyzmq on Python) across TCP loopback. The wire format is newline-less JSON, schema version 1, defined in shared/schema/ipc.schema.json.
| Channel | Pattern | Port | Payload |
|---|---|---|---|
data | PUB → SUB | 5555 | landmarks JSON |
control | REQ ↔ REP | 5556 | command JSON |
preview | PUB → SUB | 5557 | meta JSON + JPEG |
The control channel handles commands such as ping, getConfig, setConfig, setMode, setCamera, captureTPose, startRecording, exportRecording, playRecording, listCameras, and shutdown.
Key design decisions
- The engine binds all three sockets; the app connects. This makes the engine the single source of truth for port state.
- Preview is rate-decoupled (small high-water mark; stale frames dropped) so JPEG encoding never adds latency to the landmark stream.
- Job Object lifecycle ensures the Python child cannot outlive the app.
- Config hot-reload —
setConfig/setCamera/loadProfileset a dirty flag; the next loop iteration reopens the camera/source with new settings.
DTOs are currently hand-written on both sides and kept in sync manually against the schema. Schema codegen is noted as a reasonable future upgrade.
