griip-sdk 0.1.0__tar.gz

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 (119) hide show
  1. griip_sdk-0.1.0/PKG-INFO +72 -0
  2. griip_sdk-0.1.0/README.md +40 -0
  3. griip_sdk-0.1.0/pyproject.toml +47 -0
  4. griip_sdk-0.1.0/src/griip_sdk/__init__.py +4 -0
  5. griip_sdk-0.1.0/src/griip_sdk/calibration/__init__.py +0 -0
  6. griip_sdk-0.1.0/src/griip_sdk/calibration/cartesian_pose.py +219 -0
  7. griip_sdk-0.1.0/src/griip_sdk/calibration/config_store.py +114 -0
  8. griip_sdk-0.1.0/src/griip_sdk/calibration/hand_eye_calib/README.md +109 -0
  9. griip_sdk-0.1.0/src/griip_sdk/calibration/hand_eye_calib/__init__.py +38 -0
  10. griip_sdk-0.1.0/src/griip_sdk/calibration/hand_eye_calib/config/routine.yaml +9 -0
  11. griip_sdk-0.1.0/src/griip_sdk/calibration/hand_eye_calib/core.py +732 -0
  12. griip_sdk-0.1.0/src/griip_sdk/calibration/hand_eye_calib/main.py +479 -0
  13. griip_sdk-0.1.0/src/griip_sdk/calibration/hand_eye_calib/routines.py +190 -0
  14. griip_sdk-0.1.0/src/griip_sdk/calibration/hand_eye_calib/run_hand_eye_calibration.py +133 -0
  15. griip_sdk-0.1.0/src/griip_sdk/calibration/hand_eye_calib/transforms.py +57 -0
  16. griip_sdk-0.1.0/src/griip_sdk/calibration/scanning/__init__.py +11 -0
  17. griip_sdk-0.1.0/src/griip_sdk/calibration/scanning/environment_scanning.py +275 -0
  18. griip_sdk-0.1.0/src/griip_sdk/calibration/scanning/run_environment_scanning.py +147 -0
  19. griip_sdk-0.1.0/src/griip_sdk/calibration/tests/__init__.py +0 -0
  20. griip_sdk-0.1.0/src/griip_sdk/calibration/tests/conftest.py +33 -0
  21. griip_sdk-0.1.0/src/griip_sdk/calibration/tests/test_environment_scanning.py +378 -0
  22. griip_sdk-0.1.0/src/griip_sdk/calibration/tests/test_hand_eye_calibration_service.py +316 -0
  23. griip_sdk-0.1.0/src/griip_sdk/calibration/tests/test_hand_eye_helpers.py +211 -0
  24. griip_sdk-0.1.0/src/griip_sdk/calibration/tests/test_transforms.py +130 -0
  25. griip_sdk-0.1.0/src/griip_sdk/calibration/units.py +104 -0
  26. griip_sdk-0.1.0/src/griip_sdk/generated/__init__.py +0 -0
  27. griip_sdk-0.1.0/src/griip_sdk/generated/_griip_sdk_pb2_generated.py +86 -0
  28. griip_sdk-0.1.0/src/griip_sdk/generated/griip_sdk_pb2.py +24 -0
  29. griip_sdk-0.1.0/src/griip_sdk/generated/griip_sdk_pb2.pyi +561 -0
  30. griip_sdk-0.1.0/src/griip_sdk/generated/griip_sdk_pb2_grpc.py +515 -0
  31. griip_sdk-0.1.0/src/griip_sdk/grpc_adapters/__init__.py +0 -0
  32. griip_sdk-0.1.0/src/griip_sdk/grpc_adapters/servicer_as_stub.py +125 -0
  33. griip_sdk-0.1.0/src/griip_sdk/hardware/__init__.py +0 -0
  34. griip_sdk-0.1.0/src/griip_sdk/hardware/gripper.py +158 -0
  35. griip_sdk-0.1.0/src/griip_sdk/hardware/mocks/__init__.py +0 -0
  36. griip_sdk-0.1.0/src/griip_sdk/hardware/robot_client.py +1373 -0
  37. griip_sdk-0.1.0/src/griip_sdk/hardware/robotiq_2f140_gripper.py +266 -0
  38. griip_sdk-0.1.0/src/griip_sdk/hardware/tests/__init__.py +0 -0
  39. griip_sdk-0.1.0/src/griip_sdk/hardware/tests/conftest.py +8 -0
  40. griip_sdk-0.1.0/src/griip_sdk/hardware/tests/test_reset_to_normal.py +101 -0
  41. griip_sdk-0.1.0/src/griip_sdk/hardware/tests/test_robot_client_lifecycle.py +134 -0
  42. griip_sdk-0.1.0/src/griip_sdk/modeling/__init__.py +0 -0
  43. griip_sdk-0.1.0/src/griip_sdk/modeling/sphere_generation_tool/README.md +186 -0
  44. griip_sdk-0.1.0/src/griip_sdk/modeling/sphere_generation_tool/__init__.py +8 -0
  45. griip_sdk-0.1.0/src/griip_sdk/modeling/sphere_generation_tool/__main__.py +244 -0
  46. griip_sdk-0.1.0/src/griip_sdk/modeling/sphere_generation_tool/_actors.py +273 -0
  47. griip_sdk-0.1.0/src/griip_sdk/modeling/sphere_generation_tool/_geometry.py +59 -0
  48. griip_sdk-0.1.0/src/griip_sdk/modeling/sphere_generation_tool/editor.py +1143 -0
  49. griip_sdk-0.1.0/src/griip_sdk/modeling/sphere_generation_tool/object_io.py +53 -0
  50. griip_sdk-0.1.0/src/griip_sdk/modeling/sphere_generation_tool/urdf_io.py +312 -0
  51. griip_sdk-0.1.0/src/griip_sdk/modeling/tests/__init__.py +0 -0
  52. griip_sdk-0.1.0/src/griip_sdk/modeling/tests/conftest.py +123 -0
  53. griip_sdk-0.1.0/src/griip_sdk/modeling/tests/test_editor_math.py +79 -0
  54. griip_sdk-0.1.0/src/griip_sdk/modeling/tests/test_editor_smoke.py +469 -0
  55. griip_sdk-0.1.0/src/griip_sdk/modeling/tests/test_mesh_loading.py +66 -0
  56. griip_sdk-0.1.0/src/griip_sdk/modeling/tests/test_object_io.py +72 -0
  57. griip_sdk-0.1.0/src/griip_sdk/modeling/tests/test_urdf_io.py +357 -0
  58. griip_sdk-0.1.0/src/griip_sdk/orchestration/__init__.py +0 -0
  59. griip_sdk-0.1.0/src/griip_sdk/prototyping/__init__.py +0 -0
  60. griip_sdk-0.1.0/src/griip_sdk/prototyping/config/__init__.py +5 -0
  61. griip_sdk-0.1.0/src/griip_sdk/prototyping/config/loader.py +108 -0
  62. griip_sdk-0.1.0/src/griip_sdk/prototyping/config/models.py +269 -0
  63. griip_sdk-0.1.0/src/griip_sdk/prototyping/config/registry.py +16 -0
  64. griip_sdk-0.1.0/src/griip_sdk/prototyping/config/tests/test_loader.py +69 -0
  65. griip_sdk-0.1.0/src/griip_sdk/prototyping/projects/dormakaba/__init__.py +1 -0
  66. griip_sdk-0.1.0/src/griip_sdk/prototyping/projects/dormakaba/cells/bench/bench.yaml +85 -0
  67. griip_sdk-0.1.0/src/griip_sdk/prototyping/projects/dormakaba/cells/bench/collision_cuboids/full.json +257 -0
  68. griip_sdk-0.1.0/src/griip_sdk/prototyping/projects/dormakaba/cells/bench/collision_cuboids/hand_eye.json +122 -0
  69. griip_sdk-0.1.0/src/griip_sdk/prototyping/projects/dormakaba/cells/bench/gripper_collision_spheres/minimal.json +1 -0
  70. griip_sdk-0.1.0/src/griip_sdk/prototyping/projects/dormakaba/cells/bench/hand_eye_routine.yaml +9 -0
  71. griip_sdk-0.1.0/src/griip_sdk/prototyping/projects/dormakaba/cells/bench/io/bench.json +4 -0
  72. griip_sdk-0.1.0/src/griip_sdk/prototyping/projects/mcalpine/__init__.py +0 -0
  73. griip_sdk-0.1.0/src/griip_sdk/prototyping/projects/mcalpine/cells/rnd/collision_cuboids/rnd.json +287 -0
  74. griip_sdk-0.1.0/src/griip_sdk/prototyping/projects/mcalpine/cells/rnd/gripper_collision_spheres/robotiq_2f140.json +4 -0
  75. griip_sdk-0.1.0/src/griip_sdk/prototyping/projects/mcalpine/cells/rnd/gripper_collision_spheres/robotiq_2f140_vp15_closed.json +236 -0
  76. griip_sdk-0.1.0/src/griip_sdk/prototyping/projects/mcalpine/cells/rnd/io/rnd.json +34 -0
  77. griip_sdk-0.1.0/src/griip_sdk/prototyping/projects/mcalpine/cells/rnd/object_spheres/a10v.json +9 -0
  78. griip_sdk-0.1.0/src/griip_sdk/prototyping/projects/mcalpine/cells/rnd/object_spheres/vp15bdy.json +1 -0
  79. griip_sdk-0.1.0/src/griip_sdk/prototyping/projects/mcalpine/cells/rnd/rnd.yaml +259 -0
  80. griip_sdk-0.1.0/src/griip_sdk/prototyping/projects/mcalpine/grasps/__init__.py +0 -0
  81. griip_sdk-0.1.0/src/griip_sdk/prototyping/projects/mcalpine/grasps/a10v.json +11042 -0
  82. griip_sdk-0.1.0/src/griip_sdk/prototyping/projects/mcalpine/grasps/generate_grasps.py +301 -0
  83. griip_sdk-0.1.0/src/griip_sdk/prototyping/projects/mcalpine/grasps/vp15_mcalpine.json +13802 -0
  84. griip_sdk-0.1.0/src/griip_sdk/prototyping/projects/mcalpine/hardware/__init__.py +0 -0
  85. griip_sdk-0.1.0/src/griip_sdk/prototyping/projects/mcalpine/hardware/bin_shaker.py +86 -0
  86. griip_sdk-0.1.0/src/griip_sdk/prototyping/projects/mcalpine/hardware/camera.py +132 -0
  87. griip_sdk-0.1.0/src/griip_sdk/prototyping/projects/mcalpine/hardware/ft_sensor.py +347 -0
  88. griip_sdk-0.1.0/src/griip_sdk/prototyping/projects/mcalpine/hardware/io_client.py +117 -0
  89. griip_sdk-0.1.0/src/griip_sdk/prototyping/projects/mcalpine/hardware/mocks/__init__.py +0 -0
  90. griip_sdk-0.1.0/src/griip_sdk/prototyping/projects/mcalpine/hardware/mocks/camera_mock.py +55 -0
  91. griip_sdk-0.1.0/src/griip_sdk/prototyping/projects/mcalpine/hardware/mocks/gripper_client_mock.py +120 -0
  92. griip_sdk-0.1.0/src/griip_sdk/prototyping/projects/mcalpine/hardware/mocks/io_client_mock.py +28 -0
  93. griip_sdk-0.1.0/src/griip_sdk/prototyping/projects/mcalpine/hardware/mocks/robot_client_mock.py +575 -0
  94. griip_sdk-0.1.0/src/griip_sdk/prototyping/projects/mcalpine/hardware/torquing_client.py +23 -0
  95. griip_sdk-0.1.0/src/griip_sdk/prototyping/projects/mcalpine/orchestrators/__init__.py +0 -0
  96. griip_sdk-0.1.0/src/griip_sdk/prototyping/projects/mcalpine/orchestrators/bin_cuboid_extraction.py +76 -0
  97. griip_sdk-0.1.0/src/griip_sdk/prototyping/projects/mcalpine/orchestrators/callbacks.py +43 -0
  98. griip_sdk-0.1.0/src/griip_sdk/prototyping/projects/mcalpine/orchestrators/errors.py +19 -0
  99. griip_sdk-0.1.0/src/griip_sdk/prototyping/projects/mcalpine/orchestrators/grasp_queue.py +71 -0
  100. griip_sdk-0.1.0/src/griip_sdk/prototyping/projects/mcalpine/orchestrators/pick_and_place_manager.py +1665 -0
  101. griip_sdk-0.1.0/src/griip_sdk/prototyping/projects/mcalpine/orchestrators/roi_scheduler.py +150 -0
  102. griip_sdk-0.1.0/src/griip_sdk/prototyping/projects/mcalpine/orchestrators/tests/__init__.py +0 -0
  103. griip_sdk-0.1.0/src/griip_sdk/prototyping/projects/mcalpine/orchestrators/tests/conftest.py +156 -0
  104. griip_sdk-0.1.0/src/griip_sdk/prototyping/projects/mcalpine/orchestrators/tests/test_pick_and_place_manager.py +507 -0
  105. griip_sdk-0.1.0/src/griip_sdk/prototyping/projects/mcalpine/orchestrators/tests/test_pick_result_adapter.py +148 -0
  106. griip_sdk-0.1.0/src/griip_sdk/prototyping/projects/mcalpine/orchestrators/trajectory_store.py +170 -0
  107. griip_sdk-0.1.0/src/griip_sdk/prototyping/projects/mcalpine/orchestrators/types.py +238 -0
  108. griip_sdk-0.1.0/src/griip_sdk/prototyping/projects/mcalpine/orchestrators/vp15_aligner.py +62 -0
  109. griip_sdk-0.1.0/src/griip_sdk/prototyping/projects/mcalpine/part_ids.py +36 -0
  110. griip_sdk-0.1.0/src/griip_sdk/prototyping/projects/mcalpine/placement_handlers/__init__.py +17 -0
  111. griip_sdk-0.1.0/src/griip_sdk/prototyping/projects/mcalpine/placement_handlers/a10v.py +621 -0
  112. griip_sdk-0.1.0/src/griip_sdk/prototyping/projects/mcalpine/placement_handlers/vp15.py +281 -0
  113. griip_sdk-0.1.0/src/griip_sdk/prototyping/projects/mcalpine/robotiq_2f140_collision_model.py +200 -0
  114. griip_sdk-0.1.0/src/griip_sdk/prototyping/projects/mcalpine/run.py +1074 -0
  115. griip_sdk-0.1.0/src/griip_sdk/prototyping/projects/mcalpine/run_grpc.py +9 -0
  116. griip_sdk-0.1.0/src/griip_sdk/prototyping/projects/mcalpine/tests/__init__.py +0 -0
  117. griip_sdk-0.1.0/src/griip_sdk/prototyping/projects/mcalpine/tests/test_part_ids.py +25 -0
  118. griip_sdk-0.1.0/src/griip_sdk/prototyping/run_environment_scanning.py +93 -0
  119. griip_sdk-0.1.0/src/griip_sdk/prototyping/run_hand_eye_calibration.py +86 -0
@@ -0,0 +1,72 @@
1
+ Metadata-Version: 2.4
2
+ Name: griip-sdk
3
+ Version: 0.1.0
4
+ Summary: GRIIP SDK: hardware control and orchestration for bin picking
5
+ License: Proprietary
6
+ Keywords: Vention,GRIIP,SDK,Robot,Gripper,Automation
7
+ Author: VentionCo
8
+ Requires-Python: >=3.10,<3.11
9
+ Classifier: License :: Other/Proprietary License
10
+ Classifier: Operating System :: POSIX :: Linux
11
+ Classifier: Programming Language :: Python :: 3
12
+ Classifier: Programming Language :: Python :: 3.10
13
+ Requires-Dist: PyYAML (>=6.0,<7.0)
14
+ Requires-Dist: defusedxml (>=0.7.1,<0.8.0)
15
+ Requires-Dist: grpcio (>=1.78.0)
16
+ Requires-Dist: httpx (>=0.28.1,<0.29.0)
17
+ Requires-Dist: numpy (>=1.24)
18
+ Requires-Dist: omegaconf (>=2.3.0,<3.0.0)
19
+ Requires-Dist: opencv-python-headless (>=4.9.0,<5.0.0)
20
+ Requires-Dist: paho-mqtt (>=2.1.0,<3.0.0)
21
+ Requires-Dist: protobuf (>=4.0)
22
+ Requires-Dist: pydantic (>=2.12.5,<3.0.0)
23
+ Requires-Dist: pymodbus (>=3.12.1,<4.0.0)
24
+ Requires-Dist: scipy (>=1.11.0,<2.0)
25
+ Requires-Dist: spatialmath-python (>=1.1.15,<2.0.0)
26
+ Requires-Dist: toppra (>=0.6.0)
27
+ Requires-Dist: trimesh (>=4.11,<5.0)
28
+ Requires-Dist: urchin (>=0.0.30,<0.0.31)
29
+ Requires-Dist: vention-firmware-grpc-client (>=1.0.0)
30
+ Description-Content-Type: text/markdown
31
+
32
+ # griip-sdk
33
+
34
+ Python SDK for hardware control and orchestration of gripper-based bin picking on Vention's MachineMotion platform.
35
+
36
+ ## Installation
37
+
38
+ ```bash
39
+ pip install griip-sdk
40
+ ```
41
+
42
+ ## Prerequisites for hardware control
43
+
44
+ `griip-sdk` depends on two private wheels that are not on PyPI. Install them manually from GitHub Releases before using hardware features:
45
+
46
+ ```bash
47
+ # vamp_planner
48
+ pip install "https://github.com/VentionCo/platform-products/releases/download/vamp-wheel-v0.6.1-20260313/vamp_planner-<version>-cp310-cp310-linux_aarch64.whl"
49
+
50
+ # foundationstereoclient
51
+ pip install "https://github.com/VentionCo/platform-products/releases/download/foundationstereoclient/v1.11.0/foundationstereoclient-<version>-cp310-cp310-linux_aarch64.whl"
52
+ ```
53
+
54
+ > These manual steps are temporary. Both packages will eventually become MachineMotion-side services, at which point this step goes away.
55
+
56
+ ## Quickstart
57
+
58
+ ```python
59
+ from griip_sdk import BaseGripper, GripperMoveResult, Robotiq2F140Gripper
60
+
61
+ gripper = Robotiq2F140Gripper(host="<machineMotion-ip>")
62
+ result: GripperMoveResult = gripper.move(position=0.5)
63
+ ```
64
+
65
+ ## What's NOT shipped on PyPI
66
+
67
+ The demo runners under `griip_sdk.prototyping.run_*` import `mmai_griip_api` (the gRPC service). They are not part of the public API and will fail to import unless `mmai-griip-api` is installed alongside the SDK (e.g. via the Debian package on a MachineMotion).
68
+
69
+ ## Releases
70
+
71
+ Versioning is hand-bumped semver. Bump `version` in `pyproject.toml` before triggering the PyPI publish workflow (`publish-griip-sdk-pypi.yml`).
72
+
@@ -0,0 +1,40 @@
1
+ # griip-sdk
2
+
3
+ Python SDK for hardware control and orchestration of gripper-based bin picking on Vention's MachineMotion platform.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ pip install griip-sdk
9
+ ```
10
+
11
+ ## Prerequisites for hardware control
12
+
13
+ `griip-sdk` depends on two private wheels that are not on PyPI. Install them manually from GitHub Releases before using hardware features:
14
+
15
+ ```bash
16
+ # vamp_planner
17
+ pip install "https://github.com/VentionCo/platform-products/releases/download/vamp-wheel-v0.6.1-20260313/vamp_planner-<version>-cp310-cp310-linux_aarch64.whl"
18
+
19
+ # foundationstereoclient
20
+ pip install "https://github.com/VentionCo/platform-products/releases/download/foundationstereoclient/v1.11.0/foundationstereoclient-<version>-cp310-cp310-linux_aarch64.whl"
21
+ ```
22
+
23
+ > These manual steps are temporary. Both packages will eventually become MachineMotion-side services, at which point this step goes away.
24
+
25
+ ## Quickstart
26
+
27
+ ```python
28
+ from griip_sdk import BaseGripper, GripperMoveResult, Robotiq2F140Gripper
29
+
30
+ gripper = Robotiq2F140Gripper(host="<machineMotion-ip>")
31
+ result: GripperMoveResult = gripper.move(position=0.5)
32
+ ```
33
+
34
+ ## What's NOT shipped on PyPI
35
+
36
+ The demo runners under `griip_sdk.prototyping.run_*` import `mmai_griip_api` (the gRPC service). They are not part of the public API and will fail to import unless `mmai-griip-api` is installed alongside the SDK (e.g. via the Debian package on a MachineMotion).
37
+
38
+ ## Releases
39
+
40
+ Versioning is hand-bumped semver. Bump `version` in `pyproject.toml` before triggering the PyPI publish workflow (`publish-griip-sdk-pypi.yml`).
@@ -0,0 +1,47 @@
1
+ [tool.poetry]
2
+ name = "griip-sdk"
3
+ version = "0.1.0"
4
+ description = "GRIIP SDK: hardware control and orchestration for bin picking"
5
+ authors = ["VentionCo"]
6
+ license = "Proprietary"
7
+ readme = "README.md"
8
+ keywords = ["Vention", "GRIIP", "SDK", "Robot", "Gripper", "Automation"]
9
+ classifiers = [
10
+ "Operating System :: POSIX :: Linux",
11
+ "Programming Language :: Python :: 3.10",
12
+ ]
13
+ packages = [{ include = "griip_sdk", from = "src" }]
14
+
15
+ [tool.poetry.dependencies]
16
+ python = ">=3.10,<3.11"
17
+ grpcio = ">=1.78.0"
18
+ numpy = ">=1.24"
19
+ protobuf = ">=4.0"
20
+ toppra = ">=0.6.0"
21
+ pydantic = "^2.12.5"
22
+ omegaconf = "^2.3.0"
23
+ pymodbus = "^3.12.1"
24
+ urchin = "^0.0.30"
25
+ trimesh = "^4.11"
26
+ defusedxml = "^0.7.1"
27
+ spatialmath-python = "^1.1.15"
28
+ httpx = "^0.28.1"
29
+ paho-mqtt = "^2.1.0"
30
+ opencv-python-headless = "^4.9.0"
31
+ scipy = ">=1.11.0,<2.0"
32
+ PyYAML = "^6.0"
33
+ vention-firmware-grpc-client = ">=1.0.0"
34
+
35
+ [tool.poetry.group.dev.dependencies]
36
+ pytest = "^8.3.4"
37
+ pytest-cov = "6.3.0"
38
+
39
+ [tool.ruff]
40
+ line-length = 140
41
+
42
+ [tool.pytest.ini_options]
43
+ testpaths = ["src"]
44
+
45
+ [build-system]
46
+ requires = ["poetry-core>=2.0.0"]
47
+ build-backend = "poetry.core.masonry.api"
@@ -0,0 +1,4 @@
1
+ from griip_sdk.hardware.gripper import BaseGripper, GripperMoveResult
2
+ from griip_sdk.hardware.robotiq_2f140_gripper import Robotiq2F140Gripper
3
+
4
+ __all__ = ["BaseGripper", "GripperMoveResult", "Robotiq2F140Gripper"]
File without changes
@@ -0,0 +1,219 @@
1
+ from typing import List, Union, Optional
2
+ from griip_sdk.calibration.units import AngleUnit, DistanceUnit, Meters, MetersQuaternions
3
+ from scipy.spatial.transform import Rotation as ScipyRotation # type: ignore
4
+ import numpy as np
5
+ import math
6
+ from dataclasses import dataclass
7
+ from spatialmath import SE3, SO3, UnitQuaternion
8
+
9
+
10
+ NumpyNumber = Union[np.float64, np.float32, np.int64, np.int32]
11
+
12
+
13
+ # for the vision api.
14
+ def to_float_array(items: List[Union[float, NumpyNumber]]) -> List[float]:
15
+ return list(map(lambda a: float(a), items))
16
+
17
+
18
+ @dataclass
19
+ class Point:
20
+ # xyz point
21
+ x: Meters
22
+ y: Meters
23
+ z: Meters
24
+
25
+ @staticmethod
26
+ def from_array(xyz: Union[List[Meters], np.ndarray]) -> "Point":
27
+ return Point(xyz[0], xyz[1], xyz[2])
28
+
29
+ def to_array(self) -> List[Meters]:
30
+ return [self.x, self.y, self.z]
31
+
32
+ def to_vector(self) -> np.ndarray:
33
+ return np.array(self.to_array())
34
+
35
+ # returns a new point, with value x + add_shift.x, y + add_shift.y, ...
36
+ # does not modify existing.
37
+ def translate(self, add_shift: "Point") -> "Point":
38
+ a = self.to_vector()
39
+ b = add_shift.to_vector()
40
+ return Point.from_array(a + b)
41
+
42
+
43
+ class CartesianPose:
44
+ pose: SE3
45
+ position: Point
46
+ rotation: SO3
47
+
48
+ CAMERA_ANGLE_UNIT = AngleUnit.Radians
49
+ CAMERA_DISTANCE_UNIT = DistanceUnit.Meters
50
+
51
+ def __init__(self, position: Point, rotation: SO3):
52
+ if isinstance(rotation, ScipyRotation):
53
+ rotation = SO3(rotation.as_matrix())
54
+ self.pose = SE3.Rt(R=rotation, t=position.to_array())
55
+
56
+ @property
57
+ def position(self) -> Point:
58
+ return Point.from_array(self.pose.t)
59
+
60
+ @property
61
+ def rotation(self) -> SO3:
62
+ return self.pose.R
63
+
64
+ @staticmethod
65
+ def from_SE3(pose: SE3) -> "CartesianPose":
66
+ return CartesianPose(Point.from_array(pose.t), pose.R)
67
+
68
+ def to_SE3(self) -> SE3:
69
+ return SE3(self.to_homogeneous_matrix())
70
+
71
+ def __str__(self):
72
+ return f"CartesianPose({self.pose.strline('rpy/yxz')})"
73
+
74
+ def __repr__(self):
75
+ return str(self)
76
+
77
+ def invert(self) -> SE3:
78
+ return self.pose.inv()
79
+
80
+ def transform_to(self, from_frame: Optional[SE3], to_frame: Optional[SE3]) -> SE3:
81
+ # get homo. matrices.
82
+ bTc = self.pose
83
+ aTb = SE3() # defaults
84
+ cTd = SE3() # defaults.
85
+
86
+ if from_frame is not None:
87
+ aTb = from_frame
88
+ if to_frame is not None:
89
+ cTd = to_frame.inv()
90
+
91
+ # calculate the transformation matrix from the local frame to the target frame
92
+ aTd = aTb @ bTc @ cTd
93
+
94
+ # Convert the resulting transformation matrix to a CartesianPose
95
+ return aTd
96
+
97
+ def to_canonical_frame(self, from_frame: SE3) -> SE3:
98
+ # Transform the position
99
+ return self.transform_to(from_frame, None)
100
+
101
+ @staticmethod
102
+ def from_moveit(position_and_quat: MetersQuaternions) -> SE3:
103
+ # position_and_quat is [x, y, z, qx, qy, qz, qw]
104
+ pos = position_and_quat[:3]
105
+ quat = UnitQuaternion(s=position_and_quat[-1], v=position_and_quat[3:6])
106
+ return SE3.Rt(R=quat.SO3(), t=pos)
107
+
108
+ def to_moveit(self) -> MetersQuaternions:
109
+ pos = self.pose.t
110
+ quat = UnitQuaternion(self.pose.R).vec_xyzs
111
+ return to_float_array(pos + list(quat))
112
+
113
+ @staticmethod
114
+ def from_camera_units(pose_vector: List[Union[Meters, float]]) -> SE3:
115
+ """
116
+ Take a 6 dimensional pose vector in camera units and return a SE3 object.
117
+
118
+ Args:
119
+ pose_vector: A list of 6 elements representing the pose in camera units.
120
+ The first three elements are the position (x, y, z) in meters.
121
+ The last three elements are the rotation vector (rx, ry, rz) in radians or degrees
122
+ depending on the CAMERA_ANGLE_UNIT setting.
123
+ Returns:
124
+ pose: A spatialmath SE3 object representing the pose.
125
+ """
126
+ pos = DistanceUnit.Convert(
127
+ pose_vector[:3],
128
+ from_unit=CartesianPose.CAMERA_DISTANCE_UNIT,
129
+ to_unit=DistanceUnit.Meters,
130
+ )
131
+ units = "deg" if CartesianPose.CAMERA_ANGLE_UNIT == AngleUnit.Degrees else "rad"
132
+
133
+ angle = np.linalg.norm(pose_vector[3:])
134
+ axis = pose_vector[3:] / angle if angle != 0 else np.array([1.0, 0.0, 0.0])
135
+
136
+ return SE3.Rt(R=SO3.AngleAxis(angle, axis, unit=units), t=pos)
137
+
138
+ def to_camera_units(self) -> List[Union[Meters, float]]:
139
+ """
140
+ Convert the CartesianPose to a 6 dimensional pose vector in camera units.
141
+
142
+ Returns:
143
+ pose_vector: A list of 6 floats representing the pose in camera units.
144
+ The first three elements are the position (x, y, z) in meters.
145
+ The last three elements are the rotation vector (rx, ry, rz) in radians or degrees
146
+ depending on the CAMERA_ANGLE_UNIT setting.
147
+ """
148
+ rot_vec, pos_vec = self.pose.rtvec()
149
+ if CartesianPose.CAMERA_ANGLE_UNIT == AngleUnit.Degrees:
150
+ rot_vec = np.degrees(rot_vec)
151
+
152
+ return to_float_array(list(pos_vec) + list(rot_vec))
153
+
154
+ @staticmethod
155
+ def from_homogeneous_matrix(homogeneous_matrix: np.ndarray) -> SE3:
156
+ return SE3(homogeneous_matrix)
157
+
158
+ def to_homogeneous_matrix(self) -> np.ndarray:
159
+ return self.pose.A
160
+
161
+ def diff(self, other: SE3):
162
+ """
163
+ Compute the rotational and translational difference between this pose and another pose.
164
+ Args:
165
+ other: Another CartesianPose to compare against.
166
+ Returns:
167
+ rotation_error_rad: The rotational difference in radians [0, pi].
168
+
169
+ translation_error: The translational difference in meters.
170
+ """
171
+ # Angular distance [0, pi] between two rotations.
172
+ rotation_error_rad = self.pose.angdist(other)
173
+ translation_error = np.linalg.norm(self.pose.t - other.t)
174
+ return rotation_error_rad, translation_error
175
+
176
+
177
+ if __name__ == "__main__":
178
+ import math
179
+
180
+ def assert_float_array_eq(a1: List[float], a2: List[float]) -> None:
181
+ assert len(a1) == len(a1)
182
+ for i in range(len(a1)):
183
+ a, b = a1[i], a2[i]
184
+ assert math.isclose(a, b, rel_tol=1e-8)
185
+
186
+ def test_moveit_pose_conversions(move_it_pose: MetersQuaternions) -> None:
187
+ p = CartesianPose.from_moveit(move_it_pose)
188
+ camera_units_pose = p.to_camera_units()
189
+ p2 = CartesianPose.from_camera_units(camera_units_pose)
190
+ assert_float_array_eq(move_it_pose, p2.to_moveit())
191
+
192
+ def test_camera_pose_conversions(camera_units_pose: List[float]) -> None:
193
+ p = CartesianPose.from_camera_units(camera_units_pose)
194
+ move_it_pose = p.to_moveit()
195
+ p2 = CartesianPose.from_moveit(move_it_pose)
196
+ assert_float_array_eq(camera_units_pose, p2.to_camera_units())
197
+
198
+ test_camera_pose_conversions(
199
+ camera_units_pose=[
200
+ -0.36732380688872235,
201
+ 0.31711499960934564,
202
+ -0.10696901055083409,
203
+ 2.711732123419181,
204
+ 1.505353005792903,
205
+ -0.07767478350482122,
206
+ ]
207
+ )
208
+
209
+ test_moveit_pose_conversions(
210
+ move_it_pose=[
211
+ -0.366833055335579,
212
+ 0.32453574184240414,
213
+ -0.10696901055083409,
214
+ 0.8738757958661387,
215
+ 0.48511117474910254,
216
+ -0.025031275275235965,
217
+ 0.01953655012953711,
218
+ ]
219
+ )
@@ -0,0 +1,114 @@
1
+ """File-based configuration store for calibration data.
2
+
3
+ Reads versioned JSON files from /data/vention/calib_data/ (or a custom root).
4
+ """
5
+
6
+ from __future__ import annotations
7
+
8
+ import hashlib
9
+ import json
10
+ import logging
11
+ import os
12
+ import tempfile
13
+ import time
14
+ from dataclasses import dataclass
15
+ from pathlib import Path
16
+ from typing import Any
17
+
18
+ logger = logging.getLogger(__name__)
19
+
20
+ DEFAULT_CALIBRATION_DATA_PATH = "/data/vention/calib_data"
21
+
22
+
23
+ def _utc_ts() -> str:
24
+ return time.strftime("%Y-%m-%dT%H-%M-%SZ", time.gmtime())
25
+
26
+
27
+ def _digest_bytes(data: bytes) -> str:
28
+ return hashlib.sha256(data).hexdigest()[:16]
29
+
30
+
31
+ def _atomic_write(path: Path, data: bytes) -> None:
32
+ path.parent.mkdir(parents=True, exist_ok=True)
33
+ with tempfile.NamedTemporaryFile(dir=path.parent, delete=False) as tmp:
34
+ tmp.write(data)
35
+ tmp.flush()
36
+ tmp_name = tmp.name
37
+ Path(tmp_name).replace(path)
38
+ os.chmod(path, 0o664)
39
+
40
+
41
+ @dataclass(frozen=True)
42
+ class VersionInfo:
43
+ name: str
44
+ version: str
45
+ path: Path
46
+ size: int
47
+
48
+
49
+ class CalibrationConfigStore:
50
+ """Reads versioned JSON calibration files from a directory tree.
51
+
52
+ File layout:
53
+ root/
54
+ X_cam_flg__luxonis__18443010D1A8F71200__left/
55
+ 2025-09-11T17-03-41Z__abcd1234.json
56
+ calibio__luxonis__18443010D1A8F71200/
57
+ 2025-09-11T17-03-41Z__deadbeef.json
58
+ """
59
+
60
+ def __init__(self, root: Path | str = DEFAULT_CALIBRATION_DATA_PATH) -> None:
61
+ self.root = Path(root)
62
+
63
+ def format_filename(self, version: str, digest: str) -> str:
64
+ return f"{version}__{digest}.json"
65
+
66
+ @staticmethod
67
+ def format_config_name(*parts: str) -> str:
68
+ return "__".join(parts)
69
+
70
+ def add_new_version(self, name: str, data: Any) -> VersionInfo:
71
+ version = _utc_ts()
72
+ folder = self.root / name
73
+ payload = (json.dumps(data, indent=2, sort_keys=True) + "\n").encode("utf-8")
74
+ digest = _digest_bytes(payload)
75
+ filename = self.format_filename(version, digest)
76
+ path = folder / filename
77
+ if not path.exists():
78
+ _atomic_write(path, payload)
79
+ return VersionInfo(name=name, version=filename, path=path, size=path.stat().st_size)
80
+
81
+ def update_version(self, version_info: VersionInfo, data: Any) -> VersionInfo:
82
+ payload = (json.dumps(data, indent=2, sort_keys=True) + "\n").encode("utf-8")
83
+ _atomic_write(version_info.path, payload)
84
+ return VersionInfo(
85
+ name=version_info.name,
86
+ version=version_info.version,
87
+ path=version_info.path,
88
+ size=version_info.path.stat().st_size,
89
+ )
90
+
91
+ def load_latest(self, name: str) -> Any:
92
+ info = self._latest_info(name)
93
+ logger.info("Calibration store: loading latest '%s' from %s", name, info.path)
94
+ return self._load_path(info.path)
95
+
96
+ def list_versions(self, name: str) -> list[VersionInfo]:
97
+ folder = self.root / name
98
+ if not folder.exists():
99
+ return []
100
+ items = []
101
+ for json_path in sorted(folder.glob("*.json"), key=lambda path: path.name, reverse=True):
102
+ if json_path.is_file():
103
+ items.append(VersionInfo(name=name, version=json_path.name, path=json_path, size=json_path.stat().st_size))
104
+ return items
105
+
106
+ def _latest_info(self, name: str) -> VersionInfo:
107
+ versions = self.list_versions(name)
108
+ if not versions:
109
+ raise FileNotFoundError(f"No calibration versions found for '{name}' in {self.root / name}")
110
+ return versions[0]
111
+
112
+ def _load_path(self, path: Path) -> Any:
113
+ with path.open("r", encoding="utf-8") as file_handle:
114
+ return json.load(file_handle)
@@ -0,0 +1,109 @@
1
+ # Hand-Eye Calibration Module
2
+
3
+ This module performs hand-eye calibration for robot-camera systems.
4
+
5
+ ## HTTP API Usage
6
+
7
+ The calibration is exposed via an HTTP API.
8
+ Configuration is loaded from `configs/robot_cells/<robot_cell>.yaml` which contains
9
+ all robot, planner, and service URLs.
10
+
11
+ ### Calibrate
12
+
13
+ ```bash
14
+ POST /calibration/hand-eye/calibrate
15
+ Content-Type: application/json
16
+
17
+ {
18
+ "robot_cell": "vp15_mcalpine",
19
+ "calibration_routine": "mcalpine_cell_routine.yaml",
20
+ "cam_name": "luxonis",
21
+ "sensor": "left"
22
+ }
23
+ ```
24
+
25
+ ### Evaluate
26
+
27
+ ```bash
28
+ POST /calibration/hand-eye/evaluate
29
+ Content-Type: application/json
30
+
31
+ {
32
+ "robot_cell": "vp15_mcalpine",
33
+ "calibration_routine": "mcalpine_cell_routine.yaml"
34
+ }
35
+ ```
36
+
37
+ ### Status
38
+
39
+ ```bash
40
+ GET /calibration/hand-eye/status
41
+ ```
42
+
43
+ ### Teach Mode (Set Home Position)
44
+
45
+ Use teach mode to manually position the robot and save a new home position:
46
+
47
+ ```bash
48
+ # 1. Start teach mode (enables freedrive)
49
+ POST /calibration/hand-eye/teach/start
50
+ Content-Type: application/json
51
+
52
+ {
53
+ "robot_cell": "vp15_mcalpine",
54
+ "calibration_routine": "mcalpine_cell_routine.yaml"
55
+ }
56
+
57
+ # 2. Manually move the robot to desired home position...
58
+
59
+ # 3. Check current position (optional)
60
+ GET /calibration/hand-eye/teach/status
61
+
62
+ # 4. Save the position and exit freedrive
63
+ POST /calibration/hand-eye/teach/save
64
+
65
+ # Or cancel without saving
66
+ POST /calibration/hand-eye/teach/cancel
67
+ ```
68
+
69
+ ## Configuration
70
+
71
+ The `robot_cell` parameter should match a config file in `configs/robot_cells/`:
72
+ - `vp15_mcalpine` loads `configs/robot_cells/vp15_mcalpine.yaml`
73
+ - `a10v_mcalpine` loads `configs/robot_cells/a10v_mcalpine.yaml`
74
+
75
+ These configs include:
76
+ - Robot settings (server_url, robot_ip, robot_model, cuboids)
77
+ - Service URLs (curobo planner, vision server)
78
+ - Motion planning settings (use_vamp)
79
+ - Payload and gripper configuration
80
+
81
+ ## Requirements
82
+
83
+ - Robot must be connected and VRMCS driver running
84
+ - Motion planner server running (URL from config)
85
+ - MMAI Vision camera server running (URL from config)
86
+ - Charuco calibration board visible to camera
87
+
88
+ ## Python API Usage
89
+
90
+ ```python
91
+ from griip_sdk.calibration.hand_eye_calib import HandEyeCalibrationService
92
+ from griip_sdk.grpc_adapters.servicer_as_stub import ServicerAsStub
93
+
94
+ calibrator = HandEyeCalibrationService(
95
+ robo=robot_client,
96
+ cam=camera_client,
97
+ store=config_store,
98
+ planning_stub=ServicerAsStub(planning_service),
99
+ config=calibration_routine_dict,
100
+ cam_intr=camera_intrinsics,
101
+ dist_coeffs=distortion_coefficients,
102
+ im_width=image_width,
103
+ im_height=image_height,
104
+ sensor_name="left",
105
+ save_dir="/data/calibration",
106
+ )
107
+
108
+ X_cam_flg = calibrator.calibrate()
109
+ ```
@@ -0,0 +1,38 @@
1
+ from griip_sdk.calibration.hand_eye_calib.core import (
2
+ Routine,
3
+ WaypointGenerator,
4
+ Waypoint,
5
+ project_points,
6
+ draw_points,
7
+ points_in_image,
8
+ invert_pose,
9
+ generate_camera_pose_on_sphere,
10
+ )
11
+ from griip_sdk.calibration.hand_eye_calib.routines import (
12
+ BootstrapRoutine,
13
+ SphereRoutine,
14
+ )
15
+
16
+ from griip_sdk.calibration.hand_eye_calib.main import (
17
+ HandEyeCalibrationService,
18
+ camera_vector_to_se3,
19
+ se3_to_camera_vector,
20
+ intrinsic_dict_to_arrays,
21
+ )
22
+
23
+ __all__ = [
24
+ "HandEyeCalibrationService",
25
+ "Routine",
26
+ "WaypointGenerator",
27
+ "Waypoint",
28
+ "BootstrapRoutine",
29
+ "SphereRoutine",
30
+ "project_points",
31
+ "draw_points",
32
+ "points_in_image",
33
+ "invert_pose",
34
+ "generate_camera_pose_on_sphere",
35
+ "camera_vector_to_se3",
36
+ "se3_to_camera_vector",
37
+ "intrinsic_dict_to_arrays",
38
+ ]
@@ -0,0 +1,9 @@
1
+ # elevation, z rotation, radius
2
+ calib_encircle:
3
+ - [70, -80, 0.57]
4
+ - [80, -70, 0.6]
5
+ - [70, 60, 0.64]
6
+ - [60, 0, 0.6]
7
+ - [50, 0, 0.6]
8
+
9
+ eval_encircle: [70, 0, 0.6]