import numpy as np import pytest import supervision.detection.core as detection_core from supervision.config import CLASS_NAME_DATA_FIELD, ORIENTED_BOX_COORDINATES from supervision.detection.core import LMM, Detections from supervision.detection.vlm import VLM from supervision.utils.internal import SupervisionWarnings from tests.helpers import ( _FakeDeepSparseResults, _FakeDetachTensor, _FakeDetectron2Instances, _FakeMMDetPredInstances, _FakeMMDetResults, _FakeNCNNObject, _FakeTensor, _FakeUltralyticsBoxes, _FakeUltralyticsResults, _FakeYoloNasPrediction, _FakeYoloNasResults, _FakeYOLOv5Results, make_panoptic_png, ) def test_from_yolov5_maps_columns_correctly() -> None: pred = np.array( [ [10, 20, 30, 40, 0.9, 2], [1, 2, 3, 4, 0.1, 7], ], dtype=np.float32, ) results = _FakeYOLOv5Results(pred0=pred) det = Detections.from_yolov5(results) assert isinstance(det, Detections) np.testing.assert_allclose(det.xyxy, pred[:, :4]) np.testing.assert_allclose(det.confidence, pred[:, 4]) np.testing.assert_array_equal(det.class_id, pred[:, 5].astype(int)) def test_from_ultralytics_boxes_branch_maps_fields_and_class_names() -> None: xyxy = np.array([[0, 0, 10, 10], [5, 6, 7, 8]], dtype=np.float32) conf = np.array([0.8, 0.2], dtype=np.float32) cls = np.array([1, 0], dtype=np.float32) names = {0: "cat", 1: "dog"} boxes = _FakeUltralyticsBoxes(xyxy=xyxy, conf=conf, cls=cls, id_=None) results = _FakeUltralyticsResults(boxes=boxes, names=names) det = Detections.from_ultralytics(results) np.testing.assert_allclose(det.xyxy, xyxy) np.testing.assert_allclose(det.confidence, conf) np.testing.assert_array_equal(det.class_id, cls.astype(int)) assert det.tracker_id is None assert CLASS_NAME_DATA_FIELD in det.data expected_names = np.array([names[i] for i in cls.astype(int)]) np.testing.assert_array_equal(det.data[CLASS_NAME_DATA_FIELD], expected_names) def test_from_ultralytics_segmentation_only_branch_uses_masks_and_arange( monkeypatch: pytest.MonkeyPatch, ) -> None: results = _FakeUltralyticsResults(boxes=None, names={}, length=3) fake_masks = np.zeros((3, 10, 10), dtype=bool) fake_xyxy = np.array([[0, 0, 1, 1], [2, 2, 3, 3], [4, 4, 5, 5]], dtype=np.float32) monkeypatch.setattr( detection_core, "extract_ultralytics_masks", lambda _: fake_masks ) monkeypatch.setattr(detection_core, "mask_to_xyxy", lambda masks: fake_xyxy) det = Detections.from_ultralytics(results) np.testing.assert_allclose(det.xyxy, fake_xyxy) np.testing.assert_array_equal(det.mask, fake_masks) np.testing.assert_array_equal(det.class_id, np.arange(len(results))) def test_from_ultralytics_segmentation_only_without_masks_returns_empty() -> None: """Segmentation-only Ultralytics results without masks return empty detections.""" results = _FakeUltralyticsResults(boxes=None, names={}, length=0) det = Detections.from_ultralytics(results) assert len(det) == 0 assert det.xyxy.shape == (0, 4) assert det.mask is None np.testing.assert_array_equal( det.data[CLASS_NAME_DATA_FIELD], np.array([], dtype=str) ) @pytest.mark.parametrize( ("bboxes", "conf", "labels", "expected_len"), [ ( np.empty((0, 4), dtype=np.float32), np.empty((0,), dtype=np.float32), np.empty((0,), dtype=np.int64), 0, ), ( np.array([[1, 2, 3, 4], [10, 20, 30, 40]], dtype=np.float32), np.array([0.3, 0.9], dtype=np.float32), np.array([5, 6], dtype=np.int64), 2, ), ], ) def test_from_yolo_nas_handles_empty_and_non_empty( bboxes: np.ndarray, conf: np.ndarray, labels: np.ndarray, expected_len: int, ) -> None: pred = _FakeYoloNasPrediction( bboxes_xyxy=bboxes, confidence=conf, labels=labels, ) results = _FakeYoloNasResults(prediction=pred) det = Detections.from_yolo_nas(results) assert len(det) == expected_len if expected_len > 0: np.testing.assert_allclose(det.xyxy, bboxes) np.testing.assert_allclose(det.confidence, conf) np.testing.assert_array_equal(det.class_id, labels.astype(int)) def test_from_tensorflow_scales_axes_on_non_square_image() -> None: """Non-square image exposes swapped scaling: y uses height, x uses width.""" results = { "detection_boxes": [ _FakeTensor(np.array([[0.1, 0.2, 0.5, 0.6]], dtype=np.float32)) ], "detection_scores": [_FakeTensor(np.array([0.9], dtype=np.float32))], "detection_classes": [_FakeTensor(np.array([1], dtype=np.float32))], } det = Detections.from_tensorflow(results, resolution_wh=(1000, 500)) # xmin=0.2*1000, ymin=0.1*500, xmax=0.6*1000, ymax=0.5*500 np.testing.assert_allclose(det.xyxy, [[200.0, 50.0, 600.0, 250.0]]) np.testing.assert_allclose(det.confidence, [0.9]) np.testing.assert_array_equal(det.class_id, [1]) def test_from_tensorflow_does_not_mutate_source_boxes() -> None: """Scaling must copy the tensor buffer, leaving the caller's boxes untouched.""" source_boxes = np.array([[0.1, 0.2, 0.5, 0.6]], dtype=np.float32) original = source_boxes.copy() results = { "detection_boxes": [_FakeTensor(source_boxes)], "detection_scores": [_FakeTensor(np.array([0.9], dtype=np.float32))], "detection_classes": [_FakeTensor(np.array([1], dtype=np.float32))], } det = Detections.from_tensorflow(results, resolution_wh=(1000, 500)) np.testing.assert_array_equal(source_boxes, original) np.testing.assert_allclose(det.xyxy, [[200.0, 50.0, 600.0, 250.0]]) class TestFromLMMMapping: """`from_lmm` must map every LMM member to a VLM without raising KeyError.""" @pytest.mark.parametrize( "lmm_member", [pytest.param(member, id=member.name.lower()) for member in LMM], ) def test_from_lmm_maps_every_member_to_vlm( self, lmm_member: LMM, monkeypatch: pytest.MonkeyPatch ) -> None: """Each LMM member dispatches to the VLM sharing its value with args intact.""" captured: dict[str, object] = {} def fake_from_vlm(vlm: VLM, result: str, **kwargs: object) -> Detections: captured["vlm"] = vlm captured["result"] = result captured["kwargs"] = kwargs return Detections.empty() monkeypatch.setattr(Detections, "from_vlm", staticmethod(fake_from_vlm)) Detections.from_lmm(lmm_member, result="sentinel", resolution_wh=(10, 10)) assert isinstance(captured["vlm"], VLM) assert captured["vlm"].value == lmm_member.value # type: ignore[union-attr] assert captured["result"] == "sentinel" assert captured["kwargs"]["resolution_wh"] == (10, 10) # type: ignore[index] # --------------------------------------------------------------------------- # from_transformers # --------------------------------------------------------------------------- class TestFromTransformers: """from_transformers routes detection/segmentation inputs to the right processor.""" def test_detection_path_maps_boxes_labels_scores(self) -> None: """Detection result with boxes+labels+scores sets xyxy, class_id, confidence.""" xyxy = np.array([[10, 20, 30, 40], [5, 6, 7, 8]], dtype=np.float32) labels = np.array([1, 0], dtype=np.int64) scores = np.array([0.9, 0.5], dtype=np.float32) result = { "boxes": _FakeDetachTensor(xyxy), "labels": _FakeDetachTensor(labels), "scores": _FakeDetachTensor(scores), } det = Detections.from_transformers(result) np.testing.assert_allclose(det.xyxy, xyxy) np.testing.assert_array_equal(det.class_id, labels.astype(int)) np.testing.assert_allclose(det.confidence, scores) def test_detection_path_empty_returns_zero_detections(self) -> None: """Detection path with zero-row tensors yields an empty Detections.""" result = { "boxes": _FakeDetachTensor(np.empty((0, 4), dtype=np.float32)), "labels": _FakeDetachTensor(np.empty(0, dtype=np.int64)), "scores": _FakeDetachTensor(np.empty(0, dtype=np.float32)), } det = Detections.from_transformers(result) assert len(det) == 0 def test_detection_path_with_id2label_populates_class_names(self) -> None: """id2label mapping adds class name strings to data dict.""" labels = np.array([0, 1], dtype=np.int64) result = { "boxes": _FakeDetachTensor(np.zeros((2, 4), dtype=np.float32)), "labels": _FakeDetachTensor(labels), "scores": _FakeDetachTensor(np.array([0.8, 0.7], dtype=np.float32)), } det = Detections.from_transformers(result, id2label={0: "cat", 1: "dog"}) np.testing.assert_array_equal(det.data[CLASS_NAME_DATA_FIELD], ["cat", "dog"]) def test_v4_segmentation_masks_without_boxes_yield_correct_shape(self) -> None: """V4 segmentation with masks-only produces mask of shape (N, H, W).""" masks_bool = np.zeros((2, 4, 4), dtype=bool) masks_bool[0, 0:2, 0:2] = True masks_bool[1, 2:4, 2:4] = True result = { "masks": _FakeDetachTensor(masks_bool.astype(np.uint8)), "labels": _FakeDetachTensor(np.array([0, 1], dtype=np.int64)), "scores": _FakeDetachTensor(np.array([0.9, 0.8], dtype=np.float32)), } det = Detections.from_transformers(result) assert len(det) == 2 assert det.mask is not None assert det.mask.shape == (2, 4, 4) def test_v4_panoptic_png_string_extracts_masks_from_red_channel(self) -> None: """V4 panoptic: png_string+segments_info produce masks keyed by segment id.""" seg_map = np.zeros((4, 4), dtype=np.uint8) seg_map[0:2, 0:2] = 1 seg_map[2:4, 2:4] = 2 png_bytes = make_panoptic_png(seg_map) result = { "png_string": png_bytes, "segments_info": [ {"id": 1, "category_id": 3}, {"id": 2, "category_id": 7}, ], } det = Detections.from_transformers(result) assert len(det) == 2 np.testing.assert_array_equal(det.class_id, [3, 7]) assert det.mask is not None def test_v5_segmentation_key_routes_to_semantic_instance_processor(self) -> None: """'segmentation' key routes through v5 semantic/instance segmentation path.""" seg_arr = np.zeros((4, 4), dtype=np.int64) seg_arr[2:4, :] = 1 segments_info = [ {"id": 0, "label_id": 0, "score": 0.9}, {"id": 1, "label_id": 1, "score": 0.8}, ] result = { "segmentation": _FakeDetachTensor(seg_arr), "segments_info": segments_info, } det = Detections.from_transformers(result) assert len(det) == 2 np.testing.assert_array_equal(det.class_id, [0, 1]) np.testing.assert_allclose(det.confidence, [0.9, 0.8]) def test_unrecognised_keys_raise_value_error(self) -> None: """Dict with no valid keys (no boxes/masks/segmentation) raises ValueError.""" with pytest.raises(ValueError, match="valid fields"): Detections.from_transformers({}) # --------------------------------------------------------------------------- # from_detectron2 # --------------------------------------------------------------------------- class TestFromDetectron2: """from_detectron2 maps Detectron2 pred_instances to Detections fields.""" def test_two_detections_without_masks_maps_fields(self) -> None: """N=2, no pred_masks: xyxy, confidence, class_id extracted correctly.""" xyxy = np.array([[0, 0, 10, 10], [5, 5, 15, 15]], dtype=np.float32) scores = np.array([0.9, 0.7], dtype=np.float32) class_ids = np.array([1, 2], dtype=np.int64) instances = _FakeDetectron2Instances(xyxy, scores, class_ids) result = {"instances": instances} det = Detections.from_detectron2(result) np.testing.assert_allclose(det.xyxy, xyxy) np.testing.assert_allclose(det.confidence, scores) np.testing.assert_array_equal(det.class_id, class_ids.astype(int)) assert det.mask is None def test_single_detection_without_masks(self) -> None: """N=1 detection without masks returns one-element Detections.""" xyxy = np.array([[1, 2, 3, 4]], dtype=np.float32) instances = _FakeDetectron2Instances( xyxy, np.array([0.5], dtype=np.float32), np.array([0]) ) det = Detections.from_detectron2({"instances": instances}) assert len(det) == 1 def test_with_pred_masks_sets_mask_field(self) -> None: """When pred_masks present, mask is populated with boolean array.""" xyxy = np.array([[0, 0, 4, 4]], dtype=np.float32) masks = np.ones((1, 4, 4), dtype=bool) instances = _FakeDetectron2Instances( xyxy, np.array([0.8], dtype=np.float32), np.array([0]), masks=masks ) det = Detections.from_detectron2({"instances": instances}) assert det.mask is not None assert det.mask.shape == (1, 4, 4) def test_empty_instances_returns_zero_length(self) -> None: """Empty pred_instances arrays produce a zero-length Detections.""" instances = _FakeDetectron2Instances( np.empty((0, 4), dtype=np.float32), np.empty(0, dtype=np.float32), np.empty(0, dtype=np.int64), ) det = Detections.from_detectron2({"instances": instances}) assert len(det) == 0 # --------------------------------------------------------------------------- # from_mmdetection # --------------------------------------------------------------------------- class TestFromMMDetection: """from_mmdetection maps MMDet pred_instances to Detections fields.""" def test_two_detections_without_masks(self) -> None: """N=2, no masks attribute: xyxy, confidence, class_id set; mask is None.""" xyxy = np.array([[0, 0, 10, 10], [5, 5, 20, 20]], dtype=np.float32) scores = np.array([0.85, 0.6], dtype=np.float32) labels = np.array([0, 3], dtype=np.int64) pred_instances = _FakeMMDetPredInstances(xyxy, scores, labels) result = _FakeMMDetResults(pred_instances) det = Detections.from_mmdetection(result) np.testing.assert_allclose(det.xyxy, xyxy) np.testing.assert_allclose(det.confidence, scores) np.testing.assert_array_equal(det.class_id, labels.astype(int)) assert det.mask is None def test_single_detection_without_masks(self) -> None: """N=1 detection without masks returns one-element Detections.""" pred_instances = _FakeMMDetPredInstances( np.array([[2, 4, 6, 8]], dtype=np.float32), np.array([0.75], dtype=np.float32), np.array([1]), ) det = Detections.from_mmdetection(_FakeMMDetResults(pred_instances)) assert len(det) == 1 def test_with_masks_populates_mask_field(self) -> None: """When masks present in pred_instances, mask field is set.""" xyxy = np.array([[0, 0, 4, 4]], dtype=np.float32) masks = np.ones((1, 4, 4), dtype=bool) pred_instances = _FakeMMDetPredInstances( xyxy, np.array([0.9], dtype=np.float32), np.array([0]), masks=masks ) det = Detections.from_mmdetection(_FakeMMDetResults(pred_instances)) assert det.mask is not None assert det.mask.shape == (1, 4, 4) def test_empty_pred_instances_returns_zero_length(self) -> None: """Empty bboxes/scores/labels arrays yield zero-length Detections.""" pred_instances = _FakeMMDetPredInstances( np.empty((0, 4), dtype=np.float32), np.empty(0, dtype=np.float32), np.empty(0, dtype=np.int64), ) det = Detections.from_mmdetection(_FakeMMDetResults(pred_instances)) assert len(det) == 0 # --------------------------------------------------------------------------- # from_paddledet # --------------------------------------------------------------------------- class TestFromPaddleDet: """from_paddledet extracts xyxy, confidence, class_id from the bbox column array.""" def test_empty_bbox_returns_empty_detections(self) -> None: """Empty (0,6) bbox array yields zero-length Detections.""" det = Detections.from_paddledet({"bbox": np.empty((0, 6), dtype=np.float32)}) assert len(det) == 0 @pytest.mark.parametrize( ("bbox_array", "expected_len"), [ pytest.param( np.array( [[0, 0.9, 10, 20, 30, 40], [1, 0.7, 5, 6, 7, 8]], dtype=np.float32, ), 2, id="two-detections", ), pytest.param( np.array([[2, 0.5, 1, 2, 3, 4]], dtype=np.float32), 1, id="single-detection", ), ], ) def test_maps_bbox_columns_to_detections( self, bbox_array: np.ndarray, expected_len: int ) -> None: """bbox[:,0]=class_id, [:,1]=confidence, [:,2:6]=xyxy are extracted.""" result = {"bbox": bbox_array} det = Detections.from_paddledet(result) assert len(det) == expected_len np.testing.assert_allclose(det.xyxy, bbox_array[:, 2:6]) np.testing.assert_allclose(det.confidence, bbox_array[:, 1]) np.testing.assert_array_equal(det.class_id, bbox_array[:, 0].astype(int)) # --------------------------------------------------------------------------- # from_deepsparse # --------------------------------------------------------------------------- class TestFromDeepSparse: """from_deepsparse maps DeepSparse boxes/scores/labels to Detections.""" def test_empty_results_return_empty_detections(self) -> None: """Empty boxes/scores/labels arrays yield zero-length Detections.""" result = _FakeDeepSparseResults( boxes=[np.empty((0, 4), dtype=np.float32)], scores=[np.empty(0, dtype=np.float32)], labels=[np.empty(0, dtype=np.float32)], ) det = Detections.from_deepsparse(result) assert len(det) == 0 @pytest.mark.parametrize( ("boxes", "scores", "labels", "expected_len"), [ pytest.param( np.array([[0, 0, 10, 10], [5, 5, 15, 15]], dtype=np.float32), np.array([0.95, 0.8], dtype=np.float32), np.array([0, 1], dtype=np.float32), 2, id="two-detections", ), pytest.param( np.array([[1, 2, 3, 4]], dtype=np.float32), np.array([0.6], dtype=np.float32), np.array([3], dtype=np.float32), 1, id="single-detection", ), ], ) def test_maps_boxes_scores_labels_to_detections( self, boxes: np.ndarray, scores: np.ndarray, labels: np.ndarray, expected_len: int, ) -> None: """boxes[0], scores[0], labels[0] are extracted into Detections fields.""" result = _FakeDeepSparseResults(boxes=[boxes], scores=[scores], labels=[labels]) det = Detections.from_deepsparse(result) assert len(det) == expected_len np.testing.assert_allclose(det.xyxy, boxes) np.testing.assert_allclose(det.confidence, scores) np.testing.assert_array_equal(det.class_id, labels.astype(int)) # --------------------------------------------------------------------------- # from_easyocr # --------------------------------------------------------------------------- class TestFromEasyOCR: """from_easyocr converts EasyOCR polygon-corner results to Detections.""" def test_two_detections_produces_correct_xyxy_and_text(self) -> None: """Two detections with rectangular corners produce correct xyxy and text.""" bbox_a = [[10, 10], [30, 10], [30, 20], [10, 20]] bbox_b = [[50, 5], [80, 5], [80, 25], [50, 25]] results = [ (bbox_a, "hello", 0.95), (bbox_b, "world", 0.80), ] det = Detections.from_easyocr(results) assert len(det) == 2 np.testing.assert_allclose(det.xyxy[0], [10, 10, 30, 20]) np.testing.assert_allclose(det.xyxy[1], [50, 5, 80, 25]) np.testing.assert_array_equal( det.data[CLASS_NAME_DATA_FIELD], ["hello", "world"] ) def test_single_detection_returns_one_element(self) -> None: """N=1 result returns a single-detection Detections.""" results = [([[0, 0], [10, 0], [10, 5], [0, 5]], "ok", 0.7)] det = Detections.from_easyocr(results) assert len(det) == 1 def test_empty_list_returns_empty_detections(self) -> None: """Empty input returns an empty Detections.""" det = Detections.from_easyocr([]) assert len(det) == 0 def test_missing_confidence_defaults_to_zero(self) -> None: """Two-element tuples (no confidence) default confidence to 0.""" results = [([[0, 0], [5, 0], [5, 3], [0, 3]], "hi")] det = Detections.from_easyocr(results) assert len(det) == 1 assert float(det.confidence[0]) == pytest.approx(0.0) def test_preserves_oriented_corners_in_data(self) -> None: """Quadrilateral EasyOCR boxes must be preserved in the data payload.""" bbox = [[0, 0], [8, 1], [7, 5], [1, 4]] results = [(bbox, "text", 0.9)] det = Detections.from_easyocr(results) assert ORIENTED_BOX_COORDINATES in det.data np.testing.assert_allclose(det.data[ORIENTED_BOX_COORDINATES], np.array([bbox])) def test_detail_zero_results_raise_clear_error(self) -> None: """detail=0 EasyOCR results must fail with a descriptive ValueError.""" with pytest.raises(ValueError, match="detail=1"): Detections.from_easyocr(["text"]) # --------------------------------------------------------------------------- # from_azure_analyze_image # --------------------------------------------------------------------------- def _make_azure_result( detections: list[dict], ) -> dict: """Build a minimal Azure Image Analysis response dict.""" return {"objectsResult": {"values": detections}} def _make_azure_detection(x: int, y: int, w: int, h: int, tags: list[dict]) -> dict: """Build one Azure detection entry.""" return { "boundingBox": {"x": x, "y": y, "w": w, "h": h}, "tags": tags, } class TestFromAzureAnalyzeImage: """from_azure_analyze_image converts Azure object detection results.""" def test_dynamic_class_mapping_assigns_ids_in_order(self) -> None: """Without class_map, unique class names get monotonically increasing IDs.""" result = _make_azure_result( [ _make_azure_detection( 0, 0, 10, 10, [{"name": "cat", "confidence": 0.9}] ), _make_azure_detection( 20, 20, 10, 10, [{"name": "dog", "confidence": 0.7}] ), ] ) det = Detections.from_azure_analyze_image(result) assert len(det) == 2 # cat gets id=0 (first seen), dog gets id=1 np.testing.assert_array_equal(det.class_id, [0, 1]) np.testing.assert_allclose(det.confidence, [0.9, 0.7]) np.testing.assert_allclose(det.xyxy[0], [0, 0, 10, 10]) def test_explicit_class_map_filters_unknown_classes(self) -> None: """With class_map, the highest-confidence mapped tag is selected.""" class_map = {5: "cat"} result = _make_azure_result( [ _make_azure_detection( 0, 0, 10, 10, [ {"name": "unknown", "confidence": 0.95}, {"name": "cat", "confidence": 0.9}, ], ), ] ) det = Detections.from_azure_analyze_image(result, class_map=class_map) assert len(det) == 1 assert int(det.class_id[0]) == 5 np.testing.assert_allclose(det.confidence, [0.9]) def test_unmapped_tags_warn_and_skip_detection(self) -> None: """With class_map, completely unmapped tags should warn before skipping.""" class_map = {5: "cat"} result = _make_azure_result( [ _make_azure_detection( 0, 0, 10, 10, [ {"name": "unknown", "confidence": 0.95}, {"name": "other", "confidence": 0.9}, ], ), ] ) with pytest.warns(SupervisionWarnings, match="none of its tags matched"): det = Detections.from_azure_analyze_image(result, class_map=class_map) assert len(det) == 0 def test_empty_values_list_returns_empty_detections(self) -> None: """Zero detections in values list produce an empty Detections.""" result = _make_azure_result([]) det = Detections.from_azure_analyze_image(result) assert len(det) == 0 def test_error_key_raises_value_error(self) -> None: """Response containing 'error' key raises ValueError.""" result = {"error": {"message": "service unavailable"}} with pytest.raises(ValueError, match="service unavailable"): Detections.from_azure_analyze_image(result) # --------------------------------------------------------------------------- # from_ncnn # --------------------------------------------------------------------------- class TestFromNCNN: """from_ncnn converts ncnn rect objects (xywh) to xyxy Detections.""" def test_empty_objects_return_empty_detections(self) -> None: """Empty object list yields zero-length Detections.""" det = Detections.from_ncnn([]) assert len(det) == 0 @pytest.mark.parametrize( ("objects", "expected_len"), [ pytest.param( [ _FakeNCNNObject(10, 20, 30, 40, 0.9, 0), _FakeNCNNObject(5, 5, 10, 10, 0.7, 1), ], 2, id="two-detections", ), pytest.param( [_FakeNCNNObject(0, 0, 20, 20, 0.5, 2)], 1, id="single-detection", ), ], ) def test_maps_xywh_rect_to_xyxy(self, objects: list, expected_len: int) -> None: """rect xywh converts to xyxy; prob and label map to confidence/class_id.""" det = Detections.from_ncnn(objects) assert len(det) == expected_len first = objects[0] expected_x2 = first.rect.x + first.rect.w expected_y2 = first.rect.y + first.rect.h np.testing.assert_allclose(det.xyxy[0, 2], expected_x2) np.testing.assert_allclose(det.xyxy[0, 3], expected_y2) assert float(det.confidence[0]) == pytest.approx(first.prob) assert int(det.class_id[0]) == first.label # --------------------------------------------------------------------------- # from_lmm end-to-end # --------------------------------------------------------------------------- class TestFromLMMEndToEnd: """from_lmm end-to-end: deprecated dispatcher produces correct Detections.""" def test_paligemma_result_produces_correct_xyxy(self) -> None: """PaliGemma loc-token string is correctly parsed through the legacy API.""" result = " cat" with pytest.warns(SupervisionWarnings): det = Detections.from_lmm( LMM.PALIGEMMA, result, resolution_wh=(1000, 1000), classes=["cat"], ) assert len(det) == 1 np.testing.assert_allclose(det.xyxy, [[250.0, 250.0, 750.0, 750.0]]) assert int(det.class_id[0]) == 0 def test_string_lmm_name_is_accepted_and_dispatches(self) -> None: """Passing LMM name as lowercase string works identically to the enum.""" with pytest.warns(SupervisionWarnings): det = Detections.from_lmm( "paligemma", "", resolution_wh=(1000, 1000), ) assert len(det) == 0 def test_lmm_values_are_subset_of_vlm_values() -> None: """Every LMM value exists in VLM — required for VLM(lmm.value) to succeed.""" assert {m.value for m in LMM} <= {m.value for m in VLM}