refactory: task generate
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -2,3 +2,4 @@ __pycache__/
|
||||
workspace/
|
||||
logs/
|
||||
cache/
|
||||
.idea/
|
||||
34
app.py
34
app.py
@@ -1,37 +1,37 @@
|
||||
from pyapp.application import PyApplication
|
||||
from pyboot.application import PybootApplication
|
||||
from runners.task_generator import TaskGenerator
|
||||
from runners.data_generator import DataGenerator
|
||||
from runners.data_recorder import DataRecorder
|
||||
from runners.task_templates_divider import TaskTemplatesDivider
|
||||
|
||||
@PyApplication("agent")
|
||||
class AgentApp():
|
||||
@PybootApplication("agent")
|
||||
class AgentApp:
|
||||
@staticmethod
|
||||
def start():
|
||||
TaskGenerator(config_path="configs/task_generator.json").run()
|
||||
DataGenerator(config_path="configs/data_generator.json").run()
|
||||
DataRecorder(config_path="configs/data_recorder.json").run()
|
||||
TaskGenerator(config_path="configs/generate_task_config.yaml").run()
|
||||
DataGenerator(config_path="configs/generate_data_config.yaml").run()
|
||||
DataRecorder(config_path="configs/record_data_config.yaml").run()
|
||||
|
||||
@PyApplication("divide_task")
|
||||
class DivideTaskApp():
|
||||
@PybootApplication("divide_task")
|
||||
class DivideTaskApp:
|
||||
@staticmethod
|
||||
def start():
|
||||
TaskTemplatesDivider(config_path="configs/divide_task_config.yaml").run()
|
||||
|
||||
@PyApplication("generate_task")
|
||||
class GenerateTaskApp():
|
||||
@PybootApplication("generate_task")
|
||||
class GenerateTaskApp:
|
||||
@staticmethod
|
||||
def start():
|
||||
TaskGenerator(config_path="configs/task_generator.json").run()
|
||||
TaskGenerator(config_path="configs/generate_task_config.yaml").run()
|
||||
|
||||
@PyApplication("generate_data")
|
||||
class GenerateDataApp():
|
||||
@PybootApplication("generate_data")
|
||||
class GenerateDataApp:
|
||||
@staticmethod
|
||||
def start():
|
||||
DataGenerator(config_path="configs/data_generator.json").run()
|
||||
DataGenerator(config_path="configs/generate_data_config.yaml").run()
|
||||
|
||||
@PyApplication("record_data")
|
||||
class RecordDataApp():
|
||||
@PybootApplication("record_data")
|
||||
class RecordDataApp:
|
||||
@staticmethod
|
||||
def start():
|
||||
DataRecorder(config_path="configs/data_recorder.json").run()
|
||||
DataRecorder(config_path="configs/record_data_config.yaml").run()
|
||||
84
others/cost_function.py
Normal file
84
others/cost_function.py
Normal file
@@ -0,0 +1,84 @@
|
||||
import numpy as np
|
||||
|
||||
from others.sdf import get_distance_with_sdf
|
||||
from others.transform_utils import transform_points, random_point
|
||||
|
||||
|
||||
def cost_without_collision(obj_active, obj_passive, threshold=1, **kwargs):
|
||||
collision_points = obj_active.collision_points
|
||||
active2passive = np.linalg.inv(obj_passive.obj_pose) @ obj_active.obj_pose
|
||||
sdf_distance = get_distance_with_sdf(collision_points, active2passive, obj_passive.sdf)
|
||||
|
||||
cost = np.sum(sdf_distance < 0)
|
||||
print('sdf:', cost, sdf_distance.min(), sdf_distance.max())
|
||||
return cost * 10
|
||||
|
||||
|
||||
def cost_A_out_B(obj_active, obj_passive, safe_distance, **kwargs):
|
||||
activate_point = obj_active.obj_pose[:3, 3]
|
||||
passive_point = obj_passive.obj_pose[:3, 3]
|
||||
distance_vector = activate_point[:2] - passive_point[:2]
|
||||
distance = np.linalg.norm(distance_vector)
|
||||
|
||||
cost = 0
|
||||
if distance < safe_distance:
|
||||
cost += 10
|
||||
return cost
|
||||
|
||||
|
||||
def cost_A_on_B(obj_active, obj_passive, **kwargs):
|
||||
active_pts = transform_points(obj_active.anchor_points['buttom'], obj_active.obj_pose)[0]
|
||||
passive_pts = transform_points(obj_passive.anchor_points['top'], obj_passive.obj_pose)[0]
|
||||
|
||||
# active_pts = transform_points(obj_active.anchor_points['buttom'], obj_active.obj_pose)[0]
|
||||
# passive_pts = transform_points(obj_passive.anchor_points['top'], obj_passive.obj_pose)[0]
|
||||
|
||||
cost_add = 0
|
||||
# if activate_point[2] <= passive_point[2]:
|
||||
# cost_add += 10 # Large penalty if the constraint is violated
|
||||
distance_vector = active_pts - passive_pts
|
||||
# import ipdb; ipdb.set_trace()
|
||||
distance_cost = np.linalg.norm(distance_vector)
|
||||
return cost_add + distance_cost
|
||||
|
||||
|
||||
def cost_A_in_B(opt_pose_active_homo, opt_pose_passive_homo, selected_points_activate_sam, selected_points_passive_sam,
|
||||
selected_points_passive_bottom):
|
||||
# import pdb;pdb.set_trace()
|
||||
|
||||
transformed_points_activate = batch_transform_points(selected_points_activate_sam, opt_pose_active_homo)
|
||||
transformed_points_activate = transformed_points_activate.reshape(-1, 3)
|
||||
|
||||
transformed_points_passive = selected_points_passive_sam
|
||||
transformed_points_passive = transformed_points_passive.reshape(-1, 3)
|
||||
|
||||
transformed_points_passive_bot = selected_points_passive_bottom
|
||||
transformed_points_passive_bot = transformed_points_passive_bot.reshape(-1, 3)
|
||||
# Get the lowest point of the active object
|
||||
activate_point = random_point(transformed_points_activate, 3)
|
||||
# Get the highest point of the passive object
|
||||
passive_point_top = random_point(transformed_points_passive, 3)
|
||||
|
||||
passive_point_bot = random_point(transformed_points_passive_bot, 10) # 约束靠近中心,避免求解出来碰撞
|
||||
cost_add = 0
|
||||
if activate_point[2] >= passive_point_top[2]:
|
||||
cost_add += 10 # Large penalty if the constraint is violated
|
||||
distance_vector = activate_point - passive_point_bot
|
||||
distance_cost = np.linalg.norm(distance_vector)
|
||||
return cost_add + distance_cost
|
||||
|
||||
|
||||
CONSTRAINT_COST = {
|
||||
'wo_collision': cost_without_collision,
|
||||
"out": cost_A_out_B,
|
||||
"on": cost_A_on_B,
|
||||
"in": cost_A_in_B,
|
||||
"faceto": NotImplemented,
|
||||
'backto': NotImplemented
|
||||
}
|
||||
|
||||
|
||||
def get_cost_func(constraint):
|
||||
if constraint not in constraint:
|
||||
raise NotImplementedError("Constraint {} not implemented".format(constraint))
|
||||
return CONSTRAINT_COST[constraint]
|
||||
634
others/layout_2d.py
Normal file
634
others/layout_2d.py
Normal file
@@ -0,0 +1,634 @@
|
||||
import copy
|
||||
import random
|
||||
import time
|
||||
import numpy as np
|
||||
from rtree import index
|
||||
from scipy.interpolate import interp1d
|
||||
from shapely.geometry import Polygon, Point, box, LineString
|
||||
|
||||
class SolutionFound(Exception):
|
||||
def __init__(self, solution):
|
||||
self.solution = solution
|
||||
|
||||
class DFS_Solver_Floor:
|
||||
def __init__(self, grid_size, random_seed=0, max_duration=5, constraint_bouns=0.2):
|
||||
self.grid_size = grid_size # grid的边长(不是数量)
|
||||
self.random_seed = random_seed
|
||||
self.max_duration = max_duration # maximum allowed time in seconds
|
||||
self.constraint_bouns = constraint_bouns
|
||||
self.start_time = None
|
||||
self.solutions = []
|
||||
|
||||
# Define the functions in a dictionary to avoid if-else conditions
|
||||
self.func_dict = {
|
||||
"global": {"edge": self.place_edge},
|
||||
"relative": self.place_relative,
|
||||
"direction": self.place_face,
|
||||
"alignment": self.place_alignment_center,
|
||||
"distance": self.place_distance,
|
||||
}
|
||||
|
||||
self.constraint_type2weight = {
|
||||
"global": 1.0,
|
||||
"relative": 0.5,
|
||||
"direction": 0.5,
|
||||
"alignment": 0.5,
|
||||
"distance": 1.8,
|
||||
}
|
||||
|
||||
self.edge_bouns = 0.0 # worth more than one constraint
|
||||
|
||||
def get_solution(
|
||||
self, bounds, objects_list, constraints, initial_state, use_milp=False
|
||||
):
|
||||
self.start_time = time.time()
|
||||
if use_milp:
|
||||
# iterate through the constraints list
|
||||
# for each constraint type "distance", add the same constraint to the target object
|
||||
new_constraints = constraints.copy()
|
||||
for object_name, object_constraints in constraints.items():
|
||||
for constraint in object_constraints:
|
||||
if constraint["type"] == "distance":
|
||||
target_object_name = constraint["target"]
|
||||
if target_object_name in constraints.keys():
|
||||
# if there is already a distance constraint of target object_name, continue
|
||||
if any(
|
||||
constraint["type"] == "distance"
|
||||
and constraint["target"] == object_name
|
||||
for constraint in constraints[target_object_name]
|
||||
):
|
||||
continue
|
||||
new_constraint = constraint.copy()
|
||||
new_constraint["target"] = object_name
|
||||
new_constraints[target_object_name].append(new_constraint)
|
||||
# iterate through the constraints list
|
||||
# for each constraint type "left of" or "right of", add the same constraint to the target object
|
||||
# for object_name, object_constraints in constraints.items():
|
||||
# for constraint in object_constraints: if constraint["type"] == "relative":
|
||||
# if constraint["constraint"] == "left of":
|
||||
constraints = new_constraints
|
||||
|
||||
print(f"Time taken: {time.time() - self.start_time}")
|
||||
|
||||
else:
|
||||
grid_points = self.create_grids(bounds)
|
||||
grid_points = self._remove_points(grid_points, initial_state)
|
||||
try:
|
||||
self._dfs(
|
||||
bounds, objects_list, constraints, grid_points, initial_state, 30
|
||||
)
|
||||
except SolutionFound as e:
|
||||
print(f"Time taken: {time.time() - self.start_time}")
|
||||
|
||||
print(f"Number of solutions found: {len(self.solutions)}")
|
||||
max_solution = self._get_max_solution(self.solutions)
|
||||
|
||||
return max_solution
|
||||
|
||||
def _get_max_solution(self, solutions):
|
||||
path_weights = []
|
||||
for solution in solutions:
|
||||
path_weights.append(sum([obj[-1] for obj in solution.values()]))
|
||||
max_index = np.argmax(path_weights)
|
||||
return solutions[max_index]
|
||||
|
||||
def _dfs(
|
||||
self,
|
||||
room_poly,
|
||||
objects_list,
|
||||
constraints,
|
||||
grid_points,
|
||||
placed_objects,
|
||||
branch_factor,
|
||||
):
|
||||
if len(objects_list) == 0:
|
||||
self.solutions.append(placed_objects)
|
||||
return placed_objects
|
||||
|
||||
if time.time() - self.start_time > self.max_duration:
|
||||
print(f"Time limit reached.")
|
||||
raise SolutionFound(self.solutions)
|
||||
|
||||
object_name, object_dim = objects_list[0]
|
||||
placements = self._get_possible_placements(
|
||||
room_poly, object_dim, constraints[object_name], grid_points, placed_objects
|
||||
)
|
||||
|
||||
if len(placements) == 0 and len(placed_objects) != 0:
|
||||
self.solutions.append(placed_objects)
|
||||
|
||||
paths = []
|
||||
if branch_factor > 1:
|
||||
random.shuffle(placements) # shuffle the placements of the first object
|
||||
|
||||
for placement in placements[:branch_factor]:
|
||||
placed_objects_updated = copy.deepcopy(placed_objects)
|
||||
placed_objects_updated[object_name] = placement
|
||||
grid_points_updated = self._remove_points(
|
||||
grid_points, placed_objects_updated
|
||||
)
|
||||
|
||||
sub_paths = self._dfs(
|
||||
room_poly,
|
||||
objects_list[1:],
|
||||
constraints,
|
||||
grid_points_updated,
|
||||
placed_objects_updated,
|
||||
1,
|
||||
)
|
||||
paths.extend(sub_paths)
|
||||
|
||||
return paths
|
||||
|
||||
def _get_possible_placements(
|
||||
self, room_poly, object_dim, constraints, grid_points, placed_objects
|
||||
):
|
||||
solutions = self.filter_collision(
|
||||
placed_objects, self.get_all_solutions(room_poly, grid_points, object_dim)
|
||||
)
|
||||
solutions = self.filter_facing_wall(room_poly, solutions, object_dim)
|
||||
edge_solutions = self.place_edge(
|
||||
room_poly, copy.deepcopy(solutions), object_dim
|
||||
)
|
||||
|
||||
if len(edge_solutions) == 0:
|
||||
return edge_solutions
|
||||
|
||||
global_constraint = next(
|
||||
(
|
||||
constraint
|
||||
for constraint in constraints
|
||||
if constraint["type"] == "global"
|
||||
),
|
||||
None,
|
||||
)
|
||||
|
||||
if global_constraint is None:
|
||||
global_constraint = {"type": "global", "constraint": "edge"}
|
||||
|
||||
if global_constraint["constraint"] == "edge":
|
||||
candidate_solutions = copy.deepcopy(
|
||||
edge_solutions
|
||||
) # edge is hard constraint
|
||||
else:
|
||||
if len(constraints) > 1:
|
||||
candidate_solutions = (
|
||||
solutions + edge_solutions
|
||||
) # edge is soft constraint
|
||||
else:
|
||||
candidate_solutions = copy.deepcopy(solutions) # the first object
|
||||
|
||||
candidate_solutions = self.filter_collision(
|
||||
placed_objects, candidate_solutions
|
||||
) # filter again after global constraint
|
||||
|
||||
if candidate_solutions == []:
|
||||
return candidate_solutions
|
||||
random.shuffle(candidate_solutions)
|
||||
placement2score = {
|
||||
tuple(solution[:3]): solution[-1] for solution in candidate_solutions
|
||||
}
|
||||
|
||||
# add a bias to edge solutions
|
||||
for solution in candidate_solutions:
|
||||
if solution in edge_solutions and len(constraints) >= 1:
|
||||
placement2score[tuple(solution[:3])] += self.edge_bouns
|
||||
|
||||
for constraint in constraints:
|
||||
if "target" not in constraint:
|
||||
continue
|
||||
|
||||
func = self.func_dict.get(constraint["type"])
|
||||
valid_solutions = func(
|
||||
constraint["constraint"],
|
||||
placed_objects[constraint["target"]],
|
||||
candidate_solutions,
|
||||
)
|
||||
|
||||
weight = self.constraint_type2weight[constraint["type"]]
|
||||
if constraint["type"] == "distance":
|
||||
for solution in valid_solutions:
|
||||
bouns = solution[-1]
|
||||
placement2score[tuple(solution[:3])] += bouns * weight
|
||||
else:
|
||||
for solution in valid_solutions:
|
||||
placement2score[tuple(solution[:3])] += (
|
||||
self.constraint_bouns * weight
|
||||
)
|
||||
|
||||
# normalize the scores
|
||||
for placement in placement2score:
|
||||
placement2score[placement] /= max(len(constraints), 1)
|
||||
|
||||
sorted_placements = sorted(
|
||||
placement2score, key=placement2score.get, reverse=True
|
||||
)
|
||||
sorted_solutions = [
|
||||
list(placement) + [placement2score[placement]]
|
||||
for placement in sorted_placements
|
||||
]
|
||||
|
||||
return sorted_solutions
|
||||
|
||||
def create_grids(self, room_poly):
|
||||
# get the min and max bounds of the room
|
||||
min_x, min_z, max_x, max_z = room_poly.bounds
|
||||
|
||||
# create grid points
|
||||
grid_points = []
|
||||
|
||||
for x in range(int(min_x), int(max_x), self.grid_size):
|
||||
for y in range(int(min_z), int(max_z), self.grid_size):
|
||||
point = Point(x, y)
|
||||
if room_poly.contains(point):
|
||||
grid_points.append((x, y))
|
||||
|
||||
return grid_points
|
||||
|
||||
def _remove_points(self, grid_points, objects_dict):
|
||||
# Create an r-tree index
|
||||
idx = index.Index()
|
||||
|
||||
# Populate the index with bounding boxes of the objects
|
||||
for i, (_, _, obj, _) in enumerate(objects_dict.values()):
|
||||
idx.insert(i, Polygon(obj).bounds)
|
||||
|
||||
# Create Shapely Polygon objects only once
|
||||
polygons = [Polygon(obj) for _, _, obj, _ in objects_dict.values()]
|
||||
|
||||
valid_points = []
|
||||
|
||||
for point in grid_points:
|
||||
p = Point(point)
|
||||
# Get a list of potential candidates
|
||||
candidates = [polygons[i] for i in idx.intersection(p.bounds)]
|
||||
# Check if point is in any of the candidate polygons
|
||||
if not any(candidate.contains(p) for candidate in candidates):
|
||||
valid_points.append(point)
|
||||
|
||||
return valid_points
|
||||
|
||||
def get_all_solutions(self, room_poly, grid_points, object_dim):
|
||||
obj_length, obj_width = object_dim
|
||||
obj_half_length, obj_half_width = obj_length / 2, obj_width / 2
|
||||
|
||||
rotation_adjustments = {
|
||||
0: ((-obj_half_length, -obj_half_width), (obj_half_length, obj_half_width)),
|
||||
90: (
|
||||
(-obj_half_width, -obj_half_length),
|
||||
(obj_half_width, obj_half_length),
|
||||
),
|
||||
180: (
|
||||
(-obj_half_length, obj_half_width),
|
||||
(obj_half_length, -obj_half_width),
|
||||
),
|
||||
270: (
|
||||
(obj_half_width, -obj_half_length),
|
||||
(-obj_half_width, obj_half_length),
|
||||
),
|
||||
}
|
||||
|
||||
solutions = []
|
||||
for rotation in [0, 90, 180, 270]:
|
||||
for point in grid_points:
|
||||
center_x, center_y = point
|
||||
lower_left_adjustment, upper_right_adjustment = rotation_adjustments[
|
||||
rotation
|
||||
]
|
||||
lower_left = (
|
||||
center_x + lower_left_adjustment[0],
|
||||
center_y + lower_left_adjustment[1],
|
||||
)
|
||||
upper_right = (
|
||||
center_x + upper_right_adjustment[0],
|
||||
center_y + upper_right_adjustment[1],
|
||||
)
|
||||
obj_box = box(*lower_left, *upper_right)
|
||||
|
||||
if room_poly.contains(obj_box):
|
||||
solutions.append(
|
||||
[point, rotation, tuple(obj_box.exterior.coords[:]), 1]
|
||||
)
|
||||
|
||||
return solutions
|
||||
|
||||
def filter_collision(self, objects_dict, solutions):
|
||||
valid_solutions = []
|
||||
object_polygons = [
|
||||
Polygon(obj_coords) for _, _, obj_coords, _ in list(objects_dict.values())
|
||||
]
|
||||
for solution in solutions:
|
||||
sol_obj_coords = solution[2]
|
||||
sol_obj = Polygon(sol_obj_coords)
|
||||
if not any(sol_obj.intersects(obj) for obj in object_polygons):
|
||||
valid_solutions.append(solution)
|
||||
return valid_solutions
|
||||
|
||||
def filter_facing_wall(self, room_poly, solutions, obj_dim):
|
||||
valid_solutions = []
|
||||
obj_width = obj_dim[1]
|
||||
obj_half_width = obj_width / 2
|
||||
|
||||
front_center_adjustments = {
|
||||
0: (0, obj_half_width),
|
||||
90: (obj_half_width, 0),
|
||||
180: (0, -obj_half_width),
|
||||
270: (-obj_half_width, 0),
|
||||
}
|
||||
|
||||
valid_solutions = []
|
||||
for solution in solutions:
|
||||
center_x, center_y = solution[0]
|
||||
rotation = solution[1]
|
||||
|
||||
front_center_adjustment = front_center_adjustments[rotation]
|
||||
front_center_x, front_center_y = (
|
||||
center_x + front_center_adjustment[0],
|
||||
center_y + front_center_adjustment[1],
|
||||
)
|
||||
|
||||
front_center_distance = room_poly.boundary.distance(
|
||||
Point(front_center_x, front_center_y)
|
||||
)
|
||||
|
||||
if front_center_distance >= 30: # TODO: make this a parameter
|
||||
valid_solutions.append(solution)
|
||||
|
||||
return valid_solutions
|
||||
|
||||
def place_edge(self, room_poly, solutions, obj_dim):
|
||||
valid_solutions = []
|
||||
obj_width = obj_dim[1]
|
||||
obj_half_width = obj_width / 2
|
||||
|
||||
back_center_adjustments = {
|
||||
0: (0, -obj_half_width),
|
||||
90: (-obj_half_width, 0),
|
||||
180: (0, obj_half_width),
|
||||
270: (obj_half_width, 0),
|
||||
}
|
||||
|
||||
for solution in solutions:
|
||||
center_x, center_y = solution[0]
|
||||
rotation = solution[1]
|
||||
|
||||
back_center_adjustment = back_center_adjustments[rotation]
|
||||
back_center_x, back_center_y = (
|
||||
center_x + back_center_adjustment[0],
|
||||
center_y + back_center_adjustment[1],
|
||||
)
|
||||
|
||||
back_center_distance = room_poly.boundary.distance(
|
||||
Point(back_center_x, back_center_y)
|
||||
)
|
||||
center_distance = room_poly.boundary.distance(Point(center_x, center_y))
|
||||
|
||||
if (
|
||||
back_center_distance <= self.grid_size
|
||||
and back_center_distance < center_distance
|
||||
):
|
||||
solution[-1] += self.constraint_bouns
|
||||
# valid_solutions.append(solution) # those are still valid solutions, but we need to move the object to the edge
|
||||
|
||||
# move the object to the edge
|
||||
center2back_vector = np.array(
|
||||
[back_center_x - center_x, back_center_y - center_y]
|
||||
)
|
||||
center2back_vector /= np.linalg.norm(center2back_vector)
|
||||
offset = center2back_vector * (
|
||||
back_center_distance + 4.5
|
||||
) # add a small distance to avoid the object cross the wall
|
||||
solution[0] = (center_x + offset[0], center_y + offset[1])
|
||||
solution[2] = (
|
||||
(solution[2][0][0] + offset[0], solution[2][0][1] + offset[1]),
|
||||
(solution[2][1][0] + offset[0], solution[2][1][1] + offset[1]),
|
||||
(solution[2][2][0] + offset[0], solution[2][2][1] + offset[1]),
|
||||
(solution[2][3][0] + offset[0], solution[2][3][1] + offset[1]),
|
||||
)
|
||||
valid_solutions.append(solution)
|
||||
|
||||
return valid_solutions
|
||||
|
||||
def place_corner(self, room_poly, solutions, obj_dim):
|
||||
obj_length, obj_width = obj_dim
|
||||
obj_half_length, _ = obj_length / 2, obj_width / 2
|
||||
|
||||
rotation_center_adjustments = {
|
||||
0: ((-obj_half_length, 0), (obj_half_length, 0)),
|
||||
90: ((0, obj_half_length), (0, -obj_half_length)),
|
||||
180: ((obj_half_length, 0), (-obj_half_length, 0)),
|
||||
270: ((0, -obj_half_length), (0, obj_half_length)),
|
||||
}
|
||||
|
||||
edge_solutions = self.place_edge(room_poly, solutions, obj_dim)
|
||||
|
||||
valid_solutions = []
|
||||
|
||||
for solution in edge_solutions:
|
||||
(center_x, center_y), rotation = solution[:2]
|
||||
(dx_left, dy_left), (dx_right, dy_right) = rotation_center_adjustments[
|
||||
rotation
|
||||
]
|
||||
|
||||
left_center_x, left_center_y = center_x + dx_left, center_y + dy_left
|
||||
right_center_x, right_center_y = center_x + dx_right, center_y + dy_right
|
||||
|
||||
left_center_distance = room_poly.boundary.distance(
|
||||
Point(left_center_x, left_center_y)
|
||||
)
|
||||
right_center_distance = room_poly.boundary.distance(
|
||||
Point(right_center_x, right_center_y)
|
||||
)
|
||||
|
||||
if min(left_center_distance, right_center_distance) < self.grid_size:
|
||||
solution[-1] += self.constraint_bouns
|
||||
valid_solutions.append(solution)
|
||||
|
||||
return valid_solutions
|
||||
|
||||
def place_relative(self, place_type, target_object, solutions):
|
||||
valid_solutions = []
|
||||
_, target_rotation, target_coords, _ = target_object
|
||||
target_polygon = Polygon(target_coords)
|
||||
|
||||
min_x, min_y, max_x, max_y = target_polygon.bounds
|
||||
mean_x = (min_x + max_x) / 2
|
||||
mean_y = (min_y + max_y) / 2
|
||||
|
||||
comparison_dict = {
|
||||
"left of": {
|
||||
0: lambda sol_center: sol_center[0] < min_x
|
||||
and min_y <= sol_center[1] <= max_y,
|
||||
90: lambda sol_center: sol_center[1] > max_y
|
||||
and min_x <= sol_center[0] <= max_x,
|
||||
180: lambda sol_center: sol_center[0] > max_x
|
||||
and min_y <= sol_center[1] <= max_y,
|
||||
270: lambda sol_center: sol_center[1] < min_y
|
||||
and min_x <= sol_center[0] <= max_x,
|
||||
},
|
||||
"right of": {
|
||||
0: lambda sol_center: sol_center[0] > max_x
|
||||
and min_y <= sol_center[1] <= max_y,
|
||||
90: lambda sol_center: sol_center[1] < min_y
|
||||
and min_x <= sol_center[0] <= max_x,
|
||||
180: lambda sol_center: sol_center[0] < min_x
|
||||
and min_y <= sol_center[1] <= max_y,
|
||||
270: lambda sol_center: sol_center[1] > max_y
|
||||
and min_x <= sol_center[0] <= max_x,
|
||||
},
|
||||
"in front of": {
|
||||
0: lambda sol_center: sol_center[1] > max_y
|
||||
and mean_x - self.grid_size
|
||||
< sol_center[0]
|
||||
< mean_x + self.grid_size, # in front of and centered
|
||||
90: lambda sol_center: sol_center[0] > max_x
|
||||
and mean_y - self.grid_size < sol_center[1] < mean_y + self.grid_size,
|
||||
180: lambda sol_center: sol_center[1] < min_y
|
||||
and mean_x - self.grid_size < sol_center[0] < mean_x + self.grid_size,
|
||||
270: lambda sol_center: sol_center[0] < min_x
|
||||
and mean_y - self.grid_size < sol_center[1] < mean_y + self.grid_size,
|
||||
},
|
||||
"behind": {
|
||||
0: lambda sol_center: sol_center[1] < min_y
|
||||
and min_x <= sol_center[0] <= max_x,
|
||||
90: lambda sol_center: sol_center[0] < min_x
|
||||
and min_y <= sol_center[1] <= max_y,
|
||||
180: lambda sol_center: sol_center[1] > max_y
|
||||
and min_x <= sol_center[0] <= max_x,
|
||||
270: lambda sol_center: sol_center[0] > max_x
|
||||
and min_y <= sol_center[1] <= max_y,
|
||||
},
|
||||
"side of": {
|
||||
0: lambda sol_center: min_y <= sol_center[1] <= max_y,
|
||||
90: lambda sol_center: min_x <= sol_center[0] <= max_x,
|
||||
180: lambda sol_center: min_y <= sol_center[1] <= max_y,
|
||||
270: lambda sol_center: min_x <= sol_center[0] <= max_x,
|
||||
},
|
||||
}
|
||||
|
||||
compare_func = comparison_dict.get(place_type).get(target_rotation)
|
||||
|
||||
for solution in solutions:
|
||||
sol_center = solution[0]
|
||||
|
||||
if compare_func(sol_center):
|
||||
solution[-1] += self.constraint_bouns
|
||||
valid_solutions.append(solution)
|
||||
|
||||
return valid_solutions
|
||||
|
||||
def place_distance(self, distance_type, target_object, solutions):
|
||||
target_coords = target_object[2]
|
||||
target_poly = Polygon(target_coords)
|
||||
distances = []
|
||||
valid_solutions = []
|
||||
for solution in solutions:
|
||||
sol_coords = solution[2]
|
||||
sol_poly = Polygon(sol_coords)
|
||||
distance = target_poly.distance(sol_poly)
|
||||
distances.append(distance)
|
||||
|
||||
solution[-1] = distance
|
||||
valid_solutions.append(solution)
|
||||
|
||||
min_distance = min(distances)
|
||||
max_distance = max(distances)
|
||||
|
||||
if distance_type == "near":
|
||||
if min_distance < 80:
|
||||
points = [(min_distance, 1), (80, 0), (max_distance, 0)]
|
||||
else:
|
||||
points = [(min_distance, 0), (max_distance, 0)]
|
||||
|
||||
elif distance_type == "far":
|
||||
points = [(min_distance, 0), (max_distance, 1)]
|
||||
|
||||
x = [point[0] for point in points]
|
||||
y = [point[1] for point in points]
|
||||
|
||||
f = interp1d(x, y, kind="linear", fill_value="extrapolate")
|
||||
|
||||
for solution in valid_solutions:
|
||||
distance = solution[-1]
|
||||
solution[-1] = float(f(distance))
|
||||
|
||||
return valid_solutions
|
||||
|
||||
def place_face(self, face_type, target_object, solutions):
|
||||
if face_type == "face to":
|
||||
return self.place_face_to(target_object, solutions)
|
||||
|
||||
elif face_type == "face same as":
|
||||
return self.place_face_same(target_object, solutions)
|
||||
|
||||
elif face_type == "face opposite to":
|
||||
return self.place_face_opposite(target_object, solutions)
|
||||
|
||||
def place_face_to(self, target_object, solutions):
|
||||
# Define unit vectors for each rotation
|
||||
unit_vectors = {
|
||||
0: np.array([0.0, 1.0]), # Facing up
|
||||
90: np.array([1.0, 0.0]), # Facing right
|
||||
180: np.array([0.0, -1.0]), # Facing down
|
||||
270: np.array([-1.0, 0.0]), # Facing left
|
||||
}
|
||||
|
||||
target_coords = target_object[2]
|
||||
target_poly = Polygon(target_coords)
|
||||
|
||||
valid_solutions = []
|
||||
|
||||
for solution in solutions:
|
||||
sol_center = solution[0]
|
||||
sol_rotation = solution[1]
|
||||
|
||||
# Define an arbitrarily large point in the direction of the solution's rotation
|
||||
far_point = sol_center + 1e6 * unit_vectors[sol_rotation]
|
||||
|
||||
# Create a half-line from the solution's center to the far point
|
||||
half_line = LineString([sol_center, far_point])
|
||||
|
||||
# Check if the half-line intersects with the target polygon
|
||||
if half_line.intersects(target_poly):
|
||||
solution[-1] += self.constraint_bouns
|
||||
valid_solutions.append(solution)
|
||||
|
||||
return valid_solutions
|
||||
|
||||
def place_face_same(self, target_object, solutions):
|
||||
target_rotation = target_object[1]
|
||||
valid_solutions = []
|
||||
|
||||
for solution in solutions:
|
||||
sol_rotation = solution[1]
|
||||
if sol_rotation == target_rotation:
|
||||
solution[-1] += self.constraint_bouns
|
||||
valid_solutions.append(solution)
|
||||
|
||||
return valid_solutions
|
||||
|
||||
def place_face_opposite(self, target_object, solutions):
|
||||
target_rotation = (target_object[1] + 180) % 360
|
||||
valid_solutions = []
|
||||
|
||||
for solution in solutions:
|
||||
sol_rotation = solution[1]
|
||||
if sol_rotation == target_rotation:
|
||||
solution[-1] += self.constraint_bouns
|
||||
valid_solutions.append(solution)
|
||||
|
||||
return valid_solutions
|
||||
|
||||
def place_alignment_center(self, alignment_type, target_object, solutions):
|
||||
target_center = target_object[0]
|
||||
valid_solutions = []
|
||||
eps = 5
|
||||
for solution in solutions:
|
||||
sol_center = solution[0]
|
||||
if (
|
||||
abs(sol_center[0] - target_center[0]) < eps
|
||||
or abs(sol_center[1] - target_center[1]) < eps
|
||||
):
|
||||
solution[-1] += self.constraint_bouns
|
||||
valid_solutions.append(solution)
|
||||
return valid_solutions
|
||||
71
others/layout_object.py
Normal file
71
others/layout_object.py
Normal file
@@ -0,0 +1,71 @@
|
||||
import os
|
||||
|
||||
import numpy as np
|
||||
import trimesh
|
||||
from scipy.interpolate import RegularGridInterpolator
|
||||
from scipy.spatial.transform import Rotation as R
|
||||
|
||||
from others.object import OmniObject
|
||||
from others.sdf import compute_sdf_from_obj_surface
|
||||
from others.transform_utils import farthest_point_sampling, random_point
|
||||
|
||||
|
||||
def load_and_prepare_mesh(obj_path, up_axis, scale=1.0):
|
||||
if not os.path.exists(obj_path):
|
||||
return None
|
||||
mesh = trimesh.load(obj_path, force="mesh")
|
||||
if 'z' in up_axis:
|
||||
align_rotation = R.from_euler('xyz', [0, 180, 0], degrees=True).as_matrix()
|
||||
elif 'y' in up_axis:
|
||||
align_rotation = R.from_euler('xyz', [-90, 180, 0], degrees=True).as_matrix()
|
||||
elif 'x' in up_axis:
|
||||
align_rotation = R.from_euler('xyz', [0, 0, 90], degrees=True).as_matrix()
|
||||
else:
|
||||
align_rotation = R.from_euler('xyz', [-90, 180, 0], degrees=True).as_matrix()
|
||||
|
||||
|
||||
align_transform = np.eye(4)
|
||||
align_transform[:3, :3] = align_rotation
|
||||
mesh.apply_transform(align_transform)
|
||||
mesh.apply_scale(scale)
|
||||
return mesh
|
||||
|
||||
|
||||
|
||||
def setup_sdf(mesh):
|
||||
_, sdf_voxels = compute_sdf_from_obj_surface(mesh)
|
||||
# create callable sdf function with interpolation
|
||||
|
||||
min_corner = mesh.bounds[0]
|
||||
max_corner = mesh.bounds[1]
|
||||
x = np.linspace(min_corner[0], max_corner[0], sdf_voxels.shape[0])
|
||||
y = np.linspace(min_corner[1], max_corner[1], sdf_voxels.shape[1])
|
||||
z = np.linspace(min_corner[2], max_corner[2], sdf_voxels.shape[2])
|
||||
sdf_func = RegularGridInterpolator((x, y, z), sdf_voxels, bounds_error=False, fill_value=0)
|
||||
return sdf_func
|
||||
|
||||
|
||||
class LayoutObject(OmniObject):
|
||||
def __init__(self, obj_info, use_sdf=False, N_collision_points=60, **kwargs):
|
||||
super().__init__(name=obj_info['object_id'], **kwargs)
|
||||
|
||||
obj_dir = obj_info["obj_path"]
|
||||
up_axis = obj_info["upAxis"]
|
||||
if len(up_axis) ==0:
|
||||
up_axis = ['y']
|
||||
mesh_scale = obj_info.get("scale", 0.001)*1000
|
||||
self.mesh = load_and_prepare_mesh(obj_dir, up_axis, mesh_scale)
|
||||
if use_sdf:
|
||||
self.sdf = setup_sdf(self.mesh)
|
||||
|
||||
if self.mesh is not None:
|
||||
mesh_points, _ = trimesh.sample.sample_surface(self.mesh, 2000) # 表面采样
|
||||
if mesh_points.shape[0] > N_collision_points:
|
||||
self.collision_points = farthest_point_sampling(mesh_points, N_collision_points) # 碰撞检测点
|
||||
|
||||
|
||||
self.anchor_points = {'top': random_point(self.anchor_points['top'], 3)[np.newaxis, :],
|
||||
'buttom': random_point(self.anchor_points['buttom'], 3)[np.newaxis, :]}
|
||||
|
||||
self.size = self.mesh.extents.copy()
|
||||
self.up_axis = up_axis[0]
|
||||
186
others/multi_add_util.py
Normal file
186
others/multi_add_util.py
Normal file
@@ -0,0 +1,186 @@
|
||||
import math
|
||||
import random
|
||||
|
||||
import matplotlib.patches as patches
|
||||
import matplotlib.pyplot as plt
|
||||
import numpy as np
|
||||
from shapely.geometry import Point, Polygon
|
||||
|
||||
|
||||
def rotate_point(px, py, cx, cy, angle):
|
||||
radians = math.radians(angle)
|
||||
cos_a = math.cos(radians)
|
||||
sin_a = math.sin(radians)
|
||||
# cos_a = math.cos(angle)
|
||||
# sin_a = math.sin(angle)
|
||||
temp_x = px - cx
|
||||
temp_y = py - cy
|
||||
rotated_x = temp_x * cos_a - temp_y * sin_a
|
||||
rotated_y = temp_x * sin_a + temp_y * cos_a
|
||||
return rotated_x + cx, rotated_y + cy
|
||||
|
||||
|
||||
def get_rotated_corners(x, y, width, height, angle):
|
||||
cx = x
|
||||
cy = y
|
||||
corners = [
|
||||
(x - width / 2, y - height / 2),
|
||||
(x + width / 2, y - height / 2),
|
||||
(x - width / 2, y + height / 2),
|
||||
(x + width / 2, y + height / 2)
|
||||
]
|
||||
return [rotate_point(px, py, cx, cy, angle) for px, py in corners]
|
||||
|
||||
|
||||
def is_within_bounds(corners, plane_width, plane_height):
|
||||
min_x = min(px for px, _ in corners)
|
||||
max_x = max(px for px, _ in corners)
|
||||
min_y = min(py for _, py in corners)
|
||||
max_y = max(py for _, py in corners)
|
||||
|
||||
return min_x >= -plane_width / 2 and max_x <= plane_width / 2 and min_y >= -plane_height / 2 and max_y <= plane_height / 2
|
||||
|
||||
|
||||
def is_collision(new_corners, placed_objects):
|
||||
for _, existing_corners in placed_objects:
|
||||
if polygons_intersect(new_corners, existing_corners):
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def polygons_intersect(p1, p2):
|
||||
for polygon in [p1, p2]:
|
||||
for i1 in range(len(polygon)):
|
||||
i2 = (i1 + 1) % len(polygon)
|
||||
projection_axis = (-(polygon[i2][1] - polygon[i1][1]), polygon[i2][0] - polygon[i1][0])
|
||||
|
||||
min_p1, max_p1 = project_polygon(projection_axis, p1)
|
||||
min_p2, max_p2 = project_polygon(projection_axis, p2)
|
||||
|
||||
if max_p1 < min_p2 or max_p2 < min_p1:
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def project_polygon(axis, polygon):
|
||||
min_proj = max_proj = (polygon[0][0] * axis[0] + polygon[0][1] * axis[1])
|
||||
for (x, y) in polygon[1:]:
|
||||
projection = x * axis[0] + y * axis[1]
|
||||
min_proj = min(min_proj, projection)
|
||||
max_proj = max(max_proj, projection)
|
||||
return min_proj, max_proj
|
||||
|
||||
|
||||
def generate_object_sizes(num_objects, size_options):
|
||||
sizes = []
|
||||
max_size = max(size_options, key=lambda s: s[0] * s[1])
|
||||
max_count = 0
|
||||
|
||||
for _ in range(num_objects):
|
||||
if max_count < 3:
|
||||
size = random.choice(size_options)
|
||||
if size == max_size:
|
||||
max_count += 1
|
||||
else:
|
||||
size = random.choice([s for s in size_options if s != max_size])
|
||||
|
||||
sizes.append(size)
|
||||
|
||||
sizes.sort(key=lambda s: s[0] * s[1], reverse=True)
|
||||
return sizes
|
||||
|
||||
|
||||
def calculate_distance(corners1, corners2):
|
||||
center1 = np.mean(corners1, axis=0)
|
||||
center2 = np.mean(corners2, axis=0)
|
||||
return np.linalg.norm(center1 - center2)
|
||||
|
||||
|
||||
def place_objects(num_objects, plane_width, plane_height, obj_sizes):
|
||||
placed_objects = []
|
||||
attempts = 0
|
||||
max_attempts = num_objects * 100
|
||||
|
||||
while len(placed_objects) < num_objects and attempts < max_attempts:
|
||||
width, height = obj_sizes[len(placed_objects)]
|
||||
valid_position = False
|
||||
best_position = None
|
||||
max_distance = -1
|
||||
|
||||
for _ in range(200):
|
||||
x = random.uniform(-plane_width / 2 + width / 2, plane_width / 2 - width / 2)
|
||||
y = random.uniform(-plane_height / 2 + height / 2, plane_height / 2 - height / 2)
|
||||
angle = random.choice(range(0, 360, 15))
|
||||
|
||||
new_corners = get_rotated_corners(x, y, width, height, angle)
|
||||
|
||||
if is_within_bounds(new_corners, plane_width, plane_height) and not is_collision(new_corners,
|
||||
placed_objects):
|
||||
if not placed_objects:
|
||||
best_position = (x, y, width, height, angle)
|
||||
valid_position = True
|
||||
break
|
||||
|
||||
min_distance = min(calculate_distance(new_corners, obj[1]) for obj in placed_objects)
|
||||
if min_distance > max_distance:
|
||||
max_distance = min_distance
|
||||
best_position = (x, y, width, height, angle)
|
||||
valid_position = True
|
||||
|
||||
if valid_position:
|
||||
placed_objects.append((best_position, get_rotated_corners(*best_position)))
|
||||
attempts = 0
|
||||
else:
|
||||
attempts += 1
|
||||
|
||||
return [obj[0] for obj in placed_objects]
|
||||
|
||||
|
||||
color_list = [
|
||||
'blue', 'green', 'orange', 'purple', 'red', 'yellow', 'pink',
|
||||
'cyan', 'magenta', 'lime', 'teal', 'lavender', 'brown', 'beige',
|
||||
'maroon', 'navy', 'olive', 'coral', 'turquoise', 'silver', 'gold'
|
||||
]
|
||||
|
||||
|
||||
def visualize_objects(objects, plane_width, plane_height, filename):
|
||||
fig, ax = plt.subplots()
|
||||
ax.set_xlim(-plane_width / 2, plane_width / 2)
|
||||
ax.set_ylim(-plane_height / 2, plane_height / 2)
|
||||
ax.set_aspect('equal')
|
||||
|
||||
for i, (x, y, width, height, angle) in enumerate(objects):
|
||||
cx = x
|
||||
cy = y
|
||||
color = color_list[i % len(color_list)] # 从颜色列表中选择颜色
|
||||
rect = patches.Rectangle((x - width / 2, y - height / 2), width, height, edgecolor='r', facecolor=color,
|
||||
alpha=0.5)
|
||||
t = plt.matplotlib.transforms.Affine2D().rotate_deg_around(cx, cy, angle) + ax.transData
|
||||
rect.set_transform(t)
|
||||
ax.add_patch(rect)
|
||||
|
||||
plt.grid(True)
|
||||
plt.savefig(filename, bbox_inches='tight')
|
||||
# plt.show()
|
||||
|
||||
|
||||
def create_grid(plane_width, plane_height, grid_size):
|
||||
grid_points = []
|
||||
for x in np.arange(-plane_width / 2, plane_width / 2, grid_size):
|
||||
for y in np.arange(-plane_height / 2, plane_height / 2, grid_size):
|
||||
grid_points.append((x, y))
|
||||
return grid_points
|
||||
|
||||
|
||||
def filter_occupied_grids(grid_points, placed_objects):
|
||||
available_points = grid_points.copy()
|
||||
|
||||
for obj in placed_objects:
|
||||
obj_poly = Polygon(obj[1]) # 获取已放置物体的多边形
|
||||
available_points = [
|
||||
point for point in available_points
|
||||
if not obj_poly.contains(Point(point))
|
||||
]
|
||||
|
||||
return available_points
|
||||
289
others/object.py
Normal file
289
others/object.py
Normal file
@@ -0,0 +1,289 @@
|
||||
|
||||
import numpy as np
|
||||
import pickle
|
||||
import json
|
||||
import os
|
||||
|
||||
from others.transforms import transform_coordinates_3d
|
||||
from grasp_nms import nms_grasp
|
||||
|
||||
class OmniObject:
|
||||
def __init__(self,
|
||||
name='obj',
|
||||
cam_info=None,
|
||||
type='Active',
|
||||
mask=None,
|
||||
pose=np.eye(4),
|
||||
size=np.array([0.001, 0.001, 0.001])
|
||||
):
|
||||
self.name = name
|
||||
self.cam_info = cam_info
|
||||
self.type = type
|
||||
self.obj_pose = pose
|
||||
self.obj_length = size
|
||||
|
||||
self.xyz = np.array([0,0,0])
|
||||
self.direction = np.array([0,0,0.05])
|
||||
self.direction_proposals = None
|
||||
self.elements = {}
|
||||
if name=='gripper':
|
||||
self.elements['active'] = {
|
||||
'push': [
|
||||
{
|
||||
"part": "finger edge",
|
||||
"task": "forward push",
|
||||
"xyz": np.array([0,0,0]),
|
||||
"direction": np.array([0,0,0.08]),
|
||||
},
|
||||
{
|
||||
"part": "finger edge",
|
||||
"task": "side push",
|
||||
"xyz": np.array([0,0,0]),
|
||||
"direction": np.array([1,0,0.3]),
|
||||
},
|
||||
{
|
||||
"part": "finger edge",
|
||||
"task": "side push",
|
||||
"xyz": np.array([0,0,0]),
|
||||
"direction": np.array([-1,0,0.3]),
|
||||
},
|
||||
],
|
||||
'click': {
|
||||
"part": "finger edge",
|
||||
"task": "click",
|
||||
"xyz": np.array([0,0,0]),
|
||||
"direction": np.array([0,0,1]),
|
||||
},
|
||||
'touch': {
|
||||
"part": "finger edge",
|
||||
"task": "touch",
|
||||
"xyz": np.array([0,0,0]),
|
||||
"direction": np.array([0,0,1]),
|
||||
},
|
||||
'pull': {
|
||||
"part": "finger edge",
|
||||
"task": "pull",
|
||||
"xyz": np.array([0,0,0]),
|
||||
"direction": np.array([0,0,0.08]),
|
||||
},
|
||||
'rotate': {
|
||||
"part": "finger edge",
|
||||
"task": "rotate",
|
||||
"xyz": np.array([0,0,0]),
|
||||
"direction": np.array([0,0,0.08]),
|
||||
}
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def from_obj_dir(cls, obj_dir, obj_info=None):
|
||||
if "interaction" in obj_info:
|
||||
obj_info = obj_info
|
||||
interaction_info=obj_info["interaction"]
|
||||
part_joint_limits_info = obj_info.get("part_joint_limits", None)
|
||||
|
||||
else:
|
||||
|
||||
obj_info_file = '%s/object_parameters.json'%obj_dir
|
||||
interaction_label_file = '%s/interaction.json'%obj_dir
|
||||
|
||||
assert os.path.exists(obj_info_file), 'object_parameters.json not found in %s'%obj_dir
|
||||
assert os.path.exists(interaction_label_file), 'interaction.json not found in %s'%obj_dir
|
||||
|
||||
|
||||
obj_info = json.load(open(obj_info_file))
|
||||
interaction_data = json.load(open(interaction_label_file))
|
||||
interaction_info = interaction_data['interaction']
|
||||
part_joint_limits_info = interaction_data.get("part_joint_limits", None)
|
||||
|
||||
obj = cls(
|
||||
name=obj_info['object_id'],
|
||||
size=obj_info['size']
|
||||
)
|
||||
|
||||
mesh_file = '%s/Aligned.obj'%obj_dir
|
||||
if os.path.exists(mesh_file):
|
||||
obj_info['mesh_file'] = mesh_file
|
||||
|
||||
obj.part_joint_limits = part_joint_limits_info
|
||||
|
||||
''' Load interaction labels '''
|
||||
obj.part_ids = []
|
||||
for type in ['active', 'passive']:
|
||||
if type not in interaction_info:
|
||||
continue
|
||||
for action in interaction_info[type]:
|
||||
action_info = interaction_info[type][action]
|
||||
# import ipdb;ipdb.set_trace()
|
||||
if action=='grasp' and type=='passive':
|
||||
for grasp_part in action_info:
|
||||
grasp_files = action_info[grasp_part]
|
||||
grasp_data={
|
||||
"grasp_pose": [],
|
||||
"width": []
|
||||
}
|
||||
if isinstance(grasp_files, str):
|
||||
grasp_files = [grasp_files]
|
||||
for grasp_file in grasp_files:
|
||||
grasp_file = '%s/%s'%(obj_dir, grasp_file)
|
||||
if not os.path.exists(grasp_file):
|
||||
print('Grasp file %s not found'%grasp_file)
|
||||
continue
|
||||
_data = pickle.load(open(grasp_file, 'rb'))
|
||||
_data['grasp_pose'] = np.array(_data['grasp_pose'])
|
||||
_data['width'] = np.array(_data['width'])
|
||||
|
||||
if _data['grasp_pose'].shape[0]==0:
|
||||
print('Empty grasp pose in %s'%grasp_file)
|
||||
continue
|
||||
grasp_data['grasp_pose'].append(_data['grasp_pose'])
|
||||
grasp_data['width'].append(_data['width'])
|
||||
if len(grasp_data['grasp_pose'])==0:
|
||||
print('Empty grasp pose in %s'%grasp_file)
|
||||
continue
|
||||
|
||||
grasp_data['grasp_pose'] = np.concatenate(grasp_data['grasp_pose'])
|
||||
grasp_data['width'] = np.concatenate(grasp_data['width'])
|
||||
|
||||
|
||||
N_grasp = grasp_data['grasp_pose'].shape[0]
|
||||
max_grasps = 100
|
||||
use_nms = True if N_grasp>60 else False
|
||||
if use_nms:
|
||||
# import ipdb;ipdb.set_trace()
|
||||
gripper_pose = grasp_data['grasp_pose']
|
||||
N = gripper_pose.shape[0]
|
||||
width = grasp_data['width'][:N, np.newaxis]
|
||||
|
||||
rotation = gripper_pose[:, :3, :3].reshape(N, -1)
|
||||
translation = gripper_pose[:, :3, 3]
|
||||
|
||||
height = 0.02 * np.ones_like(width)
|
||||
depth = np.zeros_like(width)
|
||||
score = np.ones_like(width) * 0.1
|
||||
obj_id = -1 * np.ones_like(width)
|
||||
|
||||
|
||||
grasp_group_array = np.concatenate([score, width, height, depth, rotation, translation, obj_id], axis=-1)
|
||||
|
||||
|
||||
if True:
|
||||
nms_info = interaction_info.get('grasp_nms_threshold', {'translation': 0.015, 'rotation': 25.0})
|
||||
translation_thresh = nms_info['translation']
|
||||
rotation_thresh = nms_info['rotation'] / 180.0 * np.pi
|
||||
grasp_group_array = nms_grasp(grasp_group_array, translation_thresh, rotation_thresh)
|
||||
|
||||
|
||||
rotation = grasp_group_array[:, 4:4+9]
|
||||
translation = grasp_group_array[:, 4+9:4+9+3]
|
||||
width = grasp_group_array[:, 1]
|
||||
grasp_data['grasp_pose'] = np.tile(np.eye(4), (grasp_group_array.shape[0], 1, 1))
|
||||
grasp_data['grasp_pose'][:, :3, :3] = rotation.reshape(-1,3,3)
|
||||
grasp_data['grasp_pose'][:, :3, 3] = translation
|
||||
grasp_data['width'] = width
|
||||
|
||||
print(f"Grasp num of {obj.name} after NMS: {grasp_data['grasp_pose'].shape[0]}/{N_grasp}")
|
||||
# downsample
|
||||
if grasp_data['grasp_pose'].shape[0]>max_grasps:
|
||||
grasp_num = grasp_data['grasp_pose'].shape[0]
|
||||
index = np.random.choice(grasp_num, max_grasps, replace=False)
|
||||
grasp_data['grasp_pose'] = grasp_data['grasp_pose'][index]
|
||||
grasp_data['width'] = grasp_data['width'][index]
|
||||
print(f"Grasp num of {obj.name} after downsample: {grasp_data['grasp_pose'].shape[0]}/{N_grasp}")
|
||||
else:
|
||||
print(f"Grasp num of {obj.name}: {grasp_data['grasp_pose'].shape[0]}/{N_grasp}")
|
||||
|
||||
action_info[grasp_part] = grasp_data
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
interaction_info[type][action] = action_info
|
||||
else:
|
||||
for primitive in action_info:
|
||||
for primitive_info in action_info[primitive]:
|
||||
if 'part_id' in primitive_info:
|
||||
obj.part_ids.append(primitive_info['part_id'])
|
||||
obj.elements = interaction_info
|
||||
obj.info = obj_info
|
||||
# import ipdb;ipdb.set_trace()
|
||||
obj.is_articulated = True if part_joint_limits_info is not None else False
|
||||
return obj
|
||||
|
||||
|
||||
|
||||
def set_element(self, element):
|
||||
action = element['action']
|
||||
self.elements[action] = element
|
||||
|
||||
def set_mask(self, mask, roi=None):
|
||||
self.mask = mask
|
||||
self.roi = roi
|
||||
|
||||
def set_pose(self, pose, length):
|
||||
self.obj_pose = pose
|
||||
self.obj_length = length
|
||||
|
||||
def set_part(self, xyz=None, direction=None, direction_proposals=None, relative=True):
|
||||
if xyz is not None:
|
||||
if not isinstance(xyz, np.ndarray):
|
||||
xyz = np.array(xyz)
|
||||
if relative:
|
||||
xyz = xyz * self.obj_length / 2.0
|
||||
self.xyz = xyz
|
||||
|
||||
if direction is not None:
|
||||
if not isinstance(direction, np.ndarray):
|
||||
direction = np.array(direction)
|
||||
direction = direction / np.linalg.norm(direction) * 0.08
|
||||
self.direction = direction
|
||||
|
||||
if direction_proposals is not None:
|
||||
self.direction_proposals = direction_proposals
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
def format_object(self, relative=True):
|
||||
xyz, direction = self.xyz, self.direction
|
||||
|
||||
obj_type = self.type.lower()
|
||||
if obj_type=='active':
|
||||
xyz_start = xyz
|
||||
xyz_end = xyz_start + direction
|
||||
elif obj_type=='passive' or obj_type=='plane':
|
||||
xyz_end = xyz
|
||||
xyz_start = xyz_end - direction
|
||||
|
||||
|
||||
arrow_in_obj = np.array([xyz_start, xyz_end]).transpose(1,0)
|
||||
arrow_in_world = transform_coordinates_3d(arrow_in_obj, self.obj_pose).transpose(1,0)
|
||||
|
||||
|
||||
xyz_start_world, xyz_end_world = arrow_in_world
|
||||
|
||||
direction_world = xyz_end_world - xyz_start_world
|
||||
direction_world = direction_world / np.linalg.norm(direction_world)
|
||||
|
||||
part2obj = np.eye(4)
|
||||
part2obj[:3, 3] = xyz_start
|
||||
self.obj2part = np.linalg.inv(part2obj)
|
||||
|
||||
|
||||
|
||||
|
||||
object_world = {
|
||||
'pose': self.obj_pose,
|
||||
'length': self.obj_length,
|
||||
'xyz_start': xyz_start,
|
||||
'xyz_end': xyz_end,
|
||||
'xyz_start_world': xyz_start_world,
|
||||
'xyz_end_world': xyz_end_world,
|
||||
'direction': direction_world,
|
||||
'obj2part': self.obj2part
|
||||
}
|
||||
return object_world
|
||||
|
||||
|
||||
83
others/sdf.py
Normal file
83
others/sdf.py
Normal file
@@ -0,0 +1,83 @@
|
||||
import numpy as np
|
||||
import open3d as o3d
|
||||
from others.transform_utils import transform_points
|
||||
|
||||
def compute_sdf_from_obj_surface(mesh, resolution=2): # 2mm
|
||||
if mesh is None or len(mesh.vertices) == 0:
|
||||
raise ValueError("Invalid mesh provided.")
|
||||
bounds_min, bounds_max = mesh.bounds # 网格点上面的这个有点问题。。。
|
||||
vertices = o3d.core.Tensor(mesh.vertices, dtype=o3d.core.Dtype.Float32)
|
||||
triangles = o3d.core.Tensor(mesh.faces, dtype=o3d.core.Dtype.UInt32)
|
||||
scene = o3d.t.geometry.RaycastingScene()
|
||||
scene.add_triangles(vertices, triangles)
|
||||
shape = np.ceil((bounds_max - bounds_min) / resolution).astype(int)
|
||||
|
||||
grid = np.mgrid[
|
||||
bounds_min[0]:bounds_max[0]:complex(0, shape[0]),
|
||||
bounds_min[1]:bounds_max[1]:complex(0, shape[1]),
|
||||
bounds_min[2]:bounds_max[2]:complex(0, shape[2])
|
||||
]
|
||||
grid = grid.reshape(3, -1).T
|
||||
|
||||
sdf_voxels = scene.compute_signed_distance(grid.astype(np.float32))
|
||||
|
||||
sdf_voxels = -sdf_voxels.cpu().numpy()
|
||||
sdf_voxels = sdf_voxels.reshape(shape)
|
||||
|
||||
return grid, sdf_voxels
|
||||
|
||||
|
||||
def compute_sdf_from_obj(obj_mes, bounds_max, bounds_min, resolution=2): # 2mm
|
||||
# 读取 OBJ 文件并转换为 Trimesh 对象
|
||||
|
||||
# 将 Trimesh 对象转换为 Open3D TriangleMesh
|
||||
vertices = o3d.core.Tensor(obj_mes.vertices, dtype=o3d.core.Dtype.Float32)
|
||||
triangles = o3d.core.Tensor(obj_mes.faces, dtype=o3d.core.Dtype.UInt32)
|
||||
|
||||
# 创建 RaycastingScene
|
||||
scene = o3d.t.geometry.RaycastingScene()
|
||||
_ = scene.add_triangles(vertices, triangles) # 物体都添加计算
|
||||
|
||||
# 创建 3D 网格以进行采样
|
||||
shape = np.ceil((np.array(bounds_max) - np.array(bounds_min)) / resolution).astype(int)
|
||||
steps = (np.array(bounds_max) - np.array(bounds_min)) / shape
|
||||
grid = np.mgrid[
|
||||
bounds_min[0]:bounds_max[0]:steps[0],
|
||||
bounds_min[1]:bounds_max[1]:steps[1],
|
||||
bounds_min[2]:bounds_max[2]:steps[2]
|
||||
]
|
||||
grid = grid.reshape(3, -1).T
|
||||
|
||||
# 计算 SDF
|
||||
sdf_voxels = scene.compute_signed_distance(grid.astype(np.float32))
|
||||
|
||||
# 转换为 NumPy 数组并调整形状
|
||||
sdf_voxels = sdf_voxels.cpu().numpy()
|
||||
sdf_voxels = -sdf_voxels # 翻转符号
|
||||
sdf_voxels = sdf_voxels.reshape(shape)
|
||||
|
||||
return grid, sdf_voxels
|
||||
|
||||
|
||||
# cost function
|
||||
def get_distance_with_sdf(collision_points, pose, sdf_func):
|
||||
transformed_pcs = transform_points(collision_points, pose) # 基于不同pose下point 坐标
|
||||
transformed_pcs_flatten = transformed_pcs.reshape(-1, 3) # [num_poses * num_points, 3]
|
||||
signed_distance = sdf_func(transformed_pcs_flatten) # [num_poses * num_points]
|
||||
return signed_distance
|
||||
|
||||
|
||||
def calculate_collision_cost_sdf(transformed_pcs_active, passive_pose, sdf_func, threshold):
|
||||
assert passive_pose.shape == (4, 4)
|
||||
|
||||
# Convert transformed points to the passive object's coordinate system
|
||||
inv_passive_pose = np.linalg.inv(passive_pose)
|
||||
transformed_pcs_in_passive = transform_points(transformed_pcs_active[0], inv_passive_pose[None])
|
||||
# Flatten the points for SDF function
|
||||
transformed_pcs_flatten = transformed_pcs_in_passive.reshape(-1, 3)
|
||||
# # Calculate signed distances
|
||||
signed_distance = sdf_func(transformed_pcs_flatten)
|
||||
# Penalize if any point is closer than the minimum distance
|
||||
collision_cost = -np.sum(np.maximum(0, threshold - signed_distance))
|
||||
|
||||
return collision_cost
|
||||
365
others/solver_2d.py
Normal file
365
others/solver_2d.py
Normal file
@@ -0,0 +1,365 @@
|
||||
from scipy.spatial.transform import Rotation as R
|
||||
|
||||
from others.layout_2d import DFS_Solver_Floor
|
||||
from others.multi_add_util import *
|
||||
from others.utils import axis_to_quaternion, quaternion_rotate, get_rotation_matrix_from_quaternion, \
|
||||
get_quaternion_from_rotation_matrix, get_xyz_euler_from_quaternion
|
||||
|
||||
|
||||
def rotate_along_axis(target_affine, angle_degrees, rot_axis='z', use_local=True):
|
||||
"""
|
||||
根据指定的角度和旋转轴来旋转target_affine。
|
||||
参数:
|
||||
- target_affine: 4x4 仿射变换矩阵
|
||||
- angle_degrees: 旋转角度(以度为单位)
|
||||
- rot_axis: 旋转轴,'x'、'y' 或 'z'
|
||||
"""
|
||||
# 将角度转换为弧度
|
||||
angle_radians = np.deg2rad(angle_degrees)
|
||||
|
||||
# 创建旋转对象
|
||||
if rot_axis == 'z':
|
||||
rotation_vector = np.array([0, 0, angle_radians])
|
||||
elif rot_axis == 'y':
|
||||
rotation_vector = np.array([0, angle_radians, 0])
|
||||
elif rot_axis == 'x':
|
||||
rotation_vector = np.array([angle_radians, 0, 0])
|
||||
else:
|
||||
raise ValueError("Invalid rotation axis. Please choose from 'x', 'y', 'z'.")
|
||||
|
||||
# 生成旋转矩阵
|
||||
R_angle = R.from_rotvec(rotation_vector).as_matrix()
|
||||
|
||||
# 提取旋转部分(3x3矩阵)
|
||||
target_rotation = target_affine[:3, :3]
|
||||
|
||||
# 将 target_rotation 绕指定轴旋转指定角度,得到 target_rotation_2
|
||||
if use_local:
|
||||
target_rotation_2 = np.dot(target_rotation, R_angle)
|
||||
else:
|
||||
target_rotation_2 = np.dot(R_angle, target_rotation)
|
||||
|
||||
# 重新组合旋转矩阵 target_rotation_2 和原始的平移部分
|
||||
target_affine_2 = np.eye(4)
|
||||
target_affine_2[:3, :3] = target_rotation_2
|
||||
target_affine_2[:3, 3] = target_affine[:3, 3] # 保留原始的平移部分
|
||||
|
||||
return target_affine_2
|
||||
|
||||
|
||||
|
||||
|
||||
def quaternion_multiply(q1, q2):
|
||||
"""计算两个四元数的乘积"""
|
||||
w1, x1, y1, z1 = q1
|
||||
w2, x2, y2, z2 = q2
|
||||
|
||||
w = w1 * w2 - x1 * x2 - y1 * y2 - z1 * z2
|
||||
x = w1 * x2 + x1 * w2 + y1 * z2 - z1 * y2
|
||||
y = w1 * y2 - x1 * z2 + y1 * w2 + z1 * x2
|
||||
z = w1 * z2 + x1 * y2 - y1 * x2 + z1 * w2
|
||||
|
||||
return np.array([w, x, y, z])
|
||||
|
||||
def quaternion_rotate_z(quaternion, angle):
|
||||
"""
|
||||
Rotate a quaternion around the global z-axis by a given angle.
|
||||
|
||||
Parameters:
|
||||
quaternion (numpy array): The input quaternion [w, x, y, z].
|
||||
angle (float): The rotation angle in degrees.
|
||||
|
||||
Returns:
|
||||
numpy array: The rotated quaternion.
|
||||
"""
|
||||
# Convert angle from degrees to radians
|
||||
angle_rad = np.radians(angle)
|
||||
|
||||
# Calculate the rotation quaternion for z-axis rotation
|
||||
cos_half_angle = np.cos(angle_rad / 2)
|
||||
sin_half_angle = np.sin(angle_rad / 2)
|
||||
q_z = np.array([cos_half_angle, 0, 0, sin_half_angle])
|
||||
|
||||
# Rotate the input quaternion around the global z-axis
|
||||
rotated_quaternion = quaternion_multiply(q_z, quaternion)
|
||||
|
||||
return rotated_quaternion
|
||||
|
||||
|
||||
|
||||
def rotate_point_ext(px, py, angle, ox, oy):
|
||||
s, c = math.sin(angle), math.cos(angle)
|
||||
px, py = px - ox, py - oy
|
||||
xnew = px * c - py * s
|
||||
ynew = px * s + py * c
|
||||
return xnew + ox, ynew + oy
|
||||
|
||||
def get_corners(pose, size, angle):
|
||||
cx, cy = pose
|
||||
w, h = size
|
||||
corners = [
|
||||
rotate_point_ext(cx - w / 2, cy - h / 2, angle, cx, cy),
|
||||
rotate_point_ext(cx + w / 2, cy - h / 2, angle, cx, cy),
|
||||
rotate_point_ext(cx + w / 2, cy + h / 2, angle, cx, cy),
|
||||
rotate_point_ext(cx - w / 2, cy + h / 2, angle, cx, cy)
|
||||
]
|
||||
return corners
|
||||
|
||||
def compute_bounding_box(objects, expansion=40):
|
||||
all_corners = []
|
||||
for pose, size, angle in objects:
|
||||
all_corners.extend(get_corners(pose, size, angle))
|
||||
|
||||
min_x = min(x for x, y in all_corners)
|
||||
max_x = max(x for x, y in all_corners)
|
||||
min_y = min(y for x, y in all_corners)
|
||||
max_y = max(y for x, y in all_corners)
|
||||
|
||||
center_x = (min_x + max_x) / 2
|
||||
center_y = (min_y + max_y) / 2
|
||||
width = max_x - min_x + expansion
|
||||
height = max_y - min_y + expansion
|
||||
|
||||
return (center_x, center_y, width, height, 0)
|
||||
|
||||
|
||||
def compute_intersection(bbox, plane_center, plane_width, plane_height):
|
||||
# 解包最小外接矩形的信息
|
||||
bbox_center_x, bbox_center_y, bbox_width, bbox_height, _ = bbox
|
||||
|
||||
# 计算最小外接矩形的边界
|
||||
min_x = bbox_center_x - bbox_width / 2
|
||||
max_x = bbox_center_x + bbox_width / 2
|
||||
min_y = bbox_center_y - bbox_height / 2
|
||||
max_y = bbox_center_y + bbox_height / 2
|
||||
|
||||
# 计算平面的边界
|
||||
plane_min_x = plane_center[0] - plane_width / 2
|
||||
plane_max_x = plane_center[0] + plane_width / 2
|
||||
plane_min_y = plane_center[1] - plane_height / 2
|
||||
plane_max_y = plane_center[1] + plane_height / 2
|
||||
|
||||
# 计算相交部分的边界
|
||||
intersect_min_x = max(min_x, plane_min_x)
|
||||
intersect_max_x = min(max_x, plane_max_x)
|
||||
intersect_min_y = max(min_y, plane_min_y)
|
||||
intersect_max_y = min(max_y, plane_max_y)
|
||||
|
||||
# 检查相交区域是否有效
|
||||
if intersect_min_x < intersect_max_x and intersect_min_y < intersect_max_y:
|
||||
# 计算相交矩形的中心和尺寸
|
||||
intersect_center_x = (intersect_min_x + intersect_max_x) / 2
|
||||
intersect_center_y = (intersect_min_y + intersect_max_y) / 2
|
||||
intersect_width = intersect_max_x - intersect_min_x
|
||||
intersect_height = intersect_max_y - intersect_min_y
|
||||
|
||||
return (intersect_center_x, intersect_center_y, intersect_width, intersect_height,0)
|
||||
else:
|
||||
# 如果没有有效的相交区域,返回 None 或其他指示无相交的值
|
||||
return None
|
||||
|
||||
|
||||
class LayoutSolver2D:
|
||||
'''
|
||||
1. get room_vertices
|
||||
2. Generate Constraint with LLM
|
||||
3. Generate layout meet to the constraint
|
||||
'''
|
||||
|
||||
def __init__(self, workspace_xyz, workspace_size, objects=None, fix_obj_ids=[], obj_infos={}, angle_step=15):
|
||||
self.angle_step = angle_step
|
||||
x_half, y_half, z_half = workspace_size / 2
|
||||
room_vertices = [
|
||||
[-x_half, -y_half],
|
||||
[x_half, -y_half],
|
||||
[x_half, y_half],
|
||||
[-x_half, y_half]
|
||||
]
|
||||
self.plane_width = workspace_size[0]
|
||||
self.plane_height = workspace_size[1]
|
||||
self.room_vertices = room_vertices
|
||||
|
||||
self.objects = objects
|
||||
self.obj_infos = obj_infos
|
||||
|
||||
|
||||
self.cx, self.cy, self.cz = (coord * 1000 for coord in workspace_xyz)
|
||||
self.workspace_Z_half = workspace_size[2] / 2.0
|
||||
self.z_offset = 20
|
||||
self.fix_obj_ids = fix_obj_ids
|
||||
|
||||
def parse_solution(self, solutions, obj_id):
|
||||
[obj_cx, obj_cy], rotation, _ = solutions[obj_id][:3]
|
||||
obj_xyz = np.array([
|
||||
self.cx + obj_cx,
|
||||
self.cy + obj_cy,
|
||||
self.cz - self.workspace_Z_half + self.objects[obj_id].size[2]/2.0 + self.z_offset
|
||||
])
|
||||
init_quat = axis_to_quaternion(self.objects[obj_id].up_axis, "z")
|
||||
obj_quat = quaternion_rotate(init_quat, self.objects[obj_id].up_axis, -rotation)
|
||||
|
||||
obj_pose = np.eye(4)
|
||||
obj_pose[:3, :3] = get_rotation_matrix_from_quaternion(obj_quat)
|
||||
obj_pose[:3, 3] = obj_xyz
|
||||
self.objects[obj_id].obj_pose = obj_pose
|
||||
|
||||
|
||||
def old_solution(self,
|
||||
opt_obj_ids,
|
||||
exist_obj_ids,
|
||||
object_extent=50, # 外拓5cm
|
||||
start_with_edge=False,
|
||||
grid_size=0.01 # 1cm
|
||||
):
|
||||
solver = DFS_Solver_Floor(grid_size=int(grid_size*1000))
|
||||
room_poly = Polygon(self.room_vertices)
|
||||
grid_points = solver.create_grids(room_poly)
|
||||
|
||||
objs_succ = []
|
||||
saved_solutions = {} # TODO 将exist_obj_ids的pose保存进saved_solutions
|
||||
for obj_id in opt_obj_ids:
|
||||
size = self.objects[obj_id].size
|
||||
# import pdb;pdb.set_trace()
|
||||
size_extent = size[:2] + object_extent
|
||||
|
||||
|
||||
solutions = solver.get_all_solutions(room_poly, grid_points, size_extent)
|
||||
if len(solutions)>0:
|
||||
if start_with_edge:
|
||||
solutions = solver.place_edge(room_poly, solutions, size_extent)
|
||||
if len(saved_solutions) ==0:
|
||||
saved_solutions[obj_id] = random.choice(solutions)
|
||||
else:
|
||||
solutions = solver.filter_collision(saved_solutions, solutions)
|
||||
if len(solutions)>0:
|
||||
saved_solutions[obj_id] = random.choice(solutions)
|
||||
else:
|
||||
print(f"No valid solutions for apply {obj_id}.")
|
||||
continue
|
||||
|
||||
else:
|
||||
print(f"No valid solutions for apply {obj_id}.")
|
||||
continue
|
||||
|
||||
|
||||
self.parse_solution(saved_solutions, obj_id)
|
||||
objs_succ.append(obj_id)
|
||||
|
||||
return objs_succ
|
||||
|
||||
def __call__(self,
|
||||
opt_obj_ids,
|
||||
exist_obj_ids,
|
||||
object_extent=50, # 外拓5cm
|
||||
start_with_edge=False,
|
||||
key_obj = True,
|
||||
grid_size=0.01, # 1cm
|
||||
initial_angle = 0
|
||||
):
|
||||
|
||||
objs_succ = []
|
||||
fail_objs = []
|
||||
placed_objects = []
|
||||
main_objects = []
|
||||
label_flag = "no extra"
|
||||
|
||||
if not key_obj:
|
||||
for obj_id in self.objects:
|
||||
if obj_id not in opt_obj_ids:
|
||||
if obj_id in self.fix_obj_ids:
|
||||
continue
|
||||
size = self.objects[obj_id].size
|
||||
pose = self.objects[obj_id].obj_pose
|
||||
quaternion_rotate = get_quaternion_from_rotation_matrix(pose[:3, :3])
|
||||
angle_pa = get_xyz_euler_from_quaternion(quaternion_rotate)[2]
|
||||
main_objects.append(((pose[0, 3] - self.cx, pose[1, 3] - self.cy), (size[0], size[1]), angle_pa))
|
||||
# main_objects.append(((pose[0, 3] - self.cx, pose[1, 3] - self.cy), (size[0], size[1]), angle_pa * (180 / math.pi)))
|
||||
main_bounding_box_info = compute_bounding_box(main_objects, expansion=object_extent)
|
||||
|
||||
intersection = compute_intersection(main_bounding_box_info, (0, 0), self.plane_width, self.plane_height)
|
||||
if intersection is None:
|
||||
return objs_succ
|
||||
placed_objects.append((intersection, get_rotated_corners(*intersection)))
|
||||
label_flag = "add extra"
|
||||
object_extent = 0
|
||||
|
||||
obj_sizes = []
|
||||
grid_points = create_grid(self.plane_width, self.plane_height, int(grid_size*1000))
|
||||
saved_solutions = {}
|
||||
if len(opt_obj_ids) ==1:
|
||||
attempts = 800
|
||||
else:
|
||||
attempts = 400
|
||||
|
||||
for obj_id in opt_obj_ids:
|
||||
size = self.objects[obj_id].size
|
||||
_extent = self.obj_infos[obj_id].get("extent", object_extent)
|
||||
size_extent = size[:2] + _extent
|
||||
obj_sizes.append((size_extent[0],size_extent[1]))
|
||||
width, height = size_extent[0],size_extent[1]
|
||||
area_ratio = (width * height) / (self.plane_width * self.plane_height)
|
||||
|
||||
|
||||
# while len(placed_objects) < num_objects and attempts < max_attempts:
|
||||
# width, height = obj_sizes[len(placed_objects)]
|
||||
valid_position = False
|
||||
best_position = None
|
||||
max_distance = 1
|
||||
available_grid_points = filter_occupied_grids(grid_points, placed_objects)
|
||||
for idx in range(attempts):
|
||||
if not available_grid_points:
|
||||
break
|
||||
if len(opt_obj_ids) == 1 and area_ratio > 0.5:
|
||||
if idx < len(available_grid_points):
|
||||
x, y = available_grid_points[idx] # 使用索引获取点
|
||||
angle = random.choice([0,90,180,270])
|
||||
else:
|
||||
# 如果索引超出范围,可以选择退出循环或采取其他措施
|
||||
break
|
||||
else:
|
||||
x, y = random.choice(available_grid_points) # 随机选择一个点
|
||||
angle = np.random.choice(np.arange(0, 360, self.angle_step))
|
||||
# x, y = available_grid_points[idx] # random.choice(available_grid_points)
|
||||
# x = random.uniform(-self.plane_width / 2 + width / 2, self.plane_width / 2 - width / 2)
|
||||
# y = random.uniform(-self.plane_height / 2 + height / 2, self.plane_height / 2 - height / 2)
|
||||
# if len(placed_objects)==0:
|
||||
# angle = initial_angle
|
||||
# else:
|
||||
|
||||
|
||||
new_corners = get_rotated_corners(x, y, width, height, angle)
|
||||
|
||||
if is_within_bounds(new_corners, self.plane_width, self.plane_height) and not is_collision(new_corners, placed_objects):
|
||||
if not placed_objects:
|
||||
best_position = (x, y, width, height, angle)
|
||||
valid_position = True
|
||||
break
|
||||
|
||||
min_distance = min(calculate_distance(new_corners, obj[1]) for obj in placed_objects)
|
||||
if min_distance > max_distance:
|
||||
max_distance = min_distance
|
||||
best_position = (x, y, width, height, angle)
|
||||
valid_position = True
|
||||
# break
|
||||
|
||||
if valid_position:
|
||||
placed_objects.append((best_position, get_rotated_corners(*best_position)))
|
||||
saved_solutions[obj_id] = [[best_position[0],best_position[1]],best_position[4],None]
|
||||
self.parse_solution(saved_solutions, obj_id)
|
||||
objs_succ.append(obj_id)
|
||||
else:
|
||||
fail_objs.append(obj_id)
|
||||
|
||||
|
||||
# objects = [obj[0] for obj in placed_objects]
|
||||
# visualize_objects(objects, self.plane_width, self.plane_height,str(obj_id)+label_flag+".jpg")
|
||||
if len(fail_objs)>0:
|
||||
print("*******no solution objects************")
|
||||
print(fail_objs)
|
||||
print("******************")
|
||||
|
||||
for obj_id in objs_succ:
|
||||
self.objects[obj_id].obj_pose = rotate_along_axis(self.objects[obj_id].obj_pose, random.uniform(-self.angle_step/2.0, self.angle_step/2.0), rot_axis='z', use_local=False)
|
||||
return objs_succ
|
||||
|
||||
|
||||
166
others/solver_3d.py
Normal file
166
others/solver_3d.py
Normal file
@@ -0,0 +1,166 @@
|
||||
import numpy as np
|
||||
from scipy.interpolate import RegularGridInterpolator
|
||||
from scipy.optimize import dual_annealing
|
||||
from scipy.spatial.transform import Rotation as R
|
||||
|
||||
from others.cost_function import get_cost_func
|
||||
from others.transform_utils import unnormalize_vars, \
|
||||
normalize_vars, pose2mat, euler2quat
|
||||
|
||||
np.random.seed(0)
|
||||
|
||||
|
||||
# align
|
||||
def generate_random_pose(mesh, plane_size):
|
||||
bounding_box = mesh.bounds
|
||||
min_corner = bounding_box[0]
|
||||
max_corner = bounding_box[1]
|
||||
|
||||
object_width = max_corner[0] - min_corner[0]
|
||||
object_height = max_corner[1] - min_corner[1]
|
||||
position_range_x = (plane_size[0] / 2) - (object_width / 2)
|
||||
position_range_y = (plane_size[1] / 2) - (object_height / 2)
|
||||
position_x = np.random.uniform(-position_range_x, position_range_x)
|
||||
position_y = np.random.uniform(-position_range_y, position_range_y)
|
||||
yaw = np.random.uniform(0, 360)
|
||||
|
||||
rotation = R.from_euler('z', yaw, degrees=True).as_matrix()
|
||||
pose = np.eye(4)
|
||||
pose[:3, :3] = rotation
|
||||
pose[:3, 3] = [position_x, position_y, -min_corner[2] + 0.002]
|
||||
|
||||
return pose
|
||||
|
||||
|
||||
def passive_setup_sdf(bounding_box_range, sdf_voxels):
|
||||
# create callable sdf function with interpolation
|
||||
min_corner = bounding_box_range[0]
|
||||
max_corner = bounding_box_range[1]
|
||||
|
||||
x = np.linspace(min_corner[0], max_corner[0], sdf_voxels.shape[0])
|
||||
y = np.linspace(min_corner[1], max_corner[1], sdf_voxels.shape[1])
|
||||
z = np.linspace(min_corner[2], max_corner[2], sdf_voxels.shape[2])
|
||||
sdf_func = RegularGridInterpolator((x, y, z), sdf_voxels, bounds_error=False, fill_value=0)
|
||||
return sdf_func
|
||||
|
||||
|
||||
def passive_setup_sdf_all(bounds_max, bounds_min, sdf_voxels):
|
||||
x = np.linspace(bounds_min[0], bounds_max[0], sdf_voxels.shape[0])
|
||||
y = np.linspace(bounds_min[1], bounds_max[1], sdf_voxels.shape[1])
|
||||
z = np.linspace(bounds_min[2], bounds_max[2], sdf_voxels.shape[2])
|
||||
sdf_func = RegularGridInterpolator((x, y, z), sdf_voxels, bounds_error=False, fill_value=0)
|
||||
return sdf_func
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
class LayoutSolver3D:
|
||||
def __init__(self, workspace_xyz, workspace_size, objects=None, obj_infos=None):
|
||||
x_half, y_half, z_half = workspace_size / 2
|
||||
bounds = {"min_bound": [-x_half, -y_half, 0.1], "max_bound": [x_half, y_half, z_half]}
|
||||
self.bounds_min = bounds["min_bound"]
|
||||
self.bounds_max = bounds["max_bound"]
|
||||
self.workspace_size = np.array(bounds["max_bound"]) - np.array(bounds["min_bound"])
|
||||
|
||||
self.objects = objects
|
||||
|
||||
def init_pose(self, opt_obj_ids, constraint):
|
||||
active_obj_id, passive_obj_id, constraint = constraint['active'], constraint['passive'], constraint[
|
||||
'constraint']
|
||||
obj_active = self.objects[active_obj_id]
|
||||
obj_passive = self.objects[passive_obj_id]
|
||||
|
||||
# if 'out' in constraints or 'on' in constraints or 'in' in constraints:
|
||||
# pose_passive = generate_random_pose(obj_passive.mesh, self.workspace_size)
|
||||
# self.update_obj(passive_obj_id, pose_passive)
|
||||
|
||||
pose_active = generate_random_pose(obj_active.mesh, self.workspace_size)
|
||||
self.update_obj(active_obj_id, pose_active)
|
||||
|
||||
self.safe_distance = np.mean(obj_active.size[:2]) + np.mean(obj_passive.size[:2]) / 2 + 5
|
||||
|
||||
optimize_z = constraint == 'on'
|
||||
if optimize_z:
|
||||
pose_bounds_min_ac = np.append(self.bounds_min[:2], obj_passive.obj_pose[2, 3] + obj_passive.size[2] / 2.0)
|
||||
pose_bounds_max_ac = np.append(self.bounds_max[:2], self.bounds_max[2])
|
||||
else:
|
||||
pose_bounds_min_ac = np.append(self.bounds_min[:2], pose_active[2, 3])
|
||||
pose_bounds_max_ac = np.append(self.bounds_max[:2], pose_active[2, 3])
|
||||
|
||||
# Define bounds for optimization
|
||||
rot_bounds_min = np.array([0])
|
||||
rot_bounds_max = np.array([np.radians(360)])
|
||||
self.og_bounds = [(b_min, b_max) for b_min, b_max in zip(
|
||||
np.concatenate([pose_bounds_min_ac, rot_bounds_min]),
|
||||
np.concatenate([pose_bounds_max_ac, rot_bounds_max])
|
||||
)]
|
||||
self.norm_bounds = np.array([(-1, 1)] * len(self.og_bounds))
|
||||
|
||||
# Initialize optimization
|
||||
translation_active = pose_active[:3, 3]
|
||||
rotation_active = R.from_matrix(pose_active[:3, :3])
|
||||
euler_angles_active = rotation_active.as_euler('xyz')
|
||||
st_pose_euler_active = np.append(translation_active, euler_angles_active[2])
|
||||
init_x = normalize_vars(st_pose_euler_active, self.og_bounds)
|
||||
return init_x
|
||||
|
||||
def update_obj(self, obj_id, pose):
|
||||
self.objects[obj_id].obj_pose = pose
|
||||
|
||||
def cost_function(self, opt_vars, opt_ids, constraints):
|
||||
# Update obj pose
|
||||
for i in range(len(opt_ids)):
|
||||
obj_id = opt_ids[i]
|
||||
xyz_yaw = unnormalize_vars(opt_vars[i * 4:i * 4 + 4], self.og_bounds)
|
||||
|
||||
pose = pose2mat([xyz_yaw[:3], euler2quat(np.append([0, 0], xyz_yaw[3]))])
|
||||
self.update_obj(obj_id, pose)
|
||||
|
||||
# Calculate cost for each constraint
|
||||
cost = 0
|
||||
for info in constraints:
|
||||
active_id, passive_id, constraint = info['active'], info['passive'], info['constraint']
|
||||
cost += get_cost_func(constraint)(
|
||||
self.objects[active_id],
|
||||
self.objects[passive_id],
|
||||
safe_distance=self.safe_distance
|
||||
)
|
||||
return cost
|
||||
|
||||
def __call__(self, opt_obj_ids, exist_obj_ids, constraint, visual=False):
|
||||
x0 = self.init_pose(opt_obj_ids, constraint)
|
||||
|
||||
constraints = [constraint]
|
||||
constraints.append({'active': constraint['active'], 'passive': constraint['passive'], 'constraint': 'out'})
|
||||
|
||||
saved_solutions = {}
|
||||
# Optimization
|
||||
opt_result = dual_annealing(
|
||||
func=self.cost_function,
|
||||
args=[opt_obj_ids, constraints],
|
||||
bounds=self.norm_bounds,
|
||||
maxfun=10000,
|
||||
x0=x0,
|
||||
no_local_search=True,
|
||||
minimizer_kwargs={
|
||||
'method': 'L-BFGS-B',
|
||||
'options': {'maxiter': 200},
|
||||
},
|
||||
restart_temp_ratio=2e-5 #
|
||||
)
|
||||
|
||||
if opt_result.success:
|
||||
print("Optimization successful.")
|
||||
return opt_obj_ids
|
||||
else:
|
||||
print("Optimization failed:", opt_result.message)
|
||||
saved_solutions = None
|
||||
|
||||
return saved_solutions
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
241
others/task_generate.py
Normal file
241
others/task_generate.py
Normal file
@@ -0,0 +1,241 @@
|
||||
# old dependencies ----------------------------------------------------
|
||||
|
||||
import os
|
||||
import json
|
||||
import numpy as np
|
||||
from others.object import OmniObject
|
||||
from others.layout_object import LayoutObject
|
||||
from others.solver_2d import LayoutSolver2D
|
||||
from others.solver_3d import LayoutSolver3D
|
||||
|
||||
from utils.pose import PoseUtil
|
||||
|
||||
|
||||
def list_to_dict(data: list):
|
||||
tmp = {}
|
||||
for i in range(len(data)):
|
||||
tmp[str(i)] = data[i]
|
||||
return tmp
|
||||
|
||||
|
||||
class LayoutGenerator():
|
||||
def __init__(self, workspace, obj_infos, objects, key_obj_ids, extra_obj_ids, constraint=None, fix_obj_ids=[]):
|
||||
self.workspace = workspace
|
||||
self.objects = objects
|
||||
self.obj_infos = obj_infos
|
||||
|
||||
self.key_obj_ids = key_obj_ids
|
||||
self.extra_obj_ids = extra_obj_ids
|
||||
self.fix_obj_ids = fix_obj_ids
|
||||
self.constraint = constraint
|
||||
|
||||
if constraint is None:
|
||||
self.key_obj_ids_2d = self.key_obj_ids
|
||||
self.key_obj_ids_3d = []
|
||||
else:
|
||||
self.key_obj_ids_2d = [constraint["passive"]]
|
||||
self.key_obj_ids_3d = [constraint["active"]]
|
||||
self.constraint = constraint
|
||||
|
||||
workspace_xyz, workspace_size = np.array(workspace['position']), np.array(workspace['size'])
|
||||
workspace_size = workspace_size * 1000
|
||||
# extra info about workspace
|
||||
|
||||
self.solver_2d = LayoutSolver2D(workspace_xyz, workspace_size, objects, fix_obj_ids=fix_obj_ids,
|
||||
obj_infos=obj_infos)
|
||||
self.solver_3d = LayoutSolver3D(workspace_xyz, workspace_size, objects, obj_infos=obj_infos)
|
||||
|
||||
self.succ_obj_ids = []
|
||||
|
||||
def __call__(self):
|
||||
''' Generate Layout '''
|
||||
# import pdb;pdb.set_trace()
|
||||
if len(self.key_obj_ids_2d) > 0:
|
||||
objs_succ = self.solver_2d(self.key_obj_ids_2d, self.succ_obj_ids, object_extent=30, start_with_edge=True,
|
||||
key_obj=True, initial_angle=0)
|
||||
self.update_obj_info(objs_succ)
|
||||
print('-- 2d layout done --')
|
||||
|
||||
if len(self.key_obj_ids_3d) > 0:
|
||||
objs_succ = self.solver_3d(self.key_obj_ids_3d, self.succ_obj_ids, constraint=self.constraint)
|
||||
self.update_obj_info(objs_succ)
|
||||
print('-- 3d layout done --')
|
||||
|
||||
if len(self.extra_obj_ids) > 0:
|
||||
objs_succ = self.solver_2d(self.extra_obj_ids, self.succ_obj_ids, object_extent=30, start_with_edge=False,
|
||||
key_obj=False)
|
||||
self.update_obj_info(objs_succ)
|
||||
print('-- extra layout done --')
|
||||
|
||||
''' Check completion '''
|
||||
res_infos = []
|
||||
if len(self.key_obj_ids) > 0:
|
||||
for obj_id in self.key_obj_ids:
|
||||
if obj_id not in self.succ_obj_ids:
|
||||
return None
|
||||
res_infos.append(self.obj_infos[obj_id])
|
||||
if len(self.extra_obj_ids) > 0:
|
||||
if len(self.succ_obj_ids) > 0:
|
||||
for obj_id in self.succ_obj_ids:
|
||||
res_infos.append(self.obj_infos[obj_id])
|
||||
|
||||
return res_infos
|
||||
|
||||
def update_obj_info(self, obj_ids):
|
||||
if not isinstance(obj_ids, list):
|
||||
obj_ids = [obj_ids]
|
||||
for obj_id in obj_ids:
|
||||
pose = self.objects[obj_id].obj_pose
|
||||
xyz, quat = pose[:3, 3], PoseUtil.get_quaternion_wxyz_from_rotation_matrix(pose[:3, :3])
|
||||
self.obj_infos[obj_id]['position'] = (xyz / 1000).tolist()
|
||||
self.obj_infos[obj_id]['quaternion'] = quat.tolist()
|
||||
self.obj_infos[obj_id]["is_key"] = obj_id in self.key_obj_ids
|
||||
self.succ_obj_ids.append(obj_id)
|
||||
|
||||
|
||||
class OldTaskGenerator:
|
||||
def __init__(self, task_template):
|
||||
self.data_root = os.path.dirname(os.path.dirname(__file__)) + "/assets"
|
||||
self.init_info(task_template)
|
||||
|
||||
def _load_json(self, relative_path):
|
||||
with open(os.path.join(self.data_root, relative_path), 'r') as file:
|
||||
return json.load(file)
|
||||
|
||||
def init_info(self, task_template):
|
||||
# Load all objects & constraints
|
||||
self.fix_objs = task_template["objects"].get('fix_objects', [])
|
||||
all_objs = task_template["objects"]["task_related_objects"] + task_template["objects"][
|
||||
"extra_objects"] + self.fix_objs
|
||||
self.fix_obj_ids = [obj["object_id"] for obj in self.fix_objs]
|
||||
self.key_obj_ids, self.extra_obj_ids = {'0': []}, {'0': []}
|
||||
for obj in task_template["objects"]["task_related_objects"]:
|
||||
ws_id = obj.get("workspace_id", "0")
|
||||
if ws_id not in self.key_obj_ids:
|
||||
self.key_obj_ids[ws_id] = []
|
||||
self.key_obj_ids[ws_id].append(obj["object_id"])
|
||||
for obj in task_template["objects"]["extra_objects"]:
|
||||
ws_id = obj.get("workspace_id", "0")
|
||||
if ws_id not in self.extra_obj_ids:
|
||||
self.extra_obj_ids[ws_id] = []
|
||||
self.extra_obj_ids[ws_id].append(obj["object_id"])
|
||||
|
||||
obj_infos = {}
|
||||
objects = {}
|
||||
all_key_objs = [obj_id for ws_id in self.key_obj_ids for obj_id in self.key_obj_ids[ws_id]]
|
||||
for obj in all_objs:
|
||||
obj_id = obj["object_id"]
|
||||
if obj_id == "fix_pose":
|
||||
info = dict()
|
||||
info['object_id'] = obj_id
|
||||
info['position'] = obj['position']
|
||||
info['direction'] = obj['direction']
|
||||
obj_infos[obj_id] = info
|
||||
objects[obj_id] = OmniObject("fix_pose")
|
||||
else:
|
||||
obj_dir = os.path.join(self.data_root, obj["data_info_dir"])
|
||||
if "metadata" in obj:
|
||||
print(obj["metadata"])
|
||||
info = obj["metadata"]["info"]
|
||||
info["interaction"] = obj["metadata"]["interaction"]
|
||||
else:
|
||||
info = json.load(open(obj_dir + "/object_parameters.json"))
|
||||
info["data_info_dir"] = obj_dir
|
||||
info["obj_path"] = obj_dir + "/Aligned.obj"
|
||||
info['object_id'] = obj_id
|
||||
if "add_particle" in obj:
|
||||
info["add_particle"] = obj["add_particle"]
|
||||
if 'extent' in obj:
|
||||
info['extent'] = obj['extent']
|
||||
obj_infos[obj_id] = info
|
||||
objects[obj_id] = LayoutObject(info, use_sdf=obj_id in all_key_objs)
|
||||
|
||||
self.obj_infos, self.objects = obj_infos, objects
|
||||
|
||||
self.fix_obj_infos = []
|
||||
for fix_obj in self.fix_objs:
|
||||
fix_obj['is_key'] = True
|
||||
fix_obj.update(obj_infos[fix_obj['object_id']])
|
||||
self.fix_obj_infos.append(fix_obj)
|
||||
|
||||
if "robot" not in task_template:
|
||||
arm = "right"
|
||||
robot_id = "A2D"
|
||||
else:
|
||||
arm = task_template["robot"]["arm"]
|
||||
robot_id = task_template["robot"]["robot_id"]
|
||||
|
||||
scene_info = task_template["scene"]
|
||||
self.scene_usd = task_template["scene"]["scene_usd"]
|
||||
self.task_template = {
|
||||
"scene_usd": self.scene_usd,
|
||||
"arm": arm,
|
||||
"task_name": task_template["task"],
|
||||
"robot_id": robot_id,
|
||||
"stages": task_template['stages'],
|
||||
"object_with_material": task_template.get('object_with_material', {}),
|
||||
"lights": task_template.get('lights', {}),
|
||||
"cameras": task_template.get('cameras', {}),
|
||||
"objects": []
|
||||
}
|
||||
constraint = task_template.get("constraints")
|
||||
robot_init_workspace_id = scene_info["scene_id"].split('/')[-1]
|
||||
|
||||
# Retrieve scene information
|
||||
self.scene_usd = scene_info["scene_usd"]
|
||||
if "function_space_objects" in scene_info:
|
||||
workspaces = scene_info["function_space_objects"]
|
||||
if robot_init_workspace_id not in task_template["robot"]["robot_init_pose"]:
|
||||
self.robot_init_pose = task_template["robot"]["robot_init_pose"]
|
||||
else:
|
||||
self.robot_init_pose = task_template["robot"]["robot_init_pose"][robot_init_workspace_id]
|
||||
else:
|
||||
scene_info = self._load_json(scene_info["scene_info_dir"] + "/scene_parameters.json")
|
||||
workspaces = scene_info["function_space_objects"]
|
||||
# Normalize format
|
||||
if isinstance(scene_info["robot_init_pose"], list):
|
||||
scene_info['robot_init_pose'] = list_to_dict(scene_info["robot_init_pose"])
|
||||
self.robot_init_pose = scene_info["robot_init_pose"][robot_init_workspace_id]
|
||||
if isinstance(workspaces, list):
|
||||
workspaces = list_to_dict(workspaces)
|
||||
workspaces = {'0': workspaces[robot_init_workspace_id]}
|
||||
elif isinstance(workspaces, dict) and 'position' in workspaces:
|
||||
workspaces = {'0': workspaces}
|
||||
self.layouts = {}
|
||||
|
||||
for key in workspaces:
|
||||
ws, key_ids, extra_ids = workspaces[key], self.key_obj_ids.get(key, []), self.extra_obj_ids.get(key, [])
|
||||
self.layouts[key] = LayoutGenerator(ws, obj_infos, objects, key_ids, extra_ids, constraint=constraint,
|
||||
fix_obj_ids=self.fix_obj_ids)
|
||||
|
||||
def generate_tasks(self, save_path, task_num, task_name):
|
||||
os.makedirs(save_path, exist_ok=True)
|
||||
|
||||
for i in range(task_num):
|
||||
output_file = os.path.join(save_path, f'{task_name}_%d.json' % (i))
|
||||
self.task_template['objects'] = []
|
||||
self.task_template['objects'] += self.fix_obj_infos
|
||||
|
||||
flag_failed = False
|
||||
for key in self.layouts:
|
||||
obj_infos = self.layouts[key]()
|
||||
if not obj_infos:
|
||||
if obj_infos is None:
|
||||
flag_failed = True
|
||||
break
|
||||
continue
|
||||
self.task_template['objects'] += obj_infos
|
||||
for object_info in self.obj_infos:
|
||||
if object_info == 'fix_pose':
|
||||
fix_pose_dict = self.obj_infos['fix_pose']
|
||||
self.task_template['objects'].append(fix_pose_dict)
|
||||
break
|
||||
if flag_failed:
|
||||
print(f"Failed to place key object, skipping")
|
||||
continue
|
||||
|
||||
print('Saved task json to %s' % output_file)
|
||||
with open(output_file, 'w') as f:
|
||||
json.dump(self.task_template, f, indent=4)
|
||||
|
||||
# -------------------------------------------------------------
|
||||
195
others/transform_utils.py
Normal file
195
others/transform_utils.py
Normal file
@@ -0,0 +1,195 @@
|
||||
|
||||
import numpy as np
|
||||
import open3d as o3d
|
||||
from sklearn.cluster import KMeans
|
||||
|
||||
|
||||
def transform_points(points, transform_matrix):
|
||||
# 确保输入的点是一个 Nx3 的数组
|
||||
assert points.shape[1] == 3, "输入的点必须是 Nx3 的数组"
|
||||
assert transform_matrix.shape == (4, 4), "变换矩阵必须是 4x4 的"
|
||||
|
||||
# 将点扩展为齐次坐标 (N x 4)
|
||||
ones = np.ones((points.shape[0], 1))
|
||||
points_homogeneous = np.hstack((points, ones))
|
||||
|
||||
# 应用变换矩阵
|
||||
transformed_points_homogeneous = points_homogeneous @ transform_matrix.T
|
||||
|
||||
# 转换回非齐次坐标 (N x 3)
|
||||
transformed_points = transformed_points_homogeneous[:, :3]
|
||||
|
||||
return transformed_points
|
||||
|
||||
|
||||
|
||||
|
||||
def random_point(points,num):
|
||||
# 随机选择三个不同的点
|
||||
random_indices = np.random.choice(points.shape[0], num, replace=False)
|
||||
random_points = points[random_indices]
|
||||
|
||||
# 计算选中点坐标的均值
|
||||
x_mean = np.mean(random_points[:, 0])
|
||||
y_mean = np.mean(random_points[:, 1])
|
||||
z_mean = np.mean(random_points[:, 2])
|
||||
selected_points = np.array([x_mean, y_mean, z_mean])
|
||||
return selected_points
|
||||
|
||||
|
||||
def farthest_point_sampling(pc, num_points):
|
||||
"""
|
||||
Given a point cloud, sample num_points points that are the farthest apart.
|
||||
Use o3d the farthest point sampling.
|
||||
"""
|
||||
assert pc.ndim == 2 and pc.shape[1] == 3, "pc must be a (N, 3) numpy array"
|
||||
pcd = o3d.geometry.PointCloud()
|
||||
pcd.points = o3d.utility.Vector3dVector(pc)
|
||||
downpcd_farthest = pcd.farthest_point_down_sample(num_points)
|
||||
return np.asarray(downpcd_farthest.points)
|
||||
|
||||
|
||||
|
||||
|
||||
def get_bott_up_point(points,obj_size,descending,):
|
||||
|
||||
# 按照 Z 值从小到大排序
|
||||
ascending_indices = np.argsort(points[:, 2])
|
||||
if descending:
|
||||
# 反转索引实现降序
|
||||
ascending_indices = ascending_indices[::-1]
|
||||
sorted_points = points[ascending_indices]
|
||||
# print("sorted_points",sorted_points)
|
||||
threshold = 0.03* obj_size
|
||||
z_m = sorted_points[0][-1] #
|
||||
while True:
|
||||
top_surface_points = sorted_points[np.abs(sorted_points[:, 2] - z_m) < threshold]
|
||||
if len(top_surface_points) >= 15:
|
||||
break
|
||||
# 增加阈值以获取更多点
|
||||
threshold += 0.01 * obj_size
|
||||
# 获取顶部/底部表面的点
|
||||
top_surface_points = sorted_points[np.abs(sorted_points[:, 2] - z_m) < threshold]
|
||||
# print("sorted_points",len(top_surface_points))
|
||||
|
||||
# # 使用 KMeans 进行聚类,以保证点的均匀分布
|
||||
kmeans = KMeans(n_clusters=10)
|
||||
kmeans.fit(top_surface_points[:, :2]) # 仅使用 X 和 Y 坐标进行聚类
|
||||
# 获取每个聚类中心最近的点
|
||||
centers = kmeans.cluster_centers_
|
||||
selected_points = []
|
||||
|
||||
for center in centers:
|
||||
# 计算每个中心点到所有点的距离
|
||||
distances = np.linalg.norm(top_surface_points[:, :2] - center, axis=1)
|
||||
# 找到最近的点
|
||||
closest_point_idx = np.argmin(distances)
|
||||
selected_points.append(top_surface_points[closest_point_idx])
|
||||
selected_points = np.array(selected_points)
|
||||
selected_points[:, 2] = z_m
|
||||
# modify
|
||||
return selected_points
|
||||
|
||||
|
||||
# ===============================================
|
||||
# = optimization utils
|
||||
# ===============================================
|
||||
def normalize_vars(vars, og_bounds):
|
||||
"""
|
||||
Given 1D variables and bounds, normalize the variables to [-1, 1] range.
|
||||
"""
|
||||
normalized_vars = np.empty_like(vars)
|
||||
for i, (b_min, b_max) in enumerate(og_bounds):
|
||||
if b_max != b_min:
|
||||
normalized_vars[i] = (vars[i] - b_min) / (b_max - b_min) * 2 - 1
|
||||
else:
|
||||
# 处理 b_max 等于 b_min 的情况
|
||||
normalized_vars[i] = 0 # 或者其他合适的默认值
|
||||
return normalized_vars
|
||||
|
||||
|
||||
def unnormalize_vars(normalized_vars, og_bounds):
|
||||
"""
|
||||
Given 1D variables in [-1, 1] and original bounds, denormalize the variables to the original range.
|
||||
"""
|
||||
vars = np.empty_like(normalized_vars)
|
||||
# import pdb;pdb.set_trace()
|
||||
for i, (b_min, b_max) in enumerate(og_bounds):
|
||||
vars[i] = (normalized_vars[i] + 1) / 2.0 * (b_max - b_min) + b_min
|
||||
return vars
|
||||
|
||||
|
||||
def euler2quat(euler):
|
||||
"""
|
||||
Converts euler angles into quaternion form
|
||||
|
||||
Args:
|
||||
euler (np.array): (r,p,y) angles
|
||||
|
||||
Returns:
|
||||
np.array: (x,y,z,w) float quaternion angles
|
||||
|
||||
Raises:
|
||||
AssertionError: [Invalid input shape]
|
||||
"""
|
||||
return R.from_euler("xyz", euler).as_quat()
|
||||
|
||||
|
||||
def quat2euler(quat):
|
||||
"""
|
||||
Converts euler angles into quaternion form
|
||||
|
||||
Args:
|
||||
quat (np.array): (x,y,z,w) float quaternion angles
|
||||
|
||||
Returns:
|
||||
np.array: (r,p,y) angles
|
||||
|
||||
Raises:
|
||||
AssertionError: [Invalid input shape]
|
||||
"""
|
||||
return R.from_quat(quat).as_euler("xyz")
|
||||
|
||||
|
||||
def quat2mat(quaternion):
|
||||
"""
|
||||
Converts given quaternion to matrix.
|
||||
|
||||
Args:
|
||||
quaternion (np.array): (..., 4) (x,y,z,w) float quaternion angles
|
||||
|
||||
Returns:
|
||||
np.array: (..., 3, 3) rotation matrix
|
||||
"""
|
||||
return R.from_quat(quaternion).as_matrix()
|
||||
|
||||
|
||||
def pose2mat(pose):
|
||||
"""
|
||||
Converts pose to homogeneous matrix.
|
||||
|
||||
Args:
|
||||
pose (2-tuple): a (pos, orn) tuple where pos is vec3 float cartesian, and
|
||||
orn is vec4 float quaternion.
|
||||
|
||||
Returns:
|
||||
np.array: 4x4 homogeneous matrix
|
||||
"""
|
||||
|
||||
if isinstance(pose[0], np.ndarray):
|
||||
translation = pose[0]
|
||||
else:
|
||||
raise TypeError("Unsupported data type for translation")
|
||||
|
||||
if isinstance(pose[1], np.ndarray):
|
||||
quaternion = pose[1]
|
||||
else:
|
||||
raise TypeError("Unsupported data type for quaternion")
|
||||
|
||||
homo_pose_mat = np.zeros((4, 4), dtype=translation.dtype)
|
||||
homo_pose_mat[:3, :3] = quat2mat(quaternion)
|
||||
homo_pose_mat[:3, 3] = translation
|
||||
homo_pose_mat[3, 3] = 1.0
|
||||
|
||||
return homo_pose_mat
|
||||
|
||||
12
others/transforms.py
Executable file
12
others/transforms.py
Executable file
@@ -0,0 +1,12 @@
|
||||
import numpy as np
|
||||
from numpy import ndarray
|
||||
|
||||
|
||||
def transform_coordinates_3d(coordinates: ndarray, sRT: ndarray):
|
||||
assert coordinates.shape[0] == 3
|
||||
coordinates = np.vstack(
|
||||
[coordinates, np.ones((1, coordinates.shape[1]), dtype=np.float32)]
|
||||
)
|
||||
new_coordinates = sRT @ coordinates
|
||||
new_coordinates = new_coordinates[:3, :] / new_coordinates[3, :]
|
||||
return new_coordinates
|
||||
558
others/utils.py
Normal file
558
others/utils.py
Normal file
@@ -0,0 +1,558 @@
|
||||
def quaternion_rotate(quaternion, axis, angle):
|
||||
"""
|
||||
Rotate a quaternion around a specified axis by a given angle.
|
||||
|
||||
Parameters:
|
||||
quaternion (numpy array): The input quaternion [w, x, y, z].
|
||||
axis (str): The axis to rotate around ('x', 'y', or 'z').
|
||||
angle (float): The rotation angle in degrees.
|
||||
|
||||
Returns:
|
||||
numpy array: The rotated quaternion.
|
||||
"""
|
||||
# Convert angle from degrees to radians
|
||||
angle_rad = np.radians(angle)
|
||||
|
||||
# Calculate the rotation quaternion based on the specified axis
|
||||
cos_half_angle = np.cos(angle_rad / 2)
|
||||
sin_half_angle = np.sin(angle_rad / 2)
|
||||
|
||||
if axis == 'x':
|
||||
q_axis = np.array([cos_half_angle, sin_half_angle, 0, 0])
|
||||
elif axis == 'y':
|
||||
q_axis = np.array([cos_half_angle, 0, sin_half_angle, 0])
|
||||
elif axis == 'z':
|
||||
q_axis = np.array([cos_half_angle, 0, 0, sin_half_angle])
|
||||
else:
|
||||
raise ValueError("Unsupported axis. Use 'x', 'y', or 'z'.")
|
||||
|
||||
# Extract components of the input quaternion
|
||||
w1, x1, y1, z1 = quaternion
|
||||
w2, x2, y2, z2 = q_axis
|
||||
|
||||
# Quaternion multiplication
|
||||
w = w1 * w2 - x1 * x2 - y1 * y2 - z1 * z2
|
||||
x = w1 * x2 + x1 * w2 + y1 * z2 - z1 * y2
|
||||
y = w1 * y2 - x1 * z2 + y1 * w2 + z1 * x2
|
||||
z = w1 * z2 + x1 * y2 - y1 * x2 + z1 * w2
|
||||
|
||||
return np.array([w, x, y, z])
|
||||
|
||||
|
||||
def axis_to_quaternion(axis, target_axis='y'):
|
||||
"""
|
||||
Calculate the quaternion that rotates a given axis to the target axis.
|
||||
|
||||
Parameters:
|
||||
axis (str): The axis in the object's local coordinate system ('x', 'y', or 'z').
|
||||
target_axis (str): The target axis in the world coordinate system ('x', 'y', or 'z').
|
||||
|
||||
Returns:
|
||||
numpy array: The quaternion representing the rotation.
|
||||
"""
|
||||
# Define unit vectors for each axis
|
||||
unit_vectors = {
|
||||
'x': np.array([1, 0, 0]),
|
||||
'y': np.array([0, 1, 0]),
|
||||
'z': np.array([0, 0, 1])
|
||||
}
|
||||
|
||||
if axis not in unit_vectors or target_axis not in unit_vectors:
|
||||
raise ValueError("Unsupported axis. Use 'x', 'y', or 'z'.")
|
||||
|
||||
v1 = unit_vectors[axis]
|
||||
v2 = unit_vectors[target_axis]
|
||||
|
||||
# Calculate the cross product and dot product
|
||||
cross_prod = np.cross(v1, v2)
|
||||
dot_prod = np.dot(v1, v2)
|
||||
|
||||
# Calculate the quaternion
|
||||
w = np.sqrt((np.linalg.norm(v1) ** 2) * (np.linalg.norm(v2) ** 2)) + dot_prod
|
||||
x, y, z = cross_prod
|
||||
|
||||
# Normalize the quaternion
|
||||
q = np.array([w, x, y, z])
|
||||
q = q / np.linalg.norm(q)
|
||||
|
||||
return q
|
||||
|
||||
|
||||
def is_y_axis_up(pose_matrix):
|
||||
"""
|
||||
判断物体在世界坐标系中的 y 轴是否朝上。
|
||||
|
||||
参数:
|
||||
pose_matrix (numpy.ndarray): 4x4 齐次变换矩阵。
|
||||
|
||||
返回:
|
||||
bool: True 如果 y 轴朝上,False 如果 y 轴朝下。
|
||||
"""
|
||||
# 提取旋转矩阵的第二列
|
||||
y_axis_vector = pose_matrix[:3, 1]
|
||||
|
||||
# 世界坐标系的 y 轴
|
||||
world_y_axis = np.array([0, 1, 0])
|
||||
|
||||
# 计算点积
|
||||
dot_product = np.dot(y_axis_vector, world_y_axis)
|
||||
|
||||
# 返回 True 如果朝上,否则返回 False
|
||||
return dot_product > 0
|
||||
|
||||
|
||||
def is_local_axis_facing_world_axis(pose_matrix, local_axis='y', world_axis='z'):
|
||||
# 定义局部坐标系的轴索引
|
||||
local_axis_index = {'x': 0, 'y': 1, 'z': 2}
|
||||
|
||||
# 定义世界坐标系的轴向量
|
||||
world_axes = {
|
||||
'x': np.array([1, 0, 0]),
|
||||
'y': np.array([0, 1, 0]),
|
||||
'z': np.array([0, 0, 1])
|
||||
}
|
||||
|
||||
# 提取局部坐标系的指定轴
|
||||
local_axis_vector = pose_matrix[:3, local_axis_index[local_axis]]
|
||||
|
||||
# 获取世界坐标系的指定轴向量
|
||||
world_axis_vector = world_axes[world_axis]
|
||||
|
||||
# 计算点积
|
||||
dot_product = np.dot(local_axis_vector, world_axis_vector)
|
||||
|
||||
# 返回 True 如果朝向指定世界轴,否则返回 False
|
||||
return dot_product > 0
|
||||
|
||||
|
||||
def rotate_180_along_axis(target_affine, rot_axis='z'):
|
||||
'''
|
||||
gripper是对称结构,绕Z轴旋转180度前后是等效的。选择离当前pose更近的target pose以避免不必要的旋转
|
||||
'''
|
||||
if rot_axis == 'z':
|
||||
R_180 = np.array([[-1, 0, 0],
|
||||
[0, -1, 0],
|
||||
[0, 0, 1]])
|
||||
elif rot_axis == 'y':
|
||||
R_180 = np.array([[-1, 0, 0],
|
||||
[0, 1, 0],
|
||||
[0, 0, -1]])
|
||||
elif rot_axis == 'x':
|
||||
R_180 = np.array([[1, 0, 0],
|
||||
[0, -1, 0],
|
||||
[0, 0, -1]])
|
||||
else:
|
||||
assert False, "Invalid rotation axis. Please choose from 'x', 'y', 'z'."
|
||||
|
||||
# 提取旋转部分(3x3矩阵)
|
||||
target_rotation = target_affine[:3, :3]
|
||||
# 将target_rotation绕其自身的Z轴转180度,得到target_rotation_2
|
||||
target_rotation_2 = np.dot(target_rotation, R_180)
|
||||
|
||||
# 重新组合旋转矩阵target_rotation_2和原始的平移部分
|
||||
target_affine_2 = np.eye(4)
|
||||
target_affine_2[:3, :3] = target_rotation_2
|
||||
target_affine_2[:3, 3] = target_affine[:3, 3] # 保留原始的平移部分
|
||||
return target_affine_2
|
||||
|
||||
|
||||
def rotate_along_axis(target_affine, angle_degrees, rot_axis='z', use_local=True):
|
||||
'''
|
||||
根据指定的角度和旋转轴来旋转target_affine。
|
||||
参数:
|
||||
- target_affine: 4x4 仿射变换矩阵
|
||||
- angle_degrees: 旋转角度(以度为单位)
|
||||
- rot_axis: 旋转轴,'x'、'y' 或 'z'
|
||||
'''
|
||||
# 将角度转换为弧度
|
||||
angle_radians = np.deg2rad(angle_degrees)
|
||||
|
||||
# 创建旋转对象
|
||||
if rot_axis == 'z':
|
||||
rotation_vector = np.array([0, 0, angle_radians])
|
||||
elif rot_axis == 'y':
|
||||
rotation_vector = np.array([0, angle_radians, 0])
|
||||
elif rot_axis == 'x':
|
||||
rotation_vector = np.array([angle_radians, 0, 0])
|
||||
else:
|
||||
raise ValueError("Invalid rotation axis. Please choose from 'x', 'y', 'z'.")
|
||||
|
||||
# 生成旋转矩阵
|
||||
R_angle = R.from_rotvec(rotation_vector).as_matrix()
|
||||
|
||||
# 提取旋转部分(3x3矩阵)
|
||||
target_rotation = target_affine[:3, :3]
|
||||
|
||||
# 将 target_rotation 绕指定轴旋转指定角度,得到 target_rotation_2
|
||||
if use_local:
|
||||
target_rotation_2 = np.dot(target_rotation, R_angle)
|
||||
else:
|
||||
target_rotation_2 = np.dot(R_angle, target_rotation)
|
||||
|
||||
# 重新组合旋转矩阵 target_rotation_2 和原始的平移部分
|
||||
target_affine_2 = np.eye(4)
|
||||
target_affine_2[:3, :3] = target_rotation_2
|
||||
target_affine_2[:3, 3] = target_affine[:3, 3] # 保留原始的平移部分
|
||||
|
||||
return target_affine_2
|
||||
|
||||
|
||||
def rotate_along_axis(target_affine, angle_degrees, rot_axis='z', use_local=True):
|
||||
'''
|
||||
根据指定的角度和旋转轴来旋转target_affine。
|
||||
参数:
|
||||
- target_affine: 4x4 仿射变换矩阵
|
||||
- angle_degrees: 旋转角度(以度为单位)
|
||||
- rot_axis: 旋转轴,'x'、'y' 或 'z'
|
||||
'''
|
||||
# 将角度转换为弧度
|
||||
angle_radians = np.deg2rad(angle_degrees)
|
||||
|
||||
# 创建旋转对象
|
||||
if rot_axis == 'z':
|
||||
rotation_vector = np.array([0, 0, angle_radians])
|
||||
elif rot_axis == 'y':
|
||||
rotation_vector = np.array([0, angle_radians, 0])
|
||||
elif rot_axis == 'x':
|
||||
rotation_vector = np.array([angle_radians, 0, 0])
|
||||
else:
|
||||
raise ValueError("Invalid rotation axis. Please choose from 'x', 'y', 'z'.")
|
||||
|
||||
# 生成旋转矩阵
|
||||
R_angle = R.from_rotvec(rotation_vector).as_matrix()
|
||||
|
||||
# 提取旋转部分(3x3矩阵)
|
||||
target_rotation = target_affine[:3, :3]
|
||||
|
||||
# 将 target_rotation 绕指定轴旋转指定角度,得到 target_rotation_2
|
||||
if use_local:
|
||||
target_rotation_2 = np.dot(target_rotation, R_angle)
|
||||
else:
|
||||
target_rotation_2 = np.dot(R_angle, target_rotation)
|
||||
|
||||
# 重新组合旋转矩阵 target_rotation_2 和原始的平移部分
|
||||
target_affine_2 = np.eye(4)
|
||||
target_affine_2[:3, :3] = target_rotation_2
|
||||
target_affine_2[:3, 3] = target_affine[:3, 3] # 保留原始的平移部分
|
||||
|
||||
return target_affine_2
|
||||
|
||||
|
||||
import numpy as np
|
||||
from scipy.spatial.transform import Rotation as R
|
||||
|
||||
|
||||
def get_quaternion_wxyz_from_rotation_matrix(rotation_matrix):
|
||||
"""
|
||||
Convert a 3x3 rotation matrix to a quaternion in the wxyz format.
|
||||
|
||||
Parameters:
|
||||
R (numpy array): A 3x3 rotation matrix.
|
||||
|
||||
Returns:
|
||||
numpy array: A 4x1 quaternion in the wxyz format.
|
||||
"""
|
||||
# Convert the rotation matrix to a quaternion
|
||||
rot = R.from_matrix(rotation_matrix)
|
||||
quat = rot.as_quat()
|
||||
|
||||
# Reorder the quaternion to the wxyz format
|
||||
if quat.shape[0] == 4:
|
||||
quaternions_wxyz = quat[[3, 0, 1, 2]]
|
||||
else:
|
||||
quaternions_wxyz = quat[:, [3, 0, 1, 2]]
|
||||
return quaternions_wxyz
|
||||
|
||||
|
||||
def get_quaternion_from_rotation_matrix(R):
|
||||
assert R.shape == (3, 3)
|
||||
|
||||
# 计算四元数分量
|
||||
trace = np.trace(R)
|
||||
if trace > 0:
|
||||
S = np.sqrt(trace + 1.0) * 2 # S=4*qw
|
||||
qw = 0.25 * S
|
||||
qx = (R[2, 1] - R[1, 2]) / S
|
||||
qy = (R[0, 2] - R[2, 0]) / S
|
||||
qz = (R[1, 0] - R[0, 1]) / S
|
||||
elif (R[0, 0] > R[1, 1]) and (R[0, 0] > R[2, 2]):
|
||||
S = np.sqrt(1.0 + R[0, 0] - R[1, 1] - R[2, 2]) * 2 # S=4*qx
|
||||
qw = (R[2, 1] - R[1, 2]) / S
|
||||
qx = 0.25 * S
|
||||
qy = (R[0, 1] + R[1, 0]) / S
|
||||
qz = (R[0, 2] + R[2, 0]) / S
|
||||
elif R[1, 1] > R[2, 2]:
|
||||
S = np.sqrt(1.0 + R[1, 1] - R[0, 0] - R[2, 2]) * 2 # S=4*qy
|
||||
qw = (R[0, 2] - R[2, 0]) / S
|
||||
qx = (R[0, 1] + R[1, 0]) / S
|
||||
qy = 0.25 * S
|
||||
qz = (R[1, 2] + R[2, 1]) / S
|
||||
else:
|
||||
S = np.sqrt(1.0 + R[2, 2] - R[0, 0] - R[1, 1]) * 2 # S=4*qz
|
||||
qw = (R[1, 0] - R[0, 1]) / S
|
||||
qx = (R[0, 2] + R[2, 0]) / S
|
||||
qy = (R[1, 2] + R[2, 1]) / S
|
||||
qz = 0.25 * S
|
||||
|
||||
return np.array([qw, qx, qy, qz])
|
||||
|
||||
|
||||
# @nb.jit(nopython=True)
|
||||
def get_rotation_matrix_from_quaternion(quat: np.ndarray) -> np.ndarray:
|
||||
"""Convert a quaternion to a rotation matrix.
|
||||
|
||||
Args:
|
||||
quat (np.ndarray): A 4x1 vector in order (w, x, y, z)
|
||||
|
||||
Returns:
|
||||
np.ndarray: The resulting 3x3 rotation matrix.
|
||||
"""
|
||||
w, x, y, z = quat
|
||||
rot = np.array(
|
||||
[
|
||||
[2 * (w ** 2 + x ** 2) - 1, 2 * (x * y - w * z), 2 * (x * z + w * y)],
|
||||
[2 * (x * y + w * z), 2 * (w ** 2 + y ** 2) - 1, 2 * (y * z - w * x)],
|
||||
[2 * (x * z - w * y), 2 * (y * z + w * x), 2 * (w ** 2 + z ** 2) - 1],
|
||||
]
|
||||
)
|
||||
return rot
|
||||
|
||||
|
||||
# @nb.jit(nopython=True)
|
||||
def get_xyz_euler_from_quaternion(quat: np.ndarray) -> np.ndarray:
|
||||
"""Convert a quaternion to XYZ euler angles.
|
||||
|
||||
Args:
|
||||
quat (np.ndarray): A 4x1 vector in order (w, x, y, z).
|
||||
|
||||
Returns:
|
||||
np.ndarray: A 3x1 vector containing (roll, pitch, yaw).
|
||||
"""
|
||||
w, x, y, z = quat
|
||||
y_sqr = y * y
|
||||
|
||||
t0 = +2.0 * (w * x + y * z)
|
||||
t1 = +1.0 - 2.0 * (x * x + y_sqr)
|
||||
eulerx = np.arctan2(t0, t1)
|
||||
|
||||
t2 = +2.0 * (w * y - z * x)
|
||||
t2 = +1.0 if t2 > +1.0 else t2
|
||||
t2 = -1.0 if t2 < -1.0 else t2
|
||||
eulery = np.arcsin(t2)
|
||||
|
||||
t3 = +2.0 * (w * z + x * y)
|
||||
t4 = +1.0 - 2.0 * (y_sqr + z * z)
|
||||
eulerz = np.arctan2(t3, t4)
|
||||
|
||||
result = np.zeros(3)
|
||||
result[0] = eulerx
|
||||
result[1] = eulery
|
||||
result[2] = eulerz
|
||||
|
||||
return result
|
||||
|
||||
|
||||
# @nb.jit(nopython=True)
|
||||
def get_quaternion_from_euler(euler: np.ndarray, order: str = "XYZ") -> np.ndarray:
|
||||
"""Convert an euler angle to a quaternion based on specified euler angle order.
|
||||
|
||||
Supported Euler angle orders: {'XYZ', 'YXZ', 'ZXY', 'ZYX', 'YZX', 'XZY'}.
|
||||
|
||||
Args:
|
||||
euler (np.ndarray): A 3x1 vector with angles in radians.
|
||||
order (str, optional): The specified order of input euler angles. Defaults to "XYZ".
|
||||
|
||||
Raises:
|
||||
ValueError: If input order is not valid.
|
||||
|
||||
Reference:
|
||||
[1] https://github.com/mrdoob/three.js/blob/master/src/math/Quaternion.js
|
||||
"""
|
||||
# extract input angles
|
||||
r, p, y = euler
|
||||
# compute constants
|
||||
y = y / 2.0
|
||||
p = p / 2.0
|
||||
r = r / 2.0
|
||||
c3 = np.cos(y)
|
||||
s3 = np.sin(y)
|
||||
c2 = np.cos(p)
|
||||
s2 = np.sin(p)
|
||||
c1 = np.cos(r)
|
||||
s1 = np.sin(r)
|
||||
# convert to quaternion based on order
|
||||
if order == "XYZ":
|
||||
result = np.array(
|
||||
[
|
||||
c1 * c2 * c3 - s1 * s2 * s3,
|
||||
c1 * s2 * s3 + c2 * c3 * s1,
|
||||
c1 * c3 * s2 - s1 * c2 * s3,
|
||||
c1 * c2 * s3 + s1 * c3 * s2,
|
||||
]
|
||||
)
|
||||
if result[0] < 0:
|
||||
result = -result
|
||||
return result
|
||||
elif order == "YXZ":
|
||||
result = np.array(
|
||||
[
|
||||
c1 * c2 * c3 + s1 * s2 * s3,
|
||||
s1 * c2 * c3 + c1 * s2 * s3,
|
||||
c1 * s2 * c3 - s1 * c2 * s3,
|
||||
c1 * c2 * s3 - s1 * s2 * c3,
|
||||
]
|
||||
)
|
||||
return result
|
||||
elif order == "ZXY":
|
||||
result = np.array(
|
||||
[
|
||||
c1 * c2 * c3 - s1 * s2 * s3,
|
||||
s1 * c2 * c3 - c1 * s2 * s3,
|
||||
c1 * s2 * c3 + s1 * c2 * s3,
|
||||
c1 * c2 * s3 + s1 * s2 * c3,
|
||||
]
|
||||
)
|
||||
return result
|
||||
elif order == "ZYX":
|
||||
result = np.array(
|
||||
[
|
||||
c1 * c2 * c3 + s1 * s2 * s3,
|
||||
s1 * c2 * c3 - c1 * s2 * s3,
|
||||
c1 * s2 * c3 + s1 * c2 * s3,
|
||||
c1 * c2 * s3 - s1 * s2 * c3,
|
||||
]
|
||||
)
|
||||
return result
|
||||
elif order == "YZX":
|
||||
result = np.array(
|
||||
[
|
||||
c1 * c2 * c3 - s1 * s2 * s3,
|
||||
s1 * c2 * c3 + c1 * s2 * s3,
|
||||
c1 * s2 * c3 + s1 * c2 * s3,
|
||||
c1 * c2 * s3 - s1 * s2 * c3,
|
||||
]
|
||||
)
|
||||
return result
|
||||
elif order == "XZY":
|
||||
result = np.array(
|
||||
[
|
||||
c1 * c2 * c3 + s1 * s2 * s3,
|
||||
s1 * c2 * c3 - c1 * s2 * s3,
|
||||
c1 * s2 * c3 - s1 * c2 * s3,
|
||||
c1 * c2 * s3 + s1 * s2 * c3,
|
||||
]
|
||||
)
|
||||
return result
|
||||
else:
|
||||
raise ValueError("Input euler angle order is meaningless.")
|
||||
|
||||
|
||||
# @nb.jit(nopython=True)
|
||||
def get_rotation_matrix_from_euler(euler: np.ndarray, order: str = "XYZ") -> np.ndarray:
|
||||
quat = get_quaternion_from_euler(euler, order)
|
||||
return get_rotation_matrix_from_quaternion(quat)
|
||||
|
||||
|
||||
# @nb.jit(nopython=True)
|
||||
def quat_multiplication(q: np.ndarray, p: np.ndarray) -> np.ndarray:
|
||||
"""Compute the product of two quaternions.
|
||||
|
||||
Args:
|
||||
q (np.ndarray): First quaternion in order (w, x, y, z).
|
||||
p (np.ndarray): Second quaternion in order (w, x, y, z).
|
||||
|
||||
Returns:
|
||||
np.ndarray: A 4x1 vector representing a quaternion in order (w, x, y, z).
|
||||
"""
|
||||
quat = np.array(
|
||||
[
|
||||
p[0] * q[0] - p[1] * q[1] - p[2] * q[2] - p[3] * q[3],
|
||||
p[0] * q[1] + p[1] * q[0] - p[2] * q[3] + p[3] * q[2],
|
||||
p[0] * q[2] + p[1] * q[3] + p[2] * q[0] - p[3] * q[1],
|
||||
p[0] * q[3] - p[1] * q[2] + p[2] * q[1] + p[3] * q[0],
|
||||
]
|
||||
)
|
||||
return quat
|
||||
|
||||
|
||||
# @nb.jit(nopython=True)
|
||||
def skew(vector: np.ndarray) -> np.ndarray:
|
||||
"""Convert vector to skew symmetric matrix.
|
||||
|
||||
This function returns a skew-symmetric matrix to perform cross-product
|
||||
as a matrix multiplication operation, i.e.:
|
||||
|
||||
np.cross(a, b) = np.dot(skew(a), b)
|
||||
|
||||
|
||||
Args:
|
||||
vector (np.ndarray): A 3x1 vector.
|
||||
|
||||
Returns:
|
||||
np.ndarray: The resluting skew-symmetric matrix.
|
||||
"""
|
||||
mat = np.array([[0, -vector[2], vector[1]], [vector[2], 0, -vector[0]], [-vector[1], vector[0], 0]])
|
||||
return mat
|
||||
|
||||
|
||||
import math
|
||||
|
||||
_POLE_LIMIT = 1.0 - 1e-6
|
||||
|
||||
|
||||
def matrix_to_euler_angles(mat: np.ndarray, degrees: bool = False, extrinsic: bool = True) -> np.ndarray:
|
||||
"""Convert rotation matrix to Euler XYZ extrinsic or intrinsic angles.
|
||||
|
||||
Args:
|
||||
mat (np.ndarray): A 3x3 rotation matrix.
|
||||
degrees (bool, optional): Whether returned angles should be in degrees.
|
||||
extrinsic (bool, optional): True if the rotation matrix follows the extrinsic matrix
|
||||
convention (equivalent to ZYX ordering but returned in the reverse) and False if it follows
|
||||
the intrinsic matrix conventions (equivalent to XYZ ordering).
|
||||
Defaults to True.
|
||||
|
||||
Returns:
|
||||
np.ndarray: Euler XYZ angles (intrinsic form) if extrinsic is False and Euler XYZ angles (extrinsic form) if extrinsic is True.
|
||||
"""
|
||||
if extrinsic:
|
||||
if mat[2, 0] > _POLE_LIMIT:
|
||||
roll = np.arctan2(mat[0, 1], mat[0, 2])
|
||||
pitch = -np.pi / 2
|
||||
yaw = 0.0
|
||||
return np.array([roll, pitch, yaw])
|
||||
|
||||
if mat[2, 0] < -_POLE_LIMIT:
|
||||
roll = np.arctan2(mat[0, 1], mat[0, 2])
|
||||
pitch = np.pi / 2
|
||||
yaw = 0.0
|
||||
return np.array([roll, pitch, yaw])
|
||||
|
||||
roll = np.arctan2(mat[2, 1], mat[2, 2])
|
||||
pitch = -np.arcsin(mat[2, 0])
|
||||
yaw = np.arctan2(mat[1, 0], mat[0, 0])
|
||||
if degrees:
|
||||
roll = math.degrees(roll)
|
||||
pitch = math.degrees(pitch)
|
||||
yaw = math.degrees(yaw)
|
||||
return np.array([roll, pitch, yaw])
|
||||
else:
|
||||
if mat[0, 2] > _POLE_LIMIT:
|
||||
roll = np.arctan2(mat[1, 0], mat[1, 1])
|
||||
pitch = np.pi / 2
|
||||
yaw = 0.0
|
||||
return np.array([roll, pitch, yaw])
|
||||
|
||||
if mat[0, 2] < -_POLE_LIMIT:
|
||||
roll = np.arctan2(mat[1, 0], mat[1, 1])
|
||||
pitch = -np.pi / 2
|
||||
yaw = 0.0
|
||||
return np.array([roll, pitch, yaw])
|
||||
roll = -math.atan2(mat[1, 2], mat[2, 2])
|
||||
pitch = math.asin(mat[0, 2])
|
||||
yaw = -math.atan2(mat[0, 1], mat[0, 0])
|
||||
|
||||
if degrees:
|
||||
roll = math.degrees(roll)
|
||||
pitch = math.degrees(pitch)
|
||||
yaw = math.degrees(yaw)
|
||||
return np.array([roll, pitch, yaw])
|
||||
@@ -1,4 +1,4 @@
|
||||
from pyapp.runner import Runner
|
||||
from pyboot.runner import Runner
|
||||
|
||||
class DataGenerator(Runner):
|
||||
def __init__(self, config_path: str):
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
from pyapp.runner import Runner
|
||||
from pyboot.runner import Runner
|
||||
|
||||
class DataRecorder(Runner):
|
||||
def __init__(self, config_path: str):
|
||||
|
||||
@@ -1,8 +1,12 @@
|
||||
from pyapp.runner import Runner
|
||||
from pyboot.runner import Runner
|
||||
from others.task_generate import OldTaskGenerator
|
||||
|
||||
class TaskGenerator(Runner):
|
||||
def __init__(self, config_path: str):
|
||||
super().__init__(config_path)
|
||||
|
||||
def run(self):
|
||||
pass
|
||||
|
||||
def generate_from_template(self, template: dict):
|
||||
pass
|
||||
@@ -2,8 +2,8 @@ import os
|
||||
import shutil
|
||||
import json
|
||||
|
||||
from pyapp.runner import Runner
|
||||
from pyapp.utils.log import Log
|
||||
from pyboot.runner import Runner
|
||||
from pyboot.utils.log import Log
|
||||
|
||||
class TaskTemplatesDivider(Runner):
|
||||
def __init__(self, config_path: str):
|
||||
|
||||
15
utils/pose.py
Normal file
15
utils/pose.py
Normal file
@@ -0,0 +1,15 @@
|
||||
|
||||
from scipy.spatial.transform import Rotation as R
|
||||
|
||||
class PoseUtil:
|
||||
@staticmethod
|
||||
def get_quaternion_wxyz_from_rotation_matrix(rotation_matrix):
|
||||
rot = R.from_matrix(rotation_matrix)
|
||||
quat = rot.as_quat()
|
||||
|
||||
if quat.shape[0] == 4:
|
||||
quaternions_wxyz = quat[[3, 0, 1, 2]]
|
||||
else:
|
||||
quaternions_wxyz = quat[:, [3, 0, 1, 2]]
|
||||
return quaternions_wxyz
|
||||
|
||||
Reference in New Issue
Block a user