antioch-py 1.9.7__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.

Potentially problematic release.


This version of antioch-py might be problematic. Click here for more details.

Files changed (99) hide show
  1. antioch/__init__.py +0 -0
  2. antioch/message.py +87 -0
  3. antioch/module/__init__.py +53 -0
  4. antioch/module/clock.py +62 -0
  5. antioch/module/execution.py +278 -0
  6. antioch/module/input.py +127 -0
  7. antioch/module/module.py +218 -0
  8. antioch/module/node.py +357 -0
  9. antioch/module/token.py +42 -0
  10. antioch/session/__init__.py +150 -0
  11. antioch/session/ark.py +504 -0
  12. antioch/session/asset.py +65 -0
  13. antioch/session/error.py +80 -0
  14. antioch/session/record.py +158 -0
  15. antioch/session/scene.py +1500 -0
  16. antioch/session/session.py +220 -0
  17. antioch/session/task.py +323 -0
  18. antioch/session/views/__init__.py +40 -0
  19. antioch/session/views/animation.py +189 -0
  20. antioch/session/views/articulation.py +245 -0
  21. antioch/session/views/basis_curve.py +186 -0
  22. antioch/session/views/camera.py +92 -0
  23. antioch/session/views/collision.py +75 -0
  24. antioch/session/views/geometry.py +74 -0
  25. antioch/session/views/ground_plane.py +63 -0
  26. antioch/session/views/imu.py +73 -0
  27. antioch/session/views/joint.py +64 -0
  28. antioch/session/views/light.py +175 -0
  29. antioch/session/views/pir_sensor.py +140 -0
  30. antioch/session/views/radar.py +73 -0
  31. antioch/session/views/rigid_body.py +282 -0
  32. antioch/session/views/xform.py +119 -0
  33. antioch_py-1.9.7.dist-info/METADATA +24 -0
  34. antioch_py-1.9.7.dist-info/RECORD +99 -0
  35. antioch_py-1.9.7.dist-info/WHEEL +5 -0
  36. antioch_py-1.9.7.dist-info/entry_points.txt +2 -0
  37. antioch_py-1.9.7.dist-info/top_level.txt +2 -0
  38. common/__init__.py +0 -0
  39. common/ark/__init__.py +60 -0
  40. common/ark/ark.py +128 -0
  41. common/ark/hardware.py +121 -0
  42. common/ark/kinematics.py +31 -0
  43. common/ark/module.py +85 -0
  44. common/ark/node.py +94 -0
  45. common/ark/scheduler.py +439 -0
  46. common/ark/sim.py +33 -0
  47. common/assets/__init__.py +3 -0
  48. common/constants.py +47 -0
  49. common/core/__init__.py +52 -0
  50. common/core/agent.py +296 -0
  51. common/core/auth.py +305 -0
  52. common/core/registry.py +331 -0
  53. common/core/task.py +36 -0
  54. common/message/__init__.py +59 -0
  55. common/message/annotation.py +89 -0
  56. common/message/array.py +500 -0
  57. common/message/base.py +517 -0
  58. common/message/camera.py +91 -0
  59. common/message/color.py +139 -0
  60. common/message/frame.py +50 -0
  61. common/message/image.py +171 -0
  62. common/message/imu.py +14 -0
  63. common/message/joint.py +47 -0
  64. common/message/log.py +31 -0
  65. common/message/pir.py +15 -0
  66. common/message/point.py +109 -0
  67. common/message/point_cloud.py +63 -0
  68. common/message/pose.py +148 -0
  69. common/message/quaternion.py +273 -0
  70. common/message/radar.py +58 -0
  71. common/message/types.py +37 -0
  72. common/message/vector.py +786 -0
  73. common/rome/__init__.py +9 -0
  74. common/rome/client.py +430 -0
  75. common/rome/error.py +16 -0
  76. common/session/__init__.py +54 -0
  77. common/session/environment.py +31 -0
  78. common/session/sim.py +240 -0
  79. common/session/views/__init__.py +263 -0
  80. common/session/views/animation.py +73 -0
  81. common/session/views/articulation.py +184 -0
  82. common/session/views/basis_curve.py +102 -0
  83. common/session/views/camera.py +147 -0
  84. common/session/views/collision.py +59 -0
  85. common/session/views/geometry.py +102 -0
  86. common/session/views/ground_plane.py +41 -0
  87. common/session/views/imu.py +66 -0
  88. common/session/views/joint.py +81 -0
  89. common/session/views/light.py +96 -0
  90. common/session/views/pir_sensor.py +104 -0
  91. common/session/views/radar.py +82 -0
  92. common/session/views/rigid_body.py +236 -0
  93. common/session/views/viewport.py +21 -0
  94. common/session/views/xform.py +39 -0
  95. common/utils/__init__.py +4 -0
  96. common/utils/comms.py +571 -0
  97. common/utils/logger.py +123 -0
  98. common/utils/time.py +42 -0
  99. common/utils/usd.py +12 -0
antioch/__init__.py ADDED
File without changes
antioch/message.py ADDED
@@ -0,0 +1,87 @@
1
+ from common.message import (
2
+ Array,
3
+ Bool,
4
+ CameraInfo,
5
+ CircleAnnotation,
6
+ Color,
7
+ DeserializationError,
8
+ Float,
9
+ FrameTransform,
10
+ FrameTransforms,
11
+ Image,
12
+ ImageAnnotations,
13
+ ImageEncoding,
14
+ Int,
15
+ JointState,
16
+ JointStates,
17
+ JointTarget,
18
+ JointTargets,
19
+ Log,
20
+ LogLevel,
21
+ Message,
22
+ MessageError,
23
+ MismatchError,
24
+ Point2,
25
+ Point3,
26
+ PointCloud,
27
+ PointsAnnotation,
28
+ PointsAnnotationType,
29
+ Pose,
30
+ Quaternion,
31
+ RadarDetection,
32
+ RadarScan,
33
+ SerializationError,
34
+ String,
35
+ TextAnnotation,
36
+ Vector2,
37
+ Vector3,
38
+ )
39
+
40
+ __all__ = [
41
+ # Base types
42
+ "Message",
43
+ "MessageError",
44
+ "DeserializationError",
45
+ "SerializationError",
46
+ "MismatchError",
47
+ # Primitive types
48
+ "Array",
49
+ "Bool",
50
+ "Float",
51
+ "Int",
52
+ "String",
53
+ # Geometry types
54
+ "Point2",
55
+ "Point3",
56
+ "Vector2",
57
+ "Vector3",
58
+ "Pose",
59
+ "Quaternion",
60
+ # Color
61
+ "Color",
62
+ # Camera types
63
+ "CameraInfo",
64
+ "Image",
65
+ "ImageEncoding",
66
+ # Joint types
67
+ "JointState",
68
+ "JointStates",
69
+ "JointTarget",
70
+ "JointTargets",
71
+ # Sensor types
72
+ "RadarDetection",
73
+ "RadarScan",
74
+ "PointCloud",
75
+ # Logging
76
+ "Log",
77
+ "LogLevel",
78
+ # Annotations
79
+ "CircleAnnotation",
80
+ "ImageAnnotations",
81
+ "PointsAnnotation",
82
+ "PointsAnnotationType",
83
+ "TextAnnotation",
84
+ # Frame transforms
85
+ "FrameTransform",
86
+ "FrameTransforms",
87
+ ]
@@ -0,0 +1,53 @@
1
+ from antioch.module.clock import Clock, now_us
2
+ from antioch.module.execution import Execution, Input, Output
3
+ from antioch.module.module import Module
4
+ from antioch.module.token import Token, TokenType
5
+ from common.ark import Environment, HardwareAccessMode
6
+ from common.message import (
7
+ CameraInfo,
8
+ Image,
9
+ ImageEncoding,
10
+ ImuSample,
11
+ JointState,
12
+ JointStates,
13
+ JointTarget,
14
+ JointTargets,
15
+ Message,
16
+ Pose,
17
+ Quaternion,
18
+ RadarDetection,
19
+ RadarScan,
20
+ Vector3,
21
+ )
22
+
23
+ __all__ = [
24
+ # Core module types
25
+ "Module",
26
+ "Execution",
27
+ "Input",
28
+ "Output",
29
+ # Token types
30
+ "Token",
31
+ "TokenType",
32
+ # Timing
33
+ "Clock",
34
+ "now_us",
35
+ # Enums
36
+ "Environment",
37
+ "HardwareAccessMode",
38
+ # Message types
39
+ "Message",
40
+ "CameraInfo",
41
+ "Image",
42
+ "ImageEncoding",
43
+ "ImuSample",
44
+ "JointState",
45
+ "JointStates",
46
+ "JointTarget",
47
+ "JointTargets",
48
+ "Pose",
49
+ "Quaternion",
50
+ "RadarDetection",
51
+ "RadarScan",
52
+ "Vector3",
53
+ ]
@@ -0,0 +1,62 @@
1
+ import time
2
+ from threading import Event
3
+
4
+ from common.utils.time import now_us
5
+
6
+
7
+ class Clock:
8
+ """
9
+ Real-time clock for LET-to-wall-time mapping (real mode only).
10
+
11
+ Maps logical execution time to wall-clock time using a fixed start time.
12
+ Provides precise timing for real-mode execution to avoid clock drift.
13
+ """
14
+
15
+ def __init__(self, start_timestamp_us: int, event: Event | None = None):
16
+ """
17
+ Create a new real-time clock.
18
+
19
+ :param start_timestamp_us: Wall-clock time (microseconds since epoch) corresponding to LET=0.
20
+ :param event: Optional event to check for shutdown requests during waits.
21
+ """
22
+
23
+ self._start_timestamp_us = start_timestamp_us
24
+ self._event = event
25
+
26
+ @property
27
+ def let_us(self) -> int:
28
+ """
29
+ Get the current logical time in microseconds.
30
+
31
+ :return: Current logical time.
32
+ """
33
+
34
+ return now_us() - self._start_timestamp_us
35
+
36
+ def wait_until(self, let_us: int) -> bool:
37
+ """
38
+ Sleep until reaching target LET.
39
+
40
+ Checks shutdown event every 100ms. Uses hybrid sleep approach:
41
+ - For waits > 10ms: Sleep in chunks (max 100ms) for shutdown responsiveness
42
+ - For waits < 10ms: Sleep in 100μs increments for precision
43
+
44
+ :param let_us: Target logical execution time in microseconds.
45
+ :return: True if wait completed normally, False if interrupted by event.
46
+ """
47
+
48
+ target_timestamp_us = self._start_timestamp_us + let_us
49
+ while True:
50
+ # Check if we should exit
51
+ current = now_us()
52
+ if current >= target_timestamp_us:
53
+ return True
54
+
55
+ # Check event if provided
56
+ if self._event and self._event.is_set():
57
+ return False
58
+
59
+ # Sleep for chunk
60
+ remaining_us = target_timestamp_us - current
61
+ sleep_us = min(remaining_us, 100_000) if remaining_us > 10_000 else min(remaining_us, 100)
62
+ time.sleep(sleep_us / 1_000_000)
@@ -0,0 +1,278 @@
1
+ import time
2
+ from typing import TypeVar, overload
3
+
4
+ from antioch.module.token import Token, TokenType
5
+ from common.ark.module import ModuleParameter
6
+ from common.ark.node import NodeOutput
7
+ from common.message import Image, ImuSample, JointStates, JointTargets, Message, PirStatus, RadarScan
8
+ from common.utils.logger import Logger
9
+ from common.utils.time import now_us
10
+
11
+ T = TypeVar("T", bound=Message)
12
+
13
+
14
+ class Execution:
15
+ """
16
+ Node execution context.
17
+
18
+ Provides access to inputs, outputs, hardware, logger, and module parameters
19
+ for user callbacks.
20
+ """
21
+
22
+ name: str
23
+ let_us: int
24
+ budget_us: int
25
+ parameters: dict[str, ModuleParameter]
26
+ logger: Logger
27
+
28
+ def __init__(
29
+ self,
30
+ name: str,
31
+ budget_us: int,
32
+ parameters: dict[str, ModuleParameter],
33
+ outputs: dict[str, NodeOutput],
34
+ logger: Logger,
35
+ ):
36
+ """
37
+ Initialize execution context with static configuration.
38
+
39
+ :param name: Node name.
40
+ :param budget_us: Node execution budget in microseconds.
41
+ :param parameters: Module parameters.
42
+ :param outputs: Node output configurations.
43
+ :param logger: Logger for this execution context.
44
+ """
45
+
46
+ self.name = name
47
+ self.budget_us = budget_us
48
+ self.parameters = parameters
49
+ self.logger = logger
50
+
51
+ self._output_configs = outputs
52
+ self._outputs = {name: Output(name, output_config, self) for name, output_config in outputs.items()}
53
+
54
+ # Execution runtime state
55
+ self.let_us = 0
56
+ self._start_timestamp_us = 0
57
+ self._start_time_us = 0
58
+ self._inputs: dict[str, Input] = {}
59
+ self._output_data: dict[str, bytes | None] = {}
60
+ self._hardware_reads: dict[str, bytes] = {}
61
+ self._hardware_writes: dict[str, bytes] = {}
62
+
63
+ def start(
64
+ self,
65
+ let_us: int,
66
+ input_data: dict[str, list[Token]] = {},
67
+ hardware_reads: dict[str, bytes] = {},
68
+ ) -> None:
69
+ """
70
+ Start a new execution with runtime data.
71
+
72
+ :param let_us: Logical execution time.
73
+ :param input_data: Input tokens keyed by input name.
74
+ :param hardware_reads: Hardware read data keyed by hardware name.
75
+ """
76
+
77
+ self.let_us = let_us
78
+ self._start_timestamp_us = now_us()
79
+ self._start_time_us = time.monotonic_ns() // 1000
80
+ self._inputs = {name: Input(tokens) for name, tokens in input_data.items()}
81
+ self._output_data.clear()
82
+ self._hardware_reads = hardware_reads
83
+ self._hardware_writes.clear()
84
+
85
+ @property
86
+ def elapsed_us(self) -> int:
87
+ """
88
+ Get elapsed time in microseconds since execution start.
89
+
90
+ :return: Elapsed time in microseconds.
91
+ """
92
+
93
+ return time.monotonic_ns() // 1000 - self._start_time_us
94
+
95
+ @property
96
+ def remaining_us(self) -> int:
97
+ """
98
+ Get remaining time in microseconds until execution budget is exhausted.
99
+
100
+ :return: Remaining time in microseconds.
101
+ """
102
+
103
+ return max(0, self.budget_us - self.elapsed_us)
104
+
105
+ def input(self, name: str) -> "Input":
106
+ """
107
+ Get input by name.
108
+
109
+ :param name: Input name.
110
+ :return: Input wrapper.
111
+ :raises KeyError: If input not found.
112
+ """
113
+
114
+ if name not in self._inputs:
115
+ raise KeyError(f"Input '{name}' not found")
116
+ return self._inputs[name]
117
+
118
+ def output(self, name: str) -> "Output":
119
+ """
120
+ Get output by name.
121
+
122
+ :param name: Output name.
123
+ :return: Output wrapper.
124
+ :raises KeyError: If output not found.
125
+ """
126
+
127
+ if name not in self._outputs:
128
+ raise KeyError(f"Output '{name}' not found")
129
+ return self._outputs[name]
130
+
131
+ def read_camera(self, name: str) -> Image | None:
132
+ """
133
+ Read camera image.
134
+
135
+ :param name: Hardware name.
136
+ :return: Camera image, or None if no data.
137
+ """
138
+
139
+ data = self._hardware_reads.get(name)
140
+ return Image.unpack(data) if data else None
141
+
142
+ def read_imu(self, name: str) -> ImuSample | None:
143
+ """
144
+ Read IMU sample.
145
+
146
+ :param name: Hardware name.
147
+ :return: IMU sensor measurements, or None if no data.
148
+ """
149
+
150
+ data = self._hardware_reads.get(name)
151
+ return ImuSample.unpack(data) if data else None
152
+
153
+ def read_radar(self, name: str) -> RadarScan | None:
154
+ """
155
+ Read radar scan.
156
+
157
+ :param name: Hardware name.
158
+ :return: Radar scan with detections, or None if no data.
159
+ """
160
+
161
+ data = self._hardware_reads.get(name)
162
+ return RadarScan.unpack(data) if data else None
163
+
164
+ def read_pir(self, name: str) -> PirStatus | None:
165
+ """
166
+ Read PIR sensor status.
167
+
168
+ :param name: Hardware name.
169
+ :return: PIR status with detection state and signal info, or None if no data.
170
+ """
171
+
172
+ data = self._hardware_reads.get(name)
173
+ return PirStatus.unpack(data) if data else None
174
+
175
+ def read_actuator_group(self, name: str) -> JointStates | None:
176
+ """
177
+ Read joint states from actuator group.
178
+
179
+ :param name: Hardware name.
180
+ :return: Joint states, or None if no data.
181
+ """
182
+
183
+ data = self._hardware_reads.get(name)
184
+ return JointStates.unpack(data) if data else None
185
+
186
+ def write_actuator_group(self, name: str, targets: JointTargets) -> None:
187
+ """
188
+ Write joint targets to actuator group.
189
+
190
+ Queues joint targets for node completion.
191
+
192
+ :param name: Hardware name.
193
+ :param targets: Joint targets to apply.
194
+ """
195
+
196
+ self._hardware_writes[name] = targets.pack()
197
+
198
+
199
+ class Input:
200
+ """
201
+ Input wrapper for accessing tokens.
202
+ """
203
+
204
+ def __init__(self, tokens: list[Token]):
205
+ """
206
+ Initialize input.
207
+
208
+ :param tokens: List of tokens for this input.
209
+ """
210
+
211
+ self._tokens = tokens
212
+
213
+ @overload
214
+ def data(self, message_cls: type[T], n: int = 1) -> T | None: ...
215
+
216
+ @overload
217
+ def data(self, message_cls: type[T], n: int) -> list[T]: ...
218
+
219
+ def data(self, message_cls: type[T], n: int = 1) -> list[T] | T | None:
220
+ """
221
+ Get deserialized data from tokens.
222
+
223
+ :param message_cls: Message type to deserialize.
224
+ :param n: Number of tokens to return (1 for single, >1 for list).
225
+ :return: Single message, list of messages, or None if no data.
226
+ """
227
+
228
+ data = []
229
+ for token in reversed(self._tokens):
230
+ if token.status == TokenType.DATA and token.payload:
231
+ data.append(message_cls.unpack(token.payload))
232
+ if len(data) >= n:
233
+ break
234
+ return data if n > 1 else (data[0] if data else None)
235
+
236
+ def tokens(self) -> list[Token]:
237
+ """
238
+ Get all tokens for this input.
239
+
240
+ :return: List of tokens.
241
+ """
242
+
243
+ return self._tokens
244
+
245
+
246
+ class Output:
247
+ """
248
+ Output wrapper for setting data.
249
+ """
250
+
251
+ def __init__(self, name: str, output_config: NodeOutput, execution: Execution):
252
+ """
253
+ Initialize output.
254
+
255
+ :param name: Output name.
256
+ :param output_config: Output configuration from node.
257
+ :param execution: Parent execution context.
258
+ """
259
+
260
+ self._name = name
261
+ self._output_config = output_config
262
+ self._execution = execution
263
+
264
+ def set(self, message: Message) -> "Output":
265
+ """
266
+ Set output data.
267
+
268
+ :param message: Message to serialize and set.
269
+ :return: Self for chaining.
270
+ :raises ValueError: If message type doesn't match expected output type.
271
+ """
272
+
273
+ # Validate message type matches expected output type
274
+ if message.get_type() != self._output_config.type:
275
+ raise ValueError(f"Output '{self._name}' expects type '{self._output_config.type}', got '{message.get_type()}'")
276
+
277
+ self._execution._output_data[self._name] = message.pack()
278
+ return self
@@ -0,0 +1,127 @@
1
+ from threading import Lock
2
+
3
+ import zenoh
4
+ from sortedcontainers import SortedDict
5
+
6
+ from antioch.module.token import Token, TokenType
7
+ from common.ark.node import NodeInput
8
+ from common.utils.comms import CommsSession
9
+
10
+ TOKEN_INPUT_PATH = "_token/{path}"
11
+
12
+
13
+ class NodeInputBuffer:
14
+ """
15
+ Thread-safe input buffer with policy-based token retention.
16
+
17
+ Manages tokens for a single node input with automatic arrival validation
18
+ and buffer policy enforcement (last_n, max_age_ms, always_run, required, consume_inputs).
19
+ """
20
+
21
+ def __init__(self, input_name: str, config: NodeInput, comms: CommsSession):
22
+ """
23
+ Create a new node input buffer.
24
+
25
+ :param input_name: Name of the input.
26
+ :param config: Input configuration with buffer policies.
27
+ :param comms: Communication session for subscribing to tokens.
28
+ """
29
+
30
+ self._input_name = input_name
31
+ self._config = config
32
+ self._lock = Lock()
33
+ self._tokens: SortedDict[tuple[int, str, str], Token] = SortedDict()
34
+
35
+ # Subscribe to token path with callback
36
+ path = TOKEN_INPUT_PATH.format(path=config.path)
37
+ self._subscriber = comms.declare_callback_subscriber(path, self._on_token)
38
+
39
+ def close(self) -> None:
40
+ """
41
+ Close the input buffer and clean up resources.
42
+ """
43
+
44
+ self._subscriber.undeclare()
45
+
46
+ def add_token(self, token: Token) -> None:
47
+ """
48
+ Add token to buffer.
49
+
50
+ :param token: Token to add.
51
+ """
52
+
53
+ with self._lock:
54
+ key = (token.let_us, token.module_name, token.node_name)
55
+ self._tokens[key] = token
56
+
57
+ def get_token(self, let_us: int, source_module: str, source_node: str) -> Token | None:
58
+ """
59
+ Get specific token or None if not arrived.
60
+
61
+ :param let_us: Logical execution time of the token.
62
+ :param source_module: Source module name.
63
+ :param source_node: Source node name.
64
+ :return: Token if found, None otherwise.
65
+ """
66
+
67
+ with self._lock:
68
+ key = (let_us, source_module, source_node)
69
+ return self._tokens.get(key)
70
+
71
+ def prepare_execution(self, let_us: int) -> list[Token] | None:
72
+ """
73
+ Prepare tokens for execution by collecting and applying buffer policies.
74
+
75
+ :param let_us: Current logical execution time.
76
+ :return: Filtered tokens ready for execution, or None if required input missing.
77
+ """
78
+
79
+ with self._lock:
80
+ # Collect tokens with let_us <= current execution time
81
+ tokens = [t for t in self._tokens.values() if t.let_us <= let_us]
82
+
83
+ # Filter to data tokens only (unless always_run allows errors/overruns)
84
+ if not self._config.always_run:
85
+ tokens = [t for t in tokens if t.status == TokenType.DATA]
86
+
87
+ # Check required input constraint
88
+ if self._config.required and len(tokens) == 0:
89
+ return None
90
+
91
+ # Keep only last N tokens
92
+ if len(tokens) > self._config.last_n:
93
+ tokens = tokens[-self._config.last_n :]
94
+
95
+ # Filter out stale tokens based on max age
96
+ if self._config.max_age_ms is not None:
97
+ max_age_us = self._config.max_age_ms * 1000
98
+ tokens = [t for t in tokens if let_us - t.let_us <= max_age_us]
99
+
100
+ # Add filtered tokens back to buffer if not consuming
101
+ if not self._config.consume_inputs:
102
+ for t in tokens:
103
+ key = (t.let_us, t.module_name, t.node_name)
104
+ self._tokens[key] = t
105
+
106
+ return tokens
107
+
108
+ def _on_token(self, sample: zenoh.Sample) -> None:
109
+ """
110
+ Callback thread: receive and buffer token.
111
+
112
+ :param sample: Zenoh sample containing token.
113
+ """
114
+
115
+ token = Token.unpack(sample.payload.to_bytes())
116
+ if token.elapsed() > token.budget_us:
117
+ token = Token(
118
+ module_name=token.module_name,
119
+ node_name=token.node_name,
120
+ output_name=token.output_name,
121
+ let_us=token.let_us,
122
+ start_timestamp_us=token.start_timestamp_us,
123
+ budget_us=token.budget_us,
124
+ status=TokenType.OVERRUN,
125
+ )
126
+
127
+ self.add_token(token)