diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 00000000..a8d744c9 Binary files /dev/null and b/.DS_Store differ diff --git a/docs/detection/tools/inference_slicer.md b/docs/detection/tools/inference_slicer.md new file mode 100644 index 00000000..003ed83b --- /dev/null +++ b/docs/detection/tools/inference_slicer.md @@ -0,0 +1,3 @@ +## InferenceSlicer + +:::supervision.detection.tools.inference_slicer.InferenceSlicer diff --git a/docs/detection/tools/slicer.md b/docs/detection/tools/slicer.md deleted file mode 100644 index fbb51b2e..00000000 --- a/docs/detection/tools/slicer.md +++ /dev/null @@ -1,3 +0,0 @@ -## InferenceSlicer - -:::supervision.detection.tools.slicer.InferenceSlicer diff --git a/docs/utils/image.md b/docs/utils/image.md index 1dea2f50..36a833da 100644 --- a/docs/utils/image.md +++ b/docs/utils/image.md @@ -4,4 +4,4 @@ ## crop -:::supervision.utils.image.crop +:::supervision.utils.image.crop_image diff --git a/mkdocs.yml b/mkdocs.yml index 624966fb..fee74016 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -37,6 +37,7 @@ nav: - Utils: detection/utils.md - Tools: - Polygon Zone: detection/tools/polygon_zone.md + - Inference Slicer: detection/tools/inference_slicer.md - Trackers: - Core: tracker/core.md - Dataset: diff --git a/supervision/__init__.py b/supervision/__init__.py index 43918bf8..8fba9308 100644 --- a/supervision/__init__.py +++ b/supervision/__init__.py @@ -16,8 +16,8 @@ from supervision.dataset.core import ( from supervision.detection.annotate import BoxAnnotator, MaskAnnotator from supervision.detection.core import Detections from supervision.detection.line_counter import LineZone, LineZoneAnnotator +from supervision.detection.tools.inference_slicer import InferenceSlicer from supervision.detection.tools.polygon_zone import PolygonZone, PolygonZoneAnnotator -from supervision.detection.tools.slicer import InferenceSlicer from supervision.detection.utils import ( box_iou_batch, filter_polygons_by_area, diff --git a/supervision/detection/tools/slicer.py b/supervision/detection/tools/inference_slicer.py similarity index 51% rename from supervision/detection/tools/slicer.py rename to supervision/detection/tools/inference_slicer.py index ad0ba242..c8c156df 100644 --- a/supervision/detection/tools/slicer.py +++ b/supervision/detection/tools/inference_slicer.py @@ -11,7 +11,8 @@ def move_detections(detections: Detections, offset: np.array) -> Detections: """ Args: detections (sv.Detections): Detections object to be moved. - offset (np.array): An array of shape `(2,)` containing offset values in format is `[dx, dy]`. + offset (np.array): An array of shape `(2,)` containing offset values in format + is `[dx, dy]`. Returns: (sv.Detections) repositioned Detections object. """ @@ -21,7 +22,26 @@ def move_detections(detections: Detections, offset: np.array) -> Detections: class InferenceSlicer: """ - Slicing inference(SAHI) method for small target detection. + InferenceSlicer performs slicing-based inference for small target detection. This + method, often referred to as Slicing Adaptive Inference (SAHI), involves dividing a + larger image into smaller slices, performing inference on each slice, and then + merging the detections. + + Attributes: + 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)`. + iou_threshold (Optional[float]): Intersection over Union (IoU) threshold used + for non-max suppression. + callback (Callable): A function that performs inference on a given image slice + and returns detections. + + Note: + The class ensures that slices do not exceed the boundaries of the original + image. As a result, the final slices in the row and column dimensions might be + smaller than the specified slice dimensions if the image's width or height is + not a multiple of the slice's width or height minus the overlap. """ def __init__( @@ -38,6 +58,37 @@ class InferenceSlicer: validate_inference_callback(callback=callback) def __call__(self, image: np.ndarray) -> Detections: + """ + Performs slicing-based inference on the provided image using the specified + callback. + + Args: + image (np.ndarray): The input image on which inference needs to be + performed. The image should be in the format + `(height, width, channels)`. + + Returns: + Detections: A collection of detections for the entire image after merging + results from all slices and applying NMS. + + Example: + ```python + >>> import cv2 + >>> import supervision as sv + >>> from ultralytics import YOLO + + >>> image = cv2.imread(SOURCE_IMAGE_PATH) + >>> model = YOLO(...) + + >>> def callback(image_slice: np.ndarray) -> sv.Detections: + ... result = model(image_slice)[0] + ... return sv.Detections.from_ultralytics(result) + + >>> slicer = sv.InferenceSlicer(callback = callback) + + >>> detections = slicer(image) + ``` + """ detections_list = [] resolution_wh = (image.shape[1], image.shape[0]) offsets = self._generate_offset( @@ -62,18 +113,29 @@ class InferenceSlicer: overlap_ratio_wh: Tuple[float, float], ) -> np.ndarray: """ - Generate offset coordinates for slicing an image based on the given resolution, slice dimensions, and overlap ratios. + Generate offset coordinates for slicing an image based on the given resolution, + slice dimensions, and overlap ratios. Args: - resolution_wh (Tuple[int, int]): A tuple representing the width and height 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. + resolution_wh (Tuple[int, int]): A tuple representing the width and height + 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. Returns: - np.ndarray: An array of shape `(n, 4)` containing coordinates for each slice in the format `[xmin, ymin, xmax, ymax]`. + np.ndarray: An array of shape `(n, 4)` containing coordinates for each + slice in the format `[xmin, ymin, xmax, ymax]`. Note: - The function ensures that slices do not exceed the boundaries of the original image. As a result, the final slices in the row and column dimensions might be smaller than the specified slice dimensions if the image's width or height is not a multiple of the slice's width or height minus the overlap. + The function ensures that slices do not exceed the boundaries of the + original image. As a result, the final slices in the row and column + dimensions might be smaller than the specified slice dimensions if the + image's width or height is not a multiple of the slice's width or + height minus the overlap. """ slice_width, slice_height = slice_wh image_width, image_height = resolution_wh diff --git a/supervision/detection/utils.py b/supervision/detection/utils.py index 18edcd3a..e882173b 100644 --- a/supervision/detection/utils.py +++ b/supervision/detection/utils.py @@ -380,9 +380,10 @@ def process_roboflow_result( def move_boxes(xyxy: np.ndarray, offset: np.ndarray) -> np.ndarray: """ Args: - xyxy (np.ndarray): An array of shape `(n, 4)` containing the bounding boxes coordinates in format - `[x1, y1, x2, y2]` - offset (np.array): An array of shape `(2,)` containing offset values in format is `[dx, dy]`. + xyxy (np.ndarray): An array of shape `(n, 4)` containing the bounding boxes + coordinates in format `[x1, y1, x2, y2]` + offset (np.array): An array of shape `(2,)` containing offset values in format + is `[dx, dy]`. Returns: (np.ndarray) repositioned bounding boxes