ultralytics 8.0.196__py3-none-any.whl → 8.0.198__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 (49) hide show
  1. ultralytics/__init__.py +1 -1
  2. ultralytics/cfg/__init__.py +4 -5
  3. ultralytics/data/augment.py +2 -2
  4. ultralytics/data/converter.py +12 -13
  5. ultralytics/data/dataset.py +1 -1
  6. ultralytics/engine/__init__.py +1 -0
  7. ultralytics/engine/exporter.py +1 -1
  8. ultralytics/engine/trainer.py +2 -1
  9. ultralytics/hub/session.py +1 -1
  10. ultralytics/models/fastsam/predict.py +33 -2
  11. ultralytics/models/fastsam/prompt.py +38 -1
  12. ultralytics/models/fastsam/utils.py +5 -5
  13. ultralytics/models/fastsam/val.py +27 -1
  14. ultralytics/models/nas/model.py +20 -0
  15. ultralytics/models/nas/predict.py +23 -0
  16. ultralytics/models/nas/val.py +24 -0
  17. ultralytics/models/rtdetr/val.py +17 -5
  18. ultralytics/models/sam/modules/decoders.py +26 -1
  19. ultralytics/models/sam/modules/encoders.py +31 -3
  20. ultralytics/models/sam/modules/sam.py +22 -7
  21. ultralytics/models/sam/modules/tiny_encoder.py +147 -45
  22. ultralytics/models/sam/modules/transformer.py +47 -2
  23. ultralytics/models/sam/predict.py +19 -2
  24. ultralytics/models/utils/loss.py +20 -2
  25. ultralytics/models/utils/ops.py +5 -5
  26. ultralytics/nn/modules/block.py +33 -10
  27. ultralytics/nn/modules/conv.py +16 -4
  28. ultralytics/nn/modules/head.py +48 -17
  29. ultralytics/nn/modules/transformer.py +2 -2
  30. ultralytics/nn/tasks.py +7 -7
  31. ultralytics/utils/__init__.py +2 -1
  32. ultralytics/utils/benchmarks.py +13 -0
  33. ultralytics/utils/callbacks/mlflow.py +76 -36
  34. ultralytics/utils/callbacks/wb.py +92 -1
  35. ultralytics/utils/checks.py +4 -4
  36. ultralytics/utils/errors.py +12 -0
  37. ultralytics/utils/files.py +1 -1
  38. ultralytics/utils/instance.py +41 -3
  39. ultralytics/utils/loss.py +22 -19
  40. ultralytics/utils/metrics.py +106 -24
  41. ultralytics/utils/tal.py +1 -1
  42. ultralytics/utils/torch_utils.py +4 -2
  43. ultralytics/utils/tuner.py +10 -4
  44. {ultralytics-8.0.196.dist-info → ultralytics-8.0.198.dist-info}/METADATA +1 -1
  45. {ultralytics-8.0.196.dist-info → ultralytics-8.0.198.dist-info}/RECORD +49 -49
  46. {ultralytics-8.0.196.dist-info → ultralytics-8.0.198.dist-info}/LICENSE +0 -0
  47. {ultralytics-8.0.196.dist-info → ultralytics-8.0.198.dist-info}/WHEEL +0 -0
  48. {ultralytics-8.0.196.dist-info → ultralytics-8.0.198.dist-info}/entry_points.txt +0 -0
  49. {ultralytics-8.0.196.dist-info → ultralytics-8.0.198.dist-info}/top_level.txt +0 -0
@@ -8,7 +8,10 @@ try:
8
8
  assert SETTINGS['wandb'] is True # verify integration is enabled
9
9
  import wandb as wb
10
10
 
11
- assert hasattr(wb, '__version__')
11
+ assert hasattr(wb, '__version__') # verify package is not directory
12
+
13
+ import numpy as np
14
+ import pandas as pd
12
15
 
13
16
  _processed_plots = {}
14
17
 
@@ -16,6 +19,83 @@ except (ImportError, AssertionError):
16
19
  wb = None
17
20
 
18
21
 
22
+ def _custom_table(x, y, classes, title='Precision Recall Curve', x_title='Recall', y_title='Precision'):
23
+ """
24
+ Create and log a custom metric visualization to wandb.plot.pr_curve.
25
+
26
+ This function crafts a custom metric visualization that mimics the behavior of wandb's default precision-recall curve
27
+ while allowing for enhanced customization. The visual metric is useful for monitoring model performance across different classes.
28
+
29
+ Args:
30
+ x (List): Values for the x-axis; expected to have length N.
31
+ y (List): Corresponding values for the y-axis; also expected to have length N.
32
+ classes (List): Labels identifying the class of each point; length N.
33
+ title (str, optional): Title for the plot; defaults to 'Precision Recall Curve'.
34
+ x_title (str, optional): Label for the x-axis; defaults to 'Recall'.
35
+ y_title (str, optional): Label for the y-axis; defaults to 'Precision'.
36
+
37
+ Returns:
38
+ (wandb.Object): A wandb object suitable for logging, showcasing the crafted metric visualization.
39
+ """
40
+ df = pd.DataFrame({'class': classes, 'y': y, 'x': x}).round(3)
41
+ fields = {'x': 'x', 'y': 'y', 'class': 'class'}
42
+ string_fields = {'title': title, 'x-axis-title': x_title, 'y-axis-title': y_title}
43
+ return wb.plot_table('wandb/area-under-curve/v0',
44
+ wb.Table(dataframe=df),
45
+ fields=fields,
46
+ string_fields=string_fields)
47
+
48
+
49
+ def _plot_curve(x,
50
+ y,
51
+ names=None,
52
+ id='precision-recall',
53
+ title='Precision Recall Curve',
54
+ x_title='Recall',
55
+ y_title='Precision',
56
+ num_x=100,
57
+ only_mean=False):
58
+ """
59
+ Log a metric curve visualization.
60
+
61
+ This function generates a metric curve based on input data and logs the visualization to wandb.
62
+ The curve can represent aggregated data (mean) or individual class data, depending on the 'only_mean' flag.
63
+
64
+ Args:
65
+ x (np.ndarray): Data points for the x-axis with length N.
66
+ y (np.ndarray): Corresponding data points for the y-axis with shape CxN, where C represents the number of classes.
67
+ names (list, optional): Names of the classes corresponding to the y-axis data; length C. Defaults to an empty list.
68
+ id (str, optional): Unique identifier for the logged data in wandb. Defaults to 'precision-recall'.
69
+ title (str, optional): Title for the visualization plot. Defaults to 'Precision Recall Curve'.
70
+ x_title (str, optional): Label for the x-axis. Defaults to 'Recall'.
71
+ y_title (str, optional): Label for the y-axis. Defaults to 'Precision'.
72
+ num_x (int, optional): Number of interpolated data points for visualization. Defaults to 100.
73
+ only_mean (bool, optional): Flag to indicate if only the mean curve should be plotted. Defaults to True.
74
+
75
+ Note:
76
+ The function leverages the '_custom_table' function to generate the actual visualization.
77
+ """
78
+ # Create new x
79
+ if names is None:
80
+ names = []
81
+ x_new = np.linspace(x[0], x[-1], num_x).round(5)
82
+
83
+ # Create arrays for logging
84
+ x_log = x_new.tolist()
85
+ y_log = np.interp(x_new, x, np.mean(y, axis=0)).round(3).tolist()
86
+
87
+ if only_mean:
88
+ table = wb.Table(data=list(zip(x_log, y_log)), columns=[x_title, y_title])
89
+ wb.run.log({title: wb.plot.line(table, x_title, y_title, title=title)})
90
+ else:
91
+ classes = ['mean'] * len(x_log)
92
+ for i, yi in enumerate(y):
93
+ x_log.extend(x_new) # add new x
94
+ y_log.extend(np.interp(x_new, x, yi)) # interpolate y to new x
95
+ classes.extend([names[i]] * len(x_new)) # add class names
96
+ wb.log({id: _custom_table(x_log, y_log, classes, title, x_title, y_title)}, commit=False)
97
+
98
+
19
99
  def _log_plots(plots, step):
20
100
  """Logs plots from the input dictionary if they haven't been logged already at the specified step."""
21
101
  for name, params in plots.items():
@@ -55,6 +135,17 @@ def on_train_end(trainer):
55
135
  if trainer.best.exists():
56
136
  art.add_file(trainer.best)
57
137
  wb.run.log_artifact(art, aliases=['best'])
138
+ for curve_name, curve_values in zip(trainer.validator.metrics.curves, trainer.validator.metrics.curves_results):
139
+ x, y, x_title, y_title = curve_values
140
+ _plot_curve(
141
+ x,
142
+ y,
143
+ names=list(trainer.validator.metrics.names.values()),
144
+ id=f'curves/{curve_name}',
145
+ title=curve_name,
146
+ x_title=x_title,
147
+ y_title=y_title,
148
+ )
58
149
  wb.run.finish() # required or run continues on dashboard
59
150
 
60
151
 
@@ -165,16 +165,16 @@ def check_version(current: str = '0.0.0',
165
165
 
166
166
  Example:
167
167
  ```python
168
- # check if current version is exactly 22.04
168
+ # Check if current version is exactly 22.04
169
169
  check_version(current='22.04', required='==22.04')
170
170
 
171
- # check if current version is greater than or equal to 22.04
171
+ # Check if current version is greater than or equal to 22.04
172
172
  check_version(current='22.10', required='22.04') # assumes '>=' inequality if none passed
173
173
 
174
- # check if current version is less than or equal to 22.04
174
+ # Check if current version is less than or equal to 22.04
175
175
  check_version(current='22.04', required='<=22.04')
176
176
 
177
- # check if current version is between 20.04 (inclusive) and 22.04 (exclusive)
177
+ # Check if current version is between 20.04 (inclusive) and 22.04 (exclusive)
178
178
  check_version(current='21.10', required='>20.04,<22.04')
179
179
  ```
180
180
  """
@@ -4,6 +4,18 @@ from ultralytics.utils import emojis
4
4
 
5
5
 
6
6
  class HUBModelError(Exception):
7
+ """
8
+ Custom exception class for handling errors related to model fetching in Ultralytics YOLO.
9
+
10
+ This exception is raised when a requested model is not found or cannot be retrieved.
11
+ The message is also processed to include emojis for better user experience.
12
+
13
+ Attributes:
14
+ message (str): The error message displayed when the exception is raised.
15
+
16
+ Note:
17
+ The message is automatically processed through the 'emojis' function from the 'ultralytics.utils' package.
18
+ """
7
19
 
8
20
  def __init__(self, message='Model not found. Please check model URL and try again.'):
9
21
  """Create an exception for when a model is not found."""
@@ -45,7 +45,7 @@ def spaces_in_path(path):
45
45
  with ultralytics.utils.files import spaces_in_path
46
46
 
47
47
  with spaces_in_path('/path/with spaces') as new_path:
48
- # your code here
48
+ # Your code here
49
49
  ```
50
50
  """
51
51
 
@@ -33,9 +33,17 @@ __all__ = 'Bboxes', # tuple or list
33
33
 
34
34
  class Bboxes:
35
35
  """
36
- Bounding Boxes class.
36
+ A class for handling bounding boxes.
37
37
 
38
- Only numpy variables are supported.
38
+ The class supports various bounding box formats like 'xyxy', 'xywh', and 'ltwh'.
39
+ Bounding box data should be provided in numpy arrays.
40
+
41
+ Attributes:
42
+ bboxes (numpy.ndarray): The bounding boxes stored in a 2D numpy array.
43
+ format (str): The format of the bounding boxes ('xyxy', 'xywh', or 'ltwh').
44
+
45
+ Note:
46
+ This class does not handle normalization or denormalization of bounding boxes.
39
47
  """
40
48
 
41
49
  def __init__(self, bboxes, format='xyxy') -> None:
@@ -166,6 +174,36 @@ class Bboxes:
166
174
 
167
175
 
168
176
  class Instances:
177
+ """
178
+ Container for bounding boxes, segments, and keypoints of detected objects in an image.
179
+
180
+ Attributes:
181
+ _bboxes (Bboxes): Internal object for handling bounding box operations.
182
+ keypoints (ndarray): keypoints(x, y, visible) with shape [N, 17, 3]. Default is None.
183
+ normalized (bool): Flag indicating whether the bounding box coordinates are normalized.
184
+ segments (ndarray): Segments array with shape [N, 1000, 2] after resampling.
185
+
186
+ Args:
187
+ bboxes (ndarray): An array of bounding boxes with shape [N, 4].
188
+ segments (list | ndarray, optional): A list or array of object segments. Default is None.
189
+ keypoints (ndarray, optional): An array of keypoints with shape [N, 17, 3]. Default is None.
190
+ bbox_format (str, optional): The format of bounding boxes ('xywh' or 'xyxy'). Default is 'xywh'.
191
+ normalized (bool, optional): Whether the bounding box coordinates are normalized. Default is True.
192
+
193
+ Examples:
194
+ ```python
195
+ # Create an Instances object
196
+ instances = Instances(
197
+ bboxes=np.array([[10, 10, 30, 30], [20, 20, 40, 40]]),
198
+ segments=[np.array([[5, 5], [10, 10]]), np.array([[15, 15], [20, 20]])],
199
+ keypoints=np.array([[[5, 5, 1], [10, 10, 1]], [[15, 15, 1], [20, 20, 1]]])
200
+ )
201
+ ```
202
+
203
+ Note:
204
+ The bounding box format is either 'xywh' or 'xyxy', and is determined by the `bbox_format` argument.
205
+ This class does not perform input validation, and it assumes the inputs are well-formed.
206
+ """
169
207
 
170
208
  def __init__(self, bboxes, segments=None, keypoints=None, bbox_format='xywh', normalized=True) -> None:
171
209
  """
@@ -181,7 +219,7 @@ class Instances:
181
219
  self.normalized = normalized
182
220
 
183
221
  if len(segments) > 0:
184
- # list[np.array(1000, 2)] * num_samples
222
+ # List[np.array(1000, 2)] * num_samples
185
223
  segments = resample_segments(segments)
186
224
  # (N, 1000, 2)
187
225
  segments = np.stack(segments, axis=0)
ultralytics/utils/loss.py CHANGED
@@ -59,6 +59,7 @@ class FocalLoss(nn.Module):
59
59
 
60
60
 
61
61
  class BboxLoss(nn.Module):
62
+ """Criterion class for computing training losses during training."""
62
63
 
63
64
  def __init__(self, reg_max, use_dfl=False):
64
65
  """Initialize the BboxLoss module with regularization maximum and DFL settings."""
@@ -115,7 +116,7 @@ class v8DetectionLoss:
115
116
  """Criterion class for computing training losses."""
116
117
 
117
118
  def __init__(self, model): # model must be de-paralleled
118
-
119
+ """Initializes v8DetectionLoss with the model, defining model-related properties and BCE loss function."""
119
120
  device = next(model.parameters()).device # get model device
120
121
  h = model.args # hyperparameters
121
122
 
@@ -175,13 +176,13 @@ class v8DetectionLoss:
175
176
  imgsz = torch.tensor(feats[0].shape[2:], device=self.device, dtype=dtype) * self.stride[0] # image size (h,w)
176
177
  anchor_points, stride_tensor = make_anchors(feats, self.stride, 0.5)
177
178
 
178
- # targets
179
+ # Targets
179
180
  targets = torch.cat((batch['batch_idx'].view(-1, 1), batch['cls'].view(-1, 1), batch['bboxes']), 1)
180
181
  targets = self.preprocess(targets.to(self.device), batch_size, scale_tensor=imgsz[[1, 0, 1, 0]])
181
182
  gt_labels, gt_bboxes = targets.split((1, 4), 2) # cls, xyxy
182
183
  mask_gt = gt_bboxes.sum(2, keepdim=True).gt_(0)
183
184
 
184
- # pboxes
185
+ # Pboxes
185
186
  pred_bboxes = self.bbox_decode(anchor_points, pred_distri) # xyxy, (b, h*w, 4)
186
187
 
187
188
  _, target_bboxes, target_scores, fg_mask, _ = self.assigner(
@@ -190,11 +191,11 @@ class v8DetectionLoss:
190
191
 
191
192
  target_scores_sum = max(target_scores.sum(), 1)
192
193
 
193
- # cls loss
194
+ # Cls loss
194
195
  # loss[1] = self.varifocal_loss(pred_scores, target_scores, target_labels) / target_scores_sum # VFL way
195
196
  loss[1] = self.bce(pred_scores, target_scores.to(dtype)).sum() / target_scores_sum # BCE
196
197
 
197
- # bbox loss
198
+ # Bbox loss
198
199
  if fg_mask.sum():
199
200
  target_bboxes /= stride_tensor
200
201
  loss[0], loss[2] = self.bbox_loss(pred_distri, pred_bboxes, anchor_points, target_bboxes, target_scores,
@@ -211,6 +212,7 @@ class v8SegmentationLoss(v8DetectionLoss):
211
212
  """Criterion class for computing training losses."""
212
213
 
213
214
  def __init__(self, model): # model must be de-paralleled
215
+ """Initializes the v8SegmentationLoss class, taking a de-paralleled model as argument."""
214
216
  super().__init__(model)
215
217
  self.overlap = model.args.overlap_mask
216
218
 
@@ -222,7 +224,7 @@ class v8SegmentationLoss(v8DetectionLoss):
222
224
  pred_distri, pred_scores = torch.cat([xi.view(feats[0].shape[0], self.no, -1) for xi in feats], 2).split(
223
225
  (self.reg_max * 4, self.nc), 1)
224
226
 
225
- # b, grids, ..
227
+ # B, grids, ..
226
228
  pred_scores = pred_scores.permute(0, 2, 1).contiguous()
227
229
  pred_distri = pred_distri.permute(0, 2, 1).contiguous()
228
230
  pred_masks = pred_masks.permute(0, 2, 1).contiguous()
@@ -231,7 +233,7 @@ class v8SegmentationLoss(v8DetectionLoss):
231
233
  imgsz = torch.tensor(feats[0].shape[2:], device=self.device, dtype=dtype) * self.stride[0] # image size (h,w)
232
234
  anchor_points, stride_tensor = make_anchors(feats, self.stride, 0.5)
233
235
 
234
- # targets
236
+ # Targets
235
237
  try:
236
238
  batch_idx = batch['batch_idx'].view(-1, 1)
237
239
  targets = torch.cat((batch_idx, batch['cls'].view(-1, 1), batch['bboxes']), 1)
@@ -245,7 +247,7 @@ class v8SegmentationLoss(v8DetectionLoss):
245
247
  "correctly formatted 'segment' dataset using 'data=coco128-seg.yaml' "
246
248
  'as an example.\nSee https://docs.ultralytics.com/tasks/segment/ for help.') from e
247
249
 
248
- # pboxes
250
+ # Pboxes
249
251
  pred_bboxes = self.bbox_decode(anchor_points, pred_distri) # xyxy, (b, h*w, 4)
250
252
 
251
253
  _, target_bboxes, target_scores, fg_mask, target_gt_idx = self.assigner(
@@ -254,15 +256,15 @@ class v8SegmentationLoss(v8DetectionLoss):
254
256
 
255
257
  target_scores_sum = max(target_scores.sum(), 1)
256
258
 
257
- # cls loss
259
+ # Cls loss
258
260
  # loss[1] = self.varifocal_loss(pred_scores, target_scores, target_labels) / target_scores_sum # VFL way
259
261
  loss[2] = self.bce(pred_scores, target_scores.to(dtype)).sum() / target_scores_sum # BCE
260
262
 
261
263
  if fg_mask.sum():
262
- # bbox loss
264
+ # Bbox loss
263
265
  loss[0], loss[3] = self.bbox_loss(pred_distri, pred_bboxes, anchor_points, target_bboxes / stride_tensor,
264
266
  target_scores, target_scores_sum, fg_mask)
265
- # masks loss
267
+ # Masks loss
266
268
  masks = batch['masks'].to(self.device).float()
267
269
  if tuple(masks.shape[-2:]) != (mask_h, mask_w): # downsample
268
270
  masks = F.interpolate(masks[None], (mask_h, mask_w), mode='nearest')[0]
@@ -342,13 +344,13 @@ class v8SegmentationLoss(v8DetectionLoss):
342
344
  _, _, mask_h, mask_w = proto.shape
343
345
  loss = 0
344
346
 
345
- # normalize to 0-1
347
+ # Normalize to 0-1
346
348
  target_bboxes_normalized = target_bboxes / imgsz[[1, 0, 1, 0]]
347
349
 
348
- # areas of target bboxes
350
+ # Areas of target bboxes
349
351
  marea = xyxy2xywh(target_bboxes_normalized)[..., 2:].prod(2)
350
352
 
351
- # normalize to mask size
353
+ # Normalize to mask size
352
354
  mxyxy = target_bboxes_normalized * torch.tensor([mask_w, mask_h, mask_w, mask_h], device=proto.device)
353
355
 
354
356
  for i, single_i in enumerate(zip(fg_mask, target_gt_idx, pred_masks, proto, mxyxy, marea, masks)):
@@ -375,6 +377,7 @@ class v8PoseLoss(v8DetectionLoss):
375
377
  """Criterion class for computing training losses."""
376
378
 
377
379
  def __init__(self, model): # model must be de-paralleled
380
+ """Initializes v8PoseLoss with model, sets keypoint variables and declares a keypoint loss instance."""
378
381
  super().__init__(model)
379
382
  self.kpt_shape = model.model[-1].kpt_shape
380
383
  self.bce_pose = nn.BCEWithLogitsLoss()
@@ -390,7 +393,7 @@ class v8PoseLoss(v8DetectionLoss):
390
393
  pred_distri, pred_scores = torch.cat([xi.view(feats[0].shape[0], self.no, -1) for xi in feats], 2).split(
391
394
  (self.reg_max * 4, self.nc), 1)
392
395
 
393
- # b, grids, ..
396
+ # B, grids, ..
394
397
  pred_scores = pred_scores.permute(0, 2, 1).contiguous()
395
398
  pred_distri = pred_distri.permute(0, 2, 1).contiguous()
396
399
  pred_kpts = pred_kpts.permute(0, 2, 1).contiguous()
@@ -399,7 +402,7 @@ class v8PoseLoss(v8DetectionLoss):
399
402
  imgsz = torch.tensor(feats[0].shape[2:], device=self.device, dtype=dtype) * self.stride[0] # image size (h,w)
400
403
  anchor_points, stride_tensor = make_anchors(feats, self.stride, 0.5)
401
404
 
402
- # targets
405
+ # Targets
403
406
  batch_size = pred_scores.shape[0]
404
407
  batch_idx = batch['batch_idx'].view(-1, 1)
405
408
  targets = torch.cat((batch_idx, batch['cls'].view(-1, 1), batch['bboxes']), 1)
@@ -407,7 +410,7 @@ class v8PoseLoss(v8DetectionLoss):
407
410
  gt_labels, gt_bboxes = targets.split((1, 4), 2) # cls, xyxy
408
411
  mask_gt = gt_bboxes.sum(2, keepdim=True).gt_(0)
409
412
 
410
- # pboxes
413
+ # Pboxes
411
414
  pred_bboxes = self.bbox_decode(anchor_points, pred_distri) # xyxy, (b, h*w, 4)
412
415
  pred_kpts = self.kpts_decode(anchor_points, pred_kpts.view(batch_size, -1, *self.kpt_shape)) # (b, h*w, 17, 3)
413
416
 
@@ -417,11 +420,11 @@ class v8PoseLoss(v8DetectionLoss):
417
420
 
418
421
  target_scores_sum = max(target_scores.sum(), 1)
419
422
 
420
- # cls loss
423
+ # Cls loss
421
424
  # loss[1] = self.varifocal_loss(pred_scores, target_scores, target_labels) / target_scores_sum # VFL way
422
425
  loss[3] = self.bce(pred_scores, target_scores.to(dtype)).sum() / target_scores_sum # BCE
423
426
 
424
- # bbox loss
427
+ # Bbox loss
425
428
  if fg_mask.sum():
426
429
  target_bboxes /= stride_tensor
427
430
  loss[0], loss[4] = self.bbox_loss(pred_distri, pred_bboxes, anchor_points, target_bboxes, target_scores,
@@ -36,7 +36,7 @@ def bbox_ioa(box1, box2, iou=False, eps=1e-7):
36
36
  inter_area = (np.minimum(b1_x2[:, None], b2_x2) - np.maximum(b1_x1[:, None], b2_x1)).clip(0) * \
37
37
  (np.minimum(b1_y2[:, None], b2_y2) - np.maximum(b1_y1[:, None], b2_y1)).clip(0)
38
38
 
39
- # box2 area
39
+ # Box2 area
40
40
  area = (b2_x2 - b2_x1) * (b2_y2 - b2_y1)
41
41
  if iou:
42
42
  box1_area = (b1_x2 - b1_x1) * (b1_y2 - b1_y1)
@@ -166,8 +166,19 @@ def kpt_iou(kpt1, kpt2, area, sigma, eps=1e-7):
166
166
  return (torch.exp(-e) * kpt_mask[:, None]).sum(-1) / (kpt_mask.sum(-1)[:, None] + eps)
167
167
 
168
168
 
169
- def smooth_BCE(eps=0.1): # https://github.com/ultralytics/yolov3/issues/238#issuecomment-598028441
170
- # return positive, negative label smoothing BCE targets
169
+ def smooth_BCE(eps=0.1):
170
+ """
171
+ Computes smoothed positive and negative Binary Cross-Entropy targets.
172
+
173
+ This function calculates positive and negative label smoothing BCE targets based on a given epsilon value.
174
+ For implementation details, refer to https://github.com/ultralytics/yolov3/issues/238#issuecomment-598028441.
175
+
176
+ Args:
177
+ eps (float, optional): The epsilon value for label smoothing. Defaults to 0.1.
178
+
179
+ Returns:
180
+ (tuple): A tuple containing the positive and negative label smoothing BCE targets.
181
+ """
171
182
  return 1.0 - 0.5 * eps, 0.5 * eps
172
183
 
173
184
 
@@ -429,13 +440,18 @@ def ap_per_class(tp,
429
440
 
430
441
  Returns:
431
442
  (tuple): A tuple of six arrays and one array of unique classes, where:
432
- tp (np.ndarray): True positive counts for each class.
433
- fp (np.ndarray): False positive counts for each class.
434
- p (np.ndarray): Precision values at each confidence threshold.
435
- r (np.ndarray): Recall values at each confidence threshold.
436
- f1 (np.ndarray): F1-score values at each confidence threshold.
437
- ap (np.ndarray): Average precision for each class at different IoU thresholds.
438
- unique_classes (np.ndarray): An array of unique classes that have data.
443
+ tp (np.ndarray): True positive counts at threshold given by max F1 metric for each class.Shape: (nc,).
444
+ fp (np.ndarray): False positive counts at threshold given by max F1 metric for each class. Shape: (nc,).
445
+ p (np.ndarray): Precision values at threshold given by max F1 metric for each class. Shape: (nc,).
446
+ r (np.ndarray): Recall values at threshold given by max F1 metric for each class. Shape: (nc,).
447
+ f1 (np.ndarray): F1-score values at threshold given by max F1 metric for each class. Shape: (nc,).
448
+ ap (np.ndarray): Average precision for each class at different IoU thresholds. Shape: (nc, 10).
449
+ unique_classes (np.ndarray): An array of unique classes that have data. Shape: (nc,).
450
+ p_curve (np.ndarray): Precision curves for each class. Shape: (nc, 1000).
451
+ r_curve (np.ndarray): Recall curves for each class. Shape: (nc, 1000).
452
+ f1_curve (np.ndarray): F1-score curves for each class. Shape: (nc, 1000).
453
+ x (np.ndarray): X-axis values for the curves. Shape: (1000,).
454
+ prec_values: Precision values at mAP@0.5 for each class. Shape: (nc, 1000).
439
455
  """
440
456
 
441
457
  # Sort by objectness
@@ -447,8 +463,10 @@ def ap_per_class(tp,
447
463
  nc = unique_classes.shape[0] # number of classes, number of detections
448
464
 
449
465
  # Create Precision-Recall curve and compute AP for each class
450
- px, py = np.linspace(0, 1, 1000), [] # for plotting
451
- ap, p, r = np.zeros((nc, tp.shape[1])), np.zeros((nc, 1000)), np.zeros((nc, 1000))
466
+ x, prec_values = np.linspace(0, 1, 1000), []
467
+
468
+ # Average precision, precision and recall curves
469
+ ap, p_curve, r_curve = np.zeros((nc, tp.shape[1])), np.zeros((nc, 1000)), np.zeros((nc, 1000))
452
470
  for ci, c in enumerate(unique_classes):
453
471
  i = pred_cls == c
454
472
  n_l = nt[ci] # number of labels
@@ -462,33 +480,35 @@ def ap_per_class(tp,
462
480
 
463
481
  # Recall
464
482
  recall = tpc / (n_l + eps) # recall curve
465
- r[ci] = np.interp(-px, -conf[i], recall[:, 0], left=0) # negative x, xp because xp decreases
483
+ r_curve[ci] = np.interp(-x, -conf[i], recall[:, 0], left=0) # negative x, xp because xp decreases
466
484
 
467
485
  # Precision
468
486
  precision = tpc / (tpc + fpc) # precision curve
469
- p[ci] = np.interp(-px, -conf[i], precision[:, 0], left=1) # p at pr_score
487
+ p_curve[ci] = np.interp(-x, -conf[i], precision[:, 0], left=1) # p at pr_score
470
488
 
471
489
  # AP from recall-precision curve
472
490
  for j in range(tp.shape[1]):
473
491
  ap[ci, j], mpre, mrec = compute_ap(recall[:, j], precision[:, j])
474
492
  if plot and j == 0:
475
- py.append(np.interp(px, mrec, mpre)) # precision at mAP@0.5
493
+ prec_values.append(np.interp(x, mrec, mpre)) # precision at mAP@0.5
494
+
495
+ prec_values = np.array(prec_values) # (nc, 1000)
476
496
 
477
497
  # Compute F1 (harmonic mean of precision and recall)
478
- f1 = 2 * p * r / (p + r + eps)
498
+ f1_curve = 2 * p_curve * r_curve / (p_curve + r_curve + eps)
479
499
  names = [v for k, v in names.items() if k in unique_classes] # list: only classes that have data
480
500
  names = dict(enumerate(names)) # to dict
481
501
  if plot:
482
- plot_pr_curve(px, py, ap, save_dir / f'{prefix}PR_curve.png', names, on_plot=on_plot)
483
- plot_mc_curve(px, f1, save_dir / f'{prefix}F1_curve.png', names, ylabel='F1', on_plot=on_plot)
484
- plot_mc_curve(px, p, save_dir / f'{prefix}P_curve.png', names, ylabel='Precision', on_plot=on_plot)
485
- plot_mc_curve(px, r, save_dir / f'{prefix}R_curve.png', names, ylabel='Recall', on_plot=on_plot)
502
+ plot_pr_curve(x, prec_values, ap, save_dir / f'{prefix}PR_curve.png', names, on_plot=on_plot)
503
+ plot_mc_curve(x, f1_curve, save_dir / f'{prefix}F1_curve.png', names, ylabel='F1', on_plot=on_plot)
504
+ plot_mc_curve(x, p_curve, save_dir / f'{prefix}P_curve.png', names, ylabel='Precision', on_plot=on_plot)
505
+ plot_mc_curve(x, r_curve, save_dir / f'{prefix}R_curve.png', names, ylabel='Recall', on_plot=on_plot)
486
506
 
487
- i = smooth(f1.mean(0), 0.1).argmax() # max F1 index
488
- p, r, f1 = p[:, i], r[:, i], f1[:, i]
507
+ i = smooth(f1_curve.mean(0), 0.1).argmax() # max F1 index
508
+ p, r, f1 = p_curve[:, i], r_curve[:, i], f1_curve[:, i] # max-F1 precision, recall, F1 values
489
509
  tp = (r * nt).round() # true positives
490
510
  fp = (tp / (p + eps) - tp).round() # false positives
491
- return tp, fp, p, r, f1, ap, unique_classes.astype(int)
511
+ return tp, fp, p, r, f1, ap, unique_classes.astype(int), p_curve, r_curve, f1_curve, x, prec_values
492
512
 
493
513
 
494
514
  class Metric(SimpleClass):
@@ -634,7 +654,19 @@ class Metric(SimpleClass):
634
654
  Updates the class attributes `self.p`, `self.r`, `self.f1`, `self.all_ap`, and `self.ap_class_index` based
635
655
  on the values provided in the `results` tuple.
636
656
  """
637
- self.p, self.r, self.f1, self.all_ap, self.ap_class_index = results
657
+ (self.p, self.r, self.f1, self.all_ap, self.ap_class_index, self.p_curve, self.r_curve, self.f1_curve, self.px,
658
+ self.prec_values) = results
659
+
660
+ @property
661
+ def curves(self):
662
+ """Returns a list of curves for accessing specific metrics curves."""
663
+ return []
664
+
665
+ @property
666
+ def curves_results(self):
667
+ """Returns a list of curves for accessing specific metrics curves."""
668
+ return [[self.px, self.prec_values, 'Recall', 'Precision'], [self.px, self.f1_curve, 'Confidence', 'F1'],
669
+ [self.px, self.p_curve, 'Confidence', 'Precision'], [self.px, self.r_curve, 'Confidence', 'Recall']]
638
670
 
639
671
 
640
672
  class DetMetrics(SimpleClass):
@@ -665,6 +697,8 @@ class DetMetrics(SimpleClass):
665
697
  fitness: Computes the fitness score based on the computed detection metrics.
666
698
  ap_class_index: Returns a list of class indices sorted by their average precision (AP) values.
667
699
  results_dict: Returns a dictionary that maps detection metric keys to their computed values.
700
+ curves: TODO
701
+ curves_results: TODO
668
702
  """
669
703
 
670
704
  def __init__(self, save_dir=Path('.'), plot=False, on_plot=None, names=()) -> None:
@@ -675,6 +709,7 @@ class DetMetrics(SimpleClass):
675
709
  self.names = names
676
710
  self.box = Metric()
677
711
  self.speed = {'preprocess': 0.0, 'inference': 0.0, 'loss': 0.0, 'postprocess': 0.0}
712
+ self.task = 'detect'
678
713
 
679
714
  def process(self, tp, conf, pred_cls, target_cls):
680
715
  """Process predicted results for object detection and update metrics."""
@@ -722,6 +757,16 @@ class DetMetrics(SimpleClass):
722
757
  """Returns dictionary of computed performance metrics and statistics."""
723
758
  return dict(zip(self.keys + ['fitness'], self.mean_results() + [self.fitness]))
724
759
 
760
+ @property
761
+ def curves(self):
762
+ """Returns a list of curves for accessing specific metrics curves."""
763
+ return ['Precision-Recall(B)', 'F1-Confidence(B)', 'Precision-Confidence(B)', 'Recall-Confidence(B)']
764
+
765
+ @property
766
+ def curves_results(self):
767
+ """Returns dictionary of computed performance metrics and statistics."""
768
+ return self.box.curves_results
769
+
725
770
 
726
771
  class SegmentMetrics(SimpleClass):
727
772
  """
@@ -761,6 +806,7 @@ class SegmentMetrics(SimpleClass):
761
806
  self.box = Metric()
762
807
  self.seg = Metric()
763
808
  self.speed = {'preprocess': 0.0, 'inference': 0.0, 'loss': 0.0, 'postprocess': 0.0}
809
+ self.task = 'segment'
764
810
 
765
811
  def process(self, tp_b, tp_m, conf, pred_cls, target_cls):
766
812
  """
@@ -832,6 +878,18 @@ class SegmentMetrics(SimpleClass):
832
878
  """Returns results of object detection model for evaluation."""
833
879
  return dict(zip(self.keys + ['fitness'], self.mean_results() + [self.fitness]))
834
880
 
881
+ @property
882
+ def curves(self):
883
+ """Returns a list of curves for accessing specific metrics curves."""
884
+ return [
885
+ 'Precision-Recall(B)', 'F1-Confidence(B)', 'Precision-Confidence(B)', 'Recall-Confidence(B)',
886
+ 'Precision-Recall(M)', 'F1-Confidence(M)', 'Precision-Confidence(M)', 'Recall-Confidence(M)']
887
+
888
+ @property
889
+ def curves_results(self):
890
+ """Returns dictionary of computed performance metrics and statistics."""
891
+ return self.box.curves_results + self.seg.curves_results
892
+
835
893
 
836
894
  class PoseMetrics(SegmentMetrics):
837
895
  """
@@ -872,6 +930,7 @@ class PoseMetrics(SegmentMetrics):
872
930
  self.box = Metric()
873
931
  self.pose = Metric()
874
932
  self.speed = {'preprocess': 0.0, 'inference': 0.0, 'loss': 0.0, 'postprocess': 0.0}
933
+ self.task = 'pose'
875
934
 
876
935
  def process(self, tp_b, tp_p, conf, pred_cls, target_cls):
877
936
  """
@@ -933,6 +992,18 @@ class PoseMetrics(SegmentMetrics):
933
992
  """Computes classification metrics and speed using the `targets` and `pred` inputs."""
934
993
  return self.pose.fitness() + self.box.fitness()
935
994
 
995
+ @property
996
+ def curves(self):
997
+ """Returns a list of curves for accessing specific metrics curves."""
998
+ return [
999
+ 'Precision-Recall(B)', 'F1-Confidence(B)', 'Precision-Confidence(B)', 'Recall-Confidence(B)',
1000
+ 'Precision-Recall(P)', 'F1-Confidence(P)', 'Precision-Confidence(P)', 'Recall-Confidence(P)']
1001
+
1002
+ @property
1003
+ def curves_results(self):
1004
+ """Returns dictionary of computed performance metrics and statistics."""
1005
+ return self.box.curves_results + self.pose.curves_results
1006
+
936
1007
 
937
1008
  class ClassifyMetrics(SimpleClass):
938
1009
  """
@@ -957,6 +1028,7 @@ class ClassifyMetrics(SimpleClass):
957
1028
  self.top1 = 0
958
1029
  self.top5 = 0
959
1030
  self.speed = {'preprocess': 0.0, 'inference': 0.0, 'loss': 0.0, 'postprocess': 0.0}
1031
+ self.task = 'classify'
960
1032
 
961
1033
  def process(self, targets, pred):
962
1034
  """Target classes and predicted classes."""
@@ -979,3 +1051,13 @@ class ClassifyMetrics(SimpleClass):
979
1051
  def keys(self):
980
1052
  """Returns a list of keys for the results_dict property."""
981
1053
  return ['metrics/accuracy_top1', 'metrics/accuracy_top5']
1054
+
1055
+ @property
1056
+ def curves(self):
1057
+ """Returns a list of curves for accessing specific metrics curves."""
1058
+ return []
1059
+
1060
+ @property
1061
+ def curves_results(self):
1062
+ """Returns a list of curves for accessing specific metrics curves."""
1063
+ return []
ultralytics/utils/tal.py CHANGED
@@ -193,7 +193,7 @@ class TaskAlignedAssigner(nn.Module):
193
193
  # Expand topk_idxs for each value of k and add 1 at the specified positions
194
194
  count_tensor.scatter_add_(-1, topk_idxs[:, :, k:k + 1], ones)
195
195
  # count_tensor.scatter_add_(-1, topk_idxs, torch.ones_like(topk_idxs, dtype=torch.int8, device=topk_idxs.device))
196
- # filter invalid bboxes
196
+ # Filter invalid bboxes
197
197
  count_tensor.masked_fill_(count_tensor > 1, 0)
198
198
 
199
199
  return count_tensor.to(metrics.dtype)
@@ -311,8 +311,10 @@ def initialize_weights(model):
311
311
  m.inplace = True
312
312
 
313
313
 
314
- def scale_img(img, ratio=1.0, same_shape=False, gs=32): # img(16,3,256,416)
315
- # Scales img(bs,3,y,x) by ratio constrained to gs-multiple
314
+ def scale_img(img, ratio=1.0, same_shape=False, gs=32):
315
+ """Scales and pads an image tensor of shape img(bs,3,y,x) based on given ratio and grid size gs, optionally
316
+ retaining the original shape.
317
+ """
316
318
  if ratio == 1.0:
317
319
  return img
318
320
  h, w = img.shape[2:]