nbv_sim/src/active_grasp/simulation.py

301 lines
10 KiB
Python
Raw Normal View History

2021-05-26 14:46:12 +02:00
from pathlib import Path
2021-04-26 20:10:52 +02:00
import pybullet as p
2021-07-22 11:05:30 +02:00
import pybullet_data
2021-05-26 14:46:12 +02:00
import rospkg
2021-04-26 20:10:52 +02:00
2021-07-22 11:05:30 +02:00
from active_grasp.bbox import AABBox
from robot_helpers.bullet import *
2021-12-22 12:12:09 +01:00
from robot_helpers.io import load_yaml
2021-09-12 00:21:58 +02:00
from robot_helpers.model import KDLModel
2021-09-07 22:29:42 +02:00
from robot_helpers.spatial import Rotation
2021-11-09 11:53:16 +01:00
from vgn.perception import UniformTSDFVolume
2021-12-22 12:12:09 +01:00
from vgn.utils import find_urdfs, view_on_sphere
2021-11-09 11:53:16 +01:00
from vgn.detection import VGN, select_local_maxima
2021-11-11 10:39:57 +01:00
# import vgn.visualizer as vis
2021-09-07 22:29:42 +02:00
rospack = rospkg.RosPack()
2021-10-27 14:52:07 +02:00
pkg_root = Path(rospack.get_path("active_grasp"))
2022-01-14 14:17:38 +01:00
urdfs_dir = pkg_root / "assets"
2021-09-14 11:58:33 +02:00
2021-04-26 20:10:52 +02:00
2021-07-22 11:05:30 +02:00
class Simulation:
2021-09-07 22:29:42 +02:00
"""Robot is placed s.t. world and base frames are the same"""
2021-11-09 11:53:16 +01:00
def __init__(self, gui, scene_id, vgn_path):
2021-07-22 11:05:30 +02:00
self.configure_physics_engine(gui, 60, 4)
2021-07-09 15:53:34 +02:00
self.configure_visualizer()
2021-11-04 16:48:25 +01:00
self.seed()
2021-07-06 14:00:04 +02:00
self.load_robot()
2021-11-09 11:53:16 +01:00
self.load_vgn(Path(vgn_path))
2021-09-07 22:29:42 +02:00
self.scene = get_scene(scene_id)
2021-07-22 11:05:30 +02:00
def configure_physics_engine(self, gui, rate, sub_step_count):
self.rate = rate
self.dt = 1.0 / self.rate
p.connect(p.GUI if gui else p.DIRECT)
p.setAdditionalSearchPath(pybullet_data.getDataPath())
p.setPhysicsEngineParameter(fixedTimeStep=self.dt, numSubSteps=sub_step_count)
p.setGravity(0.0, 0.0, -9.81)
2021-05-26 14:46:12 +02:00
2021-07-09 15:53:34 +02:00
def configure_visualizer(self):
# p.configureDebugVisualizer(p.COV_ENABLE_GUI, 0)
2021-09-07 22:29:42 +02:00
p.resetDebugVisualizerCamera(1.2, 30, -30, [0.4, 0.0, 0.2])
2021-05-26 14:46:12 +02:00
2021-11-04 16:48:25 +01:00
def seed(self, seed=None):
self.rng = np.random.default_rng(seed) if seed else np.random
2021-07-06 14:00:04 +02:00
def load_robot(self):
2021-12-22 12:12:09 +01:00
panda_urdf_path = urdfs_dir / "franka/panda_arm_hand.urdf"
2021-09-12 00:21:58 +02:00
self.arm = BtPandaArm(panda_urdf_path)
2021-04-27 11:45:57 +02:00
self.gripper = BtPandaGripper(self.arm)
2021-09-12 00:21:58 +02:00
self.model = KDLModel.from_urdf_file(
panda_urdf_path, self.arm.base_frame, self.arm.ee_frame
)
2021-09-10 23:29:15 +02:00
self.camera = BtCamera(320, 240, 0.96, 0.01, 1.0, self.arm.uid, 11)
2021-04-26 20:10:52 +02:00
2021-11-09 11:53:16 +01:00
def load_vgn(self, model_path):
self.vgn = VGN(model_path)
2021-04-26 20:10:52 +02:00
def reset(self):
2024-10-19 13:57:56 -05:00
# Wait until the simulation is stable
secs = 3.0
sleep_ticks = self.rate * secs
for i in range(int(sleep_ticks)):
p.stepSimulation()
# Reset the scene
2021-11-09 11:53:16 +01:00
valid = False
while not valid:
self.set_arm_configuration([0.0, -1.39, 0.0, -2.36, 0.0, 1.57, 0.79])
self.scene.clear()
q = self.scene.generate(self.rng)
self.set_arm_configuration(q)
uid = self.select_target()
2024-11-03 01:15:25 -05:00
support_id = self.select_support()
2021-11-09 11:53:16 +01:00
bbox = self.get_target_bbox(uid)
2024-10-19 13:57:56 -05:00
valid = True
# valid = self.check_for_grasps(bbox)
2021-09-07 22:29:42 +02:00
return bbox
2021-07-22 11:05:30 +02:00
def set_arm_configuration(self, q):
2021-04-26 20:10:52 +02:00
for i, q_i in enumerate(q):
p.resetJointState(self.arm.uid, i, q_i, 0)
2021-07-06 14:00:04 +02:00
p.resetJointState(self.arm.uid, 9, 0.04, 0)
p.resetJointState(self.arm.uid, 10, 0.04, 0)
2021-10-12 19:42:37 +02:00
self.gripper.set_desired_width(0.4)
2021-07-06 14:00:04 +02:00
2021-09-07 22:29:42 +02:00
def select_target(self):
_, _, mask = self.camera.get_image()
uids, counts = np.unique(mask, return_counts=True)
mask = np.isin(uids, self.scene.object_uids) # remove ids of the floor, etc
uids, counts = uids[mask], counts[mask]
2024-10-19 13:57:56 -05:00
# If no objects are detected, try again
if(len(uids) == 0 and len(counts) == 0):
return self.select_target()
2021-09-07 22:29:42 +02:00
target_uid = uids[np.argmin(counts)]
p.changeVisualShape(target_uid, -1, rgbaColor=[1, 0, 0, 1])
return target_uid
2024-11-03 01:15:25 -05:00
def select_support(self):
support_id = self.scene.support_uid
return support_id
2021-09-07 22:29:42 +02:00
def get_target_bbox(self, uid):
aabb_min, aabb_max = p.getAABB(uid)
# enlarge the bounding box
aabb_min = np.asarray(aabb_min)
aabb_max = np.asarray(aabb_max)
aabb_min -= 0.0
aabb_max += 0.0
2021-09-07 22:29:42 +02:00
return AABBox(aabb_min, aabb_max)
2021-11-09 11:53:16 +01:00
def check_for_grasps(self, bbox):
2022-01-11 11:19:21 +01:00
origin = Transform.from_translation(self.scene.origin)
2021-11-09 11:53:16 +01:00
origin.translation[2] -= 0.05
2022-01-11 11:19:21 +01:00
center = Transform.from_translation(self.scene.center)
2021-11-09 11:53:16 +01:00
# First, reconstruct the scene from many views
tsdf = UniformTSDFVolume(self.scene.length, 40)
r = 2.0 * self.scene.length
2021-11-10 12:15:33 +01:00
theta = np.pi / 4.0
2021-11-09 11:53:16 +01:00
phis = np.linspace(0.0, 2.0 * np.pi, 5)
for view in [view_on_sphere(center, r, theta, phi) for phi in phis]:
depth_img = self.camera.get_image(view)[1]
tsdf.integrate(depth_img, self.camera.intrinsic, view.inv() * origin)
voxel_size, tsdf_grid = tsdf.voxel_size, tsdf.get_grid()
# Then check whether VGN can find any grasps on the target
out = self.vgn.predict(tsdf_grid)
2021-11-10 12:15:33 +01:00
grasps, qualities = select_local_maxima(voxel_size, out, threshold=0.9)
# vis.scene_cloud(voxel_size, tsdf.get_scene_cloud())
# vis.grasps(grasps, qualities, 0.05)
# vis.show()
2021-11-09 11:53:16 +01:00
for grasp in grasps:
pose = origin * grasp.pose
tip = pose.rotation.apply([0, 0, 0.05]) + pose.translation
if bbox.is_inside(tip):
return True
return False
2021-09-07 22:29:42 +02:00
def step(self):
p.stepSimulation()
class Scene:
def __init__(self):
2021-12-22 12:12:09 +01:00
self.support_urdf = urdfs_dir / "plane/model.urdf"
2021-09-07 22:29:42 +02:00
self.support_uid = -1
self.object_uids = []
2021-11-04 16:48:25 +01:00
def clear(self):
self.remove_support()
self.remove_all_objects()
def generate(self, rng):
raise NotImplementedError
def add_support(self, pos):
2021-09-13 18:29:41 +02:00
self.support_uid = p.loadURDF(str(self.support_urdf), pos, globalScaling=0.3)
2024-11-03 01:15:25 -05:00
print('support id: '+str(self.support_uid))
2021-09-07 22:29:42 +02:00
def remove_support(self):
2021-09-08 16:50:53 +02:00
p.removeBody(self.support_uid)
2021-09-07 22:29:42 +02:00
2021-11-04 16:48:25 +01:00
def add_object(self, urdf, ori, pos, scale=1.0):
2021-07-06 14:00:04 +02:00
uid = p.loadURDF(str(urdf), pos, ori.as_quat(), globalScaling=scale)
self.object_uids.append(uid)
return uid
def remove_object(self, uid):
p.removeBody(uid)
self.object_uids.remove(uid)
def remove_all_objects(self):
for uid in list(self.object_uids):
self.remove_object(uid)
2021-09-07 22:29:42 +02:00
2021-11-04 16:48:25 +01:00
class YamlScene(Scene):
def __init__(self, config_name):
super().__init__()
2021-12-03 11:49:50 +01:00
self.config_path = pkg_root / "cfg/sim" / config_name
2021-11-04 16:48:25 +01:00
def load_config(self):
2021-12-22 12:12:09 +01:00
self.scene = load_yaml(self.config_path)
2021-11-04 16:48:25 +01:00
self.center = np.asarray(self.scene["center"])
self.length = 0.3
self.origin = self.center - np.r_[0.5 * self.length, 0.5 * self.length, 0.0]
def generate(self, rng):
self.load_config()
self.add_support(self.center)
for object in self.scene["objects"]:
2021-12-22 12:12:09 +01:00
urdf = urdfs_dir / object["object_id"] / "model.urdf"
2021-11-04 16:48:25 +01:00
ori = Rotation.from_euler("xyz", object["rpy"], degrees=True)
pos = self.center + np.asarray(object["xyz"])
scale = object.get("scale", 1)
if randomize := object.get("randomize", False):
angle = rng.uniform(-randomize["rot"], randomize["rot"])
2021-11-10 14:52:48 +01:00
ori = Rotation.from_euler("z", angle, degrees=True) * ori
b = np.asarray(randomize["pos"])
pos += rng.uniform(-b, b)
2021-11-04 16:48:25 +01:00
self.add_object(urdf, ori, pos, scale)
for _ in range(60):
p.stepSimulation()
return self.scene["q"]
2021-09-07 22:29:42 +02:00
class RandomScene(Scene):
def __init__(self):
super().__init__()
2021-09-14 11:58:33 +02:00
self.center = np.r_[0.5, 0.0, 0.2]
2021-09-07 22:29:42 +02:00
self.length = 0.3
self.origin = self.center - np.r_[0.5 * self.length, 0.5 * self.length, 0.0]
2021-12-22 12:12:09 +01:00
self.object_urdfs = find_urdfs(urdfs_dir / "test")
2021-09-07 22:29:42 +02:00
2021-11-04 20:07:57 +01:00
def generate(self, rng, object_count=4, attempts=10):
2021-11-04 16:48:25 +01:00
self.add_support(self.center)
2021-11-04 20:07:57 +01:00
urdfs = rng.choice(self.object_urdfs, object_count)
2021-07-06 14:00:04 +02:00
for urdf in urdfs:
2021-11-04 20:07:57 +01:00
scale = rng.uniform(0.8, 1.0)
2021-11-04 16:48:25 +01:00
uid = self.add_object(urdf, Rotation.identity(), np.zeros(3), scale)
2021-07-06 14:00:04 +02:00
lower, upper = p.getAABB(uid)
z_offset = 0.5 * (upper[2] - lower[2]) + 0.002
state_id = p.saveState()
for _ in range(attempts):
2021-09-07 22:29:42 +02:00
# Try to place and check for collisions
2021-11-04 20:07:57 +01:00
ori = Rotation.from_euler("z", rng.uniform(0, 2 * np.pi))
2021-09-07 22:29:42 +02:00
pos = np.r_[rng.uniform(0.2, 0.8, 2) * self.length, z_offset]
p.resetBasePositionAndOrientation(uid, self.origin + pos, ori.as_quat())
p.stepSimulation()
2021-07-06 14:00:04 +02:00
if not p.getContactPoints(uid):
break
else:
p.restoreState(stateId=state_id)
else:
# No placement found, remove the object
self.remove_object(uid)
2021-09-14 11:58:33 +02:00
q = [0.0, -1.39, 0.0, -2.36, 0.0, 1.57, 0.79]
q += rng.uniform(-0.08, 0.08, 7)
return q
2021-07-07 10:16:12 +02:00
2024-10-24 21:54:31 -05:00
class ManualScene(Scene):
def __init__(self):
super().__init__()
self.config_path = pkg_root / "cfg/sim"
self.scene_index = 0
# Visit the directory and read all the yaml files
self.scenes = []
for file in self.config_path.iterdir():
if file.suffix == ".yaml":
self.scenes.append(file)
self.num_scenes = len(self.scenes)
def load_config(self):
self.scene = load_yaml(self.scenes[self.scene_index])
self.center = np.asarray(self.scene["center"])
self.length = 0.3
self.origin = self.center - np.r_[0.5 * self.length, 0.5 * self.length, 0.0]
def generate(self, rng):
self.load_config()
self.add_support(self.center)
for object in self.scene["objects"]:
urdf = urdfs_dir / object["object_id"] / "model.urdf"
ori = Rotation.from_euler("xyz", object["rpy"], degrees=True)
pos = self.center + np.asarray(object["xyz"])
scale = object.get("scale", 1)
if randomize := object.get("randomize", False):
angle = rng.uniform(-randomize["rot"], randomize["rot"])
ori = Rotation.from_euler("z", angle, degrees=True) * ori
b = np.asarray(randomize["pos"])
pos += rng.uniform(-b, b)
self.add_object(urdf, ori, pos, scale)
for _ in range(60):
p.stepSimulation()
self.scene_index += 1
if(self.scene_index >= self.num_scenes):
self.scene_index = 0
return self.scene["q"]
2021-09-07 22:29:42 +02:00
def get_scene(scene_id):
2021-11-04 16:48:25 +01:00
if scene_id.endswith(".yaml"):
return YamlScene(scene_id)
elif scene_id == "random":
2021-09-08 16:50:53 +02:00
return RandomScene()
2024-10-24 21:54:31 -05:00
elif scene_id == "manual":
return ManualScene()
2021-11-04 16:48:25 +01:00
2021-09-07 22:29:42 +02:00
else:
raise ValueError("Unknown scene {}.".format(scene_id))