From 7e19c633a67ef5038b4c1c2aff499ddfb0c9ca54 Mon Sep 17 00:00:00 2001 From: SkalskiP Date: Tue, 18 Feb 2025 10:04:38 +0100 Subject: [PATCH] merge 0.26.0 deprecation changes --- docs/how_to/track_objects.md | 16 ++-- .../count_people_in_zone/inference_example.py | 8 +- .../ultralytics_example.py | 8 +- examples/tracking/inference_example.py | 2 +- examples/tracking/ultralytics_example.py | 2 +- pyproject.toml | 2 +- supervision/__init__.py | 4 +- supervision/annotators/core.py | 86 ------------------- supervision/dataset/core.py | 22 ----- supervision/detection/core.py | 49 +++++++++-- supervision/detection/{lmm.py => vlm.py} | 59 +++++++------ test/detection/{test_lmm.py => test_vlm.py} | 2 +- ...m_florence_2.py => test_vlm_florence_2.py} | 2 +- 13 files changed, 97 insertions(+), 165 deletions(-) rename supervision/detection/{lmm.py => vlm.py} (88%) rename test/detection/{test_lmm.py => test_vlm.py} (99%) rename test/detection/{test_lmm_florence_2.py => test_vlm_florence_2.py} (99%) diff --git a/docs/how_to/track_objects.md b/docs/how_to/track_objects.md index 1b321e7f..c6f128c3 100644 --- a/docs/how_to/track_objects.md +++ b/docs/how_to/track_objects.md @@ -55,7 +55,7 @@ it will be modified to include tracking, labeling, and trace annotations. from ultralytics import YOLO model = YOLO("yolov8n.pt") - box_annotator = sv.BoundingBoxAnnotator() + box_annotator = sv.BoxAnnotator() def callback(frame: np.ndarray, _: int) -> np.ndarray: results = model(frame)[0] @@ -77,7 +77,7 @@ it will be modified to include tracking, labeling, and trace annotations. from inference.models.utils import get_roboflow_model model = get_roboflow_model(model_id="yolov8n-640", api_key=) - box_annotator = sv.BoundingBoxAnnotator() + box_annotator = sv.BoxAnnotator() def callback(frame: np.ndarray, _: int) -> np.ndarray: results = model.infer(frame)[0] @@ -112,7 +112,7 @@ enabling the continuous following of the object's motion path across different f model = YOLO("yolov8n.pt") tracker = sv.ByteTrack() - box_annotator = sv.BoundingBoxAnnotator() + box_annotator = sv.BoxAnnotator() def callback(frame: np.ndarray, _: int) -> np.ndarray: results = model(frame)[0] @@ -136,7 +136,7 @@ enabling the continuous following of the object's motion path across different f model = get_roboflow_model(model_id="yolov8n-640", api_key=) tracker = sv.ByteTrack() - box_annotator = sv.BoundingBoxAnnotator() + box_annotator = sv.BoxAnnotator() def callback(frame: np.ndarray, _: int) -> np.ndarray: results = model.infer(frame)[0] @@ -168,7 +168,7 @@ offering a clear visual representation of each object's class and unique identif model = YOLO("yolov8n.pt") tracker = sv.ByteTrack() - box_annotator = sv.BoundingBoxAnnotator() + box_annotator = sv.BoxAnnotator() label_annotator = sv.LabelAnnotator() def callback(frame: np.ndarray, _: int) -> np.ndarray: @@ -203,7 +203,7 @@ offering a clear visual representation of each object's class and unique identif model = get_roboflow_model(model_id="yolov8n-640", api_key=) tracker = sv.ByteTrack() - box_annotator = sv.BoundingBoxAnnotator() + box_annotator = sv.BoxAnnotator() label_annotator = sv.LabelAnnotator() def callback(frame: np.ndarray, _: int) -> np.ndarray: @@ -250,7 +250,7 @@ movement patterns and interactions between objects in the video. model = YOLO("yolov8n.pt") tracker = sv.ByteTrack() - box_annotator = sv.BoundingBoxAnnotator() + box_annotator = sv.BoxAnnotator() label_annotator = sv.LabelAnnotator() trace_annotator = sv.TraceAnnotator() @@ -288,7 +288,7 @@ movement patterns and interactions between objects in the video. model = get_roboflow_model(model_id="yolov8n-640", api_key=) tracker = sv.ByteTrack() - box_annotator = sv.BoundingBoxAnnotator() + box_annotator = sv.BoxAnnotator() label_annotator = sv.LabelAnnotator() trace_annotator = sv.TraceAnnotator() diff --git a/examples/count_people_in_zone/inference_example.py b/examples/count_people_in_zone/inference_example.py index 8f42ff43..e040a036 100644 --- a/examples/count_people_in_zone/inference_example.py +++ b/examples/count_people_in_zone/inference_example.py @@ -36,7 +36,7 @@ def load_zones_config(file_path: str) -> List[np.ndarray]: def initiate_annotators( polygons: List[np.ndarray], resolution_wh: Tuple[int, int] ) -> Tuple[ - List[sv.PolygonZone], List[sv.PolygonZoneAnnotator], List[sv.BoundingBoxAnnotator] + List[sv.PolygonZone], List[sv.PolygonZoneAnnotator], List[sv.BoxAnnotator] ]: line_thickness = sv.calculate_optimal_line_thickness(resolution_wh=resolution_wh) text_scale = sv.calculate_optimal_text_scale(resolution_wh=resolution_wh) @@ -54,7 +54,7 @@ def initiate_annotators( text_thickness=line_thickness * 2, text_scale=text_scale * 2, ) - box_annotator = sv.BoundingBoxAnnotator( + box_annotator = sv.BoxAnnotator( color=COLORS.by_idx(index), thickness=line_thickness ) zones.append(zone) @@ -97,7 +97,7 @@ def annotate( frame: np.ndarray, zones: List[sv.PolygonZone], zone_annotators: List[sv.PolygonZoneAnnotator], - box_annotators: List[sv.BoundingBoxAnnotator], + box_annotators: List[sv.BoxAnnotator], detections: sv.Detections, ) -> np.ndarray: """ @@ -108,7 +108,7 @@ def annotate( zones (List[sv.PolygonZone]): A list of polygon zones used for detection. zone_annotators (List[sv.PolygonZoneAnnotator]): A list of annotators for drawing zone annotations. - box_annotators (List[sv.BoundingBoxAnnotator]): A list of annotators for + box_annotators (List[sv.BoxAnnotator]): A list of annotators for drawing box annotations. detections (sv.Detections): Detections to be used for annotation. diff --git a/examples/count_people_in_zone/ultralytics_example.py b/examples/count_people_in_zone/ultralytics_example.py index 2fd07782..f8d03fa4 100644 --- a/examples/count_people_in_zone/ultralytics_example.py +++ b/examples/count_people_in_zone/ultralytics_example.py @@ -34,7 +34,7 @@ def load_zones_config(file_path: str) -> List[np.ndarray]: def initiate_annotators( polygons: List[np.ndarray], resolution_wh: Tuple[int, int] ) -> Tuple[ - List[sv.PolygonZone], List[sv.PolygonZoneAnnotator], List[sv.BoundingBoxAnnotator] + List[sv.PolygonZone], List[sv.PolygonZoneAnnotator], List[sv.BoxAnnotator] ]: line_thickness = sv.calculate_optimal_line_thickness(resolution_wh=resolution_wh) text_scale = sv.calculate_optimal_text_scale(resolution_wh=resolution_wh) @@ -52,7 +52,7 @@ def initiate_annotators( text_thickness=line_thickness * 2, text_scale=text_scale * 2, ) - box_annotator = sv.BoundingBoxAnnotator( + box_annotator = sv.BoxAnnotator( color=COLORS.by_idx(index), thickness=line_thickness ) zones.append(zone) @@ -94,7 +94,7 @@ def annotate( frame: np.ndarray, zones: List[sv.PolygonZone], zone_annotators: List[sv.PolygonZoneAnnotator], - box_annotators: List[sv.BoundingBoxAnnotator], + box_annotators: List[sv.BoxAnnotator], detections: sv.Detections, ) -> np.ndarray: """ @@ -105,7 +105,7 @@ def annotate( zones (List[sv.PolygonZone]): A list of polygon zones used for detection. zone_annotators (List[sv.PolygonZoneAnnotator]): A list of annotators for drawing zone annotations. - box_annotators (List[sv.BoundingBoxAnnotator]): A list of annotators for + box_annotators (List[sv.BoxAnnotator]): A list of annotators for drawing box annotations. detections (sv.Detections): Detections to be used for annotation. diff --git a/examples/tracking/inference_example.py b/examples/tracking/inference_example.py index a73a38d4..5365975d 100644 --- a/examples/tracking/inference_example.py +++ b/examples/tracking/inference_example.py @@ -18,7 +18,7 @@ def process_video( model = get_roboflow_model(model_id=model_id, api_key=roboflow_api_key) tracker = sv.ByteTrack() - box_annotator = sv.BoundingBoxAnnotator() + box_annotator = sv.BoxAnnotator() label_annotator = sv.LabelAnnotator() frame_generator = sv.get_video_frames_generator(source_path=source_video_path) video_info = sv.VideoInfo.from_video_path(video_path=source_video_path) diff --git a/examples/tracking/ultralytics_example.py b/examples/tracking/ultralytics_example.py index a3363868..0c67459e 100644 --- a/examples/tracking/ultralytics_example.py +++ b/examples/tracking/ultralytics_example.py @@ -16,7 +16,7 @@ def process_video( model = YOLO(source_weights_path) tracker = sv.ByteTrack() - box_annotator = sv.BoundingBoxAnnotator() + box_annotator = sv.BoxAnnotator() label_annotator = sv.LabelAnnotator() frame_generator = sv.get_video_frames_generator(source_path=source_video_path) video_info = sv.VideoInfo.from_video_path(video_path=source_video_path) diff --git a/pyproject.toml b/pyproject.toml index 828b636e..1816abf0 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -2,7 +2,7 @@ name = "supervision" description = "A set of easy-to-use utils that will come in handy in any Computer Vision project" license = { text = "MIT" } -version = "0.26.0rc3" +version = "0.26.0rc4" readme = "README.md" requires-python = ">=3.8" authors = [ diff --git a/supervision/__init__.py b/supervision/__init__.py index 2b2a0082..556e5d09 100644 --- a/supervision/__init__.py +++ b/supervision/__init__.py @@ -9,7 +9,6 @@ except importlib_metadata.PackageNotFoundError: from supervision.annotators.core import ( BackgroundOverlayAnnotator, BlurAnnotator, - BoundingBoxAnnotator, BoxAnnotator, BoxCornerAnnotator, CircleAnnotator, @@ -46,7 +45,7 @@ from supervision.detection.line_zone import ( LineZoneAnnotator, LineZoneAnnotatorMulticlass, ) -from supervision.detection.lmm import LMM +from supervision.detection.vlm import LMM, VLM from supervision.detection.overlap_filter import ( OverlapFilter, box_non_max_merge, @@ -126,7 +125,6 @@ __all__ = [ "BackgroundOverlayAnnotator", "BaseDataset", "BlurAnnotator", - "BoundingBoxAnnotator", "BoxAnnotator", "BoxCornerAnnotator", "ByteTrack", diff --git a/supervision/annotators/core.py b/supervision/annotators/core.py index a2b2ed56..49ce45ec 100644 --- a/supervision/annotators/core.py +++ b/supervision/annotators/core.py @@ -124,92 +124,6 @@ class BoxAnnotator(BaseAnnotator): return scene -@deprecated( - "`BoundingBoxAnnotator` is deprecated and has been renamed to `BoxAnnotator`." - " `BoundingBoxAnnotator` will be removed in supervision-0.26.0." -) -class BoundingBoxAnnotator(BaseAnnotator): - """ - A class for drawing bounding boxes on an image using provided detections. - """ - - def __init__( - self, - color: Union[Color, ColorPalette] = ColorPalette.DEFAULT, - thickness: int = 2, - color_lookup: ColorLookup = ColorLookup.CLASS, - ): - """ - Args: - color (Union[Color, ColorPalette]): The color or color palette to use for - annotating detections. - thickness (int): Thickness of the bounding box lines. - color_lookup (ColorLookup): Strategy for mapping colors to annotations. - Options are `INDEX`, `CLASS`, `TRACK`. - """ - self.color: Union[Color, ColorPalette] = color - self.thickness: int = thickness - self.color_lookup: ColorLookup = color_lookup - - @ensure_cv2_image_for_annotation - def annotate( - self, - scene: ImageType, - detections: Detections, - custom_color_lookup: Optional[np.ndarray] = None, - ) -> ImageType: - """ - Annotates the given scene with bounding boxes based on the provided detections. - - Args: - scene (ImageType): The image where bounding boxes will be drawn. `ImageType` - is a flexible type, accepting either `numpy.ndarray` or `PIL.Image.Image`. - detections (Detections): Object detections to annotate. - custom_color_lookup (Optional[np.ndarray]): Custom color lookup array. - Allows to override the default color mapping strategy. - - Returns: - The annotated image, matching the type of `scene` (`numpy.ndarray` - or `PIL.Image.Image`) - - Example: - ```python - import supervision as sv - - image = ... - detections = sv.Detections(...) - - bounding_box_annotator = sv.BoundingBoxAnnotator() - annotated_frame = bounding_box_annotator.annotate( - scene=image.copy(), - detections=detections - ) - ``` - - ![bounding-box-annotator-example](https://media.roboflow.com/ - supervision-annotator-examples/bounding-box-annotator-example-purple.png) - """ - assert isinstance(scene, np.ndarray) - for detection_idx in range(len(detections)): - x1, y1, x2, y2 = detections.xyxy[detection_idx].astype(int) - color = resolve_color( - color=self.color, - detections=detections, - detection_idx=detection_idx, - color_lookup=self.color_lookup - if custom_color_lookup is None - else custom_color_lookup, - ) - cv2.rectangle( - img=scene, - pt1=(x1, y1), - pt2=(x2, y2), - color=color.as_bgr(), - thickness=self.thickness, - ) - return scene - - class OrientedBoxAnnotator(BaseAnnotator): """ A class for drawing oriented bounding boxes on an image using provided detections. diff --git a/supervision/dataset/core.py b/supervision/dataset/core.py index f2cf7bce..8af54879 100644 --- a/supervision/dataset/core.py +++ b/supervision/dataset/core.py @@ -705,28 +705,6 @@ class ClassificationDataset(BaseDataset): "a list of paths `List[str]` instead." ) - @property - @deprecated( - "`DetectionDataset.images` property is deprecated and will be removed in " - "`supervision-0.26.0`. Iterate with `for path, image, annotation in dataset:` " - "instead." - ) - def images(self) -> Dict[str, np.ndarray]: - """ - Load all images to memory and return them as a dictionary. - - !!! warning - - Only use this when you need all images at once. - It is much more memory-efficient to initialize dataset with - image paths and use `for path, image, annotation in dataset:`. - """ - if self._images_in_memory: - return self._images_in_memory - - images = {image_path: cv2.imread(image_path) for image_path in self.image_paths} - return images - def _get_image(self, image_path: str) -> np.ndarray: """Assumes that image is in dataset""" if self._images_in_memory: diff --git a/supervision/detection/core.py b/supervision/detection/core.py index 6073dc08..f50c6190 100644 --- a/supervision/detection/core.py +++ b/supervision/detection/core.py @@ -9,11 +9,11 @@ from supervision.config import ( CLASS_NAME_DATA_FIELD, ORIENTED_BOX_COORDINATES, ) -from supervision.detection.lmm import ( +from supervision.detection.vlm import ( LMM, from_florence_2, from_paligemma, - validate_lmm_parameters, + validate_vlm_parameters, VLM, ) from supervision.detection.overlap_filter import ( box_non_max_merge, @@ -39,7 +39,7 @@ from supervision.detection.utils import ( xywh_to_xyxy, ) from supervision.geometry.core import Position -from supervision.utils.internal import get_instance_variables +from supervision.utils.internal import get_instance_variables, deprecated from supervision.validators import validate_detections_fields @@ -799,6 +799,10 @@ class Detections: ) @classmethod + @deprecated( + "`Detections.from_lmm` property is deprecated and will be removed in " + "`supervision-0.31.0`. Use Detections.from_vlm instead." + ) def from_lmm( cls, lmm: Union[LMM, str], result: Union[str, dict], **kwargs: Any ) -> Detections: @@ -837,19 +841,48 @@ class Detections: # array([0]) ``` """ - lmm = validate_lmm_parameters(lmm, result, kwargs) + # filler logic mapping old from_lmm to new from_vlm + lmm_to_vlm = { + LMM.PALIGEMMA: VLM.PALIGEMMA, + LMM.FLORENCE_2: VLM.FLORENCE_2, + LMM.QWEN_2_5_VL: VLM.QWEN_2_5_VL + } - if lmm == LMM.PALIGEMMA: + if isinstance(lmm, LMM): + vlm = lmm_to_vlm[lmm] + + elif isinstance(lmm, str): + try: + lmm_parsed = LMM(lmm.lower()) + except ValueError: + raise ValueError( + f"Invalid LMM string '{lmm}'. Must be one of " + f"{[m.value for m in LMM]}" + ) + vlm = lmm_to_vlm[lmm_parsed] + + else: + raise ValueError( + f"Invalid type for 'lmm': {type(lmm)}. Must be LMM or str." + ) + + return cls.from_vlm(vlm=vlm, result=result, **kwargs) + + @classmethod + def from_vlm(cls, vlm: Union[VLM, str], result: Union[str, dict], **kwargs: Any) -> Detections: + vlm = validate_vlm_parameters(vlm, result, kwargs) + + if vlm == VLM.PALIGEMMA: xyxy, class_id, class_name = from_paligemma(result, **kwargs) data = {CLASS_NAME_DATA_FIELD: class_name} return cls(xyxy=xyxy, class_id=class_id, data=data) - if lmm == LMM.QWEN_2_5_VL: + if vlm == VLM.QWEN_2_5_VL: xyxy, class_id, class_name = from_paligemma(result, **kwargs) data = {CLASS_NAME_DATA_FIELD: class_name} return cls(xyxy=xyxy, class_id=class_id, data=data) - if lmm == LMM.FLORENCE_2: + if vlm == VLM.FLORENCE_2: xyxy, labels, mask, xyxyxyxy = from_florence_2(result, **kwargs) if len(xyxy) == 0: return cls.empty() @@ -862,8 +895,6 @@ class Detections: return cls(xyxy=xyxy, mask=mask, data=data) - raise ValueError(f"Unsupported LMM: {lmm}") - @classmethod def from_easyocr(cls, easyocr_results: list) -> Detections: """ diff --git a/supervision/detection/lmm.py b/supervision/detection/vlm.py similarity index 88% rename from supervision/detection/lmm.py rename to supervision/detection/vlm.py index fc243335..412cdc42 100644 --- a/supervision/detection/lmm.py +++ b/supervision/detection/vlm.py @@ -6,30 +6,41 @@ from typing import Any, Dict, List, Optional, Tuple, Union import numpy as np from supervision.detection.utils import polygon_to_mask, polygon_to_xyxy +from supervision.utils.internal import deprecated +@deprecated( + "`LMM` enum is deprecated and will be removed in " + "`supervision-0.31.0`. Use VLM instead." +) class LMM(Enum): PALIGEMMA = "paligemma" FLORENCE_2 = "florence_2" QWEN_2_5_VL = "qwen_2_5_vl" -RESULT_TYPES: Dict[LMM, type] = { - LMM.PALIGEMMA: str, - LMM.FLORENCE_2: dict, - LMM.QWEN_2_5_VL: str, +class VLM(Enum): + PALIGEMMA = "paligemma" + FLORENCE_2 = "florence_2" + QWEN_2_5_VL = "qwen_2_5_vl" + + +RESULT_TYPES: Dict[VLM, type] = { + VLM.PALIGEMMA: str, + VLM.FLORENCE_2: dict, + VLM.QWEN_2_5_VL: str, } -REQUIRED_ARGUMENTS: Dict[LMM, List[str]] = { - LMM.PALIGEMMA: ["resolution_wh"], - LMM.FLORENCE_2: ["resolution_wh"], - LMM.QWEN_2_5_VL: ["input_wh", "resolution_wh"], +REQUIRED_ARGUMENTS: Dict[VLM, List[str]] = { + VLM.PALIGEMMA: ["resolution_wh"], + VLM.FLORENCE_2: ["resolution_wh"], + VLM.QWEN_2_5_VL: ["input_wh", "resolution_wh"], } -ALLOWED_ARGUMENTS: Dict[LMM, List[str]] = { - LMM.PALIGEMMA: ["resolution_wh", "classes"], - LMM.FLORENCE_2: ["resolution_wh"], - LMM.QWEN_2_5_VL: ["input_wh", "resolution_wh", "classes"], +ALLOWED_ARGUMENTS: Dict[VLM, List[str]] = { + VLM.PALIGEMMA: ["resolution_wh", "classes"], + VLM.FLORENCE_2: ["resolution_wh"], + VLM.QWEN_2_5_VL: ["input_wh", "resolution_wh", "classes"], } SUPPORTED_TASKS_FLORENCE_2 = [ @@ -46,33 +57,33 @@ SUPPORTED_TASKS_FLORENCE_2 = [ ] -def validate_lmm_parameters( - lmm: Union[LMM, str], result: Any, kwargs: Dict[str, Any] -) -> LMM: - if isinstance(lmm, str): +def validate_vlm_parameters( + vlm: Union[VLM, str], result: Any, kwargs: Dict[str, Any] +) -> VLM: + if isinstance(vlm, str): try: - lmm = LMM(lmm.lower()) + vlm = VLM(vlm.lower()) except ValueError: raise ValueError( - f"Invalid lmm value: {lmm}. Must be one of {[e.value for e in LMM]}" + f"Invalid vlm value: {vlm}. Must be one of {[e.value for e in VLM]}" ) - if not isinstance(result, RESULT_TYPES[lmm]): + if not isinstance(result, RESULT_TYPES[vlm]): raise ValueError( - f"Invalid LMM result type: {type(result)}. Must be {RESULT_TYPES[lmm]}" + f"Invalid VLM result type: {type(result)}. Must be {RESULT_TYPES[vlm]}" ) - required_args = REQUIRED_ARGUMENTS.get(lmm, []) + required_args = REQUIRED_ARGUMENTS.get(vlm, []) for arg in required_args: if arg not in kwargs: raise ValueError(f"Missing required argument: {arg}") - allowed_args = ALLOWED_ARGUMENTS.get(lmm, []) + allowed_args = ALLOWED_ARGUMENTS.get(vlm, []) for arg in kwargs: if arg not in allowed_args: - raise ValueError(f"Argument {arg} is not allowed for {lmm.name}") + raise ValueError(f"Argument {arg} is not allowed for {vlm.name}") - return lmm + return vlm def from_paligemma( diff --git a/test/detection/test_lmm.py b/test/detection/test_vlm.py similarity index 99% rename from test/detection/test_lmm.py rename to test/detection/test_vlm.py index a47f6624..ad95e7fa 100644 --- a/test/detection/test_lmm.py +++ b/test/detection/test_vlm.py @@ -4,7 +4,7 @@ from typing import List, Optional, Tuple import numpy as np import pytest -from supervision.detection.lmm import from_paligemma, from_qwen_2_5_vl +from supervision.detection.vlm import from_paligemma, from_qwen_2_5_vl @pytest.mark.parametrize( diff --git a/test/detection/test_lmm_florence_2.py b/test/detection/test_vlm_florence_2.py similarity index 99% rename from test/detection/test_lmm_florence_2.py rename to test/detection/test_vlm_florence_2.py index ebb9658d..0fbe647b 100644 --- a/test/detection/test_lmm_florence_2.py +++ b/test/detection/test_vlm_florence_2.py @@ -4,7 +4,7 @@ from typing import Optional, Tuple import numpy as np import pytest -from supervision.detection.lmm import from_florence_2 +from supervision.detection.vlm import from_florence_2 @pytest.mark.parametrize(