From 6578b4ea521823fb8689bd1d8aa0ffac0a453b58 Mon Sep 17 00:00:00 2001 From: SkalskiP Date: Wed, 5 Nov 2025 20:18:10 +0100 Subject: [PATCH 1/5] initial version of `filter_segments_by_distance` --- docs/detection/utils/masks.md | 6 + supervision/__init__.py | 2 + supervision/detection/utils/masks.py | 137 +++++++++++++++++++ test/detection/utils/test_masks.py | 192 ++++++++++++++++++++++++++- 4 files changed, 336 insertions(+), 1 deletion(-) diff --git a/docs/detection/utils/masks.md b/docs/detection/utils/masks.md index 9e53a6baa..99097bef6 100644 --- a/docs/detection/utils/masks.md +++ b/docs/detection/utils/masks.md @@ -22,3 +22,9 @@ status: new :::supervision.detection.utils.masks.contains_multiple_segments + +
+

filter_segments_by_distance

+
+ +:::supervision.detection.utils.masks.filter_segments_by_distance diff --git a/supervision/__init__.py b/supervision/__init__.py index a70dd20fe..15ebf2c79 100644 --- a/supervision/__init__.py +++ b/supervision/__init__.py @@ -88,6 +88,7 @@ contains_holes, contains_multiple_segments, move_masks, + filter_segments_by_distance, ) from supervision.detection.utils.polygons import ( approximate_polygon, @@ -219,6 +220,7 @@ "draw_text", "edit_distance", "filter_polygons_by_area", + "filter_segments_by_distance", "fuzzy_match_index", "get_coco_class_index_mapping", "get_polygon_center", diff --git a/supervision/detection/utils/masks.py b/supervision/detection/utils/masks.py index c5cfee017..119439508 100644 --- a/supervision/detection/utils/masks.py +++ b/supervision/detection/utils/masks.py @@ -1,5 +1,7 @@ from __future__ import annotations +from typing import Literal + import cv2 import numpy as np import numpy.typing as npt @@ -260,3 +262,138 @@ def resize_masks(masks: np.ndarray, max_dimension: int = 640) -> np.ndarray: resized_masks = masks[:, yv, xv] return resized_masks.reshape(masks.shape[0], new_height, new_width) + + +def filter_segments_by_distance( + mask: npt.NDArray[np.bool_], + absolute_distance: float | None = 100.0, + relative_distance: float | None = None, + connectivity: int = 8, + mode: Literal["edge", "centroid"] = "edge", +) -> npt.NDArray[np.bool_]: + """ + Keep the largest connected component and any other components within a distance threshold. + + Distance can be absolute in pixels or relative to the image diagonal. + + Args: + mask: Boolean mask HxW. + absolute_distance: Max allowed distance in pixels to the main component. + Ignored if `relative_distance` is provided. + relative_distance: Fraction of the diagonal. If set, threshold = fraction * sqrt(H^2 + W^2). + connectivity: Defines which neighboring pixels are considered connected. + - 4-connectedness: Only orthogonal neighbors. + ``` + [ ][X][ ] + [X][O][X] + [ ][X][ ] + ``` + - 8-connectedness: Includes diagonal neighbors. + ``` + [X][X][X] + [X][O][X] + [X][X][X] + ``` + Default is 8. + mode: Defines how distance between components is measured. + - "edge": Uses distance between nearest edges (via distance transform). + - "centroid": Uses distance between component centroids. + + Returns: + Boolean mask after filtering. + + Examples: + ```python + import numpy as np + import supervision as sv + + mask = np.array([ + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 1, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + ], dtype=bool) + + sv.filter_segments_by_distance( + mask, + absolute_distance=2, + mode="edge", + connectivity=8 + ).astype(int) + + # np.array([ + # [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + # [0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0], + # [0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0], + # [0, 1, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0], + # [0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0], + # [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + # [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + # [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + # [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + # [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + # [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + # [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + # ], dtype=bool) + + # The nearby 2×2 block at columns 6–7 is kept because its edge distance + # is within 2 pixels. The distant block at columns 9–10 is removed. + ``` + """ + if mask.dtype != bool: + raise TypeError("mask must be boolean") + + height, width = mask.shape + if not np.any(mask): + return mask.copy() + + image = mask.astype(np.uint8) + num_labels, labels, stats, centroids = cv2.connectedComponentsWithStats( + image, connectivity=connectivity + ) + + if num_labels <= 1: + return mask.copy() + + areas = stats[1:, cv2.CC_STAT_AREA] + main_label = 1 + int(np.argmax(areas)) + + if relative_distance is not None: + diagonal = float(np.hypot(height, width)) + threshold = float(relative_distance) * diagonal + else: + threshold = float(absolute_distance) + + keep_labels = np.zeros(num_labels, dtype=bool) + keep_labels[main_label] = True + + if mode == "centroid": + differences = centroids[1:] - centroids[main_label] + distances = np.sqrt(np.sum(differences**2, axis=1)) + nearby = 1 + np.where(distances <= threshold)[0] + keep_labels[nearby] = True + elif mode == "edge": + main_mask = (labels == main_label).astype(np.uint8) + inverse = 1 - main_mask + distance_transform = cv2.distanceTransform(inverse, cv2.DIST_L2, 3) + for label in range(1, num_labels): + if label == main_label: + continue + component = labels == label + if not np.any(component): + continue + min_distance = float(distance_transform[component].min()) + if min_distance <= threshold: + keep_labels[label] = True + else: + raise ValueError("mode must be 'edge' or 'centroid'") + + return keep_labels[labels] \ No newline at end of file diff --git a/test/detection/utils/test_masks.py b/test/detection/utils/test_masks.py index 2097f6082..287dade77 100644 --- a/test/detection/utils/test_masks.py +++ b/test/detection/utils/test_masks.py @@ -10,7 +10,7 @@ calculate_masks_centroids, contains_holes, contains_multiple_segments, - move_masks, + move_masks, filter_segments_by_distance, ) @@ -500,3 +500,193 @@ def test_contains_multiple_segments( with exception: result = contains_multiple_segments(mask=mask, connectivity=connectivity) assert result == expected_result + + +@pytest.mark.parametrize( + "mask, connectivity, mode, absolute_distance, relative_distance, expected_result, exception", + [ + # single component, unchanged + ( + np.array([ + [0, 0, 0, 0, 0, 0], + [0, 1, 1, 1, 0, 0], + [0, 1, 1, 1, 0, 0], + [0, 1, 1, 1, 0, 0], + [0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0], + ], dtype=bool), + 8, + "edge", + 2.0, + None, + np.array([ + [0, 0, 0, 0, 0, 0], + [0, 1, 1, 1, 0, 0], + [0, 1, 1, 1, 0, 0], + [0, 1, 1, 1, 0, 0], + [0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0], + ], dtype=bool), + DoesNotRaise(), + ), + # two components, edge distance 2, kept with abs=1 + ( + np.array([ + [0, 0, 0, 0, 0, 0], + [0, 1, 1, 1, 0, 1], + [0, 1, 1, 1, 0, 1], + [0, 1, 1, 1, 0, 1], + [0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0], + ], dtype=bool), + 8, + "edge", + 2.0, + None, + np.array([ + [0, 0, 0, 0, 0, 0], + [0, 1, 1, 1, 0, 1], + [0, 1, 1, 1, 0, 1], + [0, 1, 1, 1, 0, 1], + [0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0], + ], dtype=bool), + DoesNotRaise(), + ), + # centroid mode, far centroids, dropped with small relative threshold + ( + np.array([ + [1, 1, 1, 0, 0, 0], + [1, 1, 1, 0, 0, 0], + [1, 1, 1, 0, 0, 0], + [0, 0, 0, 0, 0, 0], + [0, 0, 0, 1, 1, 1], + [0, 0, 0, 1, 1, 1], + ], dtype=bool), + 8, + "centroid", + None, + 0.3, # diagonal ~8.49, threshold ~2.55, centroid gap ~4.24 + np.array([ + [1, 1, 1, 0, 0, 0], + [1, 1, 1, 0, 0, 0], + [1, 1, 1, 0, 0, 0], + [0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0], + ], dtype=bool), + DoesNotRaise(), + ), + # centroid mode, larger relative threshold, kept + ( + np.array([ + [1, 1, 1, 0, 0, 0], + [1, 1, 1, 0, 0, 0], + [1, 1, 1, 0, 0, 0], + [0, 0, 0, 0, 0, 0], + [0, 0, 0, 1, 1, 1], + [0, 0, 0, 1, 1, 1], + ], dtype=bool), + 8, + "centroid", + None, + 0.6, # diagonal ~8.49, threshold ~5.09, centroid gap ~4.24 + np.array([ + [1, 1, 1, 0, 0, 0], + [1, 1, 1, 0, 0, 0], + [1, 1, 1, 0, 0, 0], + [0, 0, 0, 0, 0, 0], + [0, 0, 0, 1, 1, 1], + [0, 0, 0, 1, 1, 1], + ], dtype=bool), + DoesNotRaise(), + ), + # empty mask + ( + np.zeros((4, 4), dtype=bool), + 4, + "edge", + 2.0, + None, + np.zeros((4, 4), dtype=bool), + DoesNotRaise(), + ), + # full mask + ( + np.ones((4, 4), dtype=bool), + 8, + "centroid", + None, + 0.2, + np.ones((4, 4), dtype=bool), + DoesNotRaise(), + ), + # two components, pixel distance = 2, kept with abs=2 + ( + np.array([ + [0, 0, 0, 0, 0, 0, 0, 0], + [0, 1, 1, 1, 0, 1, 1, 1], + [0, 1, 1, 1, 0, 1, 1, 1], + [0, 1, 1, 1, 0, 1, 1, 1], + [0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0], + ], dtype=bool), + 8, + "edge", + 2.0, # was 1.0 + None, + np.array([ + [0, 0, 0, 0, 0, 0, 0, 0], + [0, 1, 1, 1, 0, 1, 1, 1], + [0, 1, 1, 1, 0, 1, 1, 1], + [0, 1, 1, 1, 0, 1, 1, 1], + [0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0], + ], dtype=bool), + DoesNotRaise(), + ), + + # two components, pixel distance = 3, dropped with abs=2 + ( + np.array([ + [0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 1, 1, 1, 0, 0, 0, 1, 1], + [0, 1, 1, 1, 0, 0, 0, 1, 1], + [0, 1, 1, 1, 0, 0, 0, 1, 1], + [0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0], + ], dtype=bool), + 8, + "edge", + 2.0, # keep threshold below 3 so the right blob is removed + None, + np.array([ + [0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 1, 1, 1, 0, 0, 0, 0, 0], + [0, 1, 1, 1, 0, 0, 0, 0, 0], + [0, 1, 1, 1, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0], + ], dtype=bool), + DoesNotRaise(), + ), + ] +) +def test_filter_segments_by_distance_sweep( + mask: npt.NDArray, + connectivity: int, + mode: str, + absolute_distance: float | None, + relative_distance: float | None, + expected_result: npt.NDArray | None, + exception: Exception, +) -> None: + with exception: + result = filter_segments_by_distance( + mask=mask, + connectivity=connectivity, + mode=mode, # type: ignore[arg-type] + absolute_distance=absolute_distance, + relative_distance=relative_distance, + ) + assert np.array_equal(result, expected_result) From 9340cb962b5f2735a948ee7a07c72bb2cb4c9488 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Wed, 5 Nov 2025 19:22:56 +0000 Subject: [PATCH 2/5] =?UTF-8?q?fix(pre=5Fcommit):=20=F0=9F=8E=A8=20auto=20?= =?UTF-8?q?format=20pre-commit=20hooks?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- supervision/__init__.py | 2 +- supervision/detection/utils/masks.py | 2 +- test/detection/utils/test_masks.py | 234 +++++++++++++++------------ 3 files changed, 137 insertions(+), 101 deletions(-) diff --git a/supervision/__init__.py b/supervision/__init__.py index 15ebf2c79..ccd272930 100644 --- a/supervision/__init__.py +++ b/supervision/__init__.py @@ -87,8 +87,8 @@ calculate_masks_centroids, contains_holes, contains_multiple_segments, - move_masks, filter_segments_by_distance, + move_masks, ) from supervision.detection.utils.polygons import ( approximate_polygon, diff --git a/supervision/detection/utils/masks.py b/supervision/detection/utils/masks.py index 119439508..49b403116 100644 --- a/supervision/detection/utils/masks.py +++ b/supervision/detection/utils/masks.py @@ -396,4 +396,4 @@ def filter_segments_by_distance( else: raise ValueError("mode must be 'edge' or 'centroid'") - return keep_labels[labels] \ No newline at end of file + return keep_labels[labels] diff --git a/test/detection/utils/test_masks.py b/test/detection/utils/test_masks.py index 287dade77..3c61e4090 100644 --- a/test/detection/utils/test_masks.py +++ b/test/detection/utils/test_masks.py @@ -10,7 +10,8 @@ calculate_masks_centroids, contains_holes, contains_multiple_segments, - move_masks, filter_segments_by_distance, + filter_segments_by_distance, + move_masks, ) @@ -507,98 +508,122 @@ def test_contains_multiple_segments( [ # single component, unchanged ( - np.array([ - [0, 0, 0, 0, 0, 0], - [0, 1, 1, 1, 0, 0], - [0, 1, 1, 1, 0, 0], - [0, 1, 1, 1, 0, 0], - [0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0], - ], dtype=bool), + np.array( + [ + [0, 0, 0, 0, 0, 0], + [0, 1, 1, 1, 0, 0], + [0, 1, 1, 1, 0, 0], + [0, 1, 1, 1, 0, 0], + [0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0], + ], + dtype=bool, + ), 8, "edge", 2.0, None, - np.array([ - [0, 0, 0, 0, 0, 0], - [0, 1, 1, 1, 0, 0], - [0, 1, 1, 1, 0, 0], - [0, 1, 1, 1, 0, 0], - [0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0], - ], dtype=bool), + np.array( + [ + [0, 0, 0, 0, 0, 0], + [0, 1, 1, 1, 0, 0], + [0, 1, 1, 1, 0, 0], + [0, 1, 1, 1, 0, 0], + [0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0], + ], + dtype=bool, + ), DoesNotRaise(), ), # two components, edge distance 2, kept with abs=1 ( - np.array([ - [0, 0, 0, 0, 0, 0], - [0, 1, 1, 1, 0, 1], - [0, 1, 1, 1, 0, 1], - [0, 1, 1, 1, 0, 1], - [0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0], - ], dtype=bool), + np.array( + [ + [0, 0, 0, 0, 0, 0], + [0, 1, 1, 1, 0, 1], + [0, 1, 1, 1, 0, 1], + [0, 1, 1, 1, 0, 1], + [0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0], + ], + dtype=bool, + ), 8, "edge", 2.0, None, - np.array([ - [0, 0, 0, 0, 0, 0], - [0, 1, 1, 1, 0, 1], - [0, 1, 1, 1, 0, 1], - [0, 1, 1, 1, 0, 1], - [0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0], - ], dtype=bool), + np.array( + [ + [0, 0, 0, 0, 0, 0], + [0, 1, 1, 1, 0, 1], + [0, 1, 1, 1, 0, 1], + [0, 1, 1, 1, 0, 1], + [0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0], + ], + dtype=bool, + ), DoesNotRaise(), ), # centroid mode, far centroids, dropped with small relative threshold ( - np.array([ - [1, 1, 1, 0, 0, 0], - [1, 1, 1, 0, 0, 0], - [1, 1, 1, 0, 0, 0], - [0, 0, 0, 0, 0, 0], - [0, 0, 0, 1, 1, 1], - [0, 0, 0, 1, 1, 1], - ], dtype=bool), + np.array( + [ + [1, 1, 1, 0, 0, 0], + [1, 1, 1, 0, 0, 0], + [1, 1, 1, 0, 0, 0], + [0, 0, 0, 0, 0, 0], + [0, 0, 0, 1, 1, 1], + [0, 0, 0, 1, 1, 1], + ], + dtype=bool, + ), 8, "centroid", None, 0.3, # diagonal ~8.49, threshold ~2.55, centroid gap ~4.24 - np.array([ - [1, 1, 1, 0, 0, 0], - [1, 1, 1, 0, 0, 0], - [1, 1, 1, 0, 0, 0], - [0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0], - ], dtype=bool), + np.array( + [ + [1, 1, 1, 0, 0, 0], + [1, 1, 1, 0, 0, 0], + [1, 1, 1, 0, 0, 0], + [0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0], + ], + dtype=bool, + ), DoesNotRaise(), ), # centroid mode, larger relative threshold, kept ( - np.array([ - [1, 1, 1, 0, 0, 0], - [1, 1, 1, 0, 0, 0], - [1, 1, 1, 0, 0, 0], - [0, 0, 0, 0, 0, 0], - [0, 0, 0, 1, 1, 1], - [0, 0, 0, 1, 1, 1], - ], dtype=bool), + np.array( + [ + [1, 1, 1, 0, 0, 0], + [1, 1, 1, 0, 0, 0], + [1, 1, 1, 0, 0, 0], + [0, 0, 0, 0, 0, 0], + [0, 0, 0, 1, 1, 1], + [0, 0, 0, 1, 1, 1], + ], + dtype=bool, + ), 8, "centroid", None, 0.6, # diagonal ~8.49, threshold ~5.09, centroid gap ~4.24 - np.array([ - [1, 1, 1, 0, 0, 0], - [1, 1, 1, 0, 0, 0], - [1, 1, 1, 0, 0, 0], - [0, 0, 0, 0, 0, 0], - [0, 0, 0, 1, 1, 1], - [0, 0, 0, 1, 1, 1], - ], dtype=bool), + np.array( + [ + [1, 1, 1, 0, 0, 0], + [1, 1, 1, 0, 0, 0], + [1, 1, 1, 0, 0, 0], + [0, 0, 0, 0, 0, 0], + [0, 0, 0, 1, 1, 1], + [0, 0, 0, 1, 1, 1], + ], + dtype=bool, + ), DoesNotRaise(), ), # empty mask @@ -623,54 +648,65 @@ def test_contains_multiple_segments( ), # two components, pixel distance = 2, kept with abs=2 ( - np.array([ - [0, 0, 0, 0, 0, 0, 0, 0], - [0, 1, 1, 1, 0, 1, 1, 1], - [0, 1, 1, 1, 0, 1, 1, 1], - [0, 1, 1, 1, 0, 1, 1, 1], - [0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0], - ], dtype=bool), + np.array( + [ + [0, 0, 0, 0, 0, 0, 0, 0], + [0, 1, 1, 1, 0, 1, 1, 1], + [0, 1, 1, 1, 0, 1, 1, 1], + [0, 1, 1, 1, 0, 1, 1, 1], + [0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0], + ], + dtype=bool, + ), 8, "edge", 2.0, # was 1.0 None, - np.array([ - [0, 0, 0, 0, 0, 0, 0, 0], - [0, 1, 1, 1, 0, 1, 1, 1], - [0, 1, 1, 1, 0, 1, 1, 1], - [0, 1, 1, 1, 0, 1, 1, 1], - [0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0], - ], dtype=bool), + np.array( + [ + [0, 0, 0, 0, 0, 0, 0, 0], + [0, 1, 1, 1, 0, 1, 1, 1], + [0, 1, 1, 1, 0, 1, 1, 1], + [0, 1, 1, 1, 0, 1, 1, 1], + [0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0], + ], + dtype=bool, + ), DoesNotRaise(), ), - # two components, pixel distance = 3, dropped with abs=2 ( - np.array([ - [0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 1, 1, 1, 0, 0, 0, 1, 1], - [0, 1, 1, 1, 0, 0, 0, 1, 1], - [0, 1, 1, 1, 0, 0, 0, 1, 1], - [0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0], - ], dtype=bool), + np.array( + [ + [0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 1, 1, 1, 0, 0, 0, 1, 1], + [0, 1, 1, 1, 0, 0, 0, 1, 1], + [0, 1, 1, 1, 0, 0, 0, 1, 1], + [0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0], + ], + dtype=bool, + ), 8, "edge", 2.0, # keep threshold below 3 so the right blob is removed None, - np.array([ - [0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 1, 1, 1, 0, 0, 0, 0, 0], - [0, 1, 1, 1, 0, 0, 0, 0, 0], - [0, 1, 1, 1, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0], - ], dtype=bool), + np.array( + [ + [0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 1, 1, 1, 0, 0, 0, 0, 0], + [0, 1, 1, 1, 0, 0, 0, 0, 0], + [0, 1, 1, 1, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0], + ], + dtype=bool, + ), DoesNotRaise(), ), - ] + ], ) def test_filter_segments_by_distance_sweep( mask: npt.NDArray, From 4c3d51f186ad3c10f7b469169e3c61a6b540bc9b Mon Sep 17 00:00:00 2001 From: SkalskiP Date: Wed, 5 Nov 2025 20:28:55 +0100 Subject: [PATCH 3/5] making flake8 happy --- supervision/detection/utils/masks.py | 7 ++++--- test/detection/utils/test_masks.py | 2 +- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/supervision/detection/utils/masks.py b/supervision/detection/utils/masks.py index 49b403116..35896f20c 100644 --- a/supervision/detection/utils/masks.py +++ b/supervision/detection/utils/masks.py @@ -272,7 +272,8 @@ def filter_segments_by_distance( mode: Literal["edge", "centroid"] = "edge", ) -> npt.NDArray[np.bool_]: """ - Keep the largest connected component and any other components within a distance threshold. + Keep the largest connected component and any other components within a distance + threshold. Distance can be absolute in pixels or relative to the image diagonal. @@ -345,9 +346,9 @@ def filter_segments_by_distance( # ], dtype=bool) # The nearby 2×2 block at columns 6–7 is kept because its edge distance - # is within 2 pixels. The distant block at columns 9–10 is removed. + # is within 2 pixels. The distant block at columns 9-10 is removed. ``` - """ + """ # noqa E501 // docs if mask.dtype != bool: raise TypeError("mask must be boolean") diff --git a/test/detection/utils/test_masks.py b/test/detection/utils/test_masks.py index 3c61e4090..c23893ada 100644 --- a/test/detection/utils/test_masks.py +++ b/test/detection/utils/test_masks.py @@ -503,7 +503,7 @@ def test_contains_multiple_segments( assert result == expected_result -@pytest.mark.parametrize( +@pytest.mark.parametrize( # noqa: E501 "mask, connectivity, mode, absolute_distance, relative_distance, expected_result, exception", [ # single component, unchanged From 1fef946a19a385061abd45326eaf5188ee15150f Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Wed, 5 Nov 2025 19:29:17 +0000 Subject: [PATCH 4/5] =?UTF-8?q?fix(pre=5Fcommit):=20=F0=9F=8E=A8=20auto=20?= =?UTF-8?q?format=20pre-commit=20hooks?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- test/detection/utils/test_masks.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/detection/utils/test_masks.py b/test/detection/utils/test_masks.py index c23893ada..3c61e4090 100644 --- a/test/detection/utils/test_masks.py +++ b/test/detection/utils/test_masks.py @@ -503,7 +503,7 @@ def test_contains_multiple_segments( assert result == expected_result -@pytest.mark.parametrize( # noqa: E501 +@pytest.mark.parametrize( "mask, connectivity, mode, absolute_distance, relative_distance, expected_result, exception", [ # single component, unchanged From 65f6e418a1a136e9e01d87fca4d9da9e8596b5b1 Mon Sep 17 00:00:00 2001 From: SkalskiP Date: Wed, 5 Nov 2025 20:34:22 +0100 Subject: [PATCH 5/5] making flake8 happy --- test/detection/utils/test_masks.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/detection/utils/test_masks.py b/test/detection/utils/test_masks.py index 3c61e4090..b41f208ed 100644 --- a/test/detection/utils/test_masks.py +++ b/test/detection/utils/test_masks.py @@ -504,7 +504,7 @@ def test_contains_multiple_segments( @pytest.mark.parametrize( - "mask, connectivity, mode, absolute_distance, relative_distance, expected_result, exception", + "mask, connectivity, mode, absolute_distance, relative_distance, expected_result, exception", # noqa: E501 [ # single component, unchanged (