File size: 8,789 Bytes
c28dddb
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
"""
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