openaxis 1.0.0rc1__py3-none-any.whl
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.
- openaxis/__init__.py +19 -0
- openaxis/_navigation_performance.py +116 -0
- openaxis/_navigation_session.py +709 -0
- openaxis/_session.py +286 -0
- openaxis/_verify.py +194 -0
- openaxis/_version.py +1 -0
- openaxis/async_navigation_session.py +462 -0
- openaxis/client.py +655 -0
- openaxis/connection_manager.py +176 -0
- openaxis/diagnostics.py +384 -0
- openaxis/geometry.py +291 -0
- openaxis/logging.py +184 -0
- openaxis/navigation.py +218 -0
- openaxis/navigation_diagnostics.py +386 -0
- openaxis/navigation_session.py +30 -0
- openaxis/process_identity.py +40 -0
- openaxis/py.typed +0 -0
- openaxis/types.py +923 -0
- openaxis-1.0.0rc1.dist-info/METADATA +41 -0
- openaxis-1.0.0rc1.dist-info/RECORD +23 -0
- openaxis-1.0.0rc1.dist-info/WHEEL +4 -0
- openaxis-1.0.0rc1.dist-info/licenses/LEGAL.md +71 -0
- openaxis-1.0.0rc1.dist-info/licenses/LICENSE +795 -0
openaxis/__init__.py
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"""OpenAxis 1.0 Python client SDK.
|
|
2
|
+
|
|
3
|
+
Import from the submodule that matches the tier you need — each one declares
|
|
4
|
+
its public surface in ``__all__``:
|
|
5
|
+
|
|
6
|
+
- ``openaxis.types`` wire-format messages and value objects (no third-party deps)
|
|
7
|
+
- ``openaxis.process_identity`` native process identity helpers for foreground routing
|
|
8
|
+
- ``openaxis.geometry`` pure-Python Vec3/Quat and pose conversion helpers
|
|
9
|
+
- ``openaxis.diagnostics`` dependency-free diagnostic event formatting
|
|
10
|
+
- ``openaxis.logging`` optional rotating session files and console/UI mirrors
|
|
11
|
+
- ``openaxis.navigation`` synchronous Navigation query evaluation helpers
|
|
12
|
+
- ``openaxis.navigation_session`` synchronous host camera/object coordination
|
|
13
|
+
- ``openaxis.async_navigation_session`` asynchronous host camera coordination
|
|
14
|
+
- ``openaxis.client`` the async WebSocket client (requires websockets + msgpack)
|
|
15
|
+
- ``openaxis.connection_manager`` opt-in reconnect, metadata replay, and shutdown
|
|
16
|
+
|
|
17
|
+
Nothing is re-exported at the package root, so importing the geometry or wire
|
|
18
|
+
types never pulls in the websockets/msgpack transport stack.
|
|
19
|
+
"""
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
"""Fixed-size navigation counters. Owners serialize access; only flush logs."""
|
|
2
|
+
import logging
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class Timing:
|
|
6
|
+
def __init__(self):
|
|
7
|
+
self.count = self.total = self.maximum = 0
|
|
8
|
+
|
|
9
|
+
def add(self, seconds):
|
|
10
|
+
ms = max(0, seconds * 1000)
|
|
11
|
+
self.count += 1
|
|
12
|
+
self.total += ms
|
|
13
|
+
self.maximum = max(self.maximum, ms)
|
|
14
|
+
|
|
15
|
+
def text(self):
|
|
16
|
+
return f'{self.total / self.count if self.count else 0:.1f}/{self.maximum:.1f} [{self.count}]'
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class PerformanceStream:
|
|
21
|
+
def __init__(self):
|
|
22
|
+
self.received = self.coalesced = self.succeeded = self.failed = 0
|
|
23
|
+
self.incoming_gap, self.queue_wait = Timing(), Timing()
|
|
24
|
+
self.observation, self.apply, self.apply_start_gap = Timing(), Timing(), Timing()
|
|
25
|
+
self.turnaround = Timing()
|
|
26
|
+
self.last_received = self.last_apply = self.pending = None
|
|
27
|
+
self.queued_at = 0
|
|
28
|
+
|
|
29
|
+
def is_pending(self, sequence):
|
|
30
|
+
return self.pending == sequence
|
|
31
|
+
|
|
32
|
+
def receive(self, sequence, now):
|
|
33
|
+
self.received += 1
|
|
34
|
+
if self.last_received is not None:
|
|
35
|
+
self.incoming_gap.add(now - self.last_received)
|
|
36
|
+
self.last_received = self.queued_at = now
|
|
37
|
+
self.pending = sequence
|
|
38
|
+
|
|
39
|
+
def process(self, sequence, now):
|
|
40
|
+
if self.pending == sequence:
|
|
41
|
+
self.queue_wait.add(now - self.queued_at)
|
|
42
|
+
self.pending = None
|
|
43
|
+
return self.queued_at
|
|
44
|
+
return None
|
|
45
|
+
|
|
46
|
+
def applied(self, start, end, success, received_at=None):
|
|
47
|
+
if self.last_apply is not None:
|
|
48
|
+
self.apply_start_gap.add(start - self.last_apply)
|
|
49
|
+
self.last_apply = start
|
|
50
|
+
self.apply.add(end - start)
|
|
51
|
+
if success:
|
|
52
|
+
self.succeeded += 1
|
|
53
|
+
if received_at is not None:
|
|
54
|
+
self.turnaround.add(end - received_at)
|
|
55
|
+
else:
|
|
56
|
+
self.failed += 1
|
|
57
|
+
|
|
58
|
+
def text(self, name):
|
|
59
|
+
if not self.received and not self.observation.count and not self.apply.count:
|
|
60
|
+
return f'{name}: no activity'
|
|
61
|
+
overall = (f'turnaround avg {self.turnaround.total / self.turnaround.count:.1f} ms, max {self.turnaround.maximum:.1f} ms [{self.turnaround.count} applied]'
|
|
62
|
+
if self.turnaround.count else 'no updates applied')
|
|
63
|
+
replaced = 100 * self.coalesced / self.received if self.received else 0
|
|
64
|
+
return (f'{name} responsiveness: {overall}; pending poses replaced {replaced:.1f}%\n'
|
|
65
|
+
f'{name}: poses {self.received}, coalesced {self.coalesced}, writes {self.succeeded} ok/{self.failed} failed\n'
|
|
66
|
+
f' timings avg/max ms [samples]: input gap {self.incoming_gap.text()}; queue wait {self.queue_wait.text()}; '
|
|
67
|
+
f'observation {self.observation.text()}; apply {self.apply.text()}; apply gap {self.apply_start_gap.text()}')
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
class Gesture:
|
|
72
|
+
def __init__(self, gesture_id, token, now):
|
|
73
|
+
self.gesture_id, self.token, self.started = gesture_id, token, now
|
|
74
|
+
self.camera, self.object = PerformanceStream(), PerformanceStream()
|
|
75
|
+
self.reason, self.ended = '', now
|
|
76
|
+
|
|
77
|
+
def text(self):
|
|
78
|
+
return (f'navigation.performance gesture={self.gesture_id} reason={self.reason} '
|
|
79
|
+
f'duration={max(0, (self.ended - self.started) * 1000):.1f} ms\n'
|
|
80
|
+
f'{self.camera.text("camera")}\n{self.object.text("object")}')
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
class NavigationPerformance:
|
|
85
|
+
def __init__(self):
|
|
86
|
+
self.active = None
|
|
87
|
+
self.retired = []
|
|
88
|
+
|
|
89
|
+
def begin(self, gesture_id, token, now):
|
|
90
|
+
self.finish('superseded', now)
|
|
91
|
+
self.active = Gesture(gesture_id, token, now)
|
|
92
|
+
|
|
93
|
+
def stream(self, token, objects=False):
|
|
94
|
+
g = self.active
|
|
95
|
+
return (g.object if objects else g.camera) if g and g.token == token else None
|
|
96
|
+
|
|
97
|
+
def finish(self, reason, now, token=None):
|
|
98
|
+
if self.active is None or (token is not None and self.active.token != token):
|
|
99
|
+
return
|
|
100
|
+
g, self.active = self.active, None
|
|
101
|
+
g.reason, g.ended = reason, now
|
|
102
|
+
self.retired.append(g)
|
|
103
|
+
|
|
104
|
+
def take(self):
|
|
105
|
+
if not self.retired:
|
|
106
|
+
return ()
|
|
107
|
+
retired, self.retired = self.retired, []
|
|
108
|
+
return retired
|
|
109
|
+
|
|
110
|
+
@staticmethod
|
|
111
|
+
def flush(retired):
|
|
112
|
+
for g in retired:
|
|
113
|
+
try:
|
|
114
|
+
logging.getLogger('openaxis').info(g.text())
|
|
115
|
+
except Exception:
|
|
116
|
+
pass # Host logging handlers must not affect navigation.
|