mirror of
https://github.com/SikongJueluo/Mini-Nav.git
synced 2026-07-13 04:25:32 +08:00
feat(compressors): add object scoring and selection for SAM masks
This commit is contained in:
113
mini-nav/tests/test_object_score.py
Normal file
113
mini-nav/tests/test_object_score.py
Normal file
@@ -0,0 +1,113 @@
|
||||
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())
|
||||
Reference in New Issue
Block a user