detect-lib 0.1.0__py3-none-any.whl
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.
- detect/__init__.py +81 -0
- detect/backends/__init__.py +141 -0
- detect/backends/ultralytics/__init__.py +14 -0
- detect/backends/ultralytics/detectors.py +296 -0
- detect/backends/ultralytics/export.py +184 -0
- detect/cli/detect_video.py +112 -0
- detect/cli/export_model.py +100 -0
- detect/core/artifacts.py +98 -0
- detect/core/run.py +278 -0
- detect/core/schema.py +215 -0
- detect/core/viz.py +134 -0
- detect/registry/registry.py +252 -0
- detect_lib-0.1.0.dist-info/METADATA +413 -0
- detect_lib-0.1.0.dist-info/RECORD +17 -0
- detect_lib-0.1.0.dist-info/WHEEL +5 -0
- detect_lib-0.1.0.dist-info/licenses/LICENSE +21 -0
- detect_lib-0.1.0.dist-info/top_level.txt +1 -0
detect/__init__.py
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
"""
|
|
2
|
+
detect
|
|
3
|
+
======
|
|
4
|
+
detect/
|
|
5
|
+
__init__.py
|
|
6
|
+
core/
|
|
7
|
+
schema.py # Detection types + det-v1 helpers
|
|
8
|
+
run.py # detect_video core logic (no CLI)
|
|
9
|
+
artifacts.py # artifact saving control, result object
|
|
10
|
+
viz.py # drawing helpers (optional dependency on cv2)
|
|
11
|
+
backends/
|
|
12
|
+
__init__.py # plugin registry
|
|
13
|
+
ultralytics/
|
|
14
|
+
__init__.py
|
|
15
|
+
detectors.py # bbox/pose/seg wrappers
|
|
16
|
+
export.py # ultralytics exporter adapter
|
|
17
|
+
registry.json # ultralytics-specific model keys (optional)
|
|
18
|
+
registry/
|
|
19
|
+
registry.py # merge registries + resolve_weights_path
|
|
20
|
+
default.json # your current registry (or split by backend)
|
|
21
|
+
cli/
|
|
22
|
+
detect_video.py # argparse → calls core.run.detect_video()
|
|
23
|
+
export_model.py # argparse → calls backend exporter
|
|
24
|
+
|
|
25
|
+
A modular object-detection framework centered around a stable JSON output schema
|
|
26
|
+
(det-v1), with pluggable model backends (Ultralytics YOLO today; others later).
|
|
27
|
+
|
|
28
|
+
Public API (stable-ish):
|
|
29
|
+
- Detection schema types and helpers (core.schema)
|
|
30
|
+
- Detection runner (core.run.detect_video)
|
|
31
|
+
- Artifact control + result object (core.artifacts)
|
|
32
|
+
- Backend registry + detector factory (backends)
|
|
33
|
+
"""
|
|
34
|
+
|
|
35
|
+
from .core.schema import (
|
|
36
|
+
SCHEMA_VERSION,
|
|
37
|
+
Detection,
|
|
38
|
+
FrameRecord,
|
|
39
|
+
VideoMeta,
|
|
40
|
+
DetectorConfig,
|
|
41
|
+
BaseDetector,
|
|
42
|
+
select_device,
|
|
43
|
+
parse_classes,
|
|
44
|
+
frame_file_name,
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
from .core.artifacts import (
|
|
48
|
+
ArtifactOptions,
|
|
49
|
+
DetectResult,
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
from .core.run import (
|
|
53
|
+
detect_video,
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
from .backends import (
|
|
57
|
+
create_detector,
|
|
58
|
+
available_detectors,
|
|
59
|
+
available_models,
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
__all__ = [
|
|
63
|
+
# Schema / types
|
|
64
|
+
"SCHEMA_VERSION",
|
|
65
|
+
"Detection",
|
|
66
|
+
"FrameRecord",
|
|
67
|
+
"VideoMeta",
|
|
68
|
+
"DetectorConfig",
|
|
69
|
+
"BaseDetector",
|
|
70
|
+
"select_device",
|
|
71
|
+
"parse_classes",
|
|
72
|
+
"frame_file_name",
|
|
73
|
+
# Running + outputs
|
|
74
|
+
"ArtifactOptions",
|
|
75
|
+
"DetectResult",
|
|
76
|
+
"detect_video",
|
|
77
|
+
# Backends
|
|
78
|
+
"create_detector",
|
|
79
|
+
"available_detectors",
|
|
80
|
+
"available_models",
|
|
81
|
+
]
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
"""
|
|
4
|
+
detect.backends
|
|
5
|
+
---------------
|
|
6
|
+
|
|
7
|
+
Backend/plugin registry.
|
|
8
|
+
|
|
9
|
+
Today we ship an Ultralytics backend with three detectors:
|
|
10
|
+
- yolo_bbox
|
|
11
|
+
- yolo_pose
|
|
12
|
+
- yolo_seg
|
|
13
|
+
|
|
14
|
+
This module provides:
|
|
15
|
+
- register_detector() for future backends
|
|
16
|
+
- create_detector() as the main factory
|
|
17
|
+
- available_detectors() listing
|
|
18
|
+
- available_models() combining registry + installed models
|
|
19
|
+
|
|
20
|
+
Design notes:
|
|
21
|
+
- Detector keys are currently simple (e.g. "yolo_bbox") for backward compatibility.
|
|
22
|
+
- Internally, we store (backend, name) so we can support future keys like "onnxrt:foo".
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
from dataclasses import dataclass
|
|
26
|
+
from pathlib import Path
|
|
27
|
+
from typing import Callable, Dict, List, Optional, Tuple, Type, Union
|
|
28
|
+
|
|
29
|
+
from ..core.schema import BaseDetector
|
|
30
|
+
from ..registry.registry import (
|
|
31
|
+
list_installed_models,
|
|
32
|
+
list_registered_models,
|
|
33
|
+
resolve_weights_path,
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
# Import and register built-in backend(s)
|
|
37
|
+
from .ultralytics.detectors import ( # noqa: E402
|
|
38
|
+
YOLOBBoxDetector,
|
|
39
|
+
YOLOPoseDetector,
|
|
40
|
+
YOLOSegDetector,
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
@dataclass(frozen=True)
|
|
45
|
+
class DetectorSpec:
|
|
46
|
+
backend: str
|
|
47
|
+
name: str
|
|
48
|
+
cls: Type[BaseDetector]
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
# Keyed by detector "public name" (e.g. "yolo_bbox") -> spec
|
|
52
|
+
_DETECTORS: Dict[str, DetectorSpec] = {}
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def register_detector(*, name: str, backend: str, cls: Type[BaseDetector]) -> None:
|
|
56
|
+
key = (name or "").strip().lower()
|
|
57
|
+
if not key:
|
|
58
|
+
raise ValueError("Detector name must be non-empty.")
|
|
59
|
+
if key in _DETECTORS:
|
|
60
|
+
raise ValueError(f"Detector '{key}' already registered.")
|
|
61
|
+
_DETECTORS[key] = DetectorSpec(backend=backend, name=key, cls=cls)
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def available_detectors() -> List[str]:
|
|
65
|
+
return sorted(_DETECTORS.keys())
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def available_models(
|
|
69
|
+
*,
|
|
70
|
+
detector: Optional[str] = None,
|
|
71
|
+
backend: Optional[str] = None,
|
|
72
|
+
models_dir: Union[str, Path] = "models",
|
|
73
|
+
) -> dict:
|
|
74
|
+
"""
|
|
75
|
+
Return a dict: {"registered": {...}, "installed": {...}}.
|
|
76
|
+
|
|
77
|
+
- registered models are from the JSON registry files (optionally filtered).
|
|
78
|
+
- installed models are local model files found under models_dir.
|
|
79
|
+
"""
|
|
80
|
+
det_key = detector.strip().lower() if detector else None
|
|
81
|
+
return {
|
|
82
|
+
"registered": list_registered_models(detector=det_key, backend=backend),
|
|
83
|
+
"installed": list_installed_models(models_dir=models_dir),
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def create_detector(
|
|
88
|
+
*,
|
|
89
|
+
name: str,
|
|
90
|
+
weights: Union[str, Path],
|
|
91
|
+
conf: float = 0.25,
|
|
92
|
+
classes: Optional[List[int]] = None,
|
|
93
|
+
imgsz: int = 640,
|
|
94
|
+
device: str = "auto",
|
|
95
|
+
half: bool = False,
|
|
96
|
+
models_dir: Union[str, Path] = "models",
|
|
97
|
+
allow_download: bool = True,
|
|
98
|
+
) -> BaseDetector:
|
|
99
|
+
"""
|
|
100
|
+
Create a detector instance.
|
|
101
|
+
|
|
102
|
+
- Resolves weights from local path / URL / registry key into a local file.
|
|
103
|
+
- Instantiates the registered detector class.
|
|
104
|
+
"""
|
|
105
|
+
key = (name or "").strip().lower()
|
|
106
|
+
if key not in _DETECTORS:
|
|
107
|
+
raise ValueError(f"Unknown detector '{name}'. Available: {', '.join(available_detectors())}")
|
|
108
|
+
|
|
109
|
+
spec = _DETECTORS[key]
|
|
110
|
+
|
|
111
|
+
weights_path = resolve_weights_path(
|
|
112
|
+
weights,
|
|
113
|
+
models_dir=models_dir,
|
|
114
|
+
detector=key,
|
|
115
|
+
backend=spec.backend,
|
|
116
|
+
allow_download=allow_download,
|
|
117
|
+
)
|
|
118
|
+
return spec.cls(
|
|
119
|
+
weights=weights_path,
|
|
120
|
+
conf=conf,
|
|
121
|
+
classes=classes,
|
|
122
|
+
imgsz=imgsz,
|
|
123
|
+
device=device,
|
|
124
|
+
half=half,
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
# -------------------------
|
|
129
|
+
# Built-in detector wiring
|
|
130
|
+
# -------------------------
|
|
131
|
+
register_detector(name="yolo_bbox", backend="ultralytics", cls=YOLOBBoxDetector)
|
|
132
|
+
register_detector(name="yolo_pose", backend="ultralytics", cls=YOLOPoseDetector)
|
|
133
|
+
register_detector(name="yolo_seg", backend="ultralytics", cls=YOLOSegDetector)
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
__all__ = [
|
|
137
|
+
"register_detector",
|
|
138
|
+
"available_detectors",
|
|
139
|
+
"available_models",
|
|
140
|
+
"create_detector",
|
|
141
|
+
]
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"""
|
|
2
|
+
detect.backends.ultralytics
|
|
3
|
+
---------------------------
|
|
4
|
+
|
|
5
|
+
Ultralytics backend integration.
|
|
6
|
+
|
|
7
|
+
Provides:
|
|
8
|
+
- YOLO bbox/pose/seg detectors
|
|
9
|
+
- (later) exporter adapter
|
|
10
|
+
|
|
11
|
+
This backend assumes `ultralytics` is installed.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
__all__ = []
|
|
@@ -0,0 +1,296 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
"""
|
|
4
|
+
detect.backends.ultralytics.detectors
|
|
5
|
+
------------------------------------
|
|
6
|
+
|
|
7
|
+
Ultralytics YOLO detector wrappers for:
|
|
8
|
+
- bounding boxes (yolo_bbox)
|
|
9
|
+
- pose (yolo_pose)
|
|
10
|
+
- segmentation (yolo_seg)
|
|
11
|
+
|
|
12
|
+
All implement BaseDetector.process_frame(frame_bgr) and return canonical Detection dicts
|
|
13
|
+
(without det_ind; runner assigns it).
|
|
14
|
+
|
|
15
|
+
This file intentionally mirrors your previous per-file detectors but consolidates them.
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
from pathlib import Path
|
|
19
|
+
from typing import List, Optional, Union
|
|
20
|
+
|
|
21
|
+
import numpy as np
|
|
22
|
+
|
|
23
|
+
from ...core.schema import BaseDetector, Detection
|
|
24
|
+
|
|
25
|
+
try:
|
|
26
|
+
from ultralytics import YOLO # type: ignore
|
|
27
|
+
except Exception as e: # pragma: no cover
|
|
28
|
+
YOLO = None # type: ignore
|
|
29
|
+
_ULTRALYTICS_IMPORT_ERROR = e
|
|
30
|
+
|
|
31
|
+
try:
|
|
32
|
+
import torch # type: ignore
|
|
33
|
+
except Exception: # pragma: no cover
|
|
34
|
+
torch = None # type: ignore
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def _ultralytics_device_arg(device: str):
|
|
38
|
+
"""
|
|
39
|
+
Convert our device string to what Ultralytics expects.
|
|
40
|
+
|
|
41
|
+
Ultralytics expects:
|
|
42
|
+
- "cpu", "mps", or GPU indices like "0" / "0,1"
|
|
43
|
+
It does NOT accept "auto".
|
|
44
|
+
"""
|
|
45
|
+
d = (device or "").strip().lower()
|
|
46
|
+
|
|
47
|
+
if d in ("", "auto"):
|
|
48
|
+
if torch is not None:
|
|
49
|
+
# Prefer Apple MPS when available
|
|
50
|
+
if getattr(torch.backends, "mps", None) and torch.backends.mps.is_available():
|
|
51
|
+
return "mps"
|
|
52
|
+
# Prefer CUDA gpu index "0" when available
|
|
53
|
+
if torch.cuda.is_available() and torch.cuda.device_count() > 0:
|
|
54
|
+
return "0"
|
|
55
|
+
return "cpu"
|
|
56
|
+
|
|
57
|
+
# Normalize common CUDA spellings
|
|
58
|
+
if d == "cuda":
|
|
59
|
+
return "0"
|
|
60
|
+
if d.startswith("cuda:"):
|
|
61
|
+
# "cuda:0" -> "0"
|
|
62
|
+
return d.split(":", 1)[1].strip() or "0"
|
|
63
|
+
|
|
64
|
+
# passthrough: "cpu", "mps", "0", "0,1", etc.
|
|
65
|
+
return d
|
|
66
|
+
|
|
67
|
+
class _UltralyticsBase(BaseDetector):
|
|
68
|
+
backend: str = "ultralytics"
|
|
69
|
+
|
|
70
|
+
def __init__(
|
|
71
|
+
self,
|
|
72
|
+
*,
|
|
73
|
+
weights: Union[str, Path],
|
|
74
|
+
conf: float = 0.25,
|
|
75
|
+
classes: Optional[List[int]] = None,
|
|
76
|
+
imgsz: int = 640,
|
|
77
|
+
device: str = "auto",
|
|
78
|
+
half: bool = False,
|
|
79
|
+
) -> None:
|
|
80
|
+
if YOLO is None: # pragma: no cover
|
|
81
|
+
raise ImportError(
|
|
82
|
+
"ultralytics is required but failed to import"
|
|
83
|
+
) from _ULTRALYTICS_IMPORT_ERROR
|
|
84
|
+
|
|
85
|
+
super().__init__(
|
|
86
|
+
weights=weights,
|
|
87
|
+
conf=conf,
|
|
88
|
+
classes=classes,
|
|
89
|
+
imgsz=imgsz,
|
|
90
|
+
device=device,
|
|
91
|
+
half=half,
|
|
92
|
+
)
|
|
93
|
+
self.model = YOLO(str(self.weights))
|
|
94
|
+
|
|
95
|
+
# Class names map (list or dict depending on Ultralytics version)
|
|
96
|
+
self.names = None
|
|
97
|
+
try:
|
|
98
|
+
self.names = getattr(self.model, "names", None) or getattr(self.model.model, "names", None)
|
|
99
|
+
except Exception:
|
|
100
|
+
self.names = None
|
|
101
|
+
|
|
102
|
+
def _class_name(self, cls_id: int) -> Optional[str]:
|
|
103
|
+
names = self.names
|
|
104
|
+
if names is None:
|
|
105
|
+
return None
|
|
106
|
+
try:
|
|
107
|
+
if isinstance(names, dict):
|
|
108
|
+
return names.get(int(cls_id))
|
|
109
|
+
if isinstance(names, (list, tuple)):
|
|
110
|
+
if 0 <= int(cls_id) < len(names):
|
|
111
|
+
return str(names[int(cls_id)])
|
|
112
|
+
return None
|
|
113
|
+
except Exception:
|
|
114
|
+
return None
|
|
115
|
+
|
|
116
|
+
def _predict(self, frame_bgr: np.ndarray):
|
|
117
|
+
# Ultralytics can take numpy BGR directly; it handles resize/letterbox internally.
|
|
118
|
+
dev = _ultralytics_device_arg(self.device_str)
|
|
119
|
+
return self.model.predict(
|
|
120
|
+
source=frame_bgr,
|
|
121
|
+
conf=self.conf,
|
|
122
|
+
imgsz=self.imgsz,
|
|
123
|
+
classes=self.classes,
|
|
124
|
+
device=dev,
|
|
125
|
+
half=self.half,
|
|
126
|
+
verbose=False,
|
|
127
|
+
)
|
|
128
|
+
|
|
129
|
+
def warmup(self) -> None:
|
|
130
|
+
h = w = max(32, int(self.imgsz))
|
|
131
|
+
dummy = np.zeros((h, w, 3), dtype=np.uint8)
|
|
132
|
+
try:
|
|
133
|
+
dev = _ultralytics_device_arg(self.device_str)
|
|
134
|
+
_ = self.model.predict(
|
|
135
|
+
source=dummy,
|
|
136
|
+
conf=0.01,
|
|
137
|
+
imgsz=self.imgsz,
|
|
138
|
+
device=dev,
|
|
139
|
+
half=self.half,
|
|
140
|
+
verbose=False,
|
|
141
|
+
)
|
|
142
|
+
except Exception:
|
|
143
|
+
pass
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
class YOLOBBoxDetector(_UltralyticsBase):
|
|
147
|
+
"""Ultralytics YOLO bounding-box detector."""
|
|
148
|
+
|
|
149
|
+
# Keep this name stable for JSON readability
|
|
150
|
+
def __init__(self, **kwargs) -> None:
|
|
151
|
+
super().__init__(**kwargs)
|
|
152
|
+
|
|
153
|
+
def process_frame(self, frame_bgr: np.ndarray) -> List[Detection]:
|
|
154
|
+
results = self._predict(frame_bgr)
|
|
155
|
+
r = results[0]
|
|
156
|
+
detections: List[Detection] = []
|
|
157
|
+
|
|
158
|
+
if getattr(r, "boxes", None) is None or len(r.boxes) == 0:
|
|
159
|
+
return detections
|
|
160
|
+
|
|
161
|
+
boxes_xyxy = r.boxes.xyxy.cpu().numpy().astype(float)
|
|
162
|
+
scores = r.boxes.conf.cpu().numpy().astype(float)
|
|
163
|
+
cls_ids = r.boxes.cls.cpu().numpy().astype(int)
|
|
164
|
+
|
|
165
|
+
for i in range(boxes_xyxy.shape[0]):
|
|
166
|
+
x1, y1, x2, y2 = boxes_xyxy[i].tolist()
|
|
167
|
+
score = float(scores[i])
|
|
168
|
+
cid = int(cls_ids[i])
|
|
169
|
+
det: Detection = {
|
|
170
|
+
"bbox": [x1, y1, x2, y2],
|
|
171
|
+
"score": score,
|
|
172
|
+
"class_id": cid,
|
|
173
|
+
}
|
|
174
|
+
cname = self._class_name(cid)
|
|
175
|
+
if cname is not None:
|
|
176
|
+
det["class_name"] = cname
|
|
177
|
+
detections.append(det)
|
|
178
|
+
return detections
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
class YOLOPoseDetector(_UltralyticsBase):
|
|
182
|
+
"""Ultralytics YOLO pose model wrapper (boxes + keypoints)."""
|
|
183
|
+
|
|
184
|
+
def __init__(self, **kwargs) -> None:
|
|
185
|
+
super().__init__(**kwargs)
|
|
186
|
+
|
|
187
|
+
def process_frame(self, frame_bgr: np.ndarray) -> List[Detection]:
|
|
188
|
+
results = self._predict(frame_bgr)
|
|
189
|
+
r = results[0]
|
|
190
|
+
detections: List[Detection] = []
|
|
191
|
+
|
|
192
|
+
if getattr(r, "boxes", None) is None or len(r.boxes) == 0:
|
|
193
|
+
return detections
|
|
194
|
+
|
|
195
|
+
boxes_xyxy = r.boxes.xyxy.cpu().numpy().astype(float)
|
|
196
|
+
scores = r.boxes.conf.cpu().numpy().astype(float)
|
|
197
|
+
cls_ids = r.boxes.cls.cpu().numpy().astype(int)
|
|
198
|
+
|
|
199
|
+
# Keypoints tensor: (N, K, 3) with (x, y, score)
|
|
200
|
+
kpts = None
|
|
201
|
+
if getattr(r, "keypoints", None) is not None:
|
|
202
|
+
try:
|
|
203
|
+
kpts = r.keypoints.data.cpu().numpy().astype(float)
|
|
204
|
+
except Exception:
|
|
205
|
+
kpts = None
|
|
206
|
+
|
|
207
|
+
for i in range(boxes_xyxy.shape[0]):
|
|
208
|
+
x1, y1, x2, y2 = boxes_xyxy[i].tolist()
|
|
209
|
+
score = float(scores[i])
|
|
210
|
+
cid = int(cls_ids[i])
|
|
211
|
+
det: Detection = {
|
|
212
|
+
"bbox": [x1, y1, x2, y2],
|
|
213
|
+
"score": score,
|
|
214
|
+
"class_id": cid,
|
|
215
|
+
}
|
|
216
|
+
cname = self._class_name(cid)
|
|
217
|
+
if cname is not None:
|
|
218
|
+
det["class_name"] = cname
|
|
219
|
+
|
|
220
|
+
if kpts is not None and i < kpts.shape[0]:
|
|
221
|
+
kp_list = kpts[i].tolist()
|
|
222
|
+
det["keypoints"] = [[float(a), float(b), float(c)] for a, b, c in kp_list]
|
|
223
|
+
|
|
224
|
+
detections.append(det)
|
|
225
|
+
|
|
226
|
+
return detections
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
class YOLOSegDetector(_UltralyticsBase):
|
|
230
|
+
"""Ultralytics YOLO segmentation model wrapper (boxes + polygon segments)."""
|
|
231
|
+
|
|
232
|
+
def __init__(self, **kwargs) -> None:
|
|
233
|
+
super().__init__(**kwargs)
|
|
234
|
+
|
|
235
|
+
def process_frame(self, frame_bgr: np.ndarray) -> List[Detection]:
|
|
236
|
+
results = self._predict(frame_bgr)
|
|
237
|
+
r = results[0]
|
|
238
|
+
detections: List[Detection] = []
|
|
239
|
+
|
|
240
|
+
if getattr(r, "boxes", None) is None or len(r.boxes) == 0:
|
|
241
|
+
return detections
|
|
242
|
+
|
|
243
|
+
boxes_xyxy = r.boxes.xyxy.cpu().numpy().astype(float)
|
|
244
|
+
scores = r.boxes.conf.cpu().numpy().astype(float)
|
|
245
|
+
cls_ids = r.boxes.cls.cpu().numpy().astype(int)
|
|
246
|
+
|
|
247
|
+
# r.masks.xy is typically list-like per instance
|
|
248
|
+
mask_polys = None
|
|
249
|
+
if getattr(r, "masks", None) is not None:
|
|
250
|
+
try:
|
|
251
|
+
mask_polys = r.masks.xy
|
|
252
|
+
except Exception:
|
|
253
|
+
mask_polys = None
|
|
254
|
+
|
|
255
|
+
for i in range(boxes_xyxy.shape[0]):
|
|
256
|
+
x1, y1, x2, y2 = boxes_xyxy[i].tolist()
|
|
257
|
+
score = float(scores[i])
|
|
258
|
+
cid = int(cls_ids[i])
|
|
259
|
+
det: Detection = {
|
|
260
|
+
"bbox": [x1, y1, x2, y2],
|
|
261
|
+
"score": score,
|
|
262
|
+
"class_id": cid,
|
|
263
|
+
}
|
|
264
|
+
cname = self._class_name(cid)
|
|
265
|
+
if cname is not None:
|
|
266
|
+
det["class_name"] = cname
|
|
267
|
+
|
|
268
|
+
segs: List[List[List[float]]] = []
|
|
269
|
+
if mask_polys is not None and i < len(mask_polys):
|
|
270
|
+
polys_i = mask_polys[i]
|
|
271
|
+
# Ultralytics may return a list of polygons per instance
|
|
272
|
+
if isinstance(polys_i, (list, tuple)):
|
|
273
|
+
for poly in polys_i:
|
|
274
|
+
if poly is None:
|
|
275
|
+
continue
|
|
276
|
+
arr = np.asarray(poly, dtype=float)
|
|
277
|
+
if arr.ndim == 2 and arr.shape[1] == 2:
|
|
278
|
+
segs.append(arr.tolist())
|
|
279
|
+
else:
|
|
280
|
+
arr = np.asarray(polys_i, dtype=float)
|
|
281
|
+
if arr.ndim == 2 and arr.shape[1] == 2:
|
|
282
|
+
segs.append(arr.tolist())
|
|
283
|
+
|
|
284
|
+
if segs:
|
|
285
|
+
det["segments"] = segs
|
|
286
|
+
|
|
287
|
+
detections.append(det)
|
|
288
|
+
|
|
289
|
+
return detections
|
|
290
|
+
|
|
291
|
+
|
|
292
|
+
__all__ = [
|
|
293
|
+
"YOLOBBoxDetector",
|
|
294
|
+
"YOLOPoseDetector",
|
|
295
|
+
"YOLOSegDetector",
|
|
296
|
+
]
|