ultralytics 8.2.48__py3-none-any.whl → 8.2.50__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.
Potentially problematic release.
This version of ultralytics might be problematic. Click here for more details.
- tests/conftest.py +17 -5
- tests/test_cli.py +8 -8
- tests/test_cuda.py +5 -5
- tests/test_engine.py +5 -5
- tests/test_explorer.py +4 -4
- tests/test_exports.py +12 -24
- tests/test_integrations.py +9 -5
- tests/test_python.py +35 -39
- ultralytics/__init__.py +1 -1
- ultralytics/cfg/__init__.py +156 -39
- ultralytics/data/augment.py +3 -3
- ultralytics/data/explorer/explorer.py +3 -0
- ultralytics/engine/model.py +1 -1
- ultralytics/engine/results.py +160 -68
- ultralytics/hub/session.py +2 -0
- ultralytics/models/fastsam/prompt.py +1 -1
- ultralytics/models/sam/amg.py +1 -1
- ultralytics/models/sam/modules/tiny_encoder.py +1 -1
- ultralytics/models/yolo/classify/train.py +7 -16
- ultralytics/models/yolo/world/train_world.py +2 -2
- ultralytics/nn/modules/block.py +1 -0
- ultralytics/nn/tasks.py +1 -1
- ultralytics/solutions/__init__.py +1 -0
- ultralytics/solutions/ai_gym.py +3 -3
- ultralytics/solutions/streamlit_inference.py +154 -0
- ultralytics/utils/__init__.py +0 -1
- ultralytics/utils/metrics.py +1 -2
- ultralytics/utils/plotting.py +3 -3
- ultralytics/utils/torch_utils.py +22 -8
- {ultralytics-8.2.48.dist-info → ultralytics-8.2.50.dist-info}/METADATA +3 -3
- {ultralytics-8.2.48.dist-info → ultralytics-8.2.50.dist-info}/RECORD +35 -34
- {ultralytics-8.2.48.dist-info → ultralytics-8.2.50.dist-info}/WHEEL +1 -1
- {ultralytics-8.2.48.dist-info → ultralytics-8.2.50.dist-info}/LICENSE +0 -0
- {ultralytics-8.2.48.dist-info → ultralytics-8.2.50.dist-info}/entry_points.txt +0 -0
- {ultralytics-8.2.48.dist-info → ultralytics-8.2.50.dist-info}/top_level.txt +0 -0
|
@@ -22,6 +22,7 @@ from .utils import get_sim_index_schema, get_table_schema, plot_query_result, pr
|
|
|
22
22
|
|
|
23
23
|
class ExplorerDataset(YOLODataset):
|
|
24
24
|
def __init__(self, *args, data: dict = None, **kwargs) -> None:
|
|
25
|
+
"""Initializes the ExplorerDataset with the provided data arguments, extending the YOLODataset class."""
|
|
25
26
|
super().__init__(*args, data=data, **kwargs)
|
|
26
27
|
|
|
27
28
|
def load_image(self, i: int) -> Union[Tuple[np.ndarray, Tuple[int, int], Tuple[int, int]], Tuple[None, None, None]]:
|
|
@@ -59,6 +60,7 @@ class Explorer:
|
|
|
59
60
|
model: str = "yolov8n.pt",
|
|
60
61
|
uri: str = USER_CONFIG_DIR / "explorer",
|
|
61
62
|
) -> None:
|
|
63
|
+
"""Initializes the Explorer class with dataset path, model, and URI for database connection."""
|
|
62
64
|
# Note duckdb==0.10.0 bug https://github.com/ultralytics/ultralytics/pull/8181
|
|
63
65
|
checks.check_requirements(["lancedb>=0.4.3", "duckdb<=0.9.2"])
|
|
64
66
|
import lancedb
|
|
@@ -416,6 +418,7 @@ class Explorer:
|
|
|
416
418
|
def _check_imgs_or_idxs(
|
|
417
419
|
self, img: Union[str, np.ndarray, List[str], List[np.ndarray], None], idx: Union[None, int, List[int]]
|
|
418
420
|
) -> List[np.ndarray]:
|
|
421
|
+
"""Determines whether to fetch images or indexes based on provided arguments and returns image paths."""
|
|
419
422
|
if img is None and idx is None:
|
|
420
423
|
raise ValueError("Either img or idx must be provided.")
|
|
421
424
|
if img is not None and idx is not None:
|
ultralytics/engine/model.py
CHANGED
|
@@ -505,7 +505,7 @@ class Model(nn.Module):
|
|
|
505
505
|
used to customize various aspects of the validation process.
|
|
506
506
|
|
|
507
507
|
Returns:
|
|
508
|
-
(
|
|
508
|
+
(ultralytics.utils.metrics.DetMetrics): Validation metrics obtained from the validation process.
|
|
509
509
|
|
|
510
510
|
Raises:
|
|
511
511
|
AssertionError: If the model is not a PyTorch model.
|
ultralytics/engine/results.py
CHANGED
|
@@ -23,31 +23,44 @@ class BaseTensor(SimpleClass):
|
|
|
23
23
|
|
|
24
24
|
def __init__(self, data, orig_shape) -> None:
|
|
25
25
|
"""
|
|
26
|
-
Initialize BaseTensor with data and original shape.
|
|
26
|
+
Initialize BaseTensor with prediction data and the original shape of the image.
|
|
27
27
|
|
|
28
28
|
Args:
|
|
29
|
-
data (torch.Tensor | np.ndarray):
|
|
30
|
-
orig_shape (tuple): Original shape of image.
|
|
29
|
+
data (torch.Tensor | np.ndarray): Prediction data such as bounding boxes, masks, or keypoints.
|
|
30
|
+
orig_shape (tuple): Original shape of the image, typically in the format (height, width).
|
|
31
|
+
|
|
32
|
+
Returns:
|
|
33
|
+
(None)
|
|
34
|
+
|
|
35
|
+
Example:
|
|
36
|
+
```python
|
|
37
|
+
import torch
|
|
38
|
+
from ultralytics.engine.results import BaseTensor
|
|
39
|
+
|
|
40
|
+
data = torch.tensor([[1, 2, 3], [4, 5, 6]])
|
|
41
|
+
orig_shape = (720, 1280)
|
|
42
|
+
base_tensor = BaseTensor(data, orig_shape)
|
|
43
|
+
```
|
|
31
44
|
"""
|
|
32
|
-
assert isinstance(data, (torch.Tensor, np.ndarray))
|
|
45
|
+
assert isinstance(data, (torch.Tensor, np.ndarray)), "data must be torch.Tensor or np.ndarray"
|
|
33
46
|
self.data = data
|
|
34
47
|
self.orig_shape = orig_shape
|
|
35
48
|
|
|
36
49
|
@property
|
|
37
50
|
def shape(self):
|
|
38
|
-
"""
|
|
51
|
+
"""Returns the shape of the underlying data tensor for easier manipulation and device handling."""
|
|
39
52
|
return self.data.shape
|
|
40
53
|
|
|
41
54
|
def cpu(self):
|
|
42
|
-
"""Return a copy of the tensor
|
|
55
|
+
"""Return a copy of the tensor stored in CPU memory."""
|
|
43
56
|
return self if isinstance(self.data, np.ndarray) else self.__class__(self.data.cpu(), self.orig_shape)
|
|
44
57
|
|
|
45
58
|
def numpy(self):
|
|
46
|
-
"""
|
|
59
|
+
"""Returns a copy of the tensor as a numpy array for efficient numerical operations."""
|
|
47
60
|
return self if isinstance(self.data, np.ndarray) else self.__class__(self.data.numpy(), self.orig_shape)
|
|
48
61
|
|
|
49
62
|
def cuda(self):
|
|
50
|
-
"""
|
|
63
|
+
"""Moves the tensor to GPU memory, returning a new instance if necessary."""
|
|
51
64
|
return self.__class__(torch.as_tensor(self.data).cuda(), self.orig_shape)
|
|
52
65
|
|
|
53
66
|
def to(self, *args, **kwargs):
|
|
@@ -55,11 +68,11 @@ class BaseTensor(SimpleClass):
|
|
|
55
68
|
return self.__class__(torch.as_tensor(self.data).to(*args, **kwargs), self.orig_shape)
|
|
56
69
|
|
|
57
70
|
def __len__(self): # override len(results)
|
|
58
|
-
"""Return the length of the data tensor."""
|
|
71
|
+
"""Return the length of the underlying data tensor."""
|
|
59
72
|
return len(self.data)
|
|
60
73
|
|
|
61
74
|
def __getitem__(self, idx):
|
|
62
|
-
"""Return a BaseTensor
|
|
75
|
+
"""Return a new BaseTensor instance containing the specified indexed elements of the data tensor."""
|
|
63
76
|
return self.__class__(self.data[idx], self.orig_shape)
|
|
64
77
|
|
|
65
78
|
|
|
@@ -98,7 +111,7 @@ class Results(SimpleClass):
|
|
|
98
111
|
self, orig_img, path, names, boxes=None, masks=None, probs=None, keypoints=None, obb=None, speed=None
|
|
99
112
|
) -> None:
|
|
100
113
|
"""
|
|
101
|
-
Initialize the Results class.
|
|
114
|
+
Initialize the Results class for storing and manipulating inference results.
|
|
102
115
|
|
|
103
116
|
Args:
|
|
104
117
|
orig_img (numpy.ndarray): The original image as a numpy array.
|
|
@@ -109,6 +122,15 @@ class Results(SimpleClass):
|
|
|
109
122
|
probs (torch.tensor, optional): A 1D tensor of probabilities of each class for classification task.
|
|
110
123
|
keypoints (torch.tensor, optional): A 2D tensor of keypoint coordinates for each detection.
|
|
111
124
|
obb (torch.tensor, optional): A 2D tensor of oriented bounding box coordinates for each detection.
|
|
125
|
+
speed (dict, optional): A dictionary containing preprocess, inference, and postprocess speeds (ms/image).
|
|
126
|
+
|
|
127
|
+
Returns:
|
|
128
|
+
None
|
|
129
|
+
|
|
130
|
+
Example:
|
|
131
|
+
```python
|
|
132
|
+
results = model("path/to/image.jpg")
|
|
133
|
+
```
|
|
112
134
|
"""
|
|
113
135
|
self.orig_img = orig_img
|
|
114
136
|
self.orig_shape = orig_img.shape[:2]
|
|
@@ -124,18 +146,18 @@ class Results(SimpleClass):
|
|
|
124
146
|
self._keys = "boxes", "masks", "probs", "keypoints", "obb"
|
|
125
147
|
|
|
126
148
|
def __getitem__(self, idx):
|
|
127
|
-
"""Return a Results object for
|
|
149
|
+
"""Return a Results object for a specific index of inference results."""
|
|
128
150
|
return self._apply("__getitem__", idx)
|
|
129
151
|
|
|
130
152
|
def __len__(self):
|
|
131
|
-
"""Return the number of detections in the Results object."""
|
|
153
|
+
"""Return the number of detections in the Results object from a non-empty attribute set (boxes, masks, etc.)."""
|
|
132
154
|
for k in self._keys:
|
|
133
155
|
v = getattr(self, k)
|
|
134
156
|
if v is not None:
|
|
135
157
|
return len(v)
|
|
136
158
|
|
|
137
159
|
def update(self, boxes=None, masks=None, probs=None, obb=None):
|
|
138
|
-
"""
|
|
160
|
+
"""Updates detection results attributes including boxes, masks, probs, and obb with new data."""
|
|
139
161
|
if boxes is not None:
|
|
140
162
|
self.boxes = Boxes(ops.clip_boxes(boxes, self.orig_shape), self.orig_shape)
|
|
141
163
|
if masks is not None:
|
|
@@ -156,7 +178,15 @@ class Results(SimpleClass):
|
|
|
156
178
|
**kwargs: Arbitrary keyword arguments to pass to the function.
|
|
157
179
|
|
|
158
180
|
Returns:
|
|
159
|
-
Results: A new Results object with attributes modified by the applied function.
|
|
181
|
+
(Results): A new Results object with attributes modified by the applied function.
|
|
182
|
+
|
|
183
|
+
Example:
|
|
184
|
+
```python
|
|
185
|
+
results = model("path/to/image.jpg")
|
|
186
|
+
for result in results:
|
|
187
|
+
result_cuda = result.cuda()
|
|
188
|
+
result_cpu = result.cpu()
|
|
189
|
+
```
|
|
160
190
|
"""
|
|
161
191
|
r = self.new()
|
|
162
192
|
for k in self._keys:
|
|
@@ -166,23 +196,23 @@ class Results(SimpleClass):
|
|
|
166
196
|
return r
|
|
167
197
|
|
|
168
198
|
def cpu(self):
|
|
169
|
-
"""
|
|
199
|
+
"""Returns a copy of the Results object with all its tensors moved to CPU memory."""
|
|
170
200
|
return self._apply("cpu")
|
|
171
201
|
|
|
172
202
|
def numpy(self):
|
|
173
|
-
"""
|
|
203
|
+
"""Returns a copy of the Results object with all tensors as numpy arrays."""
|
|
174
204
|
return self._apply("numpy")
|
|
175
205
|
|
|
176
206
|
def cuda(self):
|
|
177
|
-
"""
|
|
207
|
+
"""Moves all tensors in the Results object to GPU memory."""
|
|
178
208
|
return self._apply("cuda")
|
|
179
209
|
|
|
180
210
|
def to(self, *args, **kwargs):
|
|
181
|
-
"""
|
|
211
|
+
"""Moves all tensors in the Results object to the specified device and dtype."""
|
|
182
212
|
return self._apply("to", *args, **kwargs)
|
|
183
213
|
|
|
184
214
|
def new(self):
|
|
185
|
-
"""
|
|
215
|
+
"""Returns a new Results object with the same image, path, names, and speed attributes."""
|
|
186
216
|
return Results(orig_img=self.orig_img, path=self.path, names=self.names, speed=self.speed)
|
|
187
217
|
|
|
188
218
|
def plot(
|
|
@@ -220,7 +250,7 @@ class Results(SimpleClass):
|
|
|
220
250
|
labels (bool): Whether to plot the label of bounding boxes.
|
|
221
251
|
boxes (bool): Whether to plot the bounding boxes.
|
|
222
252
|
masks (bool): Whether to plot the masks.
|
|
223
|
-
probs (bool): Whether to plot classification probability
|
|
253
|
+
probs (bool): Whether to plot classification probability.
|
|
224
254
|
show (bool): Whether to display the annotated image directly.
|
|
225
255
|
save (bool): Whether to save the annotated image to `filename`.
|
|
226
256
|
filename (str): Filename to save image to if save is True.
|
|
@@ -304,18 +334,18 @@ class Results(SimpleClass):
|
|
|
304
334
|
return annotator.result()
|
|
305
335
|
|
|
306
336
|
def show(self, *args, **kwargs):
|
|
307
|
-
"""Show annotated results
|
|
337
|
+
"""Show the image with annotated inference results."""
|
|
308
338
|
self.plot(show=True, *args, **kwargs)
|
|
309
339
|
|
|
310
340
|
def save(self, filename=None, *args, **kwargs):
|
|
311
|
-
"""Save annotated results image."""
|
|
341
|
+
"""Save annotated inference results image to file."""
|
|
312
342
|
if not filename:
|
|
313
343
|
filename = f"results_{Path(self.path).name}"
|
|
314
344
|
self.plot(save=True, filename=filename, *args, **kwargs)
|
|
315
345
|
return filename
|
|
316
346
|
|
|
317
347
|
def verbose(self):
|
|
318
|
-
"""
|
|
348
|
+
"""Returns a log string for each task in the results, detailing detection and classification outcomes."""
|
|
319
349
|
log_string = ""
|
|
320
350
|
probs = self.probs
|
|
321
351
|
boxes = self.boxes
|
|
@@ -331,11 +361,35 @@ class Results(SimpleClass):
|
|
|
331
361
|
|
|
332
362
|
def save_txt(self, txt_file, save_conf=False):
|
|
333
363
|
"""
|
|
334
|
-
Save
|
|
364
|
+
Save detection results to a text file.
|
|
335
365
|
|
|
336
366
|
Args:
|
|
337
|
-
txt_file (str):
|
|
338
|
-
save_conf (bool):
|
|
367
|
+
txt_file (str): Path to the output text file.
|
|
368
|
+
save_conf (bool): Whether to include confidence scores in the output.
|
|
369
|
+
|
|
370
|
+
Returns:
|
|
371
|
+
(str): Path to the saved text file.
|
|
372
|
+
|
|
373
|
+
Example:
|
|
374
|
+
```python
|
|
375
|
+
from ultralytics import YOLO
|
|
376
|
+
|
|
377
|
+
model = YOLO('yolov8n.pt')
|
|
378
|
+
results = model("path/to/image.jpg")
|
|
379
|
+
for result in results:
|
|
380
|
+
result.save_txt("output.txt")
|
|
381
|
+
```
|
|
382
|
+
|
|
383
|
+
Notes:
|
|
384
|
+
- The file will contain one line per detection or classification with the following structure:
|
|
385
|
+
- For detections: `class confidence x_center y_center width height`
|
|
386
|
+
- For classifications: `confidence class_name`
|
|
387
|
+
- For masks and keypoints, the specific formats will vary accordingly.
|
|
388
|
+
|
|
389
|
+
- The function will create the output directory if it does not exist.
|
|
390
|
+
- If save_conf is False, the confidence scores will be excluded from the output.
|
|
391
|
+
|
|
392
|
+
- Existing contents of the file will not be overwritten; new results will be appended.
|
|
339
393
|
"""
|
|
340
394
|
is_obb = self.obb is not None
|
|
341
395
|
boxes = self.obb if is_obb else self.boxes
|
|
@@ -367,11 +421,27 @@ class Results(SimpleClass):
|
|
|
367
421
|
|
|
368
422
|
def save_crop(self, save_dir, file_name=Path("im.jpg")):
|
|
369
423
|
"""
|
|
370
|
-
Save cropped
|
|
424
|
+
Save cropped detection images to `save_dir/cls/file_name.jpg`.
|
|
371
425
|
|
|
372
426
|
Args:
|
|
373
|
-
save_dir (str | pathlib.Path):
|
|
374
|
-
file_name (str | pathlib.Path):
|
|
427
|
+
save_dir (str | pathlib.Path): Directory path where the cropped images should be saved.
|
|
428
|
+
file_name (str | pathlib.Path): Filename for the saved cropped image.
|
|
429
|
+
|
|
430
|
+
Notes:
|
|
431
|
+
This function does not support Classify or Oriented Bounding Box (OBB) tasks. It will warn and exit if
|
|
432
|
+
called for such tasks.
|
|
433
|
+
|
|
434
|
+
Example:
|
|
435
|
+
```python
|
|
436
|
+
from ultralytics import YOLO
|
|
437
|
+
|
|
438
|
+
model = YOLO("yolov8n.pt")
|
|
439
|
+
results = model("path/to/image.jpg")
|
|
440
|
+
|
|
441
|
+
# Save cropped images to the specified directory
|
|
442
|
+
for result in results:
|
|
443
|
+
result.save_crop(save_dir="path/to/save/crops", file_name="crop")
|
|
444
|
+
```
|
|
375
445
|
"""
|
|
376
446
|
if self.probs is not None:
|
|
377
447
|
LOGGER.warning("WARNING ⚠️ Classify task do not support `save_crop`.")
|
|
@@ -388,7 +458,7 @@ class Results(SimpleClass):
|
|
|
388
458
|
)
|
|
389
459
|
|
|
390
460
|
def summary(self, normalize=False, decimals=5):
|
|
391
|
-
"""Convert
|
|
461
|
+
"""Convert inference results to a summarized dictionary with optional normalization for box coordinates."""
|
|
392
462
|
# Create list of detection dictionaries
|
|
393
463
|
results = []
|
|
394
464
|
if self.probs is not None:
|
|
@@ -432,7 +502,7 @@ class Results(SimpleClass):
|
|
|
432
502
|
return results
|
|
433
503
|
|
|
434
504
|
def tojson(self, normalize=False, decimals=5):
|
|
435
|
-
"""
|
|
505
|
+
"""Converts detection results to JSON format."""
|
|
436
506
|
import json
|
|
437
507
|
|
|
438
508
|
return json.dumps(self.summary(normalize=normalize, decimals=decimals), indent=2)
|
|
@@ -449,7 +519,7 @@ class Boxes(BaseTensor):
|
|
|
449
519
|
orig_shape (tuple): The original image size as a tuple (height, width), used for normalization.
|
|
450
520
|
is_track (bool): Indicates whether tracking IDs are included in the box data.
|
|
451
521
|
|
|
452
|
-
|
|
522
|
+
Attributes:
|
|
453
523
|
xyxy (torch.Tensor | numpy.ndarray): Boxes in [x1, y1, x2, y2] format.
|
|
454
524
|
conf (torch.Tensor | numpy.ndarray): Confidence scores for each box.
|
|
455
525
|
cls (torch.Tensor | numpy.ndarray): Class labels for each box.
|
|
@@ -467,13 +537,16 @@ class Boxes(BaseTensor):
|
|
|
467
537
|
|
|
468
538
|
def __init__(self, boxes, orig_shape) -> None:
|
|
469
539
|
"""
|
|
470
|
-
Initialize the Boxes class.
|
|
540
|
+
Initialize the Boxes class with detection box data and the original image shape.
|
|
471
541
|
|
|
472
542
|
Args:
|
|
473
|
-
boxes (torch.Tensor |
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
orig_shape (tuple):
|
|
543
|
+
boxes (torch.Tensor | np.ndarray): A tensor or numpy array with detection boxes of shape (num_boxes, 6)
|
|
544
|
+
or (num_boxes, 7). Columns should contain [x1, y1, x2, y2, confidence, class, (optional) track_id].
|
|
545
|
+
The track ID column is included if present.
|
|
546
|
+
orig_shape (tuple): The original image shape as (height, width). Used for normalization.
|
|
547
|
+
|
|
548
|
+
Returns:
|
|
549
|
+
(None)
|
|
477
550
|
"""
|
|
478
551
|
if boxes.ndim == 1:
|
|
479
552
|
boxes = boxes[None, :]
|
|
@@ -485,34 +558,34 @@ class Boxes(BaseTensor):
|
|
|
485
558
|
|
|
486
559
|
@property
|
|
487
560
|
def xyxy(self):
|
|
488
|
-
"""
|
|
561
|
+
"""Returns bounding boxes in [x1, y1, x2, y2] format."""
|
|
489
562
|
return self.data[:, :4]
|
|
490
563
|
|
|
491
564
|
@property
|
|
492
565
|
def conf(self):
|
|
493
|
-
"""
|
|
566
|
+
"""Returns the confidence scores for each detection box."""
|
|
494
567
|
return self.data[:, -2]
|
|
495
568
|
|
|
496
569
|
@property
|
|
497
570
|
def cls(self):
|
|
498
|
-
"""
|
|
571
|
+
"""Class ID tensor representing category predictions for each bounding box."""
|
|
499
572
|
return self.data[:, -1]
|
|
500
573
|
|
|
501
574
|
@property
|
|
502
575
|
def id(self):
|
|
503
|
-
"""Return the
|
|
576
|
+
"""Return the tracking IDs for each box if available."""
|
|
504
577
|
return self.data[:, -3] if self.is_track else None
|
|
505
578
|
|
|
506
579
|
@property
|
|
507
580
|
@lru_cache(maxsize=2) # maxsize 1 should suffice
|
|
508
581
|
def xywh(self):
|
|
509
|
-
"""
|
|
582
|
+
"""Returns boxes in [x, y, width, height] format."""
|
|
510
583
|
return ops.xyxy2xywh(self.xyxy)
|
|
511
584
|
|
|
512
585
|
@property
|
|
513
586
|
@lru_cache(maxsize=2)
|
|
514
587
|
def xyxyn(self):
|
|
515
|
-
"""
|
|
588
|
+
"""Normalize box coordinates to [x1, y1, x2, y2] relative to the original image size."""
|
|
516
589
|
xyxy = self.xyxy.clone() if isinstance(self.xyxy, torch.Tensor) else np.copy(self.xyxy)
|
|
517
590
|
xyxy[..., [0, 2]] /= self.orig_shape[1]
|
|
518
591
|
xyxy[..., [1, 3]] /= self.orig_shape[0]
|
|
@@ -521,7 +594,7 @@ class Boxes(BaseTensor):
|
|
|
521
594
|
@property
|
|
522
595
|
@lru_cache(maxsize=2)
|
|
523
596
|
def xywhn(self):
|
|
524
|
-
"""
|
|
597
|
+
"""Returns normalized bounding boxes in [x, y, width, height] format."""
|
|
525
598
|
xywh = ops.xyxy2xywh(self.xyxy)
|
|
526
599
|
xywh[..., [0, 2]] /= self.orig_shape[1]
|
|
527
600
|
xywh[..., [1, 3]] /= self.orig_shape[0]
|
|
@@ -544,7 +617,7 @@ class Masks(BaseTensor):
|
|
|
544
617
|
"""
|
|
545
618
|
|
|
546
619
|
def __init__(self, masks, orig_shape) -> None:
|
|
547
|
-
"""
|
|
620
|
+
"""Initializes the Masks class with a masks tensor and original image shape."""
|
|
548
621
|
if masks.ndim == 2:
|
|
549
622
|
masks = masks[None, :]
|
|
550
623
|
super().__init__(masks, orig_shape)
|
|
@@ -552,7 +625,7 @@ class Masks(BaseTensor):
|
|
|
552
625
|
@property
|
|
553
626
|
@lru_cache(maxsize=1)
|
|
554
627
|
def xyn(self):
|
|
555
|
-
"""Return normalized
|
|
628
|
+
"""Return normalized xy-coordinates of the segmentation masks."""
|
|
556
629
|
return [
|
|
557
630
|
ops.scale_coords(self.data.shape[1:], x, self.orig_shape, normalize=True)
|
|
558
631
|
for x in ops.masks2segments(self.data)
|
|
@@ -561,7 +634,7 @@ class Masks(BaseTensor):
|
|
|
561
634
|
@property
|
|
562
635
|
@lru_cache(maxsize=1)
|
|
563
636
|
def xy(self):
|
|
564
|
-
"""
|
|
637
|
+
"""Returns the [x, y] normalized mask coordinates for each segment in the mask tensor."""
|
|
565
638
|
return [
|
|
566
639
|
ops.scale_coords(self.data.shape[1:], x, self.orig_shape, normalize=False)
|
|
567
640
|
for x in ops.masks2segments(self.data)
|
|
@@ -572,7 +645,7 @@ class Keypoints(BaseTensor):
|
|
|
572
645
|
"""
|
|
573
646
|
A class for storing and manipulating detection keypoints.
|
|
574
647
|
|
|
575
|
-
Attributes
|
|
648
|
+
Attributes
|
|
576
649
|
xy (torch.Tensor): A collection of keypoints containing x, y coordinates for each detection.
|
|
577
650
|
xyn (torch.Tensor): A normalized version of xy with coordinates in the range [0, 1].
|
|
578
651
|
conf (torch.Tensor): Confidence values associated with keypoints if available, otherwise None.
|
|
@@ -586,7 +659,7 @@ class Keypoints(BaseTensor):
|
|
|
586
659
|
|
|
587
660
|
@smart_inference_mode() # avoid keypoints < conf in-place error
|
|
588
661
|
def __init__(self, keypoints, orig_shape) -> None:
|
|
589
|
-
"""Initializes the Keypoints object with detection keypoints and original image
|
|
662
|
+
"""Initializes the Keypoints object with detection keypoints and original image dimensions."""
|
|
590
663
|
if keypoints.ndim == 2:
|
|
591
664
|
keypoints = keypoints[None, :]
|
|
592
665
|
if keypoints.shape[2] == 3: # x, y, conf
|
|
@@ -604,7 +677,7 @@ class Keypoints(BaseTensor):
|
|
|
604
677
|
@property
|
|
605
678
|
@lru_cache(maxsize=1)
|
|
606
679
|
def xyn(self):
|
|
607
|
-
"""Returns normalized x, y
|
|
680
|
+
"""Returns normalized coordinates (x, y) of keypoints relative to the original image size."""
|
|
608
681
|
xy = self.xy.clone() if isinstance(self.xy, torch.Tensor) else np.copy(self.xy)
|
|
609
682
|
xy[..., 0] /= self.orig_shape[1]
|
|
610
683
|
xy[..., 1] /= self.orig_shape[0]
|
|
@@ -613,7 +686,7 @@ class Keypoints(BaseTensor):
|
|
|
613
686
|
@property
|
|
614
687
|
@lru_cache(maxsize=1)
|
|
615
688
|
def conf(self):
|
|
616
|
-
"""Returns confidence values
|
|
689
|
+
"""Returns confidence values for each keypoint."""
|
|
617
690
|
return self.data[..., 2] if self.has_visible else None
|
|
618
691
|
|
|
619
692
|
|
|
@@ -621,7 +694,7 @@ class Probs(BaseTensor):
|
|
|
621
694
|
"""
|
|
622
695
|
A class for storing and manipulating classification predictions.
|
|
623
696
|
|
|
624
|
-
Attributes
|
|
697
|
+
Attributes
|
|
625
698
|
top1 (int): Index of the top 1 class.
|
|
626
699
|
top5 (list[int]): Indices of the top 5 classes.
|
|
627
700
|
top1conf (torch.Tensor): Confidence of the top 1 class.
|
|
@@ -635,31 +708,31 @@ class Probs(BaseTensor):
|
|
|
635
708
|
"""
|
|
636
709
|
|
|
637
710
|
def __init__(self, probs, orig_shape=None) -> None:
|
|
638
|
-
"""Initialize
|
|
711
|
+
"""Initialize Probs with classification probabilities and optional original image shape."""
|
|
639
712
|
super().__init__(probs, orig_shape)
|
|
640
713
|
|
|
641
714
|
@property
|
|
642
715
|
@lru_cache(maxsize=1)
|
|
643
716
|
def top1(self):
|
|
644
|
-
"""Return the index of
|
|
717
|
+
"""Return the index of the class with the highest probability."""
|
|
645
718
|
return int(self.data.argmax())
|
|
646
719
|
|
|
647
720
|
@property
|
|
648
721
|
@lru_cache(maxsize=1)
|
|
649
722
|
def top5(self):
|
|
650
|
-
"""Return the indices of top 5."""
|
|
723
|
+
"""Return the indices of the top 5 class probabilities."""
|
|
651
724
|
return (-self.data).argsort(0)[:5].tolist() # this way works with both torch and numpy.
|
|
652
725
|
|
|
653
726
|
@property
|
|
654
727
|
@lru_cache(maxsize=1)
|
|
655
728
|
def top1conf(self):
|
|
656
|
-
"""
|
|
729
|
+
"""Retrieves the confidence score of the highest probability class."""
|
|
657
730
|
return self.data[self.top1]
|
|
658
731
|
|
|
659
732
|
@property
|
|
660
733
|
@lru_cache(maxsize=1)
|
|
661
734
|
def top5conf(self):
|
|
662
|
-
"""
|
|
735
|
+
"""Returns confidence scores for the top 5 classification predictions."""
|
|
663
736
|
return self.data[self.top5]
|
|
664
737
|
|
|
665
738
|
|
|
@@ -673,7 +746,7 @@ class OBB(BaseTensor):
|
|
|
673
746
|
If present, the third last column contains track IDs, and the fifth column from the left contains rotation.
|
|
674
747
|
orig_shape (tuple): Original image size, in the format (height, width).
|
|
675
748
|
|
|
676
|
-
Attributes
|
|
749
|
+
Attributes
|
|
677
750
|
xywhr (torch.Tensor | numpy.ndarray): The boxes in [x_center, y_center, width, height, rotation] format.
|
|
678
751
|
conf (torch.Tensor | numpy.ndarray): The confidence values of the boxes.
|
|
679
752
|
cls (torch.Tensor | numpy.ndarray): The class values of the boxes.
|
|
@@ -691,7 +764,7 @@ class OBB(BaseTensor):
|
|
|
691
764
|
"""
|
|
692
765
|
|
|
693
766
|
def __init__(self, boxes, orig_shape) -> None:
|
|
694
|
-
"""Initialize
|
|
767
|
+
"""Initialize an OBB instance with oriented bounding box data and original image shape."""
|
|
695
768
|
if boxes.ndim == 1:
|
|
696
769
|
boxes = boxes[None, :]
|
|
697
770
|
n = boxes.shape[-1]
|
|
@@ -702,34 +775,34 @@ class OBB(BaseTensor):
|
|
|
702
775
|
|
|
703
776
|
@property
|
|
704
777
|
def xywhr(self):
|
|
705
|
-
"""Return
|
|
778
|
+
"""Return boxes in [x_center, y_center, width, height, rotation] format."""
|
|
706
779
|
return self.data[:, :5]
|
|
707
780
|
|
|
708
781
|
@property
|
|
709
782
|
def conf(self):
|
|
710
|
-
"""
|
|
783
|
+
"""Gets the confidence values of Oriented Bounding Boxes (OBBs)."""
|
|
711
784
|
return self.data[:, -2]
|
|
712
785
|
|
|
713
786
|
@property
|
|
714
787
|
def cls(self):
|
|
715
|
-
"""
|
|
788
|
+
"""Returns the class values of the oriented bounding boxes."""
|
|
716
789
|
return self.data[:, -1]
|
|
717
790
|
|
|
718
791
|
@property
|
|
719
792
|
def id(self):
|
|
720
|
-
"""Return the
|
|
793
|
+
"""Return the tracking IDs of the oriented bounding boxes (if available)."""
|
|
721
794
|
return self.data[:, -3] if self.is_track else None
|
|
722
795
|
|
|
723
796
|
@property
|
|
724
797
|
@lru_cache(maxsize=2)
|
|
725
798
|
def xyxyxyxy(self):
|
|
726
|
-
"""
|
|
799
|
+
"""Convert OBB format to 8-point (xyxyxyxy) coordinate format of shape (N, 4, 2) for rotated bounding boxes."""
|
|
727
800
|
return ops.xywhr2xyxyxyxy(self.xywhr)
|
|
728
801
|
|
|
729
802
|
@property
|
|
730
803
|
@lru_cache(maxsize=2)
|
|
731
804
|
def xyxyxyxyn(self):
|
|
732
|
-
"""
|
|
805
|
+
"""Converts rotated bounding boxes to normalized xyxyxyxy format of shape (N, 4, 2)."""
|
|
733
806
|
xyxyxyxyn = self.xyxyxyxy.clone() if isinstance(self.xyxyxyxy, torch.Tensor) else np.copy(self.xyxyxyxy)
|
|
734
807
|
xyxyxyxyn[..., 0] /= self.orig_shape[1]
|
|
735
808
|
xyxyxyxyn[..., 1] /= self.orig_shape[0]
|
|
@@ -739,9 +812,28 @@ class OBB(BaseTensor):
|
|
|
739
812
|
@lru_cache(maxsize=2)
|
|
740
813
|
def xyxy(self):
|
|
741
814
|
"""
|
|
742
|
-
|
|
815
|
+
Convert the oriented bounding boxes (OBB) to axis-aligned bounding boxes in xyxy format (x1, y1, x2, y2).
|
|
816
|
+
|
|
817
|
+
Returns:
|
|
818
|
+
(torch.Tensor | numpy.ndarray): Axis-aligned bounding boxes in xyxy format with shape (num_boxes, 4).
|
|
819
|
+
|
|
820
|
+
Example:
|
|
821
|
+
```python
|
|
822
|
+
import torch
|
|
823
|
+
from ultralytics import YOLO
|
|
824
|
+
|
|
825
|
+
model = YOLO('yolov8n.pt')
|
|
826
|
+
results = model('path/to/image.jpg')
|
|
827
|
+
for result in results:
|
|
828
|
+
obb = result.obb
|
|
829
|
+
if obb is not None:
|
|
830
|
+
xyxy_boxes = obb.xyxy
|
|
831
|
+
# Do something with xyxy_boxes
|
|
832
|
+
```
|
|
743
833
|
|
|
744
|
-
|
|
834
|
+
Note:
|
|
835
|
+
This method is useful to perform operations that require axis-aligned bounding boxes, such as IoU
|
|
836
|
+
calculation with non-rotated boxes. The conversion approximates the OBB by the minimal enclosing rectangle.
|
|
745
837
|
"""
|
|
746
838
|
x = self.xyxyxyxy[..., 0]
|
|
747
839
|
y = self.xyxyxyxy[..., 1]
|
ultralytics/hub/session.py
CHANGED
|
@@ -230,6 +230,8 @@ class HUBTrainingSession:
|
|
|
230
230
|
*args,
|
|
231
231
|
**kwargs,
|
|
232
232
|
):
|
|
233
|
+
"""Attempts to execute `request_func` with retries, timeout handling, optional threading, and progress."""
|
|
234
|
+
|
|
233
235
|
def retry_request():
|
|
234
236
|
"""Attempts to call `request_func` with retries, timeout, and optional threading."""
|
|
235
237
|
t0 = time.time() # Record the start time for the timeout
|
|
@@ -286,7 +286,7 @@ class FastSAMPrompt:
|
|
|
286
286
|
def box_prompt(self, bbox):
|
|
287
287
|
"""Modifies the bounding box properties and calculates IoU between masks and bounding box."""
|
|
288
288
|
if self.results[0].masks is not None:
|
|
289
|
-
assert bbox[2] != 0 and bbox[3] != 0
|
|
289
|
+
assert bbox[2] != 0 and bbox[3] != 0, "Bounding box width and height should not be zero"
|
|
290
290
|
masks = self.results[0].masks.data
|
|
291
291
|
target_height, target_width = self.results[0].orig_shape
|
|
292
292
|
h = masks.shape[1]
|
ultralytics/models/sam/amg.py
CHANGED
|
@@ -133,7 +133,7 @@ def remove_small_regions(mask: np.ndarray, area_thresh: float, mode: str) -> Tup
|
|
|
133
133
|
"""Remove small disconnected regions or holes in a mask, returning the mask and a modification indicator."""
|
|
134
134
|
import cv2 # type: ignore
|
|
135
135
|
|
|
136
|
-
assert mode in {"holes", "islands"}
|
|
136
|
+
assert mode in {"holes", "islands"}, f"Provided mode {mode} is invalid"
|
|
137
137
|
correct_holes = mode == "holes"
|
|
138
138
|
working_mask = (correct_holes ^ mask).astype(np.uint8)
|
|
139
139
|
n_labels, regions, stats, _ = cv2.connectedComponentsWithStats(working_mask, 8)
|
|
@@ -261,7 +261,7 @@ class Attention(torch.nn.Module):
|
|
|
261
261
|
"""
|
|
262
262
|
super().__init__()
|
|
263
263
|
|
|
264
|
-
assert isinstance(resolution, tuple) and len(resolution) == 2
|
|
264
|
+
assert isinstance(resolution, tuple) and len(resolution) == 2, "'resolution' argument not tuple of length 2"
|
|
265
265
|
self.num_heads = num_heads
|
|
266
266
|
self.scale = key_dim**-0.5
|
|
267
267
|
self.key_dim = key_dim
|