foodforthought-cli 0.2.4__py3-none-any.whl → 0.2.8__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.
- ate/__init__.py +1 -1
- ate/behaviors/__init__.py +88 -0
- ate/behaviors/common.py +686 -0
- ate/behaviors/tree.py +454 -0
- ate/cli.py +610 -54
- ate/drivers/__init__.py +27 -0
- ate/drivers/mechdog.py +606 -0
- ate/interfaces/__init__.py +171 -0
- ate/interfaces/base.py +271 -0
- ate/interfaces/body.py +267 -0
- ate/interfaces/detection.py +282 -0
- ate/interfaces/locomotion.py +422 -0
- ate/interfaces/manipulation.py +408 -0
- ate/interfaces/navigation.py +389 -0
- ate/interfaces/perception.py +362 -0
- ate/interfaces/types.py +371 -0
- ate/mcp_server.py +387 -0
- ate/recording/__init__.py +44 -0
- ate/recording/demonstration.py +378 -0
- ate/recording/session.py +405 -0
- ate/recording/upload.py +304 -0
- ate/recording/wrapper.py +95 -0
- ate/robot/__init__.py +79 -0
- ate/robot/calibration.py +583 -0
- ate/robot/commands.py +3603 -0
- ate/robot/discovery.py +339 -0
- ate/robot/introspection.py +330 -0
- ate/robot/manager.py +270 -0
- ate/robot/profiles.py +275 -0
- ate/robot/registry.py +319 -0
- ate/robot/skill_upload.py +393 -0
- ate/robot/visual_labeler.py +1039 -0
- {foodforthought_cli-0.2.4.dist-info → foodforthought_cli-0.2.8.dist-info}/METADATA +9 -1
- {foodforthought_cli-0.2.4.dist-info → foodforthought_cli-0.2.8.dist-info}/RECORD +37 -8
- {foodforthought_cli-0.2.4.dist-info → foodforthought_cli-0.2.8.dist-info}/WHEEL +0 -0
- {foodforthought_cli-0.2.4.dist-info → foodforthought_cli-0.2.8.dist-info}/entry_points.txt +0 -0
- {foodforthought_cli-0.2.4.dist-info → foodforthought_cli-0.2.8.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,362 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Perception interfaces for robot sensors.
|
|
3
|
+
|
|
4
|
+
Supports:
|
|
5
|
+
- RGB cameras
|
|
6
|
+
- Depth cameras (stereo, ToF, structured light)
|
|
7
|
+
- LiDAR (2D and 3D)
|
|
8
|
+
- IMU
|
|
9
|
+
- Force/Torque sensors
|
|
10
|
+
|
|
11
|
+
Design principle: Return raw sensor data in standard formats.
|
|
12
|
+
Processing (object detection, SLAM, etc.) happens at higher levels.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from abc import ABC, abstractmethod
|
|
16
|
+
from typing import Optional, List, Callable, Tuple
|
|
17
|
+
from enum import Enum, auto
|
|
18
|
+
|
|
19
|
+
from .types import (
|
|
20
|
+
Vector3,
|
|
21
|
+
Quaternion,
|
|
22
|
+
Pose,
|
|
23
|
+
Image,
|
|
24
|
+
DepthImage,
|
|
25
|
+
PointCloud,
|
|
26
|
+
IMUReading,
|
|
27
|
+
ForceTorqueReading,
|
|
28
|
+
ActionResult,
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class CameraInterface(ABC):
|
|
33
|
+
"""
|
|
34
|
+
Interface for RGB cameras.
|
|
35
|
+
|
|
36
|
+
Can be:
|
|
37
|
+
- Fixed mount (e.g., head camera)
|
|
38
|
+
- On end-effector (e.g., wrist camera)
|
|
39
|
+
- External (e.g., Intel RealSense)
|
|
40
|
+
"""
|
|
41
|
+
|
|
42
|
+
@abstractmethod
|
|
43
|
+
def get_image(self) -> Image:
|
|
44
|
+
"""
|
|
45
|
+
Capture current frame.
|
|
46
|
+
|
|
47
|
+
Returns:
|
|
48
|
+
Image with RGB data
|
|
49
|
+
"""
|
|
50
|
+
pass
|
|
51
|
+
|
|
52
|
+
@abstractmethod
|
|
53
|
+
def get_resolution(self) -> Tuple[int, int]:
|
|
54
|
+
"""
|
|
55
|
+
Get camera resolution.
|
|
56
|
+
|
|
57
|
+
Returns:
|
|
58
|
+
(width, height) in pixels
|
|
59
|
+
"""
|
|
60
|
+
pass
|
|
61
|
+
|
|
62
|
+
@abstractmethod
|
|
63
|
+
def get_intrinsics(self) -> dict:
|
|
64
|
+
"""
|
|
65
|
+
Get camera intrinsic parameters.
|
|
66
|
+
|
|
67
|
+
Returns:
|
|
68
|
+
Dict with:
|
|
69
|
+
- fx, fy: focal lengths
|
|
70
|
+
- cx, cy: principal point
|
|
71
|
+
- distortion: distortion coefficients
|
|
72
|
+
"""
|
|
73
|
+
pass
|
|
74
|
+
|
|
75
|
+
def get_frame_id(self) -> str:
|
|
76
|
+
"""Get coordinate frame ID for this camera."""
|
|
77
|
+
return "camera"
|
|
78
|
+
|
|
79
|
+
def set_resolution(self, width: int, height: int) -> ActionResult:
|
|
80
|
+
"""Set camera resolution."""
|
|
81
|
+
return ActionResult.error("Resolution change not supported")
|
|
82
|
+
|
|
83
|
+
def set_exposure(self, exposure_ms: float) -> ActionResult:
|
|
84
|
+
"""Set exposure time in milliseconds."""
|
|
85
|
+
return ActionResult.error("Exposure control not supported")
|
|
86
|
+
|
|
87
|
+
# =========================================================================
|
|
88
|
+
# Streaming (optional)
|
|
89
|
+
# =========================================================================
|
|
90
|
+
|
|
91
|
+
def start_streaming(self, callback: Callable[[Image], None]) -> ActionResult:
|
|
92
|
+
"""
|
|
93
|
+
Start continuous image streaming.
|
|
94
|
+
|
|
95
|
+
Args:
|
|
96
|
+
callback: Function called with each new frame
|
|
97
|
+
"""
|
|
98
|
+
return ActionResult.error("Streaming not supported")
|
|
99
|
+
|
|
100
|
+
def stop_streaming(self) -> ActionResult:
|
|
101
|
+
"""Stop image streaming."""
|
|
102
|
+
return ActionResult.error("Streaming not supported")
|
|
103
|
+
|
|
104
|
+
def get_fps(self) -> float:
|
|
105
|
+
"""Get current frame rate."""
|
|
106
|
+
return 0.0
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
class DepthCameraInterface(CameraInterface):
|
|
110
|
+
"""
|
|
111
|
+
Interface for depth cameras.
|
|
112
|
+
|
|
113
|
+
Extends CameraInterface with depth-specific methods.
|
|
114
|
+
Implemented by: RealSense, Kinect, stereo cameras, ToF sensors.
|
|
115
|
+
"""
|
|
116
|
+
|
|
117
|
+
@abstractmethod
|
|
118
|
+
def get_depth_image(self) -> DepthImage:
|
|
119
|
+
"""
|
|
120
|
+
Capture current depth frame.
|
|
121
|
+
|
|
122
|
+
Returns:
|
|
123
|
+
DepthImage with depth values
|
|
124
|
+
"""
|
|
125
|
+
pass
|
|
126
|
+
|
|
127
|
+
@abstractmethod
|
|
128
|
+
def get_point_cloud(self) -> PointCloud:
|
|
129
|
+
"""
|
|
130
|
+
Get 3D point cloud from depth data.
|
|
131
|
+
|
|
132
|
+
Returns:
|
|
133
|
+
PointCloud in camera frame
|
|
134
|
+
"""
|
|
135
|
+
pass
|
|
136
|
+
|
|
137
|
+
def get_rgbd(self) -> Tuple[Image, DepthImage]:
|
|
138
|
+
"""
|
|
139
|
+
Get aligned RGB and depth images.
|
|
140
|
+
|
|
141
|
+
Returns:
|
|
142
|
+
(rgb_image, depth_image) tuple
|
|
143
|
+
"""
|
|
144
|
+
return (self.get_image(), self.get_depth_image())
|
|
145
|
+
|
|
146
|
+
def get_depth_range(self) -> Tuple[float, float]:
|
|
147
|
+
"""
|
|
148
|
+
Get valid depth range.
|
|
149
|
+
|
|
150
|
+
Returns:
|
|
151
|
+
(min_depth, max_depth) in meters
|
|
152
|
+
"""
|
|
153
|
+
return (0.1, 10.0)
|
|
154
|
+
|
|
155
|
+
def point_to_3d(self, x: int, y: int) -> Optional[Vector3]:
|
|
156
|
+
"""
|
|
157
|
+
Convert pixel coordinates to 3D point.
|
|
158
|
+
|
|
159
|
+
Args:
|
|
160
|
+
x, y: Pixel coordinates
|
|
161
|
+
|
|
162
|
+
Returns:
|
|
163
|
+
Vector3 in camera frame, or None if invalid depth
|
|
164
|
+
"""
|
|
165
|
+
depth = self.get_depth_image()
|
|
166
|
+
# Implementation depends on depth format
|
|
167
|
+
return None
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
class LidarInterface(ABC):
|
|
171
|
+
"""
|
|
172
|
+
Interface for LiDAR sensors.
|
|
173
|
+
|
|
174
|
+
Supports:
|
|
175
|
+
- 2D LiDAR (RPLIDAR, Hokuyo)
|
|
176
|
+
- 3D LiDAR (Velodyne, Ouster, Livox)
|
|
177
|
+
"""
|
|
178
|
+
|
|
179
|
+
@abstractmethod
|
|
180
|
+
def get_scan(self) -> PointCloud:
|
|
181
|
+
"""
|
|
182
|
+
Get current LiDAR scan.
|
|
183
|
+
|
|
184
|
+
Returns:
|
|
185
|
+
PointCloud in sensor frame
|
|
186
|
+
"""
|
|
187
|
+
pass
|
|
188
|
+
|
|
189
|
+
@abstractmethod
|
|
190
|
+
def get_range(self) -> Tuple[float, float]:
|
|
191
|
+
"""
|
|
192
|
+
Get measurement range.
|
|
193
|
+
|
|
194
|
+
Returns:
|
|
195
|
+
(min_range, max_range) in meters
|
|
196
|
+
"""
|
|
197
|
+
pass
|
|
198
|
+
|
|
199
|
+
def get_frame_id(self) -> str:
|
|
200
|
+
"""Get coordinate frame ID for this LiDAR."""
|
|
201
|
+
return "lidar"
|
|
202
|
+
|
|
203
|
+
def is_3d(self) -> bool:
|
|
204
|
+
"""Check if this is a 3D LiDAR."""
|
|
205
|
+
return True
|
|
206
|
+
|
|
207
|
+
def get_angular_resolution(self) -> float:
|
|
208
|
+
"""Get angular resolution in radians."""
|
|
209
|
+
return 0.01 # ~0.5 degrees default
|
|
210
|
+
|
|
211
|
+
# =========================================================================
|
|
212
|
+
# 2D LiDAR specific
|
|
213
|
+
# =========================================================================
|
|
214
|
+
|
|
215
|
+
def get_scan_2d(self) -> List[Tuple[float, float]]:
|
|
216
|
+
"""
|
|
217
|
+
Get 2D scan as list of (angle, distance) pairs.
|
|
218
|
+
|
|
219
|
+
Returns:
|
|
220
|
+
List of (angle_rad, distance_m) tuples
|
|
221
|
+
"""
|
|
222
|
+
# Default: project 3D to 2D
|
|
223
|
+
cloud = self.get_scan()
|
|
224
|
+
scan_2d = []
|
|
225
|
+
import math
|
|
226
|
+
for point in cloud.points:
|
|
227
|
+
angle = math.atan2(point.y, point.x)
|
|
228
|
+
distance = math.sqrt(point.x**2 + point.y**2)
|
|
229
|
+
scan_2d.append((angle, distance))
|
|
230
|
+
return scan_2d
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
class IMUInterface(ABC):
|
|
234
|
+
"""
|
|
235
|
+
Interface for Inertial Measurement Units.
|
|
236
|
+
|
|
237
|
+
Provides:
|
|
238
|
+
- Orientation (from gyro integration or sensor fusion)
|
|
239
|
+
- Angular velocity (from gyroscope)
|
|
240
|
+
- Linear acceleration (from accelerometer)
|
|
241
|
+
"""
|
|
242
|
+
|
|
243
|
+
@abstractmethod
|
|
244
|
+
def get_reading(self) -> IMUReading:
|
|
245
|
+
"""
|
|
246
|
+
Get current IMU reading.
|
|
247
|
+
|
|
248
|
+
Returns:
|
|
249
|
+
IMUReading with orientation, angular velocity, acceleration
|
|
250
|
+
"""
|
|
251
|
+
pass
|
|
252
|
+
|
|
253
|
+
@abstractmethod
|
|
254
|
+
def get_orientation(self) -> Quaternion:
|
|
255
|
+
"""
|
|
256
|
+
Get current orientation.
|
|
257
|
+
|
|
258
|
+
Returns:
|
|
259
|
+
Quaternion representing orientation
|
|
260
|
+
"""
|
|
261
|
+
pass
|
|
262
|
+
|
|
263
|
+
def get_euler(self) -> Tuple[float, float, float]:
|
|
264
|
+
"""
|
|
265
|
+
Get orientation as Euler angles.
|
|
266
|
+
|
|
267
|
+
Returns:
|
|
268
|
+
(roll, pitch, yaw) in radians
|
|
269
|
+
"""
|
|
270
|
+
return self.get_orientation().to_euler()
|
|
271
|
+
|
|
272
|
+
def get_angular_velocity(self) -> Vector3:
|
|
273
|
+
"""
|
|
274
|
+
Get angular velocity.
|
|
275
|
+
|
|
276
|
+
Returns:
|
|
277
|
+
Vector3 in rad/s
|
|
278
|
+
"""
|
|
279
|
+
return self.get_reading().angular_velocity
|
|
280
|
+
|
|
281
|
+
def get_linear_acceleration(self) -> Vector3:
|
|
282
|
+
"""
|
|
283
|
+
Get linear acceleration.
|
|
284
|
+
|
|
285
|
+
Returns:
|
|
286
|
+
Vector3 in m/s² (includes gravity)
|
|
287
|
+
"""
|
|
288
|
+
return self.get_reading().linear_acceleration
|
|
289
|
+
|
|
290
|
+
def calibrate(self) -> ActionResult:
|
|
291
|
+
"""
|
|
292
|
+
Calibrate IMU (set current orientation as reference).
|
|
293
|
+
|
|
294
|
+
Robot should be stationary and level.
|
|
295
|
+
"""
|
|
296
|
+
return ActionResult.error("Calibration not supported")
|
|
297
|
+
|
|
298
|
+
def get_frame_id(self) -> str:
|
|
299
|
+
"""Get coordinate frame ID for this IMU."""
|
|
300
|
+
return "imu"
|
|
301
|
+
|
|
302
|
+
|
|
303
|
+
class ForceTorqueInterface(ABC):
|
|
304
|
+
"""
|
|
305
|
+
Interface for force-torque sensors.
|
|
306
|
+
|
|
307
|
+
Typically mounted at wrist (between arm and gripper).
|
|
308
|
+
"""
|
|
309
|
+
|
|
310
|
+
@abstractmethod
|
|
311
|
+
def get_reading(self) -> ForceTorqueReading:
|
|
312
|
+
"""
|
|
313
|
+
Get current force-torque reading.
|
|
314
|
+
|
|
315
|
+
Returns:
|
|
316
|
+
ForceTorqueReading with force and torque vectors
|
|
317
|
+
"""
|
|
318
|
+
pass
|
|
319
|
+
|
|
320
|
+
@abstractmethod
|
|
321
|
+
def get_force(self) -> Vector3:
|
|
322
|
+
"""
|
|
323
|
+
Get force vector.
|
|
324
|
+
|
|
325
|
+
Returns:
|
|
326
|
+
Vector3 in Newtons
|
|
327
|
+
"""
|
|
328
|
+
pass
|
|
329
|
+
|
|
330
|
+
@abstractmethod
|
|
331
|
+
def get_torque(self) -> Vector3:
|
|
332
|
+
"""
|
|
333
|
+
Get torque vector.
|
|
334
|
+
|
|
335
|
+
Returns:
|
|
336
|
+
Vector3 in Nm
|
|
337
|
+
"""
|
|
338
|
+
pass
|
|
339
|
+
|
|
340
|
+
def zero(self) -> ActionResult:
|
|
341
|
+
"""
|
|
342
|
+
Zero the sensor (set current reading as offset).
|
|
343
|
+
|
|
344
|
+
Call when no external load is applied.
|
|
345
|
+
"""
|
|
346
|
+
return ActionResult.error("Zeroing not supported")
|
|
347
|
+
|
|
348
|
+
def get_frame_id(self) -> str:
|
|
349
|
+
"""Get coordinate frame ID for this sensor."""
|
|
350
|
+
return "ft_sensor"
|
|
351
|
+
|
|
352
|
+
def is_in_contact(self, threshold: float = 1.0) -> bool:
|
|
353
|
+
"""
|
|
354
|
+
Check if sensor detects contact.
|
|
355
|
+
|
|
356
|
+
Args:
|
|
357
|
+
threshold: Force threshold in Newtons
|
|
358
|
+
|
|
359
|
+
Returns:
|
|
360
|
+
True if force magnitude exceeds threshold
|
|
361
|
+
"""
|
|
362
|
+
return self.get_force().magnitude() > threshold
|