DIPO / metrics /iou_cdist.py
xinjie.wang
init commit
c28dddb
"""
This file computes the IoU-based and centroid-distance-based metrics in a symmetric manner\n
"""
import sys, os
sys.path.append(os.path.join(os.path.dirname(__file__), '..', '..'))
import numpy as np
from copy import deepcopy
from objects.dict_utils import (
get_base_part_idx,
get_bbox_vertices,
remove_handles,
compute_overall_bbox_size,
rescale_object,
find_part_mapping,
zero_center_object,
)
from objects.motions import transform_all_parts
from metrics.giou import sampling_giou, sampling_cDist
def _get_scores(
src_dict,
tgt_dict,
original_src_bbox_vertices,
original_tgt_bbox_vertices,
mapping,
num_states,
rotation_fix_range,
num_samples,
iou_include_base,
):
# Record the indices of the base parts of the src objects
src_base_idx = get_base_part_idx(src_dict)
# Compute the sum of IoU between the generated object and the candidate object over a number of articulation states
num_parts_in_src = len(src_dict["diffuse_tree"])
iou_per_part_and_state = np.zeros((num_parts_in_src, num_states), dtype=np.float32)
cDist_per_part_and_state = np.zeros(
(num_parts_in_src, num_states), dtype=np.float32
)
states = np.linspace(0, 1, num_states)
for state_idx, state in enumerate(states):
# Get a fresh copy of the bounding box vertices in rest pose
src_bbox_vertices = deepcopy(original_src_bbox_vertices)
tgt_bbox_vertices = deepcopy(original_tgt_bbox_vertices)
# Transform the objects to the current state using the joints
src_part_transfomrations = transform_all_parts(
src_bbox_vertices,
src_dict,
state,
rotation_fix_range=rotation_fix_range,
)
tgt_part_transfomrations = transform_all_parts(
tgt_bbox_vertices,
tgt_dict,
state,
rotation_fix_range=rotation_fix_range,
)
# Compute the IoU between the two objects using the transformed bounding boxes and the part mapping
for src_part_idx in range(num_parts_in_src):
# Get the index of the corresponding part in the candidate object
tgt_part_idx = int(mapping[src_part_idx, 0])
# Always use a fresh copy of the bounding box vertices in rest pose in case dry_run=False is incorrectly set
src_part_bbox_vertices = deepcopy(original_src_bbox_vertices)[src_part_idx]
tgt_part_bbox_vertices = deepcopy(original_tgt_bbox_vertices)[tgt_part_idx]
# Compute the sampling-based IoU between the two parts
iou_per_part_and_state[src_part_idx, state_idx] = sampling_giou(
src_part_bbox_vertices,
tgt_part_bbox_vertices,
src_part_transfomrations[src_part_idx],
tgt_part_transfomrations[tgt_part_idx],
num_samples=num_samples,
)
# Compute the centriod distance between the two matched parts
cDist_per_part_and_state[src_part_idx, state_idx] = sampling_cDist(
src_dict["diffuse_tree"][src_part_idx],
tgt_dict["diffuse_tree"][tgt_part_idx],
src_part_transfomrations[src_part_idx],
tgt_part_transfomrations[tgt_part_idx],
)
# IoU and cDist at the resting state
per_part_iou_avg_at_rest = iou_per_part_and_state[:, 0]
per_part_cDist_avg_at_rest = cDist_per_part_and_state[:, 0]
# Average the IoU over the states
per_part_iou_avg_over_states = np.sum(iou_per_part_and_state, axis=1) / num_states
# Average the cDist over the states
per_part_cDist_avg_over_states = (
np.sum(cDist_per_part_and_state, axis=1) / num_states
)
# Remove the base part if specified
if not iou_include_base:
per_part_iou_avg_over_states = np.delete(
per_part_iou_avg_over_states, src_base_idx
)
per_part_iou_avg_at_rest = np.delete(per_part_iou_avg_at_rest, src_base_idx)
per_part_cDist_avg_over_states = np.delete(
per_part_cDist_avg_over_states, src_base_idx
)
per_part_cDist_avg_at_rest = np.delete(per_part_cDist_avg_at_rest, src_base_idx)
aid_iou = float(np.mean(per_part_iou_avg_over_states)) if len(per_part_iou_avg_over_states) > 0 else 0
aid_cdist = float(np.mean(per_part_cDist_avg_over_states)) if len(per_part_cDist_avg_over_states) > 0 else 1
rid_iou = float(np.mean(per_part_iou_avg_at_rest)) if len(per_part_iou_avg_at_rest) > 0 else 0
rid_cdist = float(np.mean(per_part_cDist_avg_at_rest)) if len(per_part_cDist_avg_at_rest) > 0 else 1
return {
"AS-IoU": 1. - aid_iou,
"AS-cDist": aid_cdist,
"RS-IoU": 1. - rid_iou,
"RS-cDist": rid_cdist
}
def IoU_cDist(
gen_obj_dict,
gt_obj_dict,
num_states=2,
compare_handles=False,
iou_include_base=False,
rotation_fix_range=True,
num_samples=10000,
):
"""
Compute the IoU-based and centroid-distance-based metrics\n
This metric is the average sum of IoU between parts in the two objects over the sampled articulation states and at the resting state\n
- gen_obj_dict: the dictionary of the generated object\n
- gt_obj_dict: the dictionary of the gt object\n
- num_states: the number of articulation states to compute the metric\n
- compare_handles (optional): whether to compare the handles\n
- iou_include_base (optional): whether to include the base part in the IoU computation\n
- rotation_fix_range (optional): whether to fix the rotation range to 90 degrees for revolute joints\n
- num_samples (optional): the number of samples to use\n
Return:\n
- scores: a dictionary of the computed scores\n
- "AS-IoU": the average IoU over the articulation states\n
- "AS-cDist": the average centroid distance over the articulation states\n
- "RS-IoU": the average IoU at the resting state\n
- "RS-cDist": the average centroid distance at the resting state\n
"""
# Make copies of the dictionaries to avoid modifying the original dictionaries
gen_dict = deepcopy(gen_obj_dict)
gt_dict = deepcopy(gt_obj_dict)
# Strip the handles from the object if not comparing them
if not compare_handles:
gen_dict = remove_handles(gen_dict)
gt_dict = remove_handles(gt_dict)
# Zero center the objects
zero_center_object(gen_dict)
zero_center_object(gt_dict)
# scale the generated object as a whole to match the size of the gt object
gen_bbox_size = compute_overall_bbox_size(gen_dict)
gt_bbox_size = compute_overall_bbox_size(gt_dict)
scale_factor = gt_bbox_size / gen_bbox_size
rescale_object(gen_dict, scale_factor)
mapping_gen2gt = find_part_mapping(gen_dict, gt_dict, use_hungarian=True)
# for i in range(mapping_gen2gt.shape[0]):
# if mapping_gen2gt[i][0] < 100:
# gen_dict['diffuse_tree'][i]["parent"] = gt_dict['diffuse_tree'][int(mapping_gen2gt[i][0])]["parent"]
# gen_dict['diffuse_tree'][i]["children"] = gt_dict['diffuse_tree'][int(mapping_gen2gt[i][0])]["children"]
# gen_dict['diffuse_tree'][i]["id"] = gt_dict['diffuse_tree'][int(mapping_gen2gt[i][0])]["id"]
# mapping_gen2gt = find_part_mapping(gen_dict, gt_dict, use_hungarian=True)
mapping_gt2gen = find_part_mapping(gt_dict, gen_dict, use_hungarian=True)
# Save the original bounding box vertices in rest pose
original_gen_bbox_vertices = np.array(
[get_bbox_vertices(gen_dict, i) for i in range(len(gen_dict["diffuse_tree"]))],
dtype=np.float32,
)
original_gt_bbox_vertices = np.array(
[get_bbox_vertices(gt_dict, i) for i in range(len(gt_dict["diffuse_tree"]))],
dtype=np.float32,
)
# import ipdb
# ipdb.set_trace()
scores_gen2gt = _get_scores(
gen_dict,
gt_dict,
original_gen_bbox_vertices,
original_gt_bbox_vertices,
mapping_gen2gt,
num_states,
rotation_fix_range,
num_samples,
iou_include_base,
)
scores_gt2gen = _get_scores(
gt_dict,
gen_dict,
original_gt_bbox_vertices,
original_gen_bbox_vertices,
mapping_gt2gen,
num_states,
rotation_fix_range,
num_samples,
iou_include_base,
)
scores = {
"AS-IoU": (scores_gen2gt["AS-IoU"] + scores_gt2gen["AS-IoU"]) / 2,
"AS-cDist": (scores_gen2gt["AS-cDist"] + scores_gt2gen["AS-cDist"]) / 2,
"RS-IoU": (scores_gen2gt["RS-IoU"] + scores_gt2gen["RS-IoU"]) / 2,
"RS-cDist": (scores_gen2gt["RS-cDist"] + scores_gt2gen["RS-cDist"]) / 2,
}
return scores