From 22b970e4d402914ad7fc5b57bb3610e7a3058c8d Mon Sep 17 00:00:00 2001 From: SkalskiP Date: Tue, 6 Aug 2024 15:24:21 +0200 Subject: [PATCH] ready for test --- supervision/detection/overlap_filter.py | 33 +++--- .../detection/tools/inference_slicer.py | 100 ++++++++++++++---- 2 files changed, 99 insertions(+), 34 deletions(-) diff --git a/supervision/detection/overlap_filter.py b/supervision/detection/overlap_filter.py index ab4408d1..602ff56b 100644 --- a/supervision/detection/overlap_filter.py +++ b/supervision/detection/overlap_filter.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from enum import Enum from typing import List, Union @@ -248,16 +250,23 @@ class OverlapFilter(Enum): NON_MAX_SUPPRESSION = "non_max_suppression" NON_MAX_MERGE = "non_max_merge" + @classmethod + def list(cls): + return list(map(lambda c: c.value, cls)) -def validate_overlap_filter( - strategy: Union[OverlapFilter, str], -) -> OverlapFilter: - if isinstance(strategy, str): - try: - strategy = OverlapFilter(strategy.lower()) - except ValueError: - raise ValueError( - f"Invalid strategy value: {strategy}. Must be one of " - f"{[e.value for e in OverlapFilter]}" - ) - return strategy + @classmethod + def from_value(cls, value: Union[OverlapFilter, str]) -> OverlapFilter: + if isinstance(value, cls): + return value + if isinstance(value, str): + value = value.lower() + try: + return cls(value) + except ValueError: + raise ValueError( + f"Invalid value: {value}. Must be one of {cls.list()}" + ) + raise ValueError( + f"Invalid value type: {type(value)}. Must be an instance of " + f"{cls.__name__} or str." + ) diff --git a/supervision/detection/tools/inference_slicer.py b/supervision/detection/tools/inference_slicer.py index 38d85faa..9cd8bc03 100644 --- a/supervision/detection/tools/inference_slicer.py +++ b/supervision/detection/tools/inference_slicer.py @@ -6,10 +6,11 @@ import numpy as np from supervision.config import ORIENTED_BOX_COORDINATES from supervision.detection.core import Detections -from supervision.detection.overlap_filter import OverlapFilter, validate_overlap_filter +from supervision.detection.overlap_filter import OverlapFilter from supervision.detection.utils import move_boxes, move_masks, move_oriented_boxes from supervision.utils.image import crop_image -from supervision.utils.internal import SupervisionWarnings +from supervision.utils.internal import SupervisionWarnings, warn_deprecated, \ + deprecated_parameter def move_detections( @@ -56,9 +57,14 @@ class InferenceSlicer: Args: slice_wh (Tuple[int, int]): Dimensions of each slice in the format `(width, height)`. - overlap_ratio_wh (Tuple[float, float]): Overlap ratio between consecutive - slices in the format `(width_ratio, height_ratio)`. - overlap_filter_strategy (Union[OverlapFilter, str]): Strategy for + overlap_ratio_wh (Optional[Tuple[float, float]]): A tuple representing the + desired overlap ratio for width and height between consecutive slices. + Each value should be in the range [0, 1), where 0 means no overlap and + a value close to 1 means high overlap. + overlap_wh (Optional[Tuple[int, int]]): A tuple representing the desired + overlap for width and height between consecutive slices. Each value + should be greater than 0. + overlap_filter (Union[OverlapFilter, str]): Strategy for filtering or merging overlapping detections in slices. iou_threshold (float): Intersection over Union (IoU) threshold used when filtering by overlap. @@ -73,23 +79,39 @@ class InferenceSlicer: not a multiple of the slice's width or height minus the overlap. """ + @deprecated_parameter( + old_parameter="overlap_filter_strategy", + new_parameter="overlap_filter", + map_function=lambda x: x, + warning_message="`{old_parameter}` in `{function_name}` is deprecated and will " + "be remove in `supervision-0.27.0`. Use '{new_parameter}' " + "instead.", + ) def __init__( self, callback: Callable[[np.ndarray], Detections], slice_wh: Tuple[int, int] = (320, 320), - overlap_ratio_wh: Tuple[float, float] = (0.2, 0.2), - overlap_filter_strategy: Union[ + overlap_ratio_wh: Optional[Tuple[float, float]] = (0.2, 0.2), + overlap_wh: Optional[Tuple[int, int]] = None, + overlap_filter: Union[ OverlapFilter, str ] = OverlapFilter.NON_MAX_SUPPRESSION, iou_threshold: float = 0.5, thread_workers: int = 1, ): - overlap_filter_strategy = validate_overlap_filter(overlap_filter_strategy) + if overlap_ratio_wh is None: + warn_deprecated( + "`overlap_ratio_wh` in `InferenceSlicer.__init__` is deprecated and " + "will be remove in `supervision-0.27.0`. Use `overlap_wh` instead." + ) + + self._validate_overlap(overlap_ratio_wh, overlap_wh) + self.overlap_ratio_wh = overlap_ratio_wh + self.overlap_wh = overlap_wh self.slice_wh = slice_wh - self.overlap_ratio_wh = overlap_ratio_wh self.iou_threshold = iou_threshold - self.overlap_filter_strategy = overlap_filter_strategy + self.overlap_filter = OverlapFilter.from_value(overlap_filter) self.callback = callback self.thread_workers = thread_workers @@ -144,15 +166,15 @@ class InferenceSlicer: detections_list.append(future.result()) merged = Detections.merge(detections_list=detections_list) - if self.overlap_filter_strategy == OverlapFilter.NONE: + if self.overlap_filter == OverlapFilter.NONE: return merged - elif self.overlap_filter_strategy == OverlapFilter.NON_MAX_SUPPRESSION: + elif self.overlap_filter == OverlapFilter.NON_MAX_SUPPRESSION: return merged.with_nms(threshold=self.iou_threshold) - elif self.overlap_filter_strategy == OverlapFilter.NON_MAX_MERGE: + elif self.overlap_filter == OverlapFilter.NON_MAX_MERGE: return merged.with_nmm(threshold=self.iou_threshold) else: warnings.warn( - f"Invalid overlap filter strategy: {self.overlap_filter_strategy}", + f"Invalid overlap filter strategy: {self.overlap_filter}", category=SupervisionWarnings, ) return merged @@ -182,7 +204,8 @@ class InferenceSlicer: def _generate_offset( resolution_wh: Tuple[int, int], slice_wh: Tuple[int, int], - overlap_ratio_wh: Tuple[float, float], + overlap_ratio_wh: Optional[Tuple[float, float]], + overlap_wh: Optional[Tuple[int, int]] ) -> np.ndarray: """ Generate offset coordinates for slicing an image based on the given resolution, @@ -193,10 +216,13 @@ class InferenceSlicer: of the image to be sliced. slice_wh (Tuple[int, int]): A tuple representing the desired width and height of each slice. - overlap_ratio_wh (Tuple[float, float]): A tuple representing the desired - overlap ratio for width and height between consecutive slices. Each - value should be in the range [0, 1), where 0 means no overlap and a - value close to 1 means high overlap. + overlap_ratio_wh (Optional[Tuple[float, float]]): A tuple representing the + desired overlap ratio for width and height between consecutive slices. + Each value should be in the range [0, 1), where 0 means no overlap and + a value close to 1 means high overlap. + overlap_wh (Optional[Tuple[int, int]]): A tuple representing the desired + overlap for width and height between consecutive slices. Each value + should be greater than 0. Returns: np.ndarray: An array of shape `(n, 4)` containing coordinates for each @@ -211,10 +237,17 @@ class InferenceSlicer: """ slice_width, slice_height = slice_wh image_width, image_height = resolution_wh - overlap_ratio_width, overlap_ratio_height = overlap_ratio_wh + overlap_width = ( + overlap_wh[0] + if overlap_wh is not None + else int(overlap_ratio_wh[0] * slice_width)) + overlap_height = ( + overlap_wh[1] + if overlap_wh is not None + else int(overlap_ratio_wh[1] * slice_height)) - width_stride = slice_width - int(overlap_ratio_width * slice_width) - height_stride = slice_height - int(overlap_ratio_height * slice_height) + width_stride = slice_width - overlap_width + height_stride = slice_height - overlap_height ws = np.arange(0, image_width, width_stride) hs = np.arange(0, image_height, height_stride) @@ -226,3 +259,26 @@ class InferenceSlicer: offsets = np.stack([xmin, ymin, xmax, ymax], axis=-1).reshape(-1, 4) return offsets + + @staticmethod + def _validate_overlap( + overlap_ratio_wh: Optional[Tuple[float, float]], + overlap_wh: Optional[Tuple[int, int]] + ) -> None: + if overlap_ratio_wh is not None and overlap_wh is not None: + raise ValueError( + "Both `overlap_ratio_wh` and `overlap_wh` cannot be provided. " + "Please provide only one of them." + ) + if overlap_ratio_wh is not None: + if not (0 <= overlap_ratio_wh[0] < 1 and 0 <= overlap_ratio_wh[1] < 1): + raise ValueError( + "Overlap ratios must be in the range [0, 1). " + f"Received: {overlap_ratio_wh}" + ) + if overlap_wh is not None: + if not (overlap_wh[0] > 0 and overlap_wh[1] > 0): + raise ValueError( + "Overlap values must be greater than 0. " + f"Received: {overlap_wh}" + )