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.
- ultralytics/__init__.py +1 -1
- ultralytics/cfg/__init__.py +4 -5
- ultralytics/data/augment.py +2 -2
- ultralytics/data/converter.py +12 -13
- ultralytics/data/dataset.py +1 -1
- ultralytics/engine/__init__.py +1 -0
- ultralytics/engine/exporter.py +1 -1
- ultralytics/engine/trainer.py +2 -1
- ultralytics/hub/session.py +1 -1
- ultralytics/models/fastsam/predict.py +33 -2
- ultralytics/models/fastsam/prompt.py +38 -1
- ultralytics/models/fastsam/utils.py +5 -5
- ultralytics/models/fastsam/val.py +27 -1
- ultralytics/models/nas/model.py +20 -0
- ultralytics/models/nas/predict.py +23 -0
- ultralytics/models/nas/val.py +24 -0
- ultralytics/models/rtdetr/val.py +17 -5
- ultralytics/models/sam/modules/decoders.py +26 -1
- ultralytics/models/sam/modules/encoders.py +31 -3
- ultralytics/models/sam/modules/sam.py +22 -7
- ultralytics/models/sam/modules/tiny_encoder.py +147 -45
- ultralytics/models/sam/modules/transformer.py +47 -2
- ultralytics/models/sam/predict.py +19 -2
- ultralytics/models/utils/loss.py +20 -2
- ultralytics/models/utils/ops.py +5 -5
- ultralytics/nn/modules/block.py +33 -10
- ultralytics/nn/modules/conv.py +16 -4
- ultralytics/nn/modules/head.py +48 -17
- ultralytics/nn/modules/transformer.py +2 -2
- ultralytics/nn/tasks.py +7 -7
- ultralytics/utils/__init__.py +2 -1
- ultralytics/utils/benchmarks.py +13 -0
- ultralytics/utils/callbacks/mlflow.py +76 -36
- ultralytics/utils/callbacks/wb.py +92 -1
- ultralytics/utils/checks.py +4 -4
- ultralytics/utils/errors.py +12 -0
- ultralytics/utils/files.py +1 -1
- ultralytics/utils/instance.py +41 -3
- ultralytics/utils/loss.py +22 -19
- ultralytics/utils/metrics.py +106 -24
- ultralytics/utils/tal.py +1 -1
- ultralytics/utils/torch_utils.py +4 -2
- ultralytics/utils/tuner.py +10 -4
- {ultralytics-8.0.196.dist-info → ultralytics-8.0.198.dist-info}/METADATA +1 -1
- {ultralytics-8.0.196.dist-info → ultralytics-8.0.198.dist-info}/RECORD +49 -49
- {ultralytics-8.0.196.dist-info → ultralytics-8.0.198.dist-info}/LICENSE +0 -0
- {ultralytics-8.0.196.dist-info → ultralytics-8.0.198.dist-info}/WHEEL +0 -0
- {ultralytics-8.0.196.dist-info → ultralytics-8.0.198.dist-info}/entry_points.txt +0 -0
- {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
|
|
ultralytics/utils/checks.py
CHANGED
|
@@ -165,16 +165,16 @@ def check_version(current: str = '0.0.0',
|
|
|
165
165
|
|
|
166
166
|
Example:
|
|
167
167
|
```python
|
|
168
|
-
#
|
|
168
|
+
# Check if current version is exactly 22.04
|
|
169
169
|
check_version(current='22.04', required='==22.04')
|
|
170
170
|
|
|
171
|
-
#
|
|
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
|
-
#
|
|
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
|
-
#
|
|
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
|
"""
|
ultralytics/utils/errors.py
CHANGED
|
@@ -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."""
|
ultralytics/utils/files.py
CHANGED
ultralytics/utils/instance.py
CHANGED
|
@@ -33,9 +33,17 @@ __all__ = 'Bboxes', # tuple or list
|
|
|
33
33
|
|
|
34
34
|
class Bboxes:
|
|
35
35
|
"""
|
|
36
|
-
|
|
36
|
+
A class for handling bounding boxes.
|
|
37
37
|
|
|
38
|
-
|
|
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
|
-
#
|
|
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
|
-
#
|
|
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
|
-
#
|
|
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
|
-
#
|
|
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
|
-
#
|
|
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
|
-
#
|
|
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
|
-
#
|
|
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
|
-
#
|
|
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
|
-
#
|
|
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
|
-
#
|
|
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
|
-
#
|
|
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
|
-
#
|
|
347
|
+
# Normalize to 0-1
|
|
346
348
|
target_bboxes_normalized = target_bboxes / imgsz[[1, 0, 1, 0]]
|
|
347
349
|
|
|
348
|
-
#
|
|
350
|
+
# Areas of target bboxes
|
|
349
351
|
marea = xyxy2xywh(target_bboxes_normalized)[..., 2:].prod(2)
|
|
350
352
|
|
|
351
|
-
#
|
|
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
|
-
#
|
|
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
|
-
#
|
|
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
|
-
#
|
|
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
|
-
#
|
|
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
|
-
#
|
|
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,
|
ultralytics/utils/metrics.py
CHANGED
|
@@ -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
|
-
#
|
|
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):
|
|
170
|
-
|
|
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
|
|
435
|
-
r (np.ndarray): Recall values at each
|
|
436
|
-
f1 (np.ndarray): F1-score values at each
|
|
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
|
-
|
|
451
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
483
|
-
plot_mc_curve(
|
|
484
|
-
plot_mc_curve(
|
|
485
|
-
plot_mc_curve(
|
|
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(
|
|
488
|
-
p, r, f1 =
|
|
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
|
|
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
|
-
#
|
|
196
|
+
# Filter invalid bboxes
|
|
197
197
|
count_tensor.masked_fill_(count_tensor > 1, 0)
|
|
198
198
|
|
|
199
199
|
return count_tensor.to(metrics.dtype)
|
ultralytics/utils/torch_utils.py
CHANGED
|
@@ -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):
|
|
315
|
-
|
|
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:]
|