foodforthought-cli 0.2.7__py3-none-any.whl → 0.3.0__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.
Files changed (131) hide show
  1. ate/__init__.py +6 -0
  2. ate/__main__.py +16 -0
  3. ate/auth/__init__.py +1 -0
  4. ate/auth/device_flow.py +141 -0
  5. ate/auth/token_store.py +96 -0
  6. ate/behaviors/__init__.py +100 -0
  7. ate/behaviors/approach.py +399 -0
  8. ate/behaviors/common.py +686 -0
  9. ate/behaviors/tree.py +454 -0
  10. ate/cli.py +855 -3995
  11. ate/client.py +90 -0
  12. ate/commands/__init__.py +168 -0
  13. ate/commands/auth.py +389 -0
  14. ate/commands/bridge.py +448 -0
  15. ate/commands/data.py +185 -0
  16. ate/commands/deps.py +111 -0
  17. ate/commands/generate.py +384 -0
  18. ate/commands/memory.py +907 -0
  19. ate/commands/parts.py +166 -0
  20. ate/commands/primitive.py +399 -0
  21. ate/commands/protocol.py +288 -0
  22. ate/commands/recording.py +524 -0
  23. ate/commands/repo.py +154 -0
  24. ate/commands/simulation.py +291 -0
  25. ate/commands/skill.py +303 -0
  26. ate/commands/skills.py +487 -0
  27. ate/commands/team.py +147 -0
  28. ate/commands/workflow.py +271 -0
  29. ate/detection/__init__.py +38 -0
  30. ate/detection/base.py +142 -0
  31. ate/detection/color_detector.py +399 -0
  32. ate/detection/trash_detector.py +322 -0
  33. ate/drivers/__init__.py +39 -0
  34. ate/drivers/ble_transport.py +405 -0
  35. ate/drivers/mechdog.py +942 -0
  36. ate/drivers/wifi_camera.py +477 -0
  37. ate/interfaces/__init__.py +187 -0
  38. ate/interfaces/base.py +273 -0
  39. ate/interfaces/body.py +267 -0
  40. ate/interfaces/detection.py +282 -0
  41. ate/interfaces/locomotion.py +422 -0
  42. ate/interfaces/manipulation.py +408 -0
  43. ate/interfaces/navigation.py +389 -0
  44. ate/interfaces/perception.py +362 -0
  45. ate/interfaces/sensors.py +247 -0
  46. ate/interfaces/types.py +371 -0
  47. ate/llm_proxy.py +239 -0
  48. ate/mcp_server.py +387 -0
  49. ate/memory/__init__.py +35 -0
  50. ate/memory/cloud.py +244 -0
  51. ate/memory/context.py +269 -0
  52. ate/memory/embeddings.py +184 -0
  53. ate/memory/export.py +26 -0
  54. ate/memory/merge.py +146 -0
  55. ate/memory/migrate/__init__.py +34 -0
  56. ate/memory/migrate/base.py +89 -0
  57. ate/memory/migrate/pipeline.py +189 -0
  58. ate/memory/migrate/sources/__init__.py +13 -0
  59. ate/memory/migrate/sources/chroma.py +170 -0
  60. ate/memory/migrate/sources/pinecone.py +120 -0
  61. ate/memory/migrate/sources/qdrant.py +110 -0
  62. ate/memory/migrate/sources/weaviate.py +160 -0
  63. ate/memory/reranker.py +353 -0
  64. ate/memory/search.py +26 -0
  65. ate/memory/store.py +548 -0
  66. ate/recording/__init__.py +83 -0
  67. ate/recording/demonstration.py +378 -0
  68. ate/recording/session.py +415 -0
  69. ate/recording/upload.py +304 -0
  70. ate/recording/visual.py +416 -0
  71. ate/recording/wrapper.py +95 -0
  72. ate/robot/__init__.py +221 -0
  73. ate/robot/agentic_servo.py +856 -0
  74. ate/robot/behaviors.py +493 -0
  75. ate/robot/ble_capture.py +1000 -0
  76. ate/robot/ble_enumerate.py +506 -0
  77. ate/robot/calibration.py +668 -0
  78. ate/robot/calibration_state.py +388 -0
  79. ate/robot/commands.py +3735 -0
  80. ate/robot/direction_calibration.py +554 -0
  81. ate/robot/discovery.py +441 -0
  82. ate/robot/introspection.py +330 -0
  83. ate/robot/llm_system_id.py +654 -0
  84. ate/robot/locomotion_calibration.py +508 -0
  85. ate/robot/manager.py +270 -0
  86. ate/robot/marker_generator.py +611 -0
  87. ate/robot/perception.py +502 -0
  88. ate/robot/primitives.py +614 -0
  89. ate/robot/profiles.py +281 -0
  90. ate/robot/registry.py +322 -0
  91. ate/robot/servo_mapper.py +1153 -0
  92. ate/robot/skill_upload.py +675 -0
  93. ate/robot/target_calibration.py +500 -0
  94. ate/robot/teach.py +515 -0
  95. ate/robot/types.py +242 -0
  96. ate/robot/visual_labeler.py +1048 -0
  97. ate/robot/visual_servo_loop.py +494 -0
  98. ate/robot/visual_servoing.py +570 -0
  99. ate/robot/visual_system_id.py +906 -0
  100. ate/transports/__init__.py +121 -0
  101. ate/transports/base.py +394 -0
  102. ate/transports/ble.py +405 -0
  103. ate/transports/hybrid.py +444 -0
  104. ate/transports/serial.py +345 -0
  105. ate/urdf/__init__.py +30 -0
  106. ate/urdf/capture.py +582 -0
  107. ate/urdf/cloud.py +491 -0
  108. ate/urdf/collision.py +271 -0
  109. ate/urdf/commands.py +708 -0
  110. ate/urdf/depth.py +360 -0
  111. ate/urdf/inertial.py +312 -0
  112. ate/urdf/kinematics.py +330 -0
  113. ate/urdf/lifting.py +415 -0
  114. ate/urdf/meshing.py +300 -0
  115. ate/urdf/models/__init__.py +110 -0
  116. ate/urdf/models/depth_anything.py +253 -0
  117. ate/urdf/models/sam2.py +324 -0
  118. ate/urdf/motion_analysis.py +396 -0
  119. ate/urdf/pipeline.py +468 -0
  120. ate/urdf/scale.py +256 -0
  121. ate/urdf/scan_session.py +411 -0
  122. ate/urdf/segmentation.py +299 -0
  123. ate/urdf/synthesis.py +319 -0
  124. ate/urdf/topology.py +336 -0
  125. ate/urdf/validation.py +371 -0
  126. {foodforthought_cli-0.2.7.dist-info → foodforthought_cli-0.3.0.dist-info}/METADATA +9 -1
  127. foodforthought_cli-0.3.0.dist-info/RECORD +166 -0
  128. {foodforthought_cli-0.2.7.dist-info → foodforthought_cli-0.3.0.dist-info}/WHEEL +1 -1
  129. foodforthought_cli-0.2.7.dist-info/RECORD +0 -44
  130. {foodforthought_cli-0.2.7.dist-info → foodforthought_cli-0.3.0.dist-info}/entry_points.txt +0 -0
  131. {foodforthought_cli-0.2.7.dist-info → foodforthought_cli-0.3.0.dist-info}/top_level.txt +0 -0
ate/urdf/kinematics.py ADDED
@@ -0,0 +1,330 @@
1
+ """
2
+ Joint parameter estimation for URDF generation.
3
+
4
+ This module handles Phase 3 of the pipeline:
5
+ 1. Fit kinematic parameters to point cloud trajectories
6
+ 2. Estimate joint axes, pivots, and limits
7
+ 3. Generate kinematics.json output
8
+
9
+ Implements the core kinematic optimization described in the research.
10
+ """
11
+
12
+ import logging
13
+ from typing import Dict, List, Optional, Tuple, Set
14
+ from pathlib import Path
15
+ from dataclasses import dataclass, asdict
16
+
17
+ logger = logging.getLogger(__name__)
18
+
19
+ try:
20
+ import numpy as np
21
+ NUMPY_AVAILABLE = True
22
+ except ImportError:
23
+ NUMPY_AVAILABLE = False
24
+ np = None
25
+
26
+
27
+ class KinematicsError(Exception):
28
+ """Error during kinematic estimation."""
29
+ pass
30
+
31
+
32
+ @dataclass
33
+ class JointParameters:
34
+ """Estimated parameters for a single joint."""
35
+ name: str
36
+ parent_link: str
37
+ child_link: str
38
+ joint_type: str # revolute, prismatic, fixed
39
+ axis: List[float] # [x, y, z] unit vector
40
+ origin: List[float] # [x, y, z] position
41
+ limits: Dict[str, float] # lower, upper (radians or meters)
42
+ confidence: float # Estimation confidence [0, 1]
43
+
44
+ def to_dict(self) -> Dict:
45
+ return {
46
+ "name": self.name,
47
+ "parent_link": self.parent_link,
48
+ "child_link": self.child_link,
49
+ "type": self.joint_type,
50
+ "axis": self.axis,
51
+ "origin": self.origin,
52
+ "limits": self.limits,
53
+ "confidence": self.confidence,
54
+ }
55
+
56
+
57
+ def load_per_frame_clouds(
58
+ session: "ScanSession",
59
+ masks: Dict[str, Dict[int, "np.ndarray"]],
60
+ depth_maps: Dict[int, "np.ndarray"],
61
+ ) -> Dict[str, List["np.ndarray"]]:
62
+ """
63
+ Load per-frame point clouds for each link.
64
+
65
+ Args:
66
+ session: ScanSession
67
+ masks: Per-frame segmentation masks
68
+ depth_maps: Per-frame depth maps
69
+
70
+ Returns:
71
+ Dict mapping link_name -> list of point clouds per frame
72
+ """
73
+ import cv2
74
+ from .lifting import lift_to_3d
75
+ from .scale import estimate_intrinsics_from_resolution
76
+
77
+ # Get intrinsics
78
+ w, h = session.metadata.resolution
79
+ intrinsics = estimate_intrinsics_from_resolution(w, h)
80
+
81
+ # Open video
82
+ cap = cv2.VideoCapture(str(session.video_path))
83
+ if not cap.isOpened():
84
+ raise KinematicsError(f"Could not open video: {session.video_path}")
85
+
86
+ frame_indices = sorted(depth_maps.keys())
87
+ link_clouds = {name: [] for name in masks.keys()}
88
+
89
+ try:
90
+ for frame_idx in frame_indices:
91
+ cap.set(cv2.CAP_PROP_POS_FRAMES, frame_idx)
92
+ ret, frame = cap.read()
93
+ if not ret:
94
+ for name in masks.keys():
95
+ link_clouds[name].append(None)
96
+ continue
97
+
98
+ rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
99
+ depth = depth_maps[frame_idx]
100
+
101
+ for link_name in masks.keys():
102
+ if frame_idx in masks[link_name]:
103
+ mask = masks[link_name][frame_idx]
104
+ points, _ = lift_to_3d(rgb, depth, mask, intrinsics)
105
+ link_clouds[link_name].append(points if len(points) > 0 else None)
106
+ else:
107
+ link_clouds[link_name].append(None)
108
+
109
+ finally:
110
+ cap.release()
111
+
112
+ return link_clouds
113
+
114
+
115
+ def estimate_joint_parameters(
116
+ parent_traj: "LinkTrajectory",
117
+ child_traj: "LinkTrajectory",
118
+ joint_name: str,
119
+ joint_type_hint: Optional[str] = None,
120
+ ) -> JointParameters:
121
+ """
122
+ Estimate joint parameters between parent and child links.
123
+
124
+ Args:
125
+ parent_traj: Parent link trajectory
126
+ child_traj: Child link trajectory
127
+ joint_name: Name for this joint
128
+ joint_type_hint: Optional hint for joint type
129
+
130
+ Returns:
131
+ JointParameters with estimated values
132
+ """
133
+ from .motion_analysis import (
134
+ compute_relative_motion,
135
+ estimate_revolute_axis,
136
+ estimate_prismatic_axis,
137
+ estimate_joint_limits,
138
+ )
139
+
140
+ # Compute relative motion
141
+ rel_motion = compute_relative_motion(parent_traj, child_traj)
142
+
143
+ # Classify joint type
144
+ if joint_type_hint:
145
+ joint_type = joint_type_hint
146
+ confidence = 0.9 # User-provided hint
147
+ else:
148
+ joint_type, confidence = rel_motion.analyze_joint_type()
149
+
150
+ # Estimate axis and origin
151
+ if joint_type == "revolute":
152
+ axis, pivot, axis_conf = estimate_revolute_axis(rel_motion)
153
+ confidence = min(confidence, axis_conf)
154
+ origin = pivot.tolist()
155
+ elif joint_type == "prismatic":
156
+ axis, axis_conf = estimate_prismatic_axis(rel_motion)
157
+ confidence = min(confidence, axis_conf)
158
+ # Origin at parent centroid
159
+ origin = parent_traj.centroids[0].tolist()
160
+ else: # fixed
161
+ axis = [0.0, 0.0, 1.0]
162
+ origin = child_traj.centroids[0].tolist()
163
+
164
+ # Normalize axis
165
+ axis_array = np.array(axis)
166
+ axis_array = axis_array / (np.linalg.norm(axis_array) + 1e-8)
167
+ axis = axis_array.tolist()
168
+
169
+ # Estimate limits
170
+ if joint_type != "fixed":
171
+ lower, upper = estimate_joint_limits(
172
+ rel_motion, joint_type, np.array(axis), np.array(origin)
173
+ )
174
+ else:
175
+ lower, upper = 0.0, 0.0
176
+
177
+ return JointParameters(
178
+ name=joint_name,
179
+ parent_link=parent_traj.name,
180
+ child_link=child_traj.name,
181
+ joint_type=joint_type,
182
+ axis=axis,
183
+ origin=origin,
184
+ limits={"lower": lower, "upper": upper},
185
+ confidence=confidence,
186
+ )
187
+
188
+
189
+ def run_kinematic_optimization(
190
+ session: "ScanSession",
191
+ masks: Dict[str, Dict[int, "np.ndarray"]],
192
+ depth_maps: Dict[int, "np.ndarray"],
193
+ joint_type_hints: Optional[Dict[str, str]] = None,
194
+ progress_callback: Optional[callable] = None,
195
+ ) -> List[JointParameters]:
196
+ """
197
+ Run kinematic optimization to estimate joint parameters.
198
+
199
+ Args:
200
+ session: ScanSession
201
+ masks: Per-frame segmentation masks
202
+ depth_maps: Per-frame depth maps
203
+ joint_type_hints: Optional dict of link_name -> joint_type
204
+ progress_callback: Optional progress callback
205
+
206
+ Returns:
207
+ List of JointParameters
208
+ """
209
+ from .motion_analysis import extract_trajectories
210
+ from .topology import build_kinematic_tree, infer_joint_names
211
+
212
+ logger.info("Starting kinematic optimization...")
213
+
214
+ # Load per-frame clouds
215
+ logger.info("Loading per-frame point clouds...")
216
+ frame_indices = sorted(depth_maps.keys())
217
+ link_clouds = load_per_frame_clouds(session, masks, depth_maps)
218
+
219
+ # Extract trajectories
220
+ logger.info("Extracting link trajectories...")
221
+ trajectories = extract_trajectories(link_clouds, frame_indices)
222
+
223
+ if len(trajectories) < 2:
224
+ raise KinematicsError(
225
+ f"Need at least 2 links for kinematics, got {len(trajectories)}"
226
+ )
227
+
228
+ # Get fixed link hints from session
229
+ fixed_hints = {
230
+ link.name for link in session.links if link.is_fixed
231
+ }
232
+
233
+ # Build kinematic tree
234
+ logger.info("Building kinematic tree...")
235
+ tree = build_kinematic_tree(trajectories, fixed_hints)
236
+
237
+ # Get joint names
238
+ joint_names = infer_joint_names(tree)
239
+
240
+ # Estimate parameters for each joint
241
+ logger.info("Estimating joint parameters...")
242
+ joints = []
243
+ total = len(joint_names)
244
+
245
+ for i, ((parent, child), joint_name) in enumerate(joint_names.items()):
246
+ parent_traj = trajectories[parent]
247
+ child_traj = trajectories[child]
248
+
249
+ # Get type hint if available
250
+ type_hint = None
251
+ if joint_type_hints and child in joint_type_hints:
252
+ type_hint = joint_type_hints[child]
253
+
254
+ params = estimate_joint_parameters(
255
+ parent_traj, child_traj, joint_name, type_hint
256
+ )
257
+ joints.append(params)
258
+
259
+ logger.info(
260
+ f" {joint_name}: {params.joint_type} "
261
+ f"(conf={params.confidence:.2f}, axis={params.axis})"
262
+ )
263
+
264
+ if progress_callback:
265
+ progress_callback(i + 1, total)
266
+
267
+ # Update session
268
+ from .scan_session import JointInfo
269
+ session.joints = [
270
+ JointInfo(
271
+ name=j.name,
272
+ parent_link=j.parent_link,
273
+ child_link=j.child_link,
274
+ joint_type=j.joint_type,
275
+ axis=j.axis,
276
+ origin=j.origin,
277
+ limits=j.limits,
278
+ )
279
+ for j in joints
280
+ ]
281
+ session.save_kinematics()
282
+ session.metadata.optimize_complete = True
283
+ session.save_metadata()
284
+
285
+ logger.info(f"Kinematic optimization complete: {len(joints)} joints")
286
+ return joints
287
+
288
+
289
+ def save_kinematics_json(
290
+ output_path: Path,
291
+ links: List[str],
292
+ joints: List[JointParameters],
293
+ tree: "KinematicTree",
294
+ ) -> None:
295
+ """
296
+ Save kinematics to JSON file.
297
+
298
+ Args:
299
+ output_path: Path to output file
300
+ links: List of link names
301
+ joints: List of JointParameters
302
+ tree: KinematicTree structure
303
+ """
304
+ import json
305
+
306
+ data = {
307
+ "links": [
308
+ {
309
+ "name": name,
310
+ "parent": tree.get_parent(name),
311
+ }
312
+ for name in tree.get_ordered_links()
313
+ ],
314
+ "joints": [j.to_dict() for j in joints],
315
+ }
316
+
317
+ with open(output_path, 'w') as f:
318
+ json.dump(data, f, indent=2)
319
+
320
+ logger.info(f"Saved kinematics to {output_path}")
321
+
322
+
323
+ __all__ = [
324
+ "KinematicsError",
325
+ "JointParameters",
326
+ "load_per_frame_clouds",
327
+ "estimate_joint_parameters",
328
+ "run_kinematic_optimization",
329
+ "save_kinematics_json",
330
+ ]