ultralytics 8.0.238__py3-none-any.whl → 8.0.239__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 (134) hide show
  1. ultralytics/__init__.py +2 -2
  2. ultralytics/cfg/__init__.py +241 -138
  3. ultralytics/data/__init__.py +9 -2
  4. ultralytics/data/annotator.py +4 -4
  5. ultralytics/data/augment.py +186 -169
  6. ultralytics/data/base.py +54 -48
  7. ultralytics/data/build.py +34 -23
  8. ultralytics/data/converter.py +242 -70
  9. ultralytics/data/dataset.py +117 -95
  10. ultralytics/data/explorer/__init__.py +3 -1
  11. ultralytics/data/explorer/explorer.py +120 -100
  12. ultralytics/data/explorer/gui/__init__.py +1 -0
  13. ultralytics/data/explorer/gui/dash.py +123 -89
  14. ultralytics/data/explorer/utils.py +37 -39
  15. ultralytics/data/loaders.py +75 -62
  16. ultralytics/data/split_dota.py +44 -36
  17. ultralytics/data/utils.py +160 -142
  18. ultralytics/engine/exporter.py +348 -292
  19. ultralytics/engine/model.py +102 -66
  20. ultralytics/engine/predictor.py +74 -55
  21. ultralytics/engine/results.py +61 -41
  22. ultralytics/engine/trainer.py +192 -144
  23. ultralytics/engine/tuner.py +66 -59
  24. ultralytics/engine/validator.py +31 -26
  25. ultralytics/hub/__init__.py +54 -31
  26. ultralytics/hub/auth.py +28 -25
  27. ultralytics/hub/session.py +282 -133
  28. ultralytics/hub/utils.py +64 -42
  29. ultralytics/models/__init__.py +1 -1
  30. ultralytics/models/fastsam/__init__.py +1 -1
  31. ultralytics/models/fastsam/model.py +6 -6
  32. ultralytics/models/fastsam/predict.py +3 -2
  33. ultralytics/models/fastsam/prompt.py +55 -48
  34. ultralytics/models/fastsam/val.py +1 -1
  35. ultralytics/models/nas/__init__.py +1 -1
  36. ultralytics/models/nas/model.py +9 -8
  37. ultralytics/models/nas/predict.py +8 -6
  38. ultralytics/models/nas/val.py +11 -9
  39. ultralytics/models/rtdetr/__init__.py +1 -1
  40. ultralytics/models/rtdetr/model.py +11 -9
  41. ultralytics/models/rtdetr/train.py +18 -16
  42. ultralytics/models/rtdetr/val.py +25 -19
  43. ultralytics/models/sam/__init__.py +1 -1
  44. ultralytics/models/sam/amg.py +13 -14
  45. ultralytics/models/sam/build.py +44 -42
  46. ultralytics/models/sam/model.py +6 -6
  47. ultralytics/models/sam/modules/decoders.py +6 -4
  48. ultralytics/models/sam/modules/encoders.py +37 -35
  49. ultralytics/models/sam/modules/sam.py +5 -4
  50. ultralytics/models/sam/modules/tiny_encoder.py +95 -73
  51. ultralytics/models/sam/modules/transformer.py +3 -2
  52. ultralytics/models/sam/predict.py +39 -27
  53. ultralytics/models/utils/loss.py +99 -95
  54. ultralytics/models/utils/ops.py +34 -31
  55. ultralytics/models/yolo/__init__.py +1 -1
  56. ultralytics/models/yolo/classify/__init__.py +1 -1
  57. ultralytics/models/yolo/classify/predict.py +8 -6
  58. ultralytics/models/yolo/classify/train.py +37 -31
  59. ultralytics/models/yolo/classify/val.py +26 -24
  60. ultralytics/models/yolo/detect/__init__.py +1 -1
  61. ultralytics/models/yolo/detect/predict.py +8 -6
  62. ultralytics/models/yolo/detect/train.py +47 -37
  63. ultralytics/models/yolo/detect/val.py +100 -82
  64. ultralytics/models/yolo/model.py +31 -25
  65. ultralytics/models/yolo/obb/__init__.py +1 -1
  66. ultralytics/models/yolo/obb/predict.py +13 -11
  67. ultralytics/models/yolo/obb/train.py +3 -3
  68. ultralytics/models/yolo/obb/val.py +70 -59
  69. ultralytics/models/yolo/pose/__init__.py +1 -1
  70. ultralytics/models/yolo/pose/predict.py +17 -12
  71. ultralytics/models/yolo/pose/train.py +28 -25
  72. ultralytics/models/yolo/pose/val.py +91 -64
  73. ultralytics/models/yolo/segment/__init__.py +1 -1
  74. ultralytics/models/yolo/segment/predict.py +10 -8
  75. ultralytics/models/yolo/segment/train.py +16 -15
  76. ultralytics/models/yolo/segment/val.py +90 -68
  77. ultralytics/nn/__init__.py +26 -6
  78. ultralytics/nn/autobackend.py +144 -112
  79. ultralytics/nn/modules/__init__.py +96 -13
  80. ultralytics/nn/modules/block.py +28 -7
  81. ultralytics/nn/modules/conv.py +41 -23
  82. ultralytics/nn/modules/head.py +60 -52
  83. ultralytics/nn/modules/transformer.py +49 -32
  84. ultralytics/nn/modules/utils.py +20 -15
  85. ultralytics/nn/tasks.py +215 -141
  86. ultralytics/solutions/ai_gym.py +59 -47
  87. ultralytics/solutions/distance_calculation.py +17 -14
  88. ultralytics/solutions/heatmap.py +57 -55
  89. ultralytics/solutions/object_counter.py +46 -39
  90. ultralytics/solutions/speed_estimation.py +13 -16
  91. ultralytics/trackers/__init__.py +1 -1
  92. ultralytics/trackers/basetrack.py +1 -0
  93. ultralytics/trackers/bot_sort.py +2 -1
  94. ultralytics/trackers/byte_tracker.py +10 -7
  95. ultralytics/trackers/track.py +7 -7
  96. ultralytics/trackers/utils/gmc.py +25 -25
  97. ultralytics/trackers/utils/kalman_filter.py +85 -42
  98. ultralytics/trackers/utils/matching.py +8 -7
  99. ultralytics/utils/__init__.py +173 -152
  100. ultralytics/utils/autobatch.py +10 -10
  101. ultralytics/utils/benchmarks.py +76 -86
  102. ultralytics/utils/callbacks/__init__.py +1 -1
  103. ultralytics/utils/callbacks/base.py +29 -29
  104. ultralytics/utils/callbacks/clearml.py +51 -43
  105. ultralytics/utils/callbacks/comet.py +81 -66
  106. ultralytics/utils/callbacks/dvc.py +33 -26
  107. ultralytics/utils/callbacks/hub.py +44 -26
  108. ultralytics/utils/callbacks/mlflow.py +31 -24
  109. ultralytics/utils/callbacks/neptune.py +35 -25
  110. ultralytics/utils/callbacks/raytune.py +9 -4
  111. ultralytics/utils/callbacks/tensorboard.py +16 -11
  112. ultralytics/utils/callbacks/wb.py +39 -33
  113. ultralytics/utils/checks.py +189 -141
  114. ultralytics/utils/dist.py +15 -12
  115. ultralytics/utils/downloads.py +112 -96
  116. ultralytics/utils/errors.py +1 -1
  117. ultralytics/utils/files.py +11 -11
  118. ultralytics/utils/instance.py +22 -22
  119. ultralytics/utils/loss.py +117 -67
  120. ultralytics/utils/metrics.py +224 -158
  121. ultralytics/utils/ops.py +38 -28
  122. ultralytics/utils/patches.py +3 -3
  123. ultralytics/utils/plotting.py +217 -120
  124. ultralytics/utils/tal.py +19 -13
  125. ultralytics/utils/torch_utils.py +138 -109
  126. ultralytics/utils/triton.py +12 -10
  127. ultralytics/utils/tuner.py +49 -47
  128. {ultralytics-8.0.238.dist-info → ultralytics-8.0.239.dist-info}/METADATA +2 -1
  129. ultralytics-8.0.239.dist-info/RECORD +188 -0
  130. ultralytics-8.0.238.dist-info/RECORD +0 -188
  131. {ultralytics-8.0.238.dist-info → ultralytics-8.0.239.dist-info}/LICENSE +0 -0
  132. {ultralytics-8.0.238.dist-info → ultralytics-8.0.239.dist-info}/WHEEL +0 -0
  133. {ultralytics-8.0.238.dist-info → ultralytics-8.0.239.dist-info}/entry_points.txt +0 -0
  134. {ultralytics-8.0.238.dist-info → ultralytics-8.0.239.dist-info}/top_level.txt +0 -0
@@ -31,43 +31,42 @@ class ClassificationValidator(BaseValidator):
31
31
  super().__init__(dataloader, save_dir, pbar, args, _callbacks)
32
32
  self.targets = None
33
33
  self.pred = None
34
- self.args.task = 'classify'
34
+ self.args.task = "classify"
35
35
  self.metrics = ClassifyMetrics()
36
36
 
37
37
  def get_desc(self):
38
38
  """Returns a formatted string summarizing classification metrics."""
39
- return ('%22s' + '%11s' * 2) % ('classes', 'top1_acc', 'top5_acc')
39
+ return ("%22s" + "%11s" * 2) % ("classes", "top1_acc", "top5_acc")
40
40
 
41
41
  def init_metrics(self, model):
42
42
  """Initialize confusion matrix, class names, and top-1 and top-5 accuracy."""
43
43
  self.names = model.names
44
44
  self.nc = len(model.names)
45
- self.confusion_matrix = ConfusionMatrix(nc=self.nc, conf=self.args.conf, task='classify')
45
+ self.confusion_matrix = ConfusionMatrix(nc=self.nc, conf=self.args.conf, task="classify")
46
46
  self.pred = []
47
47
  self.targets = []
48
48
 
49
49
  def preprocess(self, batch):
50
50
  """Preprocesses input batch and returns it."""
51
- batch['img'] = batch['img'].to(self.device, non_blocking=True)
52
- batch['img'] = batch['img'].half() if self.args.half else batch['img'].float()
53
- batch['cls'] = batch['cls'].to(self.device)
51
+ batch["img"] = batch["img"].to(self.device, non_blocking=True)
52
+ batch["img"] = batch["img"].half() if self.args.half else batch["img"].float()
53
+ batch["cls"] = batch["cls"].to(self.device)
54
54
  return batch
55
55
 
56
56
  def update_metrics(self, preds, batch):
57
57
  """Updates running metrics with model predictions and batch targets."""
58
58
  n5 = min(len(self.names), 5)
59
59
  self.pred.append(preds.argsort(1, descending=True)[:, :n5])
60
- self.targets.append(batch['cls'])
60
+ self.targets.append(batch["cls"])
61
61
 
62
62
  def finalize_metrics(self, *args, **kwargs):
63
63
  """Finalizes metrics of the model such as confusion_matrix and speed."""
64
64
  self.confusion_matrix.process_cls_preds(self.pred, self.targets)
65
65
  if self.args.plots:
66
66
  for normalize in True, False:
67
- self.confusion_matrix.plot(save_dir=self.save_dir,
68
- names=self.names.values(),
69
- normalize=normalize,
70
- on_plot=self.on_plot)
67
+ self.confusion_matrix.plot(
68
+ save_dir=self.save_dir, names=self.names.values(), normalize=normalize, on_plot=self.on_plot
69
+ )
71
70
  self.metrics.speed = self.speed
72
71
  self.metrics.confusion_matrix = self.confusion_matrix
73
72
  self.metrics.save_dir = self.save_dir
@@ -88,24 +87,27 @@ class ClassificationValidator(BaseValidator):
88
87
 
89
88
  def print_results(self):
90
89
  """Prints evaluation metrics for YOLO object detection model."""
91
- pf = '%22s' + '%11.3g' * len(self.metrics.keys) # print format
92
- LOGGER.info(pf % ('all', self.metrics.top1, self.metrics.top5))
90
+ pf = "%22s" + "%11.3g" * len(self.metrics.keys) # print format
91
+ LOGGER.info(pf % ("all", self.metrics.top1, self.metrics.top5))
93
92
 
94
93
  def plot_val_samples(self, batch, ni):
95
94
  """Plot validation image samples."""
96
95
  plot_images(
97
- images=batch['img'],
98
- batch_idx=torch.arange(len(batch['img'])),
99
- cls=batch['cls'].view(-1), # warning: use .view(), not .squeeze() for Classify models
100
- fname=self.save_dir / f'val_batch{ni}_labels.jpg',
96
+ images=batch["img"],
97
+ batch_idx=torch.arange(len(batch["img"])),
98
+ cls=batch["cls"].view(-1), # warning: use .view(), not .squeeze() for Classify models
99
+ fname=self.save_dir / f"val_batch{ni}_labels.jpg",
101
100
  names=self.names,
102
- on_plot=self.on_plot)
101
+ on_plot=self.on_plot,
102
+ )
103
103
 
104
104
  def plot_predictions(self, batch, preds, ni):
105
105
  """Plots predicted bounding boxes on input images and saves the result."""
106
- plot_images(batch['img'],
107
- batch_idx=torch.arange(len(batch['img'])),
108
- cls=torch.argmax(preds, dim=1),
109
- fname=self.save_dir / f'val_batch{ni}_pred.jpg',
110
- names=self.names,
111
- on_plot=self.on_plot) # pred
106
+ plot_images(
107
+ batch["img"],
108
+ batch_idx=torch.arange(len(batch["img"])),
109
+ cls=torch.argmax(preds, dim=1),
110
+ fname=self.save_dir / f"val_batch{ni}_pred.jpg",
111
+ names=self.names,
112
+ on_plot=self.on_plot,
113
+ ) # pred
@@ -4,4 +4,4 @@ from .predict import DetectionPredictor
4
4
  from .train import DetectionTrainer
5
5
  from .val import DetectionValidator
6
6
 
7
- __all__ = 'DetectionPredictor', 'DetectionTrainer', 'DetectionValidator'
7
+ __all__ = "DetectionPredictor", "DetectionTrainer", "DetectionValidator"
@@ -22,12 +22,14 @@ class DetectionPredictor(BasePredictor):
22
22
 
23
23
  def postprocess(self, preds, img, orig_imgs):
24
24
  """Post-processes predictions and returns a list of Results objects."""
25
- preds = ops.non_max_suppression(preds,
26
- self.args.conf,
27
- self.args.iou,
28
- agnostic=self.args.agnostic_nms,
29
- max_det=self.args.max_det,
30
- classes=self.args.classes)
25
+ preds = ops.non_max_suppression(
26
+ preds,
27
+ self.args.conf,
28
+ self.args.iou,
29
+ agnostic=self.args.agnostic_nms,
30
+ max_det=self.args.max_det,
31
+ classes=self.args.classes,
32
+ )
31
33
 
32
34
  if not isinstance(orig_imgs, list): # input images are a torch.Tensor, not a list
33
35
  orig_imgs = ops.convert_torch2numpy_batch(orig_imgs)
@@ -30,7 +30,7 @@ class DetectionTrainer(BaseTrainer):
30
30
  ```
31
31
  """
32
32
 
33
- def build_dataset(self, img_path, mode='train', batch=None):
33
+ def build_dataset(self, img_path, mode="train", batch=None):
34
34
  """
35
35
  Build YOLO Dataset.
36
36
 
@@ -40,33 +40,37 @@ class DetectionTrainer(BaseTrainer):
40
40
  batch (int, optional): Size of batches, this is for `rect`. Defaults to None.
41
41
  """
42
42
  gs = max(int(de_parallel(self.model).stride.max() if self.model else 0), 32)
43
- return build_yolo_dataset(self.args, img_path, batch, self.data, mode=mode, rect=mode == 'val', stride=gs)
43
+ return build_yolo_dataset(self.args, img_path, batch, self.data, mode=mode, rect=mode == "val", stride=gs)
44
44
 
45
- def get_dataloader(self, dataset_path, batch_size=16, rank=0, mode='train'):
45
+ def get_dataloader(self, dataset_path, batch_size=16, rank=0, mode="train"):
46
46
  """Construct and return dataloader."""
47
- assert mode in ['train', 'val']
47
+ assert mode in ["train", "val"]
48
48
  with torch_distributed_zero_first(rank): # init dataset *.cache only once if DDP
49
49
  dataset = self.build_dataset(dataset_path, mode, batch_size)
50
- shuffle = mode == 'train'
51
- if getattr(dataset, 'rect', False) and shuffle:
50
+ shuffle = mode == "train"
51
+ if getattr(dataset, "rect", False) and shuffle:
52
52
  LOGGER.warning("WARNING ⚠️ 'rect=True' is incompatible with DataLoader shuffle, setting shuffle=False")
53
53
  shuffle = False
54
- workers = self.args.workers if mode == 'train' else self.args.workers * 2
54
+ workers = self.args.workers if mode == "train" else self.args.workers * 2
55
55
  return build_dataloader(dataset, batch_size, workers, shuffle, rank) # return dataloader
56
56
 
57
57
  def preprocess_batch(self, batch):
58
58
  """Preprocesses a batch of images by scaling and converting to float."""
59
- batch['img'] = batch['img'].to(self.device, non_blocking=True).float() / 255
59
+ batch["img"] = batch["img"].to(self.device, non_blocking=True).float() / 255
60
60
  if self.args.multi_scale:
61
- imgs = batch['img']
62
- sz = (random.randrange(self.args.imgsz * 0.5, self.args.imgsz * 1.5 + self.stride) // self.stride *
63
- self.stride) # size
61
+ imgs = batch["img"]
62
+ sz = (
63
+ random.randrange(self.args.imgsz * 0.5, self.args.imgsz * 1.5 + self.stride)
64
+ // self.stride
65
+ * self.stride
66
+ ) # size
64
67
  sf = sz / max(imgs.shape[2:]) # scale factor
65
68
  if sf != 1:
66
- ns = [math.ceil(x * sf / self.stride) * self.stride
67
- for x in imgs.shape[2:]] # new shape (stretched to gs-multiple)
68
- imgs = nn.functional.interpolate(imgs, size=ns, mode='bilinear', align_corners=False)
69
- batch['img'] = imgs
69
+ ns = [
70
+ math.ceil(x * sf / self.stride) * self.stride for x in imgs.shape[2:]
71
+ ] # new shape (stretched to gs-multiple)
72
+ imgs = nn.functional.interpolate(imgs, size=ns, mode="bilinear", align_corners=False)
73
+ batch["img"] = imgs
70
74
  return batch
71
75
 
72
76
  def set_model_attributes(self):
@@ -74,33 +78,32 @@ class DetectionTrainer(BaseTrainer):
74
78
  # self.args.box *= 3 / nl # scale to layers
75
79
  # self.args.cls *= self.data["nc"] / 80 * 3 / nl # scale to classes and layers
76
80
  # self.args.cls *= (self.args.imgsz / 640) ** 2 * 3 / nl # scale to image size and layers
77
- self.model.nc = self.data['nc'] # attach number of classes to model
78
- self.model.names = self.data['names'] # attach class names to model
81
+ self.model.nc = self.data["nc"] # attach number of classes to model
82
+ self.model.names = self.data["names"] # attach class names to model
79
83
  self.model.args = self.args # attach hyperparameters to model
80
84
  # TODO: self.model.class_weights = labels_to_class_weights(dataset.labels, nc).to(device) * nc
81
85
 
82
86
  def get_model(self, cfg=None, weights=None, verbose=True):
83
87
  """Return a YOLO detection model."""
84
- model = DetectionModel(cfg, nc=self.data['nc'], verbose=verbose and RANK == -1)
88
+ model = DetectionModel(cfg, nc=self.data["nc"], verbose=verbose and RANK == -1)
85
89
  if weights:
86
90
  model.load(weights)
87
91
  return model
88
92
 
89
93
  def get_validator(self):
90
94
  """Returns a DetectionValidator for YOLO model validation."""
91
- self.loss_names = 'box_loss', 'cls_loss', 'dfl_loss'
92
- return yolo.detect.DetectionValidator(self.test_loader,
93
- save_dir=self.save_dir,
94
- args=copy(self.args),
95
- _callbacks=self.callbacks)
95
+ self.loss_names = "box_loss", "cls_loss", "dfl_loss"
96
+ return yolo.detect.DetectionValidator(
97
+ self.test_loader, save_dir=self.save_dir, args=copy(self.args), _callbacks=self.callbacks
98
+ )
96
99
 
97
- def label_loss_items(self, loss_items=None, prefix='train'):
100
+ def label_loss_items(self, loss_items=None, prefix="train"):
98
101
  """
99
102
  Returns a loss dict with labelled training loss items tensor.
100
103
 
101
104
  Not needed for classification but necessary for segmentation & detection
102
105
  """
103
- keys = [f'{prefix}/{x}' for x in self.loss_names]
106
+ keys = [f"{prefix}/{x}" for x in self.loss_names]
104
107
  if loss_items is not None:
105
108
  loss_items = [round(float(x), 5) for x in loss_items] # convert tensors to 5 decimal place floats
106
109
  return dict(zip(keys, loss_items))
@@ -109,18 +112,25 @@ class DetectionTrainer(BaseTrainer):
109
112
 
110
113
  def progress_string(self):
111
114
  """Returns a formatted string of training progress with epoch, GPU memory, loss, instances and size."""
112
- return ('\n' + '%11s' *
113
- (4 + len(self.loss_names))) % ('Epoch', 'GPU_mem', *self.loss_names, 'Instances', 'Size')
115
+ return ("\n" + "%11s" * (4 + len(self.loss_names))) % (
116
+ "Epoch",
117
+ "GPU_mem",
118
+ *self.loss_names,
119
+ "Instances",
120
+ "Size",
121
+ )
114
122
 
115
123
  def plot_training_samples(self, batch, ni):
116
124
  """Plots training samples with their annotations."""
117
- plot_images(images=batch['img'],
118
- batch_idx=batch['batch_idx'],
119
- cls=batch['cls'].squeeze(-1),
120
- bboxes=batch['bboxes'],
121
- paths=batch['im_file'],
122
- fname=self.save_dir / f'train_batch{ni}.jpg',
123
- on_plot=self.on_plot)
125
+ plot_images(
126
+ images=batch["img"],
127
+ batch_idx=batch["batch_idx"],
128
+ cls=batch["cls"].squeeze(-1),
129
+ bboxes=batch["bboxes"],
130
+ paths=batch["im_file"],
131
+ fname=self.save_dir / f"train_batch{ni}.jpg",
132
+ on_plot=self.on_plot,
133
+ )
124
134
 
125
135
  def plot_metrics(self):
126
136
  """Plots metrics from a CSV file."""
@@ -128,6 +138,6 @@ class DetectionTrainer(BaseTrainer):
128
138
 
129
139
  def plot_training_labels(self):
130
140
  """Create a labeled training plot of the YOLO model."""
131
- boxes = np.concatenate([lb['bboxes'] for lb in self.train_loader.dataset.labels], 0)
132
- cls = np.concatenate([lb['cls'] for lb in self.train_loader.dataset.labels], 0)
133
- plot_labels(boxes, cls.squeeze(), names=self.data['names'], save_dir=self.save_dir, on_plot=self.on_plot)
141
+ boxes = np.concatenate([lb["bboxes"] for lb in self.train_loader.dataset.labels], 0)
142
+ cls = np.concatenate([lb["cls"] for lb in self.train_loader.dataset.labels], 0)
143
+ plot_labels(boxes, cls.squeeze(), names=self.data["names"], save_dir=self.save_dir, on_plot=self.on_plot)
@@ -34,7 +34,7 @@ class DetectionValidator(BaseValidator):
34
34
  self.nt_per_class = None
35
35
  self.is_coco = False
36
36
  self.class_map = None
37
- self.args.task = 'detect'
37
+ self.args.task = "detect"
38
38
  self.metrics = DetMetrics(save_dir=self.save_dir, on_plot=self.on_plot)
39
39
  self.iouv = torch.linspace(0.5, 0.95, 10) # iou vector for mAP@0.5:0.95
40
40
  self.niou = self.iouv.numel()
@@ -42,25 +42,30 @@ class DetectionValidator(BaseValidator):
42
42
 
43
43
  def preprocess(self, batch):
44
44
  """Preprocesses batch of images for YOLO training."""
45
- batch['img'] = batch['img'].to(self.device, non_blocking=True)
46
- batch['img'] = (batch['img'].half() if self.args.half else batch['img'].float()) / 255
47
- for k in ['batch_idx', 'cls', 'bboxes']:
45
+ batch["img"] = batch["img"].to(self.device, non_blocking=True)
46
+ batch["img"] = (batch["img"].half() if self.args.half else batch["img"].float()) / 255
47
+ for k in ["batch_idx", "cls", "bboxes"]:
48
48
  batch[k] = batch[k].to(self.device)
49
49
 
50
50
  if self.args.save_hybrid:
51
- height, width = batch['img'].shape[2:]
52
- nb = len(batch['img'])
53
- bboxes = batch['bboxes'] * torch.tensor((width, height, width, height), device=self.device)
54
- self.lb = [
55
- torch.cat([batch['cls'][batch['batch_idx'] == i], bboxes[batch['batch_idx'] == i]], dim=-1)
56
- for i in range(nb)] if self.args.save_hybrid else [] # for autolabelling
51
+ height, width = batch["img"].shape[2:]
52
+ nb = len(batch["img"])
53
+ bboxes = batch["bboxes"] * torch.tensor((width, height, width, height), device=self.device)
54
+ self.lb = (
55
+ [
56
+ torch.cat([batch["cls"][batch["batch_idx"] == i], bboxes[batch["batch_idx"] == i]], dim=-1)
57
+ for i in range(nb)
58
+ ]
59
+ if self.args.save_hybrid
60
+ else []
61
+ ) # for autolabelling
57
62
 
58
63
  return batch
59
64
 
60
65
  def init_metrics(self, model):
61
66
  """Initialize evaluation metrics for YOLO."""
62
- val = self.data.get(self.args.split, '') # validation path
63
- self.is_coco = isinstance(val, str) and 'coco' in val and val.endswith(f'{os.sep}val2017.txt') # is COCO
67
+ val = self.data.get(self.args.split, "") # validation path
68
+ self.is_coco = isinstance(val, str) and "coco" in val and val.endswith(f"{os.sep}val2017.txt") # is COCO
64
69
  self.class_map = converter.coco80_to_coco91_class() if self.is_coco else list(range(1000))
65
70
  self.args.save_json |= self.is_coco and not self.training # run on final val if training COCO
66
71
  self.names = model.names
@@ -74,25 +79,28 @@ class DetectionValidator(BaseValidator):
74
79
 
75
80
  def get_desc(self):
76
81
  """Return a formatted string summarizing class metrics of YOLO model."""
77
- return ('%22s' + '%11s' * 6) % ('Class', 'Images', 'Instances', 'Box(P', 'R', 'mAP50', 'mAP50-95)')
82
+ return ("%22s" + "%11s" * 6) % ("Class", "Images", "Instances", "Box(P", "R", "mAP50", "mAP50-95)")
78
83
 
79
84
  def postprocess(self, preds):
80
85
  """Apply Non-maximum suppression to prediction outputs."""
81
- return ops.non_max_suppression(preds,
82
- self.args.conf,
83
- self.args.iou,
84
- labels=self.lb,
85
- multi_label=True,
86
- agnostic=self.args.single_cls,
87
- max_det=self.args.max_det)
86
+ return ops.non_max_suppression(
87
+ preds,
88
+ self.args.conf,
89
+ self.args.iou,
90
+ labels=self.lb,
91
+ multi_label=True,
92
+ agnostic=self.args.single_cls,
93
+ max_det=self.args.max_det,
94
+ )
88
95
 
89
96
  def _prepare_batch(self, si, batch):
90
- idx = batch['batch_idx'] == si
91
- cls = batch['cls'][idx].squeeze(-1)
92
- bbox = batch['bboxes'][idx]
93
- ori_shape = batch['ori_shape'][si]
94
- imgsz = batch['img'].shape[2:]
95
- ratio_pad = batch['ratio_pad'][si]
97
+ """Prepares a batch of images and annotations for validation."""
98
+ idx = batch["batch_idx"] == si
99
+ cls = batch["cls"][idx].squeeze(-1)
100
+ bbox = batch["bboxes"][idx]
101
+ ori_shape = batch["ori_shape"][si]
102
+ imgsz = batch["img"].shape[2:]
103
+ ratio_pad = batch["ratio_pad"][si]
96
104
  if len(cls):
97
105
  bbox = ops.xywh2xyxy(bbox) * torch.tensor(imgsz, device=self.device)[[1, 0, 1, 0]] # target boxes
98
106
  ops.scale_boxes(imgsz, bbox, ori_shape, ratio_pad=ratio_pad) # native-space labels
@@ -100,9 +108,11 @@ class DetectionValidator(BaseValidator):
100
108
  return prepared_batch
101
109
 
102
110
  def _prepare_pred(self, pred, pbatch):
111
+ """Prepares a batch of images and annotations for validation."""
103
112
  predn = pred.clone()
104
- ops.scale_boxes(pbatch['imgsz'], predn[:, :4], pbatch['ori_shape'],
105
- ratio_pad=pbatch['ratio_pad']) # native-space pred
113
+ ops.scale_boxes(
114
+ pbatch["imgsz"], predn[:, :4], pbatch["ori_shape"], ratio_pad=pbatch["ratio_pad"]
115
+ ) # native-space pred
106
116
  return predn
107
117
 
108
118
  def update_metrics(self, preds, batch):
@@ -110,19 +120,21 @@ class DetectionValidator(BaseValidator):
110
120
  for si, pred in enumerate(preds):
111
121
  self.seen += 1
112
122
  npr = len(pred)
113
- stat = dict(conf=torch.zeros(0, device=self.device),
114
- pred_cls=torch.zeros(0, device=self.device),
115
- tp=torch.zeros(npr, self.niou, dtype=torch.bool, device=self.device))
123
+ stat = dict(
124
+ conf=torch.zeros(0, device=self.device),
125
+ pred_cls=torch.zeros(0, device=self.device),
126
+ tp=torch.zeros(npr, self.niou, dtype=torch.bool, device=self.device),
127
+ )
116
128
  pbatch = self._prepare_batch(si, batch)
117
- cls, bbox = pbatch.pop('cls'), pbatch.pop('bbox')
129
+ cls, bbox = pbatch.pop("cls"), pbatch.pop("bbox")
118
130
  nl = len(cls)
119
- stat['target_cls'] = cls
131
+ stat["target_cls"] = cls
120
132
  if npr == 0:
121
133
  if nl:
122
134
  for k in self.stats.keys():
123
135
  self.stats[k].append(stat[k])
124
136
  # TODO: obb has not supported confusion_matrix yet.
125
- if self.args.plots and self.args.task != 'obb':
137
+ if self.args.plots and self.args.task != "obb":
126
138
  self.confusion_matrix.process_batch(detections=None, gt_bboxes=bbox, gt_cls=cls)
127
139
  continue
128
140
 
@@ -130,24 +142,24 @@ class DetectionValidator(BaseValidator):
130
142
  if self.args.single_cls:
131
143
  pred[:, 5] = 0
132
144
  predn = self._prepare_pred(pred, pbatch)
133
- stat['conf'] = predn[:, 4]
134
- stat['pred_cls'] = predn[:, 5]
145
+ stat["conf"] = predn[:, 4]
146
+ stat["pred_cls"] = predn[:, 5]
135
147
 
136
148
  # Evaluate
137
149
  if nl:
138
- stat['tp'] = self._process_batch(predn, bbox, cls)
150
+ stat["tp"] = self._process_batch(predn, bbox, cls)
139
151
  # TODO: obb has not supported confusion_matrix yet.
140
- if self.args.plots and self.args.task != 'obb':
152
+ if self.args.plots and self.args.task != "obb":
141
153
  self.confusion_matrix.process_batch(predn, bbox, cls)
142
154
  for k in self.stats.keys():
143
155
  self.stats[k].append(stat[k])
144
156
 
145
157
  # Save
146
158
  if self.args.save_json:
147
- self.pred_to_json(predn, batch['im_file'][si])
159
+ self.pred_to_json(predn, batch["im_file"][si])
148
160
  if self.args.save_txt:
149
- file = self.save_dir / 'labels' / f'{Path(batch["im_file"][si]).stem}.txt'
150
- self.save_one_txt(predn, self.args.save_conf, pbatch['ori_shape'], file)
161
+ file = self.save_dir / "labels" / f'{Path(batch["im_file"][si]).stem}.txt'
162
+ self.save_one_txt(predn, self.args.save_conf, pbatch["ori_shape"], file)
151
163
 
152
164
  def finalize_metrics(self, *args, **kwargs):
153
165
  """Set final values for metrics speed and confusion matrix."""
@@ -157,19 +169,19 @@ class DetectionValidator(BaseValidator):
157
169
  def get_stats(self):
158
170
  """Returns metrics statistics and results dictionary."""
159
171
  stats = {k: torch.cat(v, 0).cpu().numpy() for k, v in self.stats.items()} # to numpy
160
- if len(stats) and stats['tp'].any():
172
+ if len(stats) and stats["tp"].any():
161
173
  self.metrics.process(**stats)
162
- self.nt_per_class = np.bincount(stats['target_cls'].astype(int),
163
- minlength=self.nc) # number of targets per class
174
+ self.nt_per_class = np.bincount(
175
+ stats["target_cls"].astype(int), minlength=self.nc
176
+ ) # number of targets per class
164
177
  return self.metrics.results_dict
165
178
 
166
179
  def print_results(self):
167
180
  """Prints training/validation set metrics per class."""
168
- pf = '%22s' + '%11i' * 2 + '%11.3g' * len(self.metrics.keys) # print format
169
- LOGGER.info(pf % ('all', self.seen, self.nt_per_class.sum(), *self.metrics.mean_results()))
181
+ pf = "%22s" + "%11i" * 2 + "%11.3g" * len(self.metrics.keys) # print format
182
+ LOGGER.info(pf % ("all", self.seen, self.nt_per_class.sum(), *self.metrics.mean_results()))
170
183
  if self.nt_per_class.sum() == 0:
171
- LOGGER.warning(
172
- f'WARNING ⚠️ no labels found in {self.args.task} set, can not compute metrics without labels')
184
+ LOGGER.warning(f"WARNING ⚠️ no labels found in {self.args.task} set, can not compute metrics without labels")
173
185
 
174
186
  # Print results per class
175
187
  if self.args.verbose and not self.training and self.nc > 1 and len(self.stats):
@@ -178,10 +190,9 @@ class DetectionValidator(BaseValidator):
178
190
 
179
191
  if self.args.plots:
180
192
  for normalize in True, False:
181
- self.confusion_matrix.plot(save_dir=self.save_dir,
182
- names=self.names.values(),
183
- normalize=normalize,
184
- on_plot=self.on_plot)
193
+ self.confusion_matrix.plot(
194
+ save_dir=self.save_dir, names=self.names.values(), normalize=normalize, on_plot=self.on_plot
195
+ )
185
196
 
186
197
  def _process_batch(self, detections, gt_bboxes, gt_cls):
187
198
  """
@@ -199,7 +210,7 @@ class DetectionValidator(BaseValidator):
199
210
  iou = box_iou(gt_bboxes, detections[:, :4])
200
211
  return self.match_predictions(detections[:, 5], gt_cls, iou)
201
212
 
202
- def build_dataset(self, img_path, mode='val', batch=None):
213
+ def build_dataset(self, img_path, mode="val", batch=None):
203
214
  """
204
215
  Build YOLO Dataset.
205
216
 
@@ -212,28 +223,32 @@ class DetectionValidator(BaseValidator):
212
223
 
213
224
  def get_dataloader(self, dataset_path, batch_size):
214
225
  """Construct and return dataloader."""
215
- dataset = self.build_dataset(dataset_path, batch=batch_size, mode='val')
226
+ dataset = self.build_dataset(dataset_path, batch=batch_size, mode="val")
216
227
  return build_dataloader(dataset, batch_size, self.args.workers, shuffle=False, rank=-1) # return dataloader
217
228
 
218
229
  def plot_val_samples(self, batch, ni):
219
230
  """Plot validation image samples."""
220
- plot_images(batch['img'],
221
- batch['batch_idx'],
222
- batch['cls'].squeeze(-1),
223
- batch['bboxes'],
224
- paths=batch['im_file'],
225
- fname=self.save_dir / f'val_batch{ni}_labels.jpg',
226
- names=self.names,
227
- on_plot=self.on_plot)
231
+ plot_images(
232
+ batch["img"],
233
+ batch["batch_idx"],
234
+ batch["cls"].squeeze(-1),
235
+ batch["bboxes"],
236
+ paths=batch["im_file"],
237
+ fname=self.save_dir / f"val_batch{ni}_labels.jpg",
238
+ names=self.names,
239
+ on_plot=self.on_plot,
240
+ )
228
241
 
229
242
  def plot_predictions(self, batch, preds, ni):
230
243
  """Plots predicted bounding boxes on input images and saves the result."""
231
- plot_images(batch['img'],
232
- *output_to_target(preds, max_det=self.args.max_det),
233
- paths=batch['im_file'],
234
- fname=self.save_dir / f'val_batch{ni}_pred.jpg',
235
- names=self.names,
236
- on_plot=self.on_plot) # pred
244
+ plot_images(
245
+ batch["img"],
246
+ *output_to_target(preds, max_det=self.args.max_det),
247
+ paths=batch["im_file"],
248
+ fname=self.save_dir / f"val_batch{ni}_pred.jpg",
249
+ names=self.names,
250
+ on_plot=self.on_plot,
251
+ ) # pred
237
252
 
238
253
  def save_one_txt(self, predn, save_conf, shape, file):
239
254
  """Save YOLO detections to a txt file in normalized coordinates in a specific format."""
@@ -241,8 +256,8 @@ class DetectionValidator(BaseValidator):
241
256
  for *xyxy, conf, cls in predn.tolist():
242
257
  xywh = (ops.xyxy2xywh(torch.tensor(xyxy).view(1, 4)) / gn).view(-1).tolist() # normalized xywh
243
258
  line = (cls, *xywh, conf) if save_conf else (cls, *xywh) # label format
244
- with open(file, 'a') as f:
245
- f.write(('%g ' * len(line)).rstrip() % line + '\n')
259
+ with open(file, "a") as f:
260
+ f.write(("%g " * len(line)).rstrip() % line + "\n")
246
261
 
247
262
  def pred_to_json(self, predn, filename):
248
263
  """Serialize YOLO predictions to COCO json format."""
@@ -251,28 +266,31 @@ class DetectionValidator(BaseValidator):
251
266
  box = ops.xyxy2xywh(predn[:, :4]) # xywh
252
267
  box[:, :2] -= box[:, 2:] / 2 # xy center to top-left corner
253
268
  for p, b in zip(predn.tolist(), box.tolist()):
254
- self.jdict.append({
255
- 'image_id': image_id,
256
- 'category_id': self.class_map[int(p[5])],
257
- 'bbox': [round(x, 3) for x in b],
258
- 'score': round(p[4], 5)})
269
+ self.jdict.append(
270
+ {
271
+ "image_id": image_id,
272
+ "category_id": self.class_map[int(p[5])],
273
+ "bbox": [round(x, 3) for x in b],
274
+ "score": round(p[4], 5),
275
+ }
276
+ )
259
277
 
260
278
  def eval_json(self, stats):
261
279
  """Evaluates YOLO output in JSON format and returns performance statistics."""
262
280
  if self.args.save_json and self.is_coco and len(self.jdict):
263
- anno_json = self.data['path'] / 'annotations/instances_val2017.json' # annotations
264
- pred_json = self.save_dir / 'predictions.json' # predictions
265
- LOGGER.info(f'\nEvaluating pycocotools mAP using {pred_json} and {anno_json}...')
281
+ anno_json = self.data["path"] / "annotations/instances_val2017.json" # annotations
282
+ pred_json = self.save_dir / "predictions.json" # predictions
283
+ LOGGER.info(f"\nEvaluating pycocotools mAP using {pred_json} and {anno_json}...")
266
284
  try: # https://github.com/cocodataset/cocoapi/blob/master/PythonAPI/pycocoEvalDemo.ipynb
267
- check_requirements('pycocotools>=2.0.6')
285
+ check_requirements("pycocotools>=2.0.6")
268
286
  from pycocotools.coco import COCO # noqa
269
287
  from pycocotools.cocoeval import COCOeval # noqa
270
288
 
271
289
  for x in anno_json, pred_json:
272
- assert x.is_file(), f'{x} file not found'
290
+ assert x.is_file(), f"{x} file not found"
273
291
  anno = COCO(str(anno_json)) # init annotations api
274
292
  pred = anno.loadRes(str(pred_json)) # init predictions api (must pass string, not Path)
275
- eval = COCOeval(anno, pred, 'bbox')
293
+ eval = COCOeval(anno, pred, "bbox")
276
294
  if self.is_coco:
277
295
  eval.params.imgIds = [int(Path(x).stem) for x in self.dataloader.dataset.im_files] # images to eval
278
296
  eval.evaluate()
@@ -280,5 +298,5 @@ class DetectionValidator(BaseValidator):
280
298
  eval.summarize()
281
299
  stats[self.metrics.keys[-1]], stats[self.metrics.keys[-2]] = eval.stats[:2] # update mAP50-95 and mAP50
282
300
  except Exception as e:
283
- LOGGER.warning(f'pycocotools unable to run: {e}')
301
+ LOGGER.warning(f"pycocotools unable to run: {e}")
284
302
  return stats