superiorvision 0.30.0.dev1__tar.gz → 0.30.0.dev2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (103) hide show
  1. {superiorvision-0.30.0.dev1/src/superiorvision.egg-info → superiorvision-0.30.0.dev2}/PKG-INFO +2 -2
  2. {superiorvision-0.30.0.dev1 → superiorvision-0.30.0.dev2}/README.md +1 -1
  3. {superiorvision-0.30.0.dev1 → superiorvision-0.30.0.dev2}/pyproject.toml +1 -1
  4. {superiorvision-0.30.0.dev1 → superiorvision-0.30.0.dev2/src/superiorvision.egg-info}/PKG-INFO +2 -2
  5. {superiorvision-0.30.0.dev1 → superiorvision-0.30.0.dev2}/src/supervision/detection/core.py +7 -16
  6. {superiorvision-0.30.0.dev1 → superiorvision-0.30.0.dev2}/src/supervision/detection/overlap_filter.py +46 -11
  7. {superiorvision-0.30.0.dev1 → superiorvision-0.30.0.dev2}/src/supervision/detection/tools/transformers.py +101 -27
  8. {superiorvision-0.30.0.dev1 → superiorvision-0.30.0.dev2}/src/supervision/key_points/core.py +7 -9
  9. {superiorvision-0.30.0.dev1 → superiorvision-0.30.0.dev2}/LICENSE.md +0 -0
  10. {superiorvision-0.30.0.dev1 → superiorvision-0.30.0.dev2}/setup.cfg +0 -0
  11. {superiorvision-0.30.0.dev1 → superiorvision-0.30.0.dev2}/src/superiorvision/__init__.py +0 -0
  12. {superiorvision-0.30.0.dev1 → superiorvision-0.30.0.dev2}/src/superiorvision.egg-info/SOURCES.txt +0 -0
  13. {superiorvision-0.30.0.dev1 → superiorvision-0.30.0.dev2}/src/superiorvision.egg-info/dependency_links.txt +0 -0
  14. {superiorvision-0.30.0.dev1 → superiorvision-0.30.0.dev2}/src/superiorvision.egg-info/requires.txt +0 -0
  15. {superiorvision-0.30.0.dev1 → superiorvision-0.30.0.dev2}/src/superiorvision.egg-info/top_level.txt +0 -0
  16. {superiorvision-0.30.0.dev1 → superiorvision-0.30.0.dev2}/src/supervision/__init__.py +0 -0
  17. {superiorvision-0.30.0.dev1 → superiorvision-0.30.0.dev2}/src/supervision/annotators/__init__.py +0 -0
  18. {superiorvision-0.30.0.dev1 → superiorvision-0.30.0.dev2}/src/supervision/annotators/base.py +0 -0
  19. {superiorvision-0.30.0.dev1 → superiorvision-0.30.0.dev2}/src/supervision/annotators/core.py +0 -0
  20. {superiorvision-0.30.0.dev1 → superiorvision-0.30.0.dev2}/src/supervision/annotators/utils.py +0 -0
  21. {superiorvision-0.30.0.dev1 → superiorvision-0.30.0.dev2}/src/supervision/assets/__init__.py +0 -0
  22. {superiorvision-0.30.0.dev1 → superiorvision-0.30.0.dev2}/src/supervision/assets/downloader.py +0 -0
  23. {superiorvision-0.30.0.dev1 → superiorvision-0.30.0.dev2}/src/supervision/assets/list.py +0 -0
  24. {superiorvision-0.30.0.dev1 → superiorvision-0.30.0.dev2}/src/supervision/classification/__init__.py +0 -0
  25. {superiorvision-0.30.0.dev1 → superiorvision-0.30.0.dev2}/src/supervision/classification/core.py +0 -0
  26. {superiorvision-0.30.0.dev1 → superiorvision-0.30.0.dev2}/src/supervision/config.py +0 -0
  27. {superiorvision-0.30.0.dev1 → superiorvision-0.30.0.dev2}/src/supervision/dataset/__init__.py +0 -0
  28. {superiorvision-0.30.0.dev1 → superiorvision-0.30.0.dev2}/src/supervision/dataset/core.py +0 -0
  29. {superiorvision-0.30.0.dev1 → superiorvision-0.30.0.dev2}/src/supervision/dataset/formats/__init__.py +0 -0
  30. {superiorvision-0.30.0.dev1 → superiorvision-0.30.0.dev2}/src/supervision/dataset/formats/coco.py +0 -0
  31. {superiorvision-0.30.0.dev1 → superiorvision-0.30.0.dev2}/src/supervision/dataset/formats/createml.py +0 -0
  32. {superiorvision-0.30.0.dev1 → superiorvision-0.30.0.dev2}/src/supervision/dataset/formats/labelme.py +0 -0
  33. {superiorvision-0.30.0.dev1 → superiorvision-0.30.0.dev2}/src/supervision/dataset/formats/pascal_voc.py +0 -0
  34. {superiorvision-0.30.0.dev1 → superiorvision-0.30.0.dev2}/src/supervision/dataset/formats/yolo.py +0 -0
  35. {superiorvision-0.30.0.dev1 → superiorvision-0.30.0.dev2}/src/supervision/dataset/utils.py +0 -0
  36. {superiorvision-0.30.0.dev1 → superiorvision-0.30.0.dev2}/src/supervision/detection/__init__.py +0 -0
  37. {superiorvision-0.30.0.dev1 → superiorvision-0.30.0.dev2}/src/supervision/detection/compact_mask.py +0 -0
  38. {superiorvision-0.30.0.dev1 → superiorvision-0.30.0.dev2}/src/supervision/detection/line_zone.py +0 -0
  39. {superiorvision-0.30.0.dev1 → superiorvision-0.30.0.dev2}/src/supervision/detection/tensor_utils.py +0 -0
  40. {superiorvision-0.30.0.dev1 → superiorvision-0.30.0.dev2}/src/supervision/detection/tensor_views.py +0 -0
  41. {superiorvision-0.30.0.dev1 → superiorvision-0.30.0.dev2}/src/supervision/detection/tools/__init__.py +0 -0
  42. {superiorvision-0.30.0.dev1 → superiorvision-0.30.0.dev2}/src/supervision/detection/tools/csv_sink.py +0 -0
  43. {superiorvision-0.30.0.dev1 → superiorvision-0.30.0.dev2}/src/supervision/detection/tools/inference_slicer.py +0 -0
  44. {superiorvision-0.30.0.dev1 → superiorvision-0.30.0.dev2}/src/supervision/detection/tools/json_sink.py +0 -0
  45. {superiorvision-0.30.0.dev1 → superiorvision-0.30.0.dev2}/src/supervision/detection/tools/polygon_zone.py +0 -0
  46. {superiorvision-0.30.0.dev1 → superiorvision-0.30.0.dev2}/src/supervision/detection/tools/smoother.py +0 -0
  47. {superiorvision-0.30.0.dev1 → superiorvision-0.30.0.dev2}/src/supervision/detection/utils/__init__.py +0 -0
  48. {superiorvision-0.30.0.dev1 → superiorvision-0.30.0.dev2}/src/supervision/detection/utils/_typing.py +0 -0
  49. {superiorvision-0.30.0.dev1 → superiorvision-0.30.0.dev2}/src/supervision/detection/utils/boxes.py +0 -0
  50. {superiorvision-0.30.0.dev1 → superiorvision-0.30.0.dev2}/src/supervision/detection/utils/converters.py +0 -0
  51. {superiorvision-0.30.0.dev1 → superiorvision-0.30.0.dev2}/src/supervision/detection/utils/internal.py +0 -0
  52. {superiorvision-0.30.0.dev1 → superiorvision-0.30.0.dev2}/src/supervision/detection/utils/iou_and_nms.py +0 -0
  53. {superiorvision-0.30.0.dev1 → superiorvision-0.30.0.dev2}/src/supervision/detection/utils/masks.py +0 -0
  54. {superiorvision-0.30.0.dev1 → superiorvision-0.30.0.dev2}/src/supervision/detection/utils/polygons.py +0 -0
  55. {superiorvision-0.30.0.dev1 → superiorvision-0.30.0.dev2}/src/supervision/detection/utils/vlms.py +0 -0
  56. {superiorvision-0.30.0.dev1 → superiorvision-0.30.0.dev2}/src/supervision/detection/vlm.py +0 -0
  57. {superiorvision-0.30.0.dev1 → superiorvision-0.30.0.dev2}/src/supervision/draw/__init__.py +0 -0
  58. {superiorvision-0.30.0.dev1 → superiorvision-0.30.0.dev2}/src/supervision/draw/base.py +0 -0
  59. {superiorvision-0.30.0.dev1 → superiorvision-0.30.0.dev2}/src/supervision/draw/color.py +0 -0
  60. {superiorvision-0.30.0.dev1 → superiorvision-0.30.0.dev2}/src/supervision/draw/utils.py +0 -0
  61. {superiorvision-0.30.0.dev1 → superiorvision-0.30.0.dev2}/src/supervision/geometry/__init__.py +0 -0
  62. {superiorvision-0.30.0.dev1 → superiorvision-0.30.0.dev2}/src/supervision/geometry/core.py +0 -0
  63. {superiorvision-0.30.0.dev1 → superiorvision-0.30.0.dev2}/src/supervision/geometry/utils.py +0 -0
  64. {superiorvision-0.30.0.dev1 → superiorvision-0.30.0.dev2}/src/supervision/key_points/__init__.py +0 -0
  65. {superiorvision-0.30.0.dev1 → superiorvision-0.30.0.dev2}/src/supervision/key_points/annotators.py +0 -0
  66. {superiorvision-0.30.0.dev1 → superiorvision-0.30.0.dev2}/src/supervision/key_points/skeletons.py +0 -0
  67. {superiorvision-0.30.0.dev1 → superiorvision-0.30.0.dev2}/src/supervision/keypoint/__init__.py +0 -0
  68. {superiorvision-0.30.0.dev1 → superiorvision-0.30.0.dev2}/src/supervision/keypoint/annotators.py +0 -0
  69. {superiorvision-0.30.0.dev1 → superiorvision-0.30.0.dev2}/src/supervision/keypoint/core.py +0 -0
  70. {superiorvision-0.30.0.dev1 → superiorvision-0.30.0.dev2}/src/supervision/metrics/__init__.py +0 -0
  71. {superiorvision-0.30.0.dev1 → superiorvision-0.30.0.dev2}/src/supervision/metrics/core.py +0 -0
  72. {superiorvision-0.30.0.dev1 → superiorvision-0.30.0.dev2}/src/supervision/metrics/detection.py +0 -0
  73. {superiorvision-0.30.0.dev1 → superiorvision-0.30.0.dev2}/src/supervision/metrics/f1_score.py +0 -0
  74. {superiorvision-0.30.0.dev1 → superiorvision-0.30.0.dev2}/src/supervision/metrics/mean_average_precision.py +0 -0
  75. {superiorvision-0.30.0.dev1 → superiorvision-0.30.0.dev2}/src/supervision/metrics/mean_average_recall.py +0 -0
  76. {superiorvision-0.30.0.dev1 → superiorvision-0.30.0.dev2}/src/supervision/metrics/precision.py +0 -0
  77. {superiorvision-0.30.0.dev1 → superiorvision-0.30.0.dev2}/src/supervision/metrics/recall.py +0 -0
  78. {superiorvision-0.30.0.dev1 → superiorvision-0.30.0.dev2}/src/supervision/metrics/utils/__init__.py +0 -0
  79. {superiorvision-0.30.0.dev1 → superiorvision-0.30.0.dev2}/src/supervision/metrics/utils/matching.py +0 -0
  80. {superiorvision-0.30.0.dev1 → superiorvision-0.30.0.dev2}/src/supervision/metrics/utils/object_size.py +0 -0
  81. {superiorvision-0.30.0.dev1 → superiorvision-0.30.0.dev2}/src/supervision/metrics/utils/utils.py +0 -0
  82. {superiorvision-0.30.0.dev1 → superiorvision-0.30.0.dev2}/src/supervision/py.typed +0 -0
  83. {superiorvision-0.30.0.dev1 → superiorvision-0.30.0.dev2}/src/supervision/tracker/__init__.py +0 -0
  84. {superiorvision-0.30.0.dev1 → superiorvision-0.30.0.dev2}/src/supervision/tracker/byte_tracker/__init__.py +0 -0
  85. {superiorvision-0.30.0.dev1 → superiorvision-0.30.0.dev2}/src/supervision/tracker/byte_tracker/core.py +0 -0
  86. {superiorvision-0.30.0.dev1 → superiorvision-0.30.0.dev2}/src/supervision/tracker/byte_tracker/kalman_filter.py +0 -0
  87. {superiorvision-0.30.0.dev1 → superiorvision-0.30.0.dev2}/src/supervision/tracker/byte_tracker/matching.py +0 -0
  88. {superiorvision-0.30.0.dev1 → superiorvision-0.30.0.dev2}/src/supervision/tracker/byte_tracker/single_object_track.py +0 -0
  89. {superiorvision-0.30.0.dev1 → superiorvision-0.30.0.dev2}/src/supervision/tracker/byte_tracker/utils.py +0 -0
  90. {superiorvision-0.30.0.dev1 → superiorvision-0.30.0.dev2}/src/supervision/utils/__init__.py +0 -0
  91. {superiorvision-0.30.0.dev1 → superiorvision-0.30.0.dev2}/src/supervision/utils/conversion.py +0 -0
  92. {superiorvision-0.30.0.dev1 → superiorvision-0.30.0.dev2}/src/supervision/utils/deprecate.py +0 -0
  93. {superiorvision-0.30.0.dev1 → superiorvision-0.30.0.dev2}/src/supervision/utils/file.py +0 -0
  94. {superiorvision-0.30.0.dev1 → superiorvision-0.30.0.dev2}/src/supervision/utils/image.py +0 -0
  95. {superiorvision-0.30.0.dev1 → superiorvision-0.30.0.dev2}/src/supervision/utils/internal.py +0 -0
  96. {superiorvision-0.30.0.dev1 → superiorvision-0.30.0.dev2}/src/supervision/utils/iterables.py +0 -0
  97. {superiorvision-0.30.0.dev1 → superiorvision-0.30.0.dev2}/src/supervision/utils/logger.py +0 -0
  98. {superiorvision-0.30.0.dev1 → superiorvision-0.30.0.dev2}/src/supervision/utils/notebook.py +0 -0
  99. {superiorvision-0.30.0.dev1 → superiorvision-0.30.0.dev2}/src/supervision/utils/tensor.py +0 -0
  100. {superiorvision-0.30.0.dev1 → superiorvision-0.30.0.dev2}/src/supervision/utils/video.py +0 -0
  101. {superiorvision-0.30.0.dev1 → superiorvision-0.30.0.dev2}/src/supervision/validators/__init__.py +0 -0
  102. {superiorvision-0.30.0.dev1 → superiorvision-0.30.0.dev2}/tests/test_public_api.py +0 -0
  103. {superiorvision-0.30.0.dev1 → superiorvision-0.30.0.dev2}/tests/test_validate_deprecations.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: superiorvision
3
- Version: 0.30.0.dev1
3
+ Version: 0.30.0.dev2
4
4
  Summary: A tensor-native, inference-focused fork of Supervision
5
5
  Author-email: "Roboflow et al." <develop@roboflow.com>
6
6
  Maintainer-email: Piotr Skalski <piotr@roboflow.com>
@@ -114,7 +114,7 @@ Install the SuperiorVision distribution in a
114
114
  is currently a development release, so pin it explicitly:
115
115
 
116
116
  ```bash
117
- pip install superiorvision==0.30.0.dev1
117
+ pip install superiorvision==0.30.0.dev2
118
118
  ```
119
119
 
120
120
  For an editable checkout:
@@ -63,7 +63,7 @@ Install the SuperiorVision distribution in a
63
63
  is currently a development release, so pin it explicitly:
64
64
 
65
65
  ```bash
66
- pip install superiorvision==0.30.0.dev1
66
+ pip install superiorvision==0.30.0.dev2
67
67
  ```
68
68
 
69
69
  For an editable checkout:
@@ -4,7 +4,7 @@ requires = [ "setuptools>=61" ]
4
4
 
5
5
  [project]
6
6
  name = "superiorvision"
7
- version = "0.30.0.dev1"
7
+ version = "0.30.0.dev2"
8
8
  description = "A tensor-native, inference-focused fork of Supervision"
9
9
  readme = "README.md"
10
10
  keywords = [
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: superiorvision
3
- Version: 0.30.0.dev1
3
+ Version: 0.30.0.dev2
4
4
  Summary: A tensor-native, inference-focused fork of Supervision
5
5
  Author-email: "Roboflow et al." <develop@roboflow.com>
6
6
  Maintainer-email: Piotr Skalski <piotr@roboflow.com>
@@ -114,7 +114,7 @@ Install the SuperiorVision distribution in a
114
114
  is currently a development release, so pin it explicitly:
115
115
 
116
116
  ```bash
117
- pip install superiorvision==0.30.0.dev1
117
+ pip install superiorvision==0.30.0.dev2
118
118
  ```
119
119
 
120
120
  For an editable checkout:
@@ -63,9 +63,6 @@ from supervision.detection.utils.iou_and_nms import (
63
63
  from supervision.detection.utils.iou_and_nms import (
64
64
  box_non_max_merge as numpy_box_non_max_merge,
65
65
  )
66
- from supervision.detection.utils.iou_and_nms import (
67
- box_non_max_suppression as numpy_box_non_max_suppression,
68
- )
69
66
  from supervision.detection.utils.iou_and_nms import (
70
67
  mask_non_max_merge as numpy_mask_non_max_merge,
71
68
  )
@@ -784,7 +781,7 @@ class Detections:
784
781
  @classmethod
785
782
  def from_transformers(
786
783
  cls,
787
- transformers_results: dict[str, Any],
784
+ transformers_results: Any,
788
785
  id2label: dict[int, str] | None = None,
789
786
  ) -> Detections:
790
787
  """
@@ -2961,7 +2958,10 @@ class Detections:
2961
2958
  ):
2962
2959
  index = [int(index)]
2963
2960
  tensor_index = _index_for_device(index, self.xyxy.device)
2964
- host_index = _index_for_host(index)
2961
+ needs_host_index = isinstance(self.mask, CompactMask) or any(
2962
+ not isinstance(value, torch.Tensor) for value in self.data.values()
2963
+ )
2964
+ host_index = _index_for_host(index) if needs_host_index else index
2965
2965
  data: _DetectionDataType = {}
2966
2966
  for key, value in self.data.items():
2967
2967
  if isinstance(value, torch.Tensor):
@@ -3329,10 +3329,7 @@ class Detections:
3329
3329
  )
3330
3330
 
3331
3331
  if self.mask is not None:
3332
- if (
3333
- isinstance(self.mask, torch.Tensor)
3334
- and overlap_metric == OverlapMetric.IOU
3335
- ):
3332
+ if isinstance(self.mask, torch.Tensor):
3336
3333
  indices = tensor_mask_non_max_suppression(
3337
3334
  predictions=predictions,
3338
3335
  masks=self.mask,
@@ -3359,18 +3356,12 @@ class Detections:
3359
3356
  iou_threshold=threshold,
3360
3357
  overlap_metric=overlap_metric,
3361
3358
  )
3362
- elif overlap_metric == OverlapMetric.IOU:
3359
+ else:
3363
3360
  indices = tensor_box_non_max_suppression(
3364
3361
  predictions=predictions,
3365
3362
  iou_threshold=threshold,
3366
3363
  overlap_metric=overlap_metric,
3367
3364
  )
3368
- else:
3369
- indices = numpy_box_non_max_suppression(
3370
- predictions=to_numpy(predictions),
3371
- iou_threshold=threshold,
3372
- overlap_metric=overlap_metric,
3373
- )
3374
3365
 
3375
3366
  return self.select(indices)
3376
3367
 
@@ -10,6 +10,40 @@ import torch
10
10
  from supervision.detection.utils import box_iou_batch, mask_iou_batch
11
11
 
12
12
 
13
+ def _uses_intersection_over_smaller(overlap_metric: object | None) -> bool:
14
+ """Return whether an overlap metric requests intersection over smaller area."""
15
+ value = getattr(overlap_metric, "value", overlap_metric)
16
+ return isinstance(value, str) and value.lower() == "ios"
17
+
18
+
19
+ def _tensor_mask_overlap_batch(
20
+ masks: torch.Tensor, overlap_metric: object | None
21
+ ) -> torch.Tensor:
22
+ """Compute pairwise tensor mask overlap without leaving the source device."""
23
+ flat_masks = masks.bool().flatten(1)
24
+ intersection = (flat_masks[:, None] & flat_masks[None, :]).sum(dim=2)
25
+ areas = flat_masks.sum(dim=1)
26
+ if _uses_intersection_over_smaller(overlap_metric):
27
+ denominator = torch.minimum(areas[:, None], areas[None, :])
28
+ else:
29
+ denominator = areas[:, None] + areas[None, :] - intersection
30
+ return intersection / denominator.clamp(min=1)
31
+
32
+
33
+ def _tensor_box_overlap_batch(
34
+ boxes: torch.Tensor, overlap_metric: object | None
35
+ ) -> torch.Tensor:
36
+ """Compute pairwise tensor box overlap without leaving the source device."""
37
+ if not _uses_intersection_over_smaller(overlap_metric):
38
+ return box_iou_batch(boxes, boxes)
39
+ areas = (boxes[:, 2] - boxes[:, 0]) * (boxes[:, 3] - boxes[:, 1])
40
+ top_left = torch.maximum(boxes[:, None, :2], boxes[:, :2])
41
+ bottom_right = torch.minimum(boxes[:, None, 2:], boxes[:, 2:])
42
+ intersection = (bottom_right - top_left).clamp(min=0).prod(dim=2)
43
+ denominator = torch.minimum(areas[:, None], areas[None, :])
44
+ return torch.nan_to_num(intersection / denominator)
45
+
46
+
13
47
  def resize_masks(masks: np.ndarray, max_dimension: int = 640) -> np.ndarray:
14
48
  """
15
49
  Resize all masks in the array to have a maximum dimension of max_dimension,
@@ -86,16 +120,15 @@ def mask_non_max_suppression(
86
120
  # Pairwise mask IoU without leaving the source device. This is intentionally
87
121
  # exact; callers wanting a reduced-resolution approximation can downsample
88
122
  # the masks before constructing Detections.
89
- flat_masks = sorted_masks.bool().flatten(1)
90
- intersection = (flat_masks[:, None] & flat_masks[None, :]).sum(dim=2)
91
- areas = flat_masks.sum(dim=1)
92
- ious = intersection / (areas[:, None] + areas[None, :] - intersection).clamp(min=1)
123
+ ious = _tensor_mask_overlap_batch(sorted_masks, overlap_metric)
93
124
  categories = sorted_predictions[:, 5]
94
125
  keep = torch.ones(rows, dtype=torch.bool, device=predictions.device)
95
126
  for index in range(rows):
96
- if not keep[index]:
97
- continue
98
- suppress = (ious[index] > iou_threshold) & (categories == categories[index])
127
+ suppress = (
128
+ (ious[index] > iou_threshold)
129
+ & (categories == categories[index])
130
+ & keep[index]
131
+ )
99
132
  suppress[index] = False
100
133
  keep &= ~suppress
101
134
  output = torch.zeros(rows, dtype=torch.bool, device=predictions.device)
@@ -159,12 +192,14 @@ def box_non_max_suppression(
159
192
  sort_index = torch.argsort(predictions[:, 4], descending=True)
160
193
  sorted_predictions = predictions[sort_index]
161
194
  boxes, categories = sorted_predictions[:, :4], sorted_predictions[:, 5]
162
- ious = box_iou_batch(boxes, boxes)
195
+ ious = _tensor_box_overlap_batch(boxes, overlap_metric)
163
196
  keep = torch.ones(rows, dtype=torch.bool, device=predictions.device)
164
197
  for index in range(rows):
165
- if not keep[index]:
166
- continue
167
- suppress = (ious[index] > iou_threshold) & (categories == categories[index])
198
+ suppress = (
199
+ (ious[index] > iou_threshold)
200
+ & (categories == categories[index])
201
+ & keep[index]
202
+ )
168
203
  suppress[index] = False
169
204
  keep &= ~suppress
170
205
  output = torch.zeros(rows, dtype=torch.bool, device=predictions.device)
@@ -8,7 +8,43 @@ import numpy.typing as npt
8
8
  from PIL import Image
9
9
 
10
10
  from supervision.config import CLASS_NAME_DATA_FIELD
11
- from supervision.detection.utils.converters import mask_to_xyxy
11
+ from supervision.detection.tensor_utils import mask_to_xyxy
12
+
13
+
14
+ def _detach_numeric(value: Any) -> Any:
15
+ """Detach real tensors in place while retaining legacy array-like inputs."""
16
+ import torch
17
+
18
+ if isinstance(value, torch.Tensor):
19
+ return value.detach()
20
+ if isinstance(value, np.ndarray):
21
+ return value
22
+ return value.detach().numpy()
23
+
24
+
25
+ def _as_boolean(value: Any) -> Any:
26
+ """Cast a numeric mask to boolean without changing its device or container."""
27
+ import torch
28
+
29
+ if isinstance(value, torch.Tensor):
30
+ return value.to(dtype=torch.bool)
31
+ return value.astype(bool)
32
+
33
+
34
+ def _as_integer(value: Any) -> Any:
35
+ """Cast class identifiers to integers without changing their device."""
36
+ import torch
37
+
38
+ if isinstance(value, torch.Tensor):
39
+ return value.to(dtype=torch.long)
40
+ return value.astype(int)
41
+
42
+
43
+ def _is_tensor(value: Any) -> bool:
44
+ """Return whether a value is a Torch tensor without importing Torch eagerly."""
45
+ import torch
46
+
47
+ return isinstance(value, torch.Tensor)
12
48
 
13
49
 
14
50
  def process_transformers_detection_result(
@@ -29,12 +65,12 @@ def process_transformers_detection_result(
29
65
  Processed detection result including bounding boxes, confidence scores,
30
66
  class IDs, and data.
31
67
  """
32
- class_ids = detection_result["labels"].cpu().detach().numpy().astype(int)
68
+ class_ids = _as_integer(_detach_numeric(detection_result["labels"]))
33
69
  data = append_class_names_to_data(class_ids, id2label, {})
34
70
 
35
71
  return dict(
36
- xyxy=detection_result["boxes"].cpu().detach().numpy(),
37
- confidence=detection_result["scores"].cpu().detach().numpy(),
72
+ xyxy=_detach_numeric(detection_result["boxes"]),
73
+ confidence=_detach_numeric(detection_result["scores"]),
38
74
  class_id=class_ids,
39
75
  data=data,
40
76
  )
@@ -66,14 +102,14 @@ def process_transformers_v4_segmentation_result(
66
102
  else:
67
103
  boxes = None
68
104
  if "boxes" in segmentation_result:
69
- boxes = segmentation_result["boxes"].cpu().detach().numpy()
70
- masks = segmentation_result["masks"].cpu().detach().numpy().astype(bool)
71
- class_ids = segmentation_result["labels"].cpu().detach().numpy().astype(int)
105
+ boxes = _detach_numeric(segmentation_result["boxes"])
106
+ masks = _as_boolean(_detach_numeric(segmentation_result["masks"]))
107
+ class_ids = _as_integer(_detach_numeric(segmentation_result["labels"]))
72
108
 
73
109
  return dict(
74
110
  xyxy=boxes if boxes is not None else mask_to_xyxy(masks),
75
- mask=np.squeeze(masks, axis=1) if boxes is not None else masks,
76
- confidence=segmentation_result["scores"].cpu().detach().numpy(),
111
+ mask=masks.squeeze(1) if boxes is not None else masks,
112
+ confidence=_detach_numeric(segmentation_result["scores"]),
77
113
  class_id=class_ids,
78
114
  data=append_class_names_to_data(class_ids, id2label, {}),
79
115
  )
@@ -99,8 +135,13 @@ def process_transformers_v5_segmentation_result(
99
135
  Processed segmentation result including bounding boxes, masks, confidence
100
136
  scores, class IDs, and data.
101
137
  """
138
+ if _is_tensor(segmentation_result):
139
+ segmentation_array = segmentation_result.detach()
140
+ return process_transformers_v5_panoptic_segmentation_result(
141
+ segmentation_array, id2label
142
+ )
102
143
  if segmentation_result.__class__.__name__ == "Tensor":
103
- segmentation_array = segmentation_result.cpu().detach().numpy()
144
+ segmentation_array = segmentation_result.detach().numpy()
104
145
  return process_transformers_v5_panoptic_segmentation_result(
105
146
  segmentation_array, id2label
106
147
  )
@@ -128,15 +169,38 @@ def process_transformers_v5_semantic_or_instance_segmentation_result(
128
169
  scores, class IDs, and data.
129
170
  """
130
171
  segments_info = segmentation_result["segments_info"]
131
- segmentation_array = segmentation_result["segmentation"].cpu().detach().numpy()
132
- scores = np.array([segment["score"] for segment in segments_info], dtype=float)
133
- class_ids = np.array([segment["label_id"] for segment in segments_info], dtype=int)
134
- if len(segments_info) == 0:
135
- masks = np.empty((0, *segmentation_array.shape), dtype=bool)
172
+ segmentation_array = _detach_numeric(segmentation_result["segmentation"])
173
+ if _is_tensor(segmentation_array):
174
+ import torch
175
+
176
+ device = segmentation_array.device
177
+ scores = torch.tensor(
178
+ [segment["score"] for segment in segments_info],
179
+ dtype=torch.float32,
180
+ device=device,
181
+ )
182
+ class_ids = torch.tensor(
183
+ [segment["label_id"] for segment in segments_info],
184
+ dtype=torch.long,
185
+ device=device,
186
+ )
187
+ segment_ids = torch.tensor(
188
+ [segment["id"] for segment in segments_info],
189
+ dtype=segmentation_array.dtype,
190
+ device=device,
191
+ )
192
+ masks = segmentation_array.unsqueeze(0) == segment_ids[:, None, None]
136
193
  else:
137
- masks = np.array(
138
- [segmentation_array == segment["id"] for segment in segments_info]
139
- ).astype(bool)
194
+ scores = np.array([segment["score"] for segment in segments_info], dtype=float)
195
+ class_ids = np.array(
196
+ [segment["label_id"] for segment in segments_info], dtype=int
197
+ )
198
+ if len(segments_info) == 0:
199
+ masks = np.empty((0, *segmentation_array.shape), dtype=bool)
200
+ else:
201
+ masks = np.array(
202
+ [segmentation_array == segment["id"] for segment in segments_info]
203
+ ).astype(bool)
140
204
  data = append_class_names_to_data(class_ids, id2label, {})
141
205
 
142
206
  return dict(
@@ -183,7 +247,7 @@ def process_transformers_v4_panoptic_segmentation_result(
183
247
 
184
248
 
185
249
  def process_transformers_v5_panoptic_segmentation_result(
186
- segmentation_array: npt.NDArray[Any], id2label: dict[int, str] | None
250
+ segmentation_array: Any, id2label: dict[int, str] | None
187
251
  ) -> dict[str, Any]:
188
252
  """
189
253
  Process a v5 Transformers semantic segmentation tensor.
@@ -198,13 +262,19 @@ def process_transformers_v5_panoptic_segmentation_result(
198
262
  Processed segmentation result including bounding boxes, masks,
199
263
  class IDs, and data.
200
264
  """
201
- class_ids = np.unique(segmentation_array).astype(int)
202
- if len(class_ids) == 0:
203
- masks = np.empty((0, *segmentation_array.shape), dtype=bool)
265
+ if _is_tensor(segmentation_array):
266
+ import torch
267
+
268
+ class_ids = torch.unique(segmentation_array).to(dtype=torch.long)
269
+ masks = segmentation_array.unsqueeze(0) == class_ids[:, None, None]
204
270
  else:
205
- masks = np.stack(
206
- [segmentation_array == class_id for class_id in class_ids], axis=0
207
- ).astype(bool)
271
+ class_ids = np.unique(segmentation_array).astype(int)
272
+ if len(class_ids) == 0:
273
+ masks = np.empty((0, *segmentation_array.shape), dtype=bool)
274
+ else:
275
+ masks = np.stack(
276
+ [segmentation_array == class_id for class_id in class_ids], axis=0
277
+ ).astype(bool)
208
278
  data = append_class_names_to_data(class_ids, id2label, {})
209
279
  return dict(xyxy=mask_to_xyxy(masks), mask=masks, class_id=class_ids, data=data)
210
280
 
@@ -237,7 +307,7 @@ def png_string_to_segmentation_array(png_string: bytes) -> npt.NDArray[Any]:
237
307
 
238
308
 
239
309
  def append_class_names_to_data(
240
- class_ids: npt.NDArray[Any],
310
+ class_ids: Any,
241
311
  id2label: dict[int, str] | None,
242
312
  data: dict[str, Any] | None = None,
243
313
  ) -> dict[str, Any]:
@@ -259,7 +329,11 @@ def append_class_names_to_data(
259
329
  data = {}
260
330
 
261
331
  if id2label is not None:
262
- class_names = np.array([id2label[class_id] for class_id in class_ids])
332
+ if _is_tensor(class_ids):
333
+ host_class_ids = class_ids.detach().cpu().tolist()
334
+ else:
335
+ host_class_ids = class_ids.tolist()
336
+ class_names = np.array([id2label[class_id] for class_id in host_class_ids])
263
337
  data[CLASS_NAME_DATA_FIELD] = class_names
264
338
 
265
339
  return data
@@ -11,6 +11,7 @@ import torch
11
11
 
12
12
  from supervision.config import CLASS_NAME_DATA_FIELD
13
13
  from supervision.detection.core import Detections
14
+ from supervision.detection.overlap_filter import box_non_max_suppression
14
15
  from supervision.detection.utils._typing import (
15
16
  _DetectionDataType,
16
17
  _DetectionDataValueType,
@@ -20,10 +21,7 @@ from supervision.detection.utils.internal import (
20
21
  is_data_equal,
21
22
  merge_data,
22
23
  )
23
- from supervision.detection.utils.iou_and_nms import (
24
- OverlapMetric,
25
- box_non_max_suppression,
26
- )
24
+ from supervision.detection.utils.iou_and_nms import OverlapMetric
27
25
  from supervision.utils.internal import warn_deprecated
28
26
  from supervision.utils.tensor import as_tensor, tensor_data
29
27
  from supervision.validators import _validate_keypoints_fields
@@ -56,11 +54,6 @@ def _select_data(
56
54
  ) -> _DetectionDataType:
57
55
  """Select mixed tensor/host metadata without moving numeric tensors off device."""
58
56
  selected: dict[str, Any] = {}
59
- host_index: int | slice | npt.NDArray[np.generic]
60
- if isinstance(index, torch.Tensor):
61
- host_index = index.detach().cpu().numpy()
62
- else:
63
- host_index = index
64
57
  for key, value in data.items():
65
58
  if isinstance(value, torch.Tensor):
66
59
  tensor_index = (
@@ -70,6 +63,11 @@ def _select_data(
70
63
  )
71
64
  selected[key] = value[tensor_index]
72
65
  else:
66
+ host_index = (
67
+ index.detach().cpu().numpy()
68
+ if isinstance(index, torch.Tensor)
69
+ else index
70
+ )
73
71
  selected.update(get_data_item({key: value}, cast(Any, host_index)))
74
72
  return cast(_DetectionDataType, selected)
75
73