| |
| |
| """Portable NumPy implementation of the BEVFormer NMSFreeCoder decode path.""" |
|
|
| from __future__ import annotations |
|
|
| import numpy as np |
|
|
|
|
| def _sigmoid_direct(value): |
| value = np.asarray(value, dtype=np.float32) |
| one = np.float32(1.0) |
| return one / (one + np.exp(-value)) |
|
|
|
|
| def _sigmoid_stable(value): |
| value = np.asarray(value, dtype=np.float32) |
| output = np.empty_like(value, dtype=np.float32) |
|
|
| positive = value >= np.float32(0.0) |
| negative = ~positive |
|
|
| output[positive] = np.float32(1.0) / ( |
| np.float32(1.0) + np.exp(-value[positive]) |
| ) |
|
|
| exp_value = np.exp(value[negative]) |
| output[negative] = exp_value / ( |
| np.float32(1.0) + exp_value |
| ) |
|
|
| return output |
|
|
|
|
| def _denormalize_bbox(normalized_bboxes): |
| boxes = np.asarray(normalized_bboxes, dtype=np.float32) |
|
|
| if boxes.ndim != 2: |
| raise ValueError( |
| "bbox tensor must be 2D, got {}".format(boxes.shape) |
| ) |
|
|
| if boxes.shape[1] < 8: |
| raise ValueError( |
| "bbox code size must be >=8, got {}".format(boxes.shape[1]) |
| ) |
|
|
| cx = boxes[:, 0:1] |
| cy = boxes[:, 1:2] |
| width = np.exp(boxes[:, 2:3]) |
| length = np.exp(boxes[:, 3:4]) |
| cz = boxes[:, 4:5] |
| height = np.exp(boxes[:, 5:6]) |
|
|
| rotation = np.arctan2( |
| boxes[:, 6:7], |
| boxes[:, 7:8], |
| ) |
|
|
| if boxes.shape[1] > 8: |
| if boxes.shape[1] < 10: |
| raise ValueError( |
| "velocity bbox code requires 10 values, got {}".format( |
| boxes.shape[1] |
| ) |
| ) |
|
|
| velocity_x = boxes[:, 8:9] |
| velocity_y = boxes[:, 9:10] |
|
|
| return np.ascontiguousarray( |
| np.concatenate( |
| [ |
| cx, |
| cy, |
| cz, |
| width, |
| length, |
| height, |
| rotation, |
| velocity_x, |
| velocity_y, |
| ], |
| axis=-1, |
| ), |
| dtype=np.float32, |
| ) |
|
|
| return np.ascontiguousarray( |
| np.concatenate( |
| [ |
| cx, |
| cy, |
| cz, |
| width, |
| length, |
| height, |
| rotation, |
| ], |
| axis=-1, |
| ), |
| dtype=np.float32, |
| ) |
|
|
|
|
| def decode_numpy_nmsfreecoder( |
| cls_scores, |
| bbox_preds, |
| contract, |
| sigmoid_mode=None, |
| sort_kind=None, |
| precomputed_probabilities=None, |
| ): |
| num_classes = int(contract["num_classes"]) |
| max_num = int(contract["max_num"]) |
| num_query = int(contract["num_query"]) |
| code_size = int(contract["code_size"]) |
|
|
| bbox = np.asarray(bbox_preds, dtype=np.float32).reshape( |
| num_query, |
| code_size, |
| ) |
|
|
| if not np.isfinite(bbox).all(): |
| raise ValueError("bbox predictions contain non-finite values") |
|
|
| if precomputed_probabilities is not None: |
| probabilities = np.asarray( |
| precomputed_probabilities, |
| dtype=np.float32, |
| ).reshape(num_query, num_classes) |
| else: |
| cls = np.asarray(cls_scores, dtype=np.float32).reshape( |
| num_query, |
| num_classes, |
| ) |
|
|
| if not np.isfinite(cls).all(): |
| raise ValueError("classification logits contain non-finite values") |
|
|
| selected_sigmoid = ( |
| sigmoid_mode |
| or contract.get("selected_sigmoid_mode") |
| or "direct" |
| ) |
|
|
| if selected_sigmoid == "direct": |
| probabilities = _sigmoid_direct(cls) |
| elif selected_sigmoid == "stable": |
| probabilities = _sigmoid_stable(cls) |
| else: |
| raise ValueError( |
| "unsupported sigmoid mode: {}".format(selected_sigmoid) |
| ) |
|
|
| flattened = np.ascontiguousarray( |
| probabilities.reshape(-1), |
| dtype=np.float32, |
| ) |
|
|
| keep_count = min(max_num, flattened.size) |
|
|
| |
| |
| |
| partition_index = keep_count - 1 |
| candidate_indices = np.argpartition( |
| -flattened, |
| partition_index, |
| kind="introselect", |
| )[:keep_count] |
| candidate_scores = flattened[candidate_indices] |
|
|
| |
| sort_order = np.lexsort( |
| (candidate_indices, -candidate_scores) |
| ) |
| order = candidate_indices[sort_order] |
|
|
| scores = np.ascontiguousarray( |
| flattened[order], |
| dtype=np.float32, |
| ) |
| labels = np.ascontiguousarray( |
| order % num_classes, |
| dtype=np.int64, |
| ) |
| bbox_indices = order // num_classes |
|
|
| selected_bbox = np.ascontiguousarray( |
| bbox[bbox_indices], |
| dtype=np.float32, |
| ) |
|
|
| decoded_boxes = _denormalize_bbox(selected_bbox) |
|
|
| score_threshold = contract.get("score_threshold") |
| threshold_mask = np.ones( |
| scores.shape, |
| dtype=bool, |
| ) |
|
|
| if score_threshold is not None: |
| threshold = float(score_threshold) |
| threshold_mask = scores > np.float32(threshold) |
|
|
| temporary_threshold = threshold |
|
|
| while int(np.count_nonzero(threshold_mask)) == 0: |
| temporary_threshold *= 0.9 |
|
|
| if temporary_threshold < 0.01: |
| threshold_mask = scores > np.float32(-1.0) |
| break |
|
|
| threshold_mask = scores >= np.float32( |
| temporary_threshold |
| ) |
|
|
| post_center_range = contract.get("post_center_range") |
|
|
| if post_center_range is None: |
| raise ValueError( |
| "post_center_range is required by this BEVFormer contract" |
| ) |
|
|
| post_center = np.asarray( |
| post_center_range, |
| dtype=np.float32, |
| ).reshape(-1) |
|
|
| if post_center.size != 6: |
| raise ValueError( |
| "post_center_range must contain 6 values" |
| ) |
|
|
| spatial_mask = np.all( |
| decoded_boxes[:, :3] >= post_center[:3], |
| axis=1, |
| ) |
| spatial_mask &= np.all( |
| decoded_boxes[:, :3] <= post_center[3:], |
| axis=1, |
| ) |
|
|
| |
| if score_threshold: |
| spatial_mask &= threshold_mask |
|
|
| final_boxes = np.ascontiguousarray( |
| decoded_boxes[spatial_mask], |
| dtype=np.float32, |
| ) |
| final_scores = np.ascontiguousarray( |
| scores[spatial_mask], |
| dtype=np.float32, |
| ) |
| final_labels = np.ascontiguousarray( |
| labels[spatial_mask], |
| dtype=np.int64, |
| ) |
|
|
| return final_boxes, final_scores, final_labels |
|
|