Files
Mini-Nav/mini-nav/tests/test_object_score.py

114 lines
3.4 KiB
Python

import numpy as np
from compressors.object_score import (
MaskScoringConfig,
compute_mask_features,
rank_masks,
score_mask,
select_best_mask,
)
def _rect_mask(height: int, width: int, x: int, y: int, w: int, h: int) -> np.ndarray:
mask = np.zeros((height, width), dtype=bool)
mask[y : y + h, x : x + w] = True
return mask
def test_compute_mask_features_core_metrics() -> None:
mask = _rect_mask(height=20, width=20, x=5, y=4, w=6, h=5)
mask_dict = {
"segment": mask,
"area": int(mask.sum()),
"bbox": [5, 4, 6, 5],
"predicted_iou": 0.8,
"stability_score": 0.9,
}
features = compute_mask_features(mask_dict, image_shape=(20, 20))
assert features.area_ratio == 30 / 400
assert features.fill_ratio == 1.0
assert features.aspect_ratio == 6 / 5
assert features.touch_top is False
assert features.touch_left is False
assert features.num_components == 1
assert features.largest_component_ratio == 1.0
assert features.num_holes == 0
def test_rank_masks_rejects_extreme_small_and_fragmented_masks() -> None:
cfg = MaskScoringConfig(min_area_ratio=0.02)
good_mask = _rect_mask(height=30, width=30, x=6, y=6, w=10, h=10)
fragmented = np.zeros((30, 30), dtype=bool)
fragmented[2, 2] = True
fragmented[4, 7] = True
fragmented[8, 12] = True
fragmented[12, 16] = True
fragmented[16, 20] = True
fragmented[20, 24] = True
fragmented[24, 26] = True
masks = [
{"segment": np.zeros((30, 30), dtype=bool), "area": 1, "bbox": [0, 0, 1, 1]},
{
"segment": fragmented,
"area": int(fragmented.sum()),
"bbox": [2, 2, 25, 25],
},
{
"segment": good_mask,
"area": int(good_mask.sum()),
"bbox": [6, 6, 10, 10],
"predicted_iou": 0.9,
"stability_score": 0.9,
},
]
ranked = rank_masks(masks=masks, image_shape=(30, 30), config=cfg, max_masks=3)
assert len(ranked) == 1
assert ranked[0]["area"] == int(good_mask.sum())
assert "mask_score" in ranked[0]
def test_score_mask_prefers_stable_reasonable_object() -> None:
cfg = MaskScoringConfig()
candidate = {
"segment": _rect_mask(height=100, width=100, x=30, y=20, w=24, h=25),
"area": 24 * 25,
"bbox": [30, 20, 24, 25],
"predicted_iou": 0.92,
"stability_score": 0.91,
}
weak = {
"segment": _rect_mask(height=100, width=100, x=0, y=0, w=4, h=60),
"area": 4 * 60,
"bbox": [0, 0, 4, 60],
"predicted_iou": 0.4,
"stability_score": 0.3,
}
score_candidate = score_mask(candidate, image_shape=(100, 100), config=cfg)
score_weak = score_mask(weak, image_shape=(100, 100), config=cfg)
assert score_candidate > score_weak
def test_select_best_mask_falls_back_to_largest_area_when_all_rejected() -> None:
cfg = MaskScoringConfig(min_area_ratio=0.2)
tiny = _rect_mask(height=20, width=20, x=1, y=1, w=2, h=2)
larger = _rect_mask(height=20, width=20, x=5, y=5, w=4, h=4)
masks = [
{"segment": tiny, "area": int(tiny.sum()), "bbox": [1, 1, 2, 2]},
{"segment": larger, "area": int(larger.sum()), "bbox": [5, 5, 4, 4]},
]
best = select_best_mask(masks=masks, image_shape=(20, 20), config=cfg)
assert best is not None
assert best["area"] == int(larger.sum())