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.

Files changed (35) hide show
  1. tests/conftest.py +17 -5
  2. tests/test_cli.py +8 -8
  3. tests/test_cuda.py +5 -5
  4. tests/test_engine.py +5 -5
  5. tests/test_explorer.py +4 -4
  6. tests/test_exports.py +12 -24
  7. tests/test_integrations.py +9 -5
  8. tests/test_python.py +35 -39
  9. ultralytics/__init__.py +1 -1
  10. ultralytics/cfg/__init__.py +156 -39
  11. ultralytics/data/augment.py +3 -3
  12. ultralytics/data/explorer/explorer.py +3 -0
  13. ultralytics/engine/model.py +1 -1
  14. ultralytics/engine/results.py +160 -68
  15. ultralytics/hub/session.py +2 -0
  16. ultralytics/models/fastsam/prompt.py +1 -1
  17. ultralytics/models/sam/amg.py +1 -1
  18. ultralytics/models/sam/modules/tiny_encoder.py +1 -1
  19. ultralytics/models/yolo/classify/train.py +7 -16
  20. ultralytics/models/yolo/world/train_world.py +2 -2
  21. ultralytics/nn/modules/block.py +1 -0
  22. ultralytics/nn/tasks.py +1 -1
  23. ultralytics/solutions/__init__.py +1 -0
  24. ultralytics/solutions/ai_gym.py +3 -3
  25. ultralytics/solutions/streamlit_inference.py +154 -0
  26. ultralytics/utils/__init__.py +0 -1
  27. ultralytics/utils/metrics.py +1 -2
  28. ultralytics/utils/plotting.py +3 -3
  29. ultralytics/utils/torch_utils.py +22 -8
  30. {ultralytics-8.2.48.dist-info → ultralytics-8.2.50.dist-info}/METADATA +3 -3
  31. {ultralytics-8.2.48.dist-info → ultralytics-8.2.50.dist-info}/RECORD +35 -34
  32. {ultralytics-8.2.48.dist-info → ultralytics-8.2.50.dist-info}/WHEEL +1 -1
  33. {ultralytics-8.2.48.dist-info → ultralytics-8.2.50.dist-info}/LICENSE +0 -0
  34. {ultralytics-8.2.48.dist-info → ultralytics-8.2.50.dist-info}/entry_points.txt +0 -0
  35. {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:
@@ -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
- (dict): Validation metrics obtained from the validation process.
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.
@@ -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): Predictions, such as bboxes, masks and keypoints.
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
- """Return the shape of the data tensor."""
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 on CPU memory."""
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
- """Return a copy of the tensor as a numpy array."""
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
- """Return a copy of the tensor on GPU memory."""
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 with the specified index of the data tensor."""
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 the specified index."""
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
- """Update the boxes, masks, and probs attributes of the Results object."""
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
- """Return a copy of the Results object with all tensors on CPU memory."""
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
- """Return a copy of the Results object with all tensors as numpy arrays."""
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
- """Return a copy of the Results object with all tensors on GPU memory."""
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
- """Return a copy of the Results object with tensors on the specified device and dtype."""
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
- """Return a new Results object with the same image, path, names and speed."""
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 image."""
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
- """Return log string for each task."""
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 predictions into txt file.
364
+ Save detection results to a text file.
335
365
 
336
366
  Args:
337
- txt_file (str): txt file path.
338
- save_conf (bool): save confidence score or not.
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 predictions to `save_dir/cls/file_name.jpg`.
424
+ Save cropped detection images to `save_dir/cls/file_name.jpg`.
371
425
 
372
426
  Args:
373
- save_dir (str | pathlib.Path): Save path.
374
- file_name (str | pathlib.Path): File name.
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 the results to a summarized format."""
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
- """Convert the results to JSON format."""
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
- Properties:
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 | numpy.ndarray): A tensor or numpy array containing the detection boxes, with
474
- shape (num_boxes, 6) or (num_boxes, 7). The last two columns contain confidence and class values.
475
- If present, the third last column contains track IDs.
476
- orig_shape (tuple): Original image size, in the format (height, width).
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
- """Return the boxes in xyxy format."""
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
- """Return the confidence values of the boxes."""
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
- """Return the class values of the boxes."""
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 track IDs of the boxes (if available)."""
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
- """Return the boxes in xywh format."""
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
- """Return the boxes in xyxy format normalized by original image size."""
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
- """Return the boxes in xywh format normalized by original image size."""
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
- """Initialize the Masks class with the given masks tensor and original image shape."""
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 segments."""
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
- """Return segments in pixel coordinates."""
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 size."""
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 coordinates of keypoints."""
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 of keypoints if available, else None."""
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 the Probs class with classification probabilities and optional original shape of the image."""
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 top 1."""
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
- """Return the confidence of top 1."""
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
- """Return the confidences of top 5."""
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 the Boxes class."""
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 the rotated boxes in xywhr format."""
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
- """Return the confidence values of the boxes."""
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
- """Return the class values of the boxes."""
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 track IDs of the boxes (if available)."""
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
- """Return the boxes in xyxyxyxy format, (N, 4, 2)."""
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
- """Return the boxes in xyxyxyxy format, (N, 4, 2)."""
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
- Return the horizontal boxes in xyxy format, (N, 4).
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
- Accepts both torch and numpy boxes.
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]
@@ -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]
@@ -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