dgenerate-ultralytics-headless 8.3.137__py3-none-any.whl → 8.3.224__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.
Files changed (215) hide show
  1. {dgenerate_ultralytics_headless-8.3.137.dist-info → dgenerate_ultralytics_headless-8.3.224.dist-info}/METADATA +41 -34
  2. dgenerate_ultralytics_headless-8.3.224.dist-info/RECORD +285 -0
  3. {dgenerate_ultralytics_headless-8.3.137.dist-info → dgenerate_ultralytics_headless-8.3.224.dist-info}/WHEEL +1 -1
  4. tests/__init__.py +7 -6
  5. tests/conftest.py +15 -39
  6. tests/test_cli.py +17 -17
  7. tests/test_cuda.py +17 -8
  8. tests/test_engine.py +36 -10
  9. tests/test_exports.py +98 -37
  10. tests/test_integrations.py +12 -15
  11. tests/test_python.py +126 -82
  12. tests/test_solutions.py +319 -135
  13. ultralytics/__init__.py +27 -9
  14. ultralytics/cfg/__init__.py +83 -87
  15. ultralytics/cfg/datasets/Argoverse.yaml +4 -4
  16. ultralytics/cfg/datasets/DOTAv1.5.yaml +2 -2
  17. ultralytics/cfg/datasets/DOTAv1.yaml +2 -2
  18. ultralytics/cfg/datasets/GlobalWheat2020.yaml +2 -2
  19. ultralytics/cfg/datasets/HomeObjects-3K.yaml +4 -5
  20. ultralytics/cfg/datasets/ImageNet.yaml +3 -3
  21. ultralytics/cfg/datasets/Objects365.yaml +24 -20
  22. ultralytics/cfg/datasets/SKU-110K.yaml +9 -9
  23. ultralytics/cfg/datasets/VOC.yaml +10 -13
  24. ultralytics/cfg/datasets/VisDrone.yaml +43 -33
  25. ultralytics/cfg/datasets/african-wildlife.yaml +5 -5
  26. ultralytics/cfg/datasets/brain-tumor.yaml +4 -5
  27. ultralytics/cfg/datasets/carparts-seg.yaml +5 -5
  28. ultralytics/cfg/datasets/coco-pose.yaml +26 -4
  29. ultralytics/cfg/datasets/coco.yaml +4 -4
  30. ultralytics/cfg/datasets/coco128-seg.yaml +2 -2
  31. ultralytics/cfg/datasets/coco128.yaml +2 -2
  32. ultralytics/cfg/datasets/coco8-grayscale.yaml +103 -0
  33. ultralytics/cfg/datasets/coco8-multispectral.yaml +2 -2
  34. ultralytics/cfg/datasets/coco8-pose.yaml +23 -2
  35. ultralytics/cfg/datasets/coco8-seg.yaml +2 -2
  36. ultralytics/cfg/datasets/coco8.yaml +2 -2
  37. ultralytics/cfg/datasets/construction-ppe.yaml +32 -0
  38. ultralytics/cfg/datasets/crack-seg.yaml +5 -5
  39. ultralytics/cfg/datasets/dog-pose.yaml +32 -4
  40. ultralytics/cfg/datasets/dota8-multispectral.yaml +2 -2
  41. ultralytics/cfg/datasets/dota8.yaml +2 -2
  42. ultralytics/cfg/datasets/hand-keypoints.yaml +29 -4
  43. ultralytics/cfg/datasets/lvis.yaml +9 -9
  44. ultralytics/cfg/datasets/medical-pills.yaml +4 -5
  45. ultralytics/cfg/datasets/open-images-v7.yaml +7 -10
  46. ultralytics/cfg/datasets/package-seg.yaml +5 -5
  47. ultralytics/cfg/datasets/signature.yaml +4 -4
  48. ultralytics/cfg/datasets/tiger-pose.yaml +20 -4
  49. ultralytics/cfg/datasets/xView.yaml +5 -5
  50. ultralytics/cfg/default.yaml +96 -93
  51. ultralytics/cfg/trackers/botsort.yaml +16 -17
  52. ultralytics/cfg/trackers/bytetrack.yaml +9 -11
  53. ultralytics/data/__init__.py +4 -4
  54. ultralytics/data/annotator.py +12 -12
  55. ultralytics/data/augment.py +531 -564
  56. ultralytics/data/base.py +76 -81
  57. ultralytics/data/build.py +206 -42
  58. ultralytics/data/converter.py +179 -78
  59. ultralytics/data/dataset.py +121 -121
  60. ultralytics/data/loaders.py +114 -91
  61. ultralytics/data/split.py +28 -15
  62. ultralytics/data/split_dota.py +67 -48
  63. ultralytics/data/utils.py +110 -89
  64. ultralytics/engine/exporter.py +422 -460
  65. ultralytics/engine/model.py +224 -252
  66. ultralytics/engine/predictor.py +94 -89
  67. ultralytics/engine/results.py +345 -595
  68. ultralytics/engine/trainer.py +231 -134
  69. ultralytics/engine/tuner.py +279 -73
  70. ultralytics/engine/validator.py +53 -46
  71. ultralytics/hub/__init__.py +26 -28
  72. ultralytics/hub/auth.py +30 -16
  73. ultralytics/hub/google/__init__.py +34 -36
  74. ultralytics/hub/session.py +53 -77
  75. ultralytics/hub/utils.py +23 -109
  76. ultralytics/models/__init__.py +1 -1
  77. ultralytics/models/fastsam/__init__.py +1 -1
  78. ultralytics/models/fastsam/model.py +36 -18
  79. ultralytics/models/fastsam/predict.py +33 -44
  80. ultralytics/models/fastsam/utils.py +4 -5
  81. ultralytics/models/fastsam/val.py +12 -14
  82. ultralytics/models/nas/__init__.py +1 -1
  83. ultralytics/models/nas/model.py +16 -20
  84. ultralytics/models/nas/predict.py +12 -14
  85. ultralytics/models/nas/val.py +4 -5
  86. ultralytics/models/rtdetr/__init__.py +1 -1
  87. ultralytics/models/rtdetr/model.py +9 -9
  88. ultralytics/models/rtdetr/predict.py +22 -17
  89. ultralytics/models/rtdetr/train.py +20 -16
  90. ultralytics/models/rtdetr/val.py +79 -59
  91. ultralytics/models/sam/__init__.py +8 -2
  92. ultralytics/models/sam/amg.py +53 -38
  93. ultralytics/models/sam/build.py +29 -31
  94. ultralytics/models/sam/model.py +33 -38
  95. ultralytics/models/sam/modules/blocks.py +159 -182
  96. ultralytics/models/sam/modules/decoders.py +38 -47
  97. ultralytics/models/sam/modules/encoders.py +114 -133
  98. ultralytics/models/sam/modules/memory_attention.py +38 -31
  99. ultralytics/models/sam/modules/sam.py +114 -93
  100. ultralytics/models/sam/modules/tiny_encoder.py +268 -291
  101. ultralytics/models/sam/modules/transformer.py +59 -66
  102. ultralytics/models/sam/modules/utils.py +55 -72
  103. ultralytics/models/sam/predict.py +745 -341
  104. ultralytics/models/utils/loss.py +118 -107
  105. ultralytics/models/utils/ops.py +118 -71
  106. ultralytics/models/yolo/__init__.py +1 -1
  107. ultralytics/models/yolo/classify/predict.py +28 -26
  108. ultralytics/models/yolo/classify/train.py +50 -81
  109. ultralytics/models/yolo/classify/val.py +68 -61
  110. ultralytics/models/yolo/detect/predict.py +12 -15
  111. ultralytics/models/yolo/detect/train.py +56 -46
  112. ultralytics/models/yolo/detect/val.py +279 -223
  113. ultralytics/models/yolo/model.py +167 -86
  114. ultralytics/models/yolo/obb/predict.py +7 -11
  115. ultralytics/models/yolo/obb/train.py +23 -25
  116. ultralytics/models/yolo/obb/val.py +107 -99
  117. ultralytics/models/yolo/pose/__init__.py +1 -1
  118. ultralytics/models/yolo/pose/predict.py +12 -14
  119. ultralytics/models/yolo/pose/train.py +31 -69
  120. ultralytics/models/yolo/pose/val.py +119 -254
  121. ultralytics/models/yolo/segment/predict.py +21 -25
  122. ultralytics/models/yolo/segment/train.py +12 -66
  123. ultralytics/models/yolo/segment/val.py +126 -305
  124. ultralytics/models/yolo/world/train.py +53 -45
  125. ultralytics/models/yolo/world/train_world.py +51 -32
  126. ultralytics/models/yolo/yoloe/__init__.py +7 -7
  127. ultralytics/models/yolo/yoloe/predict.py +30 -37
  128. ultralytics/models/yolo/yoloe/train.py +89 -71
  129. ultralytics/models/yolo/yoloe/train_seg.py +15 -17
  130. ultralytics/models/yolo/yoloe/val.py +56 -41
  131. ultralytics/nn/__init__.py +9 -11
  132. ultralytics/nn/autobackend.py +179 -107
  133. ultralytics/nn/modules/__init__.py +67 -67
  134. ultralytics/nn/modules/activation.py +8 -7
  135. ultralytics/nn/modules/block.py +302 -323
  136. ultralytics/nn/modules/conv.py +61 -104
  137. ultralytics/nn/modules/head.py +488 -186
  138. ultralytics/nn/modules/transformer.py +183 -123
  139. ultralytics/nn/modules/utils.py +15 -20
  140. ultralytics/nn/tasks.py +327 -203
  141. ultralytics/nn/text_model.py +81 -65
  142. ultralytics/py.typed +1 -0
  143. ultralytics/solutions/__init__.py +12 -12
  144. ultralytics/solutions/ai_gym.py +19 -27
  145. ultralytics/solutions/analytics.py +36 -26
  146. ultralytics/solutions/config.py +29 -28
  147. ultralytics/solutions/distance_calculation.py +23 -24
  148. ultralytics/solutions/heatmap.py +17 -19
  149. ultralytics/solutions/instance_segmentation.py +21 -19
  150. ultralytics/solutions/object_blurrer.py +16 -17
  151. ultralytics/solutions/object_counter.py +48 -53
  152. ultralytics/solutions/object_cropper.py +22 -16
  153. ultralytics/solutions/parking_management.py +61 -58
  154. ultralytics/solutions/queue_management.py +19 -19
  155. ultralytics/solutions/region_counter.py +63 -50
  156. ultralytics/solutions/security_alarm.py +22 -25
  157. ultralytics/solutions/similarity_search.py +107 -60
  158. ultralytics/solutions/solutions.py +343 -262
  159. ultralytics/solutions/speed_estimation.py +35 -31
  160. ultralytics/solutions/streamlit_inference.py +104 -40
  161. ultralytics/solutions/templates/similarity-search.html +31 -24
  162. ultralytics/solutions/trackzone.py +24 -24
  163. ultralytics/solutions/vision_eye.py +11 -12
  164. ultralytics/trackers/__init__.py +1 -1
  165. ultralytics/trackers/basetrack.py +18 -27
  166. ultralytics/trackers/bot_sort.py +48 -39
  167. ultralytics/trackers/byte_tracker.py +94 -94
  168. ultralytics/trackers/track.py +7 -16
  169. ultralytics/trackers/utils/gmc.py +37 -69
  170. ultralytics/trackers/utils/kalman_filter.py +68 -76
  171. ultralytics/trackers/utils/matching.py +13 -17
  172. ultralytics/utils/__init__.py +251 -275
  173. ultralytics/utils/autobatch.py +19 -7
  174. ultralytics/utils/autodevice.py +68 -38
  175. ultralytics/utils/benchmarks.py +169 -130
  176. ultralytics/utils/callbacks/base.py +12 -13
  177. ultralytics/utils/callbacks/clearml.py +14 -15
  178. ultralytics/utils/callbacks/comet.py +139 -66
  179. ultralytics/utils/callbacks/dvc.py +19 -27
  180. ultralytics/utils/callbacks/hub.py +8 -6
  181. ultralytics/utils/callbacks/mlflow.py +6 -10
  182. ultralytics/utils/callbacks/neptune.py +11 -19
  183. ultralytics/utils/callbacks/platform.py +73 -0
  184. ultralytics/utils/callbacks/raytune.py +3 -4
  185. ultralytics/utils/callbacks/tensorboard.py +9 -12
  186. ultralytics/utils/callbacks/wb.py +33 -30
  187. ultralytics/utils/checks.py +163 -114
  188. ultralytics/utils/cpu.py +89 -0
  189. ultralytics/utils/dist.py +24 -20
  190. ultralytics/utils/downloads.py +176 -146
  191. ultralytics/utils/errors.py +11 -13
  192. ultralytics/utils/events.py +113 -0
  193. ultralytics/utils/export/__init__.py +7 -0
  194. ultralytics/utils/{export.py → export/engine.py} +81 -63
  195. ultralytics/utils/export/imx.py +294 -0
  196. ultralytics/utils/export/tensorflow.py +217 -0
  197. ultralytics/utils/files.py +33 -36
  198. ultralytics/utils/git.py +137 -0
  199. ultralytics/utils/instance.py +105 -120
  200. ultralytics/utils/logger.py +404 -0
  201. ultralytics/utils/loss.py +99 -61
  202. ultralytics/utils/metrics.py +649 -478
  203. ultralytics/utils/nms.py +337 -0
  204. ultralytics/utils/ops.py +263 -451
  205. ultralytics/utils/patches.py +70 -31
  206. ultralytics/utils/plotting.py +253 -223
  207. ultralytics/utils/tal.py +48 -61
  208. ultralytics/utils/torch_utils.py +244 -251
  209. ultralytics/utils/tqdm.py +438 -0
  210. ultralytics/utils/triton.py +22 -23
  211. ultralytics/utils/tuner.py +11 -10
  212. dgenerate_ultralytics_headless-8.3.137.dist-info/RECORD +0 -272
  213. {dgenerate_ultralytics_headless-8.3.137.dist-info → dgenerate_ultralytics_headless-8.3.224.dist-info}/entry_points.txt +0 -0
  214. {dgenerate_ultralytics_headless-8.3.137.dist-info → dgenerate_ultralytics_headless-8.3.224.dist-info}/licenses/LICENSE +0 -0
  215. {dgenerate_ultralytics_headless-8.3.137.dist-info → dgenerate_ultralytics_headless-8.3.224.dist-info}/top_level.txt +0 -0
@@ -1,38 +1,39 @@
1
1
  # Ultralytics 🚀 AGPL-3.0 License - https://ultralytics.com/license
2
2
 
3
+ from __future__ import annotations
4
+
3
5
  import os
4
6
  from pathlib import Path
7
+ from typing import Any
5
8
 
6
9
  import numpy as np
7
10
  import torch
11
+ import torch.distributed as dist
8
12
 
9
13
  from ultralytics.data import build_dataloader, build_yolo_dataset, converter
10
14
  from ultralytics.engine.validator import BaseValidator
11
- from ultralytics.utils import LOGGER, ops
15
+ from ultralytics.utils import LOGGER, RANK, nms, ops
12
16
  from ultralytics.utils.checks import check_requirements
13
17
  from ultralytics.utils.metrics import ConfusionMatrix, DetMetrics, box_iou
14
- from ultralytics.utils.plotting import output_to_target, plot_images
18
+ from ultralytics.utils.plotting import plot_images
15
19
 
16
20
 
17
21
  class DetectionValidator(BaseValidator):
18
- """
19
- A class extending the BaseValidator class for validation based on a detection model.
22
+ """A class extending the BaseValidator class for validation based on a detection model.
20
23
 
21
24
  This class implements validation functionality specific to object detection tasks, including metrics calculation,
22
25
  prediction processing, and visualization of results.
23
26
 
24
27
  Attributes:
25
- nt_per_class (np.ndarray): Number of targets per class.
26
- nt_per_image (np.ndarray): Number of targets per image.
27
28
  is_coco (bool): Whether the dataset is COCO.
28
29
  is_lvis (bool): Whether the dataset is LVIS.
29
- class_map (list): Mapping from model class indices to dataset class indices.
30
+ class_map (list[int]): Mapping from model class indices to dataset class indices.
30
31
  metrics (DetMetrics): Object detection metrics calculator.
31
32
  iouv (torch.Tensor): IoU thresholds for mAP calculation.
32
33
  niou (int): Number of IoU thresholds.
33
- lb (list): List for storing ground truth labels for hybrid saving.
34
- jdict (list): List for storing JSON detection results.
35
- stats (dict): Dictionary for storing statistics during validation.
34
+ lb (list[Any]): List for storing ground truth labels for hybrid saving.
35
+ jdict (list[dict[str, Any]]): List for storing JSON detection results.
36
+ stats (dict[str, list[torch.Tensor]]): Dictionary for storing statistics during validation.
36
37
 
37
38
  Examples:
38
39
  >>> from ultralytics.models.yolo.detect import DetectionValidator
@@ -41,48 +42,41 @@ class DetectionValidator(BaseValidator):
41
42
  >>> validator()
42
43
  """
43
44
 
44
- def __init__(self, dataloader=None, save_dir=None, pbar=None, args=None, _callbacks=None):
45
- """
46
- Initialize detection validator with necessary variables and settings.
45
+ def __init__(self, dataloader=None, save_dir=None, args=None, _callbacks=None) -> None:
46
+ """Initialize detection validator with necessary variables and settings.
47
47
 
48
48
  Args:
49
49
  dataloader (torch.utils.data.DataLoader, optional): Dataloader to use for validation.
50
50
  save_dir (Path, optional): Directory to save results.
51
- pbar (Any, optional): Progress bar for displaying progress.
52
- args (dict, optional): Arguments for the validator.
53
- _callbacks (list, optional): List of callback functions.
51
+ args (dict[str, Any], optional): Arguments for the validator.
52
+ _callbacks (list[Any], optional): List of callback functions.
54
53
  """
55
- super().__init__(dataloader, save_dir, pbar, args, _callbacks)
56
- self.nt_per_class = None
57
- self.nt_per_image = None
54
+ super().__init__(dataloader, save_dir, args, _callbacks)
58
55
  self.is_coco = False
59
56
  self.is_lvis = False
60
57
  self.class_map = None
61
58
  self.args.task = "detect"
62
- self.metrics = DetMetrics(save_dir=self.save_dir)
63
59
  self.iouv = torch.linspace(0.5, 0.95, 10) # IoU vector for mAP@0.5:0.95
64
60
  self.niou = self.iouv.numel()
61
+ self.metrics = DetMetrics()
65
62
 
66
- def preprocess(self, batch):
67
- """
68
- Preprocess batch of images for YOLO validation.
63
+ def preprocess(self, batch: dict[str, Any]) -> dict[str, Any]:
64
+ """Preprocess batch of images for YOLO validation.
69
65
 
70
66
  Args:
71
- batch (dict): Batch containing images and annotations.
67
+ batch (dict[str, Any]): Batch containing images and annotations.
72
68
 
73
69
  Returns:
74
- (dict): Preprocessed batch.
70
+ (dict[str, Any]): Preprocessed batch.
75
71
  """
76
- batch["img"] = batch["img"].to(self.device, non_blocking=True)
72
+ for k, v in batch.items():
73
+ if isinstance(v, torch.Tensor):
74
+ batch[k] = v.to(self.device, non_blocking=self.device.type == "cuda")
77
75
  batch["img"] = (batch["img"].half() if self.args.half else batch["img"].float()) / 255
78
- for k in ["batch_idx", "cls", "bboxes"]:
79
- batch[k] = batch[k].to(self.device)
80
-
81
76
  return batch
82
77
 
83
- def init_metrics(self, model):
84
- """
85
- Initialize evaluation metrics for YOLO detection validation.
78
+ def init_metrics(self, model: torch.nn.Module) -> None:
79
+ """Initialize evaluation metrics for YOLO detection validation.
86
80
 
87
81
  Args:
88
82
  model (torch.nn.Module): Model to validate.
@@ -99,28 +93,26 @@ class DetectionValidator(BaseValidator):
99
93
  self.names = model.names
100
94
  self.nc = len(model.names)
101
95
  self.end2end = getattr(model, "end2end", False)
102
- self.metrics.names = self.names
103
- self.metrics.plot = self.args.plots
104
- self.confusion_matrix = ConfusionMatrix(nc=self.nc, conf=self.args.conf)
105
96
  self.seen = 0
106
97
  self.jdict = []
107
- self.stats = dict(tp=[], conf=[], pred_cls=[], target_cls=[], target_img=[])
98
+ self.metrics.names = model.names
99
+ self.confusion_matrix = ConfusionMatrix(names=model.names, save_matches=self.args.plots and self.args.visualize)
108
100
 
109
- def get_desc(self):
101
+ def get_desc(self) -> str:
110
102
  """Return a formatted string summarizing class metrics of YOLO model."""
111
103
  return ("%22s" + "%11s" * 6) % ("Class", "Images", "Instances", "Box(P", "R", "mAP50", "mAP50-95)")
112
104
 
113
- def postprocess(self, preds):
114
- """
115
- Apply Non-maximum suppression to prediction outputs.
105
+ def postprocess(self, preds: torch.Tensor) -> list[dict[str, torch.Tensor]]:
106
+ """Apply Non-maximum suppression to prediction outputs.
116
107
 
117
108
  Args:
118
109
  preds (torch.Tensor): Raw predictions from the model.
119
110
 
120
111
  Returns:
121
- (List[torch.Tensor]): Processed predictions after NMS.
112
+ (list[dict[str, torch.Tensor]]): Processed predictions after NMS, where each dict contains 'bboxes', 'conf',
113
+ 'cls', and 'extra' tensors.
122
114
  """
123
- return ops.non_max_suppression(
115
+ outputs = nms.non_max_suppression(
124
116
  preds,
125
117
  self.args.conf,
126
118
  self.args.iou,
@@ -131,17 +123,17 @@ class DetectionValidator(BaseValidator):
131
123
  end2end=self.end2end,
132
124
  rotated=self.args.task == "obb",
133
125
  )
126
+ return [{"bboxes": x[:, :4], "conf": x[:, 4], "cls": x[:, 5], "extra": x[:, 6:]} for x in outputs]
134
127
 
135
- def _prepare_batch(self, si, batch):
136
- """
137
- Prepare a batch of images and annotations for validation.
128
+ def _prepare_batch(self, si: int, batch: dict[str, Any]) -> dict[str, Any]:
129
+ """Prepare a batch of images and annotations for validation.
138
130
 
139
131
  Args:
140
132
  si (int): Batch index.
141
- batch (dict): Batch data containing images and annotations.
133
+ batch (dict[str, Any]): Batch data containing images and annotations.
142
134
 
143
135
  Returns:
144
- (dict): Prepared batch with processed annotations.
136
+ (dict[str, Any]): Prepared batch with processed annotations.
145
137
  """
146
138
  idx = batch["batch_idx"] == si
147
139
  cls = batch["cls"][idx].squeeze(-1)
@@ -149,149 +141,154 @@ class DetectionValidator(BaseValidator):
149
141
  ori_shape = batch["ori_shape"][si]
150
142
  imgsz = batch["img"].shape[2:]
151
143
  ratio_pad = batch["ratio_pad"][si]
152
- if len(cls):
144
+ if cls.shape[0]:
153
145
  bbox = ops.xywh2xyxy(bbox) * torch.tensor(imgsz, device=self.device)[[1, 0, 1, 0]] # target boxes
154
- ops.scale_boxes(imgsz, bbox, ori_shape, ratio_pad=ratio_pad) # native-space labels
155
- return {"cls": cls, "bbox": bbox, "ori_shape": ori_shape, "imgsz": imgsz, "ratio_pad": ratio_pad}
156
-
157
- def _prepare_pred(self, pred, pbatch):
158
- """
159
- Prepare predictions for evaluation against ground truth.
146
+ return {
147
+ "cls": cls,
148
+ "bboxes": bbox,
149
+ "ori_shape": ori_shape,
150
+ "imgsz": imgsz,
151
+ "ratio_pad": ratio_pad,
152
+ "im_file": batch["im_file"][si],
153
+ }
154
+
155
+ def _prepare_pred(self, pred: dict[str, torch.Tensor]) -> dict[str, torch.Tensor]:
156
+ """Prepare predictions for evaluation against ground truth.
160
157
 
161
158
  Args:
162
- pred (torch.Tensor): Model predictions.
163
- pbatch (dict): Prepared batch information.
159
+ pred (dict[str, torch.Tensor]): Post-processed predictions from the model.
164
160
 
165
161
  Returns:
166
- (torch.Tensor): Prepared predictions in native space.
162
+ (dict[str, torch.Tensor]): Prepared predictions in native space.
167
163
  """
168
- predn = pred.clone()
169
- ops.scale_boxes(
170
- pbatch["imgsz"], predn[:, :4], pbatch["ori_shape"], ratio_pad=pbatch["ratio_pad"]
171
- ) # native-space pred
172
- return predn
164
+ if self.args.single_cls:
165
+ pred["cls"] *= 0
166
+ return pred
173
167
 
174
- def update_metrics(self, preds, batch):
175
- """
176
- Update metrics with new predictions and ground truth.
168
+ def update_metrics(self, preds: list[dict[str, torch.Tensor]], batch: dict[str, Any]) -> None:
169
+ """Update metrics with new predictions and ground truth.
177
170
 
178
171
  Args:
179
- preds (List[torch.Tensor]): List of predictions from the model.
180
- batch (dict): Batch data containing ground truth.
172
+ preds (list[dict[str, torch.Tensor]]): List of predictions from the model.
173
+ batch (dict[str, Any]): Batch data containing ground truth.
181
174
  """
182
175
  for si, pred in enumerate(preds):
183
176
  self.seen += 1
184
- npr = len(pred)
185
- stat = dict(
186
- conf=torch.zeros(0, device=self.device),
187
- pred_cls=torch.zeros(0, device=self.device),
188
- tp=torch.zeros(npr, self.niou, dtype=torch.bool, device=self.device),
189
- )
190
177
  pbatch = self._prepare_batch(si, batch)
191
- cls, bbox = pbatch.pop("cls"), pbatch.pop("bbox")
192
- nl = len(cls)
193
- stat["target_cls"] = cls
194
- stat["target_img"] = cls.unique()
195
- if npr == 0:
196
- if nl:
197
- for k in self.stats.keys():
198
- self.stats[k].append(stat[k])
199
- if self.args.plots:
200
- self.confusion_matrix.process_batch(detections=None, gt_bboxes=bbox, gt_cls=cls)
201
- continue
202
-
203
- # Predictions
204
- if self.args.single_cls:
205
- pred[:, 5] = 0
206
- predn = self._prepare_pred(pred, pbatch)
207
- stat["conf"] = predn[:, 4]
208
- stat["pred_cls"] = predn[:, 5]
178
+ predn = self._prepare_pred(pred)
209
179
 
180
+ cls = pbatch["cls"].cpu().numpy()
181
+ no_pred = predn["cls"].shape[0] == 0
182
+ self.metrics.update_stats(
183
+ {
184
+ **self._process_batch(predn, pbatch),
185
+ "target_cls": cls,
186
+ "target_img": np.unique(cls),
187
+ "conf": np.zeros(0) if no_pred else predn["conf"].cpu().numpy(),
188
+ "pred_cls": np.zeros(0) if no_pred else predn["cls"].cpu().numpy(),
189
+ }
190
+ )
210
191
  # Evaluate
211
- if nl:
212
- stat["tp"] = self._process_batch(predn, bbox, cls)
213
192
  if self.args.plots:
214
- self.confusion_matrix.process_batch(predn, bbox, cls)
215
- for k in self.stats.keys():
216
- self.stats[k].append(stat[k])
193
+ self.confusion_matrix.process_batch(predn, pbatch, conf=self.args.conf)
194
+ if self.args.visualize:
195
+ self.confusion_matrix.plot_matches(batch["img"][si], pbatch["im_file"], self.save_dir)
196
+
197
+ if no_pred:
198
+ continue
217
199
 
218
200
  # Save
201
+ if self.args.save_json or self.args.save_txt:
202
+ predn_scaled = self.scale_preds(predn, pbatch)
219
203
  if self.args.save_json:
220
- self.pred_to_json(predn, batch["im_file"][si])
204
+ self.pred_to_json(predn_scaled, pbatch)
221
205
  if self.args.save_txt:
222
206
  self.save_one_txt(
223
- predn,
207
+ predn_scaled,
224
208
  self.args.save_conf,
225
209
  pbatch["ori_shape"],
226
- self.save_dir / "labels" / f"{Path(batch['im_file'][si]).stem}.txt",
210
+ self.save_dir / "labels" / f"{Path(pbatch['im_file']).stem}.txt",
227
211
  )
228
212
 
229
- def finalize_metrics(self, *args, **kwargs):
230
- """
231
- Set final values for metrics speed and confusion matrix.
232
-
233
- Args:
234
- *args (Any): Variable length argument list.
235
- **kwargs (Any): Arbitrary keyword arguments.
236
- """
213
+ def finalize_metrics(self) -> None:
214
+ """Set final values for metrics speed and confusion matrix."""
215
+ if self.args.plots:
216
+ for normalize in True, False:
217
+ self.confusion_matrix.plot(save_dir=self.save_dir, normalize=normalize, on_plot=self.on_plot)
237
218
  self.metrics.speed = self.speed
238
219
  self.metrics.confusion_matrix = self.confusion_matrix
239
-
240
- def get_stats(self):
241
- """
242
- Calculate and return metrics statistics.
220
+ self.metrics.save_dir = self.save_dir
221
+
222
+ def gather_stats(self) -> None:
223
+ """Gather stats from all GPUs."""
224
+ if RANK == 0:
225
+ gathered_stats = [None] * dist.get_world_size()
226
+ dist.gather_object(self.metrics.stats, gathered_stats, dst=0)
227
+ merged_stats = {key: [] for key in self.metrics.stats.keys()}
228
+ for stats_dict in gathered_stats:
229
+ for key in merged_stats.keys():
230
+ merged_stats[key].extend(stats_dict[key])
231
+ gathered_jdict = [None] * dist.get_world_size()
232
+ dist.gather_object(self.jdict, gathered_jdict, dst=0)
233
+ self.jdict = []
234
+ for jdict in gathered_jdict:
235
+ self.jdict.extend(jdict)
236
+ self.metrics.stats = merged_stats
237
+ self.seen = len(self.dataloader.dataset) # total image count from dataset
238
+ elif RANK > 0:
239
+ dist.gather_object(self.metrics.stats, None, dst=0)
240
+ dist.gather_object(self.jdict, None, dst=0)
241
+ self.jdict = []
242
+ self.metrics.clear_stats()
243
+
244
+ def get_stats(self) -> dict[str, Any]:
245
+ """Calculate and return metrics statistics.
243
246
 
244
247
  Returns:
245
- (dict): Dictionary containing metrics results.
248
+ (dict[str, Any]): Dictionary containing metrics results.
246
249
  """
247
- stats = {k: torch.cat(v, 0).cpu().numpy() for k, v in self.stats.items()} # to numpy
248
- self.nt_per_class = np.bincount(stats["target_cls"].astype(int), minlength=self.nc)
249
- self.nt_per_image = np.bincount(stats["target_img"].astype(int), minlength=self.nc)
250
- stats.pop("target_img", None)
251
- if len(stats):
252
- self.metrics.process(**stats, on_plot=self.on_plot)
250
+ self.metrics.process(save_dir=self.save_dir, plot=self.args.plots, on_plot=self.on_plot)
251
+ self.metrics.clear_stats()
253
252
  return self.metrics.results_dict
254
253
 
255
- def print_results(self):
254
+ def print_results(self) -> None:
256
255
  """Print training/validation set metrics per class."""
257
256
  pf = "%22s" + "%11i" * 2 + "%11.3g" * len(self.metrics.keys) # print format
258
- LOGGER.info(pf % ("all", self.seen, self.nt_per_class.sum(), *self.metrics.mean_results()))
259
- if self.nt_per_class.sum() == 0:
257
+ LOGGER.info(pf % ("all", self.seen, self.metrics.nt_per_class.sum(), *self.metrics.mean_results()))
258
+ if self.metrics.nt_per_class.sum() == 0:
260
259
  LOGGER.warning(f"no labels found in {self.args.task} set, can not compute metrics without labels")
261
260
 
262
261
  # Print results per class
263
- if self.args.verbose and not self.training and self.nc > 1 and len(self.stats):
262
+ if self.args.verbose and not self.training and self.nc > 1 and len(self.metrics.stats):
264
263
  for i, c in enumerate(self.metrics.ap_class_index):
265
264
  LOGGER.info(
266
- pf % (self.names[c], self.nt_per_image[c], self.nt_per_class[c], *self.metrics.class_result(i))
267
- )
268
-
269
- if self.args.plots:
270
- for normalize in True, False:
271
- self.confusion_matrix.plot(
272
- save_dir=self.save_dir, names=self.names.values(), normalize=normalize, on_plot=self.on_plot
265
+ pf
266
+ % (
267
+ self.names[c],
268
+ self.metrics.nt_per_image[c],
269
+ self.metrics.nt_per_class[c],
270
+ *self.metrics.class_result(i),
271
+ )
273
272
  )
274
273
 
275
- def _process_batch(self, detections, gt_bboxes, gt_cls):
276
- """
277
- Return correct prediction matrix.
274
+ def _process_batch(self, preds: dict[str, torch.Tensor], batch: dict[str, Any]) -> dict[str, np.ndarray]:
275
+ """Return correct prediction matrix.
278
276
 
279
277
  Args:
280
- detections (torch.Tensor): Tensor of shape (N, 6) representing detections where each detection is
281
- (x1, y1, x2, y2, conf, class).
282
- gt_bboxes (torch.Tensor): Tensor of shape (M, 4) representing ground-truth bounding box coordinates. Each
283
- bounding box is of the format: (x1, y1, x2, y2).
284
- gt_cls (torch.Tensor): Tensor of shape (M,) representing target class indices.
278
+ preds (dict[str, torch.Tensor]): Dictionary containing prediction data with 'bboxes' and 'cls' keys.
279
+ batch (dict[str, Any]): Batch dictionary containing ground truth data with 'bboxes' and 'cls' keys.
285
280
 
286
281
  Returns:
287
- (torch.Tensor): Correct prediction matrix of shape (N, 10) for 10 IoU levels.
282
+ (dict[str, np.ndarray]): Dictionary containing 'tp' key with correct prediction matrix of shape (N, 10) for
283
+ 10 IoU levels.
288
284
  """
289
- iou = box_iou(gt_bboxes, detections[:, :4])
290
- return self.match_predictions(detections[:, 5], gt_cls, iou)
285
+ if batch["cls"].shape[0] == 0 or preds["cls"].shape[0] == 0:
286
+ return {"tp": np.zeros((preds["cls"].shape[0], self.niou), dtype=bool)}
287
+ iou = box_iou(batch["bboxes"], preds["bboxes"])
288
+ return {"tp": self.match_predictions(preds["cls"], batch["cls"], iou).cpu().numpy()}
291
289
 
292
- def build_dataset(self, img_path, mode="val", batch=None):
293
- """
294
- Build YOLO Dataset.
290
+ def build_dataset(self, img_path: str, mode: str = "val", batch: int | None = None) -> torch.utils.data.Dataset:
291
+ """Build YOLO Dataset.
295
292
 
296
293
  Args:
297
294
  img_path (str): Path to the folder containing images.
@@ -303,9 +300,8 @@ class DetectionValidator(BaseValidator):
303
300
  """
304
301
  return build_yolo_dataset(self.args, img_path, batch, self.data, mode=mode, stride=self.stride)
305
302
 
306
- def get_dataloader(self, dataset_path, batch_size):
307
- """
308
- Construct and return dataloader.
303
+ def get_dataloader(self, dataset_path: str, batch_size: int) -> torch.utils.data.DataLoader:
304
+ """Construct and return dataloader.
309
305
 
310
306
  Args:
311
307
  dataset_path (str): Path to the dataset.
@@ -315,53 +311,66 @@ class DetectionValidator(BaseValidator):
315
311
  (torch.utils.data.DataLoader): Dataloader for validation.
316
312
  """
317
313
  dataset = self.build_dataset(dataset_path, batch=batch_size, mode="val")
318
- return build_dataloader(dataset, batch_size, self.args.workers, shuffle=False, rank=-1) # return dataloader
314
+ return build_dataloader(
315
+ dataset,
316
+ batch_size,
317
+ self.args.workers,
318
+ shuffle=False,
319
+ rank=-1,
320
+ drop_last=self.args.compile,
321
+ pin_memory=self.training,
322
+ )
319
323
 
320
- def plot_val_samples(self, batch, ni):
321
- """
322
- Plot validation image samples.
324
+ def plot_val_samples(self, batch: dict[str, Any], ni: int) -> None:
325
+ """Plot validation image samples.
323
326
 
324
327
  Args:
325
- batch (dict): Batch containing images and annotations.
328
+ batch (dict[str, Any]): Batch containing images and annotations.
326
329
  ni (int): Batch index.
327
330
  """
328
331
  plot_images(
329
- batch["img"],
330
- batch["batch_idx"],
331
- batch["cls"].squeeze(-1),
332
- batch["bboxes"],
332
+ labels=batch,
333
333
  paths=batch["im_file"],
334
334
  fname=self.save_dir / f"val_batch{ni}_labels.jpg",
335
335
  names=self.names,
336
336
  on_plot=self.on_plot,
337
337
  )
338
338
 
339
- def plot_predictions(self, batch, preds, ni):
340
- """
341
- Plot predicted bounding boxes on input images and save the result.
339
+ def plot_predictions(
340
+ self, batch: dict[str, Any], preds: list[dict[str, torch.Tensor]], ni: int, max_det: int | None = None
341
+ ) -> None:
342
+ """Plot predicted bounding boxes on input images and save the result.
342
343
 
343
344
  Args:
344
- batch (dict): Batch containing images and annotations.
345
- preds (List[torch.Tensor]): List of predictions from the model.
345
+ batch (dict[str, Any]): Batch containing images and annotations.
346
+ preds (list[dict[str, torch.Tensor]]): List of predictions from the model.
346
347
  ni (int): Batch index.
347
- """
348
+ max_det (Optional[int]): Maximum number of detections to plot.
349
+ """
350
+ # TODO: optimize this
351
+ for i, pred in enumerate(preds):
352
+ pred["batch_idx"] = torch.ones_like(pred["conf"]) * i # add batch index to predictions
353
+ keys = preds[0].keys()
354
+ max_det = max_det or self.args.max_det
355
+ batched_preds = {k: torch.cat([x[k][:max_det] for x in preds], dim=0) for k in keys}
356
+ # TODO: fix this
357
+ batched_preds["bboxes"][:, :4] = ops.xyxy2xywh(batched_preds["bboxes"][:, :4]) # convert to xywh format
348
358
  plot_images(
349
- batch["img"],
350
- *output_to_target(preds, max_det=self.args.max_det),
359
+ images=batch["img"],
360
+ labels=batched_preds,
351
361
  paths=batch["im_file"],
352
362
  fname=self.save_dir / f"val_batch{ni}_pred.jpg",
353
363
  names=self.names,
354
364
  on_plot=self.on_plot,
355
365
  ) # pred
356
366
 
357
- def save_one_txt(self, predn, save_conf, shape, file):
358
- """
359
- Save YOLO detections to a txt file in normalized coordinates in a specific format.
367
+ def save_one_txt(self, predn: dict[str, torch.Tensor], save_conf: bool, shape: tuple[int, int], file: Path) -> None:
368
+ """Save YOLO detections to a txt file in normalized coordinates in a specific format.
360
369
 
361
370
  Args:
362
- predn (torch.Tensor): Predictions in the format (x1, y1, x2, y2, conf, class).
371
+ predn (dict[str, torch.Tensor]): Dictionary containing predictions with keys 'bboxes', 'conf', and 'cls'.
363
372
  save_conf (bool): Whether to save confidence scores.
364
- shape (tuple): Shape of the original image.
373
+ shape (tuple[int, int]): Shape of the original image (height, width).
365
374
  file (Path): File path to save the detections.
366
375
  """
367
376
  from ultralytics.engine.results import Results
@@ -370,82 +379,129 @@ class DetectionValidator(BaseValidator):
370
379
  np.zeros((shape[0], shape[1]), dtype=np.uint8),
371
380
  path=None,
372
381
  names=self.names,
373
- boxes=predn[:, :6],
382
+ boxes=torch.cat([predn["bboxes"], predn["conf"].unsqueeze(-1), predn["cls"].unsqueeze(-1)], dim=1),
374
383
  ).save_txt(file, save_conf=save_conf)
375
384
 
376
- def pred_to_json(self, predn, filename):
377
- """
378
- Serialize YOLO predictions to COCO json format.
385
+ def pred_to_json(self, predn: dict[str, torch.Tensor], pbatch: dict[str, Any]) -> None:
386
+ """Serialize YOLO predictions to COCO json format.
379
387
 
380
388
  Args:
381
- predn (torch.Tensor): Predictions in the format (x1, y1, x2, y2, conf, class).
382
- filename (str): Image filename.
383
- """
384
- stem = Path(filename).stem
389
+ predn (dict[str, torch.Tensor]): Predictions dictionary containing 'bboxes', 'conf', and 'cls' keys with
390
+ bounding box coordinates, confidence scores, and class predictions.
391
+ pbatch (dict[str, Any]): Batch dictionary containing 'imgsz', 'ori_shape', 'ratio_pad', and 'im_file'.
392
+
393
+ Examples:
394
+ >>> result = {
395
+ ... "image_id": 42,
396
+ ... "file_name": "42.jpg",
397
+ ... "category_id": 18,
398
+ ... "bbox": [258.15, 41.29, 348.26, 243.78],
399
+ ... "score": 0.236,
400
+ ... }
401
+ """
402
+ path = Path(pbatch["im_file"])
403
+ stem = path.stem
385
404
  image_id = int(stem) if stem.isnumeric() else stem
386
- box = ops.xyxy2xywh(predn[:, :4]) # xywh
405
+ box = ops.xyxy2xywh(predn["bboxes"]) # xywh
387
406
  box[:, :2] -= box[:, 2:] / 2 # xy center to top-left corner
388
- for p, b in zip(predn.tolist(), box.tolist()):
407
+ for b, s, c in zip(box.tolist(), predn["conf"].tolist(), predn["cls"].tolist()):
389
408
  self.jdict.append(
390
409
  {
391
410
  "image_id": image_id,
392
- "category_id": self.class_map[int(p[5])],
411
+ "file_name": path.name,
412
+ "category_id": self.class_map[int(c)],
393
413
  "bbox": [round(x, 3) for x in b],
394
- "score": round(p[4], 5),
414
+ "score": round(s, 5),
395
415
  }
396
416
  )
397
417
 
398
- def eval_json(self, stats):
399
- """
400
- Evaluate YOLO output in JSON format and return performance statistics.
418
+ def scale_preds(self, predn: dict[str, torch.Tensor], pbatch: dict[str, Any]) -> dict[str, torch.Tensor]:
419
+ """Scales predictions to the original image size."""
420
+ return {
421
+ **predn,
422
+ "bboxes": ops.scale_boxes(
423
+ pbatch["imgsz"],
424
+ predn["bboxes"].clone(),
425
+ pbatch["ori_shape"],
426
+ ratio_pad=pbatch["ratio_pad"],
427
+ ),
428
+ }
429
+
430
+ def eval_json(self, stats: dict[str, Any]) -> dict[str, Any]:
431
+ """Evaluate YOLO output in JSON format and return performance statistics.
432
+
433
+ Args:
434
+ stats (dict[str, Any]): Current statistics dictionary.
435
+
436
+ Returns:
437
+ (dict[str, Any]): Updated statistics dictionary with COCO/LVIS evaluation results.
438
+ """
439
+ pred_json = self.save_dir / "predictions.json" # predictions
440
+ anno_json = (
441
+ self.data["path"]
442
+ / "annotations"
443
+ / ("instances_val2017.json" if self.is_coco else f"lvis_v1_{self.args.split}.json")
444
+ ) # annotations
445
+ return self.coco_evaluate(stats, pred_json, anno_json)
446
+
447
+ def coco_evaluate(
448
+ self,
449
+ stats: dict[str, Any],
450
+ pred_json: str,
451
+ anno_json: str,
452
+ iou_types: str | list[str] = "bbox",
453
+ suffix: str | list[str] = "Box",
454
+ ) -> dict[str, Any]:
455
+ """Evaluate COCO/LVIS metrics using faster-coco-eval library.
456
+
457
+ Performs evaluation using the faster-coco-eval library to compute mAP metrics for object detection. Updates the
458
+ provided stats dictionary with computed metrics including mAP50, mAP50-95, and LVIS-specific metrics if
459
+ applicable.
401
460
 
402
461
  Args:
403
- stats (dict): Current statistics dictionary.
462
+ stats (dict[str, Any]): Dictionary to store computed metrics and statistics.
463
+ pred_json (str | Path]): Path to JSON file containing predictions in COCO format.
464
+ anno_json (str | Path]): Path to JSON file containing ground truth annotations in COCO format.
465
+ iou_types (str | list[str]]): IoU type(s) for evaluation. Can be single string or list of strings. Common
466
+ values include "bbox", "segm", "keypoints". Defaults to "bbox".
467
+ suffix (str | list[str]]): Suffix to append to metric names in stats dictionary. Should correspond to
468
+ iou_types if multiple types provided. Defaults to "Box".
404
469
 
405
470
  Returns:
406
- (dict): Updated statistics dictionary with COCO/LVIS evaluation results.
471
+ (dict[str, Any]): Updated stats dictionary containing the computed COCO/LVIS evaluation metrics.
407
472
  """
408
473
  if self.args.save_json and (self.is_coco or self.is_lvis) and len(self.jdict):
409
- pred_json = self.save_dir / "predictions.json" # predictions
410
- anno_json = (
411
- self.data["path"]
412
- / "annotations"
413
- / ("instances_val2017.json" if self.is_coco else f"lvis_v1_{self.args.split}.json")
414
- ) # annotations
415
- pkg = "pycocotools" if self.is_coco else "lvis"
416
- LOGGER.info(f"\nEvaluating {pkg} mAP using {pred_json} and {anno_json}...")
417
- try: # https://github.com/cocodataset/cocoapi/blob/master/PythonAPI/pycocoEvalDemo.ipynb
474
+ LOGGER.info(f"\nEvaluating faster-coco-eval mAP using {pred_json} and {anno_json}...")
475
+ try:
418
476
  for x in pred_json, anno_json:
419
477
  assert x.is_file(), f"{x} file not found"
420
- check_requirements("pycocotools>=2.0.6" if self.is_coco else "lvis>=0.5.3")
421
- if self.is_coco:
422
- from pycocotools.coco import COCO # noqa
423
- from pycocotools.cocoeval import COCOeval # noqa
424
-
425
- anno = COCO(str(anno_json)) # init annotations api
426
- pred = anno.loadRes(str(pred_json)) # init predictions api (must pass string, not Path)
427
- val = COCOeval(anno, pred, "bbox")
428
- else:
429
- from lvis import LVIS, LVISEval
430
-
431
- anno = LVIS(str(anno_json)) # init annotations api
432
- pred = anno._load_json(str(pred_json)) # init predictions api (must pass string, not Path)
433
- val = LVISEval(anno, pred, "bbox")
434
- val.params.imgIds = [int(Path(x).stem) for x in self.dataloader.dataset.im_files] # images to eval
435
- val.evaluate()
436
- val.accumulate()
437
- val.summarize()
438
- if self.is_lvis:
439
- val.print_results() # explicitly call print_results
440
- # update mAP50-95 and mAP50
441
- stats[self.metrics.keys[-1]], stats[self.metrics.keys[-2]] = (
442
- val.stats[:2] if self.is_coco else [val.results["AP"], val.results["AP50"]]
443
- )
478
+ iou_types = [iou_types] if isinstance(iou_types, str) else iou_types
479
+ suffix = [suffix] if isinstance(suffix, str) else suffix
480
+ check_requirements("faster-coco-eval>=1.6.7")
481
+ from faster_coco_eval import COCO, COCOeval_faster
482
+
483
+ anno = COCO(anno_json)
484
+ pred = anno.loadRes(pred_json)
485
+ for i, iou_type in enumerate(iou_types):
486
+ val = COCOeval_faster(
487
+ anno, pred, iouType=iou_type, lvis_style=self.is_lvis, print_function=LOGGER.info
488
+ )
489
+ val.params.imgIds = [int(Path(x).stem) for x in self.dataloader.dataset.im_files] # images to eval
490
+ val.evaluate()
491
+ val.accumulate()
492
+ val.summarize()
493
+
494
+ # update mAP50-95 and mAP50
495
+ stats[f"metrics/mAP50({suffix[i][0]})"] = val.stats_as_dict["AP_50"]
496
+ stats[f"metrics/mAP50-95({suffix[i][0]})"] = val.stats_as_dict["AP_all"]
497
+
498
+ if self.is_lvis:
499
+ stats[f"metrics/APr({suffix[i][0]})"] = val.stats_as_dict["APr"]
500
+ stats[f"metrics/APc({suffix[i][0]})"] = val.stats_as_dict["APc"]
501
+ stats[f"metrics/APf({suffix[i][0]})"] = val.stats_as_dict["APf"]
502
+
444
503
  if self.is_lvis:
445
- stats["metrics/APr(B)"] = val.results["APr"]
446
- stats["metrics/APc(B)"] = val.results["APc"]
447
- stats["metrics/APf(B)"] = val.results["APf"]
448
- stats["fitness"] = val.results["AP"]
504
+ stats["fitness"] = stats["metrics/mAP50-95(B)"] # always use box mAP50-95 for fitness
449
505
  except Exception as e:
450
- LOGGER.warning(f"{pkg} unable to run: {e}")
506
+ LOGGER.warning(f"faster-coco-eval unable to run: {e}")
451
507
  return stats