DIPO / objects /dict_utils.py
xinjie.wang
init commit
c28dddb
import numpy as np
from scipy.optimize import linear_sum_assignment
def get_base_part_idx(obj_dict):
"""
Get the index of the base part in the object dictionary\n
- obj_dict: the object dictionary\n
Return:\n
- base_part_idx: the index of the base part
"""
# Adjust for NAP's corner case
base_part_ids = np.where(
[part["parent"] == -1 for part in obj_dict["diffuse_tree"]]
)[0]
if len(base_part_ids) > 0:
return base_part_ids[0].item()
else:
raise ValueError("No base part found")
def get_bbox_vertices(obj_dict, part_idx):
"""
Get the 8 vertices of the bounding box\n
The order of the vertices is the same as the order that pytorch3d.ops.box3d_overlap expects\n
(This order is not necessary since we are not using pytorch3d.ops.box3d_overlap anymore)\n
- bbox_center: the center of the bounding box in the form: [cx, cy, cz]\n
- bbox_size: the size of the bounding box in the form: [lx, ly, lz]\n
Return:\n
- bbox_vertices: the 8 vertices of the bounding box in the form: [[x0, y0, z0], [x1, y1, z1], ...]
"""
part = obj_dict["diffuse_tree"][part_idx]
bbox_center = np.array(part["aabb"]["center"], dtype=np.float32)
bbox_size_half = np.array(part["aabb"]["size"], dtype=np.float32) / 2
bbox_vertices = np.zeros((8, 3), dtype=np.float32)
# Get the 8 vertices of the bounding box in the order that pytorch3d.ops.box3d_overlap expects:
# 0: (x0, y0, z0) # 1: (x1, y0, z0) # 2: (x1, y1, z0) # 3: (x0, y1, z0)
# 4: (x0, y0, z1) # 5: (x1, y0, z1) # 6: (x1, y1, z1) # 7: (x0, y1, z1)
bbox_vertices[0, :] = bbox_center - bbox_size_half
bbox_vertices[1, :] = bbox_center + np.array(
[bbox_size_half[0], -bbox_size_half[1], -bbox_size_half[2]], dtype=np.float32
)
bbox_vertices[2, :] = bbox_center + np.array(
[bbox_size_half[0], bbox_size_half[1], -bbox_size_half[2]], dtype=np.float32
)
bbox_vertices[3, :] = bbox_center + np.array(
[-bbox_size_half[0], bbox_size_half[1], -bbox_size_half[2]], dtype=np.float32
)
bbox_vertices[4, :] = bbox_center + np.array(
[-bbox_size_half[0], -bbox_size_half[1], bbox_size_half[2]], dtype=np.float32
)
bbox_vertices[5, :] = bbox_center + np.array(
[bbox_size_half[0], -bbox_size_half[1], bbox_size_half[2]], dtype=np.float32
)
bbox_vertices[6, :] = bbox_center + bbox_size_half
bbox_vertices[7, :] = bbox_center + np.array(
[-bbox_size_half[0], bbox_size_half[1], bbox_size_half[2]], dtype=np.float32
)
return bbox_vertices
def compute_overall_bbox_size(obj_dict):
"""
Compute the overall bounding box size of the object\n
- obj_dict: the object dictionary\n
Return:\n
- bbox_size: the overall bounding box size in the form: [lx, ly, lz]
"""
bbox_min = np.zeros((len(obj_dict["diffuse_tree"]), 3), dtype=np.float32)
bbox_max = np.zeros((len(obj_dict["diffuse_tree"]), 3), dtype=np.float32)
# For each part, compute the bounding box and store the min and max vertices
for part_idx, part in enumerate(obj_dict["diffuse_tree"]):
bbox_center = np.array(part["aabb"]["center"], dtype=np.float32)
bbox_size_half = np.array(part["aabb"]["size"], dtype=np.float32) / 2
bbox_min[part_idx] = bbox_center - bbox_size_half
bbox_max[part_idx] = bbox_center + bbox_size_half
# Compute the overall bounding box size
bbox_min = np.min(bbox_min, axis=0)
bbox_max = np.max(bbox_max, axis=0)
bbox_size = bbox_max - bbox_min
return bbox_size
def remove_handles(obj_dict):
"""
Remove the handles from the object dictionary and adjust the id, parent, and children of the parts\n
- obj_dict: the object dictionary\n
Return:\n
- obj_dict: the object dictionary without the handles
"""
# Find the indices of the handles
handle_idxs = np.array(
[
i
for i in range(len(obj_dict["diffuse_tree"]))
if obj_dict["diffuse_tree"][i]["name"] == "handle"
and obj_dict["diffuse_tree"][i]["parent"] != -1
]
) # Added to avoid corner case of NAP where the handle is the base part
# Remove the handles from the object dictionary and adjust the id, parent, and children of the parts
for handle_idx in handle_idxs:
handle = obj_dict["diffuse_tree"][handle_idx]
parent_idx = handle["parent"]
if handle_idx in obj_dict["diffuse_tree"][parent_idx]["children"]:
obj_dict["diffuse_tree"][parent_idx]["children"].remove(handle_idx)
obj_dict["diffuse_tree"].pop(handle_idx)
# Adjust the id, parent, and children of the parts
for part in obj_dict["diffuse_tree"]:
if part["id"] > handle_idx:
part["id"] -= 1
if part["parent"] > handle_idx:
part["parent"] -= 1
for i in range(len(part["children"])):
if part["children"][i] > handle_idx:
part["children"][i] -= 1
handle_idxs -= 1
return obj_dict
# def normalize_object(obj_dict):
# """
# Normalize the object as a whole\n
# Make the base part to be centered at the origin and have a size of 2\n
# obj_dict: the object dictionary
# """
# # Find the base part and compute the translation and scaling factors
# tree = obj_dict["diffuse_tree"]
# for part in tree:
# if part["parent"] == -1:
# translate = -np.array(part["aabb"]["center"], dtype=np.float32)
# scale = 2.0 / np.array(part["aabb"]["size"], dtype=np.float32)
# break
# for part in tree:
# part["aabb"]["center"] = (
# np.array(part["aabb"]["center"], dtype=np.float32) + translate
# ) * scale
# part["aabb"]["size"] = np.array(part["aabb"]["size"], dtype=np.float32) * scale
# if part["joint"]["type"] != "fixed":
# part["joint"]["axis"]["origin"] = (
# np.array(part["joint"]["axis"]["origin"], dtype=np.float32) + translate
# ) * scale
def zero_center_object(obj_dict):
"""
Zero center the object as a whole\n
- obj_dict: the object dictionary
"""
bbox_min = np.zeros((len(obj_dict["diffuse_tree"]), 3))
bbox_max = np.zeros((len(obj_dict["diffuse_tree"]), 3))
# For each part, compute the bounding box and store the min and max vertices
for part_idx, part in enumerate(obj_dict["diffuse_tree"]):
bbox_center = np.array(part["aabb"]["center"])
bbox_size_half = np.array(part["aabb"]["size"]) / 2
bbox_min[part_idx] = bbox_center - bbox_size_half
bbox_max[part_idx] = bbox_center + bbox_size_half
# Compute the overall bounding box size
bbox_min = np.min(bbox_min, axis=0)
bbox_max = np.max(bbox_max, axis=0)
bbox_center = (bbox_min + bbox_max) / 2
translate = -bbox_center
for part in obj_dict["diffuse_tree"]:
part["aabb"]["center"] = np.array(part["aabb"]["center"]) + translate
if part["joint"]["type"] != "fixed":
part["joint"]["axis"]["origin"] = np.array(part["joint"]["axis"]["origin"]) + translate
def rescale_object(obj_dict, scale_factor):
"""
Rescale the object as a whole\n
- obj_dict: the object dictionary\n
- scale_factor: the scale factor to rescale the object
"""
for part in obj_dict["diffuse_tree"]:
part["aabb"]["center"] = (
np.array(part["aabb"]["center"], dtype=np.float32) * scale_factor
)
part["aabb"]["size"] = (
np.array(part["aabb"]["size"], dtype=np.float32) * scale_factor
)
if part["joint"]["type"] != "fixed":
part["joint"]["axis"]["origin"] = (
np.array(part["joint"]["axis"]["origin"], dtype=np.float32)
* scale_factor
)
def find_part_mapping(obj1_dict, obj2_dict, use_hungarian=False):
"""
Find the correspondences from the first object to the second object based on closest bbox centers\n
- obj1_dict: the first object dictionary\n
- obj2_dict: the second object dictionary\n
Return:\n
- mapping: the mapping from the first object to the second object in the form: [[obj_part_idx, distance], ...]
"""
if use_hungarian:
return hungarian_matching(obj1_dict, obj2_dict)
# Initialize the distances to be +inf
mapping = np.ones((len(obj1_dict["diffuse_tree"]), 2)) * np.inf
# For each part in the first object, find the closest part in the second object based on the bounding box center
for req_part_idx, req_part in enumerate(obj1_dict["diffuse_tree"]):
for obj_part_idx, obj_part in enumerate(obj2_dict["diffuse_tree"]):
distance = np.linalg.norm(
np.array(req_part["aabb"]["center"])
- np.array(obj_part["aabb"]["center"])
)
if distance < mapping[req_part_idx, 1]:
mapping[req_part_idx, :] = [obj_part_idx, distance]
return mapping
def hungarian_matching(obj1_dict, obj2_dict):
"""
Find the correspondences from the first object to the second object based on closest bbox centers using Hungarian algorithm\n
- obj1_dict: the first object dictionary\n
- obj2_dict: the second object dictionary\n
Return:\n
- mapping: the mapping from the first object to the second object in the form: [[obj_part_idx], ...]
"""
INF = 9999999
tree1 = obj1_dict["diffuse_tree"]
tree2 = obj2_dict["diffuse_tree"]
n_parts1 = len(tree1)
n_parts2 = len(tree2)
n_parts_max = max(n_parts1, n_parts2)
# Initialize the cost matrix
cost_matrix = np.ones((n_parts_max, n_parts_max), dtype=np.float32) * INF
for i in range(n_parts1):
for j in range(n_parts2):
cost_matrix[i, j] = np.linalg.norm(
np.array(tree1[i]["aabb"]["center"], dtype=np.float32)
- np.array(tree2[j]["aabb"]["center"], dtype=np.float32)
)
# Find the correspondences using the Hungarian algorithm
row_ind, col_ind = linear_sum_assignment(cost_matrix)
# Valid correspondences are those with all cost less than INF
valid_correspondences = np.where(cost_matrix[row_ind, col_ind] < INF)[0]
invalid_correspondences = np.where(np.logical_not(cost_matrix[row_ind, col_ind] < INF))[0]
row_i = row_ind[valid_correspondences]
col_i = col_ind[valid_correspondences]
# Construct the mapping
mapping = np.zeros(
(n_parts1, 2), dtype=np.float32
)
mapping[row_i, 0] = col_i
mapping[row_i, 1] = cost_matrix[row_i, col_i]
# assign the index of the most closely matched part
if n_parts1 > n_parts2:
row_j = row_ind[invalid_correspondences]
col_j = cost_matrix[row_j, :].argmin(axis=1)
mapping[row_j, 0] = col_j
mapping[row_j, 1] = cost_matrix[row_j, col_j]
return mapping