Spaces:
Running on Zero
Running on Zero
Download embodied_gen/scripts/affordance_annot/gen_grasp.py from HorizonRobotics/EmbodiedGen-Texture-Gen: direct link, hf CLI and curl.
- Browser
- Download file 28 kB
-
https://huggingface.co/spaces/HorizonRobotics/EmbodiedGen-Texture-Gen/resolve/main/embodied_gen/scripts/affordance_annot/gen_grasp.py
- Command line
-
hf download hf://spaces/HorizonRobotics/EmbodiedGen-Texture-Gen/embodied_gen/scripts/affordance_annot/gen_grasp.py
-
curl -L -o gen_grasp.py https://huggingface.co/spaces/HorizonRobotics/EmbodiedGen-Texture-Gen/resolve/main/embodied_gen/scripts/affordance_annot/gen_grasp.py
28 kB
| # Project EmbodiedGen | |
| # | |
| # Copyright (c) 2025 Horizon Robotics. All Rights Reserved. | |
| # | |
| # Licensed under the Apache License, Version 2.0 (the "License"); | |
| # you may not use this file except in compliance with the License. | |
| # You may obtain a copy of the License at | |
| # | |
| # http://www.apache.org/licenses/LICENSE-2.0 | |
| # | |
| # Unless required by applicable law or agreed to in writing, software | |
| # distributed under the License is distributed on an "AS IS" BASIS, | |
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or | |
| # implied. See the License for the specific language governing | |
| # permissions and limitations under the License. | |
| import os | |
| import sys | |
| import time | |
| from dataclasses import dataclass, field | |
| from typing import Literal | |
| from embodied_gen.utils.general import filter_warnings | |
| filter_warnings() | |
| import numpy as np | |
| import trimesh | |
| import trimesh.transformations as tra | |
| import tyro | |
| import yaml | |
| sys.path.append(".") | |
| from embodied_gen.utils.geometry import ( | |
| sample_surface_points, | |
| ) | |
| from embodied_gen.utils.io_utils import ( | |
| URDFFile, | |
| load_json, | |
| load_mesh, | |
| write_json, | |
| ) | |
| from embodied_gen.utils.log import logger | |
| from embodied_gen.utils.vis_utils import show_grasps | |
| __all__ = [ | |
| "GraspGenHyperConfig", | |
| "GraspGenXHyperConfig", | |
| "GraspPoseConfig", | |
| "GraspGenerator", | |
| "run_grasp", | |
| "entrypoint", | |
| ] | |
| FINGER_CENTER_CACHE: dict[str, np.ndarray] = { | |
| "franka_panda": np.array([0.0, 0.0, 0.1034], dtype=np.float64), | |
| } | |
| GRIPPER_GEOMETRY_CACHE: dict[str, dict[str, float]] = { | |
| "franka_panda": { | |
| "width": 0.10537486, | |
| "depth": 0.10527314, | |
| }, | |
| } | |
| class GraspGenHyperConfig: | |
| gripper_config: str = ( | |
| "~/.cache/huggingface/hub/GraspGenModels/checkpoints/graspgen_{}.yml" | |
| ) | |
| grasp_threshold: float = -1.0 | |
| num_grasps: int = 4000 | |
| return_topk: bool = True | |
| topk_num_grasps: int = 4000 | |
| mesh_scale: tuple[float, float, float] = (1.0, 1.0, 1.0) | |
| num_sample_points: int = 2000 | |
| min_grasps: int = 40 | |
| max_tries: int = 6 | |
| remove_outliers: bool = False | |
| class GraspGenXHyperConfig: | |
| checkpoint_root: str = "~/.cache/huggingface/hub/GraspGenXModels/release" | |
| gen_pth: str | None = None | |
| dis_pth: str | None = None | |
| assets_dir: str = ( | |
| "thirdparty/gripper_descriptions/gripper_descriptions/assets" | |
| ) | |
| grasp_threshold: float = -1.0 | |
| num_grasps: int = 4000 | |
| return_topk: bool = True | |
| topk_num_grasps: int = 4000 | |
| mesh_scale: tuple[float, float, float] = (1.0, 1.0, 1.0) | |
| num_sample_points: int = 2000 | |
| min_grasps: int = 40 | |
| max_tries: int = 6 | |
| remove_outliers: bool = False | |
| class GraspPoseConfig: | |
| urdf_paths: list[str] = field(default_factory=list) | |
| model_name: str = "GraspGen" | |
| gripper_name: str = "franka_panda" | |
| mesh_type: Literal["collision", "visual"] = "collision" | |
| update_urdf: bool = True | |
| overwrite: bool = True | |
| gripper_mesh_path: str = ( | |
| f"embodied_gen/scripts/affordance_annot/{gripper_name}/coll_mesh.obj" | |
| ) | |
| grasps_per_part: int = 20 | |
| visualize_grasps: bool = False | |
| visualizer_port: int = 8080 | |
| graspgen: GraspGenHyperConfig | None = None | |
| class GraspGenerator: | |
| def __init__(self, cfg: GraspPoseConfig): | |
| self.cfg = cfg | |
| self.grasp_visualizer = None | |
| self.validate_config(cfg) | |
| self.modules = self.load_model() | |
| self.pipeline = self.build_sampler() | |
| self.finger_center = FINGER_CENTER_CACHE.get( | |
| self.cfg.gripper_name, | |
| np.array([0.0, 0.0, 0.0], dtype=np.float64), | |
| ) | |
| ( | |
| self.local_contact_points, | |
| self.local_closing_directions, | |
| ) = self.build_local_contact_samples() | |
| def validate_config(self, cfg: GraspPoseConfig) -> None: | |
| cfg.model_name = cfg.model_name.lower() | |
| if cfg.model_name not in {"graspgen", "graspgenx"}: | |
| raise ValueError( | |
| "unsupported model '{}', available models: GraspGen, GraspGenX".format( | |
| cfg.model_name | |
| ) | |
| ) | |
| cfg.graspgen = ( | |
| GraspGenHyperConfig() | |
| if cfg.model_name == "graspgen" | |
| else GraspGenXHyperConfig() | |
| ) | |
| if cfg.model_name == "graspgen": | |
| cfg.graspgen.gripper_config = cfg.graspgen.gripper_config.format( | |
| cfg.gripper_name | |
| ) | |
| def load_model(self): | |
| if self.cfg.model_name == "graspgen": | |
| sys.path.append("thirdparty/GraspGen") | |
| from grasp_gen.grasp_server import GraspGenSampler, load_grasp_cfg | |
| return { | |
| "GraspGenSampler": GraspGenSampler, | |
| "model_cfg": load_grasp_cfg, | |
| } | |
| if self.cfg.model_name == "graspgenx": | |
| sys.path.append("thirdparty/GraspGenX") | |
| from graspgenx.grasp_server import GraspGenXSampler | |
| from graspgenx.utils.checkpoint_io import load_model_cfg | |
| return { | |
| "GraspGenSampler": GraspGenXSampler, | |
| "model_cfg": load_model_cfg, | |
| } | |
| def _extract_checkpoint_filenames(yml_path: str) -> list[str]: | |
| """Recursively collect all `checkpoint: <name>.pth` entries. | |
| Walks a GraspGen config yaml so we know every weight file it | |
| requires. | |
| """ | |
| with open(yml_path) as f: | |
| cfg = yaml.safe_load(f) | |
| names: set[str] = set() | |
| def _walk(node) -> None: | |
| if isinstance(node, dict): | |
| for key, value in node.items(): | |
| if ( | |
| key == "checkpoint" | |
| and isinstance(value, str) | |
| and value.strip() | |
| ): | |
| names.add(value.strip()) | |
| else: | |
| _walk(value) | |
| elif isinstance(node, list): | |
| for item in node: | |
| _walk(item) | |
| _walk(cfg) | |
| return sorted(names) | |
| def _dir_has_pth(directory: str, filename: str | None) -> bool: | |
| if filename: | |
| return os.path.exists(os.path.join(directory, filename)) | |
| return os.path.isdir(directory) and any( | |
| f.endswith(".pth") for f in os.listdir(directory) | |
| ) | |
| def _ensure_checkpoints( | |
| self, | |
| is_ready: callable, | |
| repo_id: str, | |
| allow_patterns: str, | |
| download_dir: str, | |
| desc: str, | |
| ) -> None: | |
| """Download `desc` weights from `repo_id` unless already ready. | |
| Re-checks afterwards so a stale/partial local cache (e.g. config | |
| present but a referenced .pth missing) is retried instead of | |
| silently failing deep inside third-party code. | |
| """ | |
| if is_ready(): | |
| return | |
| logger.info( | |
| f"Downloading {desc} weights from Hugging Face. " | |
| "First-time use may take several minutes; please wait patiently." | |
| ) | |
| from huggingface_hub import snapshot_download | |
| snapshot_download( | |
| repo_id=repo_id, | |
| allow_patterns=allow_patterns, | |
| local_dir=os.path.expanduser(download_dir), | |
| ) | |
| if not is_ready(): | |
| raise FileNotFoundError( | |
| f"{desc} checkpoint(s) still missing after downloading from " | |
| f"Hugging Face repo '{repo_id}'. The local cache may be from " | |
| "a previous partial download; please remove it and retry, " | |
| "or download manually." | |
| ) | |
| def build_sampler(self): | |
| if self.cfg.model_name == "graspgen": | |
| gripper_config = os.path.expanduser( | |
| self.cfg.graspgen.gripper_config | |
| ) | |
| checkpoints_dir = os.path.dirname(gripper_config) | |
| def _graspgen_ready() -> bool: | |
| if not os.path.exists(gripper_config): | |
| return False | |
| required = self._extract_checkpoint_filenames(gripper_config) | |
| return all( | |
| os.path.exists(os.path.join(checkpoints_dir, name)) | |
| for name in required | |
| ) | |
| self._ensure_checkpoints( | |
| _graspgen_ready, | |
| "adithyamurali/GraspGenModels", | |
| "checkpoints/*", | |
| "~/.cache/huggingface/hub/GraspGenModels", | |
| "GraspGen", | |
| ) | |
| grasp_cfg = self.modules["model_cfg"](gripper_config) | |
| return self.modules["GraspGenSampler"](grasp_cfg) | |
| if self.cfg.model_name == "graspgenx": | |
| checkpoint_root = os.path.expanduser( | |
| self.cfg.graspgen.checkpoint_root | |
| ) | |
| gen_dir = os.path.join(checkpoint_root, "gen") | |
| dis_dir = os.path.join(checkpoint_root, "dis") | |
| def _graspgenx_ready() -> bool: | |
| return ( | |
| os.path.exists(os.path.join(gen_dir, "config.yaml")) | |
| and self._dir_has_pth(gen_dir, self.cfg.graspgen.gen_pth) | |
| and self._dir_has_pth(dis_dir, self.cfg.graspgen.dis_pth) | |
| ) | |
| self._ensure_checkpoints( | |
| _graspgenx_ready, | |
| "adithyamurali/GraspGenXModel", | |
| "release/*", | |
| "~/.cache/huggingface/hub/GraspGenXModels", | |
| "GraspGenX", | |
| ) | |
| model_cfg = self.modules["model_cfg"]( | |
| os.path.join(checkpoint_root, "gen"), | |
| os.path.join(checkpoint_root, "dis"), | |
| self.cfg.graspgen.gen_pth, | |
| self.cfg.graspgen.dis_pth, | |
| ) | |
| return self.modules["GraspGenSampler"]( | |
| model_cfg, | |
| self.cfg.gripper_name, | |
| assets_dir=self.cfg.graspgen.assets_dir, | |
| ) | |
| def update_urdf(self, urdf_path: str | None, grasp_path: str) -> None: | |
| if urdf_path is None or not self.cfg.update_urdf: | |
| return | |
| URDFFile(urdf_path).write( | |
| { | |
| "custom_data/affordance/affordance_annot": os.path.relpath( | |
| grasp_path, | |
| os.path.dirname(urdf_path), | |
| ), | |
| } | |
| ) | |
| def get_part_seg_mesh_transform(self, urdf: URDFFile) -> dict: | |
| return { | |
| "origin_xyz": urdf.read( | |
| ".//custom_data/affordance/visual_seg/origin", | |
| attr="xyz", | |
| default="0 0 0", | |
| ), | |
| "origin_rpy": urdf.read( | |
| ".//custom_data/affordance/visual_seg/origin", | |
| attr="rpy", | |
| default="0 0 0", | |
| ), | |
| "scale": urdf.read( | |
| ".//custom_data/affordance/visual_seg/geometry/mesh", | |
| attr="scale", | |
| default="1.0 1.0 1.0", | |
| ), | |
| } | |
| def add_grasps_to_affordance( | |
| self, | |
| affordance_payload: dict, | |
| grasps: np.ndarray, | |
| confidences: np.ndarray, | |
| part_grasp_ids: dict[int, list[int]] | None, | |
| ) -> None: | |
| for affordance in affordance_payload.get("affordances", []): | |
| if ( | |
| not isinstance(affordance, dict) | |
| or affordance.get("id") is None | |
| ): | |
| continue | |
| affordance.pop("grasps", None) | |
| affordance.pop("confidences", None) | |
| grasp_ids = part_grasp_ids.get(int(affordance["id"]), []) | |
| grasp_group = {} | |
| for grasp_idx in grasp_ids: | |
| grasp = grasps[grasp_idx] | |
| quaternion = tra.quaternion_from_matrix(grasp[:3, :3]) | |
| grasp_group[f"grasp_{grasp_idx}"] = { | |
| "confidence": float(confidences[grasp_idx]), | |
| "position": grasp[:3, 3].tolist(), | |
| "orientation": { | |
| "w": float(quaternion[0]), | |
| "xyz": quaternion[1:].tolist(), | |
| }, | |
| } | |
| affordance["grasp_group"] = grasp_group | |
| def infer_grasps( | |
| self, | |
| mesh: trimesh.Trimesh, | |
| mesh_path: str, | |
| ) -> tuple[np.ndarray, np.ndarray, np.ndarray] | None: | |
| points = sample_surface_points( | |
| mesh, self.cfg.graspgen.num_sample_points | |
| ) | |
| center_transform = tra.translation_matrix(-points.mean(axis=0)) | |
| centered_points = tra.transform_points( | |
| points, center_transform | |
| ).astype(np.float32) | |
| topk_limit = ( | |
| self.cfg.graspgen.topk_num_grasps | |
| if self.cfg.graspgen.return_topk | |
| else -1 | |
| ) | |
| grasps, confidences = self.modules["GraspGenSampler"].run_inference( | |
| centered_points, | |
| self.pipeline, | |
| grasp_threshold=self.cfg.graspgen.grasp_threshold, | |
| num_grasps=self.cfg.graspgen.num_grasps, | |
| topk_num_grasps=topk_limit, | |
| min_grasps=self.cfg.graspgen.min_grasps, | |
| max_tries=self.cfg.graspgen.max_tries, | |
| remove_outliers=self.cfg.graspgen.remove_outliers, | |
| ) | |
| confidences = confidences.detach().cpu().numpy() | |
| grasps = grasps.detach().cpu().numpy() | |
| if len(grasps) == 0: | |
| logger.error(f"No grasp poses generated for mesh: {mesh_path}") | |
| return None | |
| grasps = np.asarray( | |
| [tra.inverse_matrix(center_transform) @ grasp for grasp in grasps] | |
| ) | |
| return grasps, confidences | |
| def get_grasp_centers( | |
| self, | |
| grasps: np.ndarray, | |
| ) -> np.ndarray: | |
| local_centers = np.repeat( | |
| np.asarray(self.finger_center, dtype=np.float64)[None, :], | |
| len(grasps), | |
| axis=0, | |
| ) | |
| grasp_center_points = ( | |
| np.einsum( | |
| "nij,nj->ni", | |
| grasps[:, :3, :3], | |
| local_centers, | |
| ) | |
| + grasps[:, :3, 3] | |
| ) | |
| return grasp_center_points | |
| def build_local_contact_samples(self) -> tuple[np.ndarray, np.ndarray]: | |
| geometry = GRIPPER_GEOMETRY_CACHE.get(self.cfg.gripper_name) | |
| if geometry is None: | |
| return ( | |
| np.asarray(self.finger_center, dtype=np.float64)[None, :], | |
| np.array([[1.0, 0.0, 0.0]], dtype=np.float64), | |
| ) | |
| half_width = float(geometry["width"]) / 2.0 | |
| depth = float(geometry["depth"]) | |
| z_values = np.linspace(depth - 0.015, depth, 6, dtype=np.float64) | |
| points = [] | |
| directions = [] | |
| for z_value in z_values: | |
| points.extend( | |
| [ | |
| [-half_width, 0.0, z_value], | |
| [half_width, 0.0, z_value], | |
| ] | |
| ) | |
| directions.extend( | |
| [ | |
| [1.0, 0.0, 0.0], | |
| [-1.0, 0.0, 0.0], | |
| ] | |
| ) | |
| return ( | |
| np.asarray(points, dtype=np.float64), | |
| np.asarray(directions, dtype=np.float64), | |
| ) | |
| def get_grasp_contact_rays( | |
| self, | |
| grasps: np.ndarray, | |
| ) -> tuple[np.ndarray, np.ndarray]: | |
| local_contact_points = np.repeat( | |
| self.local_contact_points[None, :, :], | |
| len(grasps), | |
| axis=0, | |
| ) | |
| local_closing_directions = np.repeat( | |
| self.local_closing_directions[None, :, :], | |
| len(grasps), | |
| axis=0, | |
| ) | |
| ray_origins = ( | |
| np.einsum( | |
| "nij,nkj->nki", | |
| grasps[:, :3, :3], | |
| local_contact_points, | |
| ) | |
| + grasps[:, None, :3, 3] | |
| ) | |
| ray_directions = np.einsum( | |
| "nij,nkj->nki", | |
| grasps[:, :3, :3], | |
| local_closing_directions, | |
| ) | |
| ray_norms = np.linalg.norm(ray_directions, axis=-1, keepdims=True) | |
| ray_directions = np.divide( | |
| ray_directions, | |
| ray_norms, | |
| out=np.zeros_like(ray_directions), | |
| where=ray_norms > 0.0, | |
| ) | |
| return ray_origins, ray_directions | |
| def bind_contact_rays_to_faces( | |
| self, | |
| part_mesh: trimesh.Trimesh, | |
| ray_origins: np.ndarray, | |
| ray_directions: np.ndarray, | |
| ) -> np.ndarray: | |
| ray_origins = np.asarray(ray_origins, dtype=np.float64) | |
| ray_directions = np.asarray(ray_directions, dtype=np.float64) | |
| if ( | |
| not isinstance(part_mesh, trimesh.Trimesh) | |
| or len(part_mesh.faces) == 0 | |
| ): | |
| return np.full(len(ray_origins), -1, dtype=np.int64) | |
| try: | |
| face_ids = part_mesh.ray.intersects_first( | |
| ray_origins, | |
| ray_directions, | |
| ) | |
| return np.asarray(face_ids, dtype=np.int64) | |
| except Exception as exc: | |
| logger.warning( | |
| "Failed to raycast grasp closing directions to part mesh: " | |
| f"{exc}" | |
| ) | |
| return np.full(len(ray_origins), -1, dtype=np.int64) | |
| def classify_grasps_by_contact( | |
| self, | |
| grasps: np.ndarray, | |
| part_mesh: trimesh.Trimesh, | |
| face_ids: np.ndarray | None, | |
| ) -> tuple[np.ndarray | None, np.ndarray | None]: | |
| if face_ids is None: | |
| return None, None | |
| face_ids = np.asarray(face_ids, dtype=np.int64) | |
| if len(face_ids) != len(part_mesh.faces): | |
| raise ValueError( | |
| "face_ids length must match segmentation mesh faces, got " | |
| f"{len(face_ids)} and {len(part_mesh.faces)}" | |
| ) | |
| ray_origins, ray_directions = self.get_grasp_contact_rays(grasps) | |
| num_grasps, num_contact_points, _ = ray_origins.shape | |
| logger.info( | |
| "Binding {} grasp closing rays to {} part faces.".format( | |
| num_grasps * num_contact_points, | |
| len(part_mesh.faces), | |
| ) | |
| ) | |
| nearest_face_ids = self.bind_contact_rays_to_faces( | |
| part_mesh, | |
| ray_origins.reshape(-1, 3), | |
| ray_directions.reshape(-1, 3), | |
| ).reshape(num_grasps, num_contact_points) | |
| contact_part_ids = self.parts_for_faces( | |
| nearest_face_ids.reshape(-1), | |
| face_ids, | |
| ).reshape(num_grasps, num_contact_points) | |
| grasp_part_ids = np.full(num_grasps, -1, dtype=np.int64) | |
| contact_scores = np.zeros(num_grasps, dtype=np.float64) | |
| for grasp_idx, part_ids in enumerate(contact_part_ids): | |
| valid_part_ids = part_ids[part_ids >= 0] | |
| if len(valid_part_ids) == 0: | |
| continue | |
| unique_ids, counts = np.unique( | |
| valid_part_ids, | |
| return_counts=True, | |
| ) | |
| best_idx = int(np.argmax(counts)) | |
| grasp_part_ids[grasp_idx] = int(unique_ids[best_idx]) | |
| contact_scores[grasp_idx] = float(counts[best_idx]) / float( | |
| num_contact_points | |
| ) | |
| return grasp_part_ids, contact_scores | |
| def parts_for_faces( | |
| self, | |
| nearest_face_ids: np.ndarray, | |
| face_ids: np.ndarray | None, | |
| ) -> np.ndarray | None: | |
| if face_ids is None: | |
| return None | |
| nearest_face_ids = np.asarray(nearest_face_ids, dtype=np.int64) | |
| grasp_part_ids = np.full(len(nearest_face_ids), -1, dtype=np.int64) | |
| valid_mask = (nearest_face_ids >= 0) & ( | |
| nearest_face_ids < len(face_ids) | |
| ) | |
| grasp_part_ids[valid_mask] = face_ids[nearest_face_ids[valid_mask]] | |
| return grasp_part_ids | |
| def pick_top_grasps( | |
| self, | |
| grasps: np.ndarray, | |
| confidences: np.ndarray, | |
| part_ids: np.ndarray | None, | |
| graspable_part_ids: set[int] | None = None, | |
| part_scores: np.ndarray | None = None, | |
| ) -> tuple[ | |
| np.ndarray, np.ndarray, np.ndarray, dict[int, list[int]] | None | |
| ]: | |
| if part_ids is None: | |
| return ( | |
| grasps, | |
| confidences, | |
| np.arange(len(grasps), dtype=np.int64), | |
| None, | |
| ) | |
| part_ids = np.asarray(part_ids, dtype=np.int64) | |
| selected_indices = [] | |
| selected_part_ids = [] | |
| for part_id in sorted( | |
| int(pid) for pid in np.unique(part_ids[part_ids >= 0]) | |
| ): | |
| if ( | |
| graspable_part_ids is not None | |
| and part_id not in graspable_part_ids | |
| ): | |
| continue | |
| part_indices = np.flatnonzero(part_ids == part_id) | |
| if part_scores is None: | |
| rank = np.argsort( | |
| -confidences[part_indices], | |
| kind="mergesort", | |
| ) | |
| else: | |
| rank = np.lexsort( | |
| ( | |
| -confidences[part_indices], | |
| -part_scores[part_indices], | |
| ) | |
| ) | |
| ranked_indices = part_indices[rank] | |
| if len(ranked_indices) > self.cfg.grasps_per_part: | |
| selected = ranked_indices[: self.cfg.grasps_per_part] | |
| else: | |
| selected = ranked_indices | |
| selected_indices.extend(int(idx) for idx in selected) | |
| selected_part_ids.extend([part_id] * len(selected)) | |
| selected_indices = np.asarray(selected_indices, dtype=np.int64) | |
| if len(selected_indices) == 0: | |
| return ( | |
| grasps[:0], | |
| confidences[:0], | |
| selected_indices, | |
| {}, | |
| ) | |
| part_grasp_ids: dict[int, list[int]] = {} | |
| for new_idx, part_id in enumerate(selected_part_ids): | |
| part_grasp_ids.setdefault(part_id, []).append(new_idx) | |
| return ( | |
| grasps[selected_indices], | |
| confidences[selected_indices], | |
| selected_indices, | |
| part_grasp_ids, | |
| ) | |
| def select_grasps( | |
| self, | |
| grasps: np.ndarray, | |
| confidences: np.ndarray, | |
| part_mesh: trimesh.Trimesh, | |
| face_ids: np.ndarray, | |
| affordance_payload: dict, | |
| ) -> tuple[np.ndarray, np.ndarray, np.ndarray, dict[int, list[int]]]: | |
| original_count = len(grasps) | |
| # Raycast from fingertip samples along the gripper closing direction. | |
| grasp_center_points = self.get_grasp_centers(grasps) | |
| grasp_part_ids, contact_scores = self.classify_grasps_by_contact( | |
| grasps, | |
| part_mesh, | |
| face_ids, | |
| ) | |
| affordances = affordance_payload.get("affordances", []) | |
| graspable_part_ids = { | |
| int(affordance["id"]) | |
| for affordance in affordances | |
| if isinstance(affordance, dict) | |
| and affordance.get("id") is not None | |
| and affordance.get("graspable") is True | |
| } | |
| # Select top grasps per part | |
| grasps, confidences, selected_indices, part_grasp_ids = ( | |
| self.pick_top_grasps( | |
| grasps, | |
| confidences, | |
| grasp_part_ids, | |
| graspable_part_ids, | |
| contact_scores, | |
| ) | |
| ) | |
| grasp_center_points = grasp_center_points[selected_indices] | |
| if len(grasps) == 0: | |
| logger.info( | |
| "Selected per-part grasps: {} -> 0 grasps across 0 parts".format( | |
| original_count, | |
| ) | |
| ) | |
| return grasps, confidences, grasp_center_points, part_grasp_ids | |
| logger.info( | |
| "Selected per-part grasps: {} -> {} grasps across {} parts, confidences range from {:.4f} to {:.4f}".format( | |
| original_count, | |
| len(grasps), | |
| len(part_grasp_ids), | |
| np.min(confidences), | |
| np.max(confidences), | |
| ) | |
| ) | |
| return grasps, confidences, grasp_center_points, part_grasp_ids | |
| def process( | |
| self, | |
| urdf_path: str, | |
| ) -> bool: | |
| try: | |
| return self._process_impl(urdf_path) | |
| except Exception as exc: | |
| logger.error( | |
| "Grasp generation failed for URDF {}: {}".format( | |
| urdf_path, | |
| exc, | |
| ) | |
| ) | |
| return False | |
| def _process_impl( | |
| self, | |
| urdf_path: str, | |
| ) -> bool: | |
| urdf = URDFFile(urdf_path) | |
| mesh_path = urdf.get_mesh_path(self.cfg.mesh_type) | |
| mesh_transform = urdf.get_mesh_transform(self.cfg.mesh_type) | |
| seg_mesh_path = urdf.get_mesh_part_seg_path() | |
| seg_mesh_transform = self.get_part_seg_mesh_transform(urdf) | |
| affordance_path = urdf.get_affordance_annot_path() | |
| affordance_payload = load_json(affordance_path) | |
| if not self.cfg.overwrite: | |
| has_existing_grasp_group = any( | |
| isinstance(affordance, dict) and "grasp_group" in affordance | |
| for affordance in affordance_payload.get("affordances", []) | |
| ) | |
| if has_existing_grasp_group: | |
| logger.info( | |
| f"Skip existing grasp poses in affordance JSON: {affordance_path}" | |
| ) | |
| return True | |
| logger.info(f"Generating grasps for mesh: {mesh_path}") | |
| mesh = load_mesh( | |
| mesh_path, | |
| apply_origin=True, | |
| apply_scale=True, | |
| **mesh_transform, | |
| ) | |
| seg_mesh, face_ids = load_mesh( | |
| seg_mesh_path, | |
| apply_origin=True, | |
| apply_scale=True, | |
| **seg_mesh_transform, | |
| ) | |
| grasp_result = self.infer_grasps(mesh, mesh_path) | |
| if grasp_result is None: | |
| return False | |
| grasps, confidences = grasp_result | |
| grasps, confidences, grasp_center_points, part_grasp_ids = ( | |
| self.select_grasps( | |
| grasps, | |
| confidences, | |
| seg_mesh, | |
| face_ids, | |
| affordance_payload, | |
| ) | |
| ) | |
| self.add_grasps_to_affordance( | |
| affordance_payload, | |
| grasps, | |
| confidences, | |
| part_grasp_ids, | |
| ) | |
| write_json(affordance_payload, affordance_path) | |
| logger.info(f"Saved predicted grasp poses to {affordance_path}") | |
| self.update_urdf(urdf_path, affordance_path) | |
| logger.info( | |
| "Grasp generation succeeded: {} -> {} ({} grasps)".format( | |
| mesh_path, | |
| affordance_path, | |
| len(grasps), | |
| ) | |
| ) | |
| if self.cfg.visualize_grasps: | |
| gripper_mesh = load_mesh(self.cfg.gripper_mesh_path) | |
| self.grasp_visualizer = show_grasps( | |
| mesh, | |
| grasps, | |
| confidences, | |
| self.cfg.gripper_name, | |
| self.cfg.model_name, | |
| grasp_center_points=grasp_center_points, | |
| visualizer=self.grasp_visualizer, | |
| visualizer_port=self.cfg.visualizer_port, | |
| gripper_info=self.pipeline.get_gripper_info() | |
| if self.cfg.model_name == "graspgenx" | |
| else None, | |
| gripper_mesh=gripper_mesh, | |
| ) | |
| return True | |
| def run_grasp(cfg: GraspPoseConfig) -> None: | |
| generator = GraspGenerator(cfg) | |
| for urdf_path in cfg.urdf_paths: | |
| generator.process(urdf_path) | |
| if cfg.visualize_grasps and generator.grasp_visualizer is not None: | |
| logger.info( | |
| "Keeping grasp visualization alive at http://localhost:{} " | |
| "(press Ctrl+C to exit).".format(cfg.visualizer_port) | |
| ) | |
| try: | |
| while True: | |
| time.sleep(1) | |
| except KeyboardInterrupt: | |
| logger.info("Stopping grasp visualization.") | |
| def entrypoint(*args, **kwargs) -> None: | |
| cfg = tyro.cli(GraspPoseConfig) | |
| run_grasp(cfg) | |
| if __name__ == "__main__": | |
| entrypoint() | |