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.
- ultralytics/__init__.py +1 -1
- ultralytics/cfg/__init__.py +5 -6
- ultralytics/data/augment.py +234 -29
- ultralytics/data/base.py +2 -1
- ultralytics/data/build.py +9 -3
- ultralytics/data/converter.py +5 -2
- ultralytics/data/dataset.py +16 -2
- ultralytics/data/loaders.py +111 -7
- ultralytics/data/utils.py +3 -3
- ultralytics/engine/exporter.py +1 -3
- ultralytics/engine/model.py +16 -9
- ultralytics/engine/predictor.py +10 -6
- ultralytics/engine/results.py +18 -8
- ultralytics/engine/trainer.py +19 -31
- ultralytics/engine/tuner.py +20 -20
- ultralytics/engine/validator.py +3 -4
- ultralytics/hub/__init__.py +2 -2
- ultralytics/hub/auth.py +18 -3
- ultralytics/hub/session.py +1 -0
- ultralytics/hub/utils.py +1 -3
- ultralytics/models/fastsam/model.py +2 -1
- ultralytics/models/fastsam/predict.py +10 -7
- ultralytics/models/fastsam/prompt.py +15 -1
- ultralytics/models/nas/model.py +3 -1
- ultralytics/models/rtdetr/model.py +4 -6
- ultralytics/models/rtdetr/predict.py +2 -1
- ultralytics/models/rtdetr/train.py +2 -1
- ultralytics/models/rtdetr/val.py +1 -0
- ultralytics/models/sam/amg.py +12 -6
- ultralytics/models/sam/model.py +5 -6
- ultralytics/models/sam/modules/decoders.py +5 -1
- ultralytics/models/sam/modules/encoders.py +15 -12
- ultralytics/models/sam/modules/tiny_encoder.py +38 -2
- ultralytics/models/sam/modules/transformer.py +2 -4
- ultralytics/models/sam/predict.py +8 -4
- ultralytics/models/utils/loss.py +35 -8
- ultralytics/models/utils/ops.py +14 -18
- ultralytics/models/yolo/classify/predict.py +1 -0
- ultralytics/models/yolo/classify/train.py +4 -2
- ultralytics/models/yolo/classify/val.py +1 -0
- ultralytics/models/yolo/detect/train.py +4 -3
- ultralytics/models/yolo/model.py +2 -4
- ultralytics/models/yolo/pose/predict.py +1 -0
- ultralytics/models/yolo/segment/predict.py +2 -0
- ultralytics/models/yolo/segment/val.py +1 -1
- ultralytics/nn/autobackend.py +54 -43
- ultralytics/nn/modules/__init__.py +13 -9
- ultralytics/nn/modules/block.py +11 -5
- ultralytics/nn/modules/conv.py +16 -7
- ultralytics/nn/modules/head.py +6 -3
- ultralytics/nn/modules/transformer.py +47 -15
- ultralytics/nn/modules/utils.py +6 -4
- ultralytics/nn/tasks.py +61 -21
- ultralytics/trackers/bot_sort.py +53 -6
- ultralytics/trackers/byte_tracker.py +71 -15
- ultralytics/trackers/track.py +0 -1
- ultralytics/trackers/utils/gmc.py +23 -0
- ultralytics/trackers/utils/kalman_filter.py +6 -6
- ultralytics/utils/__init__.py +32 -19
- ultralytics/utils/autobatch.py +1 -3
- ultralytics/utils/benchmarks.py +14 -1
- ultralytics/utils/callbacks/base.py +1 -3
- ultralytics/utils/callbacks/comet.py +11 -3
- ultralytics/utils/callbacks/dvc.py +9 -0
- ultralytics/utils/callbacks/neptune.py +5 -6
- ultralytics/utils/callbacks/wb.py +1 -0
- ultralytics/utils/checks.py +13 -9
- ultralytics/utils/dist.py +2 -1
- ultralytics/utils/downloads.py +7 -3
- ultralytics/utils/files.py +3 -3
- ultralytics/utils/instance.py +12 -3
- ultralytics/utils/loss.py +97 -22
- ultralytics/utils/metrics.py +35 -34
- ultralytics/utils/ops.py +10 -9
- ultralytics/utils/patches.py +9 -7
- ultralytics/utils/plotting.py +4 -3
- ultralytics/utils/torch_utils.py +8 -6
- ultralytics/utils/triton.py +87 -0
- {ultralytics-8.0.194.dist-info → ultralytics-8.0.196.dist-info}/METADATA +1 -1
- {ultralytics-8.0.194.dist-info → ultralytics-8.0.196.dist-info}/RECORD +84 -83
- {ultralytics-8.0.194.dist-info → ultralytics-8.0.196.dist-info}/LICENSE +0 -0
- {ultralytics-8.0.194.dist-info → ultralytics-8.0.196.dist-info}/WHEEL +0 -0
- {ultralytics-8.0.194.dist-info → ultralytics-8.0.196.dist-info}/entry_points.txt +0 -0
- {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
|
-
"""
|
|
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.
|
ultralytics/utils/downloads.py
CHANGED
|
@@ -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
|
-
|
|
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
|
-
"""
|
|
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
|
ultralytics/utils/files.py
CHANGED
|
@@ -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
|
-
|
|
35
|
-
|
|
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.
|
ultralytics/utils/instance.py
CHANGED
|
@@ -32,9 +32,14 @@ __all__ = 'Bboxes', # tuple or list
|
|
|
32
32
|
|
|
33
33
|
|
|
34
34
|
class Bboxes:
|
|
35
|
-
"""
|
|
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
|
-
"""
|
|
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
|
-
"""
|
|
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
|
-
"""
|
|
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
|
-
|
|
266
|
-
|
|
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
|
|
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
|
-
|
|
293
|
-
|
|
294
|
-
|
|
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).
|
|
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):
|
ultralytics/utils/metrics.py
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
# Ultralytics YOLO 🚀, AGPL-3.0 license
|
|
2
|
-
"""
|
|
3
|
-
|
|
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
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
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
|
-
"""
|
|
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
|
-
"""
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
ultralytics/utils/patches.py
CHANGED
|
@@ -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
|
-
"""
|
|
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
|
-
"""
|
|
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
|
-
"""
|
|
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
|
-
"""
|
|
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.
|
ultralytics/utils/plotting.py
CHANGED
|
@@ -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
|
-
|
|
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 *
|
|
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
|
-
"""
|
|
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
|
ultralytics/utils/torch_utils.py
CHANGED
|
@@ -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
|
-
"""
|
|
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]
|