ultralytics 8.0.194__py3-none-any.whl → 8.0.196__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 (84) hide show
  1. ultralytics/__init__.py +1 -1
  2. ultralytics/cfg/__init__.py +5 -6
  3. ultralytics/data/augment.py +234 -29
  4. ultralytics/data/base.py +2 -1
  5. ultralytics/data/build.py +9 -3
  6. ultralytics/data/converter.py +5 -2
  7. ultralytics/data/dataset.py +16 -2
  8. ultralytics/data/loaders.py +111 -7
  9. ultralytics/data/utils.py +3 -3
  10. ultralytics/engine/exporter.py +1 -3
  11. ultralytics/engine/model.py +16 -9
  12. ultralytics/engine/predictor.py +10 -6
  13. ultralytics/engine/results.py +18 -8
  14. ultralytics/engine/trainer.py +19 -31
  15. ultralytics/engine/tuner.py +20 -20
  16. ultralytics/engine/validator.py +3 -4
  17. ultralytics/hub/__init__.py +2 -2
  18. ultralytics/hub/auth.py +18 -3
  19. ultralytics/hub/session.py +1 -0
  20. ultralytics/hub/utils.py +1 -3
  21. ultralytics/models/fastsam/model.py +2 -1
  22. ultralytics/models/fastsam/predict.py +10 -7
  23. ultralytics/models/fastsam/prompt.py +15 -1
  24. ultralytics/models/nas/model.py +3 -1
  25. ultralytics/models/rtdetr/model.py +4 -6
  26. ultralytics/models/rtdetr/predict.py +2 -1
  27. ultralytics/models/rtdetr/train.py +2 -1
  28. ultralytics/models/rtdetr/val.py +1 -0
  29. ultralytics/models/sam/amg.py +12 -6
  30. ultralytics/models/sam/model.py +5 -6
  31. ultralytics/models/sam/modules/decoders.py +5 -1
  32. ultralytics/models/sam/modules/encoders.py +15 -12
  33. ultralytics/models/sam/modules/tiny_encoder.py +38 -2
  34. ultralytics/models/sam/modules/transformer.py +2 -4
  35. ultralytics/models/sam/predict.py +8 -4
  36. ultralytics/models/utils/loss.py +35 -8
  37. ultralytics/models/utils/ops.py +14 -18
  38. ultralytics/models/yolo/classify/predict.py +1 -0
  39. ultralytics/models/yolo/classify/train.py +4 -2
  40. ultralytics/models/yolo/classify/val.py +1 -0
  41. ultralytics/models/yolo/detect/train.py +4 -3
  42. ultralytics/models/yolo/model.py +2 -4
  43. ultralytics/models/yolo/pose/predict.py +1 -0
  44. ultralytics/models/yolo/segment/predict.py +2 -0
  45. ultralytics/models/yolo/segment/val.py +1 -1
  46. ultralytics/nn/autobackend.py +54 -43
  47. ultralytics/nn/modules/__init__.py +13 -9
  48. ultralytics/nn/modules/block.py +11 -5
  49. ultralytics/nn/modules/conv.py +16 -7
  50. ultralytics/nn/modules/head.py +6 -3
  51. ultralytics/nn/modules/transformer.py +47 -15
  52. ultralytics/nn/modules/utils.py +6 -4
  53. ultralytics/nn/tasks.py +61 -21
  54. ultralytics/trackers/bot_sort.py +53 -6
  55. ultralytics/trackers/byte_tracker.py +71 -15
  56. ultralytics/trackers/track.py +0 -1
  57. ultralytics/trackers/utils/gmc.py +23 -0
  58. ultralytics/trackers/utils/kalman_filter.py +6 -6
  59. ultralytics/utils/__init__.py +32 -19
  60. ultralytics/utils/autobatch.py +1 -3
  61. ultralytics/utils/benchmarks.py +14 -1
  62. ultralytics/utils/callbacks/base.py +1 -3
  63. ultralytics/utils/callbacks/comet.py +11 -3
  64. ultralytics/utils/callbacks/dvc.py +9 -0
  65. ultralytics/utils/callbacks/neptune.py +5 -6
  66. ultralytics/utils/callbacks/wb.py +1 -0
  67. ultralytics/utils/checks.py +13 -9
  68. ultralytics/utils/dist.py +2 -1
  69. ultralytics/utils/downloads.py +7 -3
  70. ultralytics/utils/files.py +3 -3
  71. ultralytics/utils/instance.py +12 -3
  72. ultralytics/utils/loss.py +97 -22
  73. ultralytics/utils/metrics.py +35 -34
  74. ultralytics/utils/ops.py +10 -9
  75. ultralytics/utils/patches.py +9 -7
  76. ultralytics/utils/plotting.py +4 -3
  77. ultralytics/utils/torch_utils.py +8 -6
  78. ultralytics/utils/triton.py +87 -0
  79. {ultralytics-8.0.194.dist-info → ultralytics-8.0.196.dist-info}/METADATA +1 -1
  80. {ultralytics-8.0.194.dist-info → ultralytics-8.0.196.dist-info}/RECORD +84 -83
  81. {ultralytics-8.0.194.dist-info → ultralytics-8.0.196.dist-info}/LICENSE +0 -0
  82. {ultralytics-8.0.194.dist-info → ultralytics-8.0.196.dist-info}/WHEEL +0 -0
  83. {ultralytics-8.0.194.dist-info → ultralytics-8.0.196.dist-info}/entry_points.txt +0 -0
  84. {ultralytics-8.0.194.dist-info → ultralytics-8.0.196.dist-info}/top_level.txt +0 -0
ultralytics/utils/dist.py CHANGED
@@ -13,7 +13,8 @@ from .torch_utils import TORCH_1_9
13
13
 
14
14
 
15
15
  def find_free_network_port() -> int:
16
- """Finds a free port on localhost.
16
+ """
17
+ Finds a free port on localhost.
17
18
 
18
19
  It is useful in single-node training when we don't want to connect to a real main node but have to set the
19
20
  `MASTER_PORT` environment variable.
@@ -69,8 +69,8 @@ def delete_dsstore(path, files_to_delete=('.DS_Store', '__MACOSX')):
69
69
 
70
70
  def zip_directory(directory, compress=True, exclude=('.DS_Store', '__MACOSX'), progress=True):
71
71
  """
72
- Zips the contents of a directory, excluding files containing strings in the exclude list.
73
- The resulting zip file is named after the directory and placed alongside it.
72
+ Zips the contents of a directory, excluding files containing strings in the exclude list. The resulting zip file is
73
+ named after the directory and placed alongside it.
74
74
 
75
75
  Args:
76
76
  directory (str | Path): The path to the directory to be zipped.
@@ -341,7 +341,11 @@ def get_github_assets(repo='ultralytics/assets', version='latest', retry=False):
341
341
 
342
342
 
343
343
  def attempt_download_asset(file, repo='ultralytics/assets', release='v0.0.0'):
344
- """Attempt file download from GitHub release assets if not found locally. release = 'latest', 'v6.2', etc."""
344
+ """
345
+ Attempt file download from GitHub release assets if not found locally.
346
+
347
+ release = 'latest', 'v6.2', etc.
348
+ """
345
349
  from ultralytics.utils import SETTINGS # scoped for circular import
346
350
 
347
351
  # YOLOv3/5u updates
@@ -30,9 +30,9 @@ class WorkingDirectory(contextlib.ContextDecorator):
30
30
  @contextmanager
31
31
  def spaces_in_path(path):
32
32
  """
33
- Context manager to handle paths with spaces in their names.
34
- If a path contains spaces, it replaces them with underscores, copies the file/directory to the new path,
35
- executes the context code block, then copies the file/directory back to its original location.
33
+ Context manager to handle paths with spaces in their names. If a path contains spaces, it replaces them with
34
+ underscores, copies the file/directory to the new path, executes the context code block, then copies the
35
+ file/directory back to its original location.
36
36
 
37
37
  Args:
38
38
  path (str | Path): The original path.
@@ -32,9 +32,14 @@ __all__ = 'Bboxes', # tuple or list
32
32
 
33
33
 
34
34
  class Bboxes:
35
- """Bounding Boxes class. Only numpy variables are supported."""
35
+ """
36
+ Bounding Boxes class.
37
+
38
+ Only numpy variables are supported.
39
+ """
36
40
 
37
41
  def __init__(self, bboxes, format='xyxy') -> None:
42
+ """Initializes the Bboxes class with bounding box data in a specified format."""
38
43
  assert format in _formats, f'Invalid bounding box format: {format}, format must be one of {_formats}'
39
44
  bboxes = bboxes[None, :] if bboxes.ndim == 1 else bboxes
40
45
  assert bboxes.ndim == 2
@@ -194,7 +199,7 @@ class Instances:
194
199
  return self._bboxes.areas()
195
200
 
196
201
  def scale(self, scale_w, scale_h, bbox_only=False):
197
- """this might be similar with denormalize func but without normalized sign."""
202
+ """This might be similar with denormalize func but without normalized sign."""
198
203
  self._bboxes.mul(scale=(scale_w, scale_h, scale_w, scale_h))
199
204
  if bbox_only:
200
205
  return
@@ -307,7 +312,11 @@ class Instances:
307
312
  self.keypoints[..., 1] = self.keypoints[..., 1].clip(0, h)
308
313
 
309
314
  def remove_zero_area_boxes(self):
310
- """Remove zero-area boxes, i.e. after clipping some boxes may have zero width or height. This removes them."""
315
+ """
316
+ Remove zero-area boxes, i.e. after clipping some boxes may have zero width or height.
317
+
318
+ This removes them.
319
+ """
311
320
  good = self.bbox_areas > 0
312
321
  if not all(good):
313
322
  self._bboxes = self._bboxes[good]
ultralytics/utils/loss.py CHANGED
@@ -13,7 +13,11 @@ from .tal import bbox2dist
13
13
 
14
14
 
15
15
  class VarifocalLoss(nn.Module):
16
- """Varifocal loss by Zhang et al. https://arxiv.org/abs/2008.13367."""
16
+ """
17
+ Varifocal loss by Zhang et al.
18
+
19
+ https://arxiv.org/abs/2008.13367.
20
+ """
17
21
 
18
22
  def __init__(self):
19
23
  """Initialize the VarifocalLoss class."""
@@ -33,6 +37,7 @@ class FocalLoss(nn.Module):
33
37
  """Wraps focal loss around existing loss_fcn(), i.e. criteria = FocalLoss(nn.BCEWithLogitsLoss(), gamma=1.5)."""
34
38
 
35
39
  def __init__(self, ):
40
+ """Initializer for FocalLoss class with no parameters."""
36
41
  super().__init__()
37
42
 
38
43
  @staticmethod
@@ -93,6 +98,7 @@ class KeypointLoss(nn.Module):
93
98
  """Criterion class for computing training losses."""
94
99
 
95
100
  def __init__(self, sigmas) -> None:
101
+ """Initialize the KeypointLoss class."""
96
102
  super().__init__()
97
103
  self.sigmas = sigmas
98
104
 
@@ -206,7 +212,6 @@ class v8SegmentationLoss(v8DetectionLoss):
206
212
 
207
213
  def __init__(self, model): # model must be de-paralleled
208
214
  super().__init__(model)
209
- self.nm = model.model[-1].nm # number of masks
210
215
  self.overlap = model.args.overlap_mask
211
216
 
212
217
  def __call__(self, preds, batch):
@@ -262,38 +267,108 @@ class v8SegmentationLoss(v8DetectionLoss):
262
267
  if tuple(masks.shape[-2:]) != (mask_h, mask_w): # downsample
263
268
  masks = F.interpolate(masks[None], (mask_h, mask_w), mode='nearest')[0]
264
269
 
265
- for i in range(batch_size):
266
- if fg_mask[i].sum():
267
- mask_idx = target_gt_idx[i][fg_mask[i]]
268
- if self.overlap:
269
- gt_mask = torch.where(masks[[i]] == (mask_idx + 1).view(-1, 1, 1), 1.0, 0.0)
270
- else:
271
- gt_mask = masks[batch_idx.view(-1) == i][mask_idx]
272
- xyxyn = target_bboxes[i][fg_mask[i]] / imgsz[[1, 0, 1, 0]]
273
- marea = xyxy2xywh(xyxyn)[:, 2:].prod(1)
274
- mxyxy = xyxyn * torch.tensor([mask_w, mask_h, mask_w, mask_h], device=self.device)
275
- loss[1] += self.single_mask_loss(gt_mask, pred_masks[i][fg_mask[i]], proto[i], mxyxy, marea) # seg
276
-
277
- # WARNING: lines below prevents Multi-GPU DDP 'unused gradient' PyTorch errors, do not remove
278
- else:
279
- loss[1] += (proto * 0).sum() + (pred_masks * 0).sum() # inf sums may lead to nan loss
270
+ loss[1] = self.calculate_segmentation_loss(fg_mask, masks, target_gt_idx, target_bboxes, batch_idx, proto,
271
+ pred_masks, imgsz, self.overlap)
280
272
 
281
273
  # WARNING: lines below prevent Multi-GPU DDP 'unused gradient' PyTorch errors, do not remove
282
274
  else:
283
275
  loss[1] += (proto * 0).sum() + (pred_masks * 0).sum() # inf sums may lead to nan loss
284
276
 
285
277
  loss[0] *= self.hyp.box # box gain
286
- loss[1] *= self.hyp.box / batch_size # seg gain
278
+ loss[1] *= self.hyp.box # seg gain
287
279
  loss[2] *= self.hyp.cls # cls gain
288
280
  loss[3] *= self.hyp.dfl # dfl gain
289
281
 
290
282
  return loss.sum() * batch_size, loss.detach() # loss(box, cls, dfl)
291
283
 
292
- def single_mask_loss(self, gt_mask, pred, proto, xyxy, area):
293
- """Mask loss for one image."""
294
- pred_mask = (pred @ proto.view(self.nm, -1)).view(-1, *proto.shape[1:]) # (n, 32) @ (32,80,80) -> (n,80,80)
284
+ @staticmethod
285
+ def single_mask_loss(gt_mask: torch.Tensor, pred: torch.Tensor, proto: torch.Tensor, xyxy: torch.Tensor,
286
+ area: torch.Tensor) -> torch.Tensor:
287
+ """
288
+ Compute the instance segmentation loss for a single image.
289
+
290
+ Args:
291
+ gt_mask (torch.Tensor): Ground truth mask of shape (n, H, W), where n is the number of objects.
292
+ pred (torch.Tensor): Predicted mask coefficients of shape (n, 32).
293
+ proto (torch.Tensor): Prototype masks of shape (32, H, W).
294
+ xyxy (torch.Tensor): Ground truth bounding boxes in xyxy format, normalized to [0, 1], of shape (n, 4).
295
+ area (torch.Tensor): Area of each ground truth bounding box of shape (n,).
296
+
297
+ Returns:
298
+ (torch.Tensor): The calculated mask loss for a single image.
299
+
300
+ Notes:
301
+ The function uses the equation pred_mask = torch.einsum('in,nhw->ihw', pred, proto) to produce the
302
+ predicted masks from the prototype masks and predicted mask coefficients.
303
+ """
304
+ pred_mask = torch.einsum('in,nhw->ihw', pred, proto) # (n, 32) @ (32, 80, 80) -> (n, 80, 80)
295
305
  loss = F.binary_cross_entropy_with_logits(pred_mask, gt_mask, reduction='none')
296
- return (crop_mask(loss, xyxy).mean(dim=(1, 2)) / area).mean()
306
+ return (crop_mask(loss, xyxy).mean(dim=(1, 2)) / area).sum()
307
+
308
+ def calculate_segmentation_loss(
309
+ self,
310
+ fg_mask: torch.Tensor,
311
+ masks: torch.Tensor,
312
+ target_gt_idx: torch.Tensor,
313
+ target_bboxes: torch.Tensor,
314
+ batch_idx: torch.Tensor,
315
+ proto: torch.Tensor,
316
+ pred_masks: torch.Tensor,
317
+ imgsz: torch.Tensor,
318
+ overlap: bool,
319
+ ) -> torch.Tensor:
320
+ """
321
+ Calculate the loss for instance segmentation.
322
+
323
+ Args:
324
+ fg_mask (torch.Tensor): A binary tensor of shape (BS, N_anchors) indicating which anchors are positive.
325
+ masks (torch.Tensor): Ground truth masks of shape (BS, H, W) if `overlap` is False, otherwise (BS, ?, H, W).
326
+ target_gt_idx (torch.Tensor): Indexes of ground truth objects for each anchor of shape (BS, N_anchors).
327
+ target_bboxes (torch.Tensor): Ground truth bounding boxes for each anchor of shape (BS, N_anchors, 4).
328
+ batch_idx (torch.Tensor): Batch indices of shape (N_labels_in_batch, 1).
329
+ proto (torch.Tensor): Prototype masks of shape (BS, 32, H, W).
330
+ pred_masks (torch.Tensor): Predicted masks for each anchor of shape (BS, N_anchors, 32).
331
+ imgsz (torch.Tensor): Size of the input image as a tensor of shape (2), i.e., (H, W).
332
+ overlap (bool): Whether the masks in `masks` tensor overlap.
333
+
334
+ Returns:
335
+ (torch.Tensor): The calculated loss for instance segmentation.
336
+
337
+ Notes:
338
+ The batch loss can be computed for improved speed at higher memory usage.
339
+ For example, pred_mask can be computed as follows:
340
+ pred_mask = torch.einsum('in,nhw->ihw', pred, proto) # (i, 32) @ (32, 160, 160) -> (i, 160, 160)
341
+ """
342
+ _, _, mask_h, mask_w = proto.shape
343
+ loss = 0
344
+
345
+ # normalize to 0-1
346
+ target_bboxes_normalized = target_bboxes / imgsz[[1, 0, 1, 0]]
347
+
348
+ # areas of target bboxes
349
+ marea = xyxy2xywh(target_bboxes_normalized)[..., 2:].prod(2)
350
+
351
+ # normalize to mask size
352
+ mxyxy = target_bboxes_normalized * torch.tensor([mask_w, mask_h, mask_w, mask_h], device=proto.device)
353
+
354
+ for i, single_i in enumerate(zip(fg_mask, target_gt_idx, pred_masks, proto, mxyxy, marea, masks)):
355
+ fg_mask_i, target_gt_idx_i, pred_masks_i, proto_i, mxyxy_i, marea_i, masks_i = single_i
356
+ if fg_mask_i.any():
357
+ mask_idx = target_gt_idx_i[fg_mask_i]
358
+ if overlap:
359
+ gt_mask = masks_i == (mask_idx + 1).view(-1, 1, 1)
360
+ gt_mask = gt_mask.float()
361
+ else:
362
+ gt_mask = masks[batch_idx.view(-1) == i][mask_idx]
363
+
364
+ loss += self.single_mask_loss(gt_mask, pred_masks_i[fg_mask_i], proto_i, mxyxy_i[fg_mask_i],
365
+ marea_i[fg_mask_i])
366
+
367
+ # WARNING: lines below prevents Multi-GPU DDP 'unused gradient' PyTorch errors, do not remove
368
+ else:
369
+ loss += (proto * 0).sum() + (pred_masks * 0).sum() # inf sums may lead to nan loss
370
+
371
+ return loss / fg_mask.sum()
297
372
 
298
373
 
299
374
  class v8PoseLoss(v8DetectionLoss):
@@ -1,7 +1,6 @@
1
1
  # Ultralytics YOLO 🚀, AGPL-3.0 license
2
- """
3
- Model validation metrics
4
- """
2
+ """Model validation metrics."""
3
+
5
4
  import math
6
5
  import warnings
7
6
  from pathlib import Path
@@ -194,7 +193,7 @@ class ConfusionMatrix:
194
193
 
195
194
  def process_cls_preds(self, preds, targets):
196
195
  """
197
- Update confusion matrix for classification task
196
+ Update confusion matrix for classification task.
198
197
 
199
198
  Args:
200
199
  preds (Array[N, min(nc,5)]): Predicted class labels.
@@ -307,9 +306,7 @@ class ConfusionMatrix:
307
306
  on_plot(plot_fname)
308
307
 
309
308
  def print(self):
310
- """
311
- Print the confusion matrix to the console.
312
- """
309
+ """Print the confusion matrix to the console."""
313
310
  for i in range(self.nc + 1):
314
311
  LOGGER.info(' '.join(map(str, self.matrix[i])))
315
312
 
@@ -439,7 +436,6 @@ def ap_per_class(tp,
439
436
  f1 (np.ndarray): F1-score values at each confidence threshold.
440
437
  ap (np.ndarray): Average precision for each class at different IoU thresholds.
441
438
  unique_classes (np.ndarray): An array of unique classes that have data.
442
-
443
439
  """
444
440
 
445
441
  # Sort by objectness
@@ -497,32 +493,33 @@ def ap_per_class(tp,
497
493
 
498
494
  class Metric(SimpleClass):
499
495
  """
500
- Class for computing evaluation metrics for YOLOv8 model.
501
-
502
- Attributes:
503
- p (list): Precision for each class. Shape: (nc,).
504
- r (list): Recall for each class. Shape: (nc,).
505
- f1 (list): F1 score for each class. Shape: (nc,).
506
- all_ap (list): AP scores for all classes and all IoU thresholds. Shape: (nc, 10).
507
- ap_class_index (list): Index of class for each AP score. Shape: (nc,).
508
- nc (int): Number of classes.
509
-
510
- Methods:
511
- ap50(): AP at IoU threshold of 0.5 for all classes. Returns: List of AP scores. Shape: (nc,) or [].
512
- ap(): AP at IoU thresholds from 0.5 to 0.95 for all classes. Returns: List of AP scores. Shape: (nc,) or [].
513
- mp(): Mean precision of all classes. Returns: Float.
514
- mr(): Mean recall of all classes. Returns: Float.
515
- map50(): Mean AP at IoU threshold of 0.5 for all classes. Returns: Float.
516
- map75(): Mean AP at IoU threshold of 0.75 for all classes. Returns: Float.
517
- map(): Mean AP at IoU thresholds from 0.5 to 0.95 for all classes. Returns: Float.
518
- mean_results(): Mean of results, returns mp, mr, map50, map.
519
- class_result(i): Class-aware result, returns p[i], r[i], ap50[i], ap[i].
520
- maps(): mAP of each class. Returns: Array of mAP scores, shape: (nc,).
521
- fitness(): Model fitness as a weighted combination of metrics. Returns: Float.
522
- update(results): Update metric attributes with new evaluation results.
523
- """
496
+ Class for computing evaluation metrics for YOLOv8 model.
497
+
498
+ Attributes:
499
+ p (list): Precision for each class. Shape: (nc,).
500
+ r (list): Recall for each class. Shape: (nc,).
501
+ f1 (list): F1 score for each class. Shape: (nc,).
502
+ all_ap (list): AP scores for all classes and all IoU thresholds. Shape: (nc, 10).
503
+ ap_class_index (list): Index of class for each AP score. Shape: (nc,).
504
+ nc (int): Number of classes.
505
+
506
+ Methods:
507
+ ap50(): AP at IoU threshold of 0.5 for all classes. Returns: List of AP scores. Shape: (nc,) or [].
508
+ ap(): AP at IoU thresholds from 0.5 to 0.95 for all classes. Returns: List of AP scores. Shape: (nc,) or [].
509
+ mp(): Mean precision of all classes. Returns: Float.
510
+ mr(): Mean recall of all classes. Returns: Float.
511
+ map50(): Mean AP at IoU threshold of 0.5 for all classes. Returns: Float.
512
+ map75(): Mean AP at IoU threshold of 0.75 for all classes. Returns: Float.
513
+ map(): Mean AP at IoU thresholds from 0.5 to 0.95 for all classes. Returns: Float.
514
+ mean_results(): Mean of results, returns mp, mr, map50, map.
515
+ class_result(i): Class-aware result, returns p[i], r[i], ap50[i], ap[i].
516
+ maps(): mAP of each class. Returns: Array of mAP scores, shape: (nc,).
517
+ fitness(): Model fitness as a weighted combination of metrics. Returns: Float.
518
+ update(results): Update metric attributes with new evaluation results.
519
+ """
524
520
 
525
521
  def __init__(self) -> None:
522
+ """Initializes a Metric instance for computing evaluation metrics for the YOLOv8 model."""
526
523
  self.p = [] # (nc, )
527
524
  self.r = [] # (nc, )
528
525
  self.f1 = [] # (nc, )
@@ -605,12 +602,12 @@ class Metric(SimpleClass):
605
602
  return [self.mp, self.mr, self.map50, self.map]
606
603
 
607
604
  def class_result(self, i):
608
- """class-aware result, return p[i], r[i], ap50[i], ap[i]."""
605
+ """Class-aware result, return p[i], r[i], ap50[i], ap[i]."""
609
606
  return self.p[i], self.r[i], self.ap50[i], self.ap[i]
610
607
 
611
608
  @property
612
609
  def maps(self):
613
- """mAP of each class."""
610
+ """MAP of each class."""
614
611
  maps = np.zeros(self.nc) + self.map
615
612
  for i, c in enumerate(self.ap_class_index):
616
613
  maps[c] = self.ap[i]
@@ -671,6 +668,7 @@ class DetMetrics(SimpleClass):
671
668
  """
672
669
 
673
670
  def __init__(self, save_dir=Path('.'), plot=False, on_plot=None, names=()) -> None:
671
+ """Initialize a DetMetrics instance with a save directory, plot flag, callback function, and class names."""
674
672
  self.save_dir = save_dir
675
673
  self.plot = plot
676
674
  self.on_plot = on_plot
@@ -755,6 +753,7 @@ class SegmentMetrics(SimpleClass):
755
753
  """
756
754
 
757
755
  def __init__(self, save_dir=Path('.'), plot=False, on_plot=None, names=()) -> None:
756
+ """Initialize a SegmentMetrics instance with a save directory, plot flag, callback function, and class names."""
758
757
  self.save_dir = save_dir
759
758
  self.plot = plot
760
759
  self.on_plot = on_plot
@@ -864,6 +863,7 @@ class PoseMetrics(SegmentMetrics):
864
863
  """
865
864
 
866
865
  def __init__(self, save_dir=Path('.'), plot=False, on_plot=None, names=()) -> None:
866
+ """Initialize the PoseMetrics class with directory path, class names, and plotting options."""
867
867
  super().__init__(save_dir, plot, names)
868
868
  self.save_dir = save_dir
869
869
  self.plot = plot
@@ -953,6 +953,7 @@ class ClassifyMetrics(SimpleClass):
953
953
  """
954
954
 
955
955
  def __init__(self) -> None:
956
+ """Initialize a ClassifyMetrics instance."""
956
957
  self.top1 = 0
957
958
  self.top5 = 0
958
959
  self.speed = {'preprocess': 0.0, 'inference': 0.0, 'loss': 0.0, 'postprocess': 0.0}
ultralytics/utils/ops.py CHANGED
@@ -50,6 +50,7 @@ class Profile(contextlib.ContextDecorator):
50
50
  self.t += self.dt # accumulate dt
51
51
 
52
52
  def __str__(self):
53
+ """Returns a human-readable string representing the accumulated elapsed time in the profiler."""
53
54
  return f'Elapsed time is {self.t} s'
54
55
 
55
56
  def time(self):
@@ -303,7 +304,7 @@ def clip_coords(coords, shape):
303
304
 
304
305
  def scale_image(masks, im0_shape, ratio_pad=None):
305
306
  """
306
- Takes a mask, and resizes it to the original image size
307
+ Takes a mask, and resizes it to the original image size.
307
308
 
308
309
  Args:
309
310
  masks (np.ndarray): resized and padded masks/images, [h, w, num]/[h, w, 3].
@@ -403,8 +404,8 @@ def xywhn2xyxy(x, w=640, h=640, padw=0, padh=0):
403
404
 
404
405
  def xyxy2xywhn(x, w=640, h=640, clip=False, eps=0.0):
405
406
  """
406
- Convert bounding box coordinates from (x1, y1, x2, y2) format to (x, y, width, height, normalized) format.
407
- x, y, width and height are normalized to image dimensions
407
+ Convert bounding box coordinates from (x1, y1, x2, y2) format to (x, y, width, height, normalized) format. x, y,
408
+ width and height are normalized to image dimensions.
408
409
 
409
410
  Args:
410
411
  x (np.ndarray | torch.Tensor): The input bounding box coordinates in (x1, y1, x2, y2) format.
@@ -445,7 +446,7 @@ def xywh2ltwh(x):
445
446
 
446
447
  def xyxy2ltwh(x):
447
448
  """
448
- Convert nx4 bounding boxes from [x1, y1, x2, y2] to [x1, y1, w, h], where xy1=top-left, xy2=bottom-right
449
+ Convert nx4 bounding boxes from [x1, y1, x2, y2] to [x1, y1, w, h], where xy1=top-left, xy2=bottom-right.
449
450
 
450
451
  Args:
451
452
  x (np.ndarray | torch.Tensor): The input tensor with the bounding boxes coordinates in the xyxy format
@@ -461,7 +462,7 @@ def xyxy2ltwh(x):
461
462
 
462
463
  def ltwh2xywh(x):
463
464
  """
464
- Convert nx4 boxes from [x1, y1, w, h] to [x, y, w, h] where xy1=top-left, xy=center
465
+ Convert nx4 boxes from [x1, y1, w, h] to [x, y, w, h] where xy1=top-left, xy=center.
465
466
 
466
467
  Args:
467
468
  x (torch.Tensor): the input tensor
@@ -544,7 +545,7 @@ def xywhr2xyxyxyxy(center):
544
545
 
545
546
  def ltwh2xyxy(x):
546
547
  """
547
- It converts the bounding box from [x1, y1, w, h] to [x1, y1, x2, y2] where xy1=top-left, xy2=bottom-right
548
+ It converts the bounding box from [x1, y1, w, h] to [x1, y1, x2, y2] where xy1=top-left, xy2=bottom-right.
548
549
 
549
550
  Args:
550
551
  x (np.ndarray | torch.Tensor): the input image
@@ -616,8 +617,8 @@ def crop_mask(masks, boxes):
616
617
 
617
618
  def process_mask_upsample(protos, masks_in, bboxes, shape):
618
619
  """
619
- Takes the output of the mask head, and applies the mask to the bounding boxes. This produces masks of higher
620
- quality but is slower.
620
+ Takes the output of the mask head, and applies the mask to the bounding boxes. This produces masks of higher quality
621
+ but is slower.
621
622
 
622
623
  Args:
623
624
  protos (torch.Tensor): [mask_dim, mask_h, mask_w]
@@ -713,7 +714,7 @@ def scale_masks(masks, shape, padding=True):
713
714
 
714
715
  def scale_coords(img1_shape, coords, img0_shape, ratio_pad=None, normalize=False, padding=True):
715
716
  """
716
- Rescale segment coordinates (xy) from img1_shape to img0_shape
717
+ Rescale segment coordinates (xy) from img1_shape to img0_shape.
717
718
 
718
719
  Args:
719
720
  img1_shape (tuple): The shape of the image that the coords are from.
@@ -1,7 +1,5 @@
1
1
  # Ultralytics YOLO 🚀, AGPL-3.0 license
2
- """
3
- Monkey patches to update/extend functionality of existing functions
4
- """
2
+ """Monkey patches to update/extend functionality of existing functions."""
5
3
 
6
4
  from pathlib import Path
7
5
 
@@ -14,7 +12,8 @@ _imshow = cv2.imshow # copy to avoid recursion errors
14
12
 
15
13
 
16
14
  def imread(filename: str, flags: int = cv2.IMREAD_COLOR):
17
- """Read an image from a file.
15
+ """
16
+ Read an image from a file.
18
17
 
19
18
  Args:
20
19
  filename (str): Path to the file to read.
@@ -27,7 +26,8 @@ def imread(filename: str, flags: int = cv2.IMREAD_COLOR):
27
26
 
28
27
 
29
28
  def imwrite(filename: str, img: np.ndarray, params=None):
30
- """Write an image to a file.
29
+ """
30
+ Write an image to a file.
31
31
 
32
32
  Args:
33
33
  filename (str): Path to the file to write.
@@ -45,7 +45,8 @@ def imwrite(filename: str, img: np.ndarray, params=None):
45
45
 
46
46
 
47
47
  def imshow(winname: str, mat: np.ndarray):
48
- """Displays an image in the specified window.
48
+ """
49
+ Displays an image in the specified window.
49
50
 
50
51
  Args:
51
52
  winname (str): Name of the window.
@@ -59,7 +60,8 @@ _torch_save = torch.save # copy to avoid recursion errors
59
60
 
60
61
 
61
62
  def torch_save(*args, **kwargs):
62
- """Use dill (if exists) to serialize the lambda functions where pickle does not do this.
63
+ """
64
+ Use dill (if exists) to serialize the lambda functions where pickle does not do this.
63
65
 
64
66
  Args:
65
67
  *args (tuple): Positional arguments to pass to torch.save.
@@ -155,12 +155,12 @@ class Annotator:
155
155
  masks = masks.unsqueeze(3) # shape(n,h,w,1)
156
156
  masks_color = masks * (colors * alpha) # shape(n,h,w,3)
157
157
 
158
- inv_alph_masks = (1 - masks * alpha).cumprod(0) # shape(n,h,w,1)
158
+ inv_alpha_masks = (1 - masks * alpha).cumprod(0) # shape(n,h,w,1)
159
159
  mcs = masks_color.max(dim=0).values # shape(n,h,w,3)
160
160
 
161
161
  im_gpu = im_gpu.flip(dims=[0]) # flip channel
162
162
  im_gpu = im_gpu.permute(1, 2, 0).contiguous() # shape(h,w,3)
163
- im_gpu = im_gpu * inv_alph_masks[-1] + mcs
163
+ im_gpu = im_gpu * inv_alpha_masks[-1] + mcs
164
164
  im_mask = (im_gpu * 255)
165
165
  im_mask_np = im_mask.byte().cpu().numpy()
166
166
  self.im[:] = im_mask_np if retina_masks else ops.scale_image(im_mask_np, self.im.shape)
@@ -316,7 +316,8 @@ def plot_labels(boxes, cls, names=(), save_dir=Path(''), on_plot=None):
316
316
 
317
317
 
318
318
  def save_one_box(xyxy, im, file=Path('im.jpg'), gain=1.02, pad=10, square=False, BGR=False, save=True):
319
- """Save image crop as {file} with crop size multiple {gain} and {pad} pixels. Save and/or return crop.
319
+ """
320
+ Save image crop as {file} with crop size multiple {gain} and {pad} pixels. Save and/or return crop.
320
321
 
321
322
  This function takes a bounding box and an image, and then saves a cropped portion of the image according
322
323
  to the bounding box. Optionally, the crop can be squared, and the function allows for gain and padding
@@ -205,7 +205,11 @@ def fuse_deconv_and_bn(deconv, bn):
205
205
 
206
206
 
207
207
  def model_info(model, detailed=False, verbose=True, imgsz=640):
208
- """Model information. imgsz may be int or list, i.e. imgsz=640 or imgsz=[640, 320]."""
208
+ """
209
+ Model information.
210
+
211
+ imgsz may be int or list, i.e. imgsz=640 or imgsz=[640, 320].
212
+ """
209
213
  if not verbose:
210
214
  return
211
215
  n_p = get_num_params(model) # number of parameters
@@ -517,13 +521,11 @@ def profile(input, ops, n=10, device=None):
517
521
 
518
522
 
519
523
  class EarlyStopping:
520
- """
521
- Early stopping class that stops training when a specified number of epochs have passed without improvement.
522
- """
524
+ """Early stopping class that stops training when a specified number of epochs have passed without improvement."""
523
525
 
524
526
  def __init__(self, patience=50):
525
527
  """
526
- Initialize early stopping object
528
+ Initialize early stopping object.
527
529
 
528
530
  Args:
529
531
  patience (int, optional): Number of epochs to wait after fitness stops improving before stopping.
@@ -535,7 +537,7 @@ class EarlyStopping:
535
537
 
536
538
  def __call__(self, epoch, fitness):
537
539
  """
538
- Check whether to stop training
540
+ Check whether to stop training.
539
541
 
540
542
  Args:
541
543
  epoch (int): Current epoch of training
@@ -0,0 +1,87 @@
1
+ # Ultralytics YOLO 🚀, AGPL-3.0 license
2
+
3
+ from typing import List
4
+ from urllib.parse import urlsplit
5
+
6
+ import numpy as np
7
+
8
+
9
+ class TritonRemoteModel:
10
+ """
11
+ Client for interacting with a remote Triton Inference Server model.
12
+
13
+ Attributes:
14
+ endpoint (str): The name of the model on the Triton server.
15
+ url (str): The URL of the Triton server.
16
+ triton_client: The Triton client (either HTTP or gRPC).
17
+ InferInput: The input class for the Triton client.
18
+ InferRequestedOutput: The output request class for the Triton client.
19
+ input_formats (List[str]): The data types of the model inputs.
20
+ np_input_formats (List[type]): The numpy data types of the model inputs.
21
+ input_names (List[str]): The names of the model inputs.
22
+ output_names (List[str]): The names of the model outputs.
23
+ """
24
+
25
+ def __init__(self, url: str, endpoint: str = '', scheme: str = ''):
26
+ """
27
+ Initialize the TritonRemoteModel.
28
+
29
+ Arguments may be provided individually or parsed from a collective 'url' argument of the form
30
+ <scheme>://<netloc>/<endpoint>/<task_name>
31
+
32
+ Args:
33
+ url (str): The URL of the Triton server.
34
+ endpoint (str): The name of the model on the Triton server.
35
+ scheme (str): The communication scheme ('http' or 'grpc').
36
+ """
37
+ if not endpoint and not scheme: # Parse all args from URL string
38
+ splits = urlsplit(url)
39
+ endpoint = splits.path.strip('/').split('/')[0]
40
+ scheme = splits.scheme
41
+ url = splits.netloc
42
+
43
+ self.endpoint = endpoint
44
+ self.url = url
45
+
46
+ # Choose the Triton client based on the communication scheme
47
+ if scheme == 'http':
48
+ import tritonclient.http as client # noqa
49
+ self.triton_client = client.InferenceServerClient(url=self.url, verbose=False, ssl=False)
50
+ config = self.triton_client.get_model_config(endpoint)
51
+ else:
52
+ import tritonclient.grpc as client # noqa
53
+ self.triton_client = client.InferenceServerClient(url=self.url, verbose=False, ssl=False)
54
+ config = self.triton_client.get_model_config(endpoint, as_json=True)['config']
55
+
56
+ self.InferRequestedOutput = client.InferRequestedOutput
57
+ self.InferInput = client.InferInput
58
+
59
+ type_map = {'TYPE_FP32': np.float32, 'TYPE_FP16': np.float16, 'TYPE_UINT8': np.uint8}
60
+ self.input_formats = [x['data_type'] for x in config['input']]
61
+ self.np_input_formats = [type_map[x] for x in self.input_formats]
62
+ self.input_names = [x['name'] for x in config['input']]
63
+ self.output_names = [x['name'] for x in config['output']]
64
+
65
+ def __call__(self, *inputs: np.ndarray) -> List[np.ndarray]:
66
+ """
67
+ Call the model with the given inputs.
68
+
69
+ Args:
70
+ *inputs (List[np.ndarray]): Input data to the model.
71
+
72
+ Returns:
73
+ List[np.ndarray]: Model outputs.
74
+ """
75
+ infer_inputs = []
76
+ input_format = inputs[0].dtype
77
+ for i, x in enumerate(inputs):
78
+ if x.dtype != self.np_input_formats[i]:
79
+ x = x.astype(self.np_input_formats[i])
80
+ infer_input = self.InferInput(self.input_names[i], [*x.shape], self.input_formats[i].replace('TYPE_', ''))
81
+ infer_input.set_data_from_numpy(x)
82
+ infer_inputs.append(infer_input)
83
+
84
+ infer_outputs = [self.InferRequestedOutput(output_name) for output_name in self.output_names]
85
+ outputs = self.triton_client.infer(model_name=self.endpoint, inputs=infer_inputs, outputs=infer_outputs)
86
+
87
+ return [outputs.as_numpy(output_name).astype(input_format) for output_name in self.output_names]